2009-11-14 12 views
6

Sto testando il codice che è progettato per rilevare quando un processo figlio ha un segfault. Immaginate la mia sorpresa quando questo codice non lo fa sempre segfault:Perché il programma Linux che derefrences (char *) 0 non è sempre segfault?

#include <stdio.h> 

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

sto correndo sotto un 2.6.26 del kernel Linux Debian; la mia shell è l'AT & T ksh93 dal pacchetto Debian ksh, Versione M 93s + 2008-01-31. A volte questo programma segfault ma altrimenti termina semplicemente in silenzio con uno stato di uscita diverso da zero ma nessun messaggio. Il mio programma del segnale di rilevamento riporta quanto segue:

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 

In esecuzione in puro ksh mostra che la segfault è anche raro:

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

È interessante notare che, bash rileva correttamente la segfault ogni volta.

Ho due domande:

  1. Qualcuno può spiegare questo comportamento?

  2. Qualcuno può suggerire un semplice programma C che segusterà in modo affidabile ogni esecuzione? Ho anche provato kill(getpid(), SIGSEGV), ma ottengo risultati simili.


EDIT: jbcreix ha la risposta: il mio rilevatore segfault era rotto. Sono stato ingannato perché lo ksh ha lo stesso problema. Ho provato con bash e bash lo fa bene ogni volta.

Il mio errore era che stavo passando WNOHANG a waitpid(), dove avrei dovuto passare zero. Non so cosa avrei potuto pensare! Uno si chiede che cosa è il problema con ksh, ma questa è una domanda a parte.

+0

Cosa c'è di sbagliato con 'exit (0)'? Se vuoi che il bambino esca ... – pmg

+0

Non voglio che il bambino esca --- Lo voglio segfault. Perché? Sto costruendo un rilevatore segfault e ho bisogno di un modo per testarlo! –

+1

Norman, l'ho provato su vostra richiesta. Il mio codice funziona esattamente come ho detto, grazie. Non sai cosa c'è di sbagliato nel tuo sistema, forse il processo genitore che stai usando per catturare segnali AND ksh? Ho usato il kernel 2.6.28.7 per testare la risposta mmap. Funziona anche con raise (SIG_SEGV). –

risposta

12

Scrivendo-NULL modo affidabile segmentation fault o errore del bus.

A volte un sistema operativo associa una pagina di sola lettura all'indirizzo zero. Pertanto, a volte puoi leggere da NULL.

Sebbene C definisca l'indirizzo NULL come speciale, l''implementazione' di tale stato speciale viene effettivamente gestita dal sottosistema di memoria virtuale (VM) del sistema operativo.

WINE e dosemu devono mappare una pagina al NULL per la compatibilità con Windows. Vedere mmap_min_addr nel kernel di Linux per ricostruire un kernel che non può farlo.

è attualmente un argomento caldo a causa di un exploit correlato e una fiammella pubblica verso Linus (di fama Linux, ovviamente) da Theo de Raadt, dello sforzo di OpenBSD.

Se siete disposti a codificare il bambino in questo modo, si può sempre chiamare: raise(SIGSEGV);

Inoltre, è possibile ottenere un puntatore garantito da segfault da: int *ptr_segv = mmap(NULL, PAGE_SIZE, PROT_NONE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS, -1, 0);

Dove PROT_NONE è la chiave riservare memoria a cui non è possibile accedere. Per 32 bit Intel Linux, PAGE_SIZE è 4096.

+0

Heh, i due più grandi lanciafiamme in OSS si incontrano, è ... come ... la forza irresistibile incontra l'oggetto immobile ... come ... due locomotive che si schiantano a testa alta ... Vorrei poter vendere i biglietti – DigitalRoss

+0

Heath , Mi piacerebbe k ora se hai testato la tua risposta su un sistema Linux. Non riesco a farlo funzionare ... –

+0

Heath, ho provato questi e ho provato anche 'raise (SIGSEGV)'. Stesso problema. Supposto come rilancio (SIGSEGV) dovrebbe essere equivalente a kill (getpid(), SIGSEGV) su un programma a thread singolo. Hai provato qualcuno dei tuoi suggerimenti? Mi piacerebbe sapere se i miei (orribili) risultati sono riproducibili. –

1

Non sono sicuro del motivo per cui non ha un comportamento coerente. Penserei che non sia così pignolo con la lettura. O qualcosa del genere, anche se probabilmente mi sbaglio.

Provare a scrivere su NULL. Questo sembra essere coerente per me. Non ho idea del perché tu voglia usare questo però. :)

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

La risposta alla domanda numero due da Wikipedia:

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

Non sono d'accordo con ciò che dice Wikipedia. Il programma * potrebbe * creare un errore di segmentazione, ma non possiamo esserne certi. Il comportamento non è definito IIRC. –

+0

Sì, alcuni compilatori possono inserire valori letterali nel segmento di dati (lettura/scrittura). Ricordo lo shock quando gcc ha apportato la modifica. Credo di uscire con me stesso ... –