bawejakunal
bawejakunal

Reputation: 1708

Where is the code for default signal handler in ELF binary?

I know that when we hit Ctrl+C, a SIGINT signal will be raised and the default action of terminating the process will be done by kernel. But where is the code for this termination coming from? Is it in the ELF binary or the kernel does it for us? I presume it is in the kernel and that is why we need custom handlers in our source code to override the signal behavior.

Any pointers will be much appreciated.

Upvotes: 9

Views: 1359

Answers (2)

Nemanja Boric
Nemanja Boric

Reputation: 22177

It is something kernel is doing for us. You can find all the information by reading signal.c file in kernel sources.

The point where kernel is trying to find a registered signal handler starts here: https://elixir.bootlin.com/linux/v6.11.3/source/kernel/signal.c#L2809

2257                 ka = &sighand->action[signr-1];
2258 
2259                 /* Trace actually delivered signals. */
2260                 trace_signal_deliver(signr, &ksig->info, ka);
2261 
2262                 if (ka->sa.sa_handler == SIG_IGN) /* Do nothing.  */
2263                         continue;
2264                 if (ka->sa.sa_handler != SIG_DFL) {
2265                         /* Run the handler.  */
2266                         ksig->ka = *ka;
2267 
2268                         if (ka->sa.sa_flags & SA_ONESHOT)
2269                                 ka->sa.sa_handler = SIG_DFL;
2270 
2271                         break; /* will return non-zero "signr" value */
2272                 }

So, if there's a signal handler and if it is not "ignore signal" (SIG_IGN) and if it is not "default" handler (SIG_DEF), kernel will simply mark it for being run (and depending if it's one-shot it will move handler to default handler again).

However, if there's no signal handler registered, or if it is SIG_DEF, kernel checks if maybe it needs to pause the process, and finally kernel states the following:

2330                 /*
2331                  * Anything else is fatal, maybe with a core dump.
2332                  */

https://elixir.bootlin.com/linux/v6.11.3/source/kernel/signal.c#L2885

Upvotes: 6

3442
3442

Reputation: 8576

Let's say you kill(theShell, SIGINT). What happens is something like... (not showing kernel code because it's not actually relevant)

  1. The C runtime library will take all the arguments to the system call sys_kill(), and proceed to execute the assembly code that performs a raw system call.
  2. The kernel receives arguments, performs permissions checks, etc, etc...
  3. If the process has the corresponding signal handler set to SIG_DEF, the kernel performs the corresponding default action directly and returns. If the process has the corresponding signal handler set to SIG_IGN, the signal is ignored and the system call returns. Otherwise, continue.
  4. The signal is put on the signal queue for the target process, along with some information such as the sender.
  5. Once a thread in the target process is selectable for receiving the signal, and doesn't have it masked out, the thread's context (CPU registers, stack pointer, etc...) is saved and the signal handler is invoked. If the thread was in a system call at the time of the signal's arrival, the system call returns -EINTR (for simplicity purposes) and the handler is invoked. Once the handler returns, the system call sys_sigreturn is automatically invoked, restoring the thread's state before the signal.
  6. Meanwhile step 5 happens, the kill()ing process's system call returns.

Upvotes: 2

Related Questions