2009-11-14 8 views
6

Je suis en train de tester un code qui est conçu pour détecter le moment où un processus enfant est ségrégé. Imaginez ma surprise lorsque ce code ne fonctionne pas toujours segfault:Pourquoi le programme Linux qui derefrences (char *) 0 pas toujours segfault?

#include <stdio.h> 

int main() { 
    char *p = (char *)(unsigned long)0; 
    putchar(*p); 
    return 0; 
} 

je tourne un noyau Debian Linux 2.6.26; mon shell est le AT & T ksh93 du paquet Debian ksh, version M 93s + 2008-01-31. Parfois, ce programme segfault mais sinon il se termine simplement silencieusement avec un statut de sortie différent de zéro mais aucun message. Mon programme de signaux de détection indique ce qui suit:

segfault terminated by signal 11: Segmentation fault 
segfault terminated by signal 53: Real-time signal 19 
segfault terminated by signal 11: Segmentation fault 
segfault terminated by signal 53: Real-time signal 19 
segfault terminated by signal 53: Real-time signal 19 
segfault terminated by signal 53: Real-time signal 19 
segfault terminated by signal 53: Real-time signal 19 

Fonctionnant sous pur ksh montre que le segfault est aussi rare:

Running... 
Running... 
Running... 
Running... 
Running... 
Running... Memory fault 
Running... 

Fait intéressant, bash détecte correctement le segfault chaque fois.

J'ai deux questions:

  1. Quelqu'un peut-il expliquer ce comportement? Est-ce que quelqu'un peut suggérer un programme C simple qui va segfault de façon fiable à chaque exécution? J'ai aussi essayé kill(getpid(), SIGSEGV), mais j'obtiens des résultats similaires.


EDIT: jbcreix a la réponse: mon détecteur de segfault a été brisé. J'ai été dupe parce que ksh a le même problème. J'ai essayé avec bash et bash obtient le droit chaque fois. Mon erreur était que je passais WNOHANG à waitpid(), où j'aurais dû passer zéro. Je ne sais pas ce que j'aurais pu penser! On se demande quel est le problème avec ksh, mais c'est une question distincte.

+0

Quel est le problème avec 'exit (0)'? Si vous voulez que l'enfant sorte ... – pmg

+0

Je ne veux pas que l'enfant sorte --- je veux que ça segfault. Pourquoi? Je construis un détecteur de segfault et j'ai besoin d'un moyen de le tester! –

+1

Norman, je l'ai testé à votre demande. Mon code fonctionne exactement comme je l'ai dit, merci. Vous ne savez pas ce qui ne va pas sur votre système, peut-être le processus parent que vous utilisez pour attraper les signaux ET ksh? J'ai utilisé le noyau 2.6.28.7 pour tester la réponse mmap. Fonctionne également avec raise (SIG_SEGV). –

Répondre

12

écriture-NULL sera fiable ou segfault erreur de bus.

Parfois, un système d'exploitation mappe une page en lecture seule à l'adresse zéro. Ainsi, vous pouvez parfois lire à partir de NULL.

Bien que C définisse l'adresse NULL comme étant spéciale, la «mise en œuvre» de ce statut spécial est en fait gérée par le sous-système de mémoire virtuelle (VM) du système d'exploitation. WINE et dosemu doivent cartographier une page au NULL pour la compatibilité avec Windows. Voir mmap_min_addr dans le noyau Linux pour reconstruire un noyau qui ne peut pas faire cela.

mmap_min_addrmmap_min_addr est actuellement un sujet chaud en raison d'un exploit connexe et une flamme publique vers Linus (de la renommée Linux, évidemment) de Theo de Raadt, de l'effort OpenBSD.

Si vous êtes prêt à coder l'enfant de cette façon, vous pouvez toujours appeler: raise(SIGSEGV);

En outre, vous pouvez obtenir un pointeur à segfault garanti à partir de: int *ptr_segv = mmap(NULL, PAGE_SIZE, PROT_NONE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS, -1, 0);

PROT_NONE est la clé à réserver de la mémoire qui n'est pas accessible. 32 bits Intel Linux, PAGE_SIZE est 4096.

+0

Heh, les deux plus gros flameurs de l'OSS se rencontrent, c'est ... comme ... la force irrésistible rencontre l'objet immobile ... comme ... deux locomotives s'écrasant de front ... J'aimerais pouvoir vendre des billets – DigitalRoss

+0

Heath , J'aimerais k maintenant si vous avez testé votre réponse sur un système Linux. Je ne peux pas le faire fonctionner ... –

+0

Heath, j'ai essayé ceux-ci et j'ai essayé 'raise (SIGSEGV)' aussi. Même problème. Pas surprenant que raise (SIGSEGV) est supposé être équivalent à kill (getpid(), SIGSEGV) sur un programme monothread. Avez-vous testé l'une de vos suggestions? J'aimerais savoir si mes résultats (horribles) sont reproductibles. –

1

Je ne sais pas pourquoi il n'a pas de comportement cohérent. Je pense que ce n'est pas aussi difficile à lire. Ou quelque chose comme ça, même si je me trompe totalement.

Essayez d'écrire à NULL. Cela semble être cohérent pour moi. Je ne sais pas pourquoi vous voudriez l'utiliser cependant. :)

int main() 
{ 
    *(int *)0 = 0xFFFFFFFF; 
    return -1; 
} 
1

La réponse à la question numéro deux de Wikipedia:

int main(void) 
{ 
    char *s = "hello world"; 
    *s = 'H'; 
} 
+0

Je ne suis pas d'accord avec ce que Wikipedia dit. Le programme * peut * créer une erreur de segmentation, mais nous ne pouvons pas être sûrs. Le comportement est indéterminé IIRC. –

+0

Oui, certains compilateurs sont autorisés à placer des littéraux dans le segment de données (lecture/écriture). Je me souviens du choc quand gcc a fait le changement. Je suppose que je sors avec moi ... –