RPI assembler tutorial #3

Zoals we zagen in deel 1 en 2 kunnen we data naar de registers verplaatsen (mov) en 2 registers bij elkaar optellen (add). Gelukkig kan een cpu ook met geheugen werken, anders zouden er veel limieten zijn.

Geheugen

Een computer heeft geheugen (hiermee bedoel ik ram) waar de code (.text in assembler) en data worden opgeslagen om beschikbaar te zijn voor de cpu. i386 en x86-64 architecturen kunnen zowel registers en geheugen raadplegen (om zo bijvoorbeeld iets uit het register bij iets in het geheugen toe te voegen). Dit gaat niet in ARM. Hier moet alles gebeuren in de registers. Dit ‘probleem’ is echter op te lossen door eerst data naar een register op te slaan uit het geheugen en achteraf omgekeerd.

Hiervoor zijn er 2 speciale functies. ldr en str, oftewel load en store. er zijn nog andere manieren om dit te doen maar dit zijn de simpelste.

Geheugenadressen

Omdat alle data in het geheugen in feite 1 lange reeks van eenen en nullen is, zullen we ze een naam moeten geven omdat we anders niets kunnen raadplegen. Een computer geeft deze data adressen. Deze adressen zijn nummers en in ARM is dit een 32 bit nummer dat elke byte (8 bits) in het geheugen weergeeft.

Wanneer we data opslaan of lezen uit het geheugen moeten we het adres weten (of berekenen). Dit kan in veel manieren gedan worden. Elke van deze manieren word een ‘adressing mode’ genoemd. ARM heeft er een paar van, maar hier ga ik het doen door middel van een register.

Zoals uitgelegd in deel 2 heeft een register 32bits, wat gelijk is aan de 32 bits voor de adressen voor het geheugen. Dit betekend dat we een adres in een register kunnen opslaan om dan de respecterende data te laden of op te slaan.

Data

In deel 1 zagen we dat de assembler zowel code en data kunnen opslaan. code word duidelijk gemaakt met een label .text. Deze labels zijn eigenlijk symbolische namen naar plaatsen in je programma. Deze plaatsen kunnen zowel data als code zijn. Tot nu toe hebben we het label main gebruikt om de plaats van onze main functie aan te duiden. Een label is enkel de plek, nooit de inhoud.

Ik vertelde in deel 1 dat assembler in feite gewoon een trapje hoger is dan de binaire ode, wel dat ene stapje hoger is ineens wat complexer geworden, omdat het ook verantwoordelijk geworden is om waarde aan deze labels te geven. Zo kunnen we de assembler/compiler dingen doen doen die lijken op magie.

Zo kunnen we bijvoorbeeld de juiste grote aan een variabele geven. Laat ons een 4 byte variabele maken en hem initialiseren naar 3 met de naam myvar1.

.balign 4
myvar1:
    .word 3

er zijn 2 assembler directieven in het voorbeeld hierboven: .balign en .word. Wanner de assembler een .balign directieve tegenkomt, zorgt het ervoor dat het volgende adress zal starten met een 4-byte grote grens. Dit doet niets als het adres al 4 groot was, anders zal de assembler ‘padding bytes’ overslaan.

Nu we het adres van myvar1 hebben vastgelegd weten we dat het 4 byte alligned zal zijn.

Het .word directieve zegt de assembler de waarde als en 4 byte integer nodig is. In dit geval met de waarde 3, maar neemt 4 bytes in beslag.

Data word net zoals de code in het geheugen opgeslagen. Maar word meestal appart gehouden in een data sectie (.data) wat de assembler vertelt dat het om data gaat en niet om code.

Load

Laad het voorbeeld van deel 2 erbij en pas het aan zodat het gebruik maakt van het geheugen. We zullen 2 keer een 4 byte variabele: myvar1 en myvar2 definieren. waarvan de eerste gelijk is aan 3 en de laatste aan 4. We zullen deze waardes laden met ldr en ze dan aan elkaar toevoegen. De uitkomst zal dan weer als een error 7 moeten zijn.

/* -- load01.s */
 
/* -- Data section */
.data
 
/* Ensure variable is 4-byte aligned */
.balign 4
/* Define storage for myvar1 */
myvar1:
    /* Contents of myvar1 is just 4 bytes containing value '3' */
    .word 3
 
/* Ensure variable is 4-byte aligned */
.balign 4
/* Define storage for myvar2 */
myvar2:
    /* Contents of myvar2 is just 4 bytes containing value '4' */
    .word 4
 
/* -- Code section */
.text
 
/* Ensure code is 4 byte aligned */
.balign 4
.global main
main:
    ldr r1, addr_of_myvar1 /* r1 ← &myvar1 */
    ldr r1, [r1]           /* r1 ← *r1 */
    ldr r2, addr_of_myvar2 /* r2 ← &myvar2 */
    ldr r2, [r2]           /* r2 ← *r2 */
    add r0, r1, r2         /* r0 ← r1 + r2 */
    bx lr
 
/* Labels needed to access data */
addr_of_myvar1 : .word myvar1
addr_of_myvar2 : .word myvar2

De laatste 2 regels bevatten de locaties van myar1 en myvar2. Dit is nodig voor de assembler . Dit is omdat deze in de .data sectie staan en niet in de code sectie.

De assembler compiled het naar binaire code, .word myvar1 zal niet het adres van myvar1 zijn, maar een relocation. Een relocation is de manier waarop de assembler data adressen gebruikt. De exacte waarde is onbekend, maar zal bekend worden zodra het gelinkt word. (de stap waarop de uiteindelijke executable word gemaakt met GCC).

op regel 27 en 28 staat waar de eerste variabele word geladen. Dit is echter enkel het adres. hier word namelijk enkel de inhoud en niet het adres, vandaar dat op ln28 nog een ldr staat om de waarde eruit te halen.

Als je je afvraagt waarom de 2 loads een andere syntax hebben, dat komt omdat de eerste ldr gebruikt word om het symbolische adres van add_of_myvar1 de waarde leest in addressing mode. Dus in het 2e geval gaan we de waarde van r1 als adres gebruiken. In het eerste geval weten we niet welke adressing mode de assembler gebruikt, dus deze word genegeerd.

Als je dit compiled en uitvoert zal je 7 terugzien.

Store

Neem nu het vorige voorbeeld, maar in plaats van de waarden op 3 en 4 te zetten, zetten we ze nu op 0. We zullen dezelfde code gebruiken om de waarden 3 en 4 terug te geven in de assembler

/* -- store01.s */
 
/* -- Data section */
.data
 
/* Ensure variable is 4-byte aligned */
.balign 4
/* Define storage for myvar1 */
myvar1:
    /* Contents of myvar1 is just '3' */
    .word 0
 
/* Ensure variable is 4-byte aligned */
.balign 4
/* Define storage for myvar2 */
myvar2:
    /* Contents of myvar2 is just '3' */
    .word 0
 
/* -- Code section */
.text
 
/* Ensure function section starts 4 byte aligned */
.balign 4
.global main
main:
    ldr r1, addr_of_myvar1 /* r1 ← &myvar1 */
    mov r3, #3             /* r3 ← 3 */
    str r3, [r1]           /* *r1 ← r3 */
    ldr r2, addr_of_myvar2 /* r2 ← &myvar2 */
    mov r3, #4             /* r3 ← 4 */
    str r3, [r2]           /* *r2 ← r3 */
 
    /* Same instructions as above */
    ldr r1, addr_of_myvar1 /* r1 ← &myvar1 */
    ldr r1, [r1]           /* r1 ← *r1 */
    ldr r2, addr_of_myvar2 /* r2 ← &myvar2 */
    ldr r2, [r2]           /* r2 ← *r2 */
    add r0, r1, r2
    bx lr
 
/* Labels needed to access data */
addr_of_myvar1 : .word myvar1
addr_of_myvar2 : .word myvar

Als je dit uitvoert zul je 7 te zien krijgen.

Dat was alles voor deel 3. (andere delen)

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *