Something went wrong, terribly wrong. An ELF was working at its workbench and wanted to grab a screwdriver. As he was sure he had placed his tool at the top left of the workbench, he did not look properly, and he grabbed only into thin air. The workbench was shorter than he though, so he stumbled, fell, and... well... there was a running table saw. I will not stress your imagination even more... Let's just say that the ELF is well and alive, but he is still recovering in the ELF hospital. To prevent such accidents in the future, the ELF senate decided to build self-extending workbenches that grows in the right direction if an ELF grabs beside the bench. A very specific solution, to a very specific problem. But as I already said, ELFs are technocrats that want to solve every problem with the right piece of technology.
Problems are a problem. Sometimes, and especially if you are a beginner C programmer, weird bugs are an annoying hurdle to getting things done. And today, we want to show you a (not so seriously meant) way to deal with such bugs. We will build a self-healing program, which just handles your faulty code for you. On this journey, you will learn a few details about Unix signals and their concrete implementation in Linux. The signal(7) man page gives a good overview about the topic of signals.
We will handle two kinds of bugs that might arise in your programs:
Invalid memory accesses that provoke a segmentation fault. This can happen, for example if you build a pointer out of thin air and dereference it. In this case, the memory management unit will catch this invalid memory access, generate a page fault, which the operating system forwards as a SIGSEGV
signal to our process. The default action to take on SIGSEGV
is to abort the program.
Invalid jump that provokes an illegal instruction exception. If your program tries to execute bytes that do not look familiar to your CPU, you will get an illegal instruction trap. The trap is handled, like the page fault, by the operating system and leads by default to an abort of the process. The signal is SIGILL
.
In a nutshell, we want to install two signal handlers with sigaction(2) to catch these errors and handle them accordingly. For the segmentation fault, we use mmap(2) to allocate an anonymous page-sized mapping at the accessed memory region. After returning from the handler, the process will repeat the previously invalid access, which will now succeed. For illegal instructions, we manipulate the process state, before returning, to jump over the invalid instruction.
Signals, in their original idea, are very close to hardware interrupts and traps. They can in principle happen everywhere in your program and interrupt the current control flow. So instead of fetching the next instruction from your currently running function, the operating system forces the thread to execute the previously defined signal handler. While the signal handler is running, the "main"-program is suspended. Therefore, we can say that a signal-handler execution is a function call that is forced upon the main program.
As there are multiple signals that the operating system can send to our process, the process has to inform the operating system, beforehand, which handler is connected to which signal. And we can do this with sigaction(2), which configures a signal with a struct sigaction
:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
First, we see that there are two handler fields we can configure: sa_handler
only takes the signal number, while sa_sigaction
additionally gets more information about the signal with siginfo_t
and about the interrupted program with ucontext_t
(cast to void*
). For our goal, we have to use sa_sigaction
, which we select by setting the SA_SIGINFO
flag.
On the kernel side, each process has a struct sighand_struct, which stores information about the configured signal handlers. The table is manipulated in do_sigaction. Afterwards, the signal is delivered on x86 in handle_signal.
SIGSEGV
handler that maps pages to info->si_addr
.SIGILL
handler that jumps 4 bytes forward and hopefully over the illegal instruction.do_exit
flag.Last modified: 2023-12-01 15:52:27.859821, Last author: , Permalink: /p/advent-06-sigaction
Vacancies of TU Braunschweig
Career Service' Job Exchange
Merchandising
Term Dates
Courses
Degree Programmes
Information for Freshman
TUCard
Technische Universität Braunschweig
Universitätsplatz 2
38106 Braunschweig
P. O. Box: 38092 Braunschweig
GERMANY
Phone: +49 (0) 531 391-0