-1

I want to catch SIGSEGV signal and write on stdout in which line did it occur. I guess i should use SA_SIGINFO flag for sigaction structure and use sa_sigaction instead of sa_handler because i get more information about the sginal. When SIGSEGV occur in siginfo_t structure the field si_addr is filled with the address of the fault. What does this mean? Is that address in memory of variable or function that triggered SIGSEGV?

I am learning from the book "The linux programming interface" but there is nothing about sa_sigaction (only about sa_handler), instead i have read about this on man pages.

#define _GNU_SOURCE
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<limits.h>
#include<errno.h>
#include<sys/wait.h>
#include<sys/types.h>

#define osErrorFatal(userMsg) osErrorFatalImpl((userMsg),__FILE__,__func__,__LINE__)
#define osAssert(expr,userMsg)\
    do{ if(!(expr)) {osErrorFatal(userMsg);} }while(0);

#define MAX_ARGUMENTS 10

void osErrorFatalImpl(char *userMsg,const char *fileName,const char *funcName,const int lineNum)
{
    perror(userMsg);
    fprintf(stderr,"File name: %s\nFunc name: %s\nLine num: %d\n",fileName,funcName,lineNum);
    exit(EXIT_FAILURE);
}

void signal_handler(int sig, siginfo_t *info, void *ucontext)
{
    fprintf(stdout,"Caught signal '%s'\n",strsignal(sig));
    //now i want to print in which line SIGSEGV occured in child process
}

int main(int argc, char **argv)
{
    if(argc<2)
    {
        fprintf(stderr,"Error: usage ./a.out file_path\n");
        exit(EXIT_FAILURE);
    }
    char *programPath = NULL;
    osAssert((programPath = realpath(argv[1],programPath)) != NULL, "realpath failed");
    char *programArguments[argc];
    programArguments[0] = strdup(strrchr(programPath, '/'));
    int i;
    for(i=1;i<argc-1;i++)
        programArguments[i] = strdup(argv[i+1]);
    programArguments[i] = NULL;
    int childPid;
    switch((childPid = fork()))
    {
        case -1:    //error
            osAssert(0,"fork failed");
        case 0:     //child branch
            execvp(programPath, programArguments);
            _exit(127);
        default:    //parent branch
        {
            struct sigaction sa;
            sa.sa_flags = SA_SIGINFO;
            sa.sa_sigaction = signal_handler;
            osAssert(sigemptyset(&sa.sa_mask) != -1,"sigemptyset failed");
            osAssert(sigaction(SIGSEGV,&sa,NULL) != -1, "sigaction failed");

            wait(NULL);
            exit(EXIT_SUCCESS);
        }
    }

}

This is the program that is triggering SIGSEGV :

#define _GNU_SOURCE
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<limits.h>
#include<errno.h>
#include<sys/wait.h>
#include<sys/types.h>

#define osErrorFatal(userMsg) osErrorFatalImpl((userMsg),__FILE__,__func__,__LINE__)
#define osAssert(expr,userMsg)\
    do{ if(!(expr)) {osErrorFatal(userMsg);} }while(0);

#define MAX_ARGUMENTS 10

void osErrorFatalImpl(char *userMsg,const char *fileName,const char *funcName,const int lineNum)
{
    perror(userMsg);
    fprintf(stderr,"File name: %s\nFunc name: %s\nLine num: %d\n",fileName,funcName,lineNum);
    exit(EXIT_FAILURE);
}

void signal_handler_child(int sig)
{
    osAssert( kill(getppid(), sig) != -1,"kill failed");
    exit(EXIT_FAILURE);
}


int main(int argc, char **argv)
{
    struct sigaction sa;
    sa.sa_handler = signal_handler_child;
    sa.sa_flags = 0;
    osAssert( sigemptyset(&sa.sa_mask) != -1,"sigemptyset failed");
    osAssert( sigaction(SIGSEGV,&sa,NULL) != -1,"sigaction failed");
    char *s=NULL;
    printf("%s\n",s);
    exit(EXIT_SUCCESS);
}

I know i can use gdb but i don't want to. I want to make parent process that will look out for his children.

Joe
  • 41,484
  • 20
  • 104
  • 125
Aleksa Jovanovic
  • 207
  • 3
  • 15
  • 1
    No, this is the address of the instruction where the fault occurred. If you had debug symbols (the binary is compiled with debug option. `-g` for gcc/clang). You can look that up, find this address and it will point to the filename, line number, which you can then print. – Ajay Brahmakshatriya May 15 '18 at 15:36
  • 1
    Related: https://stackoverflow.com/q/9207599/694576 – alk May 15 '18 at 15:37
  • you can get easily the address in the child process at witch the error occur from the signal handler. but getting a line in the file will require parsing the debugging symbols. – Tyker May 15 '18 at 15:38
  • 2
    Stuff one safely can do in a `SIGSEGV` handler is very limited. You *might try* [backtrace_symbols_fd](http://man7.org/linux/man-pages/man3/backtrace.3.html). – alk May 15 '18 at 15:42
  • 1
    Related: https://stackoverflow.com/questions/35325808/backtrace-inside-signal-handler – alk May 15 '18 at 15:43
  • 1
    You need to be careful with your signal handler. You can only safely call async-signal-safe functions from within a signal handler. `fprintf()` isn't one of those. – Andrew Henle May 15 '18 at 16:19

1 Answers1

1

In the parent process you may not be able to catch SIGSEGV that was generated in the child process. What you could do is waitpid and use options WIFSIGNALED and WTERMSIG to get the signal that killed the child. You can lookup for a generated core dump and report that.

Snippet from waitpid doc

        w = waitpid(cpid, &status, WUNTRACED | WCONTINUED);
        if (w == -1) {
            perror("waitpid");
            exit(EXIT_FAILURE);
        }

       if (WIFEXITED(status)) {
            printf("exited, status=%d\n", WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("killed by signal %d\n", WTERMSIG(status));
        } else if (WIFSTOPPED(status)) {
            printf("stopped by signal %d\n", WSTOPSIG(status));
        } else if (WIFCONTINUED(status)) {
            printf("continued\n");
        }

Essentially you want the information to be able to debug your application. You can setup your environment to generate a core dump that you can debug later. Run following command to enable code dumps:

ulimit -c unlimited

This will create a core dump file core.pid. You can view it in gdb for seeing backtrace with following command:

gdb executable core.pid
(gdb) bt
Mohammad Azim
  • 2,604
  • 20
  • 21
  • how is this relevant with my question? I said i don't want to use gdb – Aleksa Jovanovic May 15 '18 at 16:42
  • Updated the answer. I am not sure how you can catch a signal that is generated in another process (child). But you can get child's status using the waitpid and related options. – Mohammad Azim May 15 '18 at 17:08
  • To have signal handler in the child itself this post may help https://stackoverflow.com/questions/35325808/backtrace-inside-signal-handler?noredirect=1&lq=1 – Mohammad Azim May 15 '18 at 17:10
  • I catch signal in child process and i send it using kill() system call to parent process. When i get signal in parent process i want to see in which line signal triggered. Is there something like __LINE__ macro when errno occur, just for signals? – Aleksa Jovanovic May 15 '18 at 17:31
  • Not that I have heard of. I only know that you can get it via gdb. Since, you can only get it using debug symbols. – Mohammad Azim May 15 '18 at 21:00
  • @AleksaJovanovic if your queries got answered and you like the proposed answer you can accept the answer. – Mohammad Azim May 16 '18 at 08:41