Salta al contenuto
Note per Studenti Esercizio 35 · leggere un programma in assembly ARM

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'):

armasm
        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    lr

Frammento B, con r0 = 22:

armasm
        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    lr

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 →, 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:

armasm
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: r3≤25r3 \le 25 come numero senza segno. Se cc è tra 'a' (97) e 'z' (122), c−97∈[0,25]c - 97 \in [0, 25] e la condizione è vera. Se c<97c < 97 la sottrazione dà un numero negativo, che letto senza segno è molto grande (232−k>252^{32} - k > 25): la condizione è falsa. Se c>122c > 122 è >25> 25: falsa. Quindi un solo confronto senza segno controlla l'appartenenza a un intervallo a <= c <= z.

Funzione C equivalente:

c
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 r3≤25r3 \le 25 senza segno? r1 dopo
1 65 ('A') −32-32 = 0xFFFFFFE0 no (4 294 967 264>254\,294\,967\,264 > 25) 0
2 98 ('b') 1 sì 1
3 49 ('1') −48-48 = 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: O(n)O(n) per una stringa di nn caratteri, spazio O(1)O(1). L'esecuzione condizionata di ADDLS evita un salto per carattere.

Errori da evitare (varianti scorrette):

  • ADDLE al posto di ADDLS: LE è con segno, e 0xFFFFFFE0 è −32≤25-32 \le 25 (vero): contererebbe anche 'A', '1'... tutto ciò che sta sotto 'a'.
  • LDR r2, [r0], #1 al posto di LDRB: leggerebbe 4 byte alla volta (e non scorrerebbe per byte).
  • CMP r3, #26 con LS: 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.

c
int conta_uni(unsigned x) {
    int n = 0;
    while (x != 0) {
        n += x & 1;
        x >>= 1;
    }
    return n;
}

Traccia per r0 = 22 = 101102_2:

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 (22=10110222 = 10110_2 ha tre bit a 1).

Costo. Un giro per ogni bit fino al bit più significativo a 1: ⌊log⁡2x⌋+1\lfloor \log_2 x \rfloor + 1 giri per x>0x > 0, quindi O(log⁡x)O(\log x), al massimo 32. Per x=0x = 0 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 x=0x = 0.

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

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.

armasm
        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    lr

LDRB 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 O(n)O(n). 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: ⌊log⁡2x⌋+1\lfloor \log_2 x \rfloor + 1, O(log⁡x)O(\log x). 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.

Teoria collegata