Škola montáže: montážny jazyk pre centrálne procesorové jednotky architektúry ARM. School of Assembly: Assembly Language for ARM CPUs Arm Assembly Instructions

Najprv ARM dosť neobvyklý assembler (ak sa naučíte znova s x86, MCS51 alebo AVR). Ale má dosť jednoduchú a logickú organizáciu, takže sa to rýchlo naučí.

V ruštine je veľmi málo dokumentácie v montážnom jazyku. Môžem vám poradiť, aby ste šli na 2 odkazy (možno nájdete viac a povedzte mi? Bol by som vám vďačný.):
Architektúra a sada inštrukcií procesorov ARM RISC - http://www.gaw.ru/html.cgi/txt/doc/micros/arm/index.htm
Pochopenie zostavovateľa ARM, 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, rozptýlil hmlu =). Druhá vec, ktorá môže dobre pomôcť, je napodiv kompilátor C. IAR Embedded Workbench pre ARM(ďalej len IAR EW ARM). Faktom je, že odpradávna bol schopný (mimochodom ako všetci rešpektujúci kompilátori) kompilovať C kód do montážneho kódu, ktorý zase môže rovnako ľahko kompilovať do objektového kódu zostavovateľ IAR. Preto nie je nič lepšie napísať najjednoduchšiu funkciu v jazyku 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 C. Vycvičil som si funkciu počítania CRC16 a vďaka tomu som získal jeho plnú verziu v assembleri.

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

Crc16 = 0;
idx = 0;
while (veľkosť! = 0)
{
/ * pridajte vysoký bajt crc16 a vstupný bajt xor * /
tmpWord = (crc16 >> 8) ^ (* (((u8 *) databuf) + idx));
/ * zapíš výsledok do vysokobytového crc16 * /
tmpWord<<= 8;
crc16 = tmpWord + (0x00FF & crc16);
pre (bitCnt = 8; bitCnt! = 0; bitCnt--)
{
/ * skontrolujte batériu vysokej kvality CRC * /
ak (crc16 a 0x8000)
{
crc16<<= 1;
crc16 ^ = CRC_POLY;
}
inak
crc16<<= 1;
}
idx ++;
veľkosť--;
}
návrat crc16;
}

Je veľmi ľahké získať kód zostavy IAR EW ARM, ktorý sa má vygenerovať. Do možností súboru crc16.c (pridaného do projektu) som vložil daw Prepísať zdedené nastavenia, a potom vložte 3 začiarkavacie políčka na karte Zoznam - Výstupný súbor assembleru, Zahrnúť zdroj a Zahrňte informácie o rámci hovoru(aj keď posledné políčko pravdepodobne možno vynechať - vytvára veľa zbytočných CFI- smernica). Po kompilácii je súbor project_folder \ ewp \ at91sam7x256_sram \ List \ crc16.s. Tento súbor je možné rovnako ľahko pridať do projektu ako súbor C (bude sa kompilovať v poriadku).

Keď som samozrejme vkĺzol nevystrihnutú verziu kódu C kompilátoru C, poskytol mi taký zoznam zostavovateľa, že som o tom nič nechápal. Ale keď som z funkcie odstránil všetkých C-operátorov okrem jedného, ​​bolo to jasnejšie. Potom som krok za krokom pridal operátory C a nakoniec sa to stalo:

; súbor crc16.s
NÁZOV crc16
VEREJNÝ CRC16

CRC_POLY EQU 0x1021

SEKCIA `.text`: KÓD: NOROOT (2)
ARM

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

CRC16:
PUSH (R3, R12); zadaním som zistil, že R3 a R13 by sa mali uložiť
; nie je potrebné. Ale rozhodol som sa, že si ho nechám pre všetkých
; deje sa.
MOVS R2, R0; teraz R2 == databuf
MOV R3, # + 0
MOVS R0, R3; crc16 = 0
CRC16_LOOP:
CMP R1, # + 0; všetky spracované bajty (veľkosť == 0)?
BEQ CRC16_RETURN; ak áno, potom ukončite
LSR R3, R0, # + 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 R0, R0, # + 255; crc16 & = 0x00FF
PRIDAJTE R0, R0, R3; crc16 = tmpWord + (0x00FF & crc16)
MOV R12, # + 8; bitCnt = 8
CRC16_BIT_LOOP:
BEQ CRC16_NEXT_BYTE; bitCnt == 0?
TST R0, # 0x8000; Nie všetky bity sú ešte spracované.
BEQ CRC16_BIT15ZERO; Skontrolujte vysoký bit crc16.
LSL R0, R0, # + 1; crc16<<= 1
MOV R3, # + (LOW (CRC_POLY)); crc16 ^ = CRC_POLY
ORR R3, R3, # + (VYSOKÉ (CRC_POLY)<< 8) ;
EORR0, R3, R0;
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:
PRIDAJTE R2, R2, # + 1; databuf ++
SUBS R1, R1, # + 1; veľkosť -
B CRC16_LOOP; slučka cez všetky bajty

CRC16_RETURN:
POP (R3, R12); obnovenie registrov
BX LR; výstupný podprogram, R0 == crc16

Kompilátor IAR C vytvára prekvapivo dobrý kód. Podarilo sa mi to optimalizovať veľmi málo. Vyhodil iba extra dočasný register, ktorý chcel kompilátor použiť (z nejakého dôvodu vzal LR ako extra dočasný register, hoci R3 a R12 stačili), a tiež odstránil pár zbytočných príkazov, ktoré kontrolovali počítadlá a nastavovali príznaky ( jednoduchým pridaním prípony S k požadovaným tímom).

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

Naproti tomu RISC má relatívne jednoduchý príkazový 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é, posun údajov vľavo / vpravo alebo rotácia bitov v registri),
  • príkazy na podmienené alebo bezpodmienečné rozvetvenie na iné adresy.

Spravidla (ale nie vždy a iba v prípade, že sa programový kód dostane do pamäte cache radiča) sa vykoná jeden príkaz pre jeden cyklus procesora. Dĺžka inštrukcie procesora ARM je pevná - 4 bajty (jedno počítačové slovo). V skutočnosti môže moderný procesor ARM prepnúť do iných prevádzkových režimov, napríklad do módu THUMB, keď sa dĺžka príkazu zmení na 2 bajty. Toto vám umožní urobiť váš kód kompaktnejším. V tomto článku sa však nebudeme venovať tomuto režimu, pretože nie je podporovaný procesorom Amber ARM v2a. Z rovnakého dôvodu nebudeme brať do úvahy také režimy ako Jazelle (optimalizované na vykonávanie Java kódu) a nebudeme brať do úvahy NEON príkazy - príkazy pre operácie s viacerými dátami. Pozeráme sa na čistú sadu inštrukcií ARM.

Registre procesora ARM.

Procesor ARM má niekoľko sád registrov, z ktorých tento moment programátor má k dispozícii iba 16-krát. Existuje niekoľko prevádzkových režimov procesora, v závislosti od prevádzkového režimu je vybratá zodpovedajúca banka registrov. Tieto režimy prevádzky:

  • 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 prerušenia spracovania (IRQ, režim prerušenia) a
  • režim rýchleho prerušenia (FIRQ).

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

Okrem vyššie uvedených prevádzkových režimov majú staršie procesory ARM ďalšie režimy:

  • Abort (používa sa na spracovanie výnimiek z prístupu do pamäte),
  • Nedefinované (používa sa na programové implementovanie koprocesora) a
  • režim privilegovaných úloh operačného systému Systém.

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

Pre Amber ARM v2a môže byť sada registrov znázornená takto:

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

Je vidieť, že režim FIRQ je najizolovanejší, má najviac vlastných registrov. To sa deje tak, aby bolo možné spracovať niektoré veľmi kritické prerušenia bez ukladania registrov do zásobníka bez straty času.

Osobitná pozornosť by sa mala venovať registru r15, alias pc (Program Counter) - ukazovateľ na spustiteľné príkazy. S jeho obsahom je možné vykonať rôzne aritmetické a logické operácie, čím vykonanie programu preskočí na ďalšie adresy. Avšak práve pre procesor ARM v2a implementovaný v systéme Amber existujú niektoré jemnosti vo výklade bitov tohto registra.

Faktom je, že v tomto procesore v registri r15 (pc) sú okrem skutočného ukazovateľa na spustiteľné pokyny obsiahnuté aj tieto informácie:

Bity 31:28 - príznaky výsledku vykonania aritmetickej alebo logickej operácie
Bity 27 - IRQ maska ​​prerušenia, prerušenia sú zakázané, keď je bit nastavený.
Bity 26 - maska ​​prerušenia FIRQ, rýchle prerušenia sú deaktivované, keď je bit nastavený.
Bity 25: 2 - skutočný ukazovateľ programovej inštrukcie trvá iba 26 bitov.
Bity 1: 0 - aktuálny prevádzkový režim procesora.
3 - Vedúci
2 - Prerušenie
1 - rýchle prerušenie
0 - používateľ

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

Jednou z ťažkostí pri učení sa ARM assembleru sú alternatívne názvy niektorých registrov. Takže, ako už bolo povedané, r15 je rovnaký počítač. Existuje aj r13 - to je to isté sp (Stack Pointer), r14 je lr (Link Register) - register spiatočnej adresy z postupu. Okrem toho je r12 rovnaká ip (registračný register Intra-Procedure -call), ktorú používajú kompilátory C špeciálnym spôsobom na prístup k parametrom v zásobníku. Toto alternatívne pomenovanie je niekedy mätúce, keď sa pozriete na programový kód niekoho iného - existujú tieto aj tieto označenia registrov.

Vlastnosti vykonávania kódu.

V mnohých typoch procesorov (napríklad x86) možno podmienene vykonať iba prechod na inú adresu programu. Toto nie je prípad ARM. Každá inštrukcia procesora ARM môže alebo nemusí byť vykonaná podmienečne. To vám umožní minimalizovať počet programových skokov, a tým efektívnejšie využívať potrubie procesora.

Nakoniec, čo je to potrubie? Jedna inštrukcia procesora je teraz vybraná z programového kódu, predchádzajúca sa dekóduje a predchádzajúca sa už vykonáva. To je v prípade 3-stupňového potrubia procesora Amber A23, ktoré používame v našom projekte pre dosku Mars Rover2Marsrover2. Modifikácia procesora Amber A25 má 5-stupňové potrubie, ktoré je ešte efektívnejšie. Ale je tu jedno veľké ALE. Príkazy prechodu nútia procesor prepláchnuť potrubie a znovu ho naplniť. Je teda vybraný nový príkaz, ale stále nie je čo dekódovať, a ešte viac, nie je čo ihneď vykonať. Účinnosť vykonávania kódu klesá s častými skokmi. Moderné procesory majú najrôznejšie mechanizmy predikcie vetiev, ktoré nejako optimalizujú naplnenie potrubia, ale náš procesor nie. V každom prípade bolo ARM múdre, aby bol každý príkaz podmienený.

V procesore ARM sú v ľubovoľnom type inštrukcie štyri bity podmienky vykonania inštrukcie zakódované do horných štyroch bitov kódu inštrukcie:

V procesore sú 4 príznaky stavu:
... Negatívne - výsledok operácie sa ukázal ako negatívny,
... Nula - výsledok je nula,
... Noste - pri vykonávaní operácie s nepodpísanými chisos došlo k prenosu,
... oVerflow - pri operácii s podpísanými číslami došlo k pretečeniu, výsledok sa nezmestí do registra)

Tieto 4 príznaky tvoria veľa možných kombinácií podmienky:

Kód Prípona Hodnota Vlajky
4 "h0 ekv Rovnaké Z sada
4 "h1 ne Nerovná sa Z jasné
4 "h2 cs / hs Sada na prenášanie / nepodpísané vyššie alebo rovnaké C sada
4 "h3 cc / lo Niesť nižšie / nepodpísané nižšie C jasné
4 "h4 mi Mínus / negatív N sada
4 "h5 pl Plus / kladné alebo nulové N jasné
4 "h6 vs Pretečenie V súprava
4 "h7 vc Bez pretečenia V jasné
4 "h8 Ahoj Nepodpísaný vyššie C sada a Z jasné
4 "h9 je Nepodpísaný nižší alebo rovnaký C jasné alebo Z nastavené
4 "ha ge Podpísané väčšie alebo rovnaké N == V
4 "hb lt Podpísané 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

Z toho teraz vyplýva ďalšia ťažkosť pri učení pokynov procesora ARM - veľa prípon, ktoré je možné pridať do kódu inštrukcie. Napríklad sčítanie za predpokladu, že je nastavený príznak Z, je príkaz addeq ako prípona add + eq. Ak je príznak N = 0 blpl ako prípona bl + pl, skočte na podprogram.

Vlajky (Negatívne, Zero, Carry, oVerflow) to isté sa nastavuje nie vždy počas aritmetických alebo logických operácií, ako je to napríklad v procesore x86, ale iba vtedy, keď to programátor chce. K tomu existuje ešte jedna prípona mnemotechniky príkazu: „s“ (v kóde príkazu je zakódovaná bitom 20). Príkaz add teda nezmení príznaky a príkaz add zmení príznaky. Môže tu byť aj príkaz podmieneného pridania, ktorý však zmení príznaky. Napríklad: prídavky. Je zrejmé, že počet možných kombinácií názvov príkazov s rôznymi príponami podmieneného vykonania a nastavenia príznakov robí kód assembleru procesora ARM veľmi zvláštnym a ťažko čitateľným. Postupom času si však 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 je obsiahnutý v inštrukčných bitoch procesora.

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

V závislosti na bite I25 sa shifter_operand interpretuje buď ako číselná konštanta, alebo ako index druhého registra operandov, ba dokonca ako operácia posunu na hodnote druhého operandu.

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

pridať r0, r1, r2 @ vložiť do registra r0 súčet hodnôt registrov r1 a r2
sub r5, r4, # 7 @ put rozdiel (r4-7) v registri r5

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

4 "h0 a Boolean AND Rd: = Rn AND shifter_operand
4 "h1 eor Logické výhradné OR Rd: = Rn XOR radiaci_operand
4 "h2 sub Aritmetické odčítanie Rd: = Rn - operátor posuvu
4 "h3 rsb aritmetické inverzné odčítanie Rd: = radič_operátor - Rn
4 "h4 pridať aritmetické pridanie Rd: = Rn + posunovač_operand
4 "h5 adc Aritmetický doplnok Plus Prenášanie vlajky Rd: = Rn + posunovač_operand + prenášanie vlajky
4 "h6 sbc Carry Subtraction Rd: = Rn - radiaci operátor - NIE (Carry Flag)
4 "h7 rsc Nesie inverzné odčítanie Rd: = radič_operand - Rn - NIE (Nesie príznak)
4 "h8 tst Logické AND, ale bez uloženia výsledku sa zmenia iba príznaky Rn AND shifter_operand S bit, ktoré sú vždy nastavené
4 "h9 teq Logické exkluzívne ALEBO, ale bez uloženia výsledku sa zmenia iba príznaky Rn EOR shifter_operand
S bit vždy nastavený
4 "ha cmp Porovnanie, alebo skôr aritmetické odčítanie bez uloženia výsledku, sa zmenia iba Rn príznaky - vždy je nastavený S bitter_operand
4 "hb cmn Porovnanie inverzného alebo skôr aritmetického sčítania bez uloženia výsledku, sú zmenené iba príznaky Rn + shifter_operand vždy nastavený bit S
4 "hc orr Logické ALEBO Rd: = Rn ALEBO shifter_operand
4 "hd mov kópia hodnoty Rd: = shifter_operand (žiadny prvý operand)
4 "he bic Vymazať bity Rd: = Rn A NIE (shifter_operand)
4 "hf mvn Kopírovanie inverzného Rd: = NIE shifter_operand (žiadny prvý operand)

Posuv hlavne.

Procesor ARM má špeciálny obvod „posunu valca“, ktorý umožňuje jeden z operandov posúvať alebo otáčať o určený počet bitov pred každou aritmetickou alebo logickou operáciou. Toto je celkom zaujímavá vlastnosť procesora, ktorá umožňuje veľmi efektívne generovanie kódu.

Napríklad:

@ násobenie 9 je 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 o 15 je násobenie o 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 ľavého posunu lsl existuje aj logický pravý posun lsr a aritmetický pravý posun asr (posun so zachovaním znamienka čísla, najvýznamnejší bit sa vynásobí zľava súčasne s posunom).

K dispozícii je tiež trochu rotácie ror - bity sa tlačia doprava a tie, ktoré sa tlačia von, sa tlačia zľava.
Existuje jeden bitový posun cez príznak C - toto je príkaz rrx. Hodnota registra je posunutá o jeden bit doprava. Vľavo je príznak C načítaný do najvýznamnejšieho bitu registra

Posun nie je možné uskutočniť pomocou pevnej číselnej konštanty, ale pomocou hodnoty tretieho operátora registra. Napríklad:

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

Shifter_operand je teda to, čo popisujeme v pokynoch pre zostavovateľ, ako napríklad „r1, lsr r3“ alebo „r2, lsl # 5“.

Najzaujímavejšie je, že používanie prevádzkových zmien nestojí nič. Tieto posuny (zvyčajne) netrávia ďalšie cykly hodín, čo je veľmi dobré pre výkon systému.

Pomocou numerických operandov.

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

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

V procesore ARM je konštanta definovaná osembitovým číslom v rámci 32-bitového čísla otočeným doprava o párny počet bitov. Teda:

imm_32 = imm_8 ROR (encode_imm * 2)

Ukázalo sa to dosť zložité. Ukazuje sa, že nie každá číselná konštanta môže byť použitá v príkazoch assemblera.

Môžeš písať

pridať r0, r2, # 255 @ desatinná konštanta
pridajte r0, r3, # 0xFF @ konštantu v hex

pretože 255 je v 8-bitovom rozsahu. Tieto príkazy budú zostavené takto:

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

A môžete aj písať

pridať r0, r4, # 512
pridať r0, r5, 0x650000

Kompilovaný kód bude vyzerať takto:

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

V takom prípade sa samotné číslo 512 samozrejme nezmestí do bajtu. Ale na druhej strane si to predstavíme v hexadecimálnej podobe 32'h00000200 a vidíme, že je to 2 otočené doprava o 24 bitov (1 alebo 24). Faktor rotácie je dvakrát menší ako 24, to znamená 12. Ukázalo sa teda, že shifter_operand = (4'hc, 8'h02) - to je dvanásť najmenej významných bitov príkazu. Tak je to aj s číslom 0x650000. Pre neho, shifter_operand = (4'h8, 8'h65).

Je jasné, že nemôžete písať

pridať r0, r1, # 1234567

alebo nevieš písať

mov r0, # 511

pretože tu číslo nemôže byť zastúpené vo forme imm_8 a encode_imm - faktora rotácie. Kompilátor kompilátora vyhodí chybu.

Čo ak konštanta nemôže byť priamo zakódovaná do radiča_pohonu? Budeme musieť robiť najrôznejšie triky.
Napríklad najskôr môžete načítať číslo 512 do bezplatného registra a potom jedno odčítať:

mov r0, # 511
sub r0, r0, # 1

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

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

Najjednoduchší spôsob je písať takto:

ldr r2, = 511

V tomto prípade (všimnite si znamienko "="), ak možno konštantu predstaviť ako imm_8 a encode_imm, ak sa dá zapísať do 12-bitového shifter_operand, kompilátor kompilátora automaticky skompiluje ldr do inštrukcie mov. Ak ale číslo nemôže byť reprezentované týmto spôsobom, potom kompilátor sám vyhradí pamäťové miesto v programe pre túto konštantu a sám dá tomuto pamäťovému miestu meno a príkaz skompiluje do ldr.

Napísal som to teda takto:

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 ,; päťdesiat
1c: e59f8030 ldr r8 ,; 54
20: e3a08b01 mov r8, # 1024; 0x400
24: e59f902c ldr r9 ,; 58
.............
00000050 :
50: 00123456 .slovo 0x00123456
54: 000001ff .word 0x000001ff
58: 00003456 .slovo 0x00003456

Všimnite si, že kompilátor používa pamäťové polohy vzhľadom na register PC (alias r15).

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

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

Zdalo by sa, že existujú iba dva tímy, ale v skutočnosti majú veľa variácií. Stačí sa pozrieť na spôsoby kódovania príkazov ldr / str procesora Amber ARM, aby ste zistili, koľko bitov pomocných príznakov L 20, W 21, B 22, U 23, P 24, I 25 - a tie určujú konkrétne správanie príkazu:

  • Bit L 20 definuje zápis alebo čítanie. 1 - ldr, čítať, 0 - str, písať.
  • Bit B 22 definuje čítanie / zápis 32-bitového slova alebo 8-bitového bajtu. 1 znamená operáciu bajtu. Keď sa bajt načíta do registra, vyradia sa bity registra vyššieho rádu.
  • Bit I 25 určuje použitie poľa Ofset. Ak I 25 == 0, potom sa ofset interpretuje ako číselný posun, ktorý sa musí buď pridať k základnej adrese z registra, alebo odčítať. Ale pripočítanie alebo odčítanie závisí od bitu U 23.

(Cond) - podmienka vykonania operácie. Interpretované rovnakým spôsobom ako pri logických / aritmetických príkazoch - čítanie alebo zápis môže byť podmienené.

Do textu assembleru teda môžete napísať toto:

ldr r1, @ do registra r1 prečítané slovo na adrese z registra r0
ldrb r1, @ do registra r1 načítaný bajt na adrese z registra r0
ldreq r2, @ podmienené čítanie slova
ldrgtb r2, @ podmienené načítanie bajtu
ldr r3, @ prečítané slovo na adrese 8 vo vzťahu k adrese z registra r4
ldr r4, @ prečítané slovo na adrese -16 vo vzťahu 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 rovnakým spôsobom.

Existujú režimy pre-indexového a post-indexového spätného zápisu do pamäte. V týchto režimoch sa ukazovateľ prístupu do pamäte aktualizuje pred alebo po vykonaní príkazu. Ak ovládate programovací jazyk C, potom ovládate konštrukty ukazovateľa prístupu ako ( * psource ++;) alebo ( a = * ++ psource;). V procesore ARM je tento režim prístupu do pamäte presne implementovaný. Po vykonaní príkazu na čítanie sa aktualizujú dva registre naraz - register prijímača prijíma načítanú hodnotu z pamäte a hodnota v registri ukazovateľa do pamäťovej bunky sa posúva dopredu alebo dozadu.

Písanie týchto príkazov je podľa mňa do istej miery nelogické. Zvykanie trvá dlho.

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

Prvý príkaz ldr najskôr zvýši ukazovateľ, potom ho načíta. Druhý príkaz najskôr načíta, potom zvýši ukazovateľ. Hodnota ukazovateľa psrc je v registri r0.

Všetky vyššie uvedené príklady boli pre prípad, keď bol bitový príkazový kód I 25 vymazaný. Ale stále sa dá nainštalovať! Hodnota poľa Ofset potom nebude číselnou konštantou, ale už tretím registrom zúčastňujúcim sa na operácii. Hodnota tretieho registra môže byť navyše vopred posunutá!

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

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

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

V starších modeloch procesorov ARM je táto rozmanitosť pokynov 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 pol 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 pol slov bajtov ldrsh alebo ldrsb interpretovaných ako podpísané čísla. V týchto prípadoch sa najvýznamnejší bit načítaného prúžku alebo bytu vynásobí na najvýznamnejšie bity celého slova v registri prijímača. Napríklad načítanie pol slova 0xff25 príkazom ldrsh v cieľovom registri sa ukáže ako 0xffffff25.

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

Príkazy ldr / str nie sú jedinými príkazmi na prístup do pamäte. V procesore ARM existujú aj pokyny, ktoré vám umožňujú vykonávať prenos blokov - môžete načítať obsah niekoľkých po sebe idúcich slov z pamäte z niekoľkých registrov naraz. Je tiež možné zapisovať postupne do pamäte hodnoty niekoľkých registrov.

Mnemotechnické pomôcky pre blokovanie prenosu začínajú koreňom ldm (LoaD Multiple) alebo stm (Store Multiple). Ale potom, ako to už v ARM býva, začína príbeh s príponami.

Príkaz vo všeobecnosti vyzerá takto:

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

Prípona (Cond) je pochopiteľná, je to podmienka vykonania príkazu. Prípona (režim) je režim prenosu, k tomu viac neskôr. Rd je register, ktorý definuje základnú adresu v pamäti na čítanie alebo zápis. Výkričník za registrom Rd naznačuje, že sa po operácii čítania a zápisu zmení. Zoznam registrov, ktoré sú načítané z pamäte alebo vykladané 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)

Záznam do pamäte sa nebude vykonávať v uvedenom poradí. Zoznam jednoducho naznačuje, ktoré registre sa zapíšu do pamäte, a je to. Kód inštrukcie obsahuje 16 bitov vyhradených pre Register List, iba podľa počtu registrov v banke procesora. Každý bit v tomto poli označuje, ktorý register sa bude zúčastňovať na operácii.

Teraz o režime čítania a zápisu. Je tu veľký zmätok. Ide o to, že pre rovnakú akciu je možné použiť rôzne názvy režimov.

Ak urobíte malú lyrickú odbočku, musíte hovoriť o ... stohu. Zásobník predstavuje spôsob prístupu k údajom typu LIFO - Last In First Out (wiki) - last in, first out. Zásobník je široko používaný v programovaní pri volaní procedúr a ukladaní stavu registrov pri vstupe do funkcií a ich obnove pri výstupe, ako aj pri odovzdávaní parametrov volaným procedúram.

Zásobník v pamäti je, kto by si to myslel, štyri typy.

Prvý typ je Plný zostupný. To je prípad, keď ukazovateľ zásobníka ukazuje na obsadený prvok zásobníka a zásobník rastie v smere klesajúcich adries. Ak potrebujete vložiť slovo do stohu, ukazovateľ stohu sa najskôr zníži (Decrement Before), potom sa slovo napíše na adresu ukazovateľa stohu. Ak potrebujete odstrániť počítačové slovo zo zásobníka, bude sa slovo čítať pri aktuálnej hodnote ukazovateľa zásobníka, potom sa ukazovateľ posunie nahor (Prírastok po).

Druhým typom je Full Ascending. Zásobník nerastie nadol, ale nahor, smerom k veľkým adresám. Ukazovateľ tiež ukazuje na obsadený prvok. Ak potrebujete vložiť slovo do stohu, najskôr sa zvýši ukazovateľ stohu, potom sa slovo napíše ukazovateľom (Zvýšiť pred). Keď je potrebné odobrať zo zásobníka, čítame najskôr ukazovateľom zásobníka, pretože ten ukazuje na obsadený prvok, potom ukazovateľ zásobníka klesá (Decrement Afte r).

Tretím typom je Prázdne zostupné. Zásobník rastie dole, ako v prípade úplného zostupu, ale rozdiel je v tom, že ukazovateľ zásobníka ukazuje na neobsadenú bunku. Teda, keď potrebujete vložiť slovo do stohu, okamžite sa urobí záznam, potom sa ukazovateľ stohu zníži (Decrement After). Pri vyskakovaní zo zásobníka sa najskôr zväčší ukazovateľ, potom sa zobrazí hodnota (Zvýšiť pred).

Štvrtý typ je Prázdny vzostupne. Dúfam, že je všetko jasné - zásobník rastie smerom hore. Ukazovateľ zásobníka ukazuje na prázdny prvok. Ak chcete vložiť zásobník, je to napísať slovo na adresu ukazovateľa zásobníka a zvýšiť ukazovateľ zásobníka (Prírastok po). Odstrániť zo zásobníka - zmenšiť ukazovateľ zásobníka a prečítať slovo (Zmenšiť predtým).

Počas operácií so zásobníkom musí byť teda ukazovateľ zväčšený alebo zmenšený - (Prírastok / Zníženie) pred alebo po (Pred / Po) načítaní / zápise do pamäte, v závislosti od typu stohu. Napríklad procesory Intel majú špeciálne pokyny na prácu so zásobníkom, napríklad PUSH (vloženie slova do zásobníka) alebo POP (odstránenie slova zo zásobníka). V procesore ARM nie sú žiadne špeciálne príkazy, ale používajú sa príkazy ldm a stm.

Ak implementujete zásobník pomocou pokynov procesora ARM, dostanete nasledujúci obrázok:

Prečo ten istý tím musel dostať odlišné mená? Vôbec tomu nerozumiem ... Tu si samozrejme treba uvedomiť, že štandard stacku pre ARM je stále Full Descending.

Ukazovateľ zásobníka na procesore ARM je sp alebo r13. Všeobecne ide o takúto dohodu. Samozrejme, zápis STM alebo čítanie LDD je možné vykonať aj s inými základnými registrami. Musíte si však uvedomiť, ako sa register sp líši od ostatných registrov - môže sa líšiť v rôznych režimoch procesora (USR, SVC, IRQ, FIRQ), pretože sú tam banky registrov.

A ešte jedna poznámka. Napíš riadok do kódu zostavy ARM ako tlačiť (r0-r3), určite môžete. Iba teraz to bude v skutočnosti rovnaký príkaz. stmfd sp !, (r0-r3).

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


stmfd sp !, (r0-r3)
stmdb sp !, (r0-r3)
tlačiť (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, ks)
ldm r5, (r0, pc)

Dostaneme po kompilácii:

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, pc)

Prechody v programoch.

Programovanie nie je možné bez prechodov. V každom programe existuje cyklické vykonávanie kódu a volanie procedúr, funkcií je tiež podmienené vykonávanie sekcií kódu.

V procesore Amber ARM v2a existujú iba dva príkazy: b (od slova Branch - pobočka, pobočka) a bl (Branch with Link - pobočka so uložením spiatočnej adresy).

Syntax príkazu je veľmi jednoduchá:

b (cond) štítok
bl (kond) štítok

Je zrejmé, že akékoľvek prechody môžu byť podmienené, to znamená, že program môže obsahovať také zvláštne slová tvorené z koreňov „b“ a „bl“ a prípony podmienok (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

Odroda je úžasná, nie?

Inštrukcia skoku obsahuje 24-bitový posun. Adresa skoku sa počíta ako súčet aktuálnej hodnoty ukazovateľa pc a čísla posunutia posunutého o 2 bity doľava, interpretovaného ako podpísané číslo:

Nový ks = ks + offset * 4

Rozsah skoku je teda 32 MB dopredu alebo dozadu.

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

Pozrime sa na postup bybyte 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čítať 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íš znak na UART, iba ak nie je obsadený
4b8: 01b0f00e movseq pc, lr; podmienený návrat z postupu, ak UART nebol zaneprázdnený
4bc: 1afffff9 bne 4a8<_outbyte+0x8>; slučka na kontrolu stavu UART

Myslím si, že z komentárov tohto úryvku je zrejmé, ako tento postup funguje.

Ďalšia dôležitá poznámka k prechodom. Register r15 (pc) je možné použiť v bežných aritmetických alebo logických operáciách ako register prijímača. Takže príkaz ako add pc, pc, # 8 je celkom pokyn na prechod na inú adresu.

K prechodom je potrebné poznamenať ešte jednu vec. Majú aj staršie procesory ARM ďalšie príkazy prechody bx, blx a blj. Jedná sa o príkazy na preskočenie na útržky kódu s iným príkazovým systémom. Bx / blx umožňuje prepnúť na 16-bitový THUMB kód pre procesory ARM. Blj je volanie procedúry pre inštrukčnú sadu Jazelle (podpora jazyka Java v procesoroch ARM). Náš Amber ARM v2a nemá tieto príkazy.

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

Čo bude ďalej? Ďalej v skutočnosti môžete napísať program pomocou inštrukčnej sady thumb-2 podporovanej jadrom Cortex-M3. Zoznam a popis podporovaných príkazov nájdete v dokumente s názvom Všeobecná používateľská príručka k produktu Cortex-M3(kapitola Sada inštrukcií Cortex-M3), ktorú nájdete na karte Knihy v projektovom manažérovi v aplikácii Keil uVision 5. Viac podrobností o príkazoch thumb-2 bude napísaných v jednej z nasledujúcich častí tohto článku, ale poďme si teraz povedať o programoch pre STM32 v všeobecne.

Rovnako ako akýkoľvek iný program v assembleri, aj program pre STM32 pozostáva z príkazov a pseudopríkazov, ktoré sa prevedú priamo do strojových kódov, ako aj z rôznych smerníc, ktoré sa neprekladajú do strojových kódov, ale slúžia na servisné účely (označenie programu). , priradenie názvov symbolických konštánt atď.)

Napríklad špeciálna smernica umožňuje rozdeliť program na samostatné sekcie - PLOCHA... Má nasledujúcu syntax: AREA Section_Name (, type) (, attr) ... kde:

  1. Názov_sekcie- názov sekcie.
  2. typu- typ sekcie. Pre sekciu obsahujúcu údaje musí byť zadaný typ DATA a pre sekciu obsahujúcu príkazy typ CODE.
  3. attr- ďalšie atribúty. Napríklad atribúty readonly alebo readwrite označujú, v ktorej pamäti má byť oddiel alokovaný, atribút align = 0..31 označuje, ako má byť oddiel zarovnaný v pamäti, atribút noinit sa používa na alokovanie pamäťových oblastí, ktoré nepotrebujete byť inicializovaný alebo inicializovaný na nulu (pri použití tohto atribútu je možné od typu sekcie vynechať, pretože sa dá použiť iba pre dátové sekcie).

Smernice EQU pravdepodobne každý dobre známy, pretože sa nachádza v akomkoľvek assembleri a je navrhnutý 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 oznámi kompilátoru, že sa stretli všetky symboly názov musí byť nahradené číslom číslo... Povedzme, že ak kvalita číslo použite adresu pamäťovej bunky, potom v budúcnosti nebude možné k tejto bunke pristupovať pomocou adresy, ale pomocou ekvivalentného symbolického zápisu ( názov).

Smernice ZÍSKAJTE názov súboru vloží do programu text zo súboru s názvom názov súboru... Toto je obdoba direktívy include v assembleri pre AVR. Môže sa použiť napríklad na vytiahnutie samostatný súbor smernice na priraďovanie symbolických mien rôznym registrom. To znamená, že presunieme všetky priradenia mien do samostatného súboru a potom, aby program mohol používať tieto symbolické názvy, jednoducho tento súbor zahrnieme do nášho programu so smernicou GET.

Okrem tých, ktoré sú uvedené vyššie, samozrejme existuje aj veľa rôznych smerníc, úplný zoznam ktoré nájdete v kapitole Odkazy na smernice dokument Assembler User Guide ktorú nájdete v aplikácii Keil uVision 5 pozdĺž nasledujúcej cesty: tab Knihy projektový manažér -> Príručka používateľa nástrojov -> Kompletný výber používateľskej príručky -> Assembler User Guide.

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

(štítok) SYMBOL (expr) (, expr) (, expr) (; komentár)

(štítok) - štítok. Je to potrebné, aby bolo možné určiť adresu príkazu nasledujúceho po tomto štítku. Štítok je voliteľný prvok a používa sa iba v prípade, že potrebujete poznať adresu príkazu (napríklad na prechod na tento príkaz). Pred štítkom nesmú byť medzery (to znamená, že musí začínať od úplne prvej pozície riadku), navyše názov štítku môže začínať iba písmenom.

SYMBOL je príkaz, pseudovkaz alebo smernica. Príkaz, na rozdiel od štítku, naopak musí mať od začiatku riadku určité odsadenie, aj keď pred ním nie je štítok.

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

; - oddeľovač. Celý text na riadku za týmto oddeľovačom sa považuje za komentár.

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

ZAČIATOK OBLASTI, KÓD, ČÍTAJTE dcd 0x20000400 dcd Program_start VSTUP Program_start b Program_start END

ŠTART AREÁLU, KÓD, ČÍTAJTE dcd 0x20000400 dcd Program_start VSTUP Program_start b Program_start END

V tomto programe máme iba jednu časť s názvom ŠTART. Táto časť sa nachádza vo flash pamäti (pretože sa pre ňu používa atribút readonly).

Prvé 4 bajty tejto časti obsahujú adresu hornej časti 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 sa spustiteľný kód skladá z jedného jediného príkazu, ktorý bezpodmienečne preskočí na štítok Program_start, to znamená, že vykoná rovnaký príkaz znova.

Pretože vo flashi je iba jedna sekcia, potom v súbore scatter pre náš program, ako First_Section_Name, bude potrebné uviesť jeho názov (teda START).

V tomto prípade máme zmiešané údaje a príkazy. Adresa hornej časti zásobníka a adresa vstupného bodu (údajov) sa zapisujú pomocou smerníc dcd priamo v sekcii kódu. Samozrejme, môžete písať týmto spôsobom, ale nie veľmi pekne. Najmä ak popíšeme celú tabuľku prerušení a výnimiek (ktorá sa ukáže byť dosť dlhá), nielen vektor resetovania. Oveľa krajšie je nepohltiť kód nepotrebnými údajmi, ale umiestniť vektorovú tabuľku prerušenia do samostatnej časti, alebo ešte lepšie, do samostatného súboru. Rovnako je možné inicializáciu zásobníka umiestniť do samostatnej sekcie alebo dokonca do súboru. Napríklad umiestnime všetko do samostatných častí:

ZÁSOBNÍK V OBLASTI, NOINIT, PREČÍTAJTE PRIESTOR 0x400; preskočiť 400 bajtov Stack_top; a vložte štítok AREA RESET, DATA, READONLY dcd Stack_top; Adresa štítku Stack_top dcd Program_start; adresa štítku Program_start PLOCHOVÝ PROGRAM, KÓD, OKAMŽITÝ VSTUP; vstupný bod (začiatok spustiteľného kódu) Program_start; štítok spustenia programu b Program_start END

Rovnaký program (ktorý stále nerobí nič užitočné), ale teraz vyzerá oveľa prehľadnejšie. V súbore scatter pre tento program musíte zadať názov RESET ako First_Section_Name, aby sa táto časť nachádzala ako prvá v pamäti Flash.

Ahoj všetci!
Som programátor v odbore Java. 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ívnych aplikácií v C. Potom som narazil na problém s optimalizáciou knižníc Linuxu. Mnohé sa ukázali ako absolútne nie optimalizované pre ARM a veľmi zaťažovali procesor. Predtým som prakticky neprogramoval v montážnom jazyku, takže spočiatku bolo ťažké sa tento jazyk začať učiť, ale napriek tomu som sa rozhodol to vyskúšať. Tento článok je napísaný takpovediac od začiatočníka po začiatočníka. Pokúsim sa opísať základy, ktoré som sa už naučil, dúfam, že niekoho to bude zaujímať. Okrem toho by som rád dostal konštruktívnu kritiku od profesionálov.

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

ARM architektúra (Advanced RISC Machine, Acorn RISC Machine, pokročilý RISC stroj) je skupina licencovaných 32-bitových a 64-bitových mikroprocesorových jadier vyvinutých spoločnosťou ARM Limited. Spoločnosť sa zaoberá výlučne vývojom jadier a nástrojov pre ne (kompilátory, ladiace nástroje atď.), Ktoré zarába peniaze z licencovania architektúry pre výrobcov tretích strán.

Ak niekto nevie, tak 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 vyvinula v priebehu času a keďže boli definované profily 3 ARMv7: „A“ (aplikácia) pre aplikácie, „R“ (reálny čas) pre reálny čas, „M“ (mikrokontrolér) pre mikrokontrolér. Históriu vývoja tejto technológie a ďalšie zaujímavé údaje si môžete prečítať na Wikipédii alebo googlením na internete. ARM podporuje rôzne režimy práca (Palec a ARM, navyše sa nedávno objavil Palec-2, ktorý je zmesou PAM a Palec). V tomto článku zvážime skutočný režim ARM, v ktorom sa vykonáva 32-bitová sada inštrukcií.

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

  • 37 registrov (z ktorých je počas vývoja viditeľných iba 17)
  • Aritmeticko-logická jednotka (ALU) - vykonáva aritmetické a logické úlohy
  • Barrel shifter - zariadenie určené na pohyb blokov dát o konkrétny počet bitov
  • CP15 je špeciálny systém, ktorý riadi koprocesory ARM
  • Dekodér inštrukcií - zaoberá sa transformáciou inštrukcie do postupnosti mikroopsov
Nie sú to všetky časti ARM, ale ponorenie sa do džungle stavebných procesorov je nad rámec tohto článku.
Vykonanie potrubia
Procesory ARM používajú trojstupňový kanál (počnúc ARM8 bol implementovaný päťstupňový kanál). Pozrime sa na jednoduché potrubie, ktoré ako príklad používa procesor ARM7TDMI. Vykonanie každej inštrukcie pozostáva z troch krokov:

1. Fáza vzorkovania (F)
V tomto okamihu pokyny prúdia z pamäte RAM do potrubia procesora.
2. Stupeň dekódovania (D)
Pokyny sú dekódované a ich typ je rozpoznaný.
3. Stupeň vykonania (E)
Dáta vstupujú do ALU a sú vykonávané a výsledná hodnota je zapísaná do určeného registra.

Pri vývoji si však treba uvedomiť, že existujú pokyny, ktoré využívajú niekoľko vykonávacích cyklov, napríklad načítajú (LDR) alebo ukladajú. V tomto prípade je etapa vykonania (E) rozdelená do fáz (E1, E2, E3 ...).

Podmienené vykonávanie
Jednou z najdôležitejších funkcií zostavovateľa ARM je podmienené vykonávanie. Každá inštrukcia môže byť podmienene vykonaná a sú na to použité prípony. Ak sa k názvu inštrukcie pridá prípona, pred vykonaním sa parametre skontrolujú. Ak sa parametre nezhodujú s podmienkou, príkaz sa nevykoná. Prípony:
MI - záporné číslo
PL - kladné alebo nulové
AL - vykonať inštrukciu vždy
Existuje oveľa viac podmienených prípon. Prečítajte si zvyšok prípon a príkladov v oficiálnej dokumentácii: Dokumentácia ARM
Teraz je čas zvážiť ...
Základy syntaxe assemblera ARM
Pre tých, ktorí už predtým pracovali s assemblerom, môže byť tento bod skutočne preskočený. Pre všetkých ostatných popíšem základy práce s týmto jazykom. Takže každý program v montážnom jazyku pozostáva z pokynov. Inštrukcia je vytvorená týmto spôsobom:
(štítok) (inštrukcia | operandy) (@ ​​komentár)
Štítok je voliteľný parameter. Inštrukcia je priamo mnemotechnickou pomôckou inštrukcie pre procesor. Základné pokyny a ich použitie budú prediskutované neskôr. 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ú tieto názvy registrov:
1.r0-r15

3.v1-v8 (variabilné 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, rovnako ako v ktoromkoľvek (prakticky) inom programovacom jazyku, je možné použiť premenné a konštanty. Sú rozdelené do nasledujúcich typov:
  • Numerické
  • hlavolam
  • String
Numerické premenné sa inicializujú takto:
SETA 100; vytvorí sa číselná premenná „a“ s hodnotou 100.
Reťazcové premenné:
improb SADY "literal"; vytvorí sa premenná nepravdepodobnosti s hodnotou „literal“. POZOR! Hodnota premennej nesmie presiahnuť 5 120 znakov.
Booleovské premenné používajú hodnoty TRUE, respektíve FALSE.
Príklady pokynov pre zostavovateľ 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 upevnili používanie základných pokynov, napíšeme niekoľko jednoduchých príkladov, najskôr však potrebujeme reťazec nástrojov na ruku. Pracujem na Linuxe, takže som si vybral: frank.harvard.edu/~coldwell/toolchain (arm-unknown-linux-gnu toolchain). Inštaluje sa rovnako ľahko ako hrušky ako každý iný program pre Linux. V mojom prípade (ruská Fedora) som potreboval nainštalovať iba balíčky rpm zo stránky.
Teraz je čas napísať najjednoduchší príklad. Program bude úplne zbytočný, ale hlavné je, že bude fungovať :) Tu je kód, ktorý vám ponúkam:
start: @ Voliteľný riadok označujúci začiatok programu mov r0, # 3 @ Vložte hodnotu 3 do registra r0 mov r1, # 2 @ To isté urobte s registrom r1, len teraz s hodnotou 2 pridajte r2, r1, r0 @ Sčítajte hodnoty r0 a r1, zapíšte odpoveď na r2 mul r3, r1, r0 @ Vynásobte hodnotu registra r1 hodnotou registra r0, napíšte odpoveď na r3 stop: b stop @ The riadok ukončenia programu
Zostavte program a získate 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 arm.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 otestovanie chodu programu nie je potrebné mať vlastné zariadenie na ruku. Stačí nainštalovať QEMU. Pre referenciu:

QEMU je bezplatný a otvorený softvér na emuláciu hardvéru pre rôzne platformy.

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

Funguje na platformách Syllable, FreeBSD, FreeDOS, Linux, Windows 9x, Windows 2000, Mac OS X, QNX, Android a ďalších.

Takže na napodobnenie ramena potrebujete qemu-system-arm. Tento balík je v mňam, takže pre používateľov Fedory sa nemusíte trápiť a stačí spustiť príkaz:
yum nainštalovať qemu-system-arm

Ďalej musíte spustiť emulátor ARM, aby spustil náš program arm.bin. Za týmto účelom vytvoríme súbor flash.bin, ktorý bude flash pamäťou pre QEMU. Je to veľmi ľahké:
dd if = / dev / zero of = flash.bin bs = 4096 count = 4096 dd if = arm.bin of = flash.bin bs = 4096 conv = notrunc
Teraz načítame QEMU s prijatou pamäťou flash:
qemu-system-arm -M connectx -pflash flash.bin -nographic -serial / dev / null
Na výstupe dostanete niečo také:

$ qemu-system-arm -M connectx -pflash flash.bin -nographic -serial / dev / null
Monitor QEMU 0.15.1 - pre ďalšie informácie zadajte „help“
(qemu)

Náš program arm.bin musel zmeniť hodnoty štyroch registrov, preto sa kvôli kontrole správnosti práce pozrime na tieto rovnaké registre. To sa deje veľmi jednoduchým príkazom: informačné registre
Na výstupe uvidíte všetkých 15 registrov ARM, pričom štyri z nich majú zmenené hodnoty. Skontrolujte :) Hodnoty registrov sú rovnaké, aké by ste očakávali po vykonaní programu:
(qemu) informačné registre R00 = 00000003 R01 = 00000002 R02 = 00000005 R03 = 00000006 R04 = 00000000 R05 = 00000000 R06 = 00000000 R07 = 00000000 R08 = 00000000 R09 = 00000000 R10 = 00000000 R11 = 00000000 R00 = 00000000 R00 R15 = 00000010 PSR = 400001d3 -Z-- A svc32

P.S. V tomto článku som sa pokúsil popísať základy programovania v ARM assembleri. Dúfam, že sa vám páčilo! To stačí na ďalšie ponorenie sa do džungle tohto jazyka a písanie programov v ňom. Ak sa všetko podarí, budem ďalej písať o tom, čo zistím sám. Ak sa vyskytnú chyby, prosím nekopávajte, pretože som v assembleri nový.

Ak používate ako operačný systém Raspberry Pi distribúciu Raspbian, budete potrebovať dva pomocné programy, a to ako (assembler, ktorý prevádza zdrojový kód montážneho jazyka na binárny kód) a ld (linker, ktorý vytvorí výsledný spustiteľný súbor). Obidve pomocné programy sú súčasťou balenia softvér binutils, aby už mohli byť vo vašom systéme. Potrebujete samozrejme aj dobrého textového editora; Vždy odporúčam používať Vim na vývoj softvéru, ale má to veľkú bariéru pre vstup, takže Nano alebo akýkoľvek iný textový editor GUI bude v pohode.

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

Global _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 =. - struna

Tento program iba vytlačí reťazec „Ciao!“ na obrazovke a ak ste si prečítali články o používaní montážneho jazyka na prácu s centrálnymi procesorovými jednotkami x86, niektoré použité pokyny vám môžu byť známe. Stále však existuje veľa rozdielov medzi pokynmi architektúr x86 a ARM, ktoré možno tiež povedať o syntaxi zdrojového kódu, takže si ich podrobne rozoberieme.

Ale predtým je potrebné spomenúť, že na zostavenie daného kódu a prepojenie výsledného súboru objektu so spustiteľným súborom musíte použiť nasledujúci príkaz:

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

Teraz môžete spustiť vytvorený program pomocou príkazu. / Myfirst. Pravdepodobne ste si všimli, že spustiteľný súbor má veľmi skromnú veľkosť asi 900 bajtov - ak by ste používali programovací jazyk C a funkciu put (), veľkosť binárny súbor by bol asi päťkrát väčší!

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

Ak ste čítali predchádzajúce články v programovacom rade x86 assembleru, pravdepodobne si pamätáte na okamih, keď ste prvýkrát spustili svoj vlastný operačný systém, keď ste na obrazovke zobrazili správu bez pomoci systému Linux alebo iného operačného systému. Potom sme to vylepšili pridaním jednoduchého rozhrania príkazový riadok a mechanizmus na načítanie a spustenie programov z disku, čo ponecháva rezervu pre budúcnosť. Bolo to veľmi zaujímavé, ale nie veľmi ťažká práca hlavne kvôli vonkajšej pomoci Firmvér systému BIOS- poskytovalo zjednodušené rozhranie pre prístup na obrazovku, klávesnicu a čítačku diskiet.

V prípade Raspberry Pi už nebudete mať k dispozícii užitočné funkcie systému BIOS, takže si budete musieť sami vyvinúť ovládače pre zariadenia, čo je samo o sebe ťažká a nezaujímavá práca v porovnaní s kreslením na obrazovku a implementáciou mechanizmus vykonávania vlastných programov. Zároveň je v sieti niekoľko príručiek, ktoré podrobne popisujú počiatočné fázy procesu zavádzania Raspberry Pi, vlastnosti mechanizmu prístupu 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. V zásade je to sada výučbových programov, ktoré popisujú techniky montážneho jazyka pre zapínanie LED diód, prístup k pixelom na obrazovke, získavanie vstupu z klávesnice atď. Pri čítaní sa dozviete veľa o hardvér Raspberry Pi, s príručkami napísanými pre pôvodné modely týchto jednodeskových počítačov, takže neexistuje záruka, že budú relevantné pre modely ako A +, B + a Pi 2.

Ak uprednostňujete programovací jazyk C, mali by ste si prečítať dokument Valvers na adrese http://tinyurl.com/qa2s9bg, ktorý popisuje proces konfigurácie krížového kompilátora a zostavenia najjednoduchšieho jadra operačného systému, a v sekcii Wiki na užitočný zdroj OSDev umiestnený Na stránke http://wiki.osdev.org/Raspberry_Pi_Bare_Bones nájdete informácie o tom, ako vytvoriť a spustiť základné jadro OS na Raspberry Pi.

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

Ako to celé funguje

Prvé dva riadky kódu nie sú inštrukciami CPU, ale pokynmi na zostavenie a odkazovanie. Každý program by mal mať presne definovaný vstupný bod s názvom _start a v našom prípade to bol na úplnom začiatku kódu. Preto informujeme linkera, že vykonávanie kódu by malo začať od prvej inštrukcie a nie sú potrebné žiadne ďalšie akcie.

Nasledujúcim pokynom sme vložili číslo 4 do registra r7. (Ak ste nikdy predtým nepracovali s montážnym jazykom, mali by ste vedieť, že register je pamäťové miesto umiestnené priamo v centrálnom procesore. Vo väčšine moderných centrálne spracovateľské jednotky implementoval malý počet registrov v porovnaní s miliónmi alebo miliardami buniek RAM, ale registre sú nepostrádateľné, pretože fungujú oveľa rýchlejšie.) Čipy architektúry ARM poskytujú vývojárom veľké množstvo registrov na všeobecné účely: vývojár môže využívať až 16 registrov s názvami od r0 do r15 a tieto registre nie sú spojené so žiadnymi historicky stanovenými obmedzeniami, ako v prípade architektúry x86, kde je možné niektoré z registrov v konkrétnom čase použiť na konkrétne účely.

Aj keď je teda inštrukcia mov veľmi podobná inštrukcii s rovnakým názvom v architektúre x86, v každom prípade by ste mali venovať pozornosť hašovacím symbolom vedľa čísla 4, čo naznačuje, že nasledujúca je celočíselná hodnota, a nie adresa v pamäti. V takom prípade chceme na vytlačenie nášho riadku použiť systémové volanie s jadrom systému Linux; na použitie systémových volaní musíte vyplniť registre požadovanými hodnotami predtým, ako jadru odpustíte svoju prácu. Číslo systémového volania musí byť umiestnené v registri r7, pričom číslo 4 je číslo systémového volania zápisu.

S ďalšou inštrukciou mov sme do registra r0 vložili deskriptor súboru, do ktorého sa má zapísať reťazec „Ciao!“, Teda deskriptor štandardného výstupného toku. Pretože sa v tomto prípade použije štandardný výstupný tok, jeho štandardný deskriptor, tj. 1, sa umiestni do registra. Ďalej musíme do registra r1 vložiť adresu reťazca, ktorý chceme vygenerovať, pomocou inštrukcie ldr (inštrukcia „načítať do registra“; všimnite si znamienko rovnosti, ktoré označuje štítok, nie adresu). Na konci kódu, konkrétne v sekcii údajov, deklarujeme tento reťazec vo forme postupnosti znakov ASCII. Aby sme úspešne využili systémové volanie „write“, musíme tiež povedať jadru operačného systému, aký dlhý je výstupný reťazec, a tak vložíme hodnotu stringlen do registra r2. (Hodnota stringlen sa počíta odčítaním koncovej adresy reťazca od začiatočnej adresy.)

V tomto okamihu sme všetky registre naplnili potrebnými údajmi a sme pripravení preniesť kontrolu do linuxového jadra. Aby sme to dosiahli, použijeme inštrukciu swi, čo znamená „softvérové ​​prerušenie“, aby sme skočili do priestoru jadra OS (v podstate 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ú hodnotu 4 a dospeje k záveru: „Takže, volajúci chce vytlačiť reťazec.“ Potom preskúma obsah ďalších registrov, odošle reťazec a vráti kontrolu do nášho programu.

Na obrazovke teda vidíme riadok „Ciao!“, Po ktorom len musíme správne ukončiť vykonávanie programu. Tento problém vyriešime umiestnením volacieho čísla výstupného systému do registra r7 a potom vyvolaním nulovej inštrukcie softvérového čísla prerušenia. A to je všetko - jadro OS dokončí vykonanie nášho programu a my sa opäť presunieme do príkazového shellu.

Vim (vľavo) je vynikajúci textový editor na písanie montážneho jazyka - súbor na zvýraznenie syntaxe pre architektúru ARM je k dispozícii na adrese http://tinyurl.com/psdvjen.

Rada: pri práci s montážnym jazykom by ste nemali šetriť komentármi. Nepoužili sme Vysoké číslo komentáre v tomto článku tak, aby kód zaberal čo najmenej miesta na stránkach časopisu (a tiež preto, že sme podrobne opísali účel každého z pokynov). Ale pri vývoji zložité programy ktorého kód sa zdá byť na prvý pohľad zrejmý, mali by ste vždy premýšľať o tom, ako bude vyzerať, keď čiastočne zabudnete na syntax zostavovacieho jazyka pre architektúru ARM a po niekoľkých mesiacoch sa vrátite k vývoju. Môžete zabudnúť na všetky triky a skratky použité v kóde, po ktorých bude kód vyzerať ako úplný gýč. Na základe vyššie uvedeného by ste mali ku kódu pridať čo najviac komentárov, aj keď sa niektoré v súčasnosti javia ako príliš zrejmé!

Reverzné inžinierstvo

V niektorých prípadoch môže byť užitočná aj konverzia binárneho súboru do montážneho jazyka. Výsledkom tejto operácie nie je zvyčajne veľmi dobre tvarovaný kód bez čitateľných názvov značiek a komentárov, čo však môže byť užitočné pri štúdiu transformácií, ktoré vykonal asembler pomocou vášho kódu. Ak chcete rozobrať prvý binárny súbor, jednoducho spustite nasledujúci príkaz:

Objdump -d môj prvý

Tento príkaz rozloží spustiteľnú časť binárneho súboru (nie však sekciu s údajmi, pretože obsahuje text ASCII). Ak sa pozriete na rozobratý kód, pravdepodobne si všimnete, že pokyny v ňom sú takmer totožné s pokynmi v pôvodnom kóde. Demontéri sa používajú hlavne vtedy, keď chcete študovať správanie programu, ktorý je k dispozícii iba v binárnej forme, napríklad vírusu alebo jednoduchého programu s uzavretým zdrojom, ktorého správanie chcete napodobniť. Pritom by ste si mali pamätať na obmedzenia, ktoré ukladá autor študovaného programu! Rozobrať binárny program a jednoducho skopírovať výsledný kód do kódu projektu je samozrejme zlý nápad; výsledný kód však môžete použiť na preštudovanie princípu programu.

Podprogramy, cykly a podmienené vyhlásenia

Teraz, keď vieme, ako navrhovať, zostavovať a prepájať jednoduché programy, poďme sa teraz pozrieť na niečo trochu zložitejšie. V nasledujúcom programe sa na výstup riadkov používajú podprogramy (vďaka nim dokážeme opakovane použiť fragmenty kódu a ušetríme sa tak nutnosti vykonávať rovnaký typ operácií pri plnení registrov údajmi). Tento program implementuje hlavnú slučku udalostí, ktorá umožňuje výstup z riadku, kým užívateľ nezadá „q“. Preskúmajte kód a pokúste sa pochopiť (alebo uhádnuť!) Účel pokynov, ale nezúfajte, ak niečomu nerozumiete, pretože o niečo neskôr to tiež zvážime veľmi podrobne. Upozorňujeme, že symboly @ sa používajú na zvýraznenie komentárov v jazyku zostavy ARM.

Global _start _start: ldr r1, = string1 mov r2, # string1len bl print_string loop: mov r7, # 3 @ read mov r0, # 0 @ stdin ldr r1, = char mov r2, # 2 @ two characters swi 0 ldr r1, = char ldrb r2, cmp r2, # 113 @ ASCII kód ​​znaku „q“ beq hotovo 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 na ukončenie! \ n" string1len =. - string1 string2: .ascii "To nebolo" t q ... \ n "string2len =. - string2 char: .word 0

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ú implementáciu systémového volania zápisu a hneď potom prejde do podprogramu print_string, ktorý sa nachádza nižšie v kóde. Na uskutočnenie tohto prechodu sa použije inštrukcia bl, ktorej názov znamená „pobočka a odkaz“ a sama uloží aktuálnu adresu do kódu, čo vám umožní neskôr sa k nej vrátiť pomocou inštrukcie bx. Rutina print_string jednoducho vyplní ostatné registre, aby vykonala volanie systému zápisu rovnakým spôsobom ako v našom prvom programe, predtým ako skočí do priestoru jadra OS a potom sa vráti na uloženú adresu kódu pomocou inštrukcie bx.

Keď sa vrátime k volajúcemu kódu, nájdeme štítok s názvom loop - názov štítku už naznačuje, že sa k nemu po chvíli vrátime. Najprv však použijeme ďalšie systémové volanie s názvom read (na čísle 3), aby sme prečítali znak zadaný používateľom pomocou klávesnice. Preto sme vložili hodnotu 3 do registra r7 a hodnotu 0 (štandardný popisovač vstupného toku) do registra r0, pretože musíme čítať vstup používateľa, nie údaje zo súboru.

Ďalej umiestnime adresu, na ktorú chceme uložiť znak prečítaný a umiestnený jadrom OS do registra r1 - v našom prípade ide o oblasť pamäte char opísanú na konci dátovej časti. (V skutočnosti potrebujeme strojové slovo, to znamená oblasť pamäte na uloženie dvoch znakov, pretože sa do nej uloží aj kód klávesu Enter. Pri práci s montážnym jazykom je dôležité vždy pamätať na možnosť preplnenia oblastí pamäte. , pretože nie sú pripravené žiadne mechanizmy na vysokej úrovni, ktoré by vám pomohli!).

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

Hranaté zátvorky v tomto prípade znamenajú, že údaje sú uložené v záujmovej oblasti pamäte, a nie v samotnom registri. Register r2 teda teraz obsahuje jeden znak z znaku oblasti pamäte z dátovej sekcie a je to presne ten znak, ktorý zadal užívateľ. Našou ďalšou úlohou bude porovnať obsah registra r2 so znakom „q“, čo je 113. znak ASCII (pozri tabuľku znakov umiestnenú na www.asciichart.com). Teraz pomocou operácie cmp vykonáme operáciu porovnania a potom pomocou inštrukcie beq, ktorej názov znamená „vetva, ak je rovnaká“, preskočíme na hotový štítok, ak je hodnota v registri r2 113. Ak toto nie je prípade vytlačíme náš druhý riadok a potom pomocou inštrukcie b preskočíme na začiatok slučky.

Nakoniec po vykonanom štítku povieme jadru OS, že chceme ukončiť vykonávanie programu, rovnako ako v prvom programe. Ak chcete spustiť tento program, musíte ho zostaviť a prepojiť podľa pokynov pre prvý program.

Zvážili sme teda pomerne veľké množstvo informácií v najstručnejšej podobe, ale bude lepšie, ak sa na štúdium materiálu zameráte sami a experimentujete s vyššie uvedeným kódom. Nie je lepší spôsob, ako sa oboznámiť s programovacím jazykom, ako experimentovať s úpravou kódu niekoho iného a pozorovaním dosiahnutého účinku. Teraz môžete vyvíjať jednoduché montážne programy ARM, ktoré čítajú vstupné a výstupné údaje používateľov pomocou slučiek, porovnaní a podprogramov. Ak ste do dnešného dňa nezažili montážny jazyk, dúfam, že tento článok pre vás urobil tento jazyk trochu zrozumiteľnejším a pomohol rozptýliť populárny stereotyp, že ide o mystické remeslo dostupné iba pre pár talentovaných vývojárov.

Informácie uvedené v tomto článku týkajúce sa použitia jazyka zhromaždenia pre architektúru ARM sú samozrejme iba špičkou ľadovca. Používanie tohto programovacieho jazyka je vždy spojené s obrovským počtom nuáns. Ak chcete, aby sme o nich napísali v jednom z nasledujúcich článkov, dajte nám o tom vedieť! Medzitým vám odporúčame navštíviť vynikajúci zdroj s mnohými materiálmi pre výukové techniky na tvorbu programov pre systémy Linux bežiace na počítačoch s centrálnymi procesormi architektúry ARM, ktorý sa nachádza na adrese http://tinyurl.com/nsgzq89. . Šťastné programovanie!

Predchádzajúce články zo série „School of Assembler“: