Condividi tramite


Prologo ed epilogo x64

Ogni funzione che alloca lo spazio dello stack, chiama altre funzioni, salva registri non volatile o usa la gestione delle eccezioni deve avere un prologo i cui limiti di indirizzo sono descritti nei dati di rimozione associati alla rispettiva voce della tabella delle funzioni. Per altre informazioni, vedere Gestione delle eccezioni x64. Il prologo salva i registri degli argomenti negli indirizzi home, se necessario, esegue il push di registri non volatile nello stack, alloca la parte fissa dello stack per le variabili locali e temporaneamente e, facoltativamente, stabilisce un puntatore a frame. I dati di rimozione associati devono descrivere l'azione del prologo e devono fornire le informazioni necessarie per annullare l'effetto del codice prologo.

Se l'allocazione fissa nello stack è più di una pagina ,ovvero maggiore di 4096 byte, è possibile che l'allocazione dello stack possa estendersi su più di una pagina di memoria virtuale e, di conseguenza, l'allocazione deve essere controllata prima dell'allocazione. A questo scopo viene fornita una routine speciale chiamabile dal prologo e che non elimina alcun registro di argomenti.

Il metodo preferito per salvare i registri non volatile consiste nello spostarli nello stack prima dell'allocazione dello stack fisso. Se l'allocazione dello stack fisso viene eseguita prima del salvataggio dei registri non volatile, probabilmente è necessario uno spostamento a 32 bit per risolvere l'area di registrazione salvata. (Secondo quanto riportato, le push dei registri sono veloci quanto le mosse e dovrebbero rimanere così per il prossimo futuro nonostante la dipendenza implicita tra push). I registri non volatile possono essere salvati in qualsiasi ordine. Tuttavia, il primo uso di un registro non volatile nel prologo deve essere per salvarlo.

Codice di prologo

Il codice per un prologo tipico potrebbe essere:

    mov    [RSP + 8], RCX
    push   R15
    push   R14
    push   R13
    sub    RSP, fixed-allocation-size
    lea    R13, 128[RSP]
    ...

Questo prologo archivia il registro degli argomenti RCX nella posizione iniziale, salva i registri non volatile R13-R15, alloca la parte fissa del frame dello stack e stabilisce un puntatore frame che punta 128 byte nell'area di allocazione fissa. L'uso di un offset consente l'indirizzamento di più dell'area di allocazione fissa con offset a un byte.

Se la dimensione di allocazione fissa è maggiore o uguale a una pagina di memoria, è necessario chiamare una funzione helper prima di modificare RSP. Questo helper, __chkstk, esegue il probe dell'intervallo di stack allocato da allocare per assicurarsi che lo stack venga esteso correttamente. In tal caso, l'esempio di prologo precedente sarebbe invece:

    mov    [RSP + 8], RCX
    push   R15
    push   R14
    push   R13
    mov    RAX,  fixed-allocation-size
    call   __chkstk
    sub    RSP, RAX
    lea    R13, 128[RSP]
    ...

L'helper __chkstk non modificherà alcun registro diverso da R10, R11 e i codici di condizione. In particolare, restituirà RAX invariato e lascerà invariati tutti i registri non volatile e i registri di passaggio degli argomenti non modificati.

Codice epilogo

Il codice epilogo esiste a ogni uscita di una funzione. Mentre in genere esiste un solo prologo, possono esserci molti epilogi. Il codice epilogo taglia lo stack alle dimensioni di allocazione fisse (se necessario), dealloca l'allocazione dello stack fisso, ripristina i registri non volatile rimuovendo i valori salvati dallo stack e restituisce.

Il codice epilogo deve seguire un set rigoroso di regole per il codice di rimozione per rimuovere in modo affidabile le eccezioni e gli interrupt. Queste regole riducono la quantità di dati di rimozione necessari, perché non sono necessari dati aggiuntivi per descrivere ogni epilogo. Il codice di rimozione può invece determinare che un epilogo viene eseguito analizzando in avanti un flusso di codice per identificare un epilogo.

Se nella funzione non viene usato alcun puntatore a fotogrammi, l'epilogo deve prima deallocare la parte fissa dello stack, i registri non volatile vengono visualizzati e il controllo viene restituito alla funzione chiamante. ad esempio:

    add      RSP, fixed-allocation-size
    pop      R13
    pop      R14
    pop      R15
    ret

Se nella funzione viene usato un puntatore a fotogrammi, è necessario tagliare lo stack all'allocazione fissa prima dell'esecuzione dell'epilogo. Questa azione tecnicamente non fa parte dell'epilogo. Ad esempio, l'epilogo seguente può essere usato per annullare il prologo usato in precedenza:

    lea      RSP, -128[R13]
    ; epilogue proper starts here
    add      RSP, fixed-allocation-size
    pop      R13
    pop      R14
    pop      R15
    ret

In pratica, quando viene usato un puntatore a fotogrammi, non c'è alcun motivo valido per regolare RSP in due passaggi, quindi verrà usato l'epilogo seguente:

    lea      RSP, fixed-allocation-size - 128[R13]
    pop      R13
    pop      R14
    pop      R15
    ret

Queste forme sono le uniche per un epilogo. Deve essere costituito da un add RSP,constant oggetto o lea RSP,constant[FPReg], seguito da una serie di zero o più pop a 8 byte e da un return oggetto o .jmp Solo un subset di jmp istruzioni è consentito nell'epilogo. Il subset è esclusivamente la classe di jmp istruzioni con riferimenti alla memoria ModRM in cui il valore del campo ModRM è 00. L'uso delle jmp istruzioni nell'epilogo con il valore del campo ModRM mod 01 o 10 non è consentito. Per altre informazioni sui riferimenti a ModRM consentiti, vedi tabella A-15 in AMD x86-64 Architecture Manual Volume 3: General Purpose and System Instructions.) Nessun altro codice può essere visualizzato. In particolare, non è possibile pianificare nulla all'interno di un epilogo, incluso il caricamento di un valore restituito.

Quando non viene usato un puntatore a frame, l'epilogo deve usare add RSP,constant per deallocare la parte fissa dello stack. Potrebbe non essere invece utilizzato lea RSP,constant[RSP] . Questa restrizione esiste in modo che il codice di rimozione abbia meno modelli da riconoscere durante la ricerca di epilogi.

Seguendo queste regole, il codice di rimozione consente di determinare che un epilogo è attualmente in esecuzione e di simulare l'esecuzione del resto dell'epilogo per consentire la ricreazione del contesto della funzione chiamante.

Vedi anche

Convenzioni del software x64