2015-12-11 19 views
8

Sto lavorando a un'implementazione personalizzata setjmp/longjmp per sistemi x86-64 che salva l'intero contesto della CPU (vale a dire, tutto xmm, stack di fpu, ecc., Non solo registri callee-save). Questo è scritto direttamente in assemblea.x86_64: forzare gcc a passare argomenti nello stack

Il codice funziona bene in esempi minimi (quando lo si chiama direttamente da un'origine assembly). Il problema sorge quando lo si utilizza con il codice C, a causa del modo in cui i parametri vengono passati alle funzioni homebrew setjmp/. In effetti, SysV ABI per i sistemi x64_64 impone che gli argomenti debbano essere trasmessi tramite registri (se sono al massimo 6). La firma delle mie funzioni sono:

long long set_jmp(exec_context_t *env);
__attribute__ ((__noreturn__)) void long_jmp(exec_context_t *env, long long val);

Naturalmente, questo non può funzionare così com'è. Infatti, quando inserisco set_jmp, rdi e rsi sono già stati ignorati per mantenere un puntatore a env e val. Lo stesso vale per long_jmp rispetto a rdi.

C'è un modo per forzare GCC, ad es. facendo affidamento su qualche attributo, per forzare l'argomento passando attraverso lo stack? Questo sarebbe molto più elegante dell'involucro di set_jmp e long_jmp con alcune definizioni che spingono manualmente i registri danneggiati nello stack, in modo da recuperarli in un secondo momento.

+1

Ciò interromperà il PCS/ABI. Ti stai avvicinando dal lato sbagliato. Il tuo codice assembler deve seguire l'ABI. La cosa migliore è usare le funzioni C con inline-assembler direttamente o come wrapper per il tuo codice reale. In questo modo puoi solo specificare quale registro/memoria il codice clobbre e lasciare il salvataggio/ripristino in gcc. – Olaf

+2

'setjmp' non ha bisogno di salvare' rdi' e 'rsi'-come dici tu, sono sbavati dentro' setjmp' quindi perché dovrebbero essere salvati? Infatti, solo i registri contrassegnati come "salvati con il callee" (cioè rbp, ebx, r12, r13, r14, r15 e naturalmente rsp) devono essere preservati. – fuz

+0

Sono d'accordo che non sto rispettando l'ABI, ma c'è una ragione per questo. Tutto funziona bene con 'setjmp' e' longjmp' perché ciò che non viene salvato da 'setjmp' viene effettivamente salvato dal chiamante, nel caso sia necessario, poiché sono registri di salvataggio del chiamante. Nella mia applicazione, ho un'interazione con il Kernel Linux che, su un dato interrupt, restituisce il controllo a una parte diversa del codice a livello utente, che, a sua volta, chiama 'setjmp'. Con questa costruzione, il codice originariamente in esecuzione non sa che viene chiamato 'setjmp', e quindi i registri di salvataggio del chiamante dovrebbero essere salvati. – ilpelle

risposta

2

È possibile evitare di sovrascrivere i registri richiamando la funzione mediante l'assemblaggio in linea.

#include <stdio.h> 

static void foo(void) 
{ 
     int i; 
     asm volatile ("mov 16(%%rbp), %0" : "=g" (i)); 
     printf("%d\n", i); 
} 

#define foo(x) ({ int _i = (x); \ 
     asm ("push %0\ncall %P1\nadd $8, %%rsp\n" : : "g"(_i), "i"(foo)); }) 

int main(int argc, char *argv[]) 
{ 
     foo(argc-1); 
     return 0; 
} 

Qui, un numero intero viene inserito nello stack e viene richiamata la funzione foo. foo rende il valore disponibile nella sua variabile locale i. Dopo il ritorno, il puntatore dello stack viene regolato nuovamente al suo valore originale.

+1

Questo ovviamente si romperà orribilmente se la funzione di chiamata 'main()' non sta usando '% rbp' come un puntatore del frame, ma lo usiamo come un registro intero in più, o non lo usiamo affatto per evitare il sovraccarico di push/pop. Quest'ultimo è ciò che realmente accade per questo codice se si compila con '-O2'. –

+0

@NateEldredge: è solo "foo" che è mal progettato, ma è solo un segnaposto per il suo esempio. La macro per generare una "chiamata" ABI personalizzata sembra buona. L'OP userebbe un'implementazione personalizzata di 'setjmp' scritta a mano in ASM che non renderebbe nessuno di questi presupposti. Comincerebbe spingendo alcuni registri per ottenere dei reg di scratch, quindi salvare lo stato di ogni chiamante. IDK se 'xsave' funziona bene dallo spazio utente. –