Introduction to ARM64v8
Livelli di Eccezione - EL (ARM64v8)
Nell'architettura ARMv8, i livelli di esecuzione, noti come Livelli di Eccezione (EL), definiscono il livello di privilegio e le capacità dell'ambiente di esecuzione. Ci sono quattro livelli di eccezione, che vanno da EL0 a EL3, ognuno con uno scopo diverso:
EL0 - Modalità Utente:
Questo è il livello meno privilegiato e viene utilizzato per eseguire il codice dell'applicazione regolare.
Le applicazioni in esecuzione a EL0 sono isolate l'una dall'altra e dal software di sistema, migliorando la sicurezza e la stabilità.
EL1 - Modalità Kernel del Sistema Operativo:
La maggior parte dei kernel dei sistemi operativi funziona a questo livello.
EL1 ha più privilegi rispetto a EL0 e può accedere alle risorse di sistema, ma con alcune restrizioni per garantire l'integrità del sistema.
EL2 - Modalità Hypervisor:
Questo livello è utilizzato per la virtualizzazione. Un hypervisor in esecuzione a EL2 può gestire più sistemi operativi (ciascuno nel proprio EL1) in esecuzione sull'hardware fisico.
EL2 fornisce funzionalità per l'isolamento e il controllo degli ambienti virtualizzati.
EL3 - Modalità Monitor Sicuro:
Questo è il livello più privilegiato e viene spesso utilizzato per l'avvio sicuro e gli ambienti di esecuzione affidabili.
EL3 può gestire e controllare gli accessi tra stati sicuri e non sicuri (come l'avvio sicuro, il sistema operativo affidabile, ecc.).
L'uso di questi livelli consente di gestire in modo strutturato e sicuro diversi aspetti del sistema, dalle applicazioni utente al software di sistema più privilegiato. L'approccio di ARMv8 ai livelli di privilegio aiuta a isolare efficacemente diversi componenti di sistema, migliorando così la sicurezza e la robustezza del sistema.
Registri (ARM64v8)
ARM64 ha 31 registri a scopo generale, etichettati da x0
a x30
. Ciascuno può memorizzare un valore 64-bit (8-byte). Per operazioni che richiedono solo valori a 32 bit, gli stessi registri possono essere accessibili in modalità a 32 bit utilizzando i nomi w0 a w30.
x0
ax7
- Questi sono tipicamente utilizzati come registri temporanei e per passare parametri alle subroutine.
x0
contiene anche i dati di ritorno di una funzione.
x8
- Nel kernel Linux,x8
viene utilizzato come numero di chiamata di sistema per l'istruzionesvc
. In macOS viene utilizzato x16!x9
ax15
- Altri registri temporanei, spesso utilizzati per variabili locali.x16
ex17
- Registri di Chiamata Intra-procedurale. Registri temporanei per valori immediati. Vengono utilizzati anche per chiamate a funzioni indirette e per stub PLT (Procedure Linkage Table).
x16
viene utilizzato come numero di chiamata di sistema per l'istruzionesvc
in macOS.
x18
- Registro di Piattaforma. Può essere utilizzato come registro a scopo generale, ma su alcune piattaforme questo registro è riservato per usi specifici della piattaforma: Puntatore al blocco dell'ambiente del thread corrente in Windows, o per puntare alla struttura del task attualmente in esecuzione nel kernel Linux.x19
ax28
- Questi sono registri salvati dal chiamante. Una funzione deve preservare i valori di questi registri per il chiamante, quindi vengono memorizzati nello stack e ripristinati prima di tornare al chiamante.x29
- Puntatore al Frame per tenere traccia del frame dello stack. Quando viene creato un nuovo frame dello stack perché viene chiamata una funzione, il registrox29
viene memorizzato nello stack e l'indirizzo del nuovo frame pointer (indirizzosp
) viene memorizzato in questo registro.
Questo registro può anche essere utilizzato come registro a scopo generale anche se di solito viene utilizzato come riferimento per le variabili locali.
x30
olr
- Registro di Link. Contiene l'indirizzo di ritorno quando viene eseguita un'istruzioneBL
(Branch with Link) oBLR
(Branch with Link to Register) memorizzando il valore dipc
in questo registro.
Può essere utilizzato come qualsiasi altro registro.
Se la funzione corrente sta per chiamare una nuova funzione e quindi sovrascrivere
lr
, lo memorizzerà nello stack all'inizio, questo è l'epilogo (stp x29, x30 , [sp, #-48]; mov x29, sp
-> Memorizzafp
elr
, genera spazio e ottiene il nuovofp
) e lo recupererà alla fine, questo è il prologo (ldp x29, x30, [sp], #48; ret
-> Recuperafp
elr
e ritorna).
sp
- Puntatore dello Stack, utilizzato per tenere traccia della cima dello stack.
il valore di
sp
dovrebbe sempre essere mantenuto almeno a un allineamento di quadre parole o potrebbe verificarsi un'eccezione di allineamento.
pc
- Contatore di Programma, che punta alla prossima istruzione. Questo registro può essere aggiornato solo attraverso generazioni di eccezioni, ritorni di eccezioni e branch. Le uniche istruzioni ordinarie che possono leggere questo registro sono le istruzioni di branch con link (BL, BLR) per memorizzare l'indirizzo dipc
inlr
(Registro di Link).xzr
- Registro Zero. Chiamato anchewzr
nella sua forma a registro 32-bit. Può essere utilizzato per ottenere facilmente il valore zero (operazione comune) o per eseguire confronti usandosubs
comesubs XZR, Xn, #10
memorizzando i dati risultanti da nessuna parte (inxzr
).
I registri Wn
sono la versione a 32 bit del registro Xn
.
Registri SIMD e in Virgola Mobile
Inoltre, ci sono altri 32 registri di lunghezza 128 bit che possono essere utilizzati in operazioni ottimizzate di dati multipli con una singola istruzione (SIMD) e per eseguire operazioni aritmetiche in virgola mobile. Questi sono chiamati registri Vn anche se possono operare anche in 64-bit, 32-bit, 16-bit e 8-bit e quindi sono chiamati Qn
, Dn
, Sn
, Hn
e Bn
.
Registri di sistema
Ci sono centinaia di registri di sistema, chiamati anche registri a scopo speciale (SPR), utilizzati per monitorare e controllare il comportamento dei processori.
Possono essere letti o impostati solo utilizzando le istruzioni speciali dedicate mrs
e msr
.
I registri speciali TPIDR_EL0
e TPIDDR_EL0
sono comunemente trovati durante l'ingegneria inversa. Il suffisso EL0
indica la minima eccezione dalla quale il registro può essere accessibile (in questo caso EL0 è il livello di eccezione (privilegio) regolare con cui i programmi regolari vengono eseguiti).
Sono spesso utilizzati per memorizzare l'indirizzo di base della regione di memoria dello storage locale del thread. Di solito il primo è leggibile e scrivibile per i programmi in esecuzione in EL0, ma il secondo può essere letto da EL0 e scritto da EL1 (come il kernel).
mrs x0, TPIDR_EL0 ; Leggi TPIDR_EL0 in x0
msr TPIDR_EL0, X0 ; Scrivi x0 in TPIDR_EL0
PSTATE
PSTATE contiene diversi componenti del processo serializzati nel registro speciale SPSR_ELx
, essendo X il livello di permesso dell'eccezione scatenata (ciò consente di ripristinare lo stato del processo al termine dell'eccezione).
Questi sono i campi accessibili:
I flag di condizione
N
,Z
,C
eV
:N
indica che l'operazione ha prodotto un risultato negativoZ
indica che l'operazione ha prodotto zeroC
indica che l'operazione è stata eseguitaV
indica che l'operazione ha prodotto un overflow con segno:La somma di due numeri positivi produce un risultato negativo.
La somma di due numeri negativi produce un risultato positivo.
Nella sottrazione, quando un numero negativo grande viene sottratto da un numero positivo più piccolo (o viceversa), e il risultato non può essere rappresentato all'interno dell'intervallo della dimensione dei bit fornita.
Ovviamente il processore non sa se l'operazione è con segno o meno, quindi controllerà C e V nelle operazioni e indicherà se si è verificato un trasporto nel caso fosse con segno o senza segno.
Non tutte le istruzioni aggiornano questi flag. Alcune come CMP
o TST
lo fanno, e altre che hanno un suffisso s come ADDS
lo fanno anche.
Il flag attuale della larghezza del registro (
nRW
): Se il flag ha il valore 0, il programma verrà eseguito nello stato di esecuzione AArch64 una volta ripreso.Il livello di eccezione corrente (
EL
): Un programma regolare in esecuzione in EL0 avrà il valore 0Il flag di singolo passaggio (
SS
): Usato dai debugger per eseguire un passo alla volta impostando il flag SS su 1 all'interno diSPSR_ELx
tramite un'eccezione. Il programma eseguirà un passo e emetterà un'eccezione di passo singolo.Il flag di stato di eccezione illegale (
IL
): Viene utilizzato per segnalare quando un software privilegiato esegue un trasferimento di livello di eccezione non valido, questo flag viene impostato su 1 e il processore scatena un'eccezione di stato illegale.I flag
DAIF
: Questi flag consentono a un programma privilegiato di mascherare selettivamente determinate eccezioni esterne.Se
A
è 1 significa che verranno scatenati aborti asincroni.I
configura la risposta alle Richieste di Interruzione Hardware esterne (IRQs). e F è relativo alle Richieste di Interruzione Rapida (FIRs).I flag di selezione del puntatore dello stack (
SPS
): I programmi privilegiati in esecuzione in EL1 e superiori possono passare dall'utilizzare il proprio registro del puntatore dello stack e quello del modello utente (ad es. traSP_EL1
eEL0
). Questo passaggio viene eseguito scrivendo nel registro specialeSPSel
. Questo non può essere fatto da EL0.
Convenzione di chiamata (ARM64v8)
La convenzione di chiamata ARM64 specifica che i primi otto parametri di una funzione vengono passati nei registri x0
attraverso x7
. I parametri aggiuntivi vengono passati nello stack. Il valore di ritorno viene restituito nel registro x0
, o anche in x1
se è lungo 128 bit. I registri x19
a x30
e sp
devono essere preservati durante le chiamate alle funzioni.
Quando si legge una funzione in assembly, cercare il prologo e l'epilogo della funzione. Il prologo di solito coinvolge il salvataggio del frame pointer (x29
), impostare un nuovo frame pointer, e allocare spazio nello stack. L'epilogo di solito coinvolge il ripristino del frame pointer salvato e il ritorno dalla funzione.
Convenzione di chiamata in Swift
Swift ha la sua convenzione di chiamata che può essere trovata in https://github.com/apple/swift/blob/main/docs/ABI/CallConvSummary.rst#arm64
Istruzioni Comuni (ARM64v8)
Le istruzioni ARM64 generalmente hanno il formato opcode dst, src1, src2
, dove opcode
è l'operazione da eseguire (come add
, sub
, mov
, ecc.), dst
è il registro destinazione dove verrà memorizzato il risultato, e src1
e src2
sono i registri origine. Possono essere utilizzati anche valori immediati al posto dei registri di origine.
mov
: Sposta un valore da un registro a un altro.Esempio:
mov x0, x1
— Questo sposta il valore dax1
ax0
.ldr
: Carica un valore dalla memoria in un registro.Esempio:
ldr x0, [x1]
— Questo carica un valore dalla posizione di memoria puntata dax1
inx0
.Modalità di offset: Un offset che influisce sul puntatore di origine è indicato, ad esempio:
ldr x2, [x1, #8]
, questo caricherà in x2 il valore da x1 + 8ldr x2, [x0, x1, lsl #2]
, questo caricherà in x2 un oggetto dall'array x0, dalla posizione x1 (indice) * 4Modalità pre-indicizzata: Questo applicherà calcoli all'origine, otterrà il risultato e memorizzerà anche la nuova origine nell'origine.
ldr x2, [x1, #8]!
, questo caricheràx1 + 8
inx2
e memorizzerà in x1 il risultato dix1 + 8
str lr, [sp, #-4]!
, Memorizza il registro di link in sp e aggiorna il registro spModalità post-indicizzata: È simile alla precedente ma l'indirizzo di memoria viene accesso e poi viene calcolato e memorizzato l'offset.
ldr x0, [x1], #8
, caricax1
inx0
e aggiorna x1 conx1 + 8
Indirizzamento relativo al PC: In questo caso l'indirizzo da caricare viene calcolato in relazione al registro PC
ldr x1, =_start
, Questo caricherà l'indirizzo in cui inizia il simbolo_start
in x1 relativo al PC corrente.str
: Memorizza un valore da un registro nella memoria.Esempio:
str x0, [x1]
— Questo memorizza il valore inx0
nella posizione di memoria puntata dax1
.ldp
: Carica Coppia di Registri. Questa istruzione carica due registri da posizioni di memoria consecutive. L'indirizzo di memoria è tipicamente formato aggiungendo un offset al valore in un altro registro.Esempio:
ldp x0, x1, [x2]
— Questo caricax0
ex1
dalle posizioni di memoria inx2
ex2 + 8
, rispettivamente.stp
: Memorizza Coppia di Registri. Questa istruzione memorizza due registri in posizioni di memoria consecutive. L'indirizzo di memoria è tipicamente formato aggiungendo un offset al valore in un altro registro.Esempio:
stp x0, x1, [sp]
— Questo memorizzax0
ex1
nelle posizioni di memoria insp
esp + 8
, rispettivamente.stp x0, x1, [sp, #16]!
— Questo memorizzax0
ex1
nelle posizioni di memoria insp+16
esp + 24
, rispettivamente, e aggiornasp
consp+16
.add
: Aggiunge i valori di due registri e memorizza il risultato in un registro.Sintassi: add(s) Xn1, Xn2, Xn3 | #imm, [shift #N | RRX]
Xn1 -> Destinazione
Xn2 -> Operando 1
Xn3 | #imm -> Operando 2 (registro o immediato)
[shift #N | RRX] -> Esegue uno shift o chiama RRX
Esempio:
add x0, x1, x2
— Questo somma i valori inx1
ex2
insieme e memorizza il risultato inx0
.add x5, x5, #1, lsl #12
— Questo equivale a 4096 (un 1 shiftato 12 volte) -> 1 0000 0000 0000 0000adds
Questo esegue unadd
e aggiorna i flagsub
: Sottrai i valori di due registri e memorizza il risultato in un registro.Controlla la sintassi di
add
.Esempio:
sub x0, x1, x2
— Questo sottrae il valore inx2
dax1
e memorizza il risultato inx0
.subs
Questo è come sub ma aggiorna il flagmul
: Moltiplica i valori di due registri e memorizza il risultato in un registro.Esempio:
mul x0, x1, x2
— Questo moltiplica i valori inx1
ex2
e memorizza il risultato inx0
.div
: Dividi il valore di un registro per un altro e memorizza il risultato in un registro.Esempio:
div x0, x1, x2
— Questo divide il valore inx1
perx2
e memorizza il risultato inx0
.lsl
,lsr
,asr
,ror
,rrx
:Shift logico a sinistra: Aggiunge 0 dalla fine spostando gli altri bit in avanti (moltiplica n volte per 2)
Shift logico a destra: Aggiunge 1 all'inizio spostando gli altri bit all'indietro (divide n volte per 2 in non firmato)
Shift aritmetico a destra: Come
lsr
, ma invece di aggiungere 0 se il bit più significativo è 1, **aggiunge 1 (**divide n volte per 2 in firmato)Ruota a destra: Come
lsr
ma qualsiasi cosa venga rimossa da destra viene aggiunta a sinistraRuota a destra con estensione: Come
ror
, ma con il flag di carry come "bit più significativo". Quindi il flag di carry viene spostato al bit 31 e il bit rimosso al flag di carry.bfm
: Bit Filed Move, queste operazioni copiano i bit0...n
da un valore e li collocano nelle posizionim..m+n
. Il#s
specifica la posizione del bit più a sinistra e#r
la quantità di rotazione a destra.Spostamento del campo bit:
BFM Xd, Xn, #r
Spostamento del campo bit firmato:
SBFM Xd, Xn, #r, #s
Spostamento del campo bit non firmato:
UBFM Xd, Xn, #r, #s
Estrai e inserisci campo bit: Copia un campo bit da un registro e lo copia in un altro registro.
BFI X1, X2, #3, #4
Inserisce 4 bit da X2 dal 3° bit di X1BFXIL X1, X2, #3, #4
Estrae dal 3° bit di X2 quattro bit e li copia in X1SBFIZ X1, X2, #3, #4
Estende il segno di 4 bit da X2 e li inserisce in X1 a partire dalla posizione del bit 3 azzerando i bit a destraSBFX X1, X2, #3, #4
Estrae 4 bit a partire dal bit 3 di X2, estende il segno e inserisce il risultato in X1UBFIZ X1, X2, #3, #4
Estende a zero 4 bit da X2 e li inserisce in X1 a partire dalla posizione del bit 3 azzerando i bit a destraUBFX X1, X2, #3, #4
Estrae 4 bit a partire dal bit 3 di X2 e inserisce il risultato esteso a zero in X1.Estendi il segno a X: Estende il segno (o aggiunge solo 0 nella versione non firmata) di un valore per poter eseguire operazioni con esso:
SXTB X1, W2
Estende il segno di un byte da W2 a X1 (W2
è la metà diX2
) per riempire i 64 bitSXTH X1, W2
Estende il segno di un numero a 16 bit da W2 a X1 per riempire i 64 bitSXTW X1, W2
Estende il segno di un byte da W2 a X1 per riempire i 64 bitUXTB X1, W2
Aggiunge 0 (non firmato) a un byte da W2 a X1 per riempire i 64 bitextr
: Estrae bit da una coppia di registri concatenati specificati.Esempio:
EXTR W3, W2, W1, #3
Questo concatena W1+W2 e prende dal bit 3 di W2 fino al bit 3 di W1 e lo memorizza in W3.cmp
: Confronta due registri e imposta i flag di condizione. È un alias disubs
impostando il registro di destinazione al registro zero. Utile per sapere sem == n
.Supporta la stessa sintassi di
subs
Esempio:
cmp x0, x1
— Questo confronta i valori inx0
ex1
e imposta i flag di condizione di conseguenza.cmn
: Confronto negativo dell'operando. In questo caso è un alias diadds
e supporta la stessa sintassi. Utile per sapere sem == -n
.ccmp
: Confronto condizionale, è un confronto che verrà eseguito solo se un confronto precedente è vero e imposterà specificamente i bit nzcv.cmp x1, x2; ccmp x3, x4, 0, NE; blt _func
-> se x1 != x2 e x3 < x4, salta a funcQuesto perché
ccmp
verrà eseguito solo se il precedentecmp
era unNE
, se non lo fosse i bitnzcv
verranno impostati a 0 (che non soddisferà il confrontoblt
).Questo può anche essere usato come
ccmn
(stesso ma negativo, comecmp
vscmn
).tst
: Controlla se i valori del confronto sono entrambi 1 (funziona come un ANDS senza memorizzare il risultato da nessuna parte). È utile per controllare un registro con un valore e verificare se uno qualsiasi dei bit del registro indicato nel valore è 1.Esempio:
tst X1, #7
Controlla se uno qualsiasi degli ultimi 3 bit di X1 è 1teq
: Operazione XOR scartando il risultatob
: Salto incondizionatoEsempio:
b myFunction
Nota che questo non riempirà il registro di collegamento con l'indirizzo di ritorno (non adatto per le chiamate a subroutine che devono tornare indietro)
bl
: Salto con collegamento, usato per chiamare una sottoroutine. Memorizza l'indirizzo di ritorno inx30
.Esempio:
bl myFunction
— Questo chiama la funzionemyFunction
e memorizza l'indirizzo di ritorno inx30
.Nota che questo non riempirà il registro di collegamento con l'indirizzo di ritorno (non adatto per le chiamate a subroutine che devono tornare indietro)
blr
: Salto con collegamento al registro, usato per chiamare una sottoroutine dove il target è specificato in un registro. Memorizza l'indirizzo di ritorno inx30
. (Questo èEsempio:
blr x1
— Questo chiama la funzione il cui indirizzo è contenuto inx1
e memorizza l'indirizzo di ritorno inx30
.ret
: Ritorna dalla sottoroutine, tipicamente utilizzando l'indirizzo inx30
.Esempio:
ret
— Questo ritorna dalla sottoroutine corrente utilizzando l'indirizzo di ritorno inx30
.b.<cond>
: Salti condizionalib.eq
: Salta se uguale, basato sull'istruzionecmp
precedente.Esempio:
b.eq label
— Se l'istruzionecmp
precedente ha trovato due valori uguali, questo salta alabel
.b.ne
: Branch se Non Uguale. Questa istruzione controlla i flag di condizione (che sono stati impostati da un'istruzione di confronto precedente), e se i valori confrontati non erano uguali, salta a un'etichetta o indirizzo.Esempio: Dopo un'istruzione
cmp x0, x1
,b.ne label
— Se i valori inx0
ex1
non erano uguali, salta alabel
.cbz
: Confronta e Salta se Zero. Questa istruzione confronta un registro con zero, e se sono uguali, salta a un'etichetta o indirizzo.Esempio:
cbz x0, label
— Se il valore inx0
è zero, salta alabel
.cbnz
: Confronta e Salta se Non Zero. Questa istruzione confronta un registro con zero, e se non sono uguali, salta a un'etichetta o indirizzo.Esempio:
cbnz x0, label
— Se il valore inx0
non è zero, salta alabel
.tbnz
: Testa il bit e salta se non zeroEsempio:
tbnz x0, #8, label
tbz
: Testa il bit e salta se zeroEsempio:
tbz x0, #8, label
Operazioni di selezione condizionale: Queste sono operazioni il cui comportamento varia a seconda dei bit condizionali.
csel Xd, Xn, Xm, cond
->csel X0, X1, X2, EQ
-> Se vero, X0 = X1, se falso, X0 = X2csinc Xd, Xn, Xm, cond
-> Se vero, Xd = Xn, se falso, Xd = Xm + 1cinc Xd, Xn, cond
-> Se vero, Xd = Xn + 1, se falso, Xd = Xncsinv Xd, Xn, Xm, cond
-> Se vero, Xd = Xn, se falso, Xd = NON(Xm)cinv Xd, Xn, cond
-> Se vero, Xd = NON(Xn), se falso, Xd = Xncsneg Xd, Xn, Xm, cond
-> Se vero, Xd = Xn, se falso, Xd = - Xmcneg Xd, Xn, cond
-> Se vero, Xd = - Xn, se falso, Xd = Xncset Xd, Xn, Xm, cond
-> Se vero, Xd = 1, se falso, Xd = 0csetm Xd, Xn, Xm, cond
-> Se vero, Xd = <tutti 1>, se falso, Xd = 0adrp
: Calcola l'indirizzo di pagina di un simbolo e lo memorizza in un registro.Esempio:
adrp x0, symbol
— Questo calcola l'indirizzo di pagina disymbol
e lo memorizza inx0
.ldrsw
: Carica un valore firmato a 32 bit dalla memoria e estende il segno a 64 bit.Esempio:
ldrsw x0, [x1]
— Questo carica un valore firmato a 32 bit dalla posizione di memoria puntata dax1
, estende il segno a 64 bit e lo memorizza inx0
.stur
: Memorizza un valore di registro in una posizione di memoria, utilizzando un offset da un altro registro.Esempio:
stur x0, [x1, #4]
— Questo memorizza il valore inx0
nella posizione di memoria che è 4 byte maggiore rispetto all'indirizzo attualmente inx1
.svc
: Effettua una chiamata di sistema. Sta per "Supervisor Call". Quando il processore esegue questa istruzione, passa dalla modalità utente alla modalità kernel e salta a una posizione specifica in memoria dove si trova il codice di gestione delle chiamate di sistema del kernel.Esempio:
Prologo della Funzione
Salva il registro del link e il puntatore del frame nello stack:
Imposta il nuovo frame pointer:
mov x29, sp
(imposta il nuovo frame pointer per la funzione corrente)Allocare spazio nello stack per le variabili locali (se necessario):
sub sp, sp, <size>
(dove<size>
è il numero di byte necessario)
Epilogo della Funzione
Dealloca le variabili locali (se ne sono state allocate):
add sp, sp, <size>
Ripristina il registro del link e il frame pointer:
Ritorno:
ret
(restituisce il controllo al chiamante utilizzando l'indirizzo nel registro di collegamento)
Stato di esecuzione AARCH32
Armv8-A supporta l'esecuzione di programmi a 32 bit. AArch32 può funzionare in uno dei due set di istruzioni: A32
e T32
e può passare da uno all'altro tramite interworking
.
I programmi privilegiati a 64 bit possono pianificare l'esecuzione di programmi a 32 bit eseguendo un trasferimento di livello di eccezione al 32 bit a livello di privilegio inferiore.
Si noti che la transizione da 64 bit a 32 bit avviene con un abbassamento del livello di eccezione (ad esempio un programma a 64 bit in EL1 che attiva un programma in EL0). Ciò viene fatto impostando il bit 4 di SPSR_ELx
registro speciale a 1 quando il thread del processo AArch32
è pronto per essere eseguito e il resto di SPSR_ELx
memorizza i programmi AArch32
CPSR. Quindi, il processo privilegiato chiama l'istruzione ERET
in modo che il processore passi a AArch32
entrando in A32 o T32 a seconda di CPSR**.**
L'interworking
avviene utilizzando i bit J e T di CPSR. J=0
e T=0
significa A32
e J=0
e T=1
significa T32. Questo si traduce fondamentalmente nell'impostare il bit più basso a 1 per indicare che il set di istruzioni è T32.
Questo viene impostato durante le istruzioni di branch interworking, ma può anche essere impostato direttamente con altre istruzioni quando il PC è impostato come registro di destinazione. Esempio:
Un altro esempio:
Registri
Ci sono 16 registri da 32 bit (r0-r15). Da r0 a r14 possono essere utilizzati per qualsiasi operazione, tuttavia alcuni di essi sono di solito riservati:
r15
: Contatore di programma (sempre). Contiene l'indirizzo dell'istruzione successiva. In A32 corrente + 8, in T32, corrente + 4.r11
: Frame Pointerr12
: Registro di chiamata intra-proceduraler13
: Stack Pointerr14
: Link Register
Inoltre, i registri sono salvati nei registri bancati
. Questi sono luoghi che memorizzano i valori dei registri consentendo di eseguire cambiamenti di contesto veloci nella gestione delle eccezioni e delle operazioni privilegiate per evitare la necessità di salvare e ripristinare manualmente i registri ogni volta.
Questo avviene salvando lo stato del processore dal CPSR
al SPSR
della modalità del processore a cui viene gestita l'eccezione. Al ritorno dall'eccezione, il CPSR
viene ripristinato dal SPSR
.
CPSR - Current Program Status Register
In AArch32 il CPSR funziona in modo simile a PSTATE
in AArch64 ed è anche memorizzato in SPSR_ELx
quando viene gestita un'eccezione per ripristinare in seguito l'esecuzione:
I campi sono divisi in alcuni gruppi:
Application Program Status Register (APSR): Flag aritmetici e accessibili da EL0
Execution State Registers: Comportamento del processo (gestito dal sistema operativo).
Application Program Status Register (APSR)
I flag
N
,Z
,C
,V
(come in AArch64)Il flag
Q
: Viene impostato a 1 ogni volta che si verifica una saturazione intera durante l'esecuzione di un'istruzione aritmetica di saturazione specializzata. Una volta impostato a1
, manterrà il valore fino a quando non verrà impostato manualmente a 0. Inoltre, non c'è alcuna istruzione che controlla il suo valore implicitamente, deve essere fatto leggendolo manualmente.GE
(Greater than or equal) Flags: Viene utilizzato nelle operazioni SIMD (Single Instruction, Multiple Data), come "addizione parallela" e "sottrazione parallela". Queste operazioni consentono di elaborare più punti dati in un'unica istruzione.
Ad esempio, l'istruzione UADD8
aggiunge quattro coppie di byte (da due operandi da 32 bit) in parallelo e memorizza i risultati in un registro da 32 bit. Quindi imposta i flag GE
nell'APSR
in base a questi risultati. Ciascun flag GE corrisponde a una delle addizioni di byte, indicando se l'addizione per quella coppia di byte ha overflowed.
L'istruzione SEL
utilizza questi flag GE per eseguire azioni condizionali.
Execution State Registers
I bit
J
eT
:J
dovrebbe essere 0 e seT
è 0 viene utilizzato il set di istruzioni A32, e se è 1 viene utilizzato il set di istruzioni T32.IT Block State Register (
ITSTATE
): Questi sono i bit da 10 a 15 e da 25 a 26. Memorizzano le condizioni per le istruzioni all'interno di un gruppo con prefissoIT
.Bit
E
: Indica la endianness.Mode and Exception Mask Bits (0-4): Determinano lo stato di esecuzione corrente. Il quinto indica se il programma viene eseguito come 32 bit (un 1) o 64 bit (un 0). Gli altri 4 rappresentano la modalità di eccezione attualmente in uso (quando si verifica un'eccezione e viene gestita). Il numero impostato indica la priorità corrente nel caso in cui venga scatenata un'altra eccezione mentre questa viene gestita.
AIF
: Alcune eccezioni possono essere disabilitate utilizzando i bitA
,I
,F
. SeA
è 1 significa che verranno scatenati aborti asincroni. IlI
configura la risposta alle Richieste di Interruzione Hardware esterne (IRQ). e il F è relativo alle Richieste di Interruzione Rapida (FIRs).
macOS
Chiamate di sistema BSD
Controlla syscalls.master. Le chiamate di sistema BSD avranno x16 > 0.
Trappole Mach
Controlla in syscall_sw.c la mach_trap_table
e in mach_traps.h i prototipi. Il numero massimo di trappole Mach è MACH_TRAP_TABLE_COUNT
= 128. Le trappole Mach avranno x16 < 0, quindi è necessario chiamare i numeri dalla lista precedente con un meno: _kernelrpc_mach_vm_allocate_trap
è -10
.
Puoi anche controllare libsystem_kernel.dylib
in un disassemblatore per capire come chiamare queste chiamate di sistema (e BSD):
A volte è più facile controllare il codice decompilato da libsystem_kernel.dylib
piuttosto che controllare il codice sorgente perché il codice di diverse chiamate di sistema (BSD e Mach) è generato tramite script (controlla i commenti nel codice sorgente) mentre nella dylib puoi trovare cosa viene chiamato.
chiamate machdep
XNU supporta un altro tipo di chiamate chiamate dipendenti dalla macchina. Il numero di queste chiamate dipende dall'architettura e né le chiamate né i numeri sono garantiti di rimanere costanti.
pagina comm
Questa è una pagina di memoria proprietaria del kernel che viene mappata nello spazio degli indirizzi di ogni processo utente. È pensata per rendere più veloce la transizione dalla modalità utente allo spazio kernel rispetto all'utilizzo di chiamate di sistema per servizi kernel che vengono utilizzati così tanto che questa transizione sarebbe molto inefficiente.
Ad esempio, la chiamata gettimeofdate
legge il valore di timeval
direttamente dalla pagina comm.
objc_msgSend
È molto comune trovare questa funzione utilizzata nei programmi Objective-C o Swift. Questa funzione consente di chiamare un metodo di un oggetto Objective-C.
Parametri (più informazioni nella documentazione):
x0: self -> Puntatore all'istanza
x1: op -> Selettore del metodo
x2... -> Resto degli argomenti del metodo invocato
Quindi, se imposti un breakpoint prima del salto a questa funzione, puoi facilmente trovare cosa viene invocato in lldb con (in questo esempio l'oggetto chiama un oggetto da NSConcreteTask
che eseguirà un comando):
Impostando la variabile di ambiente NSObjCMessageLoggingEnabled=1
è possibile registrare quando questa funzione viene chiamata in un file come /tmp/msgSends-pid
.
Inoltre, impostando OBJC_HELP=1
e chiamando qualsiasi binario è possibile visualizzare altre variabili di ambiente che potresti utilizzare per registrare quando avvengono determinate azioni Objc-C.
Quando questa funzione viene chiamata, è necessario trovare il metodo chiamato dell'istanza indicata, per questo vengono effettuate ricerche diverse:
Eseguire una ricerca nella cache ottimistica:
Se ha successo, finito
Acquisire runtimeLock (lettura)
Se (realize && !cls->realized) realizzi la classe
Se (initialize && !cls->initialized) inizializza la classe
Prova la cache della classe stessa:
Se ha successo, finito
Prova l'elenco dei metodi della classe:
Se trovato, riempi la cache e finito
Prova la cache della superclasse:
Se ha successo, finito
Prova l'elenco dei metodi della superclasse:
Se trovato, riempi la cache e finito
Se (resolver) prova il risolutore del metodo e ripeti dalla ricerca della classe
Se ancora qui (= tutto il resto è fallito) prova il forwarder
Shellcodes
Per compilare:
Per estrarre i byte:
Per macOS più recenti:
Last updated