Assembly Intel X86 Linux - 2.ZH Jegyzet¶
Ez egy jegyzetet az Intel x86 Linux Assembly témaköréből. Nem teljes és egyáltalán nem biztos hogy hibátlan, de aránylag rövid és érhető.
- Szöveggel elmagyaráztam mindent
- tele van példa programokkal
- Házi feladatok megoldása elmagyarázva
A leírásban lévő videók nagyon jól emagyarázzák az Assembly működését, de egy kicsit más szintaxist mutatnak be.
Ha hibát találsz vagy valamivel kiegészítenéd, nyugodtan módosítsd a dokumentumot vagy jelezd felém.
Alap assembly program¶
Regiszterek¶
adat mozhatás (mov)¶
mov eax, ebx - eax értéke legyen ebx
Verem kezelése¶
push eax- eax mnetése a stack-repop eax- a stack "tetején" lévő (legutóbb bele rakott) elem kivétele azeax-beespregiszter (stack pointer regiszter): mindig a verem "tetejére" mutat, a legutóbb berakott elemre
Indirekt címzés (tömbök)¶
Kapcsoszárójelekkel tudjuk elérni egy memória címen tárolt adatok értékét.
-
[eax]- az eax regiszterben lévő számot memória címként használva megpróbál betölteni a memóriából egy adatot. Ha memória cím volt benne vagy szerencséd volt és pont létező memóriára mutat a benne lévő szám, megkapot az általa mutatott értéket. -
[CIMKE]- A CIMKE címkén (ami ugye egy memória címként működik) lévő adatot kéri be a memóriából
A kapcsoszárójelekben lévő memóriacímhez tudunk hozzá adni. Ez annyi bájttal elcsúsztatja a memóriacímet, amennyit hozzá adtunk. Az integer tipusú változóink 4 bájtosak, szóval ha egy tömb következő eleme érdekel 4-et kell hozzá adnod a memória címhez.
[TOMB+0*4]- TOMB címkén lévő adatsorozat (ismertedd nevén tömb) 1. eleme[TOMB+1*4]- TOMB címkén lévő tömb 2. eleme[TOMB+2*4]- TOMB címkén lévő tömb 3. eleme
Ha megfigyeled ez olyan, mint egy C-s tömb: NULLÁTÓL indexelünk!!!
Egy másik regiszterrel akár dinamikusan is indexelhetünk egy tömböt
[SZAMOK+eax*4]- TOMB címkén lévő "tomb" eax-edik eleme
És persze az eltolás is működik regiszterekből bejövő címekkel is.
[ebx+0*4]- ebx-ben lévő memóriacímen lévő tömb 1. eleme[ebx+ecx*4]- ebx-ben lévő memóriacímen lévő tömb ecx-edik eleme
mov, add, cmp, stb. utasításokban nem használhatunk mindkét paramétérben indirekt címzést!!!!!!!
Ez hibás (el se olvasd!): mov [TOMB + 2*4], [eax + edx*4] // HIBÁS
Aritmetika (matek műveletek)¶
Összeadás¶
add eax, ebx // összedja az eax-ben lévő számból az ebx-ben lévő számot, az eredményt az eax-be rakja
add ecx, 3 // ecx = ecx + 3
add ecx, [SZAMOK + 0*4] // ecx = ecx + SZAMOK tömb nulladik indexű, az az 1. eleme
add [SZAMOK + 1*4], ecx // SZAMOK 2. eleme = SZAMOK 2. eleme + ecx
Kivonás¶
sub eax, ebx // kivinja az eax-ben lévő szából az ebx-ben lévő számot, az eredményt az eax-be rakja
sub ecx, 3 // ecx = ecx - 3
sub ecx, [SZAMOK + 0*4] // ecx = ecx- SZAMOK tömb nulladik indexű, az az 1. eleme
sub [SZAMOK + 1*4], ecx // SZAMOK 2. eleme - SZAMOK 2. eleme + ecx
Szorzás¶
Külön van előjeles (imul) és elője nélküli (mul) szorzás utasítás. Mindkettő ugyan úgy működik: adsz neki egy számot és megszorozza fixen az eax-ben lévő számmal, az eredmény az eax-be kerül, ha nem fér bele, az edx-ben folytatódik, ezért szorzásnál nem szabad semmi fontosat az edx-ben hagyni.
Előjel nélküli szorzás (mul)¶
push edx // biztonsági mentjük edx-et
mul ebx // eax = eax * ebx
mul eax // eax = eax * eax
pop edx // mentés vissza töltése
Előjeles szorzás (imul)¶
pont ugyan úgy működik mint az előjel nélküli, csak kaphatunk negatív eredményt is
Osztás¶
Külön van elője nélküli (div) és előjeles (idiv) osztás utasítás. Mindkettő hasonlóképpen működik: elosztja az eax-ben lévő számot az általad megadott számmal, az eredmény az eax-be kerül, a maradék pedig az edx-be.
Előjel nélküli osztás (div)¶
az edx-be nullát kell rakni előjel nélküli osztás előtt!
Előjeles osztás (idiv)¶
Előjeles osztás előtt meg kell hívni a cdq utasítást, ez felülírja az edx-et, így ebben ne legyen semmi!
Osztás példa¶
.intel_syntax noprefix
.data
SZAMLALO: .int 4
NEVEZO: .int 2
EREDMENY: .int 0
MARADEK: .int 0
.text
// ha lenne valami a regiszterekben lementeni őket a stack-re
push edx
push eax
mov ebx, [NEVEZO] // ebx = NEVEZO "változó"
mov eax, [SZAMLALO] // eax = SZAMLALO "változó"
cdq
idiv eax, ebx // előjeles osztás: eax = eax / ebx
// eredmények vissza mentése
mov [EREDMENY], eax
mov [MARADEK], edx
// vissza tölteni a lementett regisztereket
pop eax
pop edx
Ciklusok működése¶
Assembly-ben nincsenek ciklusok. Fut a program vegrehajtasa fentrol lefele sorrol sorra, mi csak a ugralni tudunk benne feljebb es lejebb cimkekre.
Ugrás működése¶
jmp <memoriacim> - a vezerles a memoriacim-re ugrik es onnan folytatodik
Ne feledd a cimkek sima memoriacimekkent viselkednek, igy lehet rajuk ugrani.
Vegtelen ciklus:¶
Feltételes ugrás¶
Vannak feteteles ugro utasitasok is, amik csak akkor ugranak, ha egy feltetel teljesul. (ez nem igaz, a háttérben nem igy mukodik, de igy a legerthetobb és a használata a mi feladatainkban ilyen)
cmp edx, 42 // edx es a 42 osszehasonlitasa
jz kezdet // ha az elozo cmp utasitasban megadott adatok egyenlok, a kezdet cimkere ugrik
Osszehasonlito utasitasok:¶
| Relacio | Eloojeles | Elojeltelen |
|---|---|---|
| == | je, jz | je, jz |
| != | jne, jnz | jne, jnz |
| < | jl, jnge | jb, jnae |
| <= | jle, jng | jbe, jna |
| > | jg, jnle | ja, jnbe |
| >= | jge, jnl | jae, jnb |
| Relació | Művelet | Jelentés |
|---|---|---|
| == | je | Jump Equal |
| != | jne | Jump Not Equal |
| < | jl | Jump Lover |
| <= | jle | Jump Lover or Equal |
| > | jg | Jump Greater |
| >= | jge | Jump Greater or Equal |
for ciklus¶
mov ecx, 0 // legyen ez az "i" valtozo, amiben taroljuk epp hanyadik elemnel jar a ciklus
for: // for ciklus kezdetet jelzo cimke
// kilepesi feltetel, ha ez teljesul, lepjunk ki a for-bol
cmp ecx, 5 // ha ecx >= 5, az az ha az aktualis elem a tomb 5. egyben utolsó eleme,
jge forend // akkor ugorjunk a forend cimkere
// kod helye, amit a forban akarunk futtatni
inc ecx // i + 1
jmp for // ha ide ert a program vissza ugrik a for elejere
forend: // for ciklus vege
2. Házifeladat megoldása:¶
feladat által meghatározott változók¶
- esi = integer (4 bajtos) tomb kezdetenek a cime
- edx = tomb elemeinek szama
- edi = kimeneti tomb cime
- eax = masolt elemek szama
feladatok:¶
- Azokat a tomb elemeket melyek 2-nel nagyobbak vagy egyenloek masoljuk egymas uton a kimeneti tombbe.
- Ekozben az eax regiszterbe szamoljuk, hogy hany elemet masoltunk a kimeneti tombbe. A ciklus vegere ennek az eax regiszternek kell tartalmaznia hany elem kerult osszesen a kimeneti tombbe.
Megoldás¶
mov ecx, 0 // legyen ez az "i" valtozo, amiben taroljuk epp hanyadik elemnel jar a ciklus
mov eax, 0 // ide fog kerulni hogy hany elemet raktunk a kimeneti tombbe
for: // for ciklus kezdetet jelzo cimke
// kilepesi feltetel, ha ez teljesul, lepjunk ki a for-bol
cmp ecx, edx // ha ecx >= edx, az az ha az aktualis elem a tomb utolso utani eleme,
jge forend // akkor ugorjunk a forend cimkere
push ebx // elfogytak a regisztereink, de kéne még egy, ezért az épp nem haszáltat
// lementem a stack-re
mov ebx, [esi + 4*ecx] // ebx-be rakom, a bemeneti tomb aktualis elemet.
// Azert van erre szukseg, mert mov és cmp mindket oldalan
// nem lehet indirekt címzés ([eax])
// if elagazas
cmp ebx, 2
jl hamis // if(elem < 2) -> hamis | ha a a tomb eleme kisebb mint ketto, atugorjuk az igaz kodot
// igaz a feltetel
mov [edi + 4*eax], ebx // kiementi tomb kovetkezo ures helyere masolom a bemeneti tomb i-edik elemet
inc eax // masolt elem szamlalo + 1
hamis: // ha hamis, nem csinalunk semmit
pop ebx // regiszter régi értékének (tomb elemeinek szama) vissza töltése,
// mivel újra szükség lesz rá a for elején
inc ecx // i + 1
jmp for // ha ide ert a program vissza ugrik a for elejere
forend: // for ciklus vege
Függvények készítése¶
Nem érdekel az elmélet, csak a példa program
Ha szeretnél egy C-ből hívható Assembly függvényt csinálni, egy globális címkét kell csinálnod olyan nevűt, ami a függvény neve lesz.
pl. ezt a C függvényt valósítsuk meg Assembly-ben: void eljaras(){ return; }
// ez a rész a függvény nevét adja meg
.global eljaras
eljaras:
ret // ez az utasítás vissza ugrik, oda ahonnan meghívták a függvényt, szóval nagyjából return; megfelelője
Assembly-ból egy függvényt a call fuggvenyNeve utasítással tudunk meghívni.
Visszatérési érték¶
Okos programozók réges régen egy messzi-messzi... kitalálták, hogy a függvény visszatérési értékét adjuk vissza az eax regiszterben.
int ketto(){ return 2; }
.global ketto
ketto:
mov eax, 2 // ami az eax-ben van a függvény vége előtt, az lesz a visszatérési érték
ret
cdecl eljarashívás konvenció¶
A főprogram, ahonnan meghíták a függvényt is tárolna egy rakás dolgot a regiszterekben. Ha te a metódusodban neki állsz és felül írod a regiszterek tartalmát, jól szétcseszed a főprogramot. Erre találták ki "szídepöl" konvenciót, ami annyit mond: metsél le mindent stack-ra, amit használsz, aztán ha végeztél töltsed vissza.
A legbiztosabb ha lementesz mindent az eax-en és az esp-n kívül:
.global nemRontElMindent
nemRontElMindent:
push ebp
push ebx
push ecx
push edx
push esi
mov eax, 69
pop esi
pop edx
pop ecx
pop ebx
pop ebp
ret
Paraméterek és ret¶
Ez bonyolult lesz. ;( A függvény paraméterei a stack (verem) en adódnak át. A stack utolsó elemére mutat az esp regiszter, így a [esp] címzéssel elérhetjük a legutóbb berakott elemet, az [esp + 1*4]-el a következőt, stb. A stack megfordítja a beadott értékek sorrendjét: amit utoljára bele raktunk azt fogjuk tudni elsőnek kivenni.
A függvény hívás tesz egy akkor nagy szivességet hogy fordított sorrendben dobálja be a verembe a paramétereket, így mi jó sorrendben tudjuk kivenni, viszont utána jön a köcsög függvény hívás és bele teszi a címet, amire majd a függvény végén a ret utasításnak vissza kell térni.
Szóval így néz ki a verem egy függvény meghívásakor:
| Név | Címzés |
|---|---|
| 3. parameter | [esp + 3*4] |
| 2. parameter | [esp + 2*4] |
| 1. parameter | [esp + 1*4] |
| Visszateresi cim (ret) | [esp + 0*4] |
Ne ilyesszen meg hogy fejjel lefelé van a verem, valamiért lefele bővül: ha bele rakunk egy elemet az az aljára kerül és az esp pointer is lentebb mutat egyel.
A gond ott kezdődik, hogy ha rakunk még dolgokat a verembe, mondjuk mivel cdecl fegyvert fog a fejünkhöz és kötelez rá, akkor egy idő után foggalmunk nem lesz hogy mennyit kéne az esp-hez hozzá adni, hogy elérjük a paramétereket.
Erre nyújt megoldást az ebp (bázi pointer): a függvény elején ebbe elmentjük az esp értékét, így már pakolhatunk akár mit a verembe, az ebp-vel, mindig el fogjuk tudni érni a paramétereket. Annyira kell figyelni, hogy ebp-t is le kell menteni a cdecl szerint, mielőtt még módosulna.
ebp használatával cdecl konvenzió mellett így néz ki a vermünk:
| Név | Címzés |
|---|---|
| 3. parameter | [ebp + 4*4] |
| 2. parameter | [ebp + 3*4] |
| 1. parameter | [ebp + 2*4] |
| Visszateresi cim (ret) | [ebp + 1*4] |
| cdecl miatt régi ebp | [ebp + 0*4] |
| cdecl miatt régi ebx | [esp + 3*4] |
| cdecl miatt régi ecx | [esp + 2*4] |
| cdecl miatt régi edx | [esp + 1*4] |
| cdecl miatt régi esi | [esp + 0*4] |
Ez pedig egy tökéletes függvény hívás:¶
Ha így hívod meg a függvényt, csinálhatsz bármit a regiszterekkel, rakhatsz bármit a stack-re, nem lesz gond és bármikor el fogod tudni érni a paramétereket.
c függvény:
A prológust, epilógust és a paraméterek elérését érdemes utasításól utasításra megtanulni.
Assembly megvalósítás:
.global tokeletesFuggveny
tokeletesFuggveny:
// Prológus:
push ebp
mov ebp, esp
push ebx
push ecx
push edx
push esi
mov ebx, [ebp + 2*4] // 1. paraméter metöltése az ebx-be
mov ecx, [ebp + 3*4] // 2. paraméter metöltése az ecx-be
add ebx, ecx // ebx = ebx + ecx (szam1 + szam2)
mov eax, ebx // visszatérési érték beállítása az összeadás eredményére
// Epilógus:
pop esi
pop edx
pop ecx
pop ebx
mov esp, ebp
pop ebp
ret
3. Házi feladat megoldás¶
.intel_syntax noprefix
.text
/*
Elvárt függvény prototípus (C-ben):
int filterElements(int input[] , int length, int output[]);
*/
.global filterElements
filterElements:
# Függvény epilógus
push ebp
mov ebp, esp
push ebx
push ecx
push edx
push esi
/*
sum = edx
idx = ecx
average = eax
length = ebx
input = ebx
output = ecx
outIdx = edx
*/
mov edx, 0 # sum = 0
mov ebx, [ebp + 4 + 2*4] # ebx = length
# for (idx = 0 ; idx < length ; idx++)
mov ecx, 0 # idx = 0
for1:
cmp ecx, ebx # ha idx >= length
jge for1end # akkor ugorjunk a for1end cimkere (kilép a for-ból)
mov eax, [ebp+4+4] # eax = input tömb címe
add edx, [eax + ecx*4] # sum += input[idx];
inc ecx # idx++eckGAS
jmp for1 # ha ide ert a program vissza ugrik a for elejere
for1end: # for ciklus vege
# average = sum / length;
mov eax, edx
mov edx, 0
cdq # előjel helyes osztáshoz meg kell hívnunk
idiv ebx
# 2. for ciklus
mov ecx, 0 # idx = 0
mov edx, 0 # outIdx = 0
for2:
cmp ecx, ebx # ha idx >= length
jge for2end # akkor ugorjunk a for1end cimkere (kilép a for-ból)
push ebx # length lementése
mov ebx, [ebp+4+4] # ebx = input tömb címe
# If (input [idx] > average)
cmp eax, [ebx + ecx*4] # ha avarage >= input[idx]
jge false # akkor ugorjunk a false cimkere
# output[outIdx] = input[idx]
push eax # avarage lementése
mov eax, [ebx + ecx*4] # eax = input[idx]
push ecx # idx lementése
mov ecx, eax
mov eax, [ebp +4+ 3*4] # eax = output címe
mov [eax + edx*4], ecx
pop ecx # idx vissza töltése
pop eax # avarage vissza töltése
inc edx # outIdx++
false:
pop ebx # length vissza töltése
inc ecx # idx++
jmp for2 # ha ide ert a program vissza ugrik a for elejere
for2end: # for ciklus vege
# visszatérési érték
mov eax, edx
# függvény epilógus
# regiszterek vissza töltése
pop esi
pop edx
pop ecx
pop ebx
mov esp, ebp
pop ebp
ret
4. Házi feladat megoldás¶
.intel_syntax noprefix
.text
.global caseInvert
caseInvert:
/*
input = ebx
output = ecx
idx = edx
ch = al
ideiglenes matek izé = bl
*/
# Függvény epilógus
push ebp
mov ebp, esp
push ebx
push ecx
push edx
push esi
mov ebx, [ebp + 4 + 1*4] # Input betöltése
mov ecx, [ebp + 4 + 2*4] # output betöltése
# for(; input[idx]!=0; idx++)
mov edx, 0 # idx = 0
for:
cmp byte ptr [ebx + 1 * edx], 0
je forend
mov al, [ebx + 1 * edx]
# elágazás: if (’a’<= ch && ch <= ’z’ )
cmp al, 'a'
jl nemkisbetu
cmp al, 'z'
jg nemkisbetu
# output [idx] = ’A’ + (ch−’a’)
push ebx
mov bl, al
sub al, 'a'
add al, 'A'
mov byte ptr [ecx + 1 * edx], al
pop ebx
jmp kesz
# elseif ( ’A’ <= ch && ch <= ’Z’ )
nemkisbetu:
cmp al, 'A'
jl nemnagybetu
cmp al, 'Z'
jg nemnagybetu
# output [idx] = ’a’ + (ch−’A’);
push ebx
mov bl, al
sub al, 'A'
add al, 'a'
mov byte ptr [ecx + 1 * edx], al
pop ebx
jmp kesz
# ág else
nemnagybetu:
mov byte ptr [ecx + 1 * edx], al # output [idx] = ch
kesz:
inc edx # idx++
jmp for
forend:
mov byte ptr [ecx+1*edx], 0x00 # output [idx] = ch
# függvény epilógus
pop esi
pop edx
pop ecx
pop ebx
mov esp, ebp
pop ebp
ret