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:20] catalin.vasile3004 [Confuzii la accesarea datelor în memorie (operatorul de dereferenţiere)] |
bune-practici [2015/11/30 21:08] (current) catalin.vasile3004 [Încărcarea datelor în registre] |
||
---|---|---|---|
Line 26: | Line 26: | ||
add eax, var ; add to eax, the >>address<< of var | add eax, var ; add to eax, the >>address<< of var | ||
</code> | </code> | ||
- | Acest cod este echivalent cu următorul cod din C: | + | Acest cod este echivalent cu următorul cod din **C**: |
<code c> | <code c> | ||
int var = 34; | int var = 34; | ||
Line 46: | Line 46: | ||
eax = eax + var; /* add eax, [var] */ | eax = eax + var; /* add eax, [var] */ | ||
</code> | </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 70: | 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 143: | Line 151: | ||
</code> | </code> | ||
Poate că nu ai un cod care compilează, dar măcar nu ai un cod care compilează şi ruleaza greşit. | 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 |