ELF 二进制文件中默认信号处理程序的代码在哪里?
Where is the code for default signal handler in ELF binary?
我知道当我们按下 Ctrl+C 时,会发出一个 SIGINT 信号,并且终止进程的默认操作将由内核完成。但是这个终止的代码是从哪里来的呢?它是在 ELF 二进制文件中还是内核为我们做的?我认为它在内核中,这就是为什么我们需要在源代码中自定义处理程序来覆盖信号行为。
任何指点将不胜感激。
这是内核为我们做的事情。您可以通过阅读内核源代码中的 signal.c
文件找到所有信息。
内核试图找到已注册信号处理程序的点从这里开始:http://lxr.free-electrons.com/source/kernel/signal.c#L2257
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 }
所以,如果有一个信号处理程序并且它不是 "ignore signal" (SIG_IGN
) 并且如果它不是 "default" 处理程序 (SIG_DEF
),内核将只需将其标记为 运行(并且取决于它是否是一次性的,它将处理程序再次移动到默认处理程序)。
但是,如果没有注册信号处理程序,或者它是 SIG_DEF
,内核会检查是否需要暂停进程,最后内核声明如下:
2330 /*
2331 * Anything else is fatal, maybe with a core dump.
2332 */
假设你 kill(theShell, SIGINT)
。发生的事情就像...(不显示内核代码,因为它实际上不相关)
- C 运行时库将获取系统调用的所有参数
sys_kill()
,并继续执行执行原始系统调用的汇编代码。
- 内核接收参数、执行权限检查等...
- 如果进程将相应的信号处理程序设置为
SIG_DEF
,则内核直接执行相应的默认操作,returns。如果进程将相应的信号处理程序设置为 SIG_IGN
,则信号将被忽略并进行系统调用 returns。否则,继续。
- 信号被放入目标进程的信号队列,连同发送者等一些信息。
- 一旦目标进程中的线程可选择接收信号,并且没有将其屏蔽掉,线程的上下文(CPU 寄存器、堆栈指针等...)将被保存并调用信号处理程序。如果线程在信号到达时处于系统调用中,系统调用 returns
-EINTR
(为简单起见)和处理程序被调用。一旦处理程序 returns,就会自动调用系统调用 sys_sigreturn
,恢复信号之前的线程状态。
- 同时步骤
5
发生,kill()
ing 进程的系统调用 returns。
我知道当我们按下 Ctrl+C 时,会发出一个 SIGINT 信号,并且终止进程的默认操作将由内核完成。但是这个终止的代码是从哪里来的呢?它是在 ELF 二进制文件中还是内核为我们做的?我认为它在内核中,这就是为什么我们需要在源代码中自定义处理程序来覆盖信号行为。
任何指点将不胜感激。
这是内核为我们做的事情。您可以通过阅读内核源代码中的 signal.c
文件找到所有信息。
内核试图找到已注册信号处理程序的点从这里开始:http://lxr.free-electrons.com/source/kernel/signal.c#L2257
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 }
所以,如果有一个信号处理程序并且它不是 "ignore signal" (SIG_IGN
) 并且如果它不是 "default" 处理程序 (SIG_DEF
),内核将只需将其标记为 运行(并且取决于它是否是一次性的,它将处理程序再次移动到默认处理程序)。
但是,如果没有注册信号处理程序,或者它是 SIG_DEF
,内核会检查是否需要暂停进程,最后内核声明如下:
2330 /*
2331 * Anything else is fatal, maybe with a core dump.
2332 */
假设你 kill(theShell, SIGINT)
。发生的事情就像...(不显示内核代码,因为它实际上不相关)
- C 运行时库将获取系统调用的所有参数
sys_kill()
,并继续执行执行原始系统调用的汇编代码。 - 内核接收参数、执行权限检查等...
- 如果进程将相应的信号处理程序设置为
SIG_DEF
,则内核直接执行相应的默认操作,returns。如果进程将相应的信号处理程序设置为SIG_IGN
,则信号将被忽略并进行系统调用 returns。否则,继续。 - 信号被放入目标进程的信号队列,连同发送者等一些信息。
- 一旦目标进程中的线程可选择接收信号,并且没有将其屏蔽掉,线程的上下文(CPU 寄存器、堆栈指针等...)将被保存并调用信号处理程序。如果线程在信号到达时处于系统调用中,系统调用 returns
-EINTR
(为简单起见)和处理程序被调用。一旦处理程序 returns,就会自动调用系统调用sys_sigreturn
,恢复信号之前的线程状态。 - 同时步骤
5
发生,kill()
ing 进程的系统调用 returns。