# practicum week 1 > ik heb de opdrachten met behulp van g++ als compiler en gdb als debugger > gemaakt, dus sommige registernamen of syntax kan mogelijk niet overeenkomen > met wat er verwacht wordt. ## practicum setup ```bash $ make g++ -c -g main.cpp -o main.o g++ main.o -lstdc++ -o main $ gdb main Reading symbols from main... (gdb) br main Breakpoint 1 at 0x1181: file main.cpp, line 9. (gdb) run Starting program: /home/loek/docs/repos/os-huiswerk/os2w1/main [Thread debugging using libthread_db enabled] Using host libthread_db library "/usr/lib/libthread_db.so.1". Breakpoint 1, main () at main.cpp:9 9 int a = 5; (gdb) ``` ## vraag 1 In gdb heet EIP `$pc`, en deze heeft nadat het programma is gepauzeerd in de `main()` de hexadecimale waarde `0x555555555181`. ``` (gdb) print $pc $1 = (void (*)(void)) 0x555555555181 ``` ## vraag 2 `0x7fffffffcb20`: ``` (gdb) print $sp $2 = (void *) 0x7fffffffcb20 ``` ## vraag 3 De door g++ gegenereerde machinecode bevat bij mij geen 200 bytes extra, en de initialisatie van de main functie is ook een stuk beknopter: ``` (gdb) disas /m main Dump of assembler code for function main(): 8 int main() { 0x0000555555555179 <+0>: push rbp 0x000055555555517a <+1>: mov rbp,rsp 0x000055555555517d <+4>: sub rsp,0x10 9 int a = 5; => 0x0000555555555181 <+8>: mov DWORD PTR [rbp-0x8],0x5 10 int b = 7; 0x0000555555555188 <+15>: mov DWORD PTR [rbp-0x4],0x7 11 12 show(a, b); 0x000055555555518f <+22>: mov edx,DWORD PTR [rbp-0x4] 0x0000555555555192 <+25>: mov eax,DWORD PTR [rbp-0x8] 0x0000555555555195 <+28>: mov esi,edx 0x0000555555555197 <+30>: mov edi,eax 0x0000555555555199 <+32>: call 0x5555555551c3 <_Z4showii> 13 swap(a, b); 0x000055555555519e <+37>: mov edx,DWORD PTR [rbp-0x4] 0x00005555555551a1 <+40>: mov eax,DWORD PTR [rbp-0x8] 0x00005555555551a4 <+43>: mov esi,edx 0x00005555555551a6 <+45>: mov edi,eax 0x00005555555551a8 <+47>: call 0x555555555234 <_Z4swapii> 14 show(a, b); 0x00005555555551ad <+52>: mov edx,DWORD PTR [rbp-0x4] 0x00005555555551b0 <+55>: mov eax,DWORD PTR [rbp-0x8] 0x00005555555551b3 <+58>: mov esi,edx 0x00005555555551b5 <+60>: mov edi,eax 0x00005555555551b7 <+62>: call 0x5555555551c3 <_Z4showii> 15 16 return 0; 0x00005555555551bc <+67>: mov eax,0x0 17 } 0x00005555555551c1 <+72>: leave 0x00005555555551c2 <+73>: ret End of assembler dump. ``` In de meegeleverde assembly wordt `ebp` - 216 in het `edi` register gezet. Deze waarde is de nieuwe stack pointer die na het volmaken van de nieuw gealloceerde ruimte in het echte stack pointer register gezet wordt. ## vraag 4 Omdat ik de c++ code heb gecompileerd met debug symbols kan gdb automatisch van `a` en `b` zien dat het 32-bits signed integers zijn, en gdb interpreteert automatisch de data op het adres van de variabelen op de "goede" manier. ``` (gdb) print $sp $1 = (void *) 0x7fffffffcb20 (gdb) print &a $2 = (int *) 0x7fffffffcb28 (gdb) print &b $3 = (int *) 0x7fffffffcb2c (gdb) info locals a = -137244042 b = 32767 ``` ## vraag 5 ``` (gdb) br main.cpp:12 Breakpoint 2 at 0x55555555518f: file main.cpp, line 12. (gdb) c Continuing. Breakpoint 2, main () at main.cpp:12 12 show(a, b); (gdb) info locals a = 5 b = 7 (gdb) x &a 0x7fffffffcb28: 0x00000005 (gdb) x &b 0x7fffffffcb2c: 0x00000007 ``` ## vraag 6 Aan het einde van de `swap()` functie zijn de waardes van `a` en `b` (lokaal tot de `swap()` functie) daadwerkelijk aangepast. Dit zijn alleen niet dezelfde `a` en `b` als in de `main()` functie, dus lijkt het alsof er niks is gebeurt na de return instructie. ``` (gdb) disas /m swap Dump of assembler code for function _Z4swapii: 23 void swap(int a, int b) { 0x0000555555555234 <+0>: push rbp 0x0000555555555235 <+1>: mov rbp,rsp 0x0000555555555238 <+4>: mov DWORD PTR [rbp-0x14],edi 0x000055555555523b <+7>: mov DWORD PTR [rbp-0x18],esi 24 int h; 25 h = a; 0x000055555555523e <+10>: mov eax,DWORD PTR [rbp-0x14] 0x0000555555555241 <+13>: mov DWORD PTR [rbp-0x4],eax 26 a = b; 0x0000555555555244 <+16>: mov eax,DWORD PTR [rbp-0x18] 0x0000555555555247 <+19>: mov DWORD PTR [rbp-0x14],eax 27 b = h; 0x000055555555524a <+22>: mov eax,DWORD PTR [rbp-0x4] 0x000055555555524d <+25>: mov DWORD PTR [rbp-0x18],eax 28 } => 0x0000555555555250 <+28>: nop 0x0000555555555251 <+29>: pop rbp 0x0000555555555252 <+30>: ret End of assembler dump. (gdb) print a $1 = 7 (gdb) print b $2 = 5 ``` ## vraag 7 Ik zie dat er nu voor elke toewijzing van `a`, `b`, en `h` een extra instructie die het `rax` register gebruikt is toegevoegd. Ik zie ook dat het adres voor `a` en `b` in de scope van de `main()` functie nu hetzelfde is als die binnen de `swap()` functie. ``` (gdb) disas /m swap Dump of assembler code for function _Z4swapRiS_: 23 void swap(int &a, int &b) { 0x000055555555526b <+0>: push rbp 0x000055555555526c <+1>: mov rbp,rsp 0x000055555555526f <+4>: mov QWORD PTR [rbp-0x18],rdi 0x0000555555555273 <+8>: mov QWORD PTR [rbp-0x20],rsi 24 int h; 25 h = a; 0x0000555555555277 <+12>: mov rax,QWORD PTR [rbp-0x18] 0x000055555555527b <+16>: mov eax,DWORD PTR [rax] 0x000055555555527d <+18>: mov DWORD PTR [rbp-0x4],eax 26 a = b; => 0x0000555555555280 <+21>: mov rax,QWORD PTR [rbp-0x20] 0x0000555555555284 <+25>: mov edx,DWORD PTR [rax] 0x0000555555555286 <+27>: mov rax,QWORD PTR [rbp-0x18] 0x000055555555528a <+31>: mov DWORD PTR [rax],edx 27 b = h; 0x000055555555528c <+33>: mov rax,QWORD PTR [rbp-0x20] 0x0000555555555290 <+37>: mov edx,DWORD PTR [rbp-0x4] 0x0000555555555293 <+40>: mov DWORD PTR [rax],edx 28 } 0x0000555555555295 <+42>: nop 0x0000555555555296 <+43>: pop rbp 0x0000555555555297 <+44>: ret End of assembler dump. ``` ## vraag 8 ``` (gdb) disas recursief Dump of assembler code for function _Z9recursiefi: 0x0000000000001189 <+0>: push rbp 0x000000000000118a <+1>: mov rbp,rsp 0x000000000000118d <+4>: sub rsp,0x10 0x0000000000001191 <+8>: mov DWORD PTR [rbp-0x4],edi 0x0000000000001194 <+11>: mov eax,DWORD PTR [rbp-0x4] 0x0000000000001197 <+14>: sub eax,0x1 0x000000000000119a <+17>: mov edi,eax 0x000000000000119c <+19>: call 0x1189 <_Z9recursiefi> 0x00000000000011a1 <+24>: mov edx,DWORD PTR [rbp-0x4] 0x00000000000011a4 <+27>: add eax,edx 0x00000000000011a6 <+29>: leave 0x00000000000011a7 <+30>: ret End of assembler dump. ``` Op regel drie van de disassembly is een `sub rsp,0x10` instructie te zien, die 16 bytes van de stack pointer afhaalt (16 bytes allocatie). Deze bytes zullen waarschijnlijk komen door 4 bytes voor het argument dat `recursief` weer meegegeven wordt bij de volgende aanroep, 4 bytes padding, 4 bytes voor de return waarde en weer 4 bytes padding. ## vraag 9 Ik heb het volgende programma geschreven om te testen hoe groot de stack kan worden voordat het programma zichzelf laat crashen: ```c volatile unsigned long i = 0; void rec() { i++; rec(); } int main() { rec(); return 0; } ``` Ik heb het programma een paar keer uitgevoerd onder een debugger om de uiteidenlijke waarde van `i` te inspecteren, en er kwam altijd een waarde rond 523700 (decimaal) uit. Deze functie reserveert geen lokale stack variabelen: ``` (gdb) disas rec Dump of assembler code for function _Z3recv: 0x0000000000001119 <+0>: push rbp 0x000000000000111a <+1>: mov rbp,rsp 0x000000000000111d <+4>: mov rax,QWORD PTR [rip+0x2ef4] # 0x4018 0x0000000000001124 <+11>: add rax,0x1 0x0000000000001128 <+15>: mov QWORD PTR [rip+0x2ee9],rax # 0x4018 0x000000000000112f <+22>: call 0x1119 <_Z3recv> 0x0000000000001134 <+27>: nop 0x0000000000001135 <+28>: pop rbp 0x0000000000001136 <+29>: ret End of assembler dump. ``` Zonder het reserveren van stackruimte per aanroep, kost elke aanroep alleen nog 8 bytes voor het return adres. De maximale stack grootte zal dan ongeveer 4 MB zijn. De eerdere `recursief()` functie kost in totaal 32 bytes per aanroep (16 voor lokale variabelen, 8 voor return adres en nog 8 voor return waarde). Dat zou betekenen dat de `recursief()` functie na ongeveer 130000 keer de stack overvol zou maken.