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, donc votre code sera très non portable.
Jetez un œil à libsigsegv.
Lorsque votre gestionnaire de signal revient (en supposant qu'il n'appelle pas exit ou longjmp ou quelque chose qui l'empêche de revenir), le code continuera au point où le signal s'est produit, en réexécutant la même instruction. Puisqu'à ce stade, la protection de la mémoire n'a pas été modifiée, elle renverra simplement le signal à nouveau et vous serez de retour dans votre gestionnaire de signal dans une boucle infinie.
Donc, pour que cela fonctionne, vous devez appeler mprotect dans le gestionnaire de signal. Malheureusement, comme le note Steven Schansker, mprotect n'est pas asynchrone, vous ne pouvez donc pas l'appeler en toute sécurité depuis le gestionnaire de signal. Donc, en ce qui concerne POSIX, vous êtes foutu.
Heureusement sur la plupart des implémentations (toutes les variantes UNIX et Linux modernes pour autant que je sache), mprotect est un appel système, il est donc sûr d'appeler depuis un gestionnaire de signal, vous pouvez donc faire la plupart de ce que vous voulez. Le problème est que si vous souhaitez rétablir 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'architecture qui contient des informations sur l'endroit où le signal s'est produit. Sous Linux, c'est un ucontext structure, qui contient des informations spécifiques à la machine sur l'adresse $PC et d'autres contenus de registre où le signal s'est produit. Si vous modifiez cela, vous modifiez l'endroit où le gestionnaire de signal reviendra, vous pouvez donc modifier le $PC juste après l'instruction défectueuse afin qu'il ne se réexécute pas après le retour du gestionnaire. C'est très difficile à faire correctement (et non portable aussi).
modifier
Le ucontext
la structure est définie dans <ucontext.h>
. Dans les ucontext
le champ uc_mcontext
contient le contexte de la machine, et dans cela , 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 déterminer quelle instruction était erronée 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 devrait être sûr -- ils permettent d'appeler n'importe quel appel système (tout ce qui est dans la section 2 du manuel) dans un signal gestionnaire.
Vous êtes tombé dans le piège que font tous les gens lorsqu'ils essaient pour la première fois de gérer les signaux. Le piège? Penser que vous pouvez réellement faire n'importe quoi d'utile avec les gestionnaires de signaux. À partir d'un gestionnaire de signaux, vous n'êtes autorisé à appeler que des appels de bibliothèque asynchrones et réentrants sécurisés.
Consultez cet avis CERT pour savoir pourquoi et une liste des fonctions POSIX qui sont sûres.
Notez que printf(), que vous appelez déjà, n'est pas sur cette liste.
Mprotect non plus. Vous n'êtes pas autorisé à l'appeler à partir d'un gestionnaire de signal. Il pourrait travail, 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 à maîtriser !
MODIFIER
Étant donné que je suis déjà un connard de portabilité en ce moment, je soulignerai que vous ne devriez pas non plus écrire dans des variables partagées (c'est-à-dire globales) sans prendre les précautions appropriées.