Modo portatile per catturare segnali e segnalare problemi all'utente
-
01-07-2019 - |
Domanda
Se per qualche miracolo si verifica un segfault nel nostro programma, voglio catturare SIGSEGV e far sapere all'utente (possibilmente un client GUI) con un unico codice di ritorno che si è verificato un grave problema. Allo stesso tempo, vorrei visualizzare le informazioni sulla riga di comando per mostrare quale segnale è stato catturato.
Oggi il nostro gestore di segnale ha il seguente aspetto:
void catchSignal (int reason) {
std :: cerr << "Caught a signal: " << reason << std::endl;
exit (1);
}
Riesco a sentire le urla dell'orrore con quanto sopra, come ho letto da questo thread che è male chiamare una funzione non rientrante da un gestore di segnali.
Esiste un modo portatile per gestire il segnale e fornire informazioni agli utenti?
MODIFICA: O almeno portatile nel framework POSIX?
Soluzione
Questa table elenca tutte le funzioni che POSIX garantisce di essere asincrone-sicure per il segnale e che quindi possono essere chiamate da un gestore di segnale.
Usando il comando 'write' da questa tabella, il seguente relativamente "brutto" si spera che la soluzione farà il trucco:
#include <csignal>
#ifdef _WINDOWS_
#define _exit _Exit
#else
#include <unistd.h>
#endif
#define PRINT_SIGNAL(X) case X: \
write (STDERR_FILENO, #X ")\n" , sizeof(#X ")\n")-1); \
break;
void catchSignal (int reason) {
char s[] = "Caught signal: (";
write (STDERR_FILENO, s, sizeof(s) - 1);
switch (reason)
{
// These are the handlers that we catch
PRINT_SIGNAL(SIGUSR1);
PRINT_SIGNAL(SIGHUP);
PRINT_SIGNAL(SIGINT);
PRINT_SIGNAL(SIGQUIT);
PRINT_SIGNAL(SIGABRT);
PRINT_SIGNAL(SIGILL);
PRINT_SIGNAL(SIGFPE);
PRINT_SIGNAL(SIGBUS);
PRINT_SIGNAL(SIGSEGV);
PRINT_SIGNAL(SIGTERM);
}
_Exit (1); // 'exit' is not async-signal-safe
}
MODIFICA: Creazione su Windows.
Dopo aver provato a creare questo Windows, sembra che 'STDERR_FILENO' non sia definito. Dalla documentazione tuttavia il suo valore sembra essere "2".
#include <io.h>
#define STDIO_FILENO 2
EDIT: 'exit' non dovrebbe essere chiamato neanche dal gestore del segnale!
Come sottolineato da fizzer , chiamare _Exit in quanto sopra è un approccio a mazza per segnali come HUP e TERM. Idealmente, quando questi segnali vengono catturati con una bandiera con "volatile sig_atomic_t" type può essere utilizzato per notificare al programma principale che dovrebbe uscire.
Quanto segue ho trovato utile nelle mie ricerche.
Altri suggerimenti
FWIW, 2 è un errore standard anche su Windows, ma avrai bisogno di una compilazione condizionale perché il loro write () si chiama _write (). Ti consigliamo anche
#ifdef SIGUSR1 /* or whatever */
ecc. intorno a tutti i riferimenti a segnali non garantiti come definiti dallo standard C.
Inoltre, come indicato sopra, non si desidera gestire SIGUSR1, SIGHUP, SIGINT, SIGQUIT e SIGTERM in questo modo.
Richard, ancora abbastanza karma per commentare, quindi temo una nuova risposta. Questi sono segnali asincroni; non hai idea di quando vengano consegnati, quindi probabilmente sarai nel codice della biblioteca che deve essere completato per rimanere coerente. I gestori di segnali per questi segnali sono pertanto tenuti a ritornare. Se chiamate exit (), la libreria eseguirà alcune operazioni post-main (), incluse le funzioni di chiamata registrate con atexit () e la pulizia dei flussi standard. Questa elaborazione potrebbe non riuscire se, ad esempio, il segnale è arrivato in una funzione I / O di libreria standard. Pertanto in C90 non è consentito chiamare exit (). Vedo ora che C99 rilassa il requisito fornendo una nuova funzione _Exit () in stdlib.h. _Exit () può essere tranquillamente chiamato da un gestore per un segnale asincrono. _Exit () non chiamerà le funzioni atexit () e potrebbe omettere di ripulire i flussi standard a discrezione dell'implementazione.
A bk1e (commentatore alcuni post in alto) Il fatto che SIGSEGV sia sincrono è il motivo per cui non è possibile utilizzare funzioni che non sono progettate per rientrare. Cosa succede se la funzione che si è arrestata in modo anomalo era in possesso di un blocco e la funzione chiamata dal gestore del segnale tenta di acquisire lo stesso blocco?
Questa è una possibilità, ma non è "il fatto che SIGSEGV sia sincrono" che sia il problema. Chiamare funzioni non rientranti dal gestore è molto peggio con segnali asincroni per due motivi:
- gestori di segnali asincroni sono (generalmente) sperando di tornare e riprendere la normale esecuzione del programma. UN il gestore per un segnale sincrono è (generalmente) sta per terminare comunque, quindi non hai perso molto se si schianta.
- in senso perverso, hai il controllo assoluto su quando viene inviato un segnale sincrono - succede quando esegui il tuo codice difettoso, e in nessun altro momento. Non hai alcun controllo quando viene emesso un segnale asincrono. A meno che il codice I / O dell'OP non sia se stesso la causa del difetto, ad es. emettendo un brutto carattere * - il suo messaggio di errore ha una ragionevole possibilità di successo.
Scrivi un programma di avvio per eseguire il programma e segnalare all'utente un codice di uscita anomalo.