Comment écrire un gestionnaire de signal pour prendre SIGSEGV?

Je veux écrire un gestionnaire de signal pour prendre SIGSEGV. Je protège un bloc de mémoire en lecture ou en écriture

char *buffer; char *p; char a; int pagesize = 4096; mprotect(buffer,pagesize,PROT_NONE) 

Ceci protège les octets de mémoire de la page en commençant par la mémoire tampon contre toute lecture ou écriture.

Deuxièmement, j’essaie de lire la mémoire:

 p = buffer; a = *p 

Cela va générer un SIGSEGV, et mon gestionnaire sera appelé. Jusqu’ici tout va bien. Mon problème est que, une fois le gestionnaire appelé, je souhaite modifier l’écriture de l’access à la mémoire en

 mprotect(buffer,pagesize,PROT_READ); 

et continuer le fonctionnement normal de mon code. Je ne veux pas quitter la fonction. Sur les futures écritures dans la même mémoire, je veux reprendre le signal et modifier les droits d’écriture, puis enregistrer cet événement.

Voici le code :

 #include  #include  #include  #include  #include  #include  #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) char *buffer; int flag=0; static void handler(int sig, siginfo_t *si, void *unused) { printf("Got SIGSEGV at address: 0x%lx\n",(long) si->si_addr); printf("Implements the handler only\n"); flag=1; //exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { char *p; char a; int pagesize; struct sigaction sa; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sa.sa_sigaction = handler; if (sigaction(SIGSEGV, &sa, NULL) == -1) handle_error("sigaction"); pagesize=4096; /* Allocate a buffer aligned on a page boundary; initial protection is PROT_READ | PROT_WRITE */ buffer = memalign(pagesize, 4 * pagesize); if (buffer == NULL) handle_error("memalign"); printf("Start of region: 0x%lx\n", (long) buffer); printf("Start of region: 0x%lx\n", (long) buffer+pagesize); printf("Start of region: 0x%lx\n", (long) buffer+2*pagesize); printf("Start of region: 0x%lx\n", (long) buffer+3*pagesize); //if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1) if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1) handle_error("mprotect"); //for (p = buffer ; ; ) if(flag==0) { p = buffer+pagesize/2; printf("It comes here before reading memory\n"); a = *p; //trying to read the memory printf("It comes here after reading memory\n"); } else { if (mprotect(buffer + pagesize * 0, pagesize,PROT_READ) == -1) handle_error("mprotect"); a = *p; printf("Now i can read the memory\n"); } /* for (p = buffer;p<=buffer+4*pagesize ;p++ ) { //a = *(p); *(p) = 'a'; printf("Writing at address %p\n",p); }*/ printf("Loop completed\n"); /* Should never happen */ exit(EXIT_SUCCESS); } 

Le problème est que seul le gestionnaire de signal s’exécute et que je ne peux pas retourner à la fonction principale après avoir capté le signal.

    Lorsque votre gestionnaire de signal retourne (en supposant qu’il n’appelle pas exit ou longjmp ou quelque chose qui l’empêche de revenir), le code continuera au moment où le signal s’est produit, en réexécutant la même instruction. Etant donné qu’à ce stade, la protection de la mémoire n’a pas été modifiée, il ne fera que relancer le signal et vous serez de retour dans votre gestionnaire de signaux dans une boucle infinie.

    Donc, pour que cela fonctionne, vous devez appeler mprotect dans le gestionnaire de signaux. Malheureusement, comme le fait remarquer Steven Schansker, mprotect n’est pas asynchrone, vous ne pouvez donc pas l’appeler en toute sécurité depuis le gestionnaire de signaux. Donc, en ce qui concerne POSIX, vous êtes foutu.

    Heureusement, sur la plupart des implémentations (à ma connaissance, toutes les variantes UNIX et Linux modernes), mprotect est un appel système. Vous pouvez donc appeler en toute sécurité depuis un gestionnaire de signal afin de pouvoir réaliser l’essentiel de vos tâches. Le problème est que si vous souhaitez modifier les protections après la lecture, vous devrez le faire dans le programme principal après la lecture.

    Une autre possibilité est de faire quelque chose avec le troisième argument du gestionnaire de signal, qui pointe vers une structure spécifique au système d’exploitation et à l’archive qui contient des informations sur l’emplacement du signal. Sous Linux, il s’agit d’une structure ucontext , qui contient des informations spécifiques à la machine concernant l’adresse $ PC et d’autres contenus de registre où le signal s’est produit. Si vous modifiez cela, vous modifiez le retour du gestionnaire de signal, vous pouvez donc changer le $ PC juste après l’instruction défaillante pour qu’il ne soit pas ré-exécuté après le retour du gestionnaire. C’est très difficile à faire (et non portable aussi).

    modifier

    La structure ucontext est définie dans . Dans le champ ucontext le champ uc_mcontext contient le contexte de la machine et, à l’intérieur de celui-ci , le tableau gregs contient le contexte général du registre. Donc, dans votre gestionnaire de signal:

     ucontext *u = (ucontext *)unused; unsigned char *pc = (unsigned char *)u->uc_mcontext.gregs[REG_RIP]; 

    vous donnera le pc où l’exception s’est produite. Vous pouvez le lire pour savoir quelles instructions il a été fautif et faire quelque chose de différent.

    En ce qui concerne la portabilité de l’appel de mprotect dans le gestionnaire de signal, tout système qui suit la spécification SVID ou la spécification BSD4 doit être sûr – il permet d’appeler n’importe quel appel système (n’importe quoi dans la section 2 du manuel) gestionnaire.

    Vous êtes tombé dans le piège que tout le monde fait quand ils essaient pour la première fois de gérer les signaux. Le piège? En pensant que vous pouvez réellement faire quelque chose d’ utile avec les gestionnaires de signaux. A partir d’un gestionnaire de signaux, vous êtes autorisé à appeler des appels de bibliothèque asynchrones et réentrants.

    Voir cet avis CERT pour savoir pourquoi et une liste des fonctions POSIX qui sont sécurisées.

    Notez que printf (), que vous appelez déjà, ne figure pas dans cette liste.

    Ni mprotect. Vous n’êtes pas autorisé à l’appeler depuis un gestionnaire de signal. Cela pourrait fonctionner, mais je peux vous promettre que vous rencontrerez des problèmes plus tard. Soyez très prudent avec les gestionnaires de signaux, ils sont difficiles à comprendre!

    MODIFIER

    Étant donné que je suis une douchebag portabilité pour le moment, je vous ferai remarquer que vous ne devriez pas écrire sur des variables partagées (c.-à-d. Globales) sans prendre les précautions nécessaires.

    Vous pouvez récupérer de SIGSEGV sur Linux. Vous pouvez également récupérer des erreurs de segmentation sous Windows (vous verrez une exception structurée au lieu d’un signal). Mais la norme POSIX ne garantit pas la récupération , votre code sera donc très non portable.

    Jetez un oeil à libsigsegv .

    Vous ne devez pas revenir du gestionnaire de signal, car le comportement est indéfini. Plutôt, sautez hors avec longjmp.

    Ce n’est correct que si le signal est généré dans une fonction async-signal-safe. Sinon, le comportement n’est pas défini si le programme appelle une autre fonction asynchrone-signal non sécurisée. Par conséquent, le gestionnaire de signaux ne doit être établi que juste avant qu’il soit nécessaire et supprimé dès que possible.

    En fait, je connais très peu d’utilisations d’un gestionnaire SIGSEGV:

    • utiliser une bibliothèque de backtrace async-signal-safe pour enregistrer une trace, puis mourir.
    • dans une machine virtuelle telle que JVM ou CLR: vérifiez si le SIGSEGV s’est produit dans un code compilé par JIT. Sinon, meurs; Si tel est le cas, lancez une exception spécifique à la langue ( pas une exception C ++), car le compilateur JIT savait que l’interruption pouvait se produire et générait des données de décompression de trame appropriées.
    • clone () et exec () un débogueur (n’utilisez pas fork () – qui appelle les callbacks enregistrés par pthread_atfork ()).

    Enfin, notez que toute action qui déclenche SIGSEGV est probablement UB, car elle accède à une mémoire non valide. Cependant, cela ne serait pas le cas si le signal était, par exemple, SIGFPE.

    Il y a un problème de compilation avec ucontext_t ou struct ucontext (présent dans /usr/include/sys/ucontext.h )

    http://www.mail-archive.com/[email protected]/msg13853.html