[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home] [volume] [parte]


Capitolo 553.   Esempi con le «quattro operazioni»

In questo capitolo vengono mostrati esempi di programmi estremamente banali, per dimostrare il funzionamento delle istruzioni con cui si eseguono le «quattro operazioni» su valori interi, attraverso l'aiuto di GDB (GNU debugger).(1)

Si dà per scontato che si sappiano compilare i programmi con GNU AS, oppure con NASM. Se si utilizza GNU AS, è bene ricordare di inserire l'opzione --gstabs, mentre con NASM è bene aggiungere l'opzione -g, in modo da poter gestire più facilmente GDB, disponendo dei riferimenti al sorgente:

as --gstabs -o nome.o nome.s[Invio]

nasm -g -f elf -o nome.o nome.s[Invio]

Tabella 551.5. Operazioni aritmetiche.

Nome Operandi:
dstorg1org2
Descrizione Indicatori
principali:
carry,
parity,
zero, sign,
overflow
NEG
reg
mem
Inverte il segno di un numero, attraverso il complemento a due.
dst := -dst
c p z s o
# # # # #
ADD
reg, reg
reg, mem
reg, imm
mem, reg
mem, imm
Somma di interi, con o senza segno, ignorando il riporto precedente. Se i valori si intendono con segno, è importante l'esito dell'indicatore di traboccamento (overflow), se invece i valori sono da intendersi senza segno, è importante l'esito dell'indicatore di riporto (carry).
dst := org + dst
c p z s o
# # # # #
SUB
reg, reg
reg, mem
reg, imm
mem, reg
mem, imm
Sottrazione di interi con o senza segno, ignorando il riporto precedente.
dst := org - dst
c p z s o
# # # # #
ADC
reg, reg
reg, mem
reg, imm
mem, reg
mem, imm
Somma di interi, con o senza segno, aggiungendo anche il riporto precedente (l'indicatore carry).
dst := org + dst + c
c p z s o
t · · · ·
# # # # #
SBB
reg, reg
reg, mem
reg, imm
mem, reg
mem, imm
Sottrazione di interi, con o senza segno, tenendo conto del «prestito» precedente (l'indicatore carry).
dst := org + dst - c
c p z s o
t · · · ·
# # # # #
INC
reg
mem
Incrementa di una unità un intero.
dst++
c p z s o
· # # # #
DEC
reg
mem
Decrementa di una unità un valore intero.
dst--
c p z s o
· # # # #
MUL
reg
mem
Moltiplicazione intera senza segno. L'operando è il moltiplicatore, mentre il moltiplicando è costituito da registri prestabiliti.
AX := AL*src
DX:AX := AX*src
EDX:EAX := EAX*src
c p z s o
# ? ? ? #
DIV
reg
mem
Divisione intera senza segno. L'operando è il divisore, mentre il dividendo è costituito da registri prestabiliti.
AL := AX/src AH := resto
AX := DX:AX/src DX := resto
EAX := EDX:EAX/src EDX := resto
c p z s o
? ? ? ? ?
IMUL
reg
mem
Moltiplicazione intera con segno. In questo caso l'operando è il moltiplicatore, mentre il moltiplicando è costituito da registri prestabiliti.
AX := AL*src
DX:AX := AX*src
EDX:EAX := EAX*src
c p z s o
# ? ? ? #
IDIV
reg
mem
Divisione intera con segno. L'operando è il divisore, mentre il dividendo è costituito da registri prestabiliti.
AL := AX/src AH := resto
AX := DX:AX/src DX := resto
EAX := EDX:EAX/src EDX := resto
c p z s o
? ? ? ? ?

553.1   Somma

Viene proposto un programma che si limita a sommare due numeri (interi positivi) definiti in memoria e a restituire il risultato (ammesso che non sia troppo grande) attraverso il valore di uscita. Il programma viene mostrato sia nella forma adatta a GNU AS, sia in quella conforme a NASM. Le righe dei due listati coincidono.

      1 # op1 + op2
      2 #
      3 .section .data
      4 op1:    .int    15
      5 op2:    .int     5
      6 #
      7 .section .text
      8 .globl _start
      9 #
     10 _start:
     11     mov op1, %edx       # Accumula il primo addendo in EDX.
     12     add op2, %edx       # Somma il secondo addendo in EDX.
     13 bp1:
     14     mov $1,   %eax      # Restituisce il valore contenuto in EDX
     15     mov %edx, %ebx      # come valore di uscita, attraverso la
     16     int $0x80           # chiamata di sistema 1 (exit).
      1 ; op1 + op2
      2 ;
      3 section .data
      4 op1:    dd 15
      5 op2:    dd  5
      6 ;
      7 section .text
      8 global _start
      9 ;
     10 _start:
     11     mov edx, [op1]      ; Accumula il primo addendo in EDX.
     12     add edx, [op2]      ; Somma il secondo addendo in EDX.
     13 bp1:
     14     mov eax, 1          ; Restituisce il valore contenuto in EDX
     15     mov ebx, edx        ; come valore di uscita, attraverso la
     16     int 80h             ; chiamata di sistema 1 (exit).

Nelle righe 4 e 5 vengono dichiarate due aree di memoria della dimensione di un registro (32 bit), associando rispettivamente i nomi op1 e op2, a indicare il primo e il secondo operando della somma. Nella riga 11 il contenuto della memoria che rappresenta il primo operando della somma, viene inserito nel registro EDX, mentre nella riga 12 si somma quanto è rappresentato dal secondo operando, nello stesso registro.

Si osservi che per indicare l'indirizzo di memoria è stata usata la modalità di indirizzamento diretta. In pratica, il compilatore sostituisce i nomi op1 e op2 con l'indirizzo di memoria a cui fanno riferimento.

Al termine, nelle righe da 14 a 16, si prepara la chiamata di sistema exit, passando il risultato in modo che venga usato come valore di uscita. Se il risultato della somma è inferiore o uguale a 255, può essere letto.

I programmi sono uguali, a parte qualche piccola differenza nell'allocazione della memoria. Si può controllare con Objdump. Si suppone che il programma sia stato compilato con il nome add:

objdump --disassemble add[Invio]

add:    file format elf32-i386

Disassembly of section .text:

08048074 <_start>:
 8048074:       8b 15 8c 90 04 08       mov    0x804908c,%edx
 804807a:       03 15 90 90 04 08       add    0x8049090,%edx

08048080 <bp1>:
 8048080:       b8 01 00 00 00          mov    $0x1,%eax
 8048085:       89 d3                   mov    %edx,%ebx
 8048087:       cd 80                   int    $0x80

Si può controllare il funzionamento del programma, avviandolo e verificando poi il valore di uscita:

./add ; echo $?[Invio]

20

Si analizza il funzionamento del programma con GDB:

gdb add[Invio]

(gdb) break bp1[Invio]

Breakpoint 1 at 0x8048080: file add.s, line 14.

(gdb) run[Invio]

Starting program: /home/tizio/add

Breakpoint 1, bp1 () at add.s:14
14          mov $1,   %eax      # Restituisce il valore contenuto in EDX
Current language:  auto; currently asm

(gdb) info registers[Invio]

eax            0x0      0
ecx            0x0      0
edx            0x14     20
ebx            0x0      0
esp            0xbf9dcb10       0xbf9dcb10
ebp            0x0      0x0
esi            0x0      0
edi            0x0      0
eip            0x8048080        0x8048080 <bp1>
eflags         0x216    [ PF AF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x0      0

(gdb) stepi[Invio]

bp1 () at add.s:15
15          mov %edx, %ebx      # come valore di uscita, attraverso la

(gdb) stepi[Invio]

bp1 () at add.s:16
16          int $0x80           # chiamata di sistema 1 (exit).

(gdb) info registers[Invio]

eax            0x1      1
ecx            0x0      0
edx            0x14     20
ebx            0x14     20
esp            0xbfba64d0       0xbfba64d0
ebp            0x0      0x0
esi            0x0      0
edi            0x0      0
eip            0x8048087        0x8048087 <bp1+7>
eflags         0x216    [ PF AF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x0      0

(gdb) stepi[Invio](2)

Program exited with code 024.

(gdb) quit[Invio]

553.1.1   Traboccamento

Si può modificare leggermente il programma proposto, allo scopo di causare un traboccamento, in modo da vedere cosa accade nei registri:

      3 .section .data
      4 op1:    .int    0x7FFFFFFF  # 0b01111111111111111111111111111111
      5 op2:    .int    0x00000001  # 0b00000000000000000000000000000001
      3 section .data
      4 op1:    dd      0x7FFFFFFF  ; 01111111111111111111111111111111b
      5 op2:    dd      0x00000001  ; 00000000000000000000000000000001b

Compilando il programma ed eseguendolo con l'ausilio di GDB si può verificare che la somma di quei due valori trasforma il risultato in un valore apparentemente negativo, cosa che è indice di un traboccamento:

eax            0x0      0
ecx            0x0      0
edx            0x80000000       -2147483648
ebx            0x0      0
esp            0xbfa6f390       0xbfa6f390
ebp            0x0      0x0
esi            0x0      0
edi            0x0      0
eip            0x8048080        0x8048080 <bp1>
eflags         0xa96    [ PF AF SF IF OF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x0      0

A ogni modo il fatto viene sottolineato dall'indicatore di traboccamento (overflow). In questo caso non interviene l'indicatore di riporto (carry), perché se il risultato fosse da intendersi senza segno, sarebbe ancora corretto.

553.1.2   Riporto

Si può modificare leggermente il programma proposto, allo scopo di causare un riporto, in modo da vedere cosa accade nei registri:

      3 .section .data
      4 op1:    .int    0xFFFFFFFE  # 0b11111111111111111111111111111110
      5 op2:    .int    0x00000002  # 0b00000000000000000000000000000010
      3 section .data
      4 op1:    dd      0xFFFFFFFE  ; 11111111111111111111111111111110b
      5 op2:    dd      0x00000002  ; 00000000000000000000000000000001b

In questo caso, la somma dei due valori supera proprio di una unità la capienza del registro, che alla fine risulta a zero, ma l'indicatore di riporto (carry) segnala l'accaduto:

eax            0x0      0
ecx            0x0      0
edx            0x0      0
ebx            0x0      0
esp            0xbfbb44e0       0xbfbb44e0
ebp            0x0      0x0
esi            0x0      0
edi            0x0      0
eip            0x8048080        0x8048080 <bp1>
eflags         0x257    [ CF PF AF ZF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x0      0

Si può osservare che è assente l'indicatore di traboccamento (overflow), perché se la somma fosse avvenuta tra due numeri con segno, il risultato sarebbe corretto.

553.1.3   Somma di valori molti grandi

Viene mostrato un altro esempio, dove la somma riguarda valori molto grandi, divisi tra due registri. I due listati sono equivalenti e compatibili, riga per riga:

      1 # op1 + op2
      2 #
      3 .section .data
      4 op1:    .quad   0x00FFFFFFFFFFFFFF
      5 op2:    .quad   0x0000000000000001
      6 #
      7 .section .text
      8 .globl _start
      9 #
     10 _start:
     11     mov op1  , %eax     # Accumula metà del primo addendo in EAX.
     12     mov op1+4, %edx     # Accumula il resto del primo addendo in EDX.
     13     add op2,   %eax     # Somma metà del secondo addendo in EAX.
     14 bp1:
     15     adc op2+4, %edx     # Somma il resto del secondo addendo in EDX.
     16                         # Il risultato atteso in EDX:EAX è:
     17                         # 0x01000000:0x00000000
     18 bp2:
     19     mov $1,    %eax     # Conclude il funzionamento del programma
     20     mov $0,    %ebx     # restituendo zero in ogni caso.
     21     int $0x80           #
      1 ; op1 + op2
      2 ;
      3 section .data
      4 op1:    dd  0xFFFFFFFF, 0x00FFFFFF      ; 0x00FFFFFFFFFFFFFF
      5 op2:    dd  0x00000001, 0x00000000      ; 0x0000000000000001
      6 ;
      7 section .text
      8 global _start
      9 ;
     10 _start:
     11     mov eax, [op1]      ; Accumula metà del primo addendo in EAX.
     12     mov edx, [op1+4]    ; Accumula il resto del primo addendo in EDX.
     13     add eax, [op2]      ; Somma metà del secondo addendo in EAX.
     14 bp1:
     15     adc edx, [op2+4]    ; Somma il resto del secondo addendo in EDX.
     16                         ; Il risultato atteso in EDX:EAX è:
     17                         ; 0x01000000:0x00000000
     18 bp2:
     19     mov eax, 1          ; Conclude il funzionamento del programma
     20     mov edx, 0          ; restituendo zero in ogni caso.          
     21     int 80h             ; 

In questo programma ci sono delle complicazioni che vanno descritte, cominciando preferibilmente dalla versione per GNU AS (il primo listato). Nelle righe 4 e 5 vengono dichiarati due numeri molto grandi, da 64 bit. Successivamente, nelle righe da 11 a 15, si fa riferimento a questi due numeri, prendendoli a pezzi. Per la precisione, nella riga 11 si copiano i primi 32 bit a partire dall'indirizzo a cui fa riferimento l'etichetta op1 (sono solo 32 bit perché l'istruzione copia il valore in un registro di tale dimensione); nella riga 12 si copiano gli altri 32 bit, indicando che dall'indirizzo dell'etichetta op1 occorre spostarsi in avanti di quattro byte. Lo stesso ragionamento si fa per il secondo operando.

L'ordine in cui sono prelevati i dati è importante e occorre riflettere su questo fatto. Il registro EAX viene caricato con la porzione meno significativa del numero, che in memoria si trova nei «primi» 32 bit. Ciò avviene perché si intende che il microprocessore operi ordinando i byte in memoria secondo la modalità little endian.

Nel secondo listato, quello per NASM, non essendo possibile indicare un numero completo da 64 bit, si è reso necessario spezzarlo in due. In questo caso, per mantenere la stessa struttura dell'altro listato, i due tronconi sono stati messi in fila, apparentemente in ordine inverso, per riprodurre la stessa sequenza little endian complessiva.

Una volta compreso il modo in cui i dati sono prelevati dalla memoria, ci si può soffermare sulle istruzioni di somma: il troncone meno significativo viene sommato con l'istruzione ADD, mentre per quello più significativo si usa ADC che aggiunge anche il riporto, se c'è.

Alla fine, una volta compilato il programma, con GDB è possibile eseguirlo fino all'etichetta bp1 per verificare l'effetto della prima addizione, quindi lo si può fare proseguire fino all'etichetta bp2, per verificare che la coppia di registri EDX:EAX contenga il risultato corretto:

(gdb) break bp1[Invio]

(gdb) break bp2[Invio]

(gdb) run[Invio]

(gdb) info registers[Invio]

eax            0x0      0
ecx            0x0      0
edx            0xffffff 16777215
ebx            0x0      0
esp            0xbfa89bb0       0xbfa89bb0
ebp            0x0      0x0
esi            0x0      0
edi            0x0      0
eip            0x8048085        0x8048085 <bp1>
eflags         0x257    [ CF PF AF ZF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x0      0

La prima somma ha prodotto uno zero nel registro EAX, con riporto.

(gdb) continue[Invio]

(gdb) info registers[Invio]

eax            0x0      0
ecx            0x0      0
edx            0x1000000        16777216
ebx            0x0      0
esp            0xbfa89bb0       0xbfa89bb0
ebp            0x0      0x0
esi            0x0      0
edi            0x0      0
eip            0x804808b        0x804808b <bp2>
eflags         0x216    [ PF AF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x0      0

(gdb) kill[Invio]

(gdb) quit[Invio]

553.2   Sottrazione

Viene proposto un programma che esegue una sottrazione, emettendo il risultato attraverso il valore di uscita. Il programma viene mostrato sia nella forma adatta a GNU AS, sia in quella conforme a NASM. Le righe dei due listati coincidono.

      1 # op1 - op2
      2 #
      3 .section .data
      4 op1:    .int    0x00000001   # 0b00000000000000000000000000000001
      5 op2:    .int    0x00000002   # 0b00000000000000000000000000000010
      6 #
      7 .section .text
      8 .globl _start
      9 #
     10 _start:
     11     mov op1, %edx       # Accumula il minuendo in EDX.
     12     sub op2, %edx       # Riduce EDX del sottraendo.
     13 bp:
     14     mov $1,   %eax      # Restituisce il valore contenuto in EDX
     15     mov %edx, %ebx      # come valore di uscita, attraverso la
     16     int $0x80           # chiamata di sistema 1 (exit).
      1 ; op1 - op2
      2 ;
      3 section .data
      4 op1:    dd      0x00000001   ; 00000000000000000000000000000001b
      5 op2:    dd      0x00000002   ; 00000000000000000000000000000010b
      6 ;
      7 section .text
      8 global _start
      9 ;
     10 _start:
     11     mov edx, [op1]      ; Accumula il minuendo in EDX.
     12     sub edx, [op2]      ; Riduce EDX del sottraendo.
     13 bp1:
     14     mov eax, 1          ; Restituisce il valore contenuto in EDX
     15     mov ebx, edx        ; come valore di uscita, attraverso la
     16     int 80h             ; chiamata di sistema 1 (exit).

Rispetto agli esempi che riguardano la somma, qui, nella riga 12 si utilizza l'istruzione SUB. Come si può comprendere, dal momento che si sottrae una grandezza maggiore di quella contenuta nel minuendo, si ottiene un valore negativo, oppure, se i valori sono da intendersi senza segno, si ottiene un «riporto», come richiesta del prestito di una cifra oltre quella più significativa. Con GDB, in corrispondenza dell'etichetta bp1 si possono vedere i registri e si può verificare che è scattato il riporto (carry) e il segno:

...
edx            0xffffffff       -1
...
eflags         0x297    [ CF PF AF SF IF ]
...

Se si lascia concludere il programma, il valore di uscita che si ottiene è 25510, pari a FF16, ovvero 3778, ovvero 111111112, dal momento che si possono ottenere solo otto bit.

553.2.1   Inversione del segno

Si può vedere cosa accade se, invece di usare l'istruzione SUB, si cambia il segno al sottraendo e lo si somma semplicemente all'altro operando:

      1 # op1 - op2
      2 #
      3 .section .data
      4 op1:    .int    0x00000001   # 0b00000000000000000000000000000001
      5 op2:    .int    0x00000002   # 0b00000000000000000000000000000010
      6 #
      7 .section .text
      8 .globl _start
      9 #
     10 _start:
     11     mov op2, %edx  # Accumula il sottraendo in EDX.
     12     neg %edx       # Ne inverte il segno con il complemento a due.
     13 bp1:
     14     add op1, %edx  # Somma il minuendo a EDX.
     15 bp2:
     16     mov $1,   %eax # Restituisce il valore contenuto in EDX
     17     mov %edx, %ebx # come valore di uscita, attraverso la
     18     int $0x80      # chiamata di sistema 1 (exit).
      1 ; op1 - op2
      2 ;
      3 section .data
      4 op1:    dd      0x00000001   ; 00000000000000000000000000000001b
      5 op2:    dd      0x00000002   ; 00000000000000000000000000000010b
      6 ;
      7 section .text
      8 global _start
      9 ;
     10 _start:
     11     mov edx, [op2] ; Accumula il sottraendo in EDX.
     12     neg edx        ; Ne inverte il segno con il complemento a due.
     13 bp1:
     14     add edx, [op1] ; Somma il minuendo a EDX.
     15 bp2:
     16     mov eax, 1     ; Restituisce il valore contenuto in EDX
     17     mov ebx, edx   ; come valore di uscita, attraverso la
     18     int 80h        ; chiamata di sistema 1 (exit).

Rispetto a quanto fatto nel caso precedente, qui, nella riga 11 viene sommato il valore del sottraendo nel registro EDX, di cui viene invertito il segno nella riga 12. Successivamente, nella riga 14 viene sommato il valore del minuendo. Il risultato è lo stesso, ma gli indicatori si comportano in modo differente durante il procedimento. In corrispondenza dell'etichetta bp1 si può vedere quanto segue:

...
edx            0xfffffffe       -2
...
eflags         0x293    [ CF AF SF IF ]
...

Si può osservare che l'inversione del segno ha prodotto un riporto, oltre alla segnalazione del segno, ammesso che questo vada considerato.

In corrispondenza dell'etichetta bp2 è appena stata eseguita la somma del minuendo. Si può osservare che questa volta non si ottiene alcun riporto:

...
edx            0xffffffff       -1
...
eflags         0x293    [ PF SF IF ]
...

553.2.2   Sottrazione per fasi successive

Viene proposto un esempio di sottrazione da svolgere in due fasi, perché il valore non è contenibile in un solo registro. Si vuole eseguire: 100000000000000016 - 0FFFFFFFFFFFFFFF16.

      1 # op1 + op2
      2 #
      3 .section .data
      4 op1:    .quad   0x1000000000000000
      5 op2:    .quad   0x0FFFFFFFFFFFFFFF
      6 #
      7 .section .text
      8 .globl _start
      9 #
     10 _start:
     11     mov op1  , %eax # Accumula metà del primo valore in EAX.
     12     mov op1+4, %edx # Accumula il resto del primo valore in EDX.
     13 bp1:
     14     sub op2,   %eax # Sottrae metà del secondo valore in EAX.
     15 bp2:
     16     sbb op2+4, %edx # Sottrae il resto del secondo valore in EDX.
     17 bp3:
     18     mov $1,    %eax # Conclude il funzionamento del programma
     19     mov $0,    %ebx # restituendo zero in ogni caso.
     20     int $0x80       #
      1 ; op1 + op2
      2 ;
      3 section .data
      4 op1:    dd  0x00000000, 0x10000000      ; 0x1000000000000000
      5 op2:    dd  0xFFFFFFFF, 0x0FFFFFFF      ; 0x0FFFFFFFFFFFFFFF
      6 ;
      7 section .text
      8 global _start
      9 ;
     10 _start:
     11     mov eax, [op1]   ; Accumula metà del primo valore in EAX.
     12     mov edx, [op1+4] ; Accumula il resto del primo valore in EDX.
     13 bp1:
     14     sub eax, [op2]   ; Sottrae metà del secondo valore in EAX.
     15 bp2:
     16     sbb edx, [op2+4] ; Sottrae il resto del secondo valore in EDX.
     17 bp3:
     18     mov eax, 1       ; Conclude il funzionamento del programma
     19     mov edx, 0       ; restituendo zero in ogni caso.          
     20     int 0x80         ; 

Il valore del minuendo viene copiato, in due pezzi, nei registri EDX:EAX; quindi si sottraggono i 32 bit inferiori del sottraendo al registro EAX e infine si sottraggono i 32 bit più significativi del sottraendo dal registro EDX, tenendo conto del riporto precedente.

In corrispondenza dell'etichetta bp1, il minuendo è stato copiato nei registri EDX:EAX:

...
eax            0x0      0
...
edx            0x10000000       268435456
...
eflags         0x292    [ AF SF IF ]
...

Al punto di bp2 è stata eseguita la sottrazione dei 32 bit inferiori, causando un riporto, da intendersi come richiesta di un prestito:

...
eax            0x1      1
...
edx            0x10000000       268435456
...
eflags         0x213    [ CF AF IF ]
...

Al punto di bp3 è stata completata la sottrazione:

...
eax            0x1      1
...
edx            0x0      0
...
eflags         0x256    [ PF AF ZF IF ]
...

553.3   Moltiplicazione senza segno

Nella moltiplicazione si distingue il fatto che si consideri il segno o meno. Quando si esegue una moltiplicazione senza segno si usa l'istruzione MUL con l'indicazione di un solo operando, perché gli altri sono impliciti. Nella moltiplicazione il contenitore del risultato deve essere più capiente di ciò che è stato usato per produrlo. Si distinguono questi casi:

AX := AL*src
DX:AX := AX*src
EDX:EAX := EAX*src

In pratica, l'origine deve essere di pari dimensioni del moltiplicando, costituito, rispettivamente da: AL, AX o EAX.

      1 # op1 * op2
      2 #
      3 .section .data
      4 op1:    .short  0x8008
      5 op2:    .short  0x2002
      6 #
      7 .section .bss
      8 .lcomm prodotto, 4
      9 #
     10 .section .text
     11 .globl _start
     12 #
     13 _start:
     14     mov  op1, %ax       # Accumula il moltiplicando in AX.
     15     mulw op2            # Moltiplica il secondo valore per AX (implicito).
     16 bp1:
     17     mov %ax, prodotto   # Copia in memoria la prima parte del risultato.
     18     mov %dx, prodotto+2 # Copia in memoria la seconda parte del risultato.
     19     mov prodotto, %eax  # Copia il risultato dalla memoria a EAX.
     20 bp2:
     21     mov $1,   %eax      # Restituisce il valore contenuto in EAX
     22     mov %eax, %ebx      # come valore di uscita, attraverso la
     23     int $0x80           # chiamata di sistema 1 (exit).
      1 ; op1 * op2
      2 ;
      3 section .data
      4 op1:    dw      0x8008
      5 op2:    dw      0x2002
      6 ;
      7 section .bss
      8 prodotto resb 4
      9 ;
     10 section .text
     11 global _start
     12 ;
     13 _start:
     14     mov      ax, [op1]        ; Accumula il moltiplicando in AX.
     15     mul word [op2]            ; Moltiplica il secondo valore per AX (implicito).
     16 bp1:
     17     mov      [prodotto], ax   ; Copia in memoria la prima parte del risultato.
     18     mov      [prodotto+2], dx ; Copia in memoria la seconda parte del risultato.
     19     mov      eax, [prodotto]  ; Copia il risultato dalla memoria a EAX.
     20 bp2:
     21     mov      eax, 1           ; Restituisce il valore contenuto in EAX
     22     mov      ebx, eax         ; come valore di uscita, attraverso la
     23     int      0x80             ; chiamata di sistema 1 (exit).

In questo esempio i valori da moltiplicare sono della dimensione di 16 bit e sono, rispettivamente, 800816 e 200216. Moltiplicando questi due valori si deve ottenere 1002001016. In pratica si deve eseguire una moltiplicazione del tipo DX:AX := AX*src.

Rispetto a esempi già visti nelle sezioni precedenti, in questo si dichiara un'area di memoria non inizializzata, nella riga numero 8, per contenere almeno quattro byte (32 bit), con il nome simbolico prodotto. All'interno di questa area di memoria si vuole ricostruire il risultato della moltiplicazione in modo che occupi un gruppo continuo di 32 bit.

Nella riga 15 si esegue la moltiplicazione, utilizzando come operando direttamente la memoria. Tuttavia, per farlo, occorre specificare la dimensione di questo operando, altrimenti verrebbe presa in considerazione un'area più grande del voluto.

In corrispondenza del punto bp1 si può vedere il risultato della moltiplicazione diviso tra DX e AX:

...
eax            0x10     16
...
edx            0x1002   4098
...
eflags         0xa93    [ CF AF SF IF OF ]
...

Nelle righe 17 e 18 viene copiato il risultato in memoria, ricomponendolo nell'ordine corretto, osservando che si usa una rappresentazione dei valori numerici in modalità little endian, quindi la parte meno significativa viene copiata prima. In corrispondenza del punto bp2 il risultato della moltiplicazione è tutto contenuto nel registro EAX:

...
eax            0x10020010       268566544
...
edx            0x1002   4098
...
eflags         0xa93    [ CF AF SF IF OF ]
...

553.4   Moltiplicazione con segno

La moltiplicazione con segno si ottiene con un'istruzione differente, IMUL, che però va usata con la stessa modalità di quella senza segno. Sarebbe possibile usare più di un operando con questa istruzione, senza bisogno di operandi impliciti, ma in generale non è conveniente perché c'è il rischio di creare confusione sulla dimensione di questi operandi.

      1 # op1 * op2
      2 #
      3 .section .data
      4 op1:    .short   0x0007
      5 op2:    .short  -0x0001
      6 #
      7 .section .bss
      8 .lcomm prodotto, 4
      9 #
     10 .section .text
     11 .globl _start
     12 #
     13 _start:
     14     mov   op1, %ax      # Accumula il moltiplicando in AX.
     15     imulw op2           # Moltiplica il secondo valore per AX (implicito).
     16 bp1:
     17     mov %ax, prodotto   # Copia in memoria la prima parte del risultato.
     18     mov %dx, prodotto+2 # Copia in memoria la seconda parte del risultato.
     19     mov prodotto, %eax  # Copia il risultato dalla memoria a EAX.
     20 bp2:
     21     mov $1,   %eax      # Restituisce il valore contenuto in EAX
     22     mov %eax, %ebx      # come valore di uscita, attraverso la
     23     int $0x80           # chiamata di sistema 1 (exit).
      1 ; op1 * op2
      2 ;
      3 section .data
      4 op1:    dw       0x0007
      5 op2:    dw      -0x0001
      6 ;
      7 section .bss
      8 prodotto resb 4
      9 ;
     10 section .text
     11 global _start
     12 ;
     13 _start:
     14     mov       ax, [op1]        ; Accumula il moltiplicando in AX.
     15     imul word [op2]            ; Moltiplica il secondo valore per AX (implicito).
     16 bp1:
     17     mov       [prodotto], ax   ; Copia in memoria la prima parte del risultato.
     18     mov       [prodotto+2], dx ; Copia in memoria la seconda parte del risultato.
     19     mov       eax, [prodotto]  ; Copia il risultato dalla memoria a EAX.
     20 bp2:
     21     mov       eax, 1           ; Restituisce il valore contenuto in EAX
     22     mov       ebx, eax         ; come valore di uscita, attraverso la
     23     int       0x80             ; chiamata di sistema 1 (exit).

Rispetto a esempi già visti, questo utilizza una costante numerica negativa (riga 5); in pratica è il compilatore che la trasforma nel complemento a due, in modo automatico. Per il resto, tutto procede come nell'esempio della moltiplicazione intera, a parte l'uso dell'istruzione IMUL.

In corrispondenza del punto bp1 si può vedere il risultato della moltiplicazione, distribuito tra DX:AX:

...
eax            0xfff9   65529
...
edx            0xffff   65535
...
eflags         0x292    [ AF SF IF ]
...

In bp2 il risultato è completo nel registro EAX:

...
eax            0xfffffff9       -7
...
eflags         0x292    [ AF SF IF ]
...

553.5   Divisione

Per la divisione si usa un meccanismo simile a quello della moltiplicazione, ma opposto:

AL := AX/src AH := resto
AX := DX:AX/src DX := resto
EAX := EDX:EAX/src EDX := resto

In questo esempio si parte da valori che occupano 32 bit, azzerando inizialmente EDX perché il dividendo non è così grande da richiederne l'utilizzo.

      1 # op1 / op2
      2 #
      3 .section .data
      4 op1:    .int    0x00010001
      5 op2:    .int    0x00000002
      6 #
      7 .section .text
      8 .globl _start
      9 #
     10 _start:
     11     mov  op1, %eax      # Accumula il dividendo in EAX.
     12     mov  $0,  %edx      # Azzera EDX.
     13     divl op2            # Divide EDX:EAX per il divisore.
     14 bp1:
     15     mov $1,   %eax      # Restituisce il valore contenuto in EDX
     16     mov %edx, %ebx      # come valore di uscita, attraverso la
     17     int $0x80           # chiamata di sistema 1 (exit).
      1 ; op1 * op2
      2 ;
      3 section .data
      4 op1:    dd      0x00010001
      5 op2:    dd      0x00000002
      6 ;
      7 section .text
      8 global _start
      9 ;
     10 _start:
     11     mov      eax, [op1] ; Accumula il dividendo in EAX.
     12     mov      edx, 0     ; Azzera EDX.
     13     div long [op2]      ; Divide EDX:EAX per il divisore.
     14 bp1:
     15     mov      eax, 1     ; Restituisce il valore contenuto in EDX
     16     mov      edx, eax   ; come valore di uscita, attraverso la
     17     int      0x80       ; chiamata di sistema 1 (exit).

In corrispondenza del punto bp1 si può leggere il risultato della divisione in EAX e il resto in EDX:

...
eax            0x8000   32768
...
edx            0x1      1
...
eflags         0x212    [ AF IF ]
...

Per quanto riguarda la divisione con segno, tutto procede nello stesso modo, a parte il fatto che si utilizza l'istruzione IDIV:

     10 _start:
     11     mov   op1, %eax     # Accumula il dividendo in EAX.
     12     mov   $0,  %edx     # Azzera EDX.
     13     idivl op2           # Divide EDX:EAX per il divisore.
     10 _start:
     11     mov       eax, [op1]      ; Accumula il dividendo in EAX.
     12     mov       edx, 0          ; Azzera EDX.
     13     idiv long [op2]           ; Divide EDX:EAX per il divisore.

1) GDB   GNU GPL

2) Evidentemente, il valore di uscita viene espresso in base otto: 248 è uguale a 2010.


Appunti di informatica libera 2008 --- Copyright © 2000-2008 Daniele Giacomini -- <appunti2 (ad) gmail·com>


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome esempi_con_le_171_quattro_operazioni_187.htm

[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home]

Valid ISO-HTML!

CSS validator!

Gjlg Metamotore e Web Directory