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 Stapelspeichers
EIP 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:
00881044h
Quelldatei: 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 zuzugreifen
1: 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