Chapter 21.4 — The SA_SIGINFO Flag

 

Chapter 21.4 — The SA_SIGINFO Flag
Getting Detailed Signal Information via siginfo_t | EmbeddedPathashala
21.4
Section
3
Code Examples
7
Interview Qs

Key Terms
SA_SIGINFO siginfo_t sa_sigaction si_signo si_code si_pid si_uid si_addr si_status SI_USER ucontext_t

What is SA_SIGINFO?

Normally, a signal handler receives only one argument: the signal number. The SA_SIGINFO flag tells the kernel to call a richer handler that receives three arguments, including a siginfo_t structure containing detailed information about the origin and cause of the signal.

This is especially useful for hardware fault signals (SIGSEGV, SIGBUS, SIGFPE, SIGILL) where you need to know the address that caused the fault, or for SIGCHLD where you want exit status details.

Standard Handler vs SA_SIGINFO Handler
Standard Signal Handler
/* Handler prototype */
void handler(int sig);

/* Registration */
struct sigaction sa;
sa.sa_handler = handler;
sa.sa_flags = 0;
/* Only knows: WHICH signal */
SA_SIGINFO Handler
/* Handler prototype */
void handler(int sig,
             siginfo_t *info,
             void *ucontext);

/* Registration */
struct sigaction sa;
sa.sa_sigaction = handler; /* NOTE: sa_sigaction, not sa_handler */
sa.sa_flags = SA_SIGINFO;
/* Knows: which signal + WHO sent it
   + WHY + fault address + child status */
Important: When using SA_SIGINFO, you must set sa.sa_sigaction (not sa.sa_handler). Both are in a union inside struct sigaction. Setting the wrong one is a bug — the pointer values will be misinterpreted.

The siginfo_t Structure — Field Reference
Field Type Set For Contains
si_signo int All signals Signal number (same as first arg)
si_code int All signals Origin code (SI_USER, SI_KERNEL, etc.)
si_pid pid_t kill(), sigqueue() PID of sending process
si_uid uid_t kill(), sigqueue() Real user ID of sending process
si_addr void * SIGSEGV, SIGBUS, SIGILL, SIGFPE Address that caused the fault
si_status int SIGCHLD Exit status or signal that killed child
si_utime clock_t SIGCHLD User CPU time used by child
si_stime clock_t SIGCHLD System CPU time used by child
si_value union sigval sigqueue() Accompanying data from sigqueue()
si_band long SIGPOLL/SIGIO Band event for I/O
si_fd int SIGPOLL/SIGIO File descriptor for I/O event

Common si_code Values
SI_USER — sent via kill() or raise() by a user process
SI_KERNEL — sent by the kernel
SI_QUEUE — sent via sigqueue() (realtime)
SEGV_MAPERR (SIGSEGV) — address not mapped
SEGV_ACCERR (SIGSEGV) — invalid permissions
CLD_EXITED (SIGCHLD) — child exited normally
CLD_KILLED (SIGCHLD) — child killed by signal
FPE_INTDIV (SIGFPE) — integer divide by zero
ILL_ILLOPC (SIGILL) — illegal opcode

Code Example 1 — Basic SA_SIGINFO: Who Sent the Signal?
#define _POSIX_C_SOURCE 199309L
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

/*
 * SA_SIGINFO handler prototype — three arguments:
 *   sig:      signal number
 *   info:     pointer to siginfo_t with details
 *   ucontext: saved CPU context (rarely used — cast to ucontext_t *)
 */
static void info_handler(int sig, siginfo_t *info, void *ucontext)
{
    /* UNSAFE: printf used for demonstration only */
    printf("\n=== Signal Received ===\n");
    printf("  Signal number : %d\n", info->si_signo);
    printf("  si_code       : %d ", info->si_code);

    switch (info->si_code) {
        case SI_USER:   printf("(SI_USER — sent by user process via kill/raise)\n"); break;
        case SI_KERNEL: printf("(SI_KERNEL — sent by kernel)\n"); break;
        case SI_QUEUE:  printf("(SI_QUEUE — sent via sigqueue)\n"); break;
        default:        printf("(other)\n"); break;
    }

    /* si_pid and si_uid only valid when signal sent by a process */
    if (info->si_code == SI_USER || info->si_code == SI_QUEUE) {
        printf("  Sender PID    : %d\n", (int)info->si_pid);
        printf("  Sender UID    : %d\n", (int)info->si_uid);
    }
    printf("========================\n");
}

int main(void)
{
    struct sigaction sa;
    sa.sa_sigaction = info_handler;   /* Must use sa_sigaction (not sa_handler) */
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;         /* Enable the 3-argument handler */

    sigaction(SIGUSR1, &sa, NULL);
    sigaction(SIGUSR2, &sa, NULL);
    sigaction(SIGINT,  &sa, NULL);

    printf("PID = %d\n", (int)getpid());
    printf("Send signals: kill -USR1 %d  or  kill -USR2 %d\n",
           (int)getpid(), (int)getpid());
    printf("Press Ctrl+C to trigger SIGINT from terminal.\n\n");

    /* Wait for 5 signals */
    int count = 0;
    while (count < 5) {
        pause();
        count++;
    }
    printf("Done.\n");
    return 0;
}

Code Example 2 — SIGSEGV Handler: Finding the Fault Address
#define _POSIX_C_SOURCE 199309L
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

static char altstack_buf[SIGSTKSZ];

static void sigsegv_handler(int sig, siginfo_t *info, void *ucontext)
{
    /*
     * si_addr is set for hardware-generated SIGSEGV and SIGBUS.
     * It contains the memory address that caused the fault.
     */

    /* Use write() — async-signal-safe */
    char buf[256];
    int len = snprintf(buf, sizeof(buf),   /* snprintf is NOT safe but used here for demo */
        "\n[SIGSEGV] Fault at address: %p\n"
        "  si_code: %d (%s)\n",
        info->si_addr,
        info->si_code,
        (info->si_code == SEGV_MAPERR) ? "SEGV_MAPERR (unmapped address)" :
        (info->si_code == SEGV_ACCERR) ? "SEGV_ACCERR (bad permissions)"  :
                                          "other");
    write(STDERR_FILENO, buf, len);

    /* Terminate — cannot return from SIGSEGV (would re-trigger) */
    _exit(EXIT_FAILURE);
}

int main(void)
{
    /* Need alternate stack since SIGSEGV may occur on stack overflow */
    stack_t ss = {
        .ss_sp    = altstack_buf,
        .ss_size  = sizeof(altstack_buf),
        .ss_flags = 0
    };
    sigaltstack(&ss, NULL);

    struct sigaction sa;
    sa.sa_sigaction = sigsegv_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO | SA_ONSTACK;  /* Both flags together */
    sigaction(SIGSEGV, &sa, NULL);

    printf("[Main] About to dereference NULL pointer...\n");
    fflush(stdout);

    /* Trigger SIGSEGV intentionally */
    volatile int *bad = (int *)0x12345678;
    *bad = 42;

    return 0;  /* Never reached */
}

Code Example 3 — SIGCHLD with SA_SIGINFO: Full Child Exit Info
#define _POSIX_C_SOURCE 199309L
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

static void sigchld_handler(int sig, siginfo_t *info, void *ucontext)
{
    /* UNSAFE: printf for demo only */
    printf("\n[SIGCHLD Handler]\n");
    printf("  Child PID    : %d\n",   (int)info->si_pid);
    printf("  Child UID    : %d\n",   (int)info->si_uid);

    switch (info->si_code) {
        case CLD_EXITED:
            printf("  Event        : Exited normally\n");
            printf("  Exit status  : %d\n", info->si_status);
            break;
        case CLD_KILLED:
            printf("  Event        : Killed by signal %d\n", info->si_status);
            break;
        case CLD_DUMPED:
            printf("  Event        : Killed with core dump\n");
            break;
        case CLD_STOPPED:
            printf("  Event        : Stopped by signal %d\n", info->si_status);
            break;
        case CLD_CONTINUED:
            printf("  Event        : Continued\n");
            break;
        default:
            printf("  Event        : Unknown si_code %d\n", info->si_code);
    }
    printf("  User CPU time : %ld clock ticks\n", (long)info->si_utime);
    printf("  Sys CPU time  : %ld clock ticks\n", (long)info->si_stime);

    /* Still need to call waitpid to reap the zombie */
    int status;
    waitpid(info->si_pid, &status, WNOHANG);
}

int main(void)
{
    struct sigaction sa;
    sa.sa_sigaction = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO | SA_RESTART;
    sigaction(SIGCHLD, &sa, NULL);

    /* Fork 3 children with different exit behaviours */
    for (int i = 0; i < 3; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            /* Child */
            switch (i) {
                case 0: sleep(1); printf("[Child %d] Exiting with status 42\n", i); _exit(42); break;
                case 1: sleep(2); printf("[Child %d] Exiting with status 0\n",  i); _exit(0);  break;
                case 2: sleep(3); printf("[Child %d] Killing self with SIGTERM\n",i); raise(SIGTERM); break;
            }
        }
    }

    /* Parent waits for all 3 children to exit */
    printf("[Parent] Waiting for children...\n");
    sleep(5);
    printf("[Parent] Done.\n");
    return 0;
}
Note: Even with SA_SIGINFO and full child info from siginfo_t, you must still call waitpid() to reap the zombie process and free the process table entry. The siginfo_t gives you information, not a replacement for reaping.

Interview Questions — SA_SIGINFO & siginfo_t
Q1. What is the purpose of SA_SIGINFO flag in sigaction()?
SA_SIGINFO enables a richer three-argument signal handler: void handler(int sig, siginfo_t *info, void *ucontext). The siginfo_t structure provides detailed information about the signal: the sending process’s PID and UID, the fault address for hardware signals, the child’s exit status for SIGCHLD, and more.
Q2. What is the difference between sa_handler and sa_sigaction?
Both are fields in struct sigaction, but they are part of a union — only one can be used at a time. sa_handler holds a pointer to a one-argument handler (standard). sa_sigaction holds a pointer to a three-argument handler (SA_SIGINFO). When SA_SIGINFO is set in sa_flags, you must set sa_sigaction; setting sa_handler instead will cause the pointer to be misinterpreted.
Q3. What information does si_addr provide and for which signals?
si_addr is set for hardware-generated signals: SIGSEGV, SIGBUS, SIGILL, and SIGFPE. For SIGSEGV and SIGBUS, it contains the memory address that caused the invalid access. For SIGILL and SIGFPE, it contains the address of the instruction that caused the signal.
Q4. What does si_code = SI_USER mean?
SI_USER means the signal was sent by a user-space process via kill() or raise() (not by the kernel directly). When si_code == SI_USER, the si_pid and si_uid fields are valid and contain the PID and real UID of the sending process.
Q5. When a SIGCHLD handler has SA_SIGINFO, what does si_status contain?
If si_code == CLD_EXITED, then si_status contains the exit status code passed to _exit() or exit(). If si_code == CLD_KILLED or CLD_DUMPED, then si_status contains the signal number that terminated or stopped the child.
Q6. What is the third argument (ucontext) to a SA_SIGINFO handler?
It is a pointer to a ucontext_t structure (declared as void * in the prototype). It contains the saved CPU state (program counter, stack pointer, registers) of the process at the moment the signal was delivered. This allows handlers to inspect what instruction was executing and where the stack was. It is rarely used in application code but is essential for debugging tools and sanitizers.
Q7. Can you combine SA_SIGINFO with SA_ONSTACK?
Yes. You can combine any compatible sa_flags values. For example: sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART; This registers a 3-argument handler that runs on the alternate stack and automatically restarts interrupted system calls.

Next: Interruption & Restarting of System Calls →

Chapter 21.5 — System Call Interruption ← Previous

Leave a Reply

Your email address will not be published. Required fields are marked *