Hello. This time, we continue to laugh at the normal method call. I propose to get acquainted with the method call with parameters without passing parameters. Also we will try to convert the reference type to a number - its address, without using pointers and unsafe code.
Disclaimer
Before proceeding with the story, I strongly recommend that you familiarize yourself with the previous
post about StructLayout , since things specified there will not be repeated here.
I would also like to warn that this article does not contain material that should be used in real projects.
Some initial information
Before we start practicing, let's remember how C # code is converted.
Let us examine a simple example. Let me remind you that in order to have fun with StructLayout, I use only virtual methods.
')
public class Helper { public virtual void Foo(int param) { } } public class Program { public void Main() { Helper helper = new Helper(); var param = 5; helper.Foo(param); } }
This code does not contain anything difficult, but the instructions generated by JiT contain several key points. I propose to parse only a small fragment of the generated code.
1: mov dword [ebp-0x8], 0x5 2: mov ecx, [ebp-0xc] 3: mov edx, [ebp-0x8] 4: mov eax, [ecx] 5: mov eax, [eax+0x28] 6: call dword [eax+0x10]
In this small example, you can observe fastcall - an agreement on the transfer of parameters through registers (the first two parameters from left to right in the ecx and edx registers), and the remaining parameters are passed from right to left in the stack. The first (implicit) parameter is the address of the instance of the class on which the method is called. It is passed as the first implicit parameter for each instance method. The second parameter is a local variable of type int (in our case).
So, in the
first line we see the local variable 5, there is nothing interesting here.
In the
second line, we copy the address of the Helper instance into the ecx register. This is the address of the method table itself.
The third line contains a copy of local variable 5 in the register edx
The fourth line copies the address of the method table to the eax register.
The fifth line contains the
shift of the eax register by 40 bytes and the loading of the value from the memory at the address 40 bytes greater than the address of the method table: to the address of the beginning of the methods in the method table. (The method table contains various information that is stored before. Such information, for example, includes the address of the base class method table, the EEClass address, various flags, including the garbage collector flag, and so on). Accordingly, the address of the first method from the method table is now stored in the eax register.
In the
sixth line, the method is called at offset 16 from the beginning, that is, the fifth in the method table. Why is our only method fifth? I remind you that object has 4 virtual methods (ToString, Equals, GetHashCode and Finalize), which all classes will have, respectively.
Go to practice
It's time to start a small demonstration. I suggest just such a blank (very similar to the blank from the previous article).
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public Test1 Test1; [FieldOffset(0)] public Test2 Test2; } public class Test1 { public virtual int Useless(int param) { Console.WriteLine(param); return param; } } public class Test2 { public virtual int Useless() { return 888; } } public class Stub { public void Foo(int stub) { } }
And the following filling of the Main method:
class Program { static void Main(string[] args) { Test2 fake = new CustomStructWithLayout { Test2 = new Test2(), Test1 = new Test1() }.Test2; Stub bar = new Stub(); int param = 55555; bar.Foo(param); fake.Useless(); Console.Read(); } }
As you might guess, from the experience of the previous article, the Useless (int j) method of type Test1 will be called.
But what will be displayed? The attentive reader, I believe, has already answered this question. "55555" is displayed on the console.
But let's still look at the generated code fragments.
mov ecx, [ebp-0x20] mov edx, [ebp-0x10] cmp [ecx], ecx call Stub.Foo(Int32) nop mov ecx, [ebp-0x1c] mov eax, [ecx] mov eax, [eax+0x28] call dword [eax+0x10]
I think you know the virtual method call pattern, it starts after L00cc: nop. As we can see, ecx is expected to write the address of the instance on which the method is called. But since we supposedly call a method of type Test2, which has no parameters, nothing is written to edx. However, before this method was called, to which the parameter was passed just through the edx register, respectively, the value in it remained. and we can see it in the output window.
There is another interesting nuance. I specifically used the meaningful type. I suggest trying to replace the parameter type of the Foo method of the Stub type with any reference type, for example, a string. But the parameter type of the method Useless does not change. Below you can see the result on my machine with some clarifying elements: WinDBG and Calculator :)
The picture is clickableThe output window displays the address of the reference type in decimal notation.
Total
They refreshed the knowledge of calling methods using the fastcall convention and immediately used the wonderful edx register to pass a parameter 2 methods at a time. They also spat on all types and remembering that everything is only bytes easily obtained the address of the object without using pointers and unsafe code. Further I plan to use the received address for even more inapplicable purposes!
Thanks for attention!
PS C # code can be found by
reference.