2015-06-18 11 views
7

Come faccio a ingannare Linux nel pensare che una lettura/scrittura della memoria abbia avuto successo? Sto scrivendo una libreria C++ in modo tale che tutte le letture/scritture vengano reindirizzate e gestite in modo trasparente all'utente finale. Ogni volta che una variabile viene scritta o letta da essa, la libreria dovrà catturare quella richiesta e sparare a una simulazione hardware che gestirà i dati da lì.Come intrappolare le letture e le scritture di memoria usando sigsegv?

Nota che la mia biblioteca è dipendente dalla piattaforma on:

Linux ubuntu 3.16.0-39-generiC# 53 ~ 14.04.1-Ubuntu SMP x86_64 GNU/Linux

gcc (Ubuntu 4.8. 2-19ubuntu1) 4.8.2

attuale approccio: SIGSEGV cattura e l'incremento REG_RIP

mio cur L'approccio rent comporta l'acquisizione di un'area di memoria utilizzando mmap() e l'interruzione dell'accesso utilizzando mprotect(). Ho un gestore SIGSEGV per ottenere le informazioni contenenti l'indirizzo di memoria, esportare la lettura/scrittura altrove, quindi incrementare il contesto REG_RIP.

void handle_sigsegv(int code, siginfo_t *info, void *ctx) 
{ 
    void *addr = info->si_addr; 
    ucontext_t *u = (ucontext_t *)ctx; 
    int err = u->uc_mcontext.gregs[REG_ERR]; 
    bool is_write = (err & 0x2); 
    // send data read/write to simulation... 
    // then continue execution of program by incrementing RIP 
    u->uc_mcontext.gregs[REG_RIP] += 6; 
} 

Questo funziona per i casi molto semplici, come ad esempio:

int *num_ptr = (int *)nullptr; 
*num_ptr = 10;       // write segfault 

Ma per qualsiasi cosa, anche un po 'più complessa, ricevo un SIGABRT:

30729 istruzioni Illegale (core dumped) ./$target

Utilizzo di mprotect() all'interno Gestore SIGSEGV

Se non si dovesse aumentare REG_RIP, handle_sigsegv() verrà chiamato più e più volte dal kernel finché l'area di memoria non sarà disponibile per la lettura o la scrittura. Potrei correre mprotect() per tale indirizzo specifico, ma che ha molteplici avvertimenti:

  • successivo accesso di memoria non si innescherà un SIGSEGV a causa della regione di memoria ora avere la capacità PROT_WRITE. Ho provato a creare un thread che contrassegna continuamente la regione come PROT_NONE, ma che non elude il punto successivo:
  • mprotect(), alla fine della giornata, eseguirà la lettura o la scrittura in memoria, invalidando il caso di utilizzo di la mia biblioteca.

Scrivere un driver

ho anche tentato di scrivere un modulo dispositivo in modo che la libreria può chiamare mmap() sul dispositivo char, dove il conducente si occuperà della legge e scrive da lì. Questo ha senso in teoria, ma non sono stato in grado (o non ho la consapevolezza) di catturare ogni carico/memorizzare i problemi del processore sul dispositivo. Ho tentato di sovrascrivere la struttura mappata vm_operations_struct e/o la struttura address_space_operations dell'inode, ma richiamerò solo le letture/scritture quando una pagina è in errore o una pagina viene scaricata nell'archivio di backup.

Forse potrei usare mmap() e mprotect(), come spiegato sopra, il dispositivo che scrive i dati da nessuna parte (simile a /dev/null), quindi avere un processo che riconosce la legge/scrive e percorsi i dati da lì (?).

Utilizzare syscall() e fornire una funzione complesso restauratrice

Quanto segue è stato tirato dal segvcatch progetto che converte segfaults in eccezioni.

#define RESTORE(name, syscall) RESTORE2(name, syscall) 
#define RESTORE2(name, syscall)\ 
asm(\ 
    ".text\n"\ 
    ".byte 0\n"\ 
    ".align 16\n"\ 
    "__" #name ":\n"\ 
    " movq $" #syscall ", %rax\n"\ 
    " syscall\n"\ 
); 
RESTORE(restore_rt, __NR_rt_sigreturn) 
void restore_rt(void) asm("__restore_rt") __attribute__ 
((visibility("hidden"))); 

extern "C" { 
    struct kernel_sigaction { 
     void (*k_sa_sigaction)(int, siginfo_t *, void *); 
     unsigned long k_sa_flags; 
     void (*k_sa_restorer)(void); 
     sigset_t k_sa_mask; 
    }; 
} 

// then within main ... 
struct kernel_sigaction act; 
act.k_sa_sigaction = handle_sigegv; 
sigemptyset(&act.k_sa_mask); 
act.k_sa_flags = SA_SIGINFO|0x4000000; 
act.k_sa_restorer = restore_rt; 
syscall(SYS_rt_sigaction, SIGSEGV, &act, NULL, _NSIG/8); 

Ma questo finisce per funzionare non è diverso da una configurazione regolare sigaction(). Se non si imposta la funzione di ripristino, il gestore di segnale non viene chiamato più di una volta, anche se la regione di memoria non è ancora disponibile. Forse c'è qualche altro trucco che potrei fare con il segnale del kernel qui.


Anche in questo caso, l'intero obiettivo della biblioteca è quello di gestire in modo trasparente legge e scrive la memoria. Forse c'è un modo molto migliore di fare le cose, magari con ptrace() o anche aggiornare il codice del kernel che genera il segnale segfault, ma la parte importante è che il codice dell'utente finale non richiede modifiche. Ho visto esempi utilizzando setjmp() e longjmp() per continuare dopo un segfault, ma ciò richiederebbe l'aggiunta di tali chiamate ad ogni accesso alla memoria. Lo stesso vale per la conversione di un segfault in un try/catch.


segvcatch project

+0

Perché '+ = 6'? Secondo, una lettura (diciamo) imposta un valore di registro. Non vedo nulla a riguardo nel tuo codice, lo stai facendo? Dici di 'inviare i dati di lettura/scrittura alla simulazione', ma nulla su come recuperare i dati, è quello previsto? – Yakk

+0

'+ = 6' sembra essere la larghezza dell'istruzione per x86_64, almeno per il caso più semplice. Sfortunatamente non posso fornire la fonte di questa conoscenza, ma sembra funzionare. Quando dico "invia dati lettura/scrittura ...", voglio dire che emetto un'istruzione personalizzata (con i dati allegati) alla mia simulazione hardware (cioè carico/archivio). Stai dicendo che devo individuare il registro del processore che ha bisogno dei dati e caricarlo? Questo avrebbe senso ... – Will

+0

Avvolgi il tuo hardware in un livello di astrazione e fai solo chiamate al livello di astrazione. Come ulteriore vantaggio quando porti il ​​software sul nuovo hardware, devi solo aggiornare il livello di astrazione. (E gestisci eventuali bug nel tuo codice esposti da nuovi tempi e probabilmente un nuovo compilatore) – user4581301

risposta

3

È possibile utilizzare mprotect ed evitare il primo problema che si nota anche con il gestore SIGSEGV impostato il flag T nelle bandiere registrati. Quindi, si aggiunge un gestore SIGTRAP che ripristina la memoria mprotected e cancella il flag T.

Il flag T fa in modo che il processore passi in un'unica fase, quindi quando il gestore SEGV restituisce eseguirà quella singola istruzione, quindi immediatamente TRAP.

Questo ti lascia ancora il tuo secondo problema: l'istruzione di lettura/scrittura si verificherà effettivamente. Potresti essere in grado di aggirare il problema modificando attentamente la memoria prima e/o dopo l'istruzione nei due gestori di segnale ...

+0

Grazie per le informazioni relative all'impostazione del flag trap. Avrei dovuto prestare maggiore attenzione a questa domanda [collegamento] (http://stackoverflow.com/questions/21068714/trap-all-accesses-to-an-address-range-linux). Forse potrei utilizzare la sequenza di segnali SIGSEGV + SIGTRAP con una piccola area di memoria che funziona come una sorta di scambio per raggiungere la mia funzionalità desiderata. – Will

+0

Sono stato in grado di creare un emulatore "cache del processore" che funziona a livello di granularità dei blocchi utilizzando SIGSEGV, SIGTRAP e mprotect. Sfortunatamente intrappolare ogni lettura/scrittura in un indirizzo provoca enormi inconvenienti in termini di prestazioni, ma l'implementazione del concetto è valida. Grazie ancora, e mi scuso per l'accettazione ritardata. – Will