Programiranje mikroprocesorjev ARM z odprtokodnimi orodji

(povzetek prispevka študenta Marka Čeferina)

V nadaljevanju so zapisana kratka navodila, kako lahko prevajamo in izvajamo programe v zbirnem jeziku za mikroprocesorje ARM  s pomočjo odprtokodne programske opreme.

Navodila predvidevajo, da je sistem, na katerem delate, GNU/Linux, toda verjetno veljajo tudi za Mac OS. Za zagon orodij na operacijskem sistemu Windows boste verjetno morali uporabiti MinGW, kar pa je že izven obsega tega prispevka.

Priprava

Za pripravo orodij potrebujete GNU Compiler Collection. Če ga že imate, lahko preskočite to točko.

Če ne veste, ali je GCC že nameščen, poskusite zagnati gcc --version:

# gcc --version

gcc (GCC) 4.8.2 Copyright (C) 2013 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Če se vam na zaslonu izpiše nekaj takega, je GCC na voljo. Če pa dobite izpis “command not found”, pa morate GCC še namestiti.

Če uporabljate Debian GNU/Linux (ali pa njegovo izpeljanko, kot je npr. Ubuntu), lahko uporabite sudo apt-get install build-essential (če uporabljate kak grafični umesnik za upravljalnika programske opreme, morate namestiti paket “build-essential”). Na Mac OS X je GCC na voljo skupaj z Xcode programom.

binutils

Za prevajanje iz zbirnega (assembler) jezika ARM v strojno kodo ARM bo potrebno pripraviti GNU binutils za ARM.

Z http://ftp.gnu.org/gnu/binutils/ si prenesite najnovejšo različico tega paketa.

cd /tmp
mkdir arm-dev
cd arm-dev
wget http://ftp.gnu.org/gnu/binutils/binutils-2.23.2.tar.bz2

Preneseni arhiv razširite v neko začasno mapo.

tar xf binutils-2.23.2.tar.bz2
cd binutils-2.23.2

Prevedite binutils za ARM platformo in jih namestite v sistem:

./configure --enable-interwork --enable-multilib --target=arm-none-eabi
make -j4
make install

Nameščeno bo več različnih programov, predvsem pa as (asembler) in ld (linker).

Ti ukazi imajo predpono arm-none-eabi-, saj bi lahko imeli več as in ld programov za različne platforme naenkrat. Če poskusite prevesti ARM kodo z ukazom as, bo program javil napako, saj obstaja ukaz brez predpone za enako arhitekturo, kot je računalnik, na katerem ga izvedemo. Tako moramo as pognati z arm-none-eabi-as in ld  z arm-none-eabi-ld.

GDB

Za simulacijo ARM platforme ter izvajanje programov po korakih uporabimo GDB.

Najprej prenesemo GDB z http://ftp.gnu.org/gnu/gdb/ in ga razširimo v neko mapo.

cd /tmp/arm-dev
wget http://ftp.gnu.org/gnu/gdb/gdb-7.6.1.tar.bz2
tar xf gdb-7.6.1.tar.bz2

Nato ga prevedemo in namestimo.

./configure --enable-interwork --enable-multilib --target=arm-none-eabi
make -j4
make install

Po tem ukazu je nameščen razhroščevalnik GNU Debugger, ki ga poženemo z arm-none-eabi-gdb.

Prevajanje v zbirnem jeziku ARM

Za prevajanje zbirnega jezika ARM uporabimo prevajalnik as, GNU assembler. Program tvori objektno datoteko, ki vsebuje strojne ukaze za procesor ARM. To datoteko moramo pretvoriti še v izvršljivo obliko. Uporabimo povezovalnik ld, GNU linker. Povezovalnik tvori datoteko ELF (oziroma executable linked file), ki jo GDB lahko izvede.

Program v zbirnem jeziku shranite v datoteko s končnico .s (končnica ni pomembna, toda .s je standardna za GNU orodja). Pazite, da ima program nekje oznako _start (en podčrtaj, ne dva), saj se bo tam program začel izvajati.

Če hočete na primer prevesti program koda.s, izvedite naslednja ukaza:

arm-none-eabi-as koda.s -o koda.o -g
arm-none-eabi-ld koda.o -o koda -g

Prvi ukaz prevede koda.s v koda.o - objektno datoteko s strojnimi ukazi. Drugi poveže to datoteko v datoteko koda (ta ponavadi nima predpone), ki je izvršljiva. Opcija -g omogoči *GDB*ju dostop do podatkov, potrebnih za vpogled v program.

GDB ukazi

GDB se izvaja iz ukazne vrstice. Lahko ga uporabljamo kot klasični ukazni program, lahko pa tudi vključimo nekaj oken, ki nam kažejo trenutno stanje programa (sam GDB pa še vedno upravljamo z ukazno vrstico).

V glavnem bomo uporabljali te ukaze:

  • help {ukaz} – informacije o ukazu
  • target sim – vključi simulator
  • load – naloži program v simulator
  • break {vrstica ali oznaka} – naredi prekinitveno točko (kje naj zaustavi izvajanje ter nam omogoči nadaljnjo izvedbo korak po korak) na vrstici (če številka) ali oznaki
  • info breakpoints – izpiši vse prekinitvene točke
  • delete breakpoints {številka} – odstrani prekinitveno točko v vrstici številka ...
  • delete breakpoints – odstrani vse prekinitvene točke
  • run – zažene program
  • layout src – odpre okno, kjer je na voljo izvorna koda s kazalcem na vrstico, ki se trenutno izvaja
  • layout asm – odpre okno, kjer so na voljo strojni ukazi, s kazalcem na ukaz, ki se trenutno izvaja
  • layout split – odpre okna z izvorno kodo in strojnimi ukazi naenkrat
  • layout regs – odpre okno s trenutnim stanjem registrov nad oknom, ki je bilo prej odprto
  • quit – zapre GDB

Ko se program začne izvajat in se zaustavi na prekinitveni točki, lahko uporabljamo naslednje ukaze:

  • step – pojdi na naslednji ukaz
  • continue – nadaljuj izvajanje do naslednje prekinitvene točke
  • info registers – izpiši vse registre
  • print {vrednost} – izpiši vrednost; to je lahko izraz (npr. 2+2) ali pa register ($r1, $r2, itd.) (lahko pa tudi kombinacija obojega)
  • x {naslov} – izpiši vrednosti pomnilnika na naslovu; naslov je lahko število (npr. 0x8000) ali pa register (npr. $pc ali $r0)

Ukaze lahko tudi skrajšamo, dokler GDB lahko še ugane, kaj smo mislili:

  • help -> h
  • break -> b
  • info breakpoints -> i b
  • info registers -> i r
  • delete breakpoints -> d br
  • run -> r
  • step -> s
  • continue -> c
  • print -> p
  • quit -> q

Običajno pogosteje uporabljamo kratke različice bolj pogostih ukazov, kot so s, b, p in c.

Uporaba GDB

GNU Debugger je zmogljiv razhroščevalnik, ki podpira simulacijo različnih procesorskih arhitektur. Uporabili ga bomo za pregled programa korak po koraku.

Za sedaj bomo zagnali ta program:

.text
NUM1:   .word 0xabcdef00
NUM2:   .word 0x12345678
NUM3:   .space 4

.align
.global _start
_start:

adr r0, NUM1
ldr r1, [r0]
add r1, r1, #1
str r1, [r0]

adr r0, NUM2
ldr r1, [r0]
adr r0, NUM3
str r1, [r0]

_end:   b _end

Najprej moramo program prevesti (shranimo ga v test.s):

arm-none-eabi-as test.s -o test.o -g
arm-none-eabi-ld test.o -o test -g

Nato zaženemo GDB in mu povemo, da bomo delali z izvršljivo datoteko test.

arm-none-eabi-gdb test

Za tem GDB izpiše svoj standardni pozdrav ter odpre svojo ukazno vrstico. V naslednjih primerih so vrstice, ki se začnejo z (gdb), naši ukazi, ostale vrstice pa so izpis od GDB.

Najprej moramo vključiti simulator in naložiti kodo v njega:

(gdb) target sim
Connected to the simulator.
(gdb) load
Loading section .text, size 0x30 vma 0x8000
Start address 0x800c
Transfer rate: 384 bits in <1 sec

Dobro bi bilo, če kar takoj naredimo kako prekinitveno točko. _start je dobra izbira, če hočemo cel program izvajati korak po koraku:

(gdb) b _start
Breakpoint 1 at 0x8010: file test.s, line 11.

Sedaj si lahko odpremo nekaj oken za lažje spremljanje izvajanja programa.

(gdb) layout asm
(gdb) layout regs

Ta ukaza odpreta pregled strojne kode (ki ni ista kot izvirna; na primer, namesto adr se uporabi sub) in registrov (ti bodo vidni šele, ko izvedemo ukaz run). Po strojni kodi se lahko premikamo s tipkama gor in dol.

Zaženemo program.

(gdb) run
Starting program: /home/user/fri/ra/test 

Breakpoint 1, _start () at test.s:11

Registri postanejo vidni in trenutna vrstica v strojni kodi se osvetli. Na tej točki že vidimo, da se je prvi adr ukaz že izvršil, saj ima r0 vrednost 0x8000.

Lahko pogledamo vrednosti spremenljivk v pomnilniku:

(gdb) x NUM1
0x8000 <NUM1>:  0xabcdef00
(gdb) x NUM2
0x8004 <NUM2>:  0x12345678
(gdb) x NUM3
0x8008 <NUM3>:  0x00000000

NUM1 in NUM2 imata pričakovani vrednosti, NUM3 pa ima vrednost 0. Na vrednost spremenljivke NUM3 se ne moremo zanesti (zaradi deklaracije .space).

Uporabimo step toliko krat, dokler se ne izvede tudi ukaz str.

(gdb) s
(gdb) s
(gdb) s

Ko je drugi adr/sub ukaz osvetljen, se je str že izvedel. Če uporabljate GDB z odprtimi okni za spremljanje programa (kot imamo v tem primeru), lahko samo pritisnete tipko return (enter), da se izvede zadnji ukaz (step, v tem primeru) še enkrat.

Če sedaj pogledamo vrednost NUM1, vidimo, da se je res spremenila:

(gdb) x NUM1
0x8000 <NUM1>:  0xabcdef01

Pojdimo še skozi ostanek programa. Če nas ne zanima, kaj natanko se dogaja, lahko uporabimo step N, kjer je N število korakov, ki jih hočemo narediti.

(gdb) s 4
_end () at test.s:20
(gdb) x NUM3
0x8008 <NUM3>:  0x12345678

Program je prišel do konca. Če gremo še naprej, se zažene neskončna zanka; da jo prekinemo, pritisnemo ^C (tipki Control in C naenkrat). Lahko uporabimo quit, da zapremo GDB, ali pa spet run, da začnemo izvajanje od začetka.

마지막 수정됨: 월요일, 25 11월 2013, 5:29 PM