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
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
'+ = 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
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