-- 作者:admin
-- 发布时间:11/9/2004 2:25:00 AM
-- C#快餐-2
发信人: walts (休息一会), 信区: DotNET 标 题: C#快餐-2 发信站: BBS 水木清华站 (Thu Jul 26 01:10:18 2001) Lesson 2. Hacker's introduction to MSIL. First of all I am not a hacker. I may even get a PhD in Physics in a few weeks. But what can I do? I really wanted to learn Microsoft Intermediate Language (MSIL) programming and the documentation, putting it politely, is a bit limited. So, I took this tool called ildasm, and started disassembling my C# code. Whatever is said in this chapter is my empirical knowledge. That is why I called it hacking. So why do we want to learn MSIL programming? First of all, because it will look cool on your resume. Second, because we want to understand a little better how the Common Language Runtime works. Let me ask you a question: Where does term boxing come from. Is there such a keyword in C#? What about VB.Net? Why the hell do we call value to object conversion boxing? Because this is what MSIL does. Look at your favorite *.Net program which has value to object conversion and you will find a keyword box. So, let's study MSIL.The simplest MSIL program is the one which does not do anything and has no data. .assembly hello{} .class hello{ .method static public void main() il managed{ .entrypoint ret } } MSIL supports OO programming, so we can have a class hello with public method main. The entrypoint of the program needs to be manually specified. So, it doesn't really matter whether the method is called main or Dad. The only thing that matters here is that .entrypoint is specified inside a method. MSIL programs are compiled with ilasm compiler. Since we are writing a managed assembly code, we have to make sure that no variables are allocated in memory when the program goes out of scope. Here is a more complicated program that again does not do anything but has some data. .assembly hello{} .class hello{ .method static public void main() il managed{ .entrypoint .local( string V_0) ldstr "hi there" stloc.0 ret } } This program has a statement .local(string V_0),which declares a single local variable of type string. This declaration allows the compiler to allocate "hi there" on the local stack. . Because of that, stloc.0 can find "hi there" and pop it from the stack. Since you are working in a managed environment, you cannot leave data in memory before quitting the program. Memory leaks a re not allowed. So every single variable which you have allocated in memory has to be popped from the registers. Every program also needs to start with a declaration of the assembly it belongs to. In our case, we choose the assembly name to be the same as the class name. Let's see a bit more complicated example which still doesn't do anything useful. //allocating and deallocating multiple variables on the stack .assembly hello{} .class hello{ .method static public void main() il managed{ .maxstack 2 .entrypoint .local( string V_0, string V_1) //we have two local variables now ldstr "hi there" //push this string on stack ldstr "bye here" //push second string on stack stloc.0 //pop first string from the stack and store it in the local variab le 0. //you do not need to worry about dealocating local variables - it is done by the runtime. stloc.0 //pop the second string from the stack and store it in the same loca l variable ("hi there" is overwritten) ret } } There is a new element in this program: .maxstack declaration. We use . maxstack to declare the maximal number of variables we plan to have on stack at any given time. The default value is 1, so we can always omit this declaration when we use a single register. Here is a hello world program written in MSIL //compile with ilasm .assembly hello {} .method static public void main() il managed { .entrypoint ldstr "Hello MS IL!" call void [mscorlib]System.Console::WriteLine(class System.String) ret } All MSIL directives start with a period. Recall that all C# code is contained within a class. This translates on MSIL level at having all code inside assembly .hello .entrypoint and ret are equivalent to main(){ and } lsdtr loads string into a register and call to WriteLine picks it up from there. WriteLine does all the clean up before it displays "hello msil", we do not need to pop anything from the stack. We will get a runtime error if we do. Here is a program which illustrates how to store data into local varibles and how to overwrite them .assembly hello{} .assembly extern mscorlib {} .class hello{ .method static public void main() il managed{ .maxstack 2 .entrypoint .locals(string V_0, string V_1) //we have two local variables now ldstr "hi there" //push this string on stack ldstr "bye here" //push second string on stack stloc.0 //pop first string from the stack and store it in the local variable 0. //you do not need to worry about dealocating local variables - it is done by the runtime. stloc.0 //pop the second string from the stack and store it in the same local variable ("bye there" is overwritten) ldloc.0 //push the remaining local variable containing "bye there" into the register call void [mscorlib]System.Console::WriteLine(string) ret } } It is always a lot of fun to manipulate integers with Assembly language. //print number 2 .assembly hello {} .method public static void Main() il managed { .entrypoint .locals(int32 V_0) ldc.i4.2 stloc.0 ldloc.0 call void [mscorlib]System.Console::WriteLine(int32) ret } The next program adds two integers //add two numbers 1 and 3 .assembly hello {} .assembly extern mscorlib {} .class public hello { .method static public void main() { .entrypoint .maxstack 2 .locals(int32 V_0, int32 V_1) //declare two local variables ldc.i4.1 //put number 1 on the stack ldc.i4.3 //put number 3 on the stack stloc.0 //pop 1 from the stack and store it in the local variable ldloc.0 //push local variable with value 1 on the stack add //add takes care of the second value on the local stack //you should not try to deallocoate memory there. it is done by add //add works with the first variable on the stack and the value call void [mscorlib]System.Console::WriteLine(int32) ret } } It is sometimes very useful to have an explicit conversion between a value and an object. This is done with box directive. The example bellow outputs an object value. So, we need to explicitly convert the data inside the register to a boxed data. .assembly hello{} .method public static void Main() il managed { .entrypoint ldc.i4.s 100 //put 100 on stack box [mscorlib]System.Int32 //convert it to on object in place call void [mscorlib]System.Console::WriteLine(object) //print the value of t he object ret } The example above was a bit contrived to keep it simple . Here is another example .assembly hello{} .method public static void Main() il managed { .entrypoint .maxstack 2 .locals (int32 V_0) ldstr "Please enter your age:" call void [mscorlib]System.Console::WriteLine(string) call string [mscorlib]System.Console::ReadLine() call int32 [mscorlib]System.Int32::Parse(string) stloc.0 ldstr "You are {0} years old " ldloc.0 box [mscorlib]System.Int32 //convert int32 to an object on the stack call void [mscorlib]System.Console::WriteLine(string, object) ret } MSIL does not have System.Consol::WriteLine(sting,int32 ) method, therefore int32 needs to be converted to another type to allow output to the console. Exercises: Write a program that subtracts two integers. Read an MSIL article by John Robbins at MSDN magazine. Use ildasm to disassemble your .Net programs. Does compiling with /o+ optimization option change MSIL code? Why? Find a mistake in this page. Write a really cool program and send it to me. Do not forget to put a lot of comments. Do you think there is an important topic that I should have covered in this lesson? Write to me about it. -- A great poem is a fountain forever overflowing with the waters of wisdom and delight. —— Shelley ※ 来源:·BBS 水木清华站 smth.org·[FROM: 166.111.142.118] 上一篇 返回上一页 回到目录 回到页首 下一篇
|