Jump to content
pyth0n3

Basic GAS AT&T Assembly Syntax [Tutorial]

Recommended Posts

In acest tutorial voi descrie caracteristicile de baza a limbajului de programare assembly.Vom avea un simplu exemplu de baza si anume stamparea unei variabile in stdout, structura instructiilor , definirea sectiunilor de baza , memoria, registrii de baza care pot fi intalniti in arhitectura procesoarelor de 32 biti Intel , nu in ultimul rand voi converti un exemplu de cod assembly scris pentru arhitectura intel 32 biti in 64 biti.Voi folosi cei mai simpli termeni pentru a descrie structura instructiilor si a unui registru.Pentru a nu duce in erroare un user ma voi folosi doar de registrii indispensabili.Assembly este un limbaj de programare Low Level.Spre deosebirea celorlalte limbaje de programare assembly necesita o traducere simpla in Machine Code deoarece fiecare cuvant sau mai bine spus instructie in assembly vine transformata intro instructie macchine code.Intructiile in machine code sunt intructii binare care pot fi interpretate foarte simplu de catre un computer dar putin mai greu de catre o persoana.De aceea pentru fiecare instructie binara a fost creata o instructie in assembly care poate fi denumita human readable deoarece creierului uman este mai simplu sa memoreze un anumit alias decat o instructie in 0 si 1.Fiecare procesor are un set de instructii assembly care difera de la o arhitectura la alta.Pentru a traduce instructiile assembly in operation code (macchine instruction code) vine folosit un traducator chemat assembler.Acesta este un software care interpreteaza instructiile scrise in assembly si le traduce in macchine code.Ei bine exista mai multe tipuri de assembler si fiecare are o anumita sintaxa.Instructiile scrise de catre un anumit assembler nu pot fi traduse in opcode de catre alt tip de assembler deoarece instructiile sunt diverse.Cele mai cunoscute tipuri de assembler sunt urmatoarele:

NASM Windows, Linux, Mac OS X, DOS, OS/2

MASM Windows, DOS, OS/2

TASM Windows, DOS

Yasm Windows, DOS, Linux, Unix-like

HLA Windows, Linux, FreeBSD, Mac OS X

GAS Unix-like, Windows, DOS, OS/2

Evident lista poate fi mult mai lunga , dar ma limitez aici.Ceea ce este important sa intelegeti este faptul ca fiecare are o sintaxa particulara pentru a scrie intructiile si ca exista diverse moduri de a programa in assembly.Pentru acest tutorial si urmatoarele exemple eu voi folosi GAS ca assembler.Ce este important sa cunoasteti este ca acest tip de assembler permite scrierea instructiilor assembly in 2 moduri.De default este folosita sintaxa AT&T dar poate fi folosita si sintaxa Intel.Ambele sintaxe pot traduce instructiile in opcode compatibil cu procesoarele Intel doar ca difera modul in care sunt scrise instructiile.

Intex Syntax instructie dest,source

AT&T Syntax instructie source,dest

In urmatoarele exemple voi folosi doar AT&T Syntax.Assembly nu este un limbaj de programare cross platform deci anumite instructii vor putea fi executatea doar pe anumite platforme si nu pe altele.Exemplele sunt create pentru sistemele Linux 32 Biti Intel.In final voi traduce exemplul din tutorial si pentru arhitectura Intel 64 biti Linux.De obicei limbajele de programare se invata intrun anumit mod .Vin invatate structurele de date, instructiile , structurele de control , functiile si cu putina practica o persoana poate incepe sa programeze.Ei bine in assembly este putin diverssi pentru a obtine o anumita functie vor trebui scrise mai multe linii de cod.Pentru a cunoaste assembly necesita cunostinta sistemului operativ, a structurii procesorului si cum vine alocata memoria in ambientul respectiv.Acest tutorial este basic deoarece am vrut sa merg pe principul keep it simple .Vom incepe cu descrierea arhitecturii a unui computer.Avem un procesor , 3 tipuri de memorie (cache memory, main memory , secondary memory) ,Input/Output Devices (tastiera spre exemplu).Procesorul acceseaza date in memorie in urmatorul fel:Va cauta in mod direct existenta datelor in cache memory , main memory(ram) si in mod indirect date in secondary memory (hard disk).In cazul in care datele nu se gasesc in casche memory sau in main memory vor fi incarcate din secondary memory in main memory dupa care procesorul va avea access direct la ele.Conceptul programarii in assembly este urmatorul: Procesorul preia date din memorie executa operatii aritmetice asupra datelor si stocheaza rezultatul inapoi in memorie sau il stampeaza in stdout (monitorul user/ului).La randul lor datele pot fi preluate din stdin (tastiera) , pot fi procesate si stocate in memorie sau pe ecranul user-ului.In momentul in care datele vin preluate de catre cpu din main memory(ram) pentru a executa anumite operatii asupra lor vin stocate intrun alt spatiu de memorie, acest spatiu vine chemat registu.Un cpu detine mai multi registrii pentru a stocain mod temporar datele atunci cand se fac operatii asupra lor dupa care datele vin puse la locul de unde au fost luate .Ceea ce probabil il pune pe user intro stare de umra este faptul ca procesorul poate detine multi registrii.Nu e nevoie sa cunosti fiecare registru daca nu il folosesti atunci cand programezi in assembly .In acest articol nu voi descrie toti registrii deoarece voi crea o confuzie.Ceea ce este important sa cunoasteti este faptul ca atunci cand o valoare este preluata din memorie trebuie stocata intrun anumit loc temporar unde pentru a face operatii asupra ei dupa care rezultatul va fi stocat inapoi in memorie iar registrul temporar va prelua urmatoarea valoare.

Ca in fiecare limbaj de programare si in assembly programul vine impartit in anumite sectii.Exista o sectie unde pot fi declarate anumite tipuri de date.Spre exemplu exista o sectie chemata .data , aici pot fi declarate tipurile de date cu care se va lucra, Aceasta sectie poate fi paragonata cu declararea variabilelor in alte limbaje de programare. O alta sectie este .bss , in aceasta sectie se pot declara alte tipuri de date a caror valoare spre exemplu nu este cunoscuna initial.O alta sectie este .text , aici pot fi scrise instructiile care vor procesa datele declarate si procesate in sectiile .data .bss.O alta sectie importanta este .globl , aici pot fi chemate functii/librari externe care au fost deja create in precedenta.Vom avea si o sectie _start de unde instructiile vor incepe sa fie executate.Aceasta sectie poate fi paragonata cu functia main() in c.

Pentru a scrie un simplu program in assembly se vor crea sectiile respective, se vor umple cu date dupa care se vor chema instructii asupra datelor declarate.In urmatoarele exemple vom vedea cum se pot declara date , cum vin declarate instructiile? , ce se intampla atunci cand instructiile vin traduse in opcode?, cum vin incarcate datele din memorie intrun registru?, cum pot fi chemate functii externe asupra datelor?

Pentru a putea face o operatie asupra unei valori , va trebui declarata valoarea respoectiva , acelasi lucru si pentru oricare alt tip de date.Initial vom construi o sectie .data unde vom stoca un nickname.

Declar o sectie data in memorie

.data

Declar o eticheta pentru tipul de date pe care il voi crea

NickName:

Declar tipul de date pe care vreau sa le stochez in memorie

.ascii "pyth0n3\n:

Asadar am creat o sectie, o eticheta , un tip de date

Eticheta va avea o adresa in memoria RAM , la adresa respectiva se vor gasi datele pe care le-am declarat

Etichetei ii vine atribuita o adresa de memorie

NickName data variable address 0x8049098

La adresa 0x8049098 vom avea datele stocate si anume pyth0n3\n


NickName data variable content in excaped hex
0x70 0x79 0x74 0x68 0x30 0x6e 0x33 0x0a
p y t h 0 n 3 \n

Evident o data ce instructia .ascii "pyth0n3\n" va fi tradusa in opcode de catre assembler vom avea urmatorul cod


Nick Name data variable content in binary giant, 8 bytes
0000101000110011011011100011000001101000011101000111100101110000

Important este sa intelegeti faptul ca acest cod se afla la o anumita adresa si anume la 0x8049098 ,am tradus adresa in hex dar pentru procesor va fi tot o valoare in 0 si 1.Dupa ce am declarat datele ramane sa decidem ce vrem sa facem cu ele .Vom stampa pe ecran ceea ce am stocat la adresa 0x8049098.Evident pentru a face acest lucru exista mai multe alternative.In Linux ne putem folosi de syscall.Trebuie individuate functiile de care avem nevoie pentru a stampa datele + functia pentru a inchide programul.Fiecare syscall are un anumit id numeric .In fiecare sistem linux lista se poate gasi in /usr/include/asm .In fisierul unistd_32.h se vor gasi functiile pentru procesoarele 32 biti iar in unistd_64.h pentru procesoarele 64 biti.Vom folosi 2 syscall diverse 1 pentru a stampa datele declarate 2 pentru a iesi din program.

Pentru a stampa datele vom folosi #define __NR_write care are ca id numarul 4

Pentru a iesi din program vom folosi #define __NR_exit care are ca id numarul 1

Datele care vin procesate trebuie incarcate in registrii asadar vom folosi 4 registrii a procesorului pentru a stoca temporar date care vor fi executate.Asadar vom folosi EAX,EBX,ECX,EDX care pot fi paragonate cu 4 variabile temporare unde vom stoca date care trebuie executate.

In momentul in care nu am specificat nici o instructie registrii momentan nu au nici o valoare


eax 0x0 0
ecx 0x0 0
edx 0x0 0
ebx 0x0 0

Probabil va intrebati in care ordine pot fi introduse datele in registrii spre exemplu care este primul registru?

Ei bine ordinea este urmatoarea EAX EBX ECX EDX.Vom muta valoarea functiei write in primul registru

Pentru a scrie instructiile trebuie creata o sectie .text , avand in vedere oricum faptul ca vom chema si functii externe syscall va trebui specificata si o sectie pentru external routines si anume .globl _start

dupa care vom crea sectia _start unde vom incepe sa executam instructiile pe care le vom scrie

Asadar vom incepe cu prima instructie


.text
.globl _start
_start

movl $4, %eax

Dupa executarea acestei instructii registrul EAX va avea valoarea 4


eax 0x4 4
ecx 0x0 0
edx 0x0 0
ebx 0x0 0

Functia write are nevoie de cateva argumente pentru a stampa datele si anume primul argument pe care va trebui sa il specificam functiei dupa ce a fost incarcata in registrul EAX este unde anume vrem sa stampam datele?

In linux exista 3 tipuri de fisiere chemate file descriptor

Standard input identificat de catre valoarea 0 acet fisier preia input-ul de la tastiera spre exemplu

Standard output identificat de catre valoarea 1 , acest file este folosit pentru a stampa informatia

Standard error indenbtificat de catre valoarea 2, acest file este folosit pemtru a stampa mesajele de erroare.

Pentru a stampa datele vom folosi standard output, asadar primul argument va fi specificat in al doilea registru si anume EBX , standard output are ca valoare 1 asadar urmatoarea instructie pe care o vom scrie este


movl $1, %ebx

Acum registrii au urmatoarele valori


eax 0x4 4
ecx 0x0 0
edx 0x0 0
ebx 0x1 1

Al doilea parametru a functiei care trebuie specificat este adresa in memorie unde se afla datele pe care vrem sa la stampam.In urmatorul registru vom incarca adresa etichetei cu urmatoarea instructie


leal NickName, %ecx

Asadar registrii vor avea urmatoarele valori



eax 0x4 4
ecx 0x8049098 134516888
edx 0x0 0
ebx 0x1 1

Dupa cum observati instructia leal NickName, %ecx a incarcat in ECX adresa unde se afla datele si anume 0x8049098 care este echivalent cu 134516888 in decimal.Al treilea argument al functiei care trebuie incarcat in urmatorul registru si anume EDX este lungimea pe care vrem sa o stampam din datele pe care le/am declarat (lungimea nickname-ului este pyth0n3 7+1 newline).


movl $8, %edx

In acest moment registrii vor avea urmatoarele date


eax 0x4 4
ecx 0x8049098 134516888
edx 0x8 8
ebx 0x1 1

Deci pana aici i-am spus procesorului sa faca urmatorul lucru.Mergi la adresa 0x8049098 si stampeaza pe ecran

urmatoarele 8 caractere.Daca am fi specificat mai multe caractere ar fi stampat ceea ce se afla dupa 8 caractere pe care le-am declarat in memorie(incercati, o sa va stampeze garbage).In momentul in care instructiile au fost declarate ,nu ramane decat sa chemam procesorul ca sa execute ceea ce am facut pana acum.Urmatoarea instructie va face acest lucru , este un interupt (kernelul va trimite un semnal procesurului ca sa execute ceea ce a fost incarcat in registrii acum.


int 0x80

O data ce aceasta instructie vine executata , vor fi stampate pe ecran 8 caractere din datele declarate.(ultimul caracter este doar un newline).Dupa ce datele vor fi stampate registrii vor avea urmatoarele valori


eax 0x8 8
ecx 0x8049098 134516888
edx 0x8 8
ebx 0x1 1

Dupa cum observati valoarea din registru EBX este in EAX acum.Dupa ce codul vine executat se va face un return

in EAX.Acum nu ramane decat sa declaram instructiile necesare pentru a iesi din program , asadar vom folosi ca syscall exit care are valoarea 1.Dupa cum am specificat , ordinea pentru a incarca instructiile in registrii este urmatoarea .Prima valoare in EAX , iar urmatorii parametrii in EBX,ECX,EDX.Atentie acest lucru este valabil doar pentru procesoarele intel 32 biti.Deci vom chema valoare 1 in registrul EAX.


movl $1, %eax

Asadar vom avea urmatoarele valori


eax 0x1 1
ecx 0x8049098 134516888
edx 0x8 8
ebx 0x1 1

Dupa cum observati registrii nu au fost modificati si unele valori pe care le-am incarcat inainte au ramas

in registrii.Atunci cand veti scrie cod putin mai complicat in assembly va trebui sa aduceti registrul la statul initial sau uneori sa daceti un decrement dealtfel registrul va detine valoarea care a fost incarcata initial.

Urmatorul parametru pentru functia exit este un return code.Aici putem specifica modul in care vrem sa iesim din program.Un return cod 0 va iesi curat , se poate specifica un return code 1 cand vrem sa iesim cu o erroare.

In cazul nostru nu exista o erroare , asadar vom iesi cu 0.Primul si singurul parametru al functiei exit va fi 0.


movl, $0 %ebx

Dupa cum observati instructiile pe care le-am declarat au fost executate si valorile au fost urcate in registrii

EAX va avea valoarea 1 , EBX va avea valoarea 0 iar restul registrilor vor avea valorile precedente


eax 0x1 1
ecx 0x8049098 134516888
edx 0x8 8
ebx 0x0 0

Pentru a executa ceea ce este acum in EAX va trebui sachemam un inerrupt , acest semnal va spune procesorului sa execute ce gaseste acum in EAX.Nu vor fi executate datele din registrul ECX si EDX deoarece valoarea pe care

am puso in registrul EAX si este echivalenta cu functia exit chiama doar un singur parametru si anume 0 pe care l-am pus in registrul EBX .Asadar vor fi executate datele din EAX SI EBX


int $0x80

In acest caz am iesit din program , nu mai vine nimic stampat pe ecran , dar totusi putem observa cu care exist status am iesit.In Linux se poate face acest lucru folosind urmatorul comand imediat dupa ce am executat un program.


echo $?

In acest tutorial am facut un simplu exemplu in assembly si am observat ce se intampla atunci cand fiecare instructie vine executata.

Acest lucru poate fi facut cu un debugger,asadar nu uitati sa asablati codul cu extensii pentru debugging daca vreti sa vedetice face

pentru a asambla codul se vafolosi as.


as -ggstabs nickname.s -o nickname.o

Note: Am specificat optiunea ggstabs doar pentru debugging ,asadar il puteti urca intrun debugger preferat ca sa observati cum vin alocate datele.In momentul de fata vom avea un objec code nickname.o care trebuie ytrecut prin linker.Vom folosi ld pentru linking , asadar vom crea un executabil.


ld nickname.o -o nickname

Evident in momentul in care il veti trece in debugger veti observa mai multi registrii.Eu m-am limitat ca sa nu duc in erroare.Fiecare registru are rolul lui si fiecare registru este necesar.Un simplu exemplu pe care il veti pbserva atunci cand si doar daca veti face debugging este urmatorul.Presupunem ca am creat un breakpoint inainte ca prima instructie sa fie executata si vrem sa observam valorile

care sunt stocate in momentul de fata in registrii.Atentie nu in fiecare registru trebuie neaparat sa existe valori, unii vor avea un flag sau nici o valoare.

eax            0x0	0
ecx 0x0 0
edx 0x0 0
ebx 0x0 0
esp 0xbffff840 0xbffff840
ebp 0x0 0x0
esi 0x0 0
edi 0x0 0
eip 0x8048074 0x8048074 <_start>
eflags 0x200212 [ AF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x0 0

Dupa cum observati unii registrii au anumite valori precum registrul ESP care va avea intotdeuna o adresa de memorie si anume adresa dintyrun anumit segment de memorie chemat stack (probabil este interesant atunci cand se va scrie un exploit , stack overflow spre exemplu sau buffer overflow).EIP va detine intotdeuna adresa din top a segmentului de memorie chemat stack.Un alt registru inportant este EIP care este un instruction pointer , acest registru va detine intoteauna urmatoarea adresa urmatoarei sintructii care trebuie executate.Sa nu uitam faptul ca si instructiile pe care le folosim trebuie sa fie stocate undeva in memorie.Pentru acest tutorial ma voi opri aici deoarece

in momentul de fata nu am folosit alti registrii pentru a putea explica ceea ce fac.Va las exemplele codului din acest tutorial si nu in ultimul rand pentru cai care sunt curiosi voi traduce codul assembly 32 bit in 64 bit doar ca sa observati diferentele.

#nickname.s 32 bit intel gas AT&T syntax assembly


.data
NickName:
.ascii "pyth0n3\n"
.text

.globl _start

_start:
movl $4, %eax
movl $1, %ebx
leal NickName, %ecx
movl $24, %edx
int $0x80

movl $1, %eax
movl $0, %ebx
int $0x80

#nickname.s 64 bit intel gas AT&T syntax assembly


.data
NickName:
.ascii "pyth0n3\n"
.text
.globl _start


_start:
movq $1, %rax
movq $1, %rdi
movq $NickName, %rsi
movq $8, %rdx
syscall
movq $60, %rax
movq $0, %rdi
syscall

Cum am mai spus exista mai multe variante pentru a scrie acest cod.

Exemplu :

Vom stampa "OK" pe ecran intrun mod divers sintaxa pentr 32 bit


.text
.globl _start
_start:
pushl $0x0a6b6f
mov %esp, %ecx
mov $0x4, %edx
mov $0x4, %eax
movl $1, %eax
movl $0, %ebx
int $0x80

Probabil exista greseli de exprimare, nu ezitati sa scrieti , le voi corecta.

###END

Edited by pyth0n3
Link to comment
Share on other sites

In ultima bucata de cod exista o greseala, astept sa imi spuneti unde e

O alta alternativa pentru a stampa un nickname de 7 caractere + newline in GAS Intel 32 biti AT&T syntax ar fi urmatoarea

Intructia pushl pune 4 byte in stack dupa care datele vin stocate in ECX si executate, observati ca nickname-ul este scris in hex invers


.text
.globl _start
_start:
pushl $0x68747970
movl %esp, %ecx
movl $0x4, %edx #len
movl $0x1, %ebx
movl $0x4, %eax
int $0x80

pushl $0x0a336e30
movl %esp, %ecx
movl $0x4, %edx #len
movl $0x1, %ebx
movl $0x4, %eax
int $0x80

movl $1, %eax
movl $0, %ebx
int $0x80

Link to comment
Share on other sites

Felicitari pentru tutorial. Intr-adevar sunt mai multe greseli, si anume:

Dupa cum observati unii registrii au anumite valori precum registrul ESP care va avea intotdeuna o adresa de memorie si anume adresa dintyrun anumit segment de memorie chemat stack (probabil este interesant atunci cand se va scrie un exploit , stack overflow spre exemplu sau buffer overflow).

EIP va detine intotdeuna adresa din top a segmentului de memorie chemat stack. <- ai vrut sa spui ca EBP (Extended Base Pointer) detine adresa din topul stack-ului

[...]

In ultima bucata de cod exista o greseala, astept sa imi spuneti unde e

Eroarea se afla atunci cand este specificata lungimea string-ului ASCII ce trebuie printat de functia "write()"



.text # codul in sine

.globl _start

_start:
pushl $0x0a6b6f # 0a 6b 6f(h) => ASCII "\nko" 0a [\n] ; 6b [k] ; 6f [o] -- (little-endian)
mov %esp, %ecx # muta valoarea lui esp in ecx (dupa schema folosita de AT&T)

mov $0x4, %edx # 4h in EDX -- functia write
mov $0x4, %eax # ce ia ca parametru 4h (4d) lungimea string, de fapt trebuie 3 ( len("ok\n") == 3 )

# exit(0)
movl $1, %eax # exit
movl $0, %eb # return code 0

int $0x80 # tipic Linux -- process interrupt (pe viitor daca se va discuta despre Exploit Development, modelul Linux-ului de abordare prin process interrupt prezinta o mare gaura de securitate)

Si apropo, ai spus

Vom stampa "OK" pe ecran intrun mod divers sintaxa pentr 32 bit

0x6f 0x6b == "ok" (lower case) , insa ai mentionat "OK" (upper case) in hex asta inseamna: 0x4f 0x4b

Pentru cine vrea sa invete mai multe: The Art of Assembly -- ce foloseste HLA (High Level Assembly -- ce este specificat si de pyth0n3 in primul post din thread)

Insa cine vrea hardcore (pe romaneste: ceva de calitate), sa citeasca manualele de la Intel (cine cauta va fi rasplatit si cine cauta cu adevarat inseamna ca este interesat, Google pentru download link).

Edited by Flubber
Link to comment
Share on other sites

Bineinteles m/am referit la ESP (Extended Stack Pointer) care va sta intotdeauna in top la stack si la EIP (Extended Istruction Pointer( care va detine adresa urmatoarei instructii care vine executate.E doar o greseala de exprimare in context pe care am facuto.

Link to comment
Share on other sites

Bineinteles m/am referit la ESP (Extended Stack Pointer) care va sta intotdeauna in top la stack si la EIP (Extended Istruction Pointer( care va detine adresa urmatoarei instructii care vine executate.E doar o greseala de exprimare in context pe care am facuto.

Cred ca faci confuzie intre ESP si EBP.

La inceputul unui executabil in debugger (gdb spre exemplu), valoarea lui ESP este copiata in EBP. Din acest moment EBP va servi ca Base Pointer, iar Stack Pointer-ul (SP) va avea valori diferite acestea depinzand de flow-ul programului si ce se executa (operatii de PUSH, POP, CALL etc.). EBP va avea mereu (spre exemplu) valoarea 0 fiindca ESP se va referi la EBP cand va face diferite "salturi".

Spre exemplu intr-un CALL atunci cand se intra in el pentru executarea continutului, se impinge pe stack o adresa de return, la operatia PUSH a acestei adrese, ESP se va schimba si va folosi EBP pe post de referinta.


[B]$> gcc -g hello.c -o hello[/B]
[B]
$> gdb -q hello[/B]
Reading symbols from /home/x/Desktop/hello...done.
[B](gdb) disassemble main[/B]
Dump of assembler code for function main:
0x080483b4 <+0>: push %ebp[B] # Extended Base Pointer pe stack[/B] [COLOR=#b22222][B][1][/B][/COLOR]
0x080483b5 <+1>: mov %esp,%ebp [B]# valoarea lui ESP [/B][B]copiata in EBP - din acest moment EBP va fi "base pointer",
va arata [I]"TOP-ul stack-ului"[/I] in timp ce ESP va creste in jos (catre 0xffffffff)[/B] [COLOR=#b22222][B][2][/B][/COLOR]
0x080483b7 <+3>: and $0xfffffff0,%esp [B]# operatie AND[/B] [COLOR=#b22222][B][3][/B][/COLOR]
0x080483ba <+6>: sub $0x10,%esp [B]# esp - 10[/B]
0x080483bd <+9>: movl $0x8048494,(%esp) [B]# esp == functia __dso_handle [COLOR=#b22222][4][/COLOR][/B]
0x080483c4 <+16>: call 0x80482f0 <puts@plt> [B]# printf[/B]
0x080483c9 <+21>: leave
0x080483ca <+22>: ret
End of assembler dump.

[4] __dso_handle:


[B](gdb) disassemble 0x8048494[/B]
Dump of assembler code for function __dso_handle:
0x08048490 <+0>: add %al,(%eax)
0x08048492 <+2>: add %al,(%eax)
0x08048494 <+4>: dec %eax
0x08048495 <+5>: gs
0x08048496 <+6>: insb (%dx),%es:(%edi)
0x08048497 <+7>: insb (%dx),%es:(%edi)
0x08048498 <+8>: outsl %ds:(%esi),(%dx)
0x08048499 <+9>: and %dh,0x6f(%edi)
0x0804849c <+12>: jb 0x804850a
0x0804849e <+14>: and %eax,%fs:(%eax)
End of assembler dump.

[1] 0x080483b4 => valoarea lui ESP in hex la "PUSH EBP" (inainte sa se execute)


[B](gdb) break *0x080483b4[/B]
Breakpoint 1 at 0x80483b4: file hello.c, line 4.
[B](gdb) break *0x080483b5[/B]
Breakpoint 2 at 0x80483b5: file hello.c, line 4.
[B](gdb) break *0x080483b7[/B]
Breakpoint 3 at 0x80483b7: file hello.c, line 4.
[B](gdb) run[/B][I] [...][/I]


Breakpoint 1, main () at hello.c:4
4 {

[B](gdb) x/h $esp[/B]
0xbffff36c: 0x1ce6 [B]# 7398[/B][B](d)[/B]

[2] 0x080483b5 => valorile lui ESP si EBP in hex dupa ce "PUSH EBP" s-a executat


[B](gdb) continue[/B]
Continuing.

Breakpoint 2, 0x080483b5 in main () at hello.c:4
4 {
[B](gdb) x/h $esp[/B]
0xbffff368: -3096
[B](gdb) x/h $ebp[/B]
0xbffff3e8: 0 [B]# valoare 0 fiindca a fost impins pe STACK, iar registrul este "initializat"
(este pregatit pentru viitoare operatii) cu valoare 0[/B]

[3] 0x080483b7 => valorile lui ESP si EBP dupa ce s-a executat instructia de "MOV ESP, EBP"


[B](gdb) continue[/B]
Continuing.

Breakpoint 3, 0x080483b7 in main () at hello.c:4
4 {
[B](gdb) x/h $esp[/B]
0xbffff368: -3096
[B](gdb) x/h $ebp[/B]
0xbffff368: -3096

Din acest moment, ESP va fi dinamic (isi va schimba valoarea in functie de flow-ul programului), iar EBP nu, el va fi folosit drept referinta (de-aia este si numit Base Pointer).


[B](gdb) disassemble main[/B]
Dump of assembler code for function main:
0x080483b4 <+0>: push %ebp
0x080483b5 <+1>: mov %esp,%ebp
=> 0x080483b7 <+3>: and $0xfffffff0,%esp
0x080483ba <+6>: sub $0x10,%esp
0x080483bd <+9>: movl $0x8048494,(%esp)
0x080483c4 <+16>: call 0x80482f0 <puts@plt>
0x080483c9 <+21>: leave
0x080483ca <+22>: ret
End of assembler dump.
[B](gdb) break *0x080483c4[/B]
Breakpoint 4 at 0x80483c4: file hello.c, line 5.
[B](gdb) continue[/B]
Continuing.

Breakpoint 4, 0x080483c4 in main () at hello.c:5
5 printf("Hello world!\n");
[B](gdb) stepi[/B]
0x080482f0 in puts@plt ()
[B](gdb) x/h $esp[/B]
0xbffff34c: -31799
[B](gdb) x/h $ebp[/B]
0xbffff368: -3096 [B]#[/B] [B]valoarea lui EBP a ramas ca la inceput, neschimbata[/B]

Edited by Flubber
adaugare
Link to comment
Share on other sites

Cred ca faci confuzie intre ESP si EBP.

La inceputul unui executabil in debugger (gdb spre exemplu), valoarea lui ESP este copiata in EBP. Din acest moment EBP va servi ca Base Pointer, iar Stack Pointer-ul (SP) va avea valori diferite acestea depinzand de flow-ul programului si ce se executa (operatii de PUSH, POP, CALL etc.). EBP va avea mereu (spre exemplu) valoarea 0 fiindca ESP se va referi la EBP cand va face diferite "salturi".

Cunosc acest concept, doar ca pentru a explica acest lucru ar fi trebuit sa explic conceptul de memory segmentation , dupa care sa explic unde anume se afla segmentul de stack ?, faptul ca este dinamic , modul in care vin puse datele in stack , modul in care vin extrase (conceptul push, pop)Ceea ce ai explicat tu mai sus se intampla atunci cand vin definite mai multe functii inafara de main si in momentul in care o functie chiama alte functii

Oricum inainte ca valoarea ESP sa fie copiata in EBP , valoarea pe care o are EBP initial vine copiata in stack deoarece EBP la randul lui poate fi folosit in diferite functii.Deci presupunem ca se impinge pe stack adresa de return , in acest moment vine impinsa pe stack si valoarea pe care o detine EBP in momentul respectiv (o copie de backup) dupa care ESP vine copiat in EBP .

0xbffff368: -3096 # valoarea lui EBP a ramas ca la inceput, neschimbata

Deoarece EBP si-a facut o copie de backup pe stack inainte sa preia valoarea din ESP


STACK SEGMENT
-----------
- address -
-----------
- address -
-----------
- address -
-----------
- return -
-----------
- EBP(bck)-
----------- <- EBP preia ESP aici dupa ce si-a facut o copie de backup pe stack
- address -
----------- <- ESP devine dinamic de aici


Doar ca nu am explicat in detaliu conceptul de memory segmentation si de aceea nu am vrut sa vorbesc despre STACK mai mult decat sa ii pomenesc numele.

Edited by pyth0n3
Link to comment
Share on other sites

[...]

Oricum inainte ca valoarea ESP sa fie copiata in EBP , valoarea pe care o are EBP initial vine copiata in stack deoarece EBP la randul lui poate fi folosit in diferite functii.Deci presupunem ca se impinge pe stack adresa de return , in acest moment vine impinsa pe stack si valoarea pe care o detine EBP in momentul respectiv (o copie de backup) dupa care ESP vine copiat in EBP .

[...]


STACK SEGMENT
-----------
- address -
-----------
- address -
-----------
- address -
-----------
- return -
-----------
- EBP(bck)-
----------- <- EBP preia ESP aici dupa ce si-a facut o copie de backup pe stack
- address -
----------- <- ESP devine dinamic de aici


Doar ca nu am explicat in detaliu conceptul de memory segmentation si de aceea nu am vrut sa vorbesc despre STACK mai mult decat sa ii pomenesc numele.

Intocmai. In cazul care l-am prezentat singura functie este main(), insa chiar daca ar fi fost alte functii (declarate dupa main() si folosite ulterior in aceasta) la fiecare CALL atunci cand se intra in functie, se impinge pe stack o adresa de return insa si EBP, iar valoarea lui ESP este din nou copiata in EBP, insa asa cum ai mentionat si tu, este pentru acel segment de memorie (alocat functiei), in exemplul scris de mine, asta inseamna pana la RET, dupa care la intoarcerea din functie (prin adresa impinsa la inceput de return) valorile revin in registrii si depinzand de functie, valoarea computatiei este returnata in EAX (de cele mai multe ori asa se intampla, EAX fiind Accumulator).

Eu am mentionat faptul ca in post ai scris despre ESP ca acesta arata mereu topul stack-ului cand de fapt, el creste spre 0xffffffff (downwards), iar EBP ramane static. Acesta este motivul pentru care am spus ca faci confuzie, faptul ca trebuia sa specifici EBP in loc de ESP (desi afirmatia ta este partial adevarata, ESP arata pentru inceput top-ul stack-ului pana ce valoarea acestuia este copiata in EBP pentru segmentul respectiv in care se opereaza, iar apoi revenind la segmentul anterior, valorile vor corespunde respectivului pentru a continua flow-ul programului).

Link to comment
Share on other sites

Eu am mentionat faptul ca in post ai scris despre ESP ca acesta arata mereu topul stack-ului cand de fapt, el creste spre 0xffffffff (downwards), iar EBP ramane static. Acesta este motivul pentru care am spus ca faci confuzie, faptul ca trebuia sa specifici EBP in loc de ESP (desi afirmatia ta este partial adevarata, ESP arata pentru inceput top-ul stack-ului pana ce valoarea acestuia este copiata in EBP pentru segmentul respectiv in care se opereaza, iar apoi revenind la segmentul anterior, valorile vor corespunde respectivului pentru a continua flow-ul programului).

ESP va arata intotdeauna top/ul segmentului de stack ,EBP poate fi paragonat cu un pointer in internul segmentului de stack , chiar daca exista un schimb de valori intre acesti registrii sa nu uitam ca segmentul chemat stack se termina acolo unde vine impinsa ultima valoare fie ea statica sau dinamica (acest loc este top/ul segmetului chemat stack si aici va fi intotdeauna ESP) E evident ca stack/ul va merge in down dar acel down este top/ul segmentului.

 -----------  <- Aici incepe segmentul de stack  0xc0000000 
- address -
-----------
- address -
-----------
- address -
-----------
- return -
-----------
- EBP(bck)-
----------- <- EBP preia ESP aici dar ramane in internul segmentului stack
- address -
-----------
- address -
-----------
- TOP -
----------- <- ESP devine dinamic de aici (Aici este top/ul segmentului stack)




Edited by pyth0n3
Link to comment
Share on other sites

Am inteles acum la ce te referi tu. Top-ul ca fiind in ordine inversa dupa modelul LIFO (Last In First Out) ar reprezenta top-ul ca fiind ultimul element impins pe stack, asemenator ultimului prosop pus in cosul de rufe este primul ce va fi scos din cosul de rufe. Eu luasem in ordinea in care se executa instructiile, topul pentru mine fiind EBP pentru acel segment, insa ce-i drept, ESP la returnare va spune lui EBP care va fi baza in urmatorul segment.

Ai dreptate in privinta modelului LIFO, scuze. Abordam diferit.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...