Inputs for improving code debuggability apart from logs and error codes
Asked Answered
D

3

4

Apart from error codes, error strings and logs, are there any other features which can be incorporated in the code to increase getting debug / trace information during code runtime which can help debug issues (or let us know what is going on) at runtime?

Donaghue answered 1/2, 2010 at 15:9 Comment(0)
I
2

Here's an example of the code that sends a stacktrace to a file upon a segmentation fault

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <stdarg.h>

static void signal_handler(int);
static void dumpstack(void);
static void cleanup(void);
void init_signals(void);
void panic(const char *, ...);

struct sigaction sigact;
char *progname;

int main(int argc, char **argv){
    char *s;
    progname = *(argv);
    atexit(cleanup);
    init_signals();
    printf("About to seg fault by assigning zero to *s\n");
    *s = 0;
    sigemptyset(&sigact.sa_mask);
    return 0;
}

void init_signals(void){
    sigact.sa_handler = signal_handler;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = 0;
    sigaction(SIGINT, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGSEGV);
    sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGBUS);
    sigaction(SIGBUS, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGQUIT);
    sigaction(SIGQUIT, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGHUP);
    sigaction(SIGHUP, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGKILL);
    sigaction(SIGKILL, &sigact, (struct sigaction *)NULL);
}

static void signal_handler(int sig){
    if (sig == SIGHUP) panic("FATAL: Program hanged up\n");
    if (sig == SIGSEGV || sig == SIGBUS){
        dumpstack();
        panic("FATAL: %s Fault. Logged StackTrace\n", (sig == SIGSEGV) ? "Segmentation" : ((sig == SIGBUS) ? "Bus" : "Unknown"));
    }
    if (sig == SIGQUIT) panic("QUIT signal ended program\n");
    if (sig == SIGKILL) panic("KILL signal ended program\n");
    if (sig == SIGINT) ;
}

void panic(const char *fmt, ...){
    char buf[50];
    va_list argptr;
    va_start(argptr, fmt);
    vsprintf(buf, fmt, argptr);
    va_end(argptr);
    fprintf(stderr, buf);
    exit(-1);
}

static void dumpstack(void){
    /* Got this routine from http://www.whitefang.com/unix/faq_toc.html
    ** Section 6.5. Modified to redirect to file to prevent clutter
    */
    char dbx[160];
    sprintf(dbx, "echo 'where\ndetach' | dbx -a %d > %s.dump", getpid(), progname);
    system(dbx);
    return;
}

void cleanup(void){
    sigemptyset(&sigact.sa_mask);
    /* Do any cleaning up chores here */
}

In the function dumpstack, dbx needs to be changed to suit your debugger, such as gdb for the GNU Debugger, this code was used when I was programming on AIX box a few years ago. Notice how the signals are set up, and if a SIGSEGV fault occurs, the handler dumps the stack to a file with extension .dump. The code demonstrates the segmentation fault and dumps the stacktrace.

That is my favourite code.

Hope this helps, Best regards, Tom.

Intervention answered 1/2, 2010 at 15:26 Comment(2)
Will this work for multi-threaded code? Also, if I have a library as a deliverable, can I add this in that also? Won't this interfere with the application's signal handler if present?Donaghue
Signal handlers are shared between all threads: when a thread calls sigaction(), it sets how the signal is handled not only for itself, but for all other threads in the program as well. You can also look into the backtrace(3) functions.Travistravus
P
2
  • Build without optimization, to preserve as much of the "intent" of the code as possible
  • Build in debug mode, to add symbol information
  • Don't strip the executable (on Linux/Unix systems), to keep as much symbol information as possible for debuggers to use
Paraesthesia answered 1/2, 2010 at 15:12 Comment(0)
I
2

Here's an example of the code that sends a stacktrace to a file upon a segmentation fault

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <stdarg.h>

static void signal_handler(int);
static void dumpstack(void);
static void cleanup(void);
void init_signals(void);
void panic(const char *, ...);

struct sigaction sigact;
char *progname;

int main(int argc, char **argv){
    char *s;
    progname = *(argv);
    atexit(cleanup);
    init_signals();
    printf("About to seg fault by assigning zero to *s\n");
    *s = 0;
    sigemptyset(&sigact.sa_mask);
    return 0;
}

void init_signals(void){
    sigact.sa_handler = signal_handler;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = 0;
    sigaction(SIGINT, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGSEGV);
    sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGBUS);
    sigaction(SIGBUS, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGQUIT);
    sigaction(SIGQUIT, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGHUP);
    sigaction(SIGHUP, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGKILL);
    sigaction(SIGKILL, &sigact, (struct sigaction *)NULL);
}

static void signal_handler(int sig){
    if (sig == SIGHUP) panic("FATAL: Program hanged up\n");
    if (sig == SIGSEGV || sig == SIGBUS){
        dumpstack();
        panic("FATAL: %s Fault. Logged StackTrace\n", (sig == SIGSEGV) ? "Segmentation" : ((sig == SIGBUS) ? "Bus" : "Unknown"));
    }
    if (sig == SIGQUIT) panic("QUIT signal ended program\n");
    if (sig == SIGKILL) panic("KILL signal ended program\n");
    if (sig == SIGINT) ;
}

void panic(const char *fmt, ...){
    char buf[50];
    va_list argptr;
    va_start(argptr, fmt);
    vsprintf(buf, fmt, argptr);
    va_end(argptr);
    fprintf(stderr, buf);
    exit(-1);
}

static void dumpstack(void){
    /* Got this routine from http://www.whitefang.com/unix/faq_toc.html
    ** Section 6.5. Modified to redirect to file to prevent clutter
    */
    char dbx[160];
    sprintf(dbx, "echo 'where\ndetach' | dbx -a %d > %s.dump", getpid(), progname);
    system(dbx);
    return;
}

void cleanup(void){
    sigemptyset(&sigact.sa_mask);
    /* Do any cleaning up chores here */
}

In the function dumpstack, dbx needs to be changed to suit your debugger, such as gdb for the GNU Debugger, this code was used when I was programming on AIX box a few years ago. Notice how the signals are set up, and if a SIGSEGV fault occurs, the handler dumps the stack to a file with extension .dump. The code demonstrates the segmentation fault and dumps the stacktrace.

That is my favourite code.

Hope this helps, Best regards, Tom.

Intervention answered 1/2, 2010 at 15:26 Comment(2)
Will this work for multi-threaded code? Also, if I have a library as a deliverable, can I add this in that also? Won't this interfere with the application's signal handler if present?Donaghue
Signal handlers are shared between all threads: when a thread calls sigaction(), it sets how the signal is handled not only for itself, but for all other threads in the program as well. You can also look into the backtrace(3) functions.Travistravus
T
1

When building for Linux, I like to be able to print a stack backtrace from a signal handler. This helps debug crashes (SIGSEGV) or allows me to send a signal to the program to initiate a stack backtrace at runtime. Core dumps can also be useful in debugging crashes (again in Linux).

Travistravus answered 1/2, 2010 at 15:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.