Segreti

Segreti

Esercizio di apprendimento

Introduzione

Manipolazione dei bit

Ogni bit di un intero può essere usato per memorizzare un valore binario. Poiché molte situazioni coinvolgono informazioni binarie, come vero o falso, inclusione o esclusione, acceso o spento, la rappresentazione binaria di un intero a N bit fornisce un modo compatto per codificare lo stato binario di N elementi. Questo rende essenziale, in assembly, la capacità di manipolare bit e byte. Il set di istruzioni x86-64 offre un'ampia varietà di istruzioni di manipolazione bit a bit.

Manipolazione di un singolo bit

Queste istruzioni operano su singoli bit di un operando.

Prendono tutti due operandi: il secondo indica l'indice del bit su cui si opera nel primo operando. Tutti copiano il bit selezionato nel flag di carry (CF).

Nome Descrizione
bt copia il bit in CF senza modificare alcun operando
bts copia il bit in CF e lo imposta nell'operando di destinazione
btr copia il bit in CF e lo azzera nell'operando di destinazione
btc copia il bit in CF e lo complementa (lo inverte) nell'operando di destinazione

Operazioni bit a bit

Le operazioni bit a bit vengono eseguite su tutti i bit di un operando.

Hanno tutte un'istruzione con lo stesso nome dell'operazione bit a bit eseguita:

Nome Descrizione
and 1 se entrambi i bit sono 1
or 1 se almeno uno dei bit è 1
xor 1 se i bit sono diversi
not 1 se il bit era 0; 0 se il bit era 1

La maggior parte di esse prende due operandi, esegue un'operazione bit a bit su entrambi e memorizza il risultato nell'operando di destinazione. L'eccezione è not, che prende un solo operando di destinazione.

Maschere

Quando interpretiamo uno e zero rispettivamente come inclusione ed esclusione, un intero viene chiamato maschera di bit (o semplicemente maschera).

Una maschera di bit «filtra» gli elementi perché uno zero nel bit i-esimo esclude l'elemento i-esimo, mentre un uno lo include. Usiamo comunemente anche una maschera di bit per includere certi bit di un intero ed escluderne altri.

Ad esempio, sia A un intero la cui rappresentazione binaria è:

indice 7 6 5 4 3 2 1 0
bit 1 0 0 1 0 1 0 1

Sia inoltre M un intero la cui rappresentazione binaria è:

indice 7 6 5 4 3 2 1 0
bit 0 0 0 0 1 1 0 1

Entrambi sono interi a 8 bit. In questo caso, possiamo dire che M seleziona i bit 0, 2 e 3 di A ed esclude gli altri.

Le istruzioni bit a bit discusse prima sono utili per manipolare interi con le maschere. Ad esempio:

  • Per azzerare i bit di A che non sono selezionati da M, esegui l'AND bit a bit: A AND M.
  • Per impostare i bit di A selezionati da M, esegui l'OR bit a bit: A OR M.

Istruzione TEST

L'istruzione test esegue un AND bit a bit tra entrambi gli operandi e imposta i flag in base al risultato.

Se A è il primo operando e B il secondo:

flag impostato quando
CF sempre azzerato
ZF A AND B == 0
SF il bit di segno di A AND B è impostato
OF sempre azzerato

Questa istruzione prende due operandi e aggiorna i flag, ma non modifica i suoi operandi.

Operazioni di shift

Queste istruzioni spostano i bit nell'operando di destinazione di un numero di posizioni specificato dal secondo operando. Il secondo operando deve essere un numero costante (un immediate) o il registro cl (gli 8 bit più bassi di rcx).

Nome Descrizione
shl/sal Sposta i bit a sinistra
shr/sar Sposta i bit a destra

Nota che il conteggio nel secondo operando viene mascherato a 5 bit, o 6 bit con un operando di destinazione a 64 bit. Ogni bit successivo viene di fatto ignorato. Ciò significa che lo shift massimo è 31, o 63 con un operando a 64 bit.

Shl / Sal

Sia shl che sal eseguono esattamente la stessa operazione: uno è un alias dell'altro.

Ogni volta che viene eseguito uno shift a sinistra, i bit più vicini alla fine della sequenza rispetto alla lunghezza dello shift vengono prima spostati in CF e poi scartati. D'altra parte, all'inizio viene aggiunto un numero di nuovi bit azzerati pari alla lunghezza dello shift.

Poiché ogni bit in un intero rappresenta una potenza di 2, uno shift a sinistra di n posizioni ha l'effetto di moltiplicare l'intero per 2ⁿ.

Shr / Sar

Ci sono due istruzioni per spostare i bit a destra: shr e sar.

Ogni volta che viene usata una delle due istruzioni, i bit più vicini all'inizio della sequenza rispetto alla lunghezza dello shift vengono prima spostati in CF e poi scartati. D'altra parte, alla fine viene aggiunto un numero di nuovi bit pari alla lunghezza dello shift.

La differenza tra loro è che shr sposta bit 0 all'estremità sinistra, mentre sar sposta 1 se il bit più significativo era impostato e 0 altrimenti. Ciò significa che sar preserva il segno nello shift di un intero con segno.

Poiché ogni bit in un intero rappresenta una potenza di 2, uno shift a destra di n posizioni usando shr ha l'effetto di eseguire una divisione senza segno per 2ⁿ.

Analogamente, uno shift a destra di n posizioni usando sar ha l'effetto di eseguire una divisione con segno per 2ⁿ.

Operazioni di rotazione

Queste istruzioni spostano i bit nell'operando di destinazione di un numero di posizioni specificato dal secondo operando. Il secondo operando deve essere un numero costante (un immediate) o il registro cl (gli 8 bit più bassi di rcx).

La differenza tra una rotazione e uno shift è che una rotazione non scarta né aggiunge bit. I bit che verrebbero scartati da uno shift vengono invece spostati all'estremità opposta. Quindi tutti i bit rimangono, cambiano tutti posto.

Nome Descrizione
rol Ruota i bit a sinistra
ror Ruota i bit a destra

Nota che il conteggio nel secondo operando viene mascherato a 5 bit, o 6 bit con un operando di destinazione a 64 bit. Ogni bit successivo viene di fatto ignorato. Ciò significa che la rotazione massima è 31, o 63 con un operando a 64 bit.

Altre istruzioni di manipolazione dei bit

Ci sono altre utili istruzioni di manipolazione dei bit:

Nome Descrizione
popcnt Conta il numero di bit impostati
bsr Ottiene l'indice del bit impostato più significativo. Se nessun bit è impostato, il risultato non è definito
bsf Ottiene l'indice del bit impostato meno significativo. Se nessun bit è impostato, il risultato non è definito

Queste istruzioni funzionano tutte con due operandi a 16 bit, 32 bit o 64 bit. Non possono essere usate con operandi a 8 bit.

Istruzioni

Il tuo amico ti ha appena inviato un messaggio con un segreto importante. Per non rendere facile agli altri leggerlo, il messaggio è stato cifrato eseguendo una serie di manipolazioni sui bit. Dovrai scrivere i metodi che aiutano a decifrare il messaggio.

Note

Queste sono le istruzioni sui singoli bit menzionate in questo concetto:

Nome Descrizione
bt copia il bit in CF senza modificare alcun operando
bts copia il bit in CF e lo imposta nell'operando di destinazione
btr copia il bit in CF e lo azzera nell'operando di destinazione
btc copia il bit in CF e lo complementa (inverte) nell'operando di destinazione

Queste sono le istruzioni bit a bit menzionate in questo concetto:

Nome Descrizione
and 1 se entrambi i bit sono 1
or 1 se almeno uno dei bit è 1
xor 1 se i bit sono diversi
not 1 se il bit era 0; 0 se il bit era 1

Queste sono le istruzioni di scorrimento menzionate in questo concetto:

Nome Descrizione
shl/sal Sposta i bit a sinistra
shr/sar Sposta i bit a destra

Queste sono le istruzioni di rotazione menzionate in questo concetto:

Nome Descrizione
rol Ruota i bit a sinistra
ror Ruota i bit a destra

Queste sono le istruzioni varie menzionate in questo concetto:

Nome Descrizione
popcnt Conta il numero di bit impostati
bsr Ottiene l'indice del bit impostato più significativo. Se nessun bit è impostato, il risultato è indefinito
bsf Ottiene l'indice del bit impostato meno significativo. Se nessun bit è impostato, il risultato è indefinito

1. Estrai la maschera

Il messaggio è codificato in un intero a 16 bit. Tuttavia, tra questi, gli 8 bit più alti non fanno in realtà parte del messaggio, ma sono una maschera che deve essere usata nella decifratura.

Implementa la funzione extract_higher_bits che prende un intero a 16 bit e restituisce i suoi 8 bit più alti.

extract_higher_bits(0b1010010011000101)
// => 0b10100100

2. Estrai il messaggio

Saper estrarre la maschera non basta, devi anche isolare il messaggio.

Implementa la funzione extract_lower_bits che prende un intero a 16 bit e restituisce i suoi 8 bit più bassi.

extract_lower_bits(0b1010010011000101);
// => 0b11000101

3. Estrai i bit ridondanti

Alcuni bit sono impostati sia nel messaggio che nella maschera. Questa è un'informazione molto importante che verrà usata più avanti.

Implementa extract_redundant_bits che prende un intero a 16 bit, che codifica sia il messaggio che una maschera, e restituisce un intero a 8 bit con solo i bit ridondanti impostati. Un bit nel numero restituito dovrebbe essere impostato a 1 dove è 1 sia nel messaggio che nella maschera. Tutti gli altri bit dovrebbero essere azzerati.

extract_redundant_bits(0b1010010011000101);
// => 0b10000100

4. Imposta tutti i bit del messaggio

Poi, ci sono alcuni bit che devono essere impostati a 1 nel messaggio, in base alla maschera.

Implementa la funzione set_message_bits che prende un intero a 16 bit, che codifica sia il messaggio che una maschera, e restituisce il risultato dell'impostazione a 1 dei bit nel messaggio. Un bit del messaggio dovrebbe essere impostato a 1 dove il bit nella maschera è 1. Tutti gli altri bit dovrebbero essere lasciati invariati, in modo che restino impostati se erano già impostati, e azzerati se erano già azzerati.

set_message_bits(0b1010010011000101);
// => 0b11100101

5. Ruota la chiave privata

C'è un pezzo del puzzle non esplicito nel messaggio: il numero a 16 bit 0b1011001100111100. Questo numero è la tua chiave privata condivisa e dovresti usarla per aiutarti a decifrare il messaggio.

Per farlo, devi prima ruotare i bit della tua chiave privata verso sinistra di un certo numero di posizioni. Il numero di posizioni è uguale al numero di bit ridondanti impostati sia nel messaggio che nella maschera.

Implementa la funzione rotate_private_key che prende un intero a 16 bit, che codifica sia il messaggio che una maschera, e restituisce il risultato della rotazione della tua chiave privata. Questo risultato è un intero a 16 bit.

rotate_private_key(0b1010010011000101);
// => 0b1100110011110010
Note

NASM (The Netwide Assembler, l'assembler usato da questo track) supporta le costanti in formato binario con il prefisso 0b. Supporta anche l'uso di un underscore (_) come separatore in una costante, per leggibilità:

PRIVATE_KEY equ 0b1011_0011_0011_1100

6. Formatta la chiave privata

Per poter essere usata nella decifratura, la tua chiave privata deve essere formattata per isolare i bit rilevanti.

Per formattare completamente una chiave privata, devi:

  • Ruotarla.
  • Isolare la porzione più bassa di 8 bit della chiave privata ruotata, che è il valore di base.
  • Isolare la porzione più alta di 8 bit della chiave privata ruotata, che è una maschera da applicare al valore di base.
  • Invertire i bit nel valore di base che sono impostati anche nella maschera.
  • Invertire tutti i bit nel risultato.

Un bit invertito è 1 se era 0 e 0 se era 1.

Implementa la funzione format_private_key che prende un intero a 16 bit, che codifica sia il messaggio che una maschera, e restituisce una chiave privata a 8 bit completamente formattata.

format_private_key(0b1010010011000101);
// => 0b11000001

7. Completa la decifratura

Una volta che hai il messaggio con tutti i bit rilevanti impostati e la chiave privata formattata, è il momento di unirli per ottenere il messaggio risultante.

Il messaggio risultante è un intero a 16 bit, di cui:

  • Gli 8 bit più alti sono riempiti con la chiave privata formattata.
  • Gli 8 bit più bassi sono riempiti con il messaggio, dopo aver impostato tutti i bit rilevanti.

Implementa la funzione decrypt_message che prende un intero a 16 bit che codifica sia il messaggio che una maschera, e restituisce un intero a 16 bit con il messaggio completamente decifrato.

Questa funzione dovrebbe usare la chiave privata formattata che generi con format_private_key e anche il messaggio con tutti i bit rilevanti impostati con set_message_bits.

decrypt_message(0b1010010011000101);
// => 0b1100000111100101
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
x86-64 Assembly Exercism

Vuoi iniziare Segreti?

Iscriviti a Exercism per imparare e padroneggiare x86-64 Assembly con 22 concetti130 esercizi e il mentoring di persone reali, tutto gratis.