Sei sulla pagina 1di 13

Programmazione C/Assembler

per i processori ARM


Materiale didattico a cura di:
Prof. A. Mazzeo
Ing. L. Romano
Ing. L. Coppolino
Ing. A. Cilardo
Dipartimento di Informatica e Sistemistica
Universit Degli Studi di Napoli Federico II

Le routines che effettuato chiamate ad altri moduli devono


rispettare convenzioni comuni per i parametri passati e ricevuti
Per il processore ARM queste convenzioni sono espresse
dallARM Procedure Call Standard (APCS)

APCS un insieme di regole per la chiamata di


funzioni in segmenti di codici compilati o assemblati
separatamente
Definisce:

I vincoli nelluso dei registri


Convenzioni per lo stack
Il formato di uno stack backtrace data structure
Il supporto ai meccanismi di condivisione delle librerie di ARM

Esistono diverse versioni di APCS, non compatibili tra


loro, fra cui il programmatore deve scegliere

La routine assembly invocata nel programma C deve


essere esportata
AREA |mul64$$code|, CODE, READONLY
EXPORT mul64
mul64
MOV
MOV

ip, a1, LSR #16 ;ip = a_hi


a4, a2, LSR #16 ;a4 = b_hi

Il programma C deve dichiarare che utilizzer una


funzione implementata esternamente
extern int64 mul64(unsigned a, unsigned b);

Tutti i registri che non sono utilizzati nel ruolo per essi definito dallAPCS
possono essere usati come registri generali

"#

Realizzare la somma tra due interi a 64 bit


Un programma C che realizzi una tale operazione non ha accesso al flag di
carry dello Status Register
Il programma C a tale scopo realizzato
(ARM_home\examples\candasm\ADD64_1.C) potrebbe essere:
void add_64(int64 *dest, int64 *src1, int64 *src2)
{
unsigned hibit1=src1->lo >> 31, hibit2=src2->lo >> 31, hibit3;
dest->lo=src1->lo + src2->lo;
hibit3=dest->lo >> 31;
dest->hi=src1->hi + src2->hi +
((hibit1 & hibit2) || (hibit1!= hibit3));
return;
}

$#

Compilare il file add64_1.c


armcc apcs 3/32bit S add64_1.c
Il flag apcs 3/32bit forza lutilizzo dellAPCS 3
nella versione a 32 bit
Visualizzare il file add64_1.s contenente il codice
assembly generato

!
AREA |C$$code|, CODE, READONLY
|x$codeseg| DATA
add_64
STMDB sp!,{v1,lr}
LDR
v1,[a2,#0]
MOV
a4,v1,LSR #31
LDR
ip,[a3,#0]
MOV
lr,ip,LSR #31
ADD
ip,v1,ip
STR
ip,[a1,#0]
MOV
ip,ip,LSR #31
LDR
a2,[a2,#4]
LDR
a3,[a3,#4]
ADD
a2,a2,a3
TST
a4,lr
TEQEQ a4,ip
MOVNE a3,#1
MOVEQ a3,#0
ADD
a2,a2,a3
STR
a2,[a1,#4]!
LDMIA sp!,{v1,pc}
AREA |C$$data|,DATA
|x$dataseg|
EXPORT add_64
END

%#

immediato verificare
che il codice ottenuto
non risulta essere
efficiente

&#

Partiamo da codice C per la somma a 64 bit


senza considerare il flag di Carry (add64_2.c)
#include "int64.h"
void add_64(int64 *dest, int64 *src1, int64 *src2)
{ dest->lo=src1->lo + src2->lo;
dest->hi=src1->hi + src2->hi;
return;}

Ottenere il codice assembly


armcc li apcs 3/32bit S add64_2.c

!
Analizzando il codice si osserva che
la prima ADD produce la low order
word mentre la seconda la high
order word. Il codice desiderato
pu essere ottenuto semplicemente
sostituendo la prima ADD con
ADDS (ADD e SET flag) e la
seconda con ADC (ADD with
Carry)

##

add_64
LDR
LDR
ADD
ADDS
STR
LDR
LDR
ADD
ADDS
STR
MOV

a4,[a2,#0]
ip,[a3,#0]
a4,a4,ip
a4,a4,ip
a4,[a1,#0]
a2,[a2,#4]
a3,[a3,#4]
a2,a2,a3
a2,a2,a3
a2,[a1,#4]!
pc,lr

'((

'

a1 contiene un puntatore alla struttura di output della funzione


a2 ed a3 contengono i puntatori alle strutture passate in input
alla funzione
a4 ed ip sono utilizzati in luogo dei registri v per non salvare il
contenuto di tali registri sullo stack
Nessun registro stato salvato sullo stack
per il ritorno dalla
funzione basta
MOV pc,lr

Per ritornare un valore, ad esempio il valore del flag di carry,


usare il registro a1
MOV a1, #0
ADC a1, a1, #0
Il codice disponibile nel file (ARM_home\examples\candasm\add64_3.s)

)
add_64
STMDB sp!,{v1,lr}
LDR
v1,[a2,#0]
MOV
a4,v1,LSR #31
LDR
ip,[a3,#0]
MOV
lr,ip,LSR #31
ADD
ip,v1,ip
STR
ip,[a1,#0]
MOV
ip,ip,LSR #31
LDR
a2,[a2,#4]
LDR
a3,[a3,#4]
ADD
a2,a2,a3
TST
a4,lr
TEQEQ a4,ip
MOVNE a3,#1
MOVEQ a3,#0
ADD
a2,a2,a3
STR
a2,[a1,#4]!
LDMIA sp!,{v1,pc}

*
Tornando alla prima
versione dellassembly
possiamo osservare che
essendo utilizzati
localmente i registri v1 ed
lr sono preservati sullo
stack
Il ritorno dalla funzione
ottenibile mediante
LDMIA sp!,{v1,pc}

'

"$

I registri sb, sl, fp, ip, sp sono utilizzati con funzioni


dedicate nellAPCS; laddove non dovessero essere
utilizzati per i ruoli definiti dallAPCS i registri possono
essere utilizzati come registri generali
Le funzioni definite dallAPCS per i registri dedicati:
ip

lr

+
sp
sl

fp
sb

Utilizzato esclusivamente durante le chiamate di funzione.


convenzionalmente utilizzato come un registro locare. In
altri casi pu essere utilizzato come un registro temporaneo
corruttibile
Contiene lindirizzo di ritorno alluscita dalla funzione. Pu
essere utilizzato come registro temporaneo preservandone il
valore sullo stack. Questultimo valore pu essere
direttamente ricaricato nel PC

'

$$

stack pointer
stack limit , utilizzato se il controllo dei limiti
dello stack esplicito (cio realizzato dal codice
in occorrenza di un push sullo stack). Se il
controllo implicito (effettuato dallhardware) il
registro pu essere utilizzato come v7.
frame pointer. Contiene o zero o un puntatore
allultimo frame generato sullo stack.
static base. Nel caso di codice rientrante consente
laccesso ad un array di puntatori a dati statici.
Nel caso di codice non rientrante pu essere
usato come v6.

"%
Generalmente le strutture sono passate
attraverso registri o eventualmente (se le
dimensioni o il numero lo esigessero)
attraverso lo stack.
Il registro a1 punta allarea di memoria utilizzata
per la memorizzazione dei risultati (per funzioni
che ritornano strutture)
come se struct s f(int x) fosse compilata come
void f(struct s *result, int x)

$%
Si consideri il seguente codice
(ARM_home\candasm\two_ch.c)
typedef struct two_ch_struct
{ char ch1;
char ch2;
} two_ch;
two_ch max( two_ch a, two_ch b )
{ return (a.ch1>b.ch1) ? a : b; }

Il codice assembly corrispondente pu essere ottenuto mediante


armcc S two_ch.c apcs 3/32bit

%%
Il codice assembly
corrispondente evidenzia che:

max

Gli argomenti, il frame pointer, lo


stack pointer, il link register e il
current pc, sono salvati sullo stack
(nellordine inverso rispetto a quello in
cui sono stati elencati essendo lo stack
discendente)
a2 ed a3 sono usati come variabili
temporanee e memorizzano la parte di
interesse delle strutture passate
a1 un puntatore allarea di memoria
in cui porre i risultati

MOV
ip,sp
STMDB sp!,{a1-a3,fp,ip,lr,pc}
SUB
fp,ip,#4
LDRB a3,[fp,#-&14]
LDRB a2,[fp,#-&10]
CMP a3,a2
SUBLE a2,fp,#&10
SUBGT a2,fp,#&14
LDR
a2,[a2,#0]
STR a2,[a1,#0]
LDMDB fp,{fp,sp,pc}

- * "%

Una integer-like structure:


non pi grande di una parola
Ha campi per i quali il byte offset nullo

Ad esempio sono integer-like structure


struct

union polymorphic_ptr

{
unsigned a:8, b:8, c:8, d:8;

struct A *a;
struct B *b;
int *i;

}
}

Non un integer-like structure quella dellesempio precedente

Una integer like structure ha il suo valore di ritorno in a1

- * $%

Si consideri il codice seguente (half_str.c)


typedef struct half_words_struct
{ unsigned field1:16;
unsigned field2:16;
} half_words;
half_words max( half_words a, half_words b )
{ half_words x;
x= (a.field1>b.field1) ? a : b;
return x;}

Mediante lopzione S si ottenga il codice assembly


corrispondente
armcc S half_str.c apcs 3/32bit

- * %%

Il codice ottenuto evidenzia che il valore della


struttura ritornato direttamente in a1
max
MOV a3,a1,LSL #16
MOV a3,a3,LSR #16
MOV a4,a2,LSL #16
MOV a4,a4,LSR #16
CMP a3,a4
MOVLE a1,a2
MOV pc,lr

10

- *

"#

Utilizzando il registri come puntatori per lo


scambio di strutture si ha un overhead elevato.
Luso della keyword __value_in_regs forza il
passaggio delle strutture di dimensioni inferiori
alle quattro parole attraverso i registri
argomento a1-a4

- *

$#

Problema: realizzare una routine ottimizzata


per il prodotto di interi a 64 bit.
Il codice C non sarebbe ottimizzato (C flag)
Luso della memoria da evitare.
Si consideri il codice contenuto nei file mul64.s,
mul64.h, int64.h e multest.c

11

- *

Il file mul64.s evidenzia


che la struttura ritornata
attraverso i registri a1 ed
a2

%#
Mul64 MOV ip, a1, LSR #16
MOV a4, a2, LSR #16
BIC a1, a1, ip, LSL #16
BIC a2, a2, a4, LSL #16
MUL a3, a1, a2
MUL a2, ip, a2
MUL a1, a4, a1
MUL a4, ip, a4
ADDS ip, a2, a1
ADDCS a4, a4, #&10000
ADDS a1, a3, ip, LSL #16
ADC a2, a4, ip, LSR #16
MOV pc, lr

- *

&#

Affinch ci avvenga nel file int64.h la funzione


mul64 dichiarata come segue
__value_in_regs extern int64 mul64(unsigned a, unsigned b);

Per compilare, assemblare e lincare i file si proceda


come segue
armasm mul64.s o mul64.o
armcc c multest.c apcs 3/32bit
Armlink mul64.o multest.o o multest

12

- *

##

Per lesecuzione
> armsd -li multest
A.R.M. Source-level Debugger, version 4.10 (A.R.M.) [Aug 26 1992]
ARMulator V1.20, 512 Kb RAM, MMU present, Demon 1.01, FPE, Little
endian.
Object program file multest
armsd: go
Enter two unsigned 32-bit numbers in hex eg.(100 FF43D)
12345678 10000001
Least significant word of result is 92345678
Most significant word of result is 1234567
Program terminated normally at PC = 0x00008418
0x00008418: 0xef000011 .... : > swi 0x11
armsd: quit
Quitting
>

13