Ebben a fejezetben megmutatjuk, hogyan kell közvetlenül egy mikroprocesszorra programozni.
Az ARM processzort választottuk, hiszen nagy valószínűséggel olyan mobiltelefonunk van ami ezt a típust használja. Nem törekszünk teljességre, csak az adatmozgatás, összehasonlítás, indirekt memória elérés és a verem alaphasználatát mutatjuk be.
Ezen a linken találunk egy teljes értékű ARM CPU szimulátort: https://cpulator.01xz.net/?sys=arm , a továbbiakban ezt használjuk a próbáink során.
Rögtön készítsük el az első programunkat!
Másoljuk be a következő program szöveget a weblapon középen elhelyezkedő szövegmezőbe:
.global _start _start: mov r1, #2 mov r2, #3 add r4, r1, r2 bkpt
A fenti program kiszámítja a 2 + 3 összeadás értékét. Először az r1 regiszterbe másolja a 2-t (mov r1, #1), majd r2-be a 3-mat (mov r2, #3), majd r4-be helyezi az r1 és r2 összegét.
A „Compile and Load (F5)” feliratú gombot megnyomva a szöveges assembly program lefordul és a bináris változata betöltődik a memória 00000000 címére:
Az Address oszlop a memória címet mutatja, az opcode a gépi kódu utasítást. A fenti programkód gépi kódú alakja a következő:
e3a01002e3a02003e0814002e1200070
Ez a 16 byte jelenik meg a memóriában a nullás címtől kezdve a szimulátorban. Az eredeti forráskódot a Disassembly oszlopban piros kicsi számokkal jelölve látjuk. Rögtön észrevehető, hogy vannak olyan jelölések pl: _start, aminek nincs konkrét gépi kódú megfelelője.
A Disassembly visszafordítást jelent, a rendszer megpróbálja visszaalakítani a gépi kódú utasításokat assembly nyelvű szöveggé.
A sárgával kijelölt sor mutatja, hogy az utasításszámláló melyik memóriacímet fogja végrehajtani. Nyomjuk meg többször a F2 billenytyűt, ami a soronkénti végrehajtást jelenti, közben figyeljük meg a regiszterek változását a bal oldalon (pirossal kiemeli a szimulátor a változásokat). A bkpt parancsig elég elmenni.
Jól látható, hogy a megfelelő regiszterekben megjelentek a számok, és az eredmény is a r4-esben.
Az előző ábrán látható a Current Program Status Register (CPSR) regiszter legalul (ez az állapot regiszter). Ennek egyes bitjei jelölik azt, hgy egy művelet eredménye nulla, negatív, átvitelt eredményez, stb. Nem kell tudni, hogy a 32 bitből ezek melyek pontosan, a mellette megjelenő NZCVI szöveg egyes betűi jelölik a főbb állapotbiteket.
Vátsunk vissza a forráskód szerkesztő fűlre: ctrl+e, vagy alul rákattintva, majd másoljuk be az alábbi kódot a szerkesztőbe, majd fordísuk és kövessük nyomon:
.global main _start: mov r0, #2 /* r0 = 2 */ cmp r0, #3 /* r0 - 3. Negatív bit egyre fog váltani */ addlt r0, r0, #5 /* lt => less than. Ha (r0 < 3) akkor adj hozzá 5-öt */ cmp r0, #3 /* r0 - 3. Zero bit 1 lesz. Negative bit 0 lesz */ addlt r0, r0, #5 /* Ha (r0 < 3) akkor adj hozzá 1-et az r0-hoz */ bkpt
A 2. sorban a cmp (compare) összehasonlítja az argumentumait. Utána az addlt csak akkor ad hozzá 5-öt az r1 hez ha (less than) feltételnek megfelelően az alábbi táblázatban N!=V azaz a N és V kapcsolók értéke nem egyezik meg.
Feltétel kód | Jelentés | Státusz bitek |
EQ | Equal | Z==1 |
NE | Not Equal | Z==0 |
GT | Signed Greater Than | (Z==0) és (N==V) |
LT | Signed Less Than | N!=V |
GE | Signed Greater Than or Equal | N==V |
LE | Signed Less Than or Equal | (Z==1) vagy (N!=V) |
MI | Negative (or Minus) | N==1 |
PL | Positive (or Plus) | N==0 |
.global main _start: mov r4, #10 loop_label: sub r4, r4, #1 cmp r4, #0 bne loop_label
A fenti kódrészlet 10-től 0-ig számol visszafelé, az r1 regiszterben tárolja a 10-et kezdetben, majd lépésenként csökkenti 1-el. sub r4, r4, #1 jelentése r4 = r4 - 1, azaz mentsd el az r4 aktuális értékét mínusz 1-et az r4 ben. * A cmp összehasonlítja 0-val a r4-et, a bne (branch if not equals) ágazz el ha nem egyenlő.
.data /* .data szekció dinamikusan jön létre, előre nehéz megmondani, hogy hol fog elhelyezkedni */ var1: .word 3 /* var1 változó */ var2: .word 4 /* var2 változó */ .text /* kód kezdete */ .global _start _start: ldr r0, adr_var1 @ töltsük be a var1 memória címét az r0-be ldr r1, adr_var2 @ töltsük be a var2 memória címét az r1-be ldr r2, [r0] @ r2 be töltsük be az r0-regiszter által mutatott címen lévő értéket str r2, [r1] @ r2 értékét írjuk ki az r1 által mutatott memória címre bkpt adr_var1: .word var1 /* a var1 címe lesz letárolva itt */ adr_var2: .word var2 /* a var2 címe lesz letárolva itt */
A példában látható hogyan lehet változókat tárolni és a memóriacímeket ahol el vannak tárolva, hogyan lehet módosítani, illetve betölteni a regiszterekbe tartalmukat. A legutolsó utasítás felülírja adr_var2 helyen tárolt értéket, ezt a módosulást a harmadik memory fülön tudjuk ellenőrizni.
A verem adattárolásra szolgál, memóriacímét az sp regiszter mutatja és új érték verembe helyezésekor az sp regiszter csökken 4 byteot.
.global _start _start: mov r0, #2 /* r0 = 2 */ push {r0} /* r0 értékének tárolása a veremben */ mov r0, #3 /* r0 = 3 */ pop {r0} /* r0 korábbi értékének visszatöltése a veremből. */ bkpt
A push utasítás után a „memory” fülön megnézhetjük hogy a 000000 memóriacím elé 4 byte-al kezdődően beíródott a 2 egy 32 bites egészként.