copertina

Assembly x86

Articolo di Fabio Compagnoni

Articolo pubblicato il 11/12/2019

Cos'è l'Assembly

L'Assembly è un linguaggio di programmazione, il primo ad essere stato inventato, non è un linguaggio come ad esempio C++ perchè è un linguaggio a basso livello, quindi molto vicino al linguaggio macchina.
Infatti le istruzioni assembly corrispondo a un raggruppamento di codice macchina.

Come programmare in Assembly

Quest'articolo è per l'Assembly x86, questo significa che è un particolare Assembly pensato per le CPU con ISA x86.
Le attuali CPU non hanno quest'ISA infatti sono tutte a 64bit e non a 32 bit, quindi essendo un linguaggio a basso livello che lavora con i registri e si programma la CPU è necessario utilizzare degli emulatori che permettono di emulare un processore x86,
infatti è comunemente utilizzato DOSBox che è in grado di emulare il processore Intel 8086.
I file sorgenti di Assembly vengono salvati con l'estensione .asm e puoi scriverlo con un qualsiasi text-editor. basta che si salvi con l'estensione .asm e si sta attenti a non salvarlo con l'estensione .txt.asm oppure .txt sennò l'assemblatore non troverà il file non lo riconoscerà come sorgente assembly.
Quindi per non sbagliare è meglio fare "file.asm" cosicchè l'estensione la decidiamo noi tra le virgolette e non lo imposti di default l'editor.

Assemblatore

L'assemblatore è il programma che permette di assemblare, "compilare", il sorgente assembly in un file oggetto con estensione .obj.
Poi dal file oggetto tramite il linker si passerà all'eseguibile .exe. Per fare tutto ciò è necessario un assemblatore chiamato TASM, che sta appunto per turbo assemblatore.

Una volta scaricato puoi metterlo dove vuoi, ma la posizione consigliata è C:\tasm
Adesso è necessario utilizzare DOSBOX, quindi una volta aperto bisogna montare l'archivio con il comando mount m: c:\tasm , dove m è un archivio, ma voi potete utilizzare quello che volete basta che non sia c: che è già utilizzato da dosbox, e c:\tasm è la directory di tasm, voi dovete inserire quella in cui l'avete messo voi.

Comandi assemblatore

Cosa molto importante DOVETE METTERE IL SORGENTE NELLA DIRECTORY TASM sennò l'assemblatore non trova il file.
Adesso dato per scontato tutto quello detto in precendenza, adesso vediamo come utilizzare questo TASM
In ogni file dovrete mettere una sorta di snippet perchè sennò il programma assembly non funzionerà
  • TITLE ;mettete il nome del programma che volete
  • .MODEL SMALL
  • .DATA
  • .CODE
  • .STACK
  • .STARTUP
  • MOV AH,4CH
  • INT 21H
  • END
  • Per assemblare il file
  • tasm nomefile
Attenzione il nome del file deve essere al massimo di 8 caratteri esclusa l'estensione.
  • Per linkare i file quindi utilizzare il turbo linker
  • tlink nomefile
  • Per utilizzare il turbo debugger
  • td nomefile

Registri

I registri sono alla base dell'assembly, per chi non lo sapesse i registri si trovano all'interno della CPU.
I registri sono diversi e di dimensione differente.

Registri Generali

Sono quelli che vengono utilizzati più frequentemente
Sono a 16bit, e ogni registro si può vedere come due registri una parte alta contraddistinta da H e una parte bassa L.

Registri Indice

Servono per puntare le celle di memoria e sono detti anche registri puntatore
Per puntare una cella di memoria bisogna fare MOV SI o DI,indirizzo hex della cella di memoria
Se per esempio si vuole puntare la cella 0200h e la cella 0350h e poi si vuole scambiare il contenuto delle celle si fa

  • MOV SI,0200h
  • MOV DI,0350h
  • MOV AX,[SI]
  • MOV BX,[DI]
  • MOV CX,AX
  • MOV AX,BX
  • MOV BX,CX

Registri Stack

Istruzioni

MOV, INC, DEC, ADD, SUB, DIV, MUL

MOV

E' l'operazione di assegnazione, dove viene assegnato al primo parametro il secondo
Esempio
MOV BH,AL ;viene assegnato a BH il registro AL
quindi se per esempio in AL abbiamo 15h e in BH abbiamo 86h, dopo l'operazione scritta prima nel registro BH ci ritroveremo 86h.
Per lavorare con le celle di memoria bisogna fare così
Prendiamo in esempio questo caso

  • MOV SI,0100h ;puntiamo l'indirizzo della cella
  • MOV BX,[SI] ;spostiamo il contenuto della cella con indirizzo SI nel registro BX

Altro esempio
  • MOV SI,0100h
  • MOV DI,0300h
  • MOV BX,00h
  • MOV BH,[SI]
  • MOV BL,[SI]

Esempio dove scambio il contenuto di due celle di memoria

  • MOV SI,0300h
  • MOV BL,[SI]
  • MOV SI,0310h
  • MOV [SI],BL
  • MOV SI,0300h
  • MOV [SI],BH

INC

Serve per incrementare di 1, è molto comoda con i contatori nei cicli
Esempio

  • MOV SI,0300h
  • MOV BH,[SI]
  • INC SI
  • MOV [SI],BL

DEC

E' uguale a INC solamente che invece di incrementare decrementa.

DIV

E' un operatore monoparametrico, quindi ha bisogno di un solo parametro e permette di fare la divisione
Si divide in due casi

Esempi

  • MOV AX,0Ah
  • MOV BL,05h
  • DIV BL ;passo come parametro il divisore a 8bit

Se si vuole usare diretamente un numero senza usare un registro bisona fare MOV BYTE PTR 05h e se fosse stato a 16 bit, quindi una word -> DIV WORD PTR FFh


Esempio più complesso, usando i salti per contare in numeri pari
  • MOV SI,0100h
  • MOV BL,02h
  • CICLO: CMP SI,010Ah
  • JE FINE
  • MOV AX,[SI]
  • DIV BL
  • CMP AH,0
  • JE PARI
  • INC CH
  • JMP R:
  • PARI: INC CL
  • R: INC SI
  • JMP CICLO
  • FINE:

Un altro esempio per contare i numeri dispari e peri in n celle di memoria
  • MOV DH,0 ;conta pari
  • MOV SI,0100h
  • MOV DL,02h
  • MOV CX,0Ah
  • CICLO:   CMP CX,0       ;conta celle
  •                   JE FINE
  •                   MOV AL,[SI]
  •                   MOV AH,00h
  •                   PUSH DX           ;salvo DX nello stack
  •                   XOR DX,DX      ;azzero DX per evitare bug (nella divisone a 8 bit anche se non utilizzato il registro DX, nei processori Intel se non è vuoto la divisione può dare errore
  •                   POP DX
  •                   DIV DL
  •                   CMP AH,0h
  •                   JNE DISPARI
  •                   INC DH
  • DISPARI:  INC SI
  •                   DEC CX
  •                   JMP CICLO
  • FINE:

MUL

E' un operatore monoparametrico come DIV, anche questa può avere 2 casi 8bit o 16bit, quest'ultimo operatore permette di eseguire la moltiplicazione

Esempi

  • MOV SI,0100h
  • MOV BH,[SI]
  • MOV AL,BH
  • MUL BYTE PTR 06h
  • INC SI
  • MOV [SI],AX  ;I dati verranno scritti nel registro AX sequendo lo schema Little Endian (byte basso indirizzo basso), perchè sono 2 byte

ADD

ADD serve per sommare due registri, oppure registro e memoria, oppure registro e dato immediato
Esempio: ADD [SI],05h oppure ADD AX,FFh
Se si ha un overlow, si accende il flag di carry CF
Il risultato dopo l'operazione sarà il primo parametro dell'operazione quindi se si fa ADD AX,15h il risultato sarà AX.

SUB

SUB è un operatore che permette di fare un operazione tra numeri interi positivi
Se si fa SUB 5,7 si accende il flag di segno perchè il risultato è negativo.
Esempio SUB AL,6h il risultato sarà AL stesso.

Salti

Il salto è un costrutto che permette di poter utilizzare anche in assembly una selezione o un ciclo
I salti possono essere di due tipologie : Incondizionati o Condizionati

Salti Incondizionati

I salti incondizionati si fanno utilizzando JMP e indicando un etichetta che in seguito viene inserita all'inizio della riga alla cui si vuole saltare.

Salti Condizionati

I salti condizionati vengono fatti utilizzando i flag, per accendere un flag però bisogna fare un operazione di confronto utilizzando l'operatore CMP
Esempio se voglio vedere se un certo registro contenga 0 faccio CMP AL,0h
=JEJNE
>JAJNA
!>
JNAE
! >=
JAE
>=
<JBJNB
!<
JNBE
!<=
JBE
<=

Prima di ogni salto condizionato bisogna fare una CMP.
CMP vuole due parametri non adimensionali, quindi non si può inserire dirattamente un numero ma se si tratta di un byte bisogna scrivere CMP BYTE PTR, mentre se è una word (16bit) CMP WORD PTR

Esempi
CERCA IL NUMERO PIU' PICCOLO NELLE CELLE DI MEMORIA TRA LA CELLA 0200h A 0209h E LO METTE NEL REGISTRO BX

  • MOV SI,0100h
  • MOV BL,0FFh ;Inseriamo nel registro il numero pù grande che può esserci
  • CICLO: CMP SI,010Ah ;il ciclo finisce quando si raggiunge l'indirizzo 010Ah
  • JE FINE
  • CMP [SI],BL ;confrontiamo il contenuto di BL e quello della cella puntata da SI
  • JAE DOPO ; se è maggiore o uguale va a dopo:
  • MOV BL,[SI] ;arrivati qui il contenuto di SI è più piccolo della cella puntata da SI quindi assegna a BL il contenuto di della cella puntata da SI
  • DOPO: INC SI ;incremento il registro cil l'indirizzo della cella
  • JMP CICLO
  • FINE:
Scambiare il contenuto di 2Ah celle a partire da 0000h solo se contengono un valore maggiore di 3Fh
  • MOV BX,02Ah
  • MOV SI,0000h
  • MOV CX,042h
  • CICLO: CMP CL,0h
  • JE FINE
  • CMP BYTE PTR [SI],3Fh
  • JA SCAMBIO
  • R: INC SI
  • DEC CL
  • JMP CICLO
  • SCAMBIO: MOV AL,[SI]
  • MOV AH,[SI+BX] ;BX parte da 0000h quindi è come se facessimo INC SI senza perdere il contenuto di SI
  • MOV [SI],AH
  • MOV [SI+BX],AL ;sposto alla cella puntata da SI+BX il contenuto di AL
  • JMP R
  • FINE

Per comodità si è pensato il problema al contrario ovvero vedo se il contenuto è più grande quindi incremento il contatore generale, invece se è più piccolo vado avanti.
Per comodità si può anche utilizzare il contatore al contrario, quindi assegnare subito all'inizio il numero di cicli da fare poi alla fine del ciclo invce di incrementare il contatore lo decremento.

Errori da non fare

  • Non si può fare MOV dato immediato, registro MOV 65h,AX
  • Attenti alla dimensione del registro, non può mettere un registro da 16 bit in uno da 8bit MOV AL,BX
  • NON SI PUO' FARE MOV MEMORIA MEMORIA bisogna passare sempre da almeno un registro MOV [SI],[DI]
  • Per lo stesso motivo non si può fare MOV [SI+CH],[BX+DI]
  • Attenti con la divisione a 8 bit, anche se in realtà il registro DX non viene utilizzato è meglio lo stesso azzerarlo e il metodo più comodo e veloce per la macchina è XOR DX,DX, attenzione che se è utilizzato è meglio spostarlo da qualche altra parte prima di azzerarlo es
    PUSH DX
    XOR DX,DX
    DIV DL
    POP DX
  • Attenti a non dimenticarli l'etichetta del salto, per non sbagliare mettetela subito appena scritto il salto
  • Attenti a non fare una SUB o ADD con il primo parametro un dato immediato, perchè il risultato dell'operazione va nel primo parametro quindi si può mettere al primo parametro solo registro o cella di memoria


  • Esercizi di prova

    Esercizio 1

    Fare la trace table dei registri e della memoria

    REGISTRI:

  • CX=00FEh
  • SI=0100h
  • AX=0201h
  • MEMORIA:

  • 0100 [04]
  • 0101 [03]
  • 0102 [00]
  • 0103 [0A]
  • 0104 [07]
  • 0105 [04]
    • ADD AL,[SI]
    • MOV AH,00h
    • MOV BX,AX
    • MOV AX,CX
    • DIV BL
    • ADD SI,02h
    • MOV [SI],AH
    • MOV DI,0100h
    • ADD DI,[SI]
    • ADD [DI],AL

    Fabio Compagnoni