Halo semuanya. Kali ini kami terus menertawakan panggilan metode biasa. Saya menyarankan agar Anda membiasakan diri dengan pemanggilan metode dengan parameter tanpa melewati parameter. Kami juga mencoba mengubah tipe referensi menjadi angka - alamatnya, tanpa menggunakan pointer dan kode yang tidak aman.
Penafian
Sebelum memulai cerita, saya sangat menyarankan Anda membaca
posting sebelumnya
tentang StructLayout , karena hal-hal yang disepakati di sana tidak akan terulang di sini.
Saya juga ingin memperingatkan bahwa artikel ini tidak mengandung bahan yang harus digunakan dalam proyek nyata.
Beberapa informasi awal
Sebelum kita mulai, mari kita ingat bagaimana kode C # dikonversi.
Mari kita lihat contoh sederhana ini. Biarkan saya mengingatkan Anda bahwa untuk bersenang-senang dengan StructLayout, saya hanya menggunakan metode virtual.
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); } }
Kode ini tidak mengandung sesuatu yang rumit, tetapi instruksi yang dihasilkan oleh JiT berisi beberapa poin kunci. Saya mengusulkan untuk mengurai hanya sebagian kecil dari kode yang dihasilkan.
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]
Dalam contoh kecil ini, Anda dapat mengamati panggilan cepat - kesepakatan tentang melewati parameter melalui register (dua parameter pertama dari kiri ke kanan di register ecx dan edx), dan parameter yang tersisa dilewatkan dari kanan ke kiri pada stack. Parameter pertama (implisit) adalah alamat instance kelas di mana metode ini dipanggil. Itu dilewatkan sebagai parameter implisit pertama untuk setiap metode instance. Parameter kedua adalah variabel lokal bertipe int (dalam kasus kami).
Jadi, di baris
pertama kita melihat variabel lokal 5, tidak ada yang menarik di sini.
Di baris
kedua , kami menyalin alamat instance Helper ke register ecx. Ini adalah alamat dari tabel metode itu sendiri.
Baris ketiga berisi menyalin variabel lokal 5 ke register edx
Baris keempat menyalin alamat tabel metode ke register eax
Baris kelima berisi
pergeseran register eax dari 40 byte memuat nilai dari memori pada alamat 40 byte lebih besar dari alamat tabel metode: alamat awal metode dalam tabel metode. (Tabel metode berisi berbagai informasi yang disimpan sebelumnya. Informasi tersebut, misalnya, termasuk alamat tabel metode kelas dasar, alamat EEClass, berbagai bendera, termasuk bendera pengumpul sampah, dan sebagainya). Dengan demikian, alamat metode pertama dari tabel metode sekarang disimpan dalam register eax.
Pada baris
keenam , metode ini disebut pada offset 16 dari awal, yaitu kelima pada tabel metode. Mengapa hanya metode kami yang kelima? Saya mengingatkan Anda bahwa objek memiliki 4 metode virtual (ToString, Equals, GetHashCode, dan Finalisasi), yang, karenanya, akan ada di semua kelas.
Mari kita lanjutkan berlatih
Sudah waktunya untuk memulai demonstrasi kecil. Saya mengusulkan kosong di sini (sangat mirip dengan kosong dari artikel sebelumnya).
[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) { } }
Dan isian metode Utama berikut:
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(); } }
Seperti yang Anda bayangkan, dari pengalaman artikel sebelumnya, metode U1ess (int j) tipe Test1 akan dipanggil.
Tapi apa yang akan disimpulkan? Saya percaya, seorang pembaca yang penuh perhatian telah menjawab pertanyaan ini. 55555 ditampilkan di konsol.
Tapi mari kita lihat bagian-bagian dari kode yang dihasilkan.
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]
Saya pikir Anda mengenali pola panggilan metode virtual, itu dimulai setelah L00cc: nop. Seperti yang bisa kita lihat, di ecx alamat instance yang disebut metode diharapkan ditulis. Tapi sejak itu Jika kita memanggil metode seperti Test2, yang tidak memiliki parameter, tidak ada yang ditulis ke edx. Namun, sebelum itu, metode dipanggil, yang melewati parameter melalui register edx, masing-masing, dan nilainya tetap di dalamnya. dan kita bisa mengamatinya di jendela output.
Ada nuansa menarik lainnya. Saya secara khusus menggunakan tipe yang bermakna. Saya sarankan mencoba mengganti tipe parameter dari metode Foo tipe Stub dengan jenis referensi apa pun, misalnya string. Tetapi tipe parameter dari metode Useless tidak berubah. Di bawah ini Anda dapat melihat hasilnya di komputer saya dengan beberapa elemen klarifikasi: WinDBG dan Kalkulator :)
Gambar yang dapat diklikJendela output menampilkan alamat tipe referensi dalam sistem angka desimal
Ringkasan
Mereka menyegarkan pengetahuan tentang metode panggilan menggunakan konvensi panggilan cepat dan segera menggunakan register edx yang bagus untuk melewatkan metode parameter 2 sekaligus. Mereka juga tidak peduli tentang semua jenis dan mengingat bahwa semua hanya ada byte yang dengan mudah menerima alamat objek tanpa menggunakan pointer dan kode yang tidak aman. Lebih lanjut saya berencana untuk menggunakan alamat yang diterima untuk tujuan yang bahkan lebih tidak berlaku!
Terima kasih atas perhatian anda!
Kode PS C # dapat ditemukan
di sini