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


Capitolo 558.   Funzioni

Attraverso l'uso delle istruzioni CALL e RET è possibile realizzare delle subroutine, ovvero qualcosa che assomigli alle funzioni dei linguaggi di programmazione più evoluti.

CALL indirizzo
RET

L'istruzione CALL, prima di passare il controllo all'indirizzo di memoria indicato,(1) salva l'indirizzo dell'istruzione successiva alla chiamata nella pila dei dati (stack). Per converso, l'istruzione RET recupera dalla pila l'ultimo elemento e passa il controllo all'istruzione che si trova all'indirizzo contenuto in tale elemento estratto.

558.1   Esempio banale di chiamata

Per comprendere il meccanismo con cui si realizzano le subroutine, si prende in considerazione un esempio, già descritto, che calcola il prodotto tra due numeri attraverso lo scorrimento dei valori. Come sempre, vengono mostrati due listati equivalenti, fatti rispettivamente per GNU AS e NASM.

      1 # op1 * op2
      2 #
      3 .section .data
      4 op1:    .byte   0x07, 0x00    # little endian = 0x0005  intero senza segno.
      5 op2:    .byte   0x03, 0x00    # little endian = 0x0003  intero senza segno.
      6 #
      7 .section .text
      8 .globl _start
      9 #-----------------------------------------------------------------------
     10 _start:
     11     movzx op1,  %edx       # Moltiplicando.
     12     movzx op2,  %ecx       # Moltiplicatore.
     13     mov   $0,   %eax       # Risultato.
     14 bp1:
     15     call  f_mol            # Esegue la moltiplicazione: EAX = EDX * ECX
     16 bp2:
     17     mov   %eax, %ebx       # Restituisce il valore del prodotto,
     18     mov   $1,   %eax       # ammesso che sia abbastanza piccolo
     19     int   $0x80            # da poter essere rappresentato come
     20                            # valore di uscita.
     21 #-----------------------------------------------------------------------
     22 # Moltiplicazione di due numeri interi.
     23 # EAX = EDX * ECX
     24 # I registri EDX e ECX vengono alterati durante il procedimento.
     25 #
     26 f_mol:
     27     cmp   $0,   %ecx       # Se il moltiplicatore è pari a zero, il ciclo
     28     jz    f_end_mol        # deve terminare.
     29     shr   $1,   %ecx       # Fa scorrere a destra il moltiplicatore e se
     30     jnc   end_do_somma     # l'indicatore di riporto non è attivo, salta
     31                            # la somma.
     32 do_somma:
     33     add   %edx, %eax       # Aggiunge il moltiplicando al risultato.
     34 end_do_somma:    
     35     shl   $1,   %edx       # Fa scorrere il moltiplicando a sinistra.
     36     jmp   f_mol            # Torna all'inizio della funzione.
     37 f_end_mol:
     38     ret                    # Torna all'istruzione successiva alla chiamata.
      1 ; op1 * op2
      2 ;
      3 section .data
      4 op1:    dw      0x0007    ; Intero senza segno.
      5 op2:    dw      0x0003    ; Intero senza segno.
      6 ;
      7 section .text
      8 global _start
      9 ;-----------------------------------------------------------------------
     10 _start:
     11     movzx edx, word [op1]  ; Moltiplicando.
     12     movzx ecx, word [op2]  ; Moltiplicatore.
     13     mov   eax,      0      ; Risultato.
     14 bp1:
     15     call  f_mol            ; Esegue la moltiplicazione: EAX = EDX * ECX
     16 bp2:
     17     mov   ebx,      eax    ; Restituisce il valore del prodotto,
     18     mov   eax,      1      ; ammesso che sia abbastanza piccolo
     19     int   0x80             ; da poter essere rappresentato come
     20                            ; valore di uscita.
     21 ;-----------------------------------------------------------------------
     22 ; Moltiplicazione di due numeri interi.
     23 ; EAX = EDX * ECX
     24 ; I registri EDX e ECX vengono alterati durante il procedimento.
     25 ;
     26 f_mol:
     27     cmp   ecx,      0      ; Se il moltiplicatore è pari a zero, il ciclo
     28     jz    f_end_mol        ; deve terminare.
     29     shr   ecx,      1      ; Fa scorrere a destra il moltiplicatore e se
     30     jnc   end_do_somma     ; l'indicatore di riporto non è attivo, salta
     31                            ; la somma.
     32 do_somma:
     33     add   eax,      edx    ; Aggiunge il moltiplicando al risultato.
     34 end_do_somma:    
     35     shl   edx,      1      ; Fa scorrere il moltiplicando a sinistra.
     36     jmp   f_mol            ; Torna all'inizio della funzione.
     37 f_end_mol:
     38     ret                    ; Torna all'istruzione successiva alla chiamata.

Si osservi che il programma viene eseguito a partire dalla riga 11 e che si conclude alla riga 19. La subroutine, ovvero la funzione che esegue la moltiplicazione, si trova in un gruppo di istruzioni successive, il cui inizio è segnalato dall'etichetta f_mol.

La funzione riceve il moltiplicando e il moltiplicatore attraverso due registri, utilizzando a sua volta un altro registro per restituire il risultato.

558.2   Salvataggio dei registri prima della chiamata

Ogni funzione ha la necessità di elaborare dati senza interferire con il resto del programma; in pratica, ogni funzione deve poter utilizzare i registri con una certa libertà. Nell'esempio precedente vengono utilizzati dei registri per passare alla funzione i valori da moltiplicare, ma all'interno della funzione il contenuto dei registri viene modificato. Per lo scopo dell'esempio, il fatto che i registri ECX e EDX vengano modificati, non produce effetti collaterali, ma in un programma più complesso potrebbe essere il caso di salvaguardare il contenuto originale dei registri prima della chiamata di una funzione. L'esempio successivo mostra una variante del codice contenuto tra le etichette bp1 e bp2, allo scopo di conservare una copia dei registri che contengono il moltiplicando e il moltiplicatore, per ripristinarla dopo la chiamata:

bp1:
    push  %ecx          # Salva il moltiplicatore nella pila.
    push  %edx          # Salva il moltiplicando nella pila.
    call  f_mol         # Esegue la moltiplicazione: EAX = EDX * ECX
    pop   %edx          # Recupera il moltiplicando dalla pila.
    pop   %ecx          # Recupera il moltiplicatore dalla pila.
bp2:
bp1:
    push  ecx           ; Salva il moltiplicatore nella pila.
    push  edx           ; Salva il moltiplicando nella pila.
    call  f_mol         ; Esegue la moltiplicazione: EAX = EDX * ECX
    pop   edx           ; Recupera il moltiplicando dalla pila.
    pop   ecx           ; Recupera il moltiplicatore dalla pila.
bp2:

Come si può intendere, il recupero dei valori dalla pila deve avvenire in senso inverso.

558.3   Passaggio di parametri attraverso la pila

Per rendere più libera la funzione dal programma chiamante, conviene utilizzare la stessa pila per il passaggio dei parametri. In pratica, dopo avere salvato i registri che contengono dati importanti (ammesso che ciò vada fatto), occorre accumulare nella pila gli argomenti della chiamata della funzione, secondo un ordine convenuto per la funzione stessa. All'interno della funzione, poi, si vanno a pescare questi valori per usarli nell'elaborazione.

# op1 * op2
#
.section .data
op1:    .byte   0x07, 0x00 # little endian = 0x0005  intero senza segno.
op2:    .byte   0x03, 0x00 # little endian = 0x0003  intero senza segno.
#
.section .text
.globl _start
#-----------------------------------------------------------------------
_start:
    movzx op1,     %edx    # Moltiplicando.
    movzx op2,     %ecx    # Moltiplicatore.
bp1:
    push  %ecx             # Salva il moltiplicatore nella pila.
    push  %edx             # Salva il moltiplicando nella pila.
    #
    push  %ecx             # Inserisce il secondo parametro nella pila.
    push  %edx             # Inserisce il primo parametro nella pila.
    call  f_mol            # Esegue la moltiplicazione: EAX = EDX * ECX
    add   $4,      %esp    # Espelle il primo parametro della chiamata.
    add   $4,      %esp    # Espelle il secondo parametro della chiamata.
    #
    pop   %edx             # Recupera il moltiplicando dalla pila.
    pop   %ecx             # Recupera il moltiplicatore dalla pila.
bp2:
    mov   %eax,    %ebx    # Restituisce il valore del prodotto,
    mov   $1,      %eax    # ammesso che sia abbastanza piccolo
    int   $0x80            # da poter essere rappresentato come
                           # valore di uscita.
#-----------------------------------------------------------------------
# Moltiplicazione di due numeri interi.
# f_mol (a, b) => EAX
# EAX = a * b
#
f_mol:
    mov   4(%esp), %edx    # Copia il primo parametro in EDX.
    mov   8(%esp), %ecx    # Copia il secondo parametro in ECX.
    mov   $0,      %eax    # Azzera EAX per sicurezza.
do_mol:
    cmp   $0,      %ecx    # Se il moltiplicatore è pari a zero, il ciclo
    jz    end_do_mol       # deve terminare.
    shr   $1,      %ecx    # Fa scorrere a destra il moltiplicatore e se
    jnc   end_do_somma     # l'indicatore di riporto non è attivo, salta
                           # la somma.
do_somma:
    add   %edx,    %eax    # Aggiunge il moltiplicando al risultato.
end_do_somma:    
    shl   $1,      %edx    # Fa scorrere il moltiplicando a sinistra.
    jmp   do_mol           # Torna all'inizio del ciclo di moltiplicazione.
end_do_mol:
    ret                    # Torna all'istruzione successiva alla chiamata.
; op1 * op2
;
section .data
op1:    dw      0x0007      ; Intero senza segno.
op2:    dw      0x0003      ; Intero senza segno.
;
section .text
global _start
;-----------------------------------------------------------------------
_start:
    movzx edx, word [op1]   ; Moltiplicando.
    movzx ecx, word [op2]   ; Moltiplicatore.
bp1:
    push  ecx               ; Salva il moltiplicatore nella pila.
    push  edx               ; Salva il moltiplicando nella pila.
    ;
    push  ecx               ; Inserisce il secondo parametro nella pila.
    push  edx               ; Inserisce il primo parametro nella pila.
    call  f_mol             ; Esegue la moltiplicazione: EAX = EDX * ECX
    add   esp,      4       ; Espelle il primo parametro della chiamata.
    add   esp,      4       ; Espelle il secondo parametro della chiamata.
    ;
    pop   edx               ; Recupera il moltiplicando dalla pila.
    pop   ecx               ; Recupera il moltiplicatore dalla pila.
bp2:
    mov   ebx,      eax     ; Restituisce il valore del prodotto,
    mov   eax,      1       ; ammesso che sia abbastanza piccolo
    int   0x80              ; da poter essere rappresentato come
                            ; valore di uscita.
;-----------------------------------------------------------------------
; Moltiplicazione di due numeri interi.
; f_mol (a, b) => EAX
; EAX = a * b
;
f_mol:
    mov   edx,      [esp+4] ; Copia il primo parametro in EDX.
    mov   ecx,      [esp+8] ; Copia il secondo parametro in ECX.
    mov   eax,      0       ; Azzera EAX per sicurezza.
    ;
do_mol:
    cmp   ecx,      0      ; Se il moltiplicatore è pari a zero, il ciclo
    jz    end_do_mol       ; deve terminare.
    shr   ecx,      1      ; Fa scorrere a destra il moltiplicatore e se
    jnc   end_do_somma     ; l'indicatore di riporto non è attivo, salta
                           ; la somma.
do_somma:
    add   eax,      edx    ; Aggiunge il moltiplicando al risultato.
end_do_somma:    
    shl   edx,      1      ; Fa scorrere il moltiplicando a sinistra.
    jmp   do_mol           ; Torna all'inizio del ciclo di moltiplicazione.
end_do_mol:
    ret                    ; Torna all'istruzione successiva alla chiamata.

Come si vede, rispetto alla versione precedente dello stesso programma, il risultato del prodotto, calcolato all'interno della funzione, continua a essere restituito attraverso il registro EAX, ma sarebbe stato possibile accumulare nella pila, prima della chiamata, un valore in più, da considerare poi come il risultato generato dalla funzione.

All'inizio della funzione vengono recuperati i valori che costituiscono gli argomenti della chiamata. Tale operazione viene eseguita attraverso queste istruzioni:

    mov   4(%esp), %edx    # Copia il primo parametro in EDX.
    mov   8(%esp), %ecx    # Copia il secondo parametro in ECX.
    mov   edx,      [esp+4] ; Copia il primo parametro in EDX.
    mov   ecx,      [esp+8] ; Copia il secondo parametro in ECX.

L'operando 4(%esp), ovvero [esp+4], individua l'indirizzo di memoria corrispondente al valore del registro ESP, più quattro byte.

Il registro ESP è l'indice della pila (stack pointer) che punta all'ultimo elemento presente (quello in cima alla pila). Nei sistemi Unix (compresi i sistemi GNU) la pila parte da una posizione elevata della memoria e «cresce» utilizzando indirizzi che invece decrescono. Pertanto, considerato che l'ultimo elemento della pila è l'indirizzo di ritorno, l'elemento immediatamente precedente lo si raggiunge quattro byte dopo (32 bit) e quello ancora precedente si trova otto byte dopo la posizione finale.

Figura 558.9. Situazione della pila in corrispondenza dell'etichetta f_mol.

pila

La funzione elabora il prodotto dei valori forniti come argomento e ne lascia il risultato nel registro EAX. Al ritorno, la pila si presenta come si vede nella figura successiva:

Figura 558.10. Situazione della pila immediatamente dopo la chiamata della funzione.

pila

Come si vede, occorre espellere dalla pila i parametri usati per la chiamata. Dal momento che non c'è bisogno di rileggere il loro valore, ci si limita a decrementare l'indice della pila, ovvero si incrementa il valore del registro ESP a gruppi di quattro byte alla volta.

Figura 558.11. Situazione della pila dopo l'espulsione dei parametri della chiamata.

pila

Naturalmente, considerato che la funzione non altera i valori accumulati nella pila, la chiamata potrebbe essere semplificata un po':

bp1:
    push  %ecx  # Salva ECX, inserendolo come secondo parametro nella pila.
    push  %edx  # Salva EDX, inserendolo come primo parametro nella pila.
    call  f_mol # Esegue la moltiplicazione: EAX = EDX * ECX
    pop   %edx  # Recupera EDX dalla pila.
    pop   %ecx  # Recupera ECX dalla pila.
bp2:
bp1:
    push  ecx   ; Salva ECX, inserendolo come secondo parametro nella pila.
    push  edx   ; Salva EDX, inserendolo come primo parametro nella pila.
    call  f_mol ; Esegue la moltiplicazione: EAX = EDX * ECX
    pop   edx   ; Recupera EDX dalla pila.
    pop   ecx   ; Recupera ECX dalla pila.
bp2:

558.4   Utilizzo del registro «EBP»

Perché una funzione possa gestire delle variabili «locali», ovvero tali da avere un campo di azione limitato alla funzione stessa, senza lasciare tracce al ritorno dalla chiamata, si deve usare la pila aggiungendovi altri elementi. Questo fatto complica l'accesso ai parametri della chiamata, perché durante l'esecuzione delle istruzioni della funzione, l'indice della pila può spostarsi. A questo proposito, all'inizio di una funzione, conviene conservare una copia del registro ESP in un altro registro apposito: EBP (base pointer). In pratica, l'indice contenuto in EBP dovrebbe essere sempre usato per rappresentare la posizione in cui si trova l'indirizzo di ritorno della funzione in cui ci si trova.

Viene riproposto il programma già presentato nella sezione precedente, con le semplificazioni già descritte a proposito della chiamata e con le modifiche relative all'uso del registro EBP.

# op1 * op2
#
.section .data
op1:    .byte   0x07, 0x00 # little endian = 0x0005  intero senza segno.
op2:    .byte   0x03, 0x00 # little endian = 0x0003  intero senza segno.
#
.section .text
.globl _start
#-----------------------------------------------------------------------
_start:
    movzx op1,     %edx    # Moltiplicando.
    movzx op2,     %ecx    # Moltiplicatore.
    mov   $0,      %eax    # Risultato.
bp1:
    push  %ebp             # Salva il registro EBP prima della chiamata.
    push  %ecx             # Inserisce il moltiplicando nella pila.
    push  %edx             # Inserisce il moltiplicatore nella pila.
    call  f_mol            # Esegue la moltiplicazione: EAX = EDX * ECX
    pop   %edx             # Recupera il moltiplicando dalla pila.
    pop   %ecx             # Recupera il moltiplicatore dalla pila.
    pop   %ebp             # Recupera il registro EBP dopo la chiamata.
bp2:
    mov   %eax,    %ebx    # Restituisce il valore del prodotto,
    mov   $1,      %eax    # ammesso che sia abbastanza piccolo
    int   $0x80            # da poter essere rappresentato come
                           # valore di uscita.
#-----------------------------------------------------------------------
# Moltiplicazione di due numeri interi.
# f_mol (a, b) => EAX
# EAX = a * b
#
f_mol:
    mov   %esp,    %ebp    # Copia ESP in EBP.
    mov   4(%ebp), %edx    # Copia il primo parametro in EDX.
    mov   8(%ebp), %ecx    # Copia il secondo parametro in ECX.
    mov   $0,      %eax    # Azzera EAX per sicurezza.
    #
do_mol:
    cmp   $0,      %ecx    # Se il moltiplicatore è pari a zero, il ciclo
    jz    end_do_mol       # deve terminare.
    shr   $1,      %ecx    # Fa scorrere a destra il moltiplicatore e se
    jnc   end_do_somma     # l'indicatore di riporto non è attivo, salta
                           # la somma.
do_somma:
    add   %edx,    %eax    # Aggiunge il moltiplicando al risultato.
end_do_somma:    
    shl   $1,      %edx    # Fa scorrere il moltiplicando a sinistra.
    jmp   do_mol           # Torna all'inizio del ciclo di moltiplicazione.
end_do_mol:
    mov   %ebp,    %esp    # Ripristina ESP, espellendo le variabili locali.
    ret                    # Torna all'istruzione successiva alla chiamata.
; op1 * op2
;
section .data
op1:    dw      0x0007      ; Intero senza segno.
op2:    dw      0x0003      ; Intero senza segno.
;
section .text
global _start
;-----------------------------------------------------------------------
_start:
    movzx edx, word [op1]   ; Moltiplicando.
    movzx ecx, word [op2]   ; Moltiplicatore.
bp1:
    push  ebp               ; Salva il registro EBP prima della chiamata.
    push  ecx               ; Inserisce il moltiplicando nella pila.
    push  edx               ; Inserisce il moltiplicatore nella pila.
    call  f_mol             ; Esegue la moltiplicazione: EAX = EDX * ECX
    pop   edx               ; Recupera il moltiplicando dalla pila.
    pop   ecx               ; Recupera il moltiplicatore dalla pila.
    pop   ebp               ; Recupera il registro EBP dopo la chiamata.
bp2:
    mov   ebx,      eax     ; Restituisce il valore del prodotto,
    mov   eax,      1       ; ammesso che sia abbastanza piccolo
    int   0x80              ; da poter essere rappresentato come
                            ; valore di uscita.
;-----------------------------------------------------------------------
; Moltiplicazione di due numeri interi.
; f_mol (a, b) => EAX
; EAX = a * b
;
f_mol:
    mov   ebp,      esp     ; Copia ESP in EBP.
    mov   edx,      [ebp+4] ; Copia il primo parametro in EDX.
    mov   ecx,      [ebp+8] ; Copia il secondo parametro in ECX.
    mov   eax,      0       ; Azzera EAX per sicurezza.
    ;
do_mol:
    cmp   ecx,      0       ; Se il moltiplicatore è pari a zero, il ciclo
    jz    end_do_mol        ; deve terminare.
    shr   ecx,      1       ; Fa scorrere a destra il moltiplicatore e se
    jnc   end_do_somma      ; l'indicatore di riporto non è attivo, salta
                            ; la somma.
do_somma:
    add   eax,      edx     ; Aggiunge il moltiplicando al risultato.
end_do_somma:    
    shl   edx,      1       ; Fa scorrere il moltiplicando a sinistra.
    jmp   do_mol            ; Torna all'inizio del ciclo di moltiplicazione.
end_do_mol:
    mov   esp,      ebp     ; Ripristina ESP, espellendo le variabili locali.
    ret                     ; Torna all'istruzione successiva alla chiamata.

Nei listati appena mostrati si vede che EBP viene salvato prima della chiamata e ripristinato successivamente. Esiste però un altro modo, più diffuso, per cui il registro EBP va salvato nella pila all'inizio della funzione, con le conseguenze che ciò comporta:

...
bp1:
    push  %ecx             # Inserisce il moltiplicando nella pila.
    push  %edx             # Inserisce il moltiplicatore nella pila.
    call  f_mol            # Esegue la moltiplicazione: EAX = EDX * ECX
    pop   %edx             # Recupera il moltiplicando dalla pila.
    pop   %ecx             # Recupera il moltiplicatore dalla pila.
bp2:
...
f_mol:
    push   %ebp            # Salva il registro EBP.
bp3:
    mov   %esp,     %ebp   # Copia ESP in EBP.
    mov   8(%ebp),  %edx   # Copia il primo parametro in EDX.
    mov   12(%ebp), %ecx   # Copia il secondo parametro in ECX.
    mov   $0,       %eax   # Azzera EAX per sicurezza.
...
end_do_mol:
    mov   %ebp,     %esp   # Ripristina ESP, espellendo le variabili locali.
    pop   %ebp             # Riporta il registro EBP allo stato precedente.
    ret                    # Torna all'istruzione successiva alla chiamata.
...
bp1:
    push  ecx               ; Inserisce il moltiplicando nella pila.
    push  edx               ; Inserisce il moltiplicatore nella pila.
    call  f_mol             ; Esegue la moltiplicazione: EAX = EDX * ECX
    pop   edx               ; Recupera il moltiplicando dalla pila.
    pop   ecx               ; Recupera il moltiplicatore dalla pila.
bp2:
...
f_mol:
    push  ebp                ; Salva il registro EBP.
bp3:
    mov   ebp,      esp      ; Copia ESP in EBP.
    mov   edx,      [ebp+8]  ; Copia il primo parametro in EDX.
    mov   ecx,      [ebp+12] ; Copia il secondo parametro in ECX.
    mov   eax,      0        ; Azzera EAX per sicurezza.
...
end_do_mol:
    mov   esp,      ebp      ; Ripristina ESP, espellendo le variabili locali.
    pop   ebp                ; Ripristina il registro EBP.
    ret                      ; Torna all'istruzione successiva alla chiamata.

In tal caso, in corrispondenza dell'etichetta bp3, la pila ha i contenuti che sono schematizzati nella figura successiva.

Figura 558.18. Situazione della pila in corrispondenza dell'etichetta bp3.

pila

558.5   Allocazione dello spazio per le variabili locali e preservazione dei registri

Come accennato nella sezione precedente, una volta salvato il valore di EBP nella pila e assegnatovi il valore di ESP, le variabili locali possono essere accumulate nella pila quando servono. Tuttavia, di solito si preferisce definire subito lo spazio utilizzato dalle variabili locali. Per esempio, supponendo di averne due, la pila potrebbe mostrarsi come nella figura successiva.

Figura 558.19. Situazione della pila, all'interno di una funzione con due variabili locali.

pila

L'insieme degli elementi della pila, costituito dai parametri della funzione fino alle variabili locali, è noto come stack frame. Spesso, le «convenzioni di chiamata» prescrivono che siano le funzioni stesse a preservare lo stato precedente dei registri, pertanto, di solito, dopo la definizione dello spazio usato dalle variabili locali, si salvano nella pila anche tutti i registri principali, eventualmente con l'aiuto dell'istruzione PUSHA. I listati successivi mostrano una modifica ulteriore del programma già utilizzato nelle sezioni precedenti.

# op1 * op2
#
.section .data
op1:    .byte   0x07, 0x00    # little endian = 0x0005  intero senza segno.
op2:    .byte   0x03, 0x00    # little endian = 0x0003  intero senza segno.
#
.section .text
.globl _start
#-----------------------------------------------------------------------
_start:
    movzx op1,     %edx    # Moltiplicando.
    movzx op2,     %ecx    # Moltiplicatore.
    mov   $0,      %eax    # Risultato.
bp1:
    push  %ecx             # Inserisce il moltiplicando nella pila.
    push  %edx             # Inserisce il moltiplicatore nella pila.
    call  f_mol            # Esegue la moltiplicazione: EAX = EDX * ECX
    add   $8, %esp         # Espelle i parametri di chiamata.
bp2:
    mov   %eax,    %ebx    # Restituisce il valore del prodotto,
    mov   $1,      %eax    # ammesso che sia abbastanza piccolo
    int   $0x80            # da poter essere rappresentato come
                           # valore di uscita.
#-----------------------------------------------------------------------
# Moltiplicazione di due numeri interi.
# f_mol (a, b) => EAX
# EAX = a * b
#
f_mol:
    push  %ebp             # Salva il registro EBP.
    mov   %esp,    %ebp    # Copia ESP in EBP.
    sub   $4,      %esp    # Crea lo spazio per una variabile locale.
    pusha                  # Salva i registri principali.
    #
    mov   8(%ebp), %edx    # Copia il primo parametro in EDX.
    mov   12(%ebp), %ecx   # Copia il secondo parametro in ECX.
    mov   $0,      %eax    # Azzera EAX per sicurezza.
    #
do_mol:
    cmp   $0,      %ecx    # Se il moltiplicatore è pari a zero, il ciclo
    jz    end_do_mol       # deve terminare.
    shr   $1,      %ecx    # Fa scorrere a destra il moltiplicatore e se
    jnc   end_do_somma     # l'indicatore di riporto non è attivo, salta
                           # la somma.
do_somma:
    add   %edx,    %eax    # Aggiunge il moltiplicando al risultato.
end_do_somma:    
    shl   $1,      %edx    # Fa scorrere il moltiplicando a sinistra.
    jmp   do_mol           # Torna all'inizio del ciclo di moltiplicazione.
end_do_mol:
    mov   %eax,   -4(%ebp) # Copia EAX nella variabile locale prevista.
    popa                   # Ripristina i registri principali.
    mov  -4(%ebp), %eax    # Rimette a posto il valore di EAX che deve
                           # essere restituito.
    mov   %ebp,    %esp    # Ripristina ESP, espellendo le variabili locali.
    pop   %ebp             # Riporta il registro EBP allo stato precedente.
    ret                    # Torna all'istruzione successiva alla chiamata.
; op1 * op2
;
section .data
op1:    dw      0x0007      ; Intero senza segno.
op2:    dw      0x0003      ; Intero senza segno.
;
section .text
global _start
;-----------------------------------------------------------------------
_start:
    movzx edx, word [op1]   ; Moltiplicando.
    movzx ecx, word [op2]   ; Moltiplicatore.
bp1:
    push  ecx               ; Inserisce il moltiplicando nella pila.
    push  edx               ; Inserisce il moltiplicatore nella pila.
    call  f_mol             ; Esegue la moltiplicazione: EAX = EDX * ECX
    add esp,        8       ; Espelle i parametri della chiamata.
bp2:
    mov   ebx,      eax     ; Restituisce il valore del prodotto,
    mov   eax,      1       ; ammesso che sia abbastanza piccolo
    int   0x80              ; da poter essere rappresentato come
                            ; valore di uscita.
;-----------------------------------------------------------------------
; Moltiplicazione di due numeri interi.
; f_mol (a, b) => EAX
; EAX = a * b
;
f_mol:
    push  ebp                ; Salva il registro EBP.
    mov   ebp,      esp      ; Copia ESP in EBP.
    sub   esp,      4        ; Crea lo spazio per una variabile locale.
    pusha                    ; Salva i registri principali.
    ;
    mov   edx,      [ebp+8]  ; Copia il primo parametro in EDX.
    mov   ecx,      [ebp+12] ; Copia il secondo parametro in ECX.
    mov   eax,      0        ; Azzera EAX per sicurezza.
    ;
do_mol:
    cmp   ecx,      0        ; Se il moltiplicatore è pari a zero, il ciclo
    jz    end_do_mol         ; deve terminare.
    shr   ecx,      1        ; Fa scorrere a destra il moltiplicatore e se
    jnc   end_do_somma       ; l'indicatore di riporto non è attivo, salta
                             ; la somma.
do_somma:
    add   eax,      edx      ; Aggiunge il moltiplicando al risultato.
end_do_somma:    
    shl   edx,      1        ; Fa scorrere il moltiplicando a sinistra.
    jmp   do_mol             ; Torna all'inizio del ciclo di moltiplicazione.
end_do_mol:
    mov   [ebp-4],  eax      ; Copia EAX nella variabile locale prevista.
    popa                     ; Ripristina i registri principali.
    mov   eax,      [ebp-4]  ; Rimette a posto il valore di EAX che deve
                             ; essere restituito.
    mov   esp,      ebp      ; Ripristina ESP, espellendo le variabili locali.
    pop   ebp                ; Ripristina il registro EBP.
    ret                      ; Torna all'istruzione successiva alla chiamata.

Come si vede, avendo usato la coppia di istruzioni PUSHA, POPA, alla fine occorre prendersi cura del risultato che è già disponibile nel registro EAX: infatti viene salvato prima nello spazio riservato per la variabile locale, quindi vengono ripristinati tutti i registri (tutti eccetto ESP) e ancora viene ripristinato EAX, che deve trasmettere il valore alla chiamata.

A questo punto occorre sapere che le istruzioni seguenti possono essere sostituite dall'istruzione ENTER, dove n rappresenta una quantità di byte:

    push  %ebp
    mov   %esp, %ebp
    sub   $n, %esp
    push  ebp
    mov   ebp, esp
    sub   esp, n

Per la precisione, il rimpiazzo avviene come nei due brani seguenti: si osservi che in questo caso, gli attributi di ENTER non vengono invertiti nelle due sintassi.

    enter $n, $0
    enter n, 0

Le istruzioni che invece va a rimpiazzare LEAVE sono quelle seguenti:

    mov   %ebp, %esp
    pop   %ebp
    mov   %esp, %ebp
    pop   %ebp

Logicamente, LEAVE non richiede operandi, quindi si usa nello stesso modo nelle due sintassi:

    leave

558.6   Convenzioni di chiamata

Da quanto descritto si comprende che si possono usare diversi modi per chiamare una funzione, ma anche se esistono delle modalità equivalenti, occorre definire una convenzione. In generale, per chi scrive programmi in un sistema compatibile con la tradizione Unix, la cosa migliore è uniformarsi alle convenzioni di chiamata del linguaggio C (precisamente servono quelle usate dal proprio compilatore), in modo da poter mettere assieme programmi scritti in parte in linguaggio assemblatore e in parte anche in C. Di solito, le regole per chi scrive funzioni in linguaggio assemblatore sono sostanzialmente quelle dell'ultimo esempio mostrato nella sezione precedente:

Nel caso si vogliano utilizzare funzioni scritte in linguaggio C, all'interno di un programma scritto in linguaggio assemblatore, occorre verificare quali registri le funzioni scritte in C non preservano (oltre alla coppia EDX:EAX, usata per restituire il risultato della chiamata). In generale, si può considerare che le funzioni scritte in linguaggio C potrebbero alterare i registri EAX, ECX e EDX. Se però si vuole avere la certezza assoluta sul contenuto dei registri dopo la chiamata di una funzione realizzata con un altro linguaggio, conviene organizzarsi salvando tutti quelli che si stanno utilizzando prima della chiamata e ripristinandoli subito dopo, come in parte è stato mostrato in questo capitolo.

558.7   Nota sugli array «locali»

Generalmente, un array viene gestito attraverso uno spazio di memoria condiviso da tutto il programma, dove le funzioni che devono manipolarlo ricevono l'indirizzo di questo, tra i parametri della chiamata. Tuttavia, nel caso si volesse gestire, all'interno di una funzione, un array locale, il cui contenuto viene abbandonato alla conclusione della stessa, l'unico modo per ottenere ciò è attraverso la pila dei dati. In pratica, come per le variabili locali scalari, andrebbe riservato un certo spazio aumentando la dimensione della pila in modo adeguato, per poi scandire tale spazio con indici appropriati.


1) L'indirizzo di memoria da raggiungere con l'istruzione CALL, può essere fornito in modo «immediato», attraverso l'indicazione di un'etichetta, oppure con un registro o con un indirizzo di memoria. Nell'ipotesi di un registro o di un indirizzo di memoria, si intende che il contenuto del registro o della variabile in memoria vadano considerati come l'indirizzo di destinazione della chiamata.

2) Nel caso di un valore in virgola mobile, il risultato potrebbe essere atteso dal registro ST0, ma la gestione della virgola mobile non viene affrontata in questi capitoli.


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 funzioni.htm

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

Valid ISO-HTML!

CSS validator!

Gjlg Metamotore e Web Directory