Technische Informatik I
Unterprogramme und Adressierung
Thorsten Thormählen
28. Januar 2020
Teil 10, Kapitel 2
Thorsten Thormählen
28. Januar 2020
Teil 10, Kapitel 2
Dies ist die Druck-Ansicht.
Weiterschalten der Folien durch die → Taste oder
durch das Klicken auf den rechten Folienrand.
Das Weiterschalten der Folien kann ebenfalls durch das Klicken auf den rechten bzw. linken Folienrand erfolgen.
Typ | Schriftart | Beispiele |
---|---|---|
Variablen (Skalare) | kursiv | $a, b, x, y$ |
Funktionen | aufrecht | $\mathrm{f}, \mathrm{g}(x), \mathrm{max}(x)$ |
Vektoren | fett, Elemente zeilenweise | $\mathbf{a}, \mathbf{b}= \begin{pmatrix}x\\y\end{pmatrix} = (x, y)^\top,$ $\mathbf{B}=(x, y, z)^\top$ |
Matrizen | Schreibmaschine | $\mathtt{A}, \mathtt{B}= \begin{bmatrix}a & b\\c & d\end{bmatrix}$ |
Mengen | kalligrafisch | $\mathcal{A}, B=\{a, b\}, b \in \mathcal{B}$ |
Zahlenbereiche, Koordinatenräume | doppelt gestrichen | $\mathbb{N}, \mathbb{Z}, \mathbb{R}^2, \mathbb{R}^3$ |
call
verwendet
call ziel
ret
(engl. "return") steht am Ende des Unterprogramms und sorgt dafür, dass zum aufrufenden Programm
zurückgekehrt und dieses weiter abgearbeitet wirdcall
und ret
den Befehlszähler EIP
int main() { int counter = 0; __asm { call Subroutine_IncrementCounter ; // counter = 1 call Subroutine_IncrementCounter ; // counter = 2 call Subroutine_IncrementCounter ; // counter = 3 jmp EndMainProgram Subroutine_IncrementCounter: mov eax, counter inc eax mov counter, eax ret EndMainProgram : } }Quelldatei: main.cpp
push
legt den Inhalt von quelle
auf den Stapel
push quelle
pop
entnimmt das oberste Element vom Stapel und kopiert es in ziel
pop ziel
Subroutine_IncrementCounter: push ebx // push content of ebx to stack mov ebx, counter inc ebx mov counter, ebx pop ebx // pop content from stack to ebx retQuelldatei: main.cpp
BP
und SP
angegeben
(bzw. in den entsprechenden 32-Bit Versionen: EBP
und ESP
):
BP
: Anfangsadresse des StapelspeichersSP
: Adresse des obersten Elements des StapelspeichersEIP
den Sprung in das Unterprogramm aus00881033h call Subroutine_IncrementCounter;// push 00881038h; set EIP=088103Ah 00881038h jmp EndMainProgram ;// set EIP=00881044h Subroutine_IncrementCounter: 0088103Ah push ebx ;// push content of ebx to stack 0088103Bh mov ebx, counter 0088103Eh inc ebx 0088103Fh mov counter, ebx 00881042h pop ebx ;// pop content of ebx from stack 00881043h ret ;// pop 00881038h; set EIP=00881038h EndMainProgram: 00881044hQuelldatei: main.cpp
pop
Befehl an Adresse 00881042h
auskommentiert würdeEIP
würde mit dem Inhalt von EBX
geladen, d.h. das Programm würde
an einer unsinnigen Stelle im Speicher weiter ausgeführt. Dies resultiert bei Windows
typischerweise in einem Speicherzugriffsfehler (engl. "Access violation")int main() { int counterForward = 0; int counterBackward = 0; __asm { call Subroutine_IncrementCounter ; jmp EndMainProgram Subroutine_IncrementCounter: mov eax, counterForward inc eax mov counterForward, eax cmp eax, 5 jz Subroutine_IncrementCounterPart2 call Subroutine_IncrementCounter ; // recursive call of subroutine Subroutine_IncrementCounterPart2: mov eax, counterBackward inc eax mov counterBackward, eax ret EndMainProgram : } }
EAX
, ECX
und EDX
EBX
, ESI
und EDI
(sowie EBP
und ESP
)int add(int varA, int varB ) { int retVal = varA + varB; return retVal; }
EAX
-Register an das aufrufende Programm übergebenEDX:EAX
an das aufrufende Programm übergebenint add(int varA, int varB ) { return varA + varB; } int main() { int result = add(1, 2); return 0; }Quelldatei: main.cpp
main()
zunächst ein push 2
und dann ein push 1
ausgeführt--- main.cpp --------- 1: int add(int varA, int varB ) { 009C1020 55 push ebp // push stack base pointer 009C1021 8B EC mov ebp,esp // setup new stack frame 2: return varA + varB; 009C1023 8B 45 08 mov eax,[ebp+08h] // access parameters 009C1026 03 45 0C add eax,[ebp+0Ch] 3: } 009C1029 5D pop ebp // pop stack base pointer 009C102A C3 ret --- keine quelldatei --- 009C102B CC int 3 009C102C CC int 3 009C102D CC int 3 009C102E CC int 3 009C102F CC int 3 --- main.cpp --------- 4: 5: int main() { 009C1030 55 push ebp 009C1031 8B EC mov ebp,esp 009C1033 51 push ecx 6: int result = add(1, 2); 009C1034 6A 02 push 2 009C1036 6A 01 push 1 009C1038 E8 C8 FF FF FF call add (9C1020h) 009C103D 83 C4 08 add esp,8 // clean up parameter pushes 009C1040 89 45 FC mov result,eax 7: return 0; 009C1043 33 C0 xor eax,eax 8: } 009C1045 8B E5 mov esp,ebp // clean up "push ecx" 009C1047 5D pop ebp 009C1048 C3 ret
push
auf den Stapelpop
vom Stapel holtpop
blockiert
EBP
-Register ein Rolle:
EBP
-Register zeigt immer auf die Anfangsadresse des aktuellen StapelsegmentsEBP
um auf
die Übergabeparameter zuzugreifen1: int add(int varA, int varB ) { 009C1020 push ebp // legt die alte Segmentanfangsadresse auf den Stack 009C1021 mov ebp,esp // die aktuelle Stackadresse wird neue Segmentanfangsadresse 2: return varA + varB; 009C1023 mov eax,[ebp+08h] // greift auf den ersten Parameter zu 009C1026 add eax,[ebp+0Ch] // greift auf den zweiten Parameter zu 3: } 009C1029 pop ebp // stellt die alte Segmentanfangsadresse wieder her 009C102A ret // entfernt die Rücksprungadresse vom Stack
pop
Befehle passierenESP
mit 8 (8 Byte, weil 4 Byte pro 32-Bit Übergabeparameter):
add esp,8
mov eax, [110h]
DS
(Daten-Segment) Segmentregister plus dieser
Offset-Adresse gebildet, d.h. der oben angegebene Befehl war gleichbedeutend mit
mov eax, ds:[110h]
DS
(Daten-Segment), SS
(Stapel-Segment) oder CS
(Code-Segment)offset
gesetzt ist die Speicheradresse selbst gemeint und nicht der gespeicherte Wertint myVar = 42; int main() { __asm { mov eax, myVar // move the content of myVar to eax mov eax, [myVar] // move the content of myVar to eax mov eax, offset myVar // move the address of myVar to eax } }Quelldatei: main.cpp
#include <stdio.h> int main() { int studentIds[] = {8012, 12341, 12412, 13343, 13423}; // create C-Array for(int i=0; i<5; i++) printf("studentIds[%d]: %d\n", i, studentIds[i]); // output __asm { mov eax, [studentIds+4] // move 12341 to eax mov ecx, [studentIds+12] // move 13343 to ecx mov [studentIds+4], ecx // move 13343 to studentIds[1] mov [studentIds+12], eax // move 12341 to studentIds[3] } for(int i=0; i<5; i++) printf("studentIds[%d]: %d\n", i, studentIds[i]); // output return 0; }Quelldatei: main.cpp
#include <stdio.h> int main() { int studentIds[] = {8012, 12341, 12412, 13343, 13423}; // create C-Array for(int i=0; i<5; i++) printf("studentIds[%d]: %d\n", i, studentIds[i]); // output __asm { mov eax, studentIds+4 // move 12341 to eax mov ecx, studentIds[12] // move 13343 to ecx mov studentIds[4], ecx // move 13343 to studentIds[1] mov studentIds+12, eax // move 12341 to studentIds[3] } for(int i=0; i<5; i++) printf("studentIds[%d]: %d\n", i, studentIds[i]); // output return 0; }
mov eax, [ebx]
EBX
-Register der Wert 110h
, dann würde der Wert an der Speicheradresse
110h
in das EAX
-Register kopiertlea
(engl. "load effective address") dient dazu die Adresse einer
Variablen zu ermitteln
lea ziel quelle
quelle
angegebene Variablen, wird in ziel
geladenint main() { int num = 8; __asm { mov eax, 4; lea ecx, num ;// ecx = address of variable "num"; add eax, [ecx] ;// add 4 + 8 } }Quelldatei: main.cpp
lea
-Befehls und bei bekannten Aufrufkonventionen der C-Funktionen, kann
die printf
C-Funktion nun auch aus dem Inline-Assembler aufgerufen werden (Quelldatei: main.cpp) :
#include <stdio.h> char format[] = "%s %s %d\n"; char hello[] = "Hello"; char world[] = "world: num="; int num = 8; int main() { __asm { push num ;// pushes parameter to stack lea eax, world ;// load address of array "world" to eax push eax ;// pushes parameter to stack lea eax, hello ;// load address of array "world" to eax push eax ;// pushes parameter to stack lea eax, format ;// load address of array "format" to eax push eax ;// pushes parameter to stack mov eax, printf ;// get address of C-printf function call eax ;// calls C-printf function add esp, 16 ;// clean up the stack } }
bx
bzw. ebx
("Base") ist dafür vorgesehen, um in Felder zu indizieren[studentIds+ebx]
sind auch einfache konstante Ausdrücke zugelassen
wie z.B. [studentIds+4*ebx-4]
int main() { int studentIds[] = {8012, 12341, 12412, 13343, 13423}; // create C-Array __asm { mov ebx, 5 ;// set loop counter LoopOverIds: mov eax, [studentIds+4*ebx-4] ;// studentIds[4], studentIds[3], ... inc eax mov [studentIds+4*ebx-4], eax dec ebx; jnz LoopOverIds } return 0; }Quelldatei: main.cpp
Befehl | Elementgröße |
---|---|
movsb | Byte (1 Byte) |
movsw | Word (2 Bytes) |
movsd | Double Word (4 Bytes) |
movsq | Quad Word (8 Bytes) |
cmpsb | Byte (1 Byte) |
cmpvsw | Word (2 Bytes) |
cmpsd | Double Word (4 Bytes) |
cmpsq | Quad Word (8 Bytes) |
movs
("Move Data from String to String") und cmps
("Compare String") Befehle esi
("Extended Source Index") und
edi
("Extended Destination Index") verwendet werdenmovsb
ein Byte von der Adresse in esi
zur Adresse in edi
esi
und edi
automatisch erhöht oder erniedigt, je nachdem ob
das Direction-Flag UP gesetzt ist oder nichtcld
("clear direction") für aufsteigende Abarbeitungstd
("set direction") für absteigende Abarbeitung#include <stdio.h> int main() { char myText[] = "John is the fastest kid in town"; char myReplace[] = "Bill"; printf("%s\n", myText); __asm { cld ;// clear direction flag mov ecx, 4 ;// set loop counter lea eax, myReplace mov esi, eax ;// set esi to address of myReplace lea eax, myText mov edi, eax; ;// set edi to address of myText LoopOver: movsb; loop LoopOver } printf("%s\n", myText); return 0; }
rep
vor einem movs
Befehl kann den loop
Befehl ersetzen. Auch bei rep
wird
das ecx
Register als Zähler verwendet (Quelldatei: main.cpp) :
#include <stdio.h> int main() { char myText[] = "John is the fastest kid in town"; char myReplace[] = "pet"; printf("%s\n", myText); __asm { cld ;// clear direction flag mov ecx, 3 ;// set loop counter lea eax, myReplace mov esi, eax ;// set esi to address of myReplace lea eax, myText+20 mov edi, eax; ;// set edi to address of myText rep movsb; } printf("%s\n", myText); return 0; }
Anregungen oder Verbesserungsvorschläge können auch gerne per E-mail an mich gesendet werden: Kontakt