Gli appunti di questa repository hanno come unico scopo quello di essere un facile e veloce riassunto sui concetti principali della programmazione in assembly su architettura ARM. Lo studio diretto attraverso tali appunti è fortemente sconsigliato.
- Ci sono 31 registri general purpose
X<n>
registri a 64bit.W<n>
registri a 32bit (i meno significativi diX<n>
).S<n>
registri floating point a 32bit.D<n>
registri floating point a 64bit.
XZR
/WZR
contiene sempre il valore0
e ignora le write.X28
oSP
è usato come stack pointer.X29
oFP
è usato come frame pointer.X30
è usato per memorizzare l’indirizzo di ritorno di una funzione, chiamato link registerLR
.
Nota: Non si può utilizzare xzr come secondo operando. Al suo posto si utilizza MOV
Nelle istruzioni, la scelta dei registri
X
,W
,V
,S
oD
determina la dimensione dell'operazione.
.cpu
-> specifica il tipo di CPU..text
-> indica la parte di codice da eseguire..data
-> specifica le variabili salvate nell'Heap..p2align 2
-> indica che gli indirizzi di memoria devono essere multipli di 2^2. Va specificato dopo.text
e.data
. Per maggiori informazioni riguardante questa direttiva consultare il seguente link.
Istruzione | Formato | Note |
---|---|---|
Load | ldr <Rd>, [<Rn>] |
<Rd> < MEM[<Rn>] |
Store | str <Rd>, [<Rn>] |
<Rd> > MEM[<Rn>] |
Address | adr <Rd>, label |
Carica l'indirizzo di memoria in un registro (usato spesso con le variabili) |
Swap | swp Rd, Rm, [Rn] |
Swap Rm with location [Rn], [Rn] value placed in Rd |
Note sull'istruzione
adr
: la label deve trovarsi entro 1MB del PC perchè l'indirizzo è specificato come offset dal PC. Con l'istruzioneADPR
(stesso formato diadr
) l'indice della pagina deve trovarsi entro 4GB dal PC.
Istruzione | Formato | Note |
---|---|---|
Add | add <Rd>, <Rn>, <Operand2> |
Rd = Rn + Operand2 |
Sub | sub <Rd>, <Rn>, <Operand2> |
Rd = Rn - Operand2 |
Multiply | mul <Rd>, <Rn>, <Rs> |
Rd = Rn * Rs |
And | and <Rd>, <Rn>, <Operand2> |
Rd = Rn && Operand2 |
Or | or <Rd>, <Rn>, <Operand2> |
Rd = Rn || Operand2 |
Move | mov <Rd>, <Rn> |
Copia il valore del secondo registro nel primo |
Compare | cmp <Rd>, <Rn> |
Compara due registri (usata per i salti condizionati) |
Note: se l'operazione logico/aritmetica è seguita dal prefisso "s" (
ands
,ors') viene effettuato anche il compare dei registri (
cmp`).
Note sull'istruzione
cmp
: tale istruzione viene utilizzata quando abbiamo bisogno di comparare due registri per eventuali salti condizionati. Il risultato viene salvato automaticamente nel registroAPSR
.
Note sull'istruzione
mov
: per usare immediati più grandi di 16bit si utilizza l'istruzioneMOVZ
(carica i bit e mette gli altri a zero) o 'MOVK' (carica i bit lasciando inalterati gli altri) shiftando le cifre a gruppi di 16bit le cifre.
Shiftando un numero è possibile moltiplicare (shift a sinistra) o dividere (shift a destra) per potenze di 2. Queste istruzioni vengono usate nel secondo operando (detto anche operando flessibile).
Istruzione | Formato | Note |
---|---|---|
LSL | add <Rd>, <Rn>, <Operand2>, LSL #n |
Operand2 is the contents of <Operand2> multiplied by #n |
LSR | add <Rd>, <Rn>, <Operand2> |
Operand2 is the contents of <Operand2> divided by #n |
ARS | add <Rd>, <Rn>, <Operand2>, ASR #n |
Operand2 is the contents of <Operand2> divided by #n |
L'istruzione lsl
(Logical Left Shift) e lsr
(Logical Right Shift) vengono utilizzate per inserire degli zeri, rispettivamente a sinistra e a destra.
L'istruzione ars
(Arithmetic Right Shift) inserisce 0 o 1 tenendo conto del segno.
Istruzione | Formato | Note |
---|---|---|
BL | bl label |
The BL instruction copies the address of the next instruction into r14 (lr, the link register), and causes a branch to label. |
BR | br Rm |
Salta all’indirizzo contenuto nel registro Rm |
BLR | blr Rm |
Salta all’indirizzo contenuto nel registro Rm e salva l’indirizzo dell’istruzione successiva nel registro x30 . |
Istruzione | Formato | Note |
---|---|---|
CBZ | cbz Rm, label |
Salta all’indirizzo label se Rm è uguale a zero |
CBNZ | cbnz Rm, label |
Salta all’indirizzo label se Rm è diverso da zero |
Formato istruzione: b.<cond> label
Salta all'indirizzo label sulla base di una conditional mnemonics, descritti attraverso la tabella che segue:
<cond> |
Meaning | Flags |
---|---|---|
eq |
Equal | Z == 1 |
ne |
Not equal | Z == 0 |
hs |
Higher or same (unsigned >=) | C == 1 |
lo |
Lower (unsigned < ) | C == 0 |
mi |
Negative | N == 1 |
pl |
Positive or zero | N == 0 |
vs |
Overflow | V == 1 |
vc |
No overflow | V == 0 |
hi |
Higher (unsigned <=) | C == 1 && Z == 0 |
ls |
Lower or same (unsigned <=) | !(C == 1 && Z == 0) |
ge |
Signed >= | N == V |
lt |
Signed < | N != V |
gt |
Signed > | Z == 0 && N == V |
le |
Signed <= | ` |
al /nv |
Always | Always |
Nota: La colonna "Flags" indica i condition codes contenuti nel registro APSR (Application Program Status Register). N (se negativo), Z (se è zero), V (se c'è stato overflow) e C (se c'è stato un carry).
Conditional select, returning the first or second input.
Formato istruzione: csel <Rd>, <Rn>, <Rm>, <cond>
Traduzione:
if (<cond>) {
<Rd> = <Rn>;
} else {
<Rd> = <Rm>;
}
Conditional select increment, returning the first input or incremented second input.
Formato istruzione: csinc <Rd>, <Rn>, <Rm>, <cond>
Traduzione:
if (<cond>) {
<Rd> = <Rn>;
} else {
<Rd> = <Rm> + 1;
}
Formato istruzione: svc #n
Attraverso questa operazione il controllo passa al Kernel che esegue una funzione particolare (syscall) in base al valore contenuto nel registro x8
. Nella tabella sottostante sono ripotate alcune delle systemcall disponibili, la colonna "Numero" indica il valore che dovrà essere inserito nel registro x8
.
Numero | syscall name | x0 |
x1 |
x2 |
x3 |
x4 |
x5 |
---|---|---|---|---|---|---|---|
63 | read | unsigned int fd |
char* buf |
size_t count |
|||
64 | write | unsigned int fd |
const char *buf |
size_t count |
|||
93 | exit | int error_code |
Una lista completa di tutte le System Calls è disponibile a questo link
Le variabili in Assembly vengono definite nella sezione .data
specificando il nome e il tipo.
I tipi delle variabili possono essere i seguenti:
.float
-> inserisce un numero float32.ascii
-> inserisce una stringa (non terminata da 0).asciiz
-> inserisce una stringa terminata da zero.byte
-> inserisce un byte.word
-> inserisce un numero int32 (bisognerà usare i registriw<n>
).dword
-> inserisce un numero int64 (bisognerà usare i registrix<n>
).space
-> riserva k bytes non inizializzati
Esempio:
.data
.p2align 2
a: .word -5, 10, 17, 100 //Array
b: .dword 20
c: .dword 0.4
str: .ascii "Hi\n!" //String di 3 Bytes
Nota: Le variabili vengono salvate in memoria, per poterle utilizzare è necessario salvare l'indirizzo di memoria in un registro (istruzione adr
) e successivamente (ed eventualmente) caricare il valore (istruzione ldr
).
ATTENZIONE: verificare il tipo di variabile, se usiamo un intero a 32Bit questo dovrà essere salvato in un registro w<n>
, altrimenti se a 64Bit x<n>
. In ogni caso l'indirizzo di memoria andrà salvato in un registro a 64Bit (x<n>
).
[...]
//dove size è tipo .dword (64Bit, doppia precisione)
adr x16, size // x16 = &size (indirizzo di memoria)
ldr x14, [x16] // x14 < mem[x16]
[...]
for (int i = 0; i < 10; i++) {
[...]
}
cmp x2, #10 //x2 = i
b.ge exitfor // >=
initfor:
[...]
add x2, x2, #1 //increment counter
cmp x2, #10
b.lt initfor
exitfor:
mov x2, #0
if (x <cond> y)
[...]
else
[...]
cmp x1, x2 //x1 = x, x2 = y
b.<cond> elselabel
[...]
b exitlabel
elselabel:
[...]
exitlabel:
do {
[...]
} while (x <cond> y);
initdo:
[...]
cmp x1, x2 //x1 = x, x2 = y
b.<!cond> initdo
while (x <cond> y) {
[...]
}
cmp x1, x2 //x1 = x, x2 = y
b.<!cond> exitwhile
initwhile:
[...]
cmp x1, x2
b.<cond> initwhile
exitwhile:
Per chiamare una procedura abbiamo bisogno d'interrompere il flusso d'istruzioni e dare il controllo al codice della procedura stessa. Al termine della procedura l’esecuzione deve riprendere all’istruzione successiva l’interruzione. Esiste un insieme di regole, Procedure Call Standard (PCS), che regola il passaggio di parametri e il ritorno:
- I parametri della funzione chiamata sono passati nei registri
x0
, ...,x7
. Se la funzione accetta più di 8 parametri occorre usare lo Stack. - Il valore di ritorno di una funzione è passato nel registro
x0
. - L’indirizzo alla prossima istruzione del codice chiamante è contenuto in
x30
, chiamato ancheLR
- La funzione chiamata può utilizzare i registri
x0
, ...,x15
senza preoccuparsi di preservarne il loro valore. E’ responsabilità del chiamante salvare il loro valore se necessario. - La funzione chiamata deve preservare il valore dei registri
x1
, ...,x27
. Significa che può modificarli, però prima di ritornare al chiamante (conRET
) deve ripristinare il loro valore originario. - Generalmente i registri
x8
,x16
,x17
,x18
sono riservati.
Esempio:
procedure:
[...]
ret //Prima del ret ricordarsi di salvare in x0 il valore da ritornare
_start:
[...]
bl procedure
Lo Stack è inizialmente allocato all’indirizzo 0x7FFFFFF2B0
e puntato dal registro SP
.
Cresce e decrese quando viene aggiunto (push) o tolto (pop) un elemento.
Tutti gli accessi alla memoria effettuati mediante il registro SP
devono essere allineati a 16 Bytes (gli indirizzi devono essere multipli di 16).
/* Primo modo, push e pop */
str w0, [SP, #-16]! // push w0
str w1, [SP, #-16]! // push w1
str x2, [SP, #-16]! // push x2
ldr x5, [SP], #16 // pop x5
ldr w4, [SP], #16 // pop w4
ldr w3, [SP], #16 // pop w3
/* Secondo modo, push e pop */
//Decrementiamo manualmente SP
sub SP, SP, #16 // creiamo spazio per 16 bytes nello stack
str w0, [SP, #12] // *(sp+12) = w0
str w1, [SP, #8] // *(sp+8) = w1
str x2, [SP] // *(sp) = x2
ldr x5, [SP] // x5 = *(sp)
ldr w4, [SP, #8] // w4 = *(sp+8)
ldr w3, [SP, #12] // w3 = *(sp+12)
add SP, SP, #16 // togliamo lo spazio per i 16 bytes