Esercizio 35leggere un programma in assembly ARM
In questa pagina 4
Testo (tipo di esercizio previsto dalla modalità di verifica del corso per la programmazione in assembly ARM: "analizzare e comprendere semplici programmi in linguaggio assembly ARM"; nessun tema pubblico di questo tipo è stato trovato, quindi il testo è originale). Per ciascuno dei due frammenti, scritti secondo la convenzione AAPCS (argomenti in r0–r3, risultato in r0, ritorno con BX lr): (a) dire che cosa calcola, scrivendo la funzione C equivalente; (b) costruire la tabella di traccia per l'ingresso indicato; (c) dire quanto costa in funzione dell'ingresso e se ci sono errori da correggere.
Frammento A, con r0 = indirizzo della stringa "Ab1c" (terminata da '\0'):
MOV r1, #0
ciclo: LDRB r2, [r0], #1
CMP r2, #0
BEQ fine
SUB r3, r2, #97 @ 97 = 'a'
CMP r3, #25
ADDLS r1, r1, #1
B ciclo
fine: MOV r0, r1
BX lrFrammento B, con r0 = 22:
MOV r1, #0
ciclo: CMP r0, #0
BEQ fine
AND r2, r0, #1
ADD r1, r1, r2
MOV r0, r0, LSR #1
B ciclo
fine: MOV r0, r1
BX lrTeoria: Istruzioni ARM di elaborazione datiIstruzioni aritmetiche (ADD, SUB, RSB, ADC), logiche (AND, ORR, EOR, BIC, MVN), di spostamento (MOV), moltiplicazione (MUL, MLA); secondo operando immediato o registro scalato con LSL, LSR, ASR, ROR; aggiornamento dei flag con S, CMP e TST; esempi di traduzione di espressioni C.Istruzioni ARM di elaborazione dati →, Istruzioni ARM di accesso alla memoriaLDR e STR per parole, byte e mezze parole con e senza segno; modalità di indirizzamento con offset immediato o registro scalato, pre-indicizzata con write-back e post-indicizzata, con esempi di indirizzo effettivo; caricamento di indirizzi e costanti; LDM e STM.Istruzioni ARM di accesso alla memoria →, Strutture di controllo in assembly ARMSalti B e condizionati, codici di condizione con e senza segno; traduzione di if, if-else, while, for e do-while da C ad ARM; esecuzione condizionata per eliminare salti brevi; switch con tabella di salto.Strutture di controllo in assembly ARM →, Array e strutture dati in assembly ARMArray in memoria e calcolo dell'indirizzo di un elemento; scorrimento con indice o con puntatore; esempi svolti (somma, massimo, copia); stringhe terminate da zero e calcolo della lunghezza; matrici per righe; struct con offset dei campi e allineamento.Array e strutture dati in assembly ARM →.
Frammento A
Lettura. r1 è un contatore (parte da 0). A ogni giro: LDRB r2, [r0], #1 legge un byte (zero-esteso a 32 bit) dall'indirizzo r0 e poi incrementa r0 di 1 (post-indicizzato: scorre la stringa); CMP r2, #0 / BEQ fine ferma la lettura al terminatore '\0'. Poi:
SUB r3, r2, #97 @ r3 = c - 'a'
CMP r3, #25
ADDLS r1, r1, #1 @ se r3 <= 25 (SENZA segno): r1++LS (lower or same) è la condizione senza segno: come numero senza segno. Se è tra 'a' (97) e 'z' (122), e la condizione è vera. Se la sottrazione dà un numero negativo, che letto senza segno è molto grande (): la condizione è falsa. Se è : falsa. Quindi un solo confronto senza segno controlla l'appartenenza a un intervallo a <= c <= z.
Funzione C equivalente:
int conta_minuscole(const char *s) {
int n = 0;
for (; *s != '\0'; s++)
if (*s >= 'a' && *s <= 'z') n++;
return n;
}Traccia per "Ab1c" (byte 0x41 0x62 0x31 0x63 0x00):
| giro | r2 (carattere) |
r3 = r2 - 97 |
senza segno? | r1 dopo |
|---|---|---|---|---|
| 1 | 65 ('A') |
= 0xFFFFFFE0 |
no () | 0 |
| 2 | 98 ('b') |
1 | sì | 1 |
| 3 | 49 ('1') |
= 0xFFFFFFD0 |
no | 1 |
| 4 | 99 ('c') |
2 | sì | 2 |
| 5 | 0 ('\0') |
esce con BEQ |
2 |
MOV r0, r1 → il risultato è 2 (le lettere minuscole sono b e c).
Costo. Una iterazione per carattere: per una stringa di caratteri, spazio . L'esecuzione condizionata di ADDLS evita un salto per carattere.
Errori da evitare (varianti scorrette):
ADDLEal posto diADDLS:LEè con segno, e0xFFFFFFE0è (vero): contererebbe anche'A','1'... tutto ciò che sta sotto'a'.LDR r2, [r0], #1al posto diLDRB: leggerebbe 4 byte alla volta (e non scorrerebbe per byte).CMP r3, #26conLS: includerebbe anche il carattere dopo la'z'.
Frammento B
Lettura. r1 accumula; in ogni giro AND r2, r0, #1 isola il bit meno significativo di r0 (0 o 1), ADD r1, r1, r2 lo somma, e MOV r0, r0, LSR #1 sposta a destra di un bit (logical shift right: divide per 2 scartando il bit appena contato). Si ferma quando r0 diventa 0. Il risultato è la somma dei bit di r0: il numero di bit a 1.
int conta_uni(unsigned x) {
int n = 0;
while (x != 0) {
n += x & 1;
x >>= 1;
}
return n;
}Traccia per r0 = 22 = 10110:
| giro | r0 all'inizio |
r0 & 1 |
r1 dopo |
r0 dopo LSR #1 |
|---|---|---|---|---|
| 1 | 22 (10110) |
0 | 0 | 11 (1011) |
| 2 | 11 (1011) |
1 | 1 | 5 (101) |
| 3 | 5 (101) |
1 | 2 | 2 (10) |
| 4 | 2 (10) |
0 | 2 | 1 (1) |
| 5 | 1 (1) |
1 | 3 | 0 |
| 6 | 0 | esce con BEQ |
Risultato: 3 ( ha tre bit a 1).
Costo. Un giro per ogni bit fino al bit più significativo a 1: giri per , quindi , al massimo 32. Per non gira.
Osservazioni. Il LSR è logico: con un valore "negativo" in complemento a 2 (r0 = 0xFFFFFFFF) si ottiene 32; con un ASR (aritmetico) il bit di segno si replicherebbe e il ciclo non terminerebbe mai. Il test iniziale CMP r0, #0 all'inizio del ciclo (non in fondo) fa funzionare anche il caso .
Verifica
I due frammenti sono stati assemblati ed eseguiti in un emulatore ARM: il frammento A su 500 stringhe casuali (lettere maiuscole, minuscole, cifre e simboli), il B su 500 interi a 32 bit casuali (più 0 e 0xFFFFFFFF), confrontando con le funzioni C riscritte in Python; risultati: A su "Ab1c" → 2, B su 22 → 3, B su 0xFFFFFFFF → 32, B su 0 → 0. Le due varianti scorrette (ADDLE per A, ASR per B) sono state provate: la prima conta anche 'A' e '1' (da 2 a 4 sulla stessa stringa), la seconda non termina su un valore con il bit di segno a 1.
Errori comuni
- Credere che
CMP r3, #25/ADDLSconfronti con segno: i suffissiHI,HS,LO,LSsono senza segno,GT,GE,LT,LEcon segno (Strutture di controllo in assembly ARMSalti B e condizionati, codici di condizione con e senza segno; traduzione di if, if-else, while, for e do-while da C ad ARM; esecuzione condizionata per eliminare salti brevi; switch con tabella di salto.Strutture di controllo in assembly ARM →). - Dimenticare che
LDRBlegge un solo byte e che il post-indice#1incrementa di 1 (per un array diintsarebbe 4). - Confondere
LSR(logico) conASR(aritmetico) nello scorrimento di un numero senza segno. - Costruire la traccia saltando il terminatore
'\0'o il test iniziale del ciclo. - Non scrivere il risultato in
r0prima diBX lr.
Versione ripasso
Tipo di esercizio previsto dalla modalità di verifica (leggere assembly ARM; testo originale). Teoria: Istruzioni ARM di elaborazione datiIstruzioni aritmetiche (ADD, SUB, RSB, ADC), logiche (AND, ORR, EOR, BIC, MVN), di spostamento (MOV), moltiplicazione (MUL, MLA); secondo operando immediato o registro scalato con LSL, LSR, ASR, ROR; aggiornamento dei flag con S, CMP e TST; esempi di traduzione di espressioni C.Istruzioni ARM di elaborazione dati →, Strutture di controllo in assembly ARMSalti B e condizionati, codici di condizione con e senza segno; traduzione di if, if-else, while, for e do-while da C ad ARM; esecuzione condizionata per eliminare salti brevi; switch con tabella di salto.Strutture di controllo in assembly ARM →.
Frammento A (r0 = stringa "Ab1c"): conta le lettere minuscole.
MOV r1, #0
ciclo: LDRB r2, [r0], #1
CMP r2, #0
BEQ fine
SUB r3, r2, #97
CMP r3, #25
ADDLS r1, r1, #1
B ciclo
fine: MOV r0, r1
BX lrLDRB legge un byte e incrementa r0 di 1; c - 'a' <= 25 con un solo confronto senza segno (LS): i valori sotto 'a' diventano numeri enormi. Traccia A, b, 1, c: r1 = 0, 1, 1, 2 → 2. Costo . ADDLE (con segno) conterebbe anche 'A' e '1'.
Frammento B (r0 = 22): conta i bit a 1 (AND r2, r0, #1, ADD r1, r1, r2, MOV r0, r0, LSR #1, CMP r0, #0 / BEQ). 22 = 10110 → r1 = 0, 1, 2, 2, 3 → 3. Giri: , . LSR è logico: con ASR un valore negativo non terminerebbe.
Errori comuni: LS/HI letti con segno; LDR al posto di LDRB; LSR confuso con ASR; terminatore o test iniziale saltati; risultato non in r0.