• Keine Ergebnisse gefunden

Erzeugung von Assemblercode durch Compiler

Prozessor die R¨ucksprungadresse auf den Stack (Code und Kommentar zu Zeilen 22 und 23).

EBP sichern Die gerufene Funktion selbst sichert nun zun¨achst den aktuellen Inhalt von EBP auf dem Stack (PUSH EBP)

ESP auf EBP kopieren Um den Stack ¨uber EBP bequem adressieren zu k¨onnen und ESP frei zu haben f¨ur weitere Stackreservierungen (z.B. durch PUSH) wird ESP auf EBP kopiert.

Platz f¨ur lokale Variable reservieren Durch Verkleinerung von ESP oder durch PUSH kann f¨ur ben¨otigte lokale Variable Platz auf dem Stack reserviert werden (Code und Kommentar zu Zeile 3).

Freigabe der lokalen Variablen Durch Zur¨ucksetzen des ESP auf den Wert vor der Reser-vierung wird vor Beendigung der Funktion der Speicherplatz der lokalen Variablen wieder freigegeben (Zeile 7). Der gerettete EBP-Inhalt muss jetzt Top of Stack (TOS) sein.

EBP wieder herstellen Durch POP EBP erh¨alt EBP wieder den Wert, den er vor dem Funk-tionsaufruf hatte. Erst dadurch sind geschachtelte FunkFunk-tionsaufrufe m¨oglich!

R¨ucksprung TOS ist jetzt die R¨ucksprungadresse, die durch RET (Return) vom Stack genom-men wird.

Parameter vom Stack entfernen Nun m¨ussen nur noch die ¨ubergebenen Parameter vom Stack entfernt werden. Dies wird bei standardm¨aßiger C-Kompilierung durch das rufende Programm ausgef¨uhrt.

Die Art der Parameter¨ubergabe und -entfernung l¨asst sich durch Compileroptionen steuern (s.Abschn.13.5.1) Wenn man beispielhaft annimmt, dass die Parameter und die lokalen Variablen 4 Byte groß sind, hat durch dieses Vorgehen der Stack w¨ahrend der Ausf¨uhrung einer Funktion folgenden Aufbau, der auch als sog. Stackframe bekannt ist. (Ein Stackframe in einem 16-Bit-Programm ist in Abschnitt 13.6.3gezeigt.)

Adresse Inhalt EBP + 20 . EBP + 16 .

EBP + 12 zweites Parameterwort EBP + 8 erstes Parameterwort EBP + 4 ucksprungadresse EBP geretteterEBP

EBP - 4 erstes Wort der lokalen Variablen EBP - 8 zweites Wort der lokalen Variablen EBP - 12 drittes Wort der lokalen Variablen EBP - 16 .

EBP - 20 .

13.4 Erzeugung von Assemblercode durch Compiler

Das folgende Beispiel zeigt ein C++-Programm, das mit einem 32-Bit-Compiler ¨ubersetzt wurde.

Zun¨achst der C++-Quellcode:

98 KAPITEL 13. DIE SCHNITTSTELLE ZWISCHEN ASSEMBLER UND C/C++

#include <stdio.h>

int Produkt(int Faktor1, int Faktor2) { int Ergebnis;

Ergebnis = Faktor1 * Faktor2;

return Ergebnis;

// Benutzung von Zeigern

p1 = &a; // p1 erh¨alt die Adresse von a

*p1 = 100; // a=100

p2 = p1; // p2 enth¨alt die gleiche Adresse wie p1 printf("p2 zeigt auf die Variable a=%i, *p2=%i \n",a,*p2);

b=25;

Der Compiler erzeugt aus diesem Quellcode folgenden Assemblercode:

--- C:\ASM\inlasm\codebsp1.cpp ---1: #include <stdio.h>

2:

3: int Produkt(int Faktor1, int Faktor2) {

00401020 55 push ebp

00401021 8B EC mov ebp,esp

00401023 51 push ecx

4: int Ergebnis;

5: Ergebnis = Faktor1 * Faktor2;

00401024 8B 45 08 mov eax,dword ptr [Faktor1]

00401027 0F AF 45 0C imul eax,dword ptr [Faktor2]

0040102B 89 45 FC mov dword ptr [Ergebnis],eax 6: return Ergebnis;

0040102E 8B 45 FC mov eax,dword ptr [Ergebnis]

7: }

00401031 8B E5 mov esp,ebp

00401033 5D pop ebp

00401034 C3 ret

8:

9:

10: main() {

00401035 55 push ebp

13.4. ERZEUGUNG VON ASSEMBLERCODE DURCH COMPILER 99

00401036 8B EC mov ebp,esp

00401038 83 EC 18 sub esp,18h

11: int a,b,c;

12: int *p1, *p2;

13: float f=15.0;

0040103B C7 45 F0 00 00 70 41 mov dword ptr [f],41700000h 14:

15: // Benutzung von Zeigern

16: p1 = &a; // p1 erh¨alt die Adresse von a

00401042 8D 45 FC lea eax,dword ptr [a]

00401045 89 45 EC mov dword ptr [p1],eax 17: *p1 = 100; // a=100

00401048 8B 4D EC mov ecx,dword ptr [p1]

0040104B C7 01 64 00 00 00 mov dword ptr [ecx],64h 18: p2 = p1; // p2 enth¨alt die gleiche Adresse wie p1 00401051 8B 55 EC mov edx,dword ptr [p1]

00401054 89 55 E8 mov dword ptr [p2],edx 19: printf("p2 zeigt auf die Variable a=%i, *p2=%i \n",a,*p2);

00401057 8B 45 E8 mov eax,dword ptr [p2]

0040105A 8B 08 mov ecx,dword ptr [eax]

0040105C 51 push ecx

0040105D 8B 55 FC mov edx,dword ptr [a]

00401060 52 push edx

00401061 68 30 5A 41 00 push offset ___xt_z(0x00415a30)+10Ch 00401066 E8 65 00 00 00 call printf(0x004010d0)

0040106B 83 C4 0C add esp,0Ch

20:

21: b=25;

0040106E C7 45 F8 19 00 00 00 mov dword ptr [b],19h 22: c=Produkt(a,b); // Funktionsaufruf

00401075 8B 45 F8 mov eax,dword ptr [b]

00401078 50 push eax

00401079 8B 4D FC mov ecx,dword ptr [a]

0040107C 51 push ecx

0040107D E8 7E FF FF FF call @ILT+0(?Produkt@@YAHHH@Z)(0x00401000)

00401082 83 C4 08 add esp,8

00401085 89 45 F4 mov dword ptr [c],eax 23: printf("%6i\n",c);

00401088 8B 55 F4 mov edx,dword ptr [c]

0040108B 52 push edx

0040108C 68 5C 5A 41 00 push offset ___xt_z(0x00415a5c)+138h 00401091 E8 3A 00 00 00 call printf(0x004010d0)

00401096 83 C4 08 add esp,8

24:

25: f=f/2; // Fliesskomma-Operationen

00401099 D9 45 F0 fld dword ptr [f]

0040109C D8 35 54 30 41 00 fdiv dword ptr [??_C@_08GNFC@printf?4c?$AA@(0x00413054)-4]

004010A2 D9 5D F0 fstp dword ptr [f]

26:

27: return 0;

004010A5 33 C0 xor eax,eax

28: }

004010A7 8B E5 mov esp,ebp

004010A9 5D pop ebp

100 KAPITEL 13. DIE SCHNITTSTELLE ZWISCHEN ASSEMBLER UND C/C++

004010AA C3 ret

--- Ende Quellcodedatei

---Zum Verst¨andnis dieses Assemblercodes die folgenden Kommentare. Sie beziehen sich auf die As-semblerbefehle, durch die der C++-Befehl in der genannten Zeile realisiert wird. Man sieht sehr deutlich, dass es sich um 32-Bit-Code handelt: Es werden 32-Bit-Register benutzt (EAX,EBX usw.), Zeiger sind einfache 32-Bit-Zahlen, Integer sind mit 32-Bit codiert, die Register ecx und eax werden f¨ur die Adressierung benutzt (nicht m¨oglich beim i8086).

Zeile 3 Aufbau des Stackframes mit ebp und esp als 32-Bit-Zeigerregister Reservierung von 32 Bit f¨ur die lokale Variable Ergebnis durch push ecx

Zeile 4,5 Multiplikation von Faktor1 und Faktor2, Resultat in Ergebnis abspeichern. Faktor1, Faktor2 und Ergebnis sind vom Typ Integer und werden als 32-Bit-Variablen auf dem Stack angelegt.

Zeile 6 R¨uckgabe des Ergebnisses in EAX

Zeile 7 Durch ”}”wird die Funktion beendet: Abbau Stackframe und RET-Befehl Zeile 10 – 13

”main“ wird wie jede andere Funktion ¨ubersetzt, Stackframe und Platz f¨ur lokale Variable auf dem Stack: drei Integer zu je 4 Byte, zwei Zeiger zu je 4 Byte, eine float-Var.

zu 4 Byte ergeben 24 Byte (18h), daher also: sub esp,18h; Initialisierung von f mit 32-Bit Zeile 16 Adresse (Offset) von a via EAX nach p1 kopieren.

Zeile 17 Der Wert 100 (64h) wird auf den Speicherplatz geschrieben, dessen Adresse in p1 steht.

Zeile 18 Kopieren der Adresse in p1 via EDX nach p2

Zeile 19 Die drei Argumente des printf-Aufrufs werden – beginnend mit dem letzten – nach-einander auf den Stack gebracht; Aufruf der Bibliotheks-Prozedur printf, anschliessende Stackbereinigung (12 Byte)

Zeile 21 Wert 25 (19h) in Variable b

Zeile 22 a und b auf den Stack bringen, Aufruf der selbstgeschriebenen Prozedur Produkt, acht Byte wieder vom Stack entfernen, Funktionsergebnis aus EAX entnehmen und in c kopieren

Zeile 23 zwei Parameter auf Stack bringen, Aufruf von printf, R¨uckgabewert wird nicht ver-wertet, acht Byte vom Stack entfernen

Zeile 25 Division einer Fließkommazahl durch drei Koprozessorbefehle: fld (Laden der Varia-blen), fdiv (Division), fstp (Speichern)

Zeile 27 R¨uckgabewert von “main“ ist Null und kommt nach EAX.

Zeile 28 Abbau des Stackframes, RET-Befehl