Assembly school: assembler pre centrálne procesory architektúry ARM. Montážna škola: jazyk montáže pre centrálne procesory architektúry ARM Príkazy zostavy arma

Najprv ARM pomerne nezvyčajný assembler (ak sa naučíte z x86, MCS51 alebo AVR). Má ale pomerne jednoduchú a logickú organizáciu, takže sa rýchlo učí.

V ruštine je veľmi málo dokumentácie pre assembler. Môžem vám poradiť ísť na 2 odkazy (možno nájdete viac a poviete mi? Budem vďačný.):
Architektúra a inštrukčný systém procesorov RISC rodiny ARM - http://www.gaw.ru/html.cgi/txt/doc/micros/arm/index.htm
Pochopenie ARM assembleru, zo série článkov „GBA ASM“, autor Mike H, trans. Aquila - http://wasm.ru/series.php?sid=21.

Posledný odkaz mi veľmi pomohol, rozpustil hmlu =). Druhá vec, ktorá môže pomôcť, je napodiv C kompilátor IAR Embedded Workbench pre ARM(ďalej len jednoducho IAR EW ARM). Faktom je, že už od pradávna dokázal (ako však všetky sebaúctyhodné kompilátory) skompilovať kód C do kódu assembleru, ktorý je zase rovnako ľahko zostavený assemblerom IAR do objektového kódu. Preto nie je nič lepšie, ako napísať jednoduchú funkciu v C, skompilovať ju do assembleru a hneď bude jasné, ktorý príkaz assembleru čo robí, ako sa odovzdávajú argumenty a ako sa vracia výsledok. Zabijete dve muchy jednou ranou – naučíte sa assembler a zároveň získate informácie o tom, ako integrovať montážny kód do projektu v jazyku C, ktorý som trénoval na počítacej funkcii CRC16 a vďaka tomu som dostal jej plnú verziu v assembleri .

Tu je pôvodná funkcia v C (u16 znamená unsigned short, u32 znamená unsigned int, u8 znamená unsigned char):
// súbor crc16.c
u16 CRC16 (neplatný* databuf, veľkosť u32)
{
u16 tmpWord, crc16, idx;
u8bitCnt;
#define CRC_POLY 0x1021;

Crc16 = 0;
idx=0;
kým (veľkosť! = 0)
{
/* x alebo vysoký bajt crc16 a vstupný bajt */
tmpWord = (crc16>>8) ^ (*(((u8*)databuf)+idx));
/* zapíšte výsledok do vysokého bajtu crc16 */
tmpWord<<= 8;
crc16 = tmpWord + (0x00FF & crc16);
pre (bitCnt=8;bitCnt!=0;bitCnt--)
{
/* skontrolujte CRC batérie vyššej kategórie */
if (crc16 & 0x8000)
{
crc16<<= 1;
crc16 ^= CRC_POLY;
}
inak
crc16<<= 1;
}
idx++;
veľkosť --;
}
návrat crc16;
}

Získanie kódu zostavy IAR EW ARM na vygenerovanie je veľmi jednoduché. V možnostiach súboru crc16.c (pridaného do projektu) som zaškrtol políčko Prepísať zdedené nastavenia a potom na karte Zoznam začiarknite 3 políčka - Výstupný súbor assembleru, Zahrňte zdroj A Zahrňte informácie o rámci hovoru(aj keď pravdepodobne nemusíte začiarknuť posledné políčko - generuje to veľa nepotrebných CFI-smernice). Po kompilácii bol výsledný súbor project_folder\ewp\at91sam7x256_sram\List\crc16.s. Tento súbor sa dá rovnako jednoducho pridať do projektu ako súbor C (normálne sa skompiluje).

Samozrejme, keď som dal kompilátoru C nezostrihanú verziu kódu C, dalo mi to taký zoznam zostavy, že som z toho nič nerozumel. Ale keď som z funkcie odstránil všetky operátory C okrem jedného, ​​bolo to jasnejšie. Potom som pridal operátory C krok za krokom a toto je konečný výsledok:

; súbor crc16.s
NAME crc16
VEREJNÁ CRC16

CRC_POLY EQU 0x1021

SECTION `.text`:CODE:NOROOT(2)
ARM

// u16 CRC16 (void* databuf, veľkosť u32)
;R0 - návratový výsledok, CRC16
;R1 - parameter veľkosti
;R2 - parameter databuf (bol tam pri zadávaní R0)
;R3, R12 - dočasné registre

CRC16:
PUSH (R3,R12) náhodne som zistil, že R3 a R13 by sa mali uložiť
; nie je potrebné. Ale rozhodol som sa, že si to pre každý prípad uložím
; deje.
MOVS R2,R0 ;teraz R2==databuf
MOV R3,#+0
MOVS R0,R3; crc16 = 0
CRC16_LOOP:
CMP R1, #+0 ;všetky bajty spracované (veľkosť==0)?
BEQ CRC16_RETURN ;ak áno, tak ukončite
LSR R3, R°, #+8; R3 = crc16>>8
LDRB R12, ;R12 = *databuf
EOR R3, R3, R12 ;R3 = *databuf ^ HIGH (crc16)
LSL R3, R3, #+8;R3<<= 8 (tmpWord <<= 8)
AND RO, R0, #+255;crc16 &= 0x00FF
PRIDAŤ R0, R0, R3 ;crc16 = tmpWord + (0x00FF & crc16)
MOV R12, #+8; bitCnt = 8
CRC16_BIT_LOOP:
BEQ CRC16_NEXT_BYTE ;bitCnt == 0?
TST R0,#0x8000 ;Ešte nie sú spracované všetky bity.
BEQ CRC16_BIT15ZERO ;Skontrolujte najvýznamnejší bit crc16.
LSL R0,R0,#+1;crc16<<= 1
MOV R3, #+(LOW (CRC_POLY)) ;crc16 ^= CRC_POLY
ORR R3,R3,#+(VYSOKÉ(CRC_POLY)<< 8) ;
EOR R°, R3, R°;
B CRC16_NEXT_BIT

CRC16_BIT15ZERO:
LSL R0,R0,#+1;crc16<<= 1
CRC16_NEXT_BIT:
SUBS R12,R12,#+1 ;bitCnt--
B CRC16_BIT_LOOP ;

CRC16_NEXT_BYTE:
PRIDAŤ R2,R2,#+1 ;databuf++
SUBS R1,R1,#+1 ;veľkosť--
B CRC16_LOOP ;prehľad cez všetky bajty

CRC16_RETURN:
POP (R3,R12) ;obnovenie registrov
BX LR ;opustenie podprogramu, R0==crc16

Kompilátor C IAR vytvára prekvapivo dobrý kód. Mám veľmi malý úspech v optimalizácii. Vyhodil som iba extra dočasný register, ktorý chcel kompilátor použiť (z nejakého dôvodu vzal LR ako extra dočasný register, hoci stačili R3 a R12), a tiež som odstránil pár ďalších príkazov, ktoré kontrolovali počítadlá a nastavovali príznaky ( jednoduchým pridaním prípony S k potrebným tímom).

Procesory CISC vykonávajú pomerne zložité operácie v jednej inštrukcii, vrátane aritmetických a logických operácií s obsahom pamäťových buniek. Inštrukcie procesora CISC môžu mať rôzne dĺžky.

Naproti tomu RISC má relatívne jednoduchý inštrukčný systém s jasným rozdelením podľa typu operácie:

  • práca s pamäťou (čítanie z pamäte do registrov alebo zápis z registrov do pamäte),
  • spracovanie údajov v registroch (aritmetické, logické, posuny údajov doľava/doprava alebo rotácia bitov v registri),
  • príkazy podmienených alebo nepodmienených prechodov na iné adresy.

Spravidla (ale nie vždy a iba ak sa kód programu dostane do vyrovnávacej pamäte radiča) sa vykoná jeden príkaz v rámci jedného cyklu procesora. Dĺžka inštrukcie procesora ARM je pevná – 4 bajty (jedno počítačové slovo). V skutočnosti sa moderný procesor ARM môže prepnúť do iných prevádzkových režimov, napríklad do režimu THUMB, keď dĺžka inštrukcie dosiahne 2 bajty. To vám umožní urobiť kód kompaktnejším. Tento režim však v tomto článku nepokrývame, pretože nie je podporovaný v procesore Amber ARM v2a. Z rovnakého dôvodu nebudeme brať do úvahy režimy ako Jazelle (optimalizované na vykonávanie Java kódu) a nebudeme brať do úvahy príkazy NEON – príkazy pre operácie s viacerými dátami. Koniec koncov, študujeme čistý inštrukčný systém ARM.

Registre procesora ARM.

Procesor ARM má niekoľko sád registrov, z toho tento moment programátor má k dispozícii iba 16 režimov činnosti procesora, v závislosti od prevádzkového režimu sa vyberie príslušná banka registrov. Tieto prevádzkové režimy:

  • aplikačný režim (USR, užívateľský režim),
  • režim alebo režim supervízora operačný systém(SVC, režim supervízora),
  • režim spracovania prerušenia (IRQ, režim prerušenia) a
  • režim spracovania „naliehavé prerušenie“ (FIRQ, režim rýchleho prerušenia).

To znamená, že keď napríklad dôjde k prerušeniu, procesor sám prejde na adresu programu obsluhy prerušenia a automaticky „prepne“ banky registrov.

Procesory ARM starších verzií majú okrem vyššie uvedených prevádzkových režimov aj ďalšie režimy:

  • Prerušiť (používa sa na spracovanie výnimiek prístupu do pamäte),
  • Nedefinované (používa sa na implementáciu koprocesora v softvéri) a
  • režim privilegovaných úloh operačného systému Systém.

Procesor Amber ARM v2a tieto ďalšie tri režimy nemá.

Pre Amber ARM v2a môže byť sada registrov reprezentovaná nasledovne:

Registre r0-r7 sú rovnaké pre všetky režimy.
Registre r8-r12 sú spoločné len pre režimy USR, SVC, IRQ.
Register r13 je ukazovateľ zásobníka. Je svoj vo všetkých režimoch.
Register r14 - register návratu z podprogramu je tiež odlišný vo všetkých režimoch.
Register r15 je ukazovateľ na spustiteľné inštrukcie. Je to spoločné pre všetky režimy.

Je vidieť, že režim FIRQ je najviac izolovaný, má najviac vlastných registrov. To sa robí tak, že niektoré veľmi kritické prerušenia môžu byť spracované bez uloženia registrov v zásobníku, bez straty času.

Osobitná pozornosť by sa mala venovať registru r15, tiež známemu ako pc (Program Counter) - ukazovateľ na spustiteľné príkazy. S jeho obsahom môžete vykonávať rôzne aritmetické a logické operácie, čím sa vykonávanie programu presunie na iné adresy. Konkrétne pre procesor ARM v2a implementovaný v systéme Amber však existujú určité jemnosti v interpretácii bitov tohto registra.

Faktom je, že v tomto procesore sú v registri r15 (pc) okrem skutočného ukazovateľa na spustiteľné príkazy obsiahnuté nasledujúce informácie:

Bity 31:28 - príznaky pre výsledok aritmetickej alebo logickej operácie
Bity 27 - maska ​​IRQ prerušenia, pri nastavení bitu sú prerušenia zakázané.
Bity 26 - maska ​​prerušenia FIRQ, rýchle prerušenia sú vypnuté, keď je bit nastavený.
Bity 25:2 - skutočný ukazovateľ na programové inštrukcie zaberá iba 26 bitov.
Bity 1:0 - aktuálny prevádzkový režim procesora.
3 - Vedúci
2 - Prerušiť
1 - Rýchle prerušenie
0 - Používateľ

V starších procesoroch ARM sú všetky príznaky a servisné bity umiestnené v samostatných registroch Register aktuálneho stavu programu(cpsr) a Register stavu uloženého programu (spsr), pre prístup ku ktorým existujú samostatné špeciálne príkazy. Toto sa robí s cieľom rozšíriť dostupný adresný priestor pre programy.

Jednou z ťažkostí pri ovládaní ARM assembleru sú alternatívne názvy niektorých registrov. Takže, ako je uvedené vyššie, r15 je rovnaký počítač. Existuje aj r13 - to je ten istý sp (Stack Pointer), r14 je lr (Link Register) - register návratových adries z procedúry. Okrem toho, r12 je rovnaký ip (Intra-Procedure -call scratch register), ktorý používajú kompilátory C špeciálnym spôsobom na prístup k parametrom v zásobníku. Takéto alternatívne pomenovanie je niekedy mätúce, keď sa pozriete na cudzí programový kód – nachádzajú sa tam obe tieto označenia registrov.

Vlastnosti vykonávania kódu.

V mnohých typoch procesorov (napríklad x86) môže byť vykonaný iba prechod na inú adresu programu podľa podmienky. Toto nie je prípad ARM. Každá inštrukcia procesora ARM môže alebo nemusí byť vykonaná podmienečne. To vám umožňuje minimalizovať počet prechodov programom a tým efektívnejšie využívať procesorovú pipeline.

Koniec koncov, čo je to potrubie? Jedna inštrukcia procesora je teraz vybraná z programového kódu, predchádzajúca sa už dekóduje a predchádzajúca sa už vykonáva. To je prípad 3-stupňového pipeline procesora Amber A23, ktorý používame v našom projekte pre dosku Mars Rover2Mars Rover2. Modifikácia procesora Amber A25 má 5-stupňovú pipeline, je ešte efektívnejšia. Je tu však jedno veľké ALE. Príkazy skoku prinútia procesor vyčistiť potrubie a doplniť ho. Vyberie sa teda nový príkaz, no stále nie je čo dekódovať a navyše nie je čo okamžite vykonať. Efektívnosť vykonávania kódu klesá s častými prechodmi. Moderné procesory majú všetky druhy mechanizmov predikcie vetiev, ktoré nejakým spôsobom optimalizujú plnenie potrubia, ale náš procesor toto nemá. V každom prípade bol ARM múdry, aby umožnil vykonanie každého príkazu podmienečne.

Na procesore ARM sú v akomkoľvek type inštrukcie štyri bity podmienky vykonania inštrukcie zakódované v najvyšších štyroch bitoch kódu inštrukcie:

V procesore sú celkom 4 príznaky stavu:
. Negatívny - výsledok operácie bol negatívny,
. Nula - výsledok je nula,
. Carry - prenos nastal pri vykonávaní operácie s číslami bez znamienka,
. oPretečenie - pri vykonávaní operácie s číslami so znamienkom došlo k pretečeniu, výsledok sa nezmestí do registra)

Tieto 4 príznaky tvoria mnoho možných kombinácií podmienok:

kód Prípona Význam Vlajky
4"h0 ekv Rovnaký Sada Z
4"h1 nie Nerovná sa Z jasné
4"h2 cs/hs Noste súpravu / nepodpísanú vyššie alebo rovnako C set
4"h3 cc/lo Noste čisté / neoznačené nižšie C jasné
4"h4 mi Mínus/zápor N set
4"h5 pl Plus / plus alebo nula N jasné
4"h6 vs Pretečenie V set
4"h7 vc Žiadny prepad V jasné
4"h8 Ahoj Bez znamienka vyššie C set a Z jasné
4"h9 ls Nesignované nižšie alebo rovnaké C clear alebo Z set
4" ha ge Značené väčšie alebo rovné N == V
4"hb lt Podpísaných menej ako N = V
4"hc GT Podpísané väčšie ako Z == 0, N == V
4" HD le Podpísané menšie alebo rovnaké Z == 1 alebo N!= V
4" on al Vždy (bezpodmienečné)
4"hf - Neplatný stav

Teraz to vedie k ďalšiemu problému pri učení inštrukcií procesora ARM - k mnohým príponám, ktoré možno pridať do kódu inštrukcie. Napríklad sčítanie za predpokladu, že je nastavený príznak Z, je príkaz addeq ako add + prípona eq . Preskočte na podprogram, ak príznak N=0 je blpl ako bl + prípona pl .

Vlajky (zápor, nula, prenášanie, pretečenie) to isté nie je vždy nastavené počas aritmetických alebo logických operácií, ako sa to stáva, povedzme, v procesore x86, ale iba vtedy, keď to programátor chce. Na tento účel existuje ďalšia prípona k mnemotechnickému povelu: „s“ (v kóde príkazu je zakódovaný bitom 20). Príkaz sčítania teda nemení príznaky, ale príkaz add ich mení. Alebo môže existovať aj príkaz podmieneného sčítania, ktorý však zmení príznaky. Napríklad: addgts. Je zrejmé, že množstvo možných kombinácií názvov príkazov s rôznymi príponami pre podmienené vykonávanie a nastavenie príznakov robí kód zostavy procesora ARM veľmi zvláštnym a ťažko čitateľným. Postupom času si však na to zvyknete a začnete tomuto textu rozumieť.

Aritmetické a logické operácie (Spracovanie údajov).

Procesor ARM môže vykonávať rôzne aritmetické a logické operácie.

Skutočný štvorbitový operačný kód (Opcode) je obsiahnutý v bitoch inštrukcie procesora.

Akákoľvek operácia sa vykoná s obsahom registra a takzvaným posuvným operandom. Výsledok operácie sa uloží do registra. Štvorbitové Rn a Rd sú indexy registrov v aktívnej banke procesora.

V závislosti od bitu I 25 sa operand posunu spracováva buď ako číselná konštanta, alebo ako index druhého registra operandu a dokonca aj operácia posunu hodnoty druhého operandu.

Jednoduché príklady príkazov assembleru by vyzerali takto:

add r0,r1,r2 @ umiestnite súčet hodnôt registrov r1 a r2 do registra r0
sub r5,r4,#7 @ umiestnite rozdiel (r4-7) do registra r5

Vykonané operácie sú kódované takto:

4"h0 a logické AND Rd:= Rn AND operand_radenia
4"h1 eor Logical exclusive OR Rd:= Rn XOR shifter_operand
4"h2 sub Aritmetické odčítanie Rd:= Rn - operand_posunovača
4"h3 rsb Aritmetické spätné odčítanie Rd:= shifter_operand - Rn
4"h4 add Aritmetické sčítanie Rd:= Rn + shifter_operand
4"h5 adc Aritmetické sčítanie plus vlajka prenosu Rd:= Rn + operand_posunovača + vlajka prenosu
4"h6 sbc Aritmetické odčítanie s prenosom Rd:= Rn -operand_posunovača - NOT(Príznak prenosu)
4"h7 rsc Aritmetické spätné odčítanie s Rd:= shifter_operand - Rn - NOT(Carry Flag)
4"h8 tst Logický AND, ale bez uloženia výsledku sa zmenia iba vždy nastavené príznaky Rn AND shifter_operand S bit
4"h9 teq Logical exclusive OR, ale bez uloženia výsledku sa zmenia iba príznaky Rn EOR shifter_operand
Vždy nastavený bit S
4"ha cmp Porovnanie, alebo skôr aritmetické odčítanie bez uloženia výsledku, menia sa iba príznaky Rn - vždy nastavený bit shifter_operand S
4"hb cmn Porovnanie inverzného, ​​alebo skôr aritmetického sčítania bez uloženia výsledku, vždy sa menia len príznaky Rn + shifter_operand S bit
4"hc orr Logické OR Rd:= Rn ALEBO shifter_operand
4"hd mov Kopírovať hodnotu Rd:= shifter_operand (bez prvého operandu)
4"he bic Resetovacie bity Rd:= Rn AND NOT(operand_posun)
4"hf mvn Kopírovať inverznú hodnotu Rd:= NOT shifter_operand (žiadny prvý operand)

Prehadzovač sudov.

Procesor ARM má špeciálny obvod „barrel shifter“, ktorý umožňuje jeden z operandov posunúť alebo otočiť o určený počet bitov pred akoukoľvek aritmetickou alebo logickou operáciou. Ide o pomerne zaujímavú vlastnosť procesora, ktorá umožňuje vytvárať veľmi efektívny kód.

Napríklad:

@násobenie 9 znamená násobenie čísla 8
@ posunutím doľava o 3 bity plus ďalšie číslo
pridať r0, r1, r1, lsl #3 @ r0= r1+(r1<<3) = r1*9

@ násobenie číslom 15 je násobenie číslom 16 mínus číslo
rsb r0, r1, r1, lsl #4 @ r0= (r1<<4)-r1 = r1*15

@ prístup k tabuľke 4 bajtových slov, kde
@r1 je základná adresa tabuľky
@r2 je index prvku v tabuľke
ldr r0,

Okrem logického posunu vľavo lsl existuje aj logický posun vpravo lsr a aritmetický posun vpravo asr (posun zachovávajúci znamienko, najvýznamnejší bit sa násobí vľavo súčasne s posunom).

Dochádza aj k rotácii bitov ROR - bity sa posúvajú doprava a vytiahnuté sa zasúvajú doľava.
Existuje jeden bitový posun cez príznak C - toto je príkaz rrx. Hodnota registra sa posunie o jeden bit doprava. Vľavo je príznak C načítaný do najvýznamnejšieho bitu registra.

Posun môže byť uskutočnený nie pevným konštantným číslom, ale hodnotou tretieho registra operandov. Napríklad:

pridajte r0, r1, r1, lsr r3 @ toto je r0 = r1 + (r1>>r3);
pridajte r0, r0, r1, lsr r3 @ toto je r0 = r0 + (r1>>r3);

Takže shifter_operand je to, čo popisujeme v príkazoch assembleru, napríklad ako "r1, lsr r3" alebo "r2, lsl #5".

Najzaujímavejšie je, že používanie zmien v prevádzke nič nestojí. Tieto posuny (zvyčajne) nevyžadujú ďalšie taktovacie cykly, čo je veľmi dobré pre výkon systému.

Použitie číselných operandov.

Aritmetické alebo logické operácie môžu využívať ako druhý operand nielen obsah registra, ale aj číselnú konštantu.

Bohužiaľ, je tu jedno dôležité obmedzenie. Keďže všetky príkazy majú pevnú dĺžku 4 bajty (32 bitov), ​​nebude možné do nich zakódovať „akékoľvek“ číslo. V operačnom kóde sú už 4 bity obsadené kódom stavu vykonania (Cond), 4 bity pre samotný operačný kód (Opcode), potom 4 bity - register Rd prijímača a ďalšie 4 bity - register prvého operandu. Rn plus rôzne príznaky I 25 (označuje iba číselnú konštantu v kóde operácie) a S 20 (nastavenie príznakov po operácii). Celkovo zostáva len 12 bitov na možnú konštantu, takzvaný shifter_operand – videli sme to vyššie. Keďže 12 bitov dokáže kódovať čísla len v úzkom rozsahu, vývojári procesora ARM sa rozhodli kódovať konštantu nasledovne. Dvanásť bitov posuvného operandu je rozdelených na dve časti: štvorbitový indikátor rotácie encode_imm a aktuálnu osembitovú číselnú hodnotu imm_8.

Na procesore ARM je konštanta definovaná ako osembitové číslo vnútri 32-bitového čísla, otočené doprava o párny počet bitov. To je:

imm_32 = imm_8 ROR (encode_imm *2)

Ukázalo sa to dosť ošemetne. Ukazuje sa, že nie každé konštantné číslo je možné použiť v príkazoch assembleru.

Môžeš písať

pridajte r0, r2, #255 @ konštantu v desiatkovom tvare
pridajte r0, r3, #0xFF @ konštantu v šestnástkovej sústave

pretože 255 je v rozsahu 8 bitov. Tieto príkazy budú skompilované takto:

0: e28200ff pridať r0, r2, #255 ; 0xff
4: e28300ff pridať r0, r3, #255; 0xff

A dokonca môžete písať

pridajte r0, r4, #512
pridajte r0, r5, 0x650000

Kompilovaný kód bude vyzerať takto:

0: e2840c02 pridaj r0, r4, #512 ; 0x200
4: e2850865 pridať r0, r5, #6619136; 0x650000

Samotné číslo 512 sa v tomto prípade do bajtu samozrejme nezmestí. Ale potom si to predstavíme v hexadecimálnom tvare 32'h00000200 a vidíme, že toto je 2 rozšírené doprava o 24 bitov (1 alebo 24). Koeficient rotácie je dvakrát menší ako 24, teda 12. Takže to vyjde shifter_operand = ( 4’hc , 8’h02 ) – toto je dvanásť najmenej významných bitov príkazu. To isté platí pre číslo 0x650000. Pre neho je shifter_operand = ( 4’h8, 8’h65 ).

Je jasné, že nevieš písať

pridajte r0, r1,#1234567

alebo nevieš písať

mov r0, #511

keďže tu číslo nemôže byť reprezentované vo forme imm_8 a encode_imm - faktor rotácie. Kompilátor assembleru vyvolá chybu.

Čo robiť, keď konštantu nemožno priamo zakódovať do operandu shifter_operand? Budeme musieť robiť všelijaké triky.
Napríklad môžete najskôr načítať číslo 512 do voľného registra a potom odčítať číslo:

mov r0, #511
pod r0,r0,#1

Druhým spôsobom, ako načítať konkrétne číslo do registra, je načítať ho zo špeciálne vyhradenej premennej umiestnenej v pamäti:

ldr r7,my_var
.....
my_var: .word 0x123456

Najjednoduchší spôsob, ako to napísať, je takto:

ldr r2 = 511

V tomto prípade (všimnite si znak "="), ak môže byť konštanta reprezentovaná ako imm_8 a encode_imm , ak sa zmestí do bitu 12 shifter_operand , kompilátor zostavy automaticky skompiluje ldr do inštrukcie mov. Ale ak číslo nemôže byť reprezentované týmto spôsobom, potom samotný kompilátor vyhradí pamäťovú bunku v programe pre túto konštantu a sám dá tejto pamäťovej bunke názov a skompiluje príkaz do ldr .

Toto som napísal:

ldr r7,my_var
ldr r8 = 511
ldr r8 = 1024
ldr r9,=0x3456
........
My_var: .word 0x123456

Po kompilácii som dostal toto:

18: e59f7030 ldr r7, ; 50
1c: e59f8030 ldr r8, ; 54
20: e3a08b01 mov r8, #1024 ; 0x400
24: e59f902c ldr r9, ; 58
.............
00000050 :
50: 00123456 .slovo 0x00123456
54: 000001ff .slovo 0x000001ff
58: 00003456 .slovo 0x00003456

Všimnite si, že kompilátor používa adresovanie pamäte relatívne k registru pc (aka r15).

Čítanie pamäťovej bunky a zápis registra do pamäte.

Ako som písal vyššie, procesor ARM môže vykonávať iba aritmetické alebo logické operácie s obsahom registrov. Dáta pre operácie sa musia načítať z pamäte a výsledok operácií sa musí zapísať späť do pamäte. Sú na to špeciálne príkazy: ldr (pravdepodobne z kombinácie “LoaD Register”) na čítanie a str (pravdepodobne “STore Register”) na zápis.

Zdalo by sa, že existujú len dva tímy, no v skutočnosti majú veľa variácií. Stačí sa pozrieť na spôsob, akým sú príkazy ldr /str zakódované na procesore Amber ARM, aby ste videli, koľko pomocných príznakových bitov je L 20, W 21, B 22, U 23, P 24, I 25 - a určujú špecifické správanie príkaz:

  • Bit L 20 určuje zápis alebo čítanie. 1 - ldr, čítanie, 0 - str, zápis.
  • Bit B 22 určuje čítanie/zápis 32-bitového slova alebo 8-bitového bajtu. 1 znamená bajtovú operáciu. Keď sa bajt načíta do registra, najvýznamnejšie bity registra sa vynulujú.
  • Bit I 25 určuje použitie poľa Offset. Ak I 25 ==0, potom Offset je interpretovaný ako číselný offset, ktorý musí byť buď pridaný k základnej adrese z registra, alebo odčítaný. Ale sčítanie alebo odčítanie závisí od bitu U 23.

(Cond) - podmienka na vykonanie operácie. Interpretované rovnakým spôsobom ako pre logické/aritmetické príkazy – čítanie alebo zápis môže byť podmienený.

V texte zostavy teda môžete napísať niečo takéto:

ldr r1, @ do registra r1 prečíta slovo na adrese z registra r0
ldrb r1, @ do registra r1 načíta bajt na adrese z registra r0
ldreq r2, @ podmienené čítanie slov
ldrgtb r2, @ podmienené čítanie bajtu
ldr r3, @ číta slovo na adrese 8 relatívne k adrese z registra r4
ldr r4, @ číta slovo na adrese -16 relatívne k adrese z registra r5

Po zostavení tohto textu môžete vidieť skutočné kódy týchto príkazov:

0: e5901000 ldr r1,
4: e5d01000 ldrb r1,
8: 05912000 ldreq r2,
c: c5d12000 ldrbgt r2,
10: e5943008 ldr r3,
14: e5154010 ldr r4,

Vo vyššie uvedenom príklade používam iba ldr , ale str sa používa takmer rovnakým spôsobom.

Existujú režimy prístupu k pamäti pred indexovaním a po indexovaní. V týchto režimoch sa ukazovateľ prístupu do pamäte aktualizuje pred alebo po vykonaní inštrukcie. Ak ste oboznámení s programovacím jazykom C, potom ste oboznámení s konštrukciami prístupu ukazovateľa ako ( *psource++;) alebo ( a=*++psource;). Procesor ARM implementuje tento režim prístupu do pamäte. Pri vykonaní príkazu čítania sa aktualizujú dva registre naraz - register prijímača prijme hodnotu načítanú z pamäte a hodnota v registri ukazovateľa do pamäťovej bunky sa posunie dopredu alebo dozadu.

Písanie týchto príkazov je podľa mňa trochu nelogické. Zvyknúť si na to trvá dlho.

ldr r3, ! @psrc++; r3 = *psrc;
ldr r3, , #4 @ r3 = *psrc; psrc++;

Prvý príkaz ldr najprv zvýši ukazovateľ a potom načíta. Druhý príkaz najprv číta a potom zvyšuje ukazovateľ. Hodnota ukazovateľa psrc je v registri r0.

Všetky príklady diskutované vyššie boli pre prípad, keď bol bit I 25 v príkazovom kóde resetovaný. Ale stále sa dá nainštalovať! Potom hodnota poľa Offset nebude obsahovať číselnú konštantu, ale tretí register zúčastňujúci sa operácie. Navyše, hodnota tretieho registra môže byť stále vopred posunutá!

Tu sú príklady možných variácií kódu:

0: e7921003 ldr r1, @ čítať adresu - súčet hodnôt z registrov r2 a r3
4: e7b21003 ldr r1, ! @ to isté, ale po prečítaní r2 sa zvýši o hodnotu z r3
8: e6932004 ldr r2, , r4 @ najprv sa načíta adresa r3 a potom sa r3 zvýši o r4
c: e7943185 ldr r3, @ čítať adresu r4+r5*8
10: e7b43285 ldr r3, ! @ prečítaj adresu r4+r5*32, po prečítaní sa r4 nastaví na hodnotu tejto adresy
14: e69431a5 ldr r3, , r5, lsr #3 @ adresa pre čítanie r4, po vykonaní príkazu r4 sa nastaví na r4+r5/8

Toto sú variácie príkazov na čítanie/zápis v procesore ARM v2a.

V starších modeloch procesorov ARM je táto rozmanitosť príkazov ešte väčšia.
Je to spôsobené tým, že procesor umožňuje napríklad čítať nielen slová (32-bitové čísla) a bajty, ale aj polovičné slová (16 bitov, 2 bajty). Potom sa k príkazom ldr / str pridá prípona „h“ zo slova polovičné slovo. Príkazy budú vyzerať ako ldrh alebo strh. Existujú aj príkazy na načítanie polovičných slov ldrsh alebo bajtov ldrsb interpretovaných ako čísla so znamienkom. V týchto prípadoch sa najvýznamnejší bit načítaného slovného slova alebo bajtu násobí na najvýznamnejšie bity celého slova v registri prijímača. Napríklad načítanie polslova 0xff25 pomocou príkazu ldrsh v cieľovom registri má za následok 0xffffff25 .

Viacnásobné čítanie a zápis.

Príkazy ldr /str nie sú jediné na prístup k pamäti. Procesor ARM má tiež príkazy, ktoré umožňujú vykonávať prenosy blokov – obsah niekoľkých po sebe idúcich slov môžete načítať z pamäte a niekoľkých registrov naraz. Do pamäte môžete zapísať aj hodnoty niekoľkých registrov postupne.

Mnemotechniky príkazov na prenos blokov začínajú v koreňovom adresári ldm (LoaD Multiple) alebo stm (Store Multiple). Ale potom, ako to už v ARM býva, začína príbeh s príponami.

Vo všeobecnosti príkaz vyzerá takto:

op(cond)(režim) Rd(, {Register list} !}

Prípona (Cond) je zrozumiteľná, je to podmienka pre vykonanie príkazu. Prípona (režim) je režim prenosu, o tom neskôr. Rd je register, ktorý určuje základnú adresu v pamäti pre čítanie alebo zápis. Výkričník za registrom Rd označuje, že sa po operácii čítania/zápisu zmení. Zoznam registrov, ktoré sú načítané z pamäte alebo stránkované do pamäte, je (Zoznam registrov).

Zoznam registrov je uvedený v zložených zátvorkách oddelených čiarkami alebo ako rozsah. Napríklad:

stm r0,(r3,r1, r5-r8)

Pamäť bude zapísaná mimo prevádzky. Zoznam jednoducho uvádza, ktoré registre sa zapíšu do pamäte a to je všetko. Príkazový kód obsahuje 16 bitov vyhradených pre zoznam registrov, čo je presne počet registrov v procesorovej banke. Každý bit v tomto poli označuje, ktorý register sa zúčastní operácie.

Teraz o režime čítania/zápisu. Tu je priestor na zmätok. Faktom je, že pre rovnakú akciu možno použiť rôzne názvy režimov.

Ak urobíme malú lyrickú odbočku, potom sa musíme porozprávať o... hromade. Zásobník je spôsob prístupu k údajom typu LIFO - Last In First Out (wiki) - posledný dovnútra, prvý von. Zásobník je široko používaný pri programovaní pri volaní procedúr a ukladaní stavu registrov na vstupe funkcií a ich obnove pri výstupe, ako aj pri odovzdávaní parametrov volaným procedúram.

Existujú, kto by si myslel, štyri typy pamäťových zásobníkov.

Prvý typ je Plne zostupne. Toto je, keď ukazovateľ zásobníka ukazuje na obsadený prvok zásobníka a zásobník rastie smerom k klesajúcim adresám. Keď potrebujete vložiť slovo do zásobníka, najprv sa zníži ukazovateľ zásobníka (Decrement Before), potom sa slovo zapíše na adresu ukazovateľa zásobníka. Keď potrebujete odstrániť počítačové slovo zo zásobníka, slovo sa načíta pomocou aktuálnej hodnoty ukazovateľa zásobníka a potom sa ukazovateľ posunie nahor (Increment After).

Druhý typ je Full Ascending. Zásobník nerastie nadol, ale nahor, smerom k väčším adresám. Ukazovateľ tiež ukazuje na obsadený prvok. Keď potrebujete vložiť slovo do zásobníka, najprv sa zvýši ukazovateľ zásobníka, potom sa slovo zapíše do ukazovateľa (Increment Before). Keď potrebujete odstrániť zo zásobníka, najprv si prečítajte ukazovateľ zásobníka, pretože ukazuje na obsadený prvok, potom sa ukazovateľ zásobníka zníži (Decrement After).

Tretím typom je Empty Descending. Zásobník rastie smerom nadol, ako v prípade Úplného zostupovania, ale rozdiel je v tom, že ukazovateľ zásobníka ukazuje na neobsadenú bunku. Preto, keď potrebujete vložiť slovo do zásobníka, záznam sa urobí okamžite a ukazovateľ zásobníka sa zníži (Decrement After). Pri odstraňovaní zo zásobníka sa ukazovateľ najprv zvýši a potom sa načíta (Increment Before).

Štvrtým typom je Empty Ascending. Dúfam, že je všetko jasné - zásobník rastie nahor. Ukazovateľ zásobníka ukazuje na prázdny prvok. Vloženie do zásobníka znamená napísanie slova na adresu ukazovateľa zásobníka a zvýšenie ukazovateľa zásobníka (Increment After). Vyskočiť zo zásobníka - znížte ukazovateľ zásobníka a prečítajte si slovo (Znížiť predtým).

Pri vykonávaní operácií na zásobníku je teda potrebné zvýšiť alebo znížiť ukazovateľ - (Increment/Decrement) pred alebo po (Pred/Po) čítaní/zápise do pamäte, v závislosti od typu zásobníka. Napríklad procesory Intel majú špeciálne príkazy na prácu so zásobníkom, ako je PUSH (vloženie slova do zásobníka) alebo POP (vysunutie slova zo zásobníka). V procesore ARM nie sú žiadne špeciálne inštrukcie, ale používajú sa inštrukcie ldm a stm.

Ak implementujete zásobník pomocou inštrukcií procesora ARM, získate nasledujúci obrázok:

Prečo bolo potrebné tomu istému tímu dať iné mená? Vôbec tomu nerozumiem... Tu, samozrejme, treba poznamenať, že štandard zásobníka pre ARM je stále Full Descending.

Ukazovateľ zásobníka v procesore ARM je register sp alebo r13. Toto je vo všeobecnosti dohoda. Samozrejme, zápis stm alebo čítanie ldm je možné vykonať aj s inými základnými registrami. Treba si však zapamätať, ako sa sp register líši od iných registrov – môže sa líšiť v rôznych prevádzkových režimoch procesora (USR, SVC, IRQ, FIRQ), pretože majú svoje vlastné banky registrov.

A ešte jedna poznámka. Napíšte takýto riadok do kódu zostavy ARM push(r0-r3), Samozrejme môžete. Len v skutočnosti to bude ten istý tím stmfd sp!,(r0-r3).

Nakoniec uvediem príklad kódu assembleru a jeho zostaveného rozloženého textu. Máme:


stmfd sp!,(r0-r3)
stmdb sp!, (r0-r3)
push(r0-r3)

@tieto tri pokyny sú rovnaké a robia to isté
pop(r0-r3)
ldmia sp!,(r0-r3)
ldmfd r13!, (r0-r3)

Stmfd r4,(r0-r3,r5,r8)
stmea r4!,(r0-r3,r7,r9,lr,pc)
ldm r5,(r0,pc)

Po kompilácii dostaneme:

0: e92d000f push (r0, r1, r2, r3)
4: e92d000f push (r0, r1, r2, r3)
8: e92d000f push (r0, r1, r2, r3)
c: e8bd000f pop (r0, r1, r2, r3)
10: e8bd000f pop (r0, r1, r2, r3)
14: e8bd000f pop (r0, r1, r2, r3)
18: e904012f stmdb r4, (r0, r1, r2, r3, r5, r8)
1c: e8a4c28f stmia r4!, (r0, r1, r2, r3, r7, r9, lr, pc)
20: e8958001 ldm r5, (r0, ks)

Prechody v programoch.

Programovanie nie je možné bez prechodov. V každom programe existuje cyklické vykonávanie kódu a volania procedúr a funkcií a existuje aj podmienené vykonávanie častí kódu.

Procesor Amber ARM v2a má len dva príkazy: b (od slova Branch - vetva, prechod) a bl (Branch with Link - prechod pri zachovaní návratovej adresy).

Syntax príkazu je veľmi jednoduchá:

b(pod)označenie
bl(cond)label

Je jasné, že akékoľvek prechody môžu byť podmienené, to znamená, že program môže obsahovať zvláštne slová, ako sú tieto, vytvorené z koreňov „b“ a „bl“ a prípony podmienky (Cond):

beq, bne, bcs, bhs, bcc, blo, bmi, bpl, bvs, bvc, bhi, bls, bge, bgt, ble, bal, b

bleq, blne, blcs, blhs, blcc, bllo, blmi, blpl, blvs, blvc, blhi, blls, blge, blgt, blle, blal, bl

Rozmanitosť je úžasná, však?

Príkaz skoku obsahuje 24-bitový offset Offset. Adresa skoku sa vypočíta ako súčet aktuálnej hodnoty ukazovateľa pc a čísla posunu posunutého o 2 bity doľava, interpretovaného ako číslo so znamienkom:

Nový ks = ks + posun*4

Rozsah prechodov je teda 32 MB dopredu alebo dozadu.

Pozrime sa, čo je to prechod pri zachovaní spiatočnej adresy bl. Tento príkaz sa používa na volanie podprogramov. Zaujímavosťou tohto príkazu je, že návratová adresa z procedúry pri volaní procedúry nie je uložená v zásobníku ako na procesoroch Intel, ale v bežnom registri r14. Potom, aby ste sa vrátili z procedúry, nepotrebujete špeciálny príkaz ret, ako pri rovnakých procesoroch Intel, ale môžete jednoducho skopírovať hodnotu r14 späť do počítača. Teraz je jasné, prečo má register r14 alternatívne meno lr (Link Register).

Pozrime sa na postup outbyte z projektu hello-world pre Amber SoC.

000004a0<_outbyte>:
4a0: e59f1454 ldr r1, ; 8fc< адрес регистра данных UART >
4a4: e59f3454 ldr r3, ; 900< адрес регистра статуса UART >
4a8: e5932000 ldr r2, ; prečítajte si aktuálny stav
4ac: e2022020 a r2, r2, #32
4b0: e3520000 cmp r2, #0 ; skontrolujte, či UART nie je zaneprázdnený
4b4: 05c10000 strbeq r0, ; napíšte znak do UART iba vtedy, ak nie je zaneprázdnený
4b8: 01b0f00e movseq pc, lr ; podmienený návrat z procedúry, ak UART nebol zaneprázdnený
4bc: 1affff9 bne 4a8<_outbyte+0x8>; slučky na kontrolu stavu UART

Myslím, že z komentárov v tomto fragmente je jasné, ako tento postup funguje.

Ďalšia dôležitá poznámka o prechodoch. Register r15 (pc) možno použiť v bežných aritmetických alebo logických operáciách ako cieľový register. Takže príkaz ako add pc,pc,#8 je celkom inštrukcia na presun na inú adresu.

V súvislosti s prechodmi je potrebné urobiť ešte jednu poznámku. Staršie ARM procesory majú tiež dodatočné príkazy prechody bx, blx a blj. Toto sú príkazy na preskočenie na fragmenty kódu s iným príkazovým systémom. Bx /blx vám umožňuje prepnúť na 16-bitový kód THUMB procesorov ARM. Blj je volanie procedúr inštrukčného systému Jazelle (podpora jazyka Java v procesoroch ARM). Náš Amber ARM v2a tieto príkazy nemá.

Tak sme vytvorili nový projekt, dokončili základné nastavenia, vytvorili a pripojili k projektu súbor, do ktorého chceme napísať nejaký jednoduchý program v assembleri.

Čo bude ďalej? Potom v skutočnosti môžete napísať program pomocou sady príkazov thumb-2 podporovanej jadrom Cortex-M3. Zoznam a popis podporovaných príkazov nájdete v dokumente tzv Všeobecná používateľská príručka Cortex-M3(kapitola Inštrukčná sada Cortex-M3), ktorý nájdete na karte Knihy v správcovi projektov v Keil uVision 5. Podrobnosti o príkazoch thumb-2 budú napísané v jednej z nasledujúcich častí tohto článku, ale teraz si povieme niečo o programoch pre STM32 v všeobecný.

Ako každý iný program v assembleri, aj program pre STM32 pozostáva z príkazov a pseudopríkazov, ktoré budú preložené priamo do strojových kódov, ako aj z rôznych direktív, ktoré sa neprekladajú do strojových kódov, ale používajú sa na servisné účely (značky programu, priraďovanie symbolických symbolov názvom konštánt atď.)

Napríklad špeciálna smernica vám umožňuje rozdeliť program do samostatných sekcií - AREA. Má nasledujúcu syntax: AREA Názov_sekcie (,typ) (, attr) …, Kde:

  1. Názov_sekcie— názov sekcie.
  2. typu— typ sekcie. Pre sekciu obsahujúcu údaje musí byť špecifikovaný typ DATA a pre sekciu obsahujúcu príkazy musí byť špecifikovaný typ CODE.
  3. attr- dodatočné atribúty. Napríklad atribúty len na čítanie alebo na čítanie označujú, v ktorej pamäti by sa sekcia mala nachádzať, atribút align=0..31 určuje, ako má byť sekcia zarovnaná v pamäti, atribút noinit sa používa na prideľovanie pamäťových oblastí, ktoré nepotrebujú byť inicializované alebo ktoré sú inicializované na nulu (pri použití tohto Atribút nemusí špecifikovať typ sekcie, pretože môže byť použitý len pre dátové sekcie).

smernice EQU je asi každému dobre známa, keďže sa nachádza v každom assembleri a je určená na priraďovanie symbolických mien rôznym konštantám, pamäťovým bunkám atď. Má nasledujúcu syntax: názov EQU číslo a povie kompilátoru, že sa vyskytli všetky symboly názov je potrebné nahradiť číslom číslo. Povedzme ak ako číslo použite adresu pamäťovej bunky, potom bude v budúcnosti k tejto bunke možné pristupovať nie podľa jej adresy, ale pomocou ekvivalentného symbolického zápisu ( názov).

smernice GET názov súboru vloží text do programu zo súboru s názvom názov súboru. Toto je analógia smernice include v assembleri pre AVR. Dá sa použiť napr samostatný súbor smernice na prideľovanie symbolických mien rôznym registrom. To znamená, že všetky priradenia názvov vložíme do samostatného súboru a potom, aby sa tieto symbolické názvy dali použiť v programe, jednoducho tento súbor zahrnieme do nášho programu s direktívou GET.

Samozrejme, okrem tých, ktoré sú uvedené vyššie, existuje množstvo rôznych smerníc, úplný zoznam ktoré nájdete v kapitole Odkaz na smernice dokument Užívateľská príručka pre Assembler, ktorý nájdete v Keil uVision 5 na nasledujúcej ceste: tab knihy projektový manažér -> Nástroje Používateľská príručka -> Kompletný výber používateľskej príručky -> Užívateľská príručka pre Assembler.

Väčšina príkazov, pseudopríkazov a direktív v programe má nasledujúcu syntax:

(štítok) SYMBOL (výraz) (,výraz) (,výraz) (; komentár)

(label) - štítok. Je to potrebné, aby bolo možné určiť adresu príkazu nasledujúceho po tomto štítku. Označenie je voliteľný prvok a používa sa len vtedy, keď je potrebné zistiť adresu príkazu (napríklad preskočiť na tento príkaz). Pred štítkom nesmú byť žiadne medzery (to znamená, že musí začínať úplne od prvej pozície riadku) a názov štítku môže začínať iba písmenom.

SYMBOL je príkaz, pseudopríkaz alebo príkaz. Príkaz, na rozdiel od označenia, naopak musí mať od začiatku riadku nejaké odsadenie, aj keď pred ním nie je žiadne označenie.

(expr) (,expr) (,expr) - operandy (registre, konštanty...)

; - oddeľovač. Všetok text v riadku za týmto oddeľovačom sa považuje za komentár.

Teraz, ako som sľúbil, najjednoduchší program:

OBLASŤ START , KÓD , LEN NA ČÍTANIE dcd 0x20000400 dcd Start_programu VSTUP Program_start b Program_start END

OBLASŤ ŠTART, KÓD, LEN NA ČÍTANIE dcd 0x20000400 dcd Start_programu VSTUP Program_start b Program_start END

V tomto programe máme len jednu sekciu, ktorá sa volá ŠTART. Táto sekcia sa nachádza vo flash pamäti (keďže sa pre ňu používa atribút iba na čítanie).

Prvé 4 bajty tejto sekcie obsahujú adresu vrcholu zásobníka (v našom prípade 0x20000400) a druhé 4 bajty obsahujú adresu vstupného bodu (začiatok spustiteľného kódu). Nasleduje samotný kód. V našom najjednoduchšom príklade spustiteľný kód pozostáva z jedného jediného príkazu, ktorý bezpodmienečne preskočí na označenie Program_start, to znamená, že znova vykoná rovnaký príkaz.

Keďže vo flashi je len jedna sekcia, v rozptylovom súbore pre náš program budeme musieť zadať jej názov (tj START) ako First_Section_Name.

V tomto prípade máme zmiešané údaje a príkazy. Adresa vrcholu zásobníka a adresa vstupného bodu (dát) sa zapisuje pomocou direktív dcd priamo v sekcii kódu. Samozrejme, že môžete takto písať, ale nie je to veľmi pekné. Najmä ak opíšeme celú tabuľku prerušení a výnimiek (ktoré sa ukážu ako dosť dlhé), a nielen vektor resetovania. Oveľa krajšie je nezahlcovať kód zbytočnými údajmi, ale tabuľku vektorov prerušení umiestniť do samostatnej sekcie, alebo ešte lepšie – do samostatného súboru. Podobne môže byť inicializácia zásobníka umiestnená v samostatnej sekcii alebo dokonca súbore. Napríklad všetko umiestnime do samostatných sekcií:

AREA STACK, NOINIT, READWRITE SPACE 0x400 ; preskočiť 400 bajtov Stack_top ; a označte AREA RESET, DATA, READONLY dcd Stack_top ; adresa štítku Stack_top dcd Program_start ; adresa štítku Štart_programu PROGRAM OBLASTI, KÓD, VSTUP LEN NA ČÍTANIE ; vstupný bod (začiatok spustiteľného kódu) Program_start ; značka spustenia programu b Štart_programu KONIEC

Ten istý program (ktorý stále nerobí nič užitočné), ale teraz to vyzerá oveľa jasnejšie. V rozptylovom súbore pre tento program musíte zadať názov RESET ako First_Section_Name, aby bola táto sekcia umiestnená ako prvá v pamäti flash.

Ahojte všetci!
Podľa povolania som Java programátor. Posledné mesiace práce ma prinútili zoznámiť sa s vývojom pre Android NDK a podľa toho aj s písaním natívne aplikácie v C. Tu som narazil na problém s optimalizáciou linuxových knižníc. Mnohé sa ukázali byť úplne neoptimalizované pre ARM a značne zaťažovali procesor. Predtým som prakticky nikdy neprogramoval v assembleri, takže spočiatku bolo ťažké začať sa učiť tento jazyk, ale aj tak som sa rozhodol skúsiť to. Tento článok bol napísaný takpovediac od začiatočníka pre začiatočníkov. Skúsim popísať základy, ktoré som sa už naučil, dúfam, že to niekoho zaujme. Okrem toho budem rád, ak dostanem konštruktívnu kritiku od profesionálov.

Úvod
Najprv si teda poďme zistiť, čo je ARM. Wikipedia dáva túto definíciu:

Architektúra ARM (Advanced RISC Machine, Acorn RISC Machine, pokročilý stroj RISC) je rodina licencovaných 32-bitových a 64-bitových mikroprocesorových jadier vyvinutých spoločnosťou ARM Limited. Spoločnosť pre ne výhradne vyvíja jadrá a nástroje (kompilátory, nástroje na ladenie atď.), pričom zarába na licencovaní architektúry výrobcom tretích strán.

Ak niekto nevie, teraz väčšina mobilné zariadenia, tablety sú navrhnuté špeciálne pre túto architektúru procesora. Hlavnou výhodou tejto rodiny je nízka spotreba energie, vďaka čomu sa často používa v rôznych vstavaných systémoch. Architektúra sa postupom času vyvíjala a počnúc ARMv7 boli definované 3 profily: ‚A‘ (aplikácia) – aplikácie, ‚R‘ (v reálnom čase) – v reálnom čase, ‚M‘ (mikrokontrolér) – mikrokontrolér. Históriu vývoja tejto technológie a ďalšie zaujímavé údaje si môžete prečítať na Wikipédii alebo vygoogliť na internete. Podporuje ARM rôzne režimy funguje (Thumb a ARM, navyše sa nedávno objavil Thumb-2, čo je zmes ARM a Thumb). V tomto článku sa pozrieme na samotný režim ARM, v ktorom sa vykonáva 32-bitová inštrukčná sada.

Každý procesor ARM je vytvorený z nasledujúcich blokov:

  • 37 registrov (z ktorých iba 17 je viditeľných počas vývoja)
  • Aritmetická logická jednotka (ALU) - vykonáva aritmetické a logické úlohy
  • Barrel shifter - zariadenie určené na presun blokov dát o určitý počet bitov
  • CP15 - špeciálny systém, riadiaci ARM koprocesory
  • Dekodér inštrukcií – zaoberá sa prevodom inštrukcií na postupnosť mikrooperácií
Toto nie sú všetky komponenty ARM, no ponorenie sa do džungle konštrukcie procesorov je nad rámec tohto článku.
Realizácia potrubia
Procesory ARM používajú 3-stupňový pipeline (od ARM8 bol implementovaný 5-stupňový pipeline). Pozrime sa na jednoduchú pipeline s použitím procesora ARM7TDMI ako príklad. Vykonanie každej inštrukcie pozostáva z troch krokov:

1. Fáza odberu vzoriek (F)
V tomto štádiu prúdia inštrukcie z RAM do potrubia procesora.
2. Fáza dekódovania (D)
Inštrukcie sú dekódované a ich typ je rozpoznaný.
3. Fáza vykonávania (E)
Dáta vstúpia do ALU a vykonajú sa a výsledná hodnota sa zapíše do určeného registra.

Pri vývoji je však potrebné vziať do úvahy, že existujú inštrukcie, ktoré používajú niekoľko cyklov vykonávania, napríklad načítanie (LDR) alebo ukladanie. V tomto prípade je realizačná etapa (E) rozdelená na etapy (E1, E2, E3...).

Podmienečné prevedenie
Jeden z základné funkcie ARM assembler - podmienené vykonávanie. Každá inštrukcia môže byť vykonaná podmienečne a používajú sa na to prípony. Ak sa k názvu inštrukcie pridá prípona, pred jej vykonaním sa skontrolujú parametre. Ak parametre nespĺňajú podmienku, inštrukcia sa nevykoná. Prípony:
MI - záporné číslo
PL - kladné alebo nulové
AL - vždy vykonávať inštrukcie
Existuje oveľa viac prípon podmieneného vykonania. Prečítajte si zvyšok prípon a príkladov v oficiálnej dokumentácii: Dokumentácia ARM
Teraz je čas zvážiť...
Základná syntax assemblera ARM
Pre tých, ktorí už predtým pracovali s assemblerom, môžete tento bod preskočiť. Pre všetkých ostatných popíšem základy práce s týmto jazykom. Takže každý program v assembleri pozostáva z inštrukcií. Inštrukcia je vytvorená týmto spôsobom:
(štítok) (návod|operandy) (@komentár)
Označenie je voliteľný parameter. Inštrukcia je priama mnemotechnická pomôcka inštrukcií pre procesor. Základné pokyny a ich použitie budú uvedené nižšie. Operandy - konštanty, adresy registrov, adresy v Náhodný vstup do pamäťe. Komentár je voliteľný parameter, ktorý neovplyvňuje vykonávanie programu.
Registrovať mená
Povolené sú nasledujúce názvy registrov:
1.r0-r15

3.v1-v8 (premenné registre, r4 až r11)

4.sb a SB (statický register, r9)

5.sl a SL (r10)

6.fp a FP (r11)

7.ip a IP (r12)

8.sp a SP (r13)

9.lr a LR (r14)

10.pc a PC (počítadlo programov, r15).

Premenné a konštanty
V ARM assembleri, ako v každom (prakticky) inom programovacom jazyku, možno použiť premenné a konštanty. Sú rozdelené do nasledujúcich typov:
  • Číselné
  • hlavolam
  • Reťazec
Číselné premenné sa inicializujú takto:
SETA 100; vytvorí sa číselná premenná "a" s hodnotou 100.
Premenné reťazca:
improb SETS "doslova"; vytvorí sa premenná improb s hodnotou „literal“. POZOR! Hodnota premennej nemôže presiahnuť 5120 znakov.
Booleovské premenné používajú hodnoty TRUE a FALSE.
Príklady inštrukcií pre assembler ARM
V tejto tabuľke som zhromaždil základné pokyny, ktoré budú potrebné pre ďalší vývoj (v najzákladnejšej fáze:):

Aby sme posilnili používanie základných inštrukcií, napíšme niekoľko jednoduchých príkladov, ale najprv budeme potrebovať arm toolchain. Pracujem na Linuxe, takže som si vybral: frank.harvard.edu/~coldwell/toolchain (arm-unknown-linux-gnu toolchain). Dá sa nainštalovať rovnako jednoducho ako ktorýkoľvek iný program na Linuxe. V mojom prípade (ruská Fedora) som potreboval nainštalovať iba rpm balíčky zo stránky.
Teraz je čas písať najjednoduchší príklad. Program bude úplne zbytočný, ale hlavné je, že bude fungovať :) Tu je kód, ktorý vám ponúkam:
štart: @ Voliteľný riadok označujúci začiatok programu mov r0, #3 @ Načítajte register r0 s hodnotou 3 mov r1, #2 @ Urobte to isté s registrom r1, len teraz s hodnotou 2 pridajte r2, r1, r0 @ Pridajte hodnoty r0 a r1, odpoveď sa zapíše do r2 mul r3, r1, r0 @ Vynásobte hodnotu registra r1 hodnotou registra r0, odpoveď sa zapíše do r3 stop: b stop @ Riadok ukončenia programu
Kompilujeme program, aby sme získali súbor .bin:
/usr/arm/bin/arm-unknown-linux-gnu-as -o arm.o arm.s /usr/arm/bin/arm-unknown-linux-gnu-ld -Ttext=0x0 -o ​​​​arm. elf arm .o /usr/arm/bin/arm-unknown-linux-gnu-objcopy -O binárne rameno.elf arm.bin
(kód je v súbore arm.s a reťazec nástrojov je v mojom prípade v adresári /usr/arm/bin/)
Ak všetko prebehlo dobre, budete mať 3 súbory: arm.s (skutočný kód), arm.o, arm.elf, arm.bin (skutočný spustiteľný program). Na kontrolu fungovania programu nie je potrebné mať vlastné rameno. Stačí nainštalovať QEMU. Pre informáciu:

QEMU je bezplatný program s otvoreným zdrojom zdrojový kód emulovať hardvér rôznych platforiem.

Zahŕňa emuláciu procesory Intel x86 a I/O zariadenia. Dokáže emulovať 80386, 80486, Pentium, Pentium Pro, AMD64 a ďalšie procesory kompatibilné s x86; PowerPC, ARM, MIPS, SPARC, SPARC64, m68k - len čiastočne.

Funguje na Syllable, FreeBSD, FreeDOS, Linux, Windows 9x, Windows 2000, Mac OS X, QNX, Android atď.

Takže na emuláciu arm budete potrebovať qemu-system-arm. Tento balík je v yum, takže pre tých, ktorí majú Fedoru, sa nemusíte obťažovať a stačí spustiť príkaz:
yum install qemu-system-arm

Ďalej musíme spustiť emulátor ARM, aby spustil náš program arm.bin. Na tento účel si vytvoríme súbor flash.bin, ktorý bude flash pamäťou pre QEMU. Je to veľmi jednoduché:
dd if=/dev/zero of=flash.bin bs=4096 počet=4096 dd if=arm.bin of=flash.bin bs=4096 conv=notrunc
Teraz načítame QEMU s výslednou flash pamäťou:
qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
Výstup bude asi takýto:

$ qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
Monitor QEMU 0.15.1 – pre viac informácií napíšte „help“.
(qemu)

Náš program arm.bin musel zmeniť hodnoty štyroch registrov, preto, aby sme skontrolovali správnu činnosť, pozrime sa na tieto rovnaké registre. Robí sa to veľmi jednoduchým príkazom: info registers
Na výstupe uvidíte všetkých 15 ARM registrov a štyri z nich budú mať zmenené hodnoty. Skontrolujte :) Hodnoty registra sa zhodujú s tými, ktoré možno očakávať po spustení programu:
(qemu) informačné registre R00=00000003 R01=00000002 R02=00000005 R03=00000006 R04=00000000 R05=00000000 R06=00000000 R07=000 00000 R10=00000000 R11=00000000 R12=00000000 R13=00000000 R14= 00000000 R15=00000010 PSR=400001d3 -Z-- A svc32

P.S. V tomto článku som sa snažil popísať základy programovania v ARM assembleri. Dúfam, že sa vám to páčilo! To bude stačiť na ďalšie ponorenie sa do džungle tohto jazyka a písanie programov v ňom. Ak sa všetko podarí, napíšem ďalej, čo sám zistím. Ak sa vyskytnú chyby, nevyhadzujte ma, pretože som v assembleri nový.

Ak používate distribúciu Raspbian ako operačný systém vášho Raspberry Pi, budete potrebovať dva nástroje, a to as (assembler, ktorý konvertuje zdrojový kód jazyka assembleru na binárny kód) a ld (linker, ktorý vytvorí výsledný spustiteľný súbor). Obe pomôcky sú súčasťou balenia softvér binutils , takže môžu byť už prítomné vo vašom systéme. Samozrejme, budete potrebovať aj dobrý textový editor; Vždy odporúčam používať Vim na vývoj programov, ale má vysokú vstupnú bariéru, takže Nano alebo akýkoľvek iný textový editor GUI bude fungovať dobre.

Ste pripravení začať? Skopírujte nasledujúci kód a uložte ho do súboru myfirst.s:

Globálny _start _start: mov r7, #4 mov r0, #1 ldr r1, =string mov r2, #stringlen swi 0 mov r7, #1 swi 0 .data string: .ascii "Ciao!\n" stringlen = . - reťazec

Tento program len vypíše reťazec "Ciao!" na obrazovku a ak ste čítali články o používaní jazyka symbolických inštrukcií pri práci s x86 CPU, niektoré z použitých pokynov vám môžu byť známe. Medzi inštrukciami architektúr x86 a ARM je však stále veľa rozdielov, čo možno povedať aj v syntaxi zdrojového kódu, takže si to podrobne rozoberieme.

Predtým však treba spomenúť, že na zostavenie daného kódu a prepojenie výsledného objektového súboru do spustiteľného súboru je potrebné použiť nasledujúci príkaz:

Ako -o myfirst.o myfirst.s && ld -o myfirst myfirst.o

Teraz môžete vytvorený program spustiť pomocou príkazu ./myfirst . Možno ste si všimli, že spustiteľný súbor má veľmi skromnú veľkosť približne 900 bajtov – ak ste používali programovací jazyk C a funkciu puts(), veľkosť binárny súbor bude asi päťkrát väčší!

Vytvorenie vlastného operačného systému pre Raspberry Pi

Ak ste čítali predchádzajúce články tejto série o programovaní v jazyku symbolických inštrukcií x86, pravdepodobne si pamätáte, ako ste prvýkrát spustili svoj vlastný operačný systém, ktorý zobrazil správu na obrazovke bez Linux pomoc alebo akýkoľvek iný operačný systém. Potom sme to vylepšili pridaním jednoduchého rozhrania príkazový riadok a mechanizmus na načítanie a spúšťanie programov z disku, čím sa ponecháva základ pre budúcnosť. Bolo to veľmi zaujímavé, ale nie veľmi ťažká práca hlavne vďaka vonkajšej pomoci Firmvér systému BIOS- poskytovalo zjednodušené rozhranie pre prístup k obrazovke, klávesnici a čítačke diskiet.

S Raspberry Pi už nebudete mať k dispozícii užitočné funkcie BIOSu, takže si budete musieť vyvíjať ovládače zariadení sami, čo je samo o sebe náročná a nezaujímavá práca v porovnaní s kreslením na obrazovku a implementáciou enginu na spúšťanie vlastných programov. . Zároveň existuje v sieti niekoľko sprievodcov, ktoré podrobne popisujú počiatočné fázy procesu zavádzania Raspberry Pi, funkcie mechanizmu pre prístup k pinom GPIO atď.

Jedným z najlepších takýchto dokumentov je dokument s názvom Baking Pi (www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/index.html) z University of Cambridge. Ide v podstate o súbor tutoriálov, ktoré popisujú techniky práce s jazykom symbolických inštrukcií na zapnutie LED diód, prístup k pixelom na obrazovke, príjem vstupu z klávesnice atď. Pri čítaní sa veľa dozviete hardvér Raspberry Pi a manuály boli napísané pre pôvodné modely týchto jednodoskových počítačov, takže nie je zaručené, že budú relevantné pre modely ako A+, B+ a Pi 2.

Ak dávate prednosť programovaciemu jazyku C, mali by ste si prečítať dokument zo zdroja Valvers, ktorý sa nachádza na adrese http://tinyurl.com/qa2s9bg a obsahuje popis procesu nastavenia krížového kompilátora a zostavenia jednoduchého operačného systému. kernel a v sekcii Wiki užitočný zdroj OSDev, ktorý sa nachádza na http://wiki.osdev.org/Raspberry_Pi_Bare_Bones, tiež poskytuje informácie o tom, ako vytvoriť a spustiť základné jadro operačného systému na Raspberry Pi.

Ako už bolo spomenuté vyššie, najväčším problémom je v tomto prípade potreba vyvinúť ovládače pre rôzne hardvérové ​​zariadenia Raspberry Pi: radič USB, slot na SD kartu atď. Veď aj kód pre spomínané zariadenia dokáže zabrať desaťtisíce riadkov. Ak si stále chcete vyvinúť vlastný plnohodnotný operačný systém pre Raspberry Pi, mali by ste navštíviť fóra na www.osdev.org a opýtať sa, či už niekto nevyvinul ovládače pre tieto zariadenia a ak je to možné, prispôsobil ich pre vaše jadro operačný systém, čím ušetríte veľké množstvo vášho času.

Ako to celé funguje

Prvé dva riadky kódu nie sú inštrukcie CPU, ale príkazy assembleru a linkera. Každý program musí mať jasne definovaný vstupný bod s názvom _start a v našom prípade bol na úplnom začiatku kódu. Preto informujeme linkera, že vykonávanie kódu by sa malo začať prvou inštrukciou a nie sú potrebné žiadne ďalšie akcie.

S nasledujúcim pokynom vložíme číslo 4 do registra r7. (Ak ste ešte nikdy nepracovali s assemblerom, mali by ste vedieť, že register je pamäťové miesto umiestnené priamo v centrálnej procesorovej jednotke. centrálne procesorové jednotky existuje malý počet implementovaných registrov v porovnaní s miliónmi alebo miliardami buniek RAM, ale registre sú nevyhnutné, pretože fungujú oveľa rýchlejšie.) Čipy architektúry ARM poskytujú vývojárom veľké množstvo všeobecných registrov: dizajnér môže využiť až až 16 registrov s názvom r0 až r15, pričom tieto registre nie sú spojené so žiadnymi historickými obmedzeniami, ako je to v prípade architektúry x86, kde niektoré z registrov možno v určitých časoch použiť na určité účely.

Takže aj keď je inštrukcia mov veľmi podobná inštrukcii x86 s rovnakým názvom, mali by ste venovať pozornosť symbolu hash vedľa čísla 4, ktorý naznačuje, že to, čo nasleduje, je celočíselná hodnota a nie adresa pamäte. V tomto prípade chceme použiť systémové volanie Linuxové jadro zapíše na výstup náš reťazec; Ak chcete použiť systémové volania, musíte naplniť registre potrebnými hodnotami predtým, ako požiadate jadro, aby vykonalo svoju prácu. Číslo systémového volania musí byť umiestnené v registri r7, pričom číslo 4 je zapisovacie systémové volacie číslo.

S nasledujúcou inštrukciou mov umiestnime handle súboru, do ktorého sa má zapísať reťazec "Ciao!", teda štandardný handle výstupného prúdu, do registra r0. Pretože sa v tomto prípade používa štandardný výstupný tok, jeho štandardný deskriptor, teda 1, sa umiestni do registra. Ďalej musíme umiestniť adresu reťazca, ktorý chceme vydať, do registra r1 pomocou inštrukcie ldr (inštrukcia „načítať do registra“; všimnite si znamienko rovnosti označujúce, že to, čo nasleduje, je skôr označenie ako adresa). Na konci kódu, konkrétne v dátovej časti, deklarujeme tento reťazec vo forme sekvencie ASCII znakov. Aby sme úspešne použili systémové volanie "write", musíme jadru operačného systému povedať aj to, aký dlhý je výstupný reťazec, takže do registra r2 vložíme hodnotu stringlen. (Hodnota stringlen sa vypočíta odčítaním koncovej adresy reťazca od počiatočnej adresy.)

V tomto bode sme všetky registre naplnili potrebnými údajmi a sme pripravení preniesť riadenie na jadro Linuxu. Na tento účel používame inštrukciu swi, ktorej názov znamená „softwarové prerušenie“, ktorá preskočí do priestoru jadra OS (takmer rovnakým spôsobom ako inštrukcia int v článkoch o architektúre x86). Jadro OS preskúma obsah registra r7, nájde v ňom celočíselné číslo 4 a dospeje k záveru: „Takže volajúci program chce vytlačiť reťazec.“ Potom preskúma obsah iných registrov, vypíše reťazec a vráti riadenie nášmu programu.

Na obrazovke teda vidíme riadok „Ciao!“, po ktorom môžeme iba správne ukončiť vykonávanie programu. Tento problém riešime umiestnením výstupného systémového volacieho čísla do registra r7 a následným volaním inštrukcie softvérového prerušenia číslo nula. A to je všetko – jadro OS dokončí vykonávanie nášho programu a my sa presunieme späť do príkazového shellu.

Vim (vľavo) je vynikajúci textový editor písať kód v assembleri - súbor na zvýraznenie syntaxe tohto jazyka pre architektúru ARM je dostupný na http://tinyurl.com/psdvjen.

Poradenstvo: Pri práci s jazykom symbolických inštancií by ste nemali šetriť komentármi. Nepoužili sme veľká kvantita komentáre v tomto článku, aby kód na stránkach časopisu zaberal čo najmenej miesta (a tiež preto, že účel každého z návodov sme podrobne opísali). Ale počas vývoja komplexné programy, ktorého kód sa zdá na prvý pohľad zrejmý, by ste si mali vždy premyslieť, ako to bude vyzerať, keď čiastočne zabudnete na syntax assembleru ARM a po niekoľkých mesiacoch sa vrátite k vývoju. Môžete zabudnúť na všetky triky a skratky používané v kóde, po ktorých bude kód vyzerať ako úplná hlúposť. Na základe všetkých vyššie uvedených skutočností by ste mali do svojho kódu pridať čo najviac komentárov, aj keď sa niektoré z nich momentálne zdajú príliš zrejmé!

Reverzné inžinierstvo

V niektorých prípadoch môže byť užitočná aj konverzia binárneho súboru do kódu assembleru. Výsledkom tejto operácie je zvyčajne nie veľmi kvalitný kód bez čitateľných názvov štítkov a komentárov, ktoré však môžu byť užitočné pri štúdiu transformácií, ktoré na vašom kóde vykonal assembler. Ak chcete rozobrať binárny súbor myfirst, jednoducho spustite nasledujúci príkaz:

Objdump -d môjprvý

Tento príkaz rozloží sekciu spustiteľného kódu binárneho súboru (ale nie dátovú sekciu, pretože obsahuje text ASCII). Ak sa pozriete na kód získaný v dôsledku demontáže, pravdepodobne si všimnete, že pokyny v ňom sú prakticky rovnaké ako pokyny v pôvodnom kóde. Disassemblery sa používajú hlavne vtedy, keď potrebujete študovať správanie programu, ktorý je dostupný iba vo formulári binárny kód, napríklad vírus alebo jednoduchý uzavretý program, ktorého správanie chcete napodobniť. Zároveň by ste mali vždy pamätať na obmedzenia, ktoré ukladá autor študovaného programu! Rozoberanie súboru binárneho programu a jednoduché skopírovanie výsledného kódu do kódu vášho projektu je, samozrejme, zlý nápad; zároveň môžete výsledný kód celkom použiť na štúdium princípu fungovania programu.

Podprogramy, cykly a podmienené príkazy

Teraz, keď vieme, ako navrhnúť, zostaviť a prepojiť jednoduché programy, poďme sa pozrieť na niečo zložitejšie. Nasledujúci program používa na tlač reťazcov podprogramy (vďaka nim môžeme znova použiť fragmenty kódu a ušetríme sa od toho, aby sme museli vykonávať rovnaké operácie napĺňania registrov údajmi). Tento program implementuje slučku hlavnej udalosti, ktorá umožňuje výstup reťazca, kým používateľ nezadá „q“. Preštudujte si kód a pokúste sa pochopiť (alebo uhádnuť!) účel pokynov, ale ak niečomu nerozumiete, nezúfajte, pretože o niečo neskôr sa na to tiež pozrieme veľmi podrobne. Všimnite si, že symboly @ v jazyku zostavy ARM zvýrazňujú komentáre.

Global _start _start: ldr r1, =string1 mov r2, #string1len bl print_string loop: mov r7, #3 @ čítanie mov r0, #0 @ stdin ldr r1, =char mov r2, #2 @ dva znaky swi 0 ldr r1, =char ldrb r2, cmp r2, #113 @ ASCII kód ​​pre "q" beq done ldr r1, =string2 mov r2, #string2len bl print_string b slučka hotová: mov r7, #1 swi 0 print_string: mov r7, #4 mov r0, #1 swi 0 bx lr .data string1: .ascii "Zadajte q pre ukončenie!\n" string1len = . - reťazec1 reťazec2: .ascii "To nebolo q...\n" reťazec2len =

Náš program začína umiestnením ukazovateľa na začiatok reťazca a jeho dĺžky do príslušných registrov pre následné vykonanie systémového volania zápisu, po ktorom okamžite preskočí na podprogram print_string umiestnený nižšie v kóde. Na uskutočnenie tohto prechodu sa používa inštrukcia bl, ktorej názov znamená „vetva a odkaz“ („pobočka so zachovaním adresy“) a sama ukladá aktuálnu adresu do kódu, čo vám umožní vrátiť sa k nej neskôr. pomocou inštrukcie bx. Rutina print_string jednoducho vyplní ďalšie registre, aby vykonala systémové volanie zápisu rovnakým spôsobom ako náš prvý program, predtým, než skočí do priestoru jadra OS a potom sa vráti na uloženú kódovú adresu pomocou inštrukcie bx.

Keď sa vrátime k volaciemu kódu, nájdeme štítok s názvom loop - už názov štítku napovedá, že sa k nemu o chvíľu vrátime. Najprv však použijeme ďalšie systémové volanie s názvom read (očíslované 3) na prečítanie znaku zadaného používateľom pomocou klávesnice. Vložíme teda hodnotu 3 do registra r7 a hodnotu 0 (štandardný vstupný handle) do registra r0, pretože potrebujeme čítať užívateľský vstup a nie dáta zo súboru.

Ďalej do registra r1 umiestnime adresu, kam chceme uložiť znak načítaný a umiestnený jadrom OS – v našom prípade ide o oblasť pamäte znakov opísanú na konci dátovej časti. (V skutočnosti potrebujeme strojové slovo, teda oblasť pamäte na uloženie dvoch znakov, pretože v nej bude uložený aj kód pre kláves Enter. Pri práci s jazykom symbolov je dôležité vždy pamätať na možnosť preplnenia pamäte oblasti, pretože neexistujú žiadne mechanizmy na vysokej úrovni pripravené pomôcť vám!).

Keď sa vrátime k hlavnému kódu, vidíme, že hodnota 2 je umiestnená v registri r2, čo zodpovedá dvom znakom, ktoré chceme uložiť, a potom skočíme do priestoru jadra, aby sme vykonali operáciu čítania. Používateľ zadá znak a stlačí kláves Enter. Teraz musíme skontrolovať, čo je to za znak: adresu oblasti pamäte (char v dátovej časti) vložíme do registra r1 a potom pomocou inštrukcie ldrb načítame bajt z oblasti pamäte, na ktorú ukazuje hodnota v ten register.

Hranaté zátvorky v tomto prípade označujú, že údaje sú uložené v pamäťovej oblasti, ktorá nás zaujíma, a nie v samotnom registri. Register r2 teda teraz obsahuje jeden znak z oblasti pamäte znakov z dátovej časti, a to je presne ten znak, ktorý používateľ zadal. Našou ďalšou úlohou bude porovnať obsah registra r2 so znakom "q", čo je 113. znak ASCII tabuľky (pozri tabuľku znakov umiestnenú na www.asciichart.com). Teraz použijeme inštrukciu cmp na vykonanie operácie porovnania a potom použijeme inštrukciu beq, ktorá znamená „branch if equal“, aby sme preskočili na hotový štítok, ak je hodnota v registri r2 113. Ak to tak nie je , potom vytlačíme náš druhý riadok, po ktorom preskočíme na začiatok cyklu pomocou inštrukcie b.

Nakoniec po značke hotovo povieme jadru OS, že chceme program ukončiť, rovnako ako v prvom programe. Ak chcete spustiť tento program, jednoducho ho zostavte a prepojte podľa pokynov uvedených pre prvý program.

Skontrolovali sme teda pomerne veľké množstvo informácií v najzhustenejšej forme, ale bude lepšie, ak si materiál preštudujete sami a budete experimentovať s vyššie uvedeným kódom. Nie najlepšia cesta oboznámenie sa s programovacím jazykom namiesto uskutočňovania experimentov, ktoré zahŕňajú úpravu kódu niekoho iného a pozorovanie dosiahnutého efektu. Teraz môžete vyvíjať jednoduché programy v assembleri ARM, ktoré čítajú užívateľské vstupné a výstupné dáta pomocou slučiek, porovnávaní a podprogramov. Ak ste sa ešte nestretli s assemblerom dnes, Dúfam, že vám tento článok trochu ozrejmil jazyk a pomohol rozptýliť populárny stereotyp, že ide o mystické remeslo vyhradené len pre pár talentovaných vývojárov.

Samozrejme, informácie uvedené v článku týkajúce sa použitia assembleru pre architektúru ARM sú len špičkou ľadovca. Používanie tohto programovacieho jazyka je vždy spojené s obrovským množstvom nuancií a ak chcete, aby sme o nich napísali v niektorom z nasledujúcich článkov, dajte nám o tom vedieť! Medzitým odporúčame navštíviť vynikajúci zdroj s množstvom materiálov na učenie sa techník vytvárania programov pre Linuxové systémy, spustený na počítačoch s centrálnymi procesormi architektúry ARM, ktorý sa nachádza na http://tinyurl.com/nsgzq89. Šťastné programovanie!

Predchádzajúce články zo série „Škola montáže“: