PDA

View Full Version : [C++] Linux catch di stack overflow


tomminno
04-02-2008, 17:16
Come realizzo su Linux qualcosa di equivalente a:


void Loop()
{
static int count = 0;
cout << count++ << endl;
Loop();
}

int main(int argc, CHAR* argv[])
{
__try
{
Loop();
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
cout << "exception caught" << endl;
}
return 0;
}


Ho provato in tutti i modi ma con i signal non riesco a catturare uno stack overflow.

71104
04-02-2008, 19:06
non arriva il segnale di segmentation fault in questi casi? :wtf:

ilsensine
04-02-2008, 23:12
Dubito che un catch su uno stach overflow sia in ogni caso sicuro. Considera questo esempio, e immagina di eseguirlo quando hai solo una manciata di byte di stack disponibili:

__try {
A a; // A e' una classe
char buf[128]; // stack overflow qui
...
} __except (ecc ecc)

Quando accade lo stack overflow, cosa fai? Il distruttore di A viene chiamato?
E se il distruttore è qualcosa tipo

A::~A()
{
char buf[2048]; // stack overflow n.2 versione extralarge
...
}

come lo gestisci?

Se rinunci alla mancata chiamata dei distruttori, puoi provare questa tecnica.
Ho aggiunto qualche velato tentativo di supporto multithread e nested, il tutto rigorosamente non testato:

static __thread stack_t thread_sigstack;
static __thread stack_t old_thread_sigstack;
void sig_try_init()
{
thread_sigstack.ss_size = SIGSTKSZ;
thread_sigstack.ss_flags = 0;
thread_sigstack.ss_sp = malloc(thread_sigstack.ss_size);
thread_sigstack.ss_sp = (char *) thread_sigstack.ss_sp + thread_sigstack.ss_size;
sigaltstack(&thread_sigstack, &old_thread_sigstack);

struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK;
sa.sa_sigaction = sighandler;
sigaction(SIGSEGV, &sa, NULL);
}

void sig_try_exit()
{
sigaltstack(&old_thread_sigstack, NULL);
free((char *) thread_sigstack.ss_sp - thread_sigstack.ss_size);
}

void overflow(char *v)
{
if (v)
memset(v, 0, 1024);
char a[1024];
overflow(a);
}

int main()
{
sig_try_init();

__try {
fprintf(stderr, "Start...\n");
overflow(NULL);
fprintf(stderr, "Stop\n");
} __catch() {
fprintf(stderr, "Exception caught\n");
}

sig_try_exit();
return 0;
}

Mancano alcuni pezzi, che ti segno qui a parte:

__thread sigjmp_buf *env = NULL;
void sighandler(int sig, siginfo_t *si, void *unused)
{
if (env!=NULL)
siglongjmp(*env, 1);
else
abort();
}

__thread bool __in_catch = false;
#define __try \
{ \
bool in_catch = false; \
sigjmp_buf ebuf; \
sigjmp_buf *old_ebuf = env; \
env = &ebuf; \
if (sigsetjmp(*env, 1)==0)

#define __catch() \
else \
in_catch = true; \
env = old_ebuf; \
__in_catch = in_catch; \
} \
if (__in_catch)

ilsensine
04-02-2008, 23:15
Il programma di test, compilabile, è questo.
Becca anche i segfault, ovviamente.

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>
#include <string.h>

__thread sigjmp_buf *env = NULL;
static void sighandler(int sig, siginfo_t *si, void *unused)
{
if (env!=NULL)
siglongjmp(*env, 1);
else
abort();
}

static __thread stack_t thread_sigstack;
static __thread stack_t old_thread_sigstack;
void sig_try_init()
{
thread_sigstack.ss_size = SIGSTKSZ;
thread_sigstack.ss_flags = 0;
thread_sigstack.ss_sp = malloc(thread_sigstack.ss_size);
thread_sigstack.ss_sp = (char *) thread_sigstack.ss_sp + thread_sigstack.ss_size;
sigaltstack(&thread_sigstack, &old_thread_sigstack);

struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK;
sa.sa_sigaction = sighandler;
sigaction(SIGSEGV, &sa, NULL);
}

void sig_try_exit()
{
sigaltstack(&old_thread_sigstack, NULL);
free((char *) thread_sigstack.ss_sp - thread_sigstack.ss_size);
}

__thread bool __in_catch = false;
#define __try \
{ \
bool in_catch = false; \
sigjmp_buf ebuf; \
sigjmp_buf *old_ebuf = env; \
env = &ebuf; \
if (sigsetjmp(*env, 1)==0)

#define __catch() \
else \
in_catch = true; \
env = old_ebuf; \
__in_catch = in_catch; \
} \
if (__in_catch)

void overflow(char *v)
{
if (v)
memset(v, 0, 1024);
char a[1024];
overflow(a);
}

int main()
{
sig_try_init();

__try {
fprintf(stderr, "Start...\n");
overflow(NULL);
fprintf(stderr, "Stop\n");
} __catch() {
fprintf(stderr, "Exception caught\n");
}

sig_try_exit();
return 0;
}

cdimauro
05-02-2008, 06:26
Dubito che un catch su uno stach overflow sia in ogni caso sicuro. Considera questo esempio, e immagina di eseguirlo quando hai solo una manciata di byte di stack disponibili:

__try {
A a; // A e' una classe
char buf[128]; // stack overflow qui
...
} __except (ecc ecc)

Quando accade lo stack overflow, cosa fai? Il distruttore di A viene chiamato?
E se il distruttore è qualcosa tipo

A::~A()
{
char buf[2048]; // stack overflow n.2 versione extralarge
...
}

come lo gestisci?
Niente: becchi un mortale double fault e ti metti l'anima in pace. :p

ilsensine
05-02-2008, 09:04
Niente: becchi un mortale double fault e ti metti l'anima in pace. :p
No non è un double fault...

tomminno
05-02-2008, 12:54
Dubito che un catch su uno stach overflow sia in ogni caso sicuro. Considera questo esempio, e immagina di eseguirlo quando hai solo una manciata di byte di stack disponibili:

__try {
A a; // A e' una classe
char buf[128]; // stack overflow qui
...
} __except (ecc ecc)

Quando accade lo stack overflow, cosa fai? Il distruttore di A viene chiamato?
E se il distruttore è qualcosa tipo

A::~A()
{
char buf[2048]; // stack overflow n.2 versione extralarge
...
}

come lo gestisci?


Intanto grazie per il codice.
Per quanto riguarda la gestione dello stack overflow, più che altro serve per debug su codice di produzione, per rilevare eventuali problemi ed avere modo di trovare la causa più facilmente.
Si suppone che errori da terminazione immediata non siano così comuni, ma sporadici (e si spera inesistenti...), per cui se il tutto serve per trovare un bug di quelli nascostissimi, l'eventuale memory leak passa in secondo piano.
Inoltre nel caso di una allocazione sullo stack in un distruttore, beh pace.

Il codice che avevo postato è per Windows e in quel modo qualunque porcata commessa nel __try (dereferenziazione di puntatori nulli, doppia delete, stack overflow...) viene catturata da __except e con qualche riga di codice in più è facile avere anche lo stack trace.
Esattamente come accade in C# o Java dove con un try catch sei sicuro che il software continua sempre e comunque a funzionare.

Volevo riuscire a fare la stessa cosa anche su Linux.

cdimauro
06-02-2008, 08:33
No non è un double fault...
Sicuramente sono un po' arrugginito con l'assembly 80x86 :D, ma se ho uno stack fault, e nell'handler per gestirlo viene generato un altro stack fault, non dovrebbe scatenarsi un double fault? :stordita:

ilsensine
06-02-2008, 09:06
Sicuramente sono un po' arrugginito con l'assembly 80x86 :D, ma se ho uno stack fault, e nell'handler per gestirlo viene generato un altro stack fault, non dovrebbe scatenarsi un double fault? :stordita:
Secondo te un processo utente ha i privilegi per gestire un page fault? :stordita:

cdimauro
06-02-2008, 09:28
Ma allora come si fa a "intrappolare" uno stack fault? Dovrebbe esistere un qualche metodo per "ripassare" il controllo in user-space? Giusto per capire.

ilsensine
06-02-2008, 09:43
Un double fault capita quando per gestire una eccezione capita una seconda eccezione. In pratica, se il gate di eccezione punta a un segmenetno non valido o accade...un segfault nel tentativo di lanciare l'handler. Su linux solo un bug del kernel può provocare un double fault in quanto l'handler è sempre presente e su memoria persistente e valida.

L'eccezione di stack viene gestita correttamente dall'handler del kernel (quindi nessun double fault), che invia un segnale al processo. A seguito del segnale, il processo riparte dall'handler predisposto; se durante l'esecuzione di questo handler si verifica nuovamente la stessa eccezione, si ha un (ancora una volta singolo) fault.

cdimauro
06-02-2008, 09:49
Sì, le condizioni per il double fault me le ricordavo. Non sapevo invece com'era gestito lo stack fault.

Adesso mi è tutto chiaro e concordo con te, ovviamente, visto che l'eccezione di stack fault viene (dal kernel) "reindirizzata" in user-space all'applicazione.

Grazie :)