Comment piéger les lectures et écritures de mémoire en utilisant sigsegv?

Comment est-ce que je trompe Linux en pensant qu’une lecture / écriture de mémoire était réussie? J’écris une bibliothèque C ++ de sorte que toutes les lectures / écritures soient redirigées et gérées de manière transparente pour l’utilisateur final. A chaque fois qu’une variable est écrite ou lue, la bibliothèque devra intercepter cette requête et la lancer dans une simulation matérielle qui traitera les données à partir de là.

Notez que ma bibliothèque dépend de la plate-forme:

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

Approche actuelle: intercepter SIGSEGV et incrémenter REG_RIP

Mon approche actuelle consiste à obtenir une région mémoire à l’aide de mmap() et à fermer l’access à l’aide de mprotect() . J’ai un gestionnaire SIGSEGV pour obtenir les informations contenant l’adresse mémoire, exporter la lecture / écriture ailleurs, puis incrémenter le contexte 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; } 

Cela fonctionne pour des cas très simples, tels que:

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

Mais pour quelque chose encore un peu plus complexe, je reçois un SIGABRT:

30729 Instruction illégale (core dumped) ./$target

Utilisation de mprotect () dans le gestionnaire SIGSEGV

Si je ne devais pas incrémenter REG_RIP, handle_sigsegv() sera appelé encore et encore par le kernel jusqu’à ce que la région de mémoire devienne disponible pour la lecture ou l’écriture. Je pourrais exécuter mprotect() pour cette adresse spécifique, mais cela comporte plusieurs inconvénients:

  • L’access ultérieur à la mémoire ne déclenchera pas un SIGSEGV en raison de la capacité PROT_WRITE de la région de mémoire. J’ai essayé de créer un thread qui marque continuellement la région comme PROT_NONE, mais cela n’échappe pas au point suivant:
  • mprotect() , à la fin de la journée, effectuera la lecture ou l’écriture en mémoire, ce qui invalidera le cas d’utilisation de ma bibliothèque.

Ecrire un pilote de périphérique

J’ai également tenté d’écrire un module de périphérique tel que la bibliothèque puisse appeler mmap() sur le périphérique de char, où le pilote gérera les lectures et les écritures. Cela a du sens en théorie, mais je n’ai pas été capable (ou n’ai pas la connaissance) d’attraper chaque chargement / stockage des problèmes du processeur sur le périphérique. J’ai tenté de remplacer la vm_operations_struct mapped vm_operations_struct et / ou address_space_operations l’inode, mais cela ne fera vm_operations_struct lectures / écritures lorsqu’une page est défaillante ou qu’une page est vidée dans le magasin de sauvegarde.

Peut-être pourrais-je utiliser mmap() et mprotect() , comme expliqué ci-dessus, sur le périphérique qui écrit des données nulle part (similaire à /dev/null ), puis avoir un processus qui reconnaît les lectures / écritures ).

Utiliser syscall() et fournir une fonction d’assemblage de restauration

Ce qui suit a été extrait du projet segvcatch 1 qui convertit les segvcatch segmentation en exceptions.

 #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") __atsortingbute__ ((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); 

Mais cela finit par ne pas être différent d’une configuration sigaction() . Si je ne configure pas la fonction de restauration, le gestionnaire de signaux n’est pas appelé plus d’une fois, même si la zone de mémoire n’est toujours pas disponible. Il y a peut-être une autre ruse que je pourrais faire avec le signal du kernel ici.


Encore une fois, l’objective de la bibliothèque est de gérer de manière transparente les lectures et les écritures en mémoire. Peut-être existe-t-il une meilleure façon de faire, peut-être avec ptrace() ou même la mise à jour du code du kernel qui génère le signal segfault, mais le plus important est que le code de l’utilisateur final ne nécessite aucune modification. J’ai vu des exemples utilisant setjmp() et longjmp() pour continuer après un segfault, mais cela nécessiterait d’append ces appels à chaque access mémoire. La même chose vaut pour la conversion d’un segfault en un try / catch.


1 projet segvcatch

Vous pouvez utiliser mprotect et éviter le premier problème que vous remarquez en demandant également au gestionnaire SIGSEGV de définir l’indicateur T dans le registre des indicateurs. Ensuite, vous ajoutez un gestionnaire SIGTRAP qui restaure la mémoire protégée et efface l’indicateur T.

L’indicateur T entraîne le processeur en une seule étape. Par conséquent, lorsque le gestionnaire SEGV retourne, il exécute cette instruction unique, puis TRAP immédiatement.

Cela vous laisse toujours avec votre deuxième problème – l’instruction de lecture / écriture se produira réellement. Vous pourrez peut-être contourner ce problème en modifiant soigneusement la mémoire avant et / ou après les instructions dans les deux gestionnaires de signaux …