Bonjour à tous. Cette fois, nous continuons de rire de l'appel à la méthode normale. Je vous suggère de vous familiariser avec l'appel de méthode avec des paramètres sans passer de paramètres. Nous essayons également de convertir le type de référence en nombre - son adresse, sans utiliser de pointeurs et de code dangereux.
Clause de non-responsabilité
Avant de commencer l'histoire, je vous recommande fortement de lire le
post précédent
sur StructLayout , car les choses convenues ne seront pas répétées ici.
Je tiens également à avertir que cet article ne contient pas de matériel qui devrait être utilisé dans des projets réels.
Quelques informations initiales
Avant de commencer, rappelons comment le code C # est converti.
Jetons un coup d'œil à un exemple simple. Permettez-moi de vous rappeler que pour m'amuser avec StructLayout, j'utilise uniquement des méthodes virtuelles.
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); } }
Ce code ne contient rien de compliqué, mais les instructions générées par JiT contiennent plusieurs points clés. Je propose d'analyser uniquement un petit fragment du code généré.
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]
Dans ce petit exemple, vous pouvez observer fastcall - un accord sur le passage des paramètres dans les registres (les deux premiers paramètres sont de gauche à droite dans les registres ecx et edx), et les paramètres restants sont passés de droite à gauche sur la pile. Le premier paramètre (implicite) est l'adresse de l'instance de classe sur laquelle la méthode est appelée. Il est transmis comme premier paramètre implicite pour chaque méthode d'instance. Le deuxième paramètre est une variable locale de type int (dans notre cas).
Donc, dans la
première ligne, nous voyons la variable locale 5, il n'y a rien d'intéressant ici.
Dans la
deuxième ligne, nous copions l'adresse de l'instance Helper dans le registre ecx. Il s'agit de l'adresse de la table de méthodes elle-même.
La troisième ligne contient la copie de la variable locale 5 dans le registre edx
La quatrième ligne copie l'adresse de la table des méthodes dans le registre eax
La cinquième ligne contient un
décalage de registre eax de 40 octets chargeant une valeur de la mémoire à une adresse 40 octets supérieure à l'adresse de la table des méthodes: l'adresse du début des méthodes dans la table des méthodes. (La table de méthode contient diverses informations qui sont stockées auparavant. Ces informations, par exemple, incluent l'adresse de la table de méthode de classe de base, l'adresse EEClass, divers indicateurs, y compris l'indicateur de garbage collector, etc.). Par conséquent, l'adresse de la première méthode de la table de méthodes est maintenant stockée dans le registre eax.
Dans la
sixième ligne, la méthode est appelée à l'offset 16 depuis le début, c'est-à-dire la cinquième dans le tableau des méthodes. Pourquoi notre seule méthode est-elle la cinquième? Je vous rappelle que l'objet a 4 méthodes virtuelles (ToString, Equals, GetHashCode et Finalize), qui, en conséquence, seront dans toutes les classes.
Passons à la pratique
Il est temps de commencer une petite démonstration. Je propose ici un tel blanc (très similaire au blanc de l'article précédent).
[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) { } }
Et le bourrage suivant de la méthode Main:
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(); } }
Comme vous pouvez l'imaginer, d'après l'expérience de l'article précédent, la méthode Useless (int j) de type Test1 sera appelée.
Mais qu'est-ce qui en sera déduit? Un lecteur attentif, je crois, a déjà répondu à cette question. 55555 s'affiche sur la console.
Mais regardons les fragments du code généré.
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]
Je pense que vous reconnaissez le modèle d'appel de méthode virtuelle, il commence après L00cc: nop. Comme nous pouvons le voir, dans ecx, l'adresse de l'instance sur laquelle la méthode est appelée devrait être écrite. Mais depuis Si nous appelons une méthode comme Test2, qui n'a pas de paramètres, rien n'est écrit dans edx. Cependant, avant cela, la méthode a été appelée, qui a transmis le paramètre via le registre edx, respectivement, et la valeur y est restée. et nous pouvons l'observer dans la fenêtre de sortie.
Il y a une autre nuance intéressante. J'ai utilisé spécifiquement un type significatif. Je suggère d'essayer de remplacer le type de paramètre de la méthode Foo de type Stub par n'importe quel type de référence, par exemple, une chaîne. Mais le type de paramètre de la méthode Useless n'est pas modifié. Ci-dessous, vous pouvez voir le résultat sur ma machine avec quelques éléments de clarification: WinDBG et calculatrice :)
Image cliquableLa fenêtre de sortie affiche l'adresse du type de référence dans le système de nombres décimaux
Résumé
Ils ont rafraîchi la connaissance des méthodes d'appel à l'aide de la convention fastcall et ont immédiatement utilisé le merveilleux registre edx pour passer les méthodes du paramètre 2 à la fois. Ils ne se soucient pas non plus de tous les types et se souviennent que tous les octets ne reçoivent que facilement l'adresse de l'objet sans utiliser de pointeurs et de code dangereux. De plus, je prévois d'utiliser l'adresse reçue à des fins encore plus inapplicables!
Merci de votre attention!
Le code PS C # peut être trouvé
ici