Vincent Lammens

Terug

Rpi Assembler Tutorial 6

2018/12/08
tutorial
tutorial, arm, arm-assembler

In het vorige deel heb ik de branch instructies vergeleken met control-flow tools, dit was niet helemaal juist. Er zijn in ARM assembler ingebouwde if-else structuren te vinden.

IF, THEN, ELSE

Dit is een gemakkelijke. We hebben deze structuur al gebruikt in het vorige deel. Beeld je de volgende structuur in, waar E een expressie is, en S1 en S2 statements zijn.

if (E) then
   S1
else
   S2

Een manier om dit in assembler voor te stellen is:

if_eval: 
    /* Assembler that evaluates E and updates the cpsr accordingly */
bXX else /* Here XX is the appropiate condition */
then_part: 
   /* assembler for S1, the "then" part */
   b end_of_if
else:
   /* assembler for S2, the "else" part */
end_of_if:

Als er geen else deel is, kunnen we de bXX else met Bxx end_of_if vervangen.

Loops

Dit is ook een belangrijke. Een loop kan je voorstellen als volgt:

while (E)
  S

Zo doet S iets waardoor E uiteindelijk een waarde = false krijgt, waardoor de loop stopt. Anders zou hij altijd in de loop blijven. Dit is soms handig maar niet voor onze voorbeelden. Zo kan je deze implementeren:

while_condition : /* assembler to evaluate E and update cpsr */
bXX end_of_loop /* If E is false, then leave the loop right now */
/* assembler of S */
b while_condition /* Unconditional branch to the beginning */
end_of_loop:

Een andere veelvoorkomende loop is de FOR loop, deze laat een integer altijd stijgen (of dalen) zoals:

for (i = L; i < N; i += K)
  S

Deze is niets meer dan:

i = L;
  while (i < N)
  {
     S;
     i += K;
  }

Hierdoor kunnen we door een while-loop te gebruiken dezelfde functie krijgen als een for-loop

Een voorbeeld

Laat ons voor dit voorbeeld alle nummers van 1-22 optellen (1+2+3+… …+22)

/* -- loop01.s */
.text
.global main
main:
    mov r1, #0       /* r1 ← 0 */
    mov r2, #1       /* r2 ← 1 */
loop: 
    cmp r2, #22      /* compare r2 and 22 */
    bgt end          /* branch if r2 > 22 to end */
    add r1, r1, r2   /* r1 ← r1 + r2 */
    add r2, r2, #1   /* r2 ← r2 + 1 */
    b loop
end:
    mov r0, r1       /* r0 ← r1 */
    bx lr

Hier tellen we van 1 naar 22, we zullen r2 als counter gebruiken. Op lijn 6 zetten we r2 naar 1. De som zal opgeslagen worden in r1. Op het eind verplaatsten we de inhoud van r1 naar r0, waardoor we het resultaat van de som als een error code kunnen zien. Deze laatste stap kan je echter overslaan door direct r0 te gebruiken.

Op lijn 8 vergelijken we r2 (de counter) met 22 Dit update de cpsr, dus op lijn 9 kunnen we het resultaat gaan controleren. Als dat het geval is kunnen we branchen naar het eind (de end: tag). Anders voegen we de waarde van r2 toe aan r1.

Lijn 11 is een belangerijke. Hier  voegen we 1 toe aan de waarde van r2. Dit is omdat we van 1 naar 22 aan het tellen zijn we hebben de waarde van r2 al bij de som van r1 geteld. Op regel 12 branchen we weer naar het begin van de loop. Als regel 11 er niet was zou de loop nooit eindigen.

Als we dit uitvoeren zien we het resultaat.

$ ./loop01; echo $?
253

Nu, waarom heb ik 22 gekozen, en geen getal zoals bijvoorbeeld 100. Als we het zouden aanpassen naar 100 zouden we 5050 moeten krijgen, maar:

$ ./loop01; echo $?
186

Hier loopt iets mis. Dit gebeurt omdat de error-code van een programma in linux een getal is van 0 tot 255 (8 bits, 1 byte). Als het resultaat 5050 is, worden enkel de laagste 8 bits van het getal gebruikt. zo word 5050 in binair: 1001110111010, De laagste 8 bits zijn 10111010, wat 186 is. Laat ons GDB gebruiken om het te debuggen.

$ gdb loop
...
(gdb) start
Temporary breakpoint 1 at 0x8390
Starting program: /home/roger/asm/chapter06/loop01 
 
Temporary breakpoint 1, 0x00008390 in main ()
(gdb) disas main,+(9*4)
Dump of assembler code from 0x8390 to 0x83b4:
   0x00008390 <main+0>:	mov	r1, #0
   0x00008394 <main+4>:	mov	r2, #1
   0x00008398 <loop+0>:	cmp	r2, #100	; 0x64
   0x0000839c <loop+4>:	bgt	0x83ac <end>
   0x000083a0 <loop+8>:	add	r1, r1, r2
   0x000083a4 <loop+12>:	add	r2, r2, #1
   0x000083a8 <loop+16>:	b	0x8398 <loop>
   0x000083ac <end+0>:	mov	r0, r1
   0x000083b0 <end+4>:	bx	lr
End of assembler dump.

Als we een breakpoint instellen op 0x00083ac, net voordat we r1 naar r0 verplaatsen:

(gdb) break *0x000083ac
(gdb) cont
Continuing.
 
Breakpoint 2, 0x000083ac in end ()
(gdb) disas
Dump of assembler code for function end:
=> 0x000083ac <+0>:	mov	r0, r1
   0x000083b0 <+4>:	bx	lr
End of assembler dump.
(gdb) info register r1
r1             0x13ba	5050

Hier zien we wat we verwacht hadden, maar we konden het niet zien door de limieten van error codes.

Er is ook iets aan de gang met onze labels die functies worden. Dit zal ik verder uitleggen in een verder deel.

Dit was alles voor deel 6.