This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
|
bune-practici [2015/11/30 15:07] catalin.vasile3004 [Încărcarea datelor în registre] |
bune-practici [2015/11/30 21:08] (current) catalin.vasile3004 [Încărcarea datelor în registre] |
||
|---|---|---|---|
| Line 15: | Line 15: | ||
| ===== Erori des întâlnite ===== | ===== Erori des întâlnite ===== | ||
| + | ==== Confuzii la accesarea datelor în memorie (operatorul de dereferenţiere) ==== | ||
| + | Pentru cei care sunt la început de drum la a învăţa assembly, este o confunzie foarte mare cum se foloseşte operatorul de dereferenţiere din asamblare: ''[ ]''\\ | ||
| + | Care este diferenţa între ''op reg, var'' şi ''op reg, [var]''?\\ | ||
| + | În 99.999999999999% din cazuri, operaţia fără paranteze pătrate înseamnă să foloseşti adresa acelei variabile pe post de operand. Exemple: | ||
| + | <code asm> | ||
| + | section .data | ||
| + | var: DD 34 | ||
| + | section .text | ||
| + | mov eax, var ; put var's >>address<< into the eax register | ||
| + | add eax, var ; add to eax, the >>address<< of var | ||
| + | </code> | ||
| + | Acest cod este echivalent cu următorul cod din **C**: | ||
| + | <code c> | ||
| + | int var = 34; | ||
| + | eax = &var; /* mov eax, var */ | ||
| + | eax = eax + &var; /* add eax, var */ | ||
| + | </code> | ||
| + | În cazul în care foloseşti paranteze pătrate: | ||
| + | <code asm> | ||
| + | section .data | ||
| + | var: DD 34 | ||
| + | section .text | ||
| + | mov eax, [var] ; put var's >>value<< into eax | ||
| + | add eax, [var] ; add to eax, the >>value<< of var | ||
| + | </code> | ||
| + | Acest lucru ar fi echivalent în **C** cu: | ||
| + | <code c> | ||
| + | int var = 34; | ||
| + | eax = var; /* mov eax, [var] */ | ||
| + | eax = eax + var; /* add eax, [var] */ | ||
| + | </code> | ||
| + | Printre singurele instrucţiuni care fac abatare de la aceste reguli, este **lea** (load effective address). | ||
| + | <code asm> | ||
| + | section .data | ||
| + | var: DD 34 | ||
| + | section .text | ||
| + | lea eax, [var] ; put var's >>address<< into the eax register | ||
| + | </code> | ||
| + | În rest, toate celelalte instrucţiuni aderă la regulile enunţate mai sus. Dacă or mai exista şi alte instrucţiuni care se comportă ca **lea**, cel mai probabil nu vor fi tratate în aceste laboratoare. | ||
| ==== Încărcarea datelor în registre ==== | ==== Încărcarea datelor în registre ==== | ||
| Adesea apar erori chiar la încărcarea datelor în registre. | Adesea apar erori chiar la încărcarea datelor în registre. | ||
| Line 40: | Line 78: | ||
| Deşi **nr** are valoarea 23, programul afişează **number: 1836412439**.\\ | Deşi **nr** are valoarea 23, programul afişează **number: 1836412439**.\\ | ||
| De ce? Pentru că la **nr** find un singur byte, procesorul continuă să aducă din memorie încă 3 bytes astfel încât să îl poate umple pe **eax**. În cazul nostru, după **nr**, în memorie, este declarat vectorul **str**, aşa că va lua încă 3 bytes de la el pentru a-l umple pe **eax**. | De ce? Pentru că la **nr** find un singur byte, procesorul continuă să aducă din memorie încă 3 bytes astfel încât să îl poate umple pe **eax**. În cazul nostru, după **nr**, în memorie, este declarat vectorul **str**, aşa că va lua încă 3 bytes de la el pentru a-l umple pe **eax**. | ||
| - | <note important>Intuitiv, v-aţi aştepta ca asamblorul/compilatorul să urle la voi "că uite domne, eu am declarat variabila de 1 byte, şi am scris din greşeală că vreau să aduc 4 bytes de acolo. Ca şi în cazul limbajului **C**, limbajul de asamblare te lasă să te împuşti singur în picior. Nu este treaba lui să facă check-uri. Dacă tu vrei **1 milion de bytes** de la adresa **0xB00B5**, el o să-ţi codifice programul în binar astfel încât să-ţi aducă date de la adresa **0xB00B5**. Că după îţi bubuie programul în faţă cu un **Segmentation Fault** pentru că ai încercat să accesezi o zonă de memorie care nu ţi-a fost alocată, e deja treaba sistemului de operare şi a procesorului.</note> | + | <note important>Intuitiv, v-aţi aştepta ca asamblorul/compilatorul să urle la voi "că uite domne, eu am declarat variabila de 1 byte, şi am scris din greşeală că vreau să aduc 4 bytes de acolo". Ca şi în cazul limbajului **C**, limbajul de asamblare te lasă să te împuşti singur în picior. Nu este treaba lui să facă check-uri. Dacă tu vrei **1 milion de bytes** de la adresa **0xB00B5**, el o să-ţi codifice programul în binar astfel încât să-ţi aducă date de la adresa **0xB00B5**. Că după îţi bubuie programul în faţă cu un **Segmentation Fault** pentru că ai încercat să accesezi o zonă de memorie care nu ţi-a fost alocată, e deja treaba sistemului de operare şi a procesorului.</note> |
| === O primă rezolvare === | === O primă rezolvare === | ||
| O primă încercare de a rezolva problema ar fi să încercăm să-l aducem pe **nr** direct într-un registru de 1 byte. | O primă încercare de a rezolva problema ar fi să încercăm să-l aducem pe **nr** direct într-un registru de 1 byte. | ||
| Line 92: | Line 130: | ||
| section .data | section .data | ||
| - | nr: DW 23 | + | nr: DW 23 ; declare a variable of word type (2 bytes) |
| str: DB 'number: %d', 0 | str: DB 'number: %d', 0 | ||
| Line 100: | Line 138: | ||
| main: | main: | ||
| - | mov eax, word [nr] | + | mov eax, word [nr] ; try to access a varible of word type ; try to bring 2 bytes into eax |
| push eax | push eax | ||
| push str | push str | ||
| Line 112: | Line 150: | ||
| load.asm:12: error: invalid combination of opcode and operands | load.asm:12: error: invalid combination of opcode and operands | ||
| </code> | </code> | ||
| + | Poate că nu ai un cod care compilează, dar măcar nu ai un cod care compilează şi ruleaza greşit. | ||
| + | |||
| + | ==== Segmentation Fault debugging: GDB quicky ==== | ||
| + | **gdb** este un debugger în linie de comandă. Unul din lucrurile la care ne poate ajuta acesta este să găsim punctele în care ne dă **Segmentation Fault** un program. Mulţi abordează această problemă prin imbricarea de **printf**-uri în puncte intermediare în program. Acest lucru nu prea ajută. Uitaţi cam cum este prelucrat un program de un procesor: | ||
| + | - Într-o singură etapă se aduc mai multe instrucţiuni din memorie. Accesul la memorie este scump, şi dacă la fiecare instrucţiune de 5-6 bytes ne-am duce în memorie, nu am avea o performanţă foarte bună. Din acest motiv s-a inventat un modul în procesor, numit prefetching, în care se înmagazinează mai multe instrucţiuni de la adresa de la care se aduce cod/instrucţiuni, pentru ca execuţia să fie mai fluidă. | ||
| + | - În momentul în care procesorul îşi dă seama că una din instrucţiuni accesează o zonă nevalidă din memorie, trimite un semnal către sistemul de operare. Şi sistemul de operare este tot o bucată de cod care se execută pe procesor. Până când acest semnal trezeşte codul din sistemul de operare, e foarte posibil ca programul să mai fi executat o căruţă de instrucţiuni, din acest motiv, o înşiruire de printf-uri s-ar putea executa şi după instrucţiunea care a produs Segmentation Fault-ul. | ||
| + | - Sistemul de operare se trezeşte şi închide forţat programul care a cauzat probleme. Printre datele primite de la semnal se regăseşte şi adresa instrucţiunii care a cauzat Segmentation Fault. Cu un debugger, se poate afla şi din userspace ce instrucţiune a cauzat Segmentation Fault. | ||
| + | Exemplu de cod cu probleme: | ||
| + | <file asm segfault.asm> | ||
| + | extern printf | ||
| + | |||
| + | section .data | ||
| + | str: DB `number: %d\n` | ||
| + | nr: DD 1, 2, 3, 4, 5 | ||
| + | len: DD 4000 | ||
| + | |||
| + | section .text | ||
| + | |||
| + | global main | ||
| + | |||
| + | main: | ||
| + | xor ecx, ecx | ||
| + | keep_printing: | ||
| + | push ecx ; save ecx, because it will be destroyed by printf call | ||
| + | push dword [nr + 4*ecx] | ||
| + | push str | ||
| + | call printf | ||
| + | add esp, 8 | ||
| + | pop ecx ; restore ecx | ||
| + | inc ecx | ||
| + | cmp ecx, [len] | ||
| + | jl keep_printing | ||
| + | ret | ||
| + | </file> | ||
| + | Programul parcurge un vector şi afişează valorile sale. Deşi programul are doar 5 elemente, **len**-ul este setat greşit la 4000 de elemente. Dacă compilăm şi rulam programul acesta ne va da un **segfault**: | ||
| + | <code bash> | ||
| + | # ... | ||
| + | number: 0 | ||
| + | number: 0 | ||
| + | number: 0 | ||
| + | Segmentation fault | ||
| + | </code> | ||
| + | Cum rulăm gdb: | ||
| + | <code> | ||
| + | gdb nume_binar | ||
| + | </code> | ||
| + | Exemplu: | ||
| + | <code bash> | ||
| + | catalin.vasile3004@fep ~ $ gdb ./segfault | ||
| + | GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6) | ||
| + | Copyright (C) 2010 Free Software Foundation, Inc. | ||
| + | License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> | ||
| + | This is free software: you are free to change and redistribute it. | ||
| + | There is NO WARRANTY, to the extent permitted by law. Type "show copying" | ||
| + | and "show warranty" for details. | ||
| + | This GDB was configured as "x86_64-redhat-linux-gnu". | ||
| + | For bug reporting instructions, please see: | ||
| + | <http://www.gnu.org/software/gdb/bugs/>... | ||
| + | Reading symbols from /export/home/acs/stud/c/catalin.vasile3004/load...(no debugging symbols found)...done. | ||
| + | (gdb) | ||
| + | </code> | ||
| + | În acest moment s-a deschis consola debugger-ului, **dar programul NU rulează**. | ||
| + | Pentru a rula programul: | ||
| + | <code> | ||
| + | set disassembly-flavor intel | ||
| + | run param1 param2 param3 < fisier.in > fisier.out | ||
| + | </code> | ||
| + | Cu **run**-ul dat ca exemplu, e ca şi cum am fi rulat programul în felul următor: | ||
| + | <code bash> | ||
| + | ./segfault param1 param2 param3 < fisier.in > fisier.out | ||
| + | </code> | ||
| + | ''set disassembly-flavor intel'' vă ajută pentru a afişa eventualele printări de cod de asamblare într-o sintaxă cunoscută. Limbajul de asamblare reprezintă un set de alias-uri pentru instrucţiunile din binarul unui program. Aceste alias-uri nu au o formă standardizată motiv pentru care acestea diferă de la un asamblor la altul. By default, tool-urile din Linux folosesc sintaxa [[https://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax|AT&T]]. 99% din tool-urile din Linux (gdb NU se află printre ele) pot primii argumentul ''-M intel'' pentru a afişa sau a trata codul de asamblare ca şi cum ar fi în sintaxa recomandată de Intel (care se regăseşte şi la NASM). Programe care pot primi acest flag sunt: gcc (gas), objdump, etc.\\ | ||
| + | **Revenind la gdb**, în momentul în care rulăm o să ne dea următoarea eroare: | ||
| + | <code bash> | ||
| + | # ... | ||
| + | number: 0 | ||
| + | number: 0 | ||
| + | number: 0 | ||
| + | number: 0 | ||
| + | |||
| + | Program received signal SIGSEGV, Segmentation fault. | ||
| + | 0x08048423 in keep_printing () | ||
| + | </code> | ||
| + | Pentru a vedea ce instrucţiunea a provocat segfault, putem da următoarea comandă: | ||
| + | <code bash> | ||
| + | (gdb) display/10i $pc | ||
| + | 1: x/10i $pc | ||
| + | => 0x8048423 <keep_printing+1>: push DWORD PTR [ecx*4+0x804a02c] | ||
| + | 0x804842a <keep_printing+8>: push 0x804a020 | ||
| + | 0x804842f <keep_printing+13>: call 0x80482f0 <printf@plt> | ||
| + | 0x8048434 <keep_printing+18>: add esp,0x8 | ||
| + | 0x8048437 <keep_printing+21>: pop ecx | ||
| + | 0x8048438 <keep_printing+22>: inc ecx | ||
| + | 0x8048439 <keep_printing+23>: cmp ecx,DWORD PTR ds:0x804a040 | ||
| + | 0x804843f <keep_printing+29>: jl 0x8048422 <keep_printing> | ||
| + | 0x8048441 <keep_printing+31>: ret | ||
| + | 0x8048442 <keep_printing+32>: xchg ax,ax | ||
| + | </code> | ||
| + | * **$pc** este o variabilă **gdb**, şi vine de la **P**rogram **C**ounter (este pointer-ul la instrucţiunea curentă). | ||
| + | * **display** face dump de la un pointer dat ca argument, în cazul nostru **$pc** | ||
| + | * **i**-ul îi spune lui **display** să interpreteze datele de acolo ca şi cum ar fi instrucţiuni | ||
| + | * **10** îi spune lui **display** câţi operanzi de tipul **i** (instrucţiune) să afişeze\\ \\ | ||
| + | Prin ''<keep_printing+some_number>'', **gdb** incearcă să ne arate cam pe unde ar fi această instrucţiune. În cazul nostru instrucţiunea este aproape de label-ul **keep_printing**.\\ | ||
| + | Pentru a vedea ce valoare a avut un registru la momentul în care s-a declanşat **segfault**-ul, puteţi da: | ||
| + | <code> | ||
| + | (gdb) print $nume_registru | ||
| + | </code> | ||
| + | În cazul nostru s-ar putea să ne intereseze ce valoare are **ecx**. Pentru a afla acest lucru: | ||
| + | <code> | ||
| + | (gdb) print $ecx | ||
| + | </code> | ||
| + | |||
| ===== Categorie 3 ===== | ===== Categorie 3 ===== | ||
| * TODO | * TODO | ||
| * TODO | * TODO | ||