How to Determine If Code Is Running in Signal-Handler Context

How to determine if code is running in signal-handler context?

Apparently, newer Linux/x86 (probably since some 2.6.x kernel) calls signal handlers from the vdso. You could use this fact to inflict the following horrible hack upon the unsuspecting world:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>

#include <unistd.h>

uintmax_t vdso_start = 0;
uintmax_t vdso_end = 0; /* actually, next byte */

int check_stack_for_vdso(uint32_t *esp, size_t len)
{
size_t i;

for (i = 0; i < len; i++, esp++)
if (*esp >= vdso_start && *esp < vdso_end)
return 1;

return 0;
}

void handler(int signo)
{
uint32_t *esp;

__asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
/* XXX only for demonstration, don't call printf from a signal handler */
printf("handler: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
}

void parse_maps()
{
FILE *maps;
char buf[256];
char path[7];
uintmax_t start, end, offset, inode;
char r, w, x, p;
unsigned major, minor;

maps = fopen("/proc/self/maps", "rt");
if (maps == NULL)
return;

while (!feof(maps) && !ferror(maps)) {
if (fgets(buf, 256, maps) != NULL) {
if (sscanf(buf, "%jx-%jx %c%c%c%c %jx %u:%u %ju %6s",
&start, &end, &r, &w, &x, &p, &offset,
&major, &minor, &inode, path) == 11) {
if (!strcmp(path, "[vdso]")) {
vdso_start = start;
vdso_end = end;
break;
}
}
}
}

fclose(maps);

printf("[vdso] at %jx-%jx\n", vdso_start, vdso_end);
}

int main()
{
struct sigaction sa;
uint32_t *esp;

parse_maps();
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = handler;
sa.sa_flags = SA_RESTART;

if (sigaction(SIGUSR1, &sa, NULL) < 0) {
perror("sigaction");
exit(1);
}

__asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
printf("before kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));

kill(getpid(), SIGUSR1);

__asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
printf("after kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));

return 0;
}

SCNR.

How to know which line of code was being executed when a signal is received

You can use caller in list context to get the package, file and line number of the place that the current sub got called from.

$SIG{ALRM} = sub {
my ($pkg, $file, $line) = caller;

CORE::say $line;
die;
};

alarm 2;

while (1) {
1;
}

This will output 11 (if I counted correctly, in my file it's 1740, and the $SIG line is 1730.

It also works with other signal handlers, like warn.

$SIG{__WARN__} = sub {
my ($pkg, $file, $line) = caller;

CORE::say $line;
};

warn 'foo';

This will output 7

Note that your code has a syntax error. You are assigning a hash reference as a signal handler, not a sub reference!

execution context of signal handler in python

The answer you're looking for is right in the python signal documentation:

Python signal handlers are always executed in the main Python thread, even if the signal was received in another thread.

Also:

the low-level signal handler sets a flag which tells the virtual machine to execute the corresponding Python signal handler at a later point

So when a signal is received, the handler doesn't execute alongside the code in the while loop. Instead, the virtual machine executing your code is told to run the signal handling code 'soon', which could be after X number of bytecode instructions, so essentially your loop goes on pause while the handler code is running. Changing your code a little demonstrates this:

def sig_handler(signal_frame, num):
print('handler PID: {}'.format(os.getpid()))
print('current thread identity: {}'.format(threading.current_thread().ident))
time.sleep(5) # we put a long delay here

signal.signal(signal.SIGTERM, sig_handler)

try:
print('main execution PID: {}'.format(os.getpid()))
print('current thread identity: {}'.format(threading.current_thread().ident))
while True:
time.sleep(1) # sleep less now
print('Hello')
except KeyboardInterrupt:
print('Good bye')

Now, when you send your SIGTERM, you'll notice the execution of your while loop pauses for 5s.

How can I get execution context in a signal handler?

If you do want to follow the signal handler route it is not hard to get the address of the instruction that causes the signal handler to run. When you create your signal handler set the SA_SIGINFO bit in the flags setting. Then when you write your handler it will have access to a siginfo_t structure that includes the field si_addr which is the address of the memory location that triggered the exception.

Status of threads when signal handler runs

The specification is pretty clear how signals and threads interact:

Signals generated for the process shall be delivered to exactly one of those threads within the process which is in a call to a sigwait() function selecting that signal or has not blocked delivery of the signal.

As the signal is delivered to exactly one thread, other threads are unaffected (and keep running).

Getting the saved instruction pointer address from a signal handler

/* sigsegv.c */
/**
* This source file is used to print out a stack-trace when your program
* segfaults. It is relatively reliable and spot-on accurate.
*
* This code is in the public domain. Use it as you see fit, some credit
* would be appreciated, but is not a prerequisite for usage. Feedback
* on it's use would encourage further development and maintenance.
*
* Due to a bug in gcc-4.x.x you currently have to compile as C++ if you want
* demangling to work.
*
* Please note that it's been ported into my ULS library, thus the check for
* HAS_ULSLIB and the use of the sigsegv_outp macro based on that define.
*
* Author: Jaco Kroon <jaco@kroon.co.za>
*
* Copyright (C) 2005 - 2010 Jaco Kroon
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

/* Bug in gcc prevents from using CPP_DEMANGLE in pure "C" */
#if !defined(__cplusplus) && !defined(NO_CPP_DEMANGLE)
#define NO_CPP_DEMANGLE
#endif

#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <ucontext.h>
#include <dlfcn.h>
#ifndef NO_CPP_DEMANGLE
#include <cxxabi.h>
#ifdef __cplusplus
using __cxxabiv1::__cxa_demangle;
#endif
#endif

#ifdef HAS_ULSLIB
#include "uls/logger.h"
#define sigsegv_outp(x) sigsegv_outp(,gx)
#else
#define sigsegv_outp(x, ...) fprintf(stderr, x "\n", ##__VA_ARGS__)
#endif

#if defined(REG_RIP)
# define SIGSEGV_STACK_IA64
# define REGFORMAT "%016lx"
#elif defined(REG_EIP)
# define SIGSEGV_STACK_X86
# define REGFORMAT "%08x"
#else
# define SIGSEGV_STACK_GENERIC
# define REGFORMAT "%x"
#endif

static void signal_segv(int signum, siginfo_t* info, void*ptr) {
static const char *si_codes[3] = {"", "SEGV_MAPERR", "SEGV_ACCERR"};

int i, f = 0;
ucontext_t *ucontext = (ucontext_t*)ptr;
Dl_info dlinfo;
void **bp = 0;
void *ip = 0;

sigsegv_outp("Segmentation Fault!");
sigsegv_outp("info.si_signo = %d", signum);
sigsegv_outp("info.si_errno = %d", info->si_errno);
sigsegv_outp("info.si_code = %d (%s)", info->si_code, si_codes[info->si_code]);
sigsegv_outp("info.si_addr = %p", info->si_addr);
for(i = 0; i < NGREG; i++)
sigsegv_outp("reg[%02d] = 0x" REGFORMAT, i, ucontext->uc_mcontext.gregs[i]);

#ifndef SIGSEGV_NOSTACK
#if defined(SIGSEGV_STACK_IA64) || defined(SIGSEGV_STACK_X86)
#if defined(SIGSEGV_STACK_IA64)
ip = (void*)ucontext->uc_mcontext.gregs[REG_RIP];
bp = (void**)ucontext->uc_mcontext.gregs[REG_RBP];
#elif defined(SIGSEGV_STACK_X86)
ip = (void*)ucontext->uc_mcontext.gregs[REG_EIP];
bp = (void**)ucontext->uc_mcontext.gregs[REG_EBP];
#endif

sigsegv_outp("Stack trace:");
while(bp && ip) {
if(!dladdr(ip, &dlinfo))
break;

const char *symname = dlinfo.dli_sname;

#ifndef NO_CPP_DEMANGLE
int status;
char * tmp = __cxa_demangle(symname, NULL, 0, &status);

if (status == 0 && tmp)
symname = tmp;
#endif

sigsegv_outp("% 2d: %p <%s+%lu> (%s)",
++f,
ip,
symname,
(unsigned long)ip - (unsigned long)dlinfo.dli_saddr,
dlinfo.dli_fname);

#ifndef NO_CPP_DEMANGLE
if (tmp)
free(tmp);
#endif

if(dlinfo.dli_sname && !strcmp(dlinfo.dli_sname, "main"))
break;

ip = bp[1];
bp = (void**)bp[0];
}
#else
sigsegv_outp("Stack trace (non-dedicated):");
sz = backtrace(bt, 20);
strings = backtrace_symbols(bt, sz);
for(i = 0; i < sz; ++i)
sigsegv_outp("%s", strings[i]);
#endif
sigsegv_outp("End of stack trace.");
#else
sigsegv_outp("Not printing stack strace.");
#endif
_exit (-1);
}

static void __attribute__((constructor)) setup_sigsegv() {
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_sigaction = signal_segv;
action.sa_flags = SA_SIGINFO;
if(sigaction(SIGSEGV, &action, NULL) < 0)
perror("sigaction");
}

$ g++ -fPIC -shared -o libsigsegv.so -ldl sigsegv

$ export LD_PRELOAD=/path/to/libsigsegv.so

I found this code on a LUG. Couldn't get to the page to point the URL here, so pasted the whole code. This code prints a small stack trace when SIGSEGV occurs. Not sure if there is some other way that does not use ucontext_t.

When Are Signals Handled?

Theoretically, let's say process A is running and it sent a signal to process B, When process B starts running it might never run in kernel mode those never see the signal and handle it.

Linux is a preemptive multitasking operating system. This means that the kernel gives every process a time slice and the CPU will receive a hardware interrupt in regular time intervals, which will return it to kernel-mode, so that the kernel can, for example, give a time slice to a different process.

Therefore, the situation you describe (that a process will run forever in user-mode and never reach kernel-mode) will never occur in a preemptive multitasking operating system such as Linux.



Related Topics



Leave a reply



Submit