找回密码
 立即注册
首页 资源区 代码 聊一聊 .NET Dump 中的 Linux信号机制

聊一聊 .NET Dump 中的 Linux信号机制

AlanaPasco 2025-5-28 22:05:55
一:背景

1. 讲故事

当 .NET程序 在Linux上崩溃时,我们可以配置一些参考拿到对应程序的core文件,拿到core文件后用windbg打开,往往会看到这样的一句信息 Signal SIGABRT code SI_USER (Sent by kill, sigsend, raise),参考如下:
  1. (1.1d): Signal SIGABRT code SI_USER (Sent by kill, sigsend, raise)
  2. libc_so!wait4+0x57:
  3. 00007fbd`09313c17 483d00f0ffff    cmp     rax,0FFFFFFFFFFFFF000h
  4. 0:023> ? 1d
  5. Evaluate expression: 29 = 00000000`0000001d
  6. 0:023> ~29s
  7. *** WARNING: Unable to verify timestamp for libSystem.Native.so
  8. libc_so!read+0x4c:
  9. 00007fbd`0933829c 483d00f0ffff    cmp     rax,0FFFFFFFFFFFFF000h
复制代码
从字面上看是 kill,sigsend,raise 发出了携带 SI_USER 代码的 SIGABRT 信号,看起来和Linux信号机制有关,那具体是什么意思呢?这就是本篇和大家详聊的。
二:Linux 信号机制

1. 信号机制简介

简单的说Linux信号是一种进程间通信机制,大概可以做三件事情。

  • 通知进程发生了某种事件,比如:段错误。
  • 允许进程间发送简单的消息。
  • 控制进程行为,比如:终止、暂停、继续等。
在 linux 上有60多个信号,默认能产生core文件的有11个,这也是我们最关心的,整理成表格如下:
信号名称信号编号说明SIGQUIT3通常由 Ctrl+\ 触发SIGILL4非法指令SIGABRT6由 abort() 函数产生SIGFPE8浮点异常SIGSEGV11段错误(非法内存访问)SIGBUS7总线错误(内存访问对齐问题等)SIGSYS31错误的系统调用SIGTRAP5跟踪/断点陷阱SIGXCPU24超出 CPU 时间限制SIGXFSZ25超出文件大小限制SIGEMT7EMT 指令(某些架构)有了这些基础之后就可以解读 Signal SIGABRT code SI_USER (Sent by kill, sigsend, raise) 这句话了。
1) SIGABRT

全称 signal abort ,是一种能产生 core 的信号。
2) SI_USER

在 linux 源码中有这样一句代码(type == PIDTYPE_PID) ? SI_TKILL : SI_USER,参考如下:
  1. static void prepare_kill_siginfo(int sig, struct kernel_siginfo *info,enum pid_type type)
  2. {
  3.         clear_siginfo(info);
  4.         info->si_signo = sig;
  5.         info->si_errno = 0;
  6.         info->si_code = (type == PIDTYPE_PID) ? SI_TKILL : SI_USER;
  7.         info->si_pid = task_tgid_vnr(current);
  8.         info->si_uid = from_kuid_munged(current_user_ns(), current_uid());
  9. }
复制代码
代码中的 kernel_siginfo.si_code 字段用来表示信号的来源,比如说 SI_USER 表示信号来源于用户进程,而后者的 SI_TKILL 表示信号来源于 tgkill,tkill 系统调用。
3) kill,sigsend,raise

熟悉 linux 的朋友应该对 kill 和 raise 方法非常熟悉,毕竟他们遵守 POSIX 标准,至于他们有什么区别,看签名就知道了。。。
  1. /* Raise signal SIG, i.e., send SIG to yourself.  */
  2. extern int raise (int __sig) __THROW;
  3. /* Send signal SIG to process number PID.  If PID is zero,
  4.    send SIG to all processes in the current process's process group.
  5.    If PID is < -1, send SIG to all processes in process group - PID.  */
  6. #ifdef __USE_POSIX
  7. extern int kill (__pid_t __pid, int __sig) __THROW;
  8. #endif /* Use POSIX.  */
复制代码
相比前面的函数,这个 sigsend 就不是 POSIX 标准了,只在部分Unix上可用,比如 Solaris,SunOS,不过功能还是很强大的,不仅可以指定 pid,还可以指定 pidgroup 以及 user 来批量的 kill 进程,这里做个了解即可,签名如下:
  1. int sigsend(idtype_t idtype, id_t id, int sig);
复制代码
这些信息汇总之后更准确的意思就是:你的程序可能调用了 kill(SIGABRT) ,raise(SIGABRT),abort 引发的程序崩溃,那是不是这样的呢?可以用 windbg 的 ~* k 观察每个线程的调用栈,最终还真给找到了。
  1. 0:023> k
  2. # Child-SP          RetAddr               Call Site
  3. 00 00007fbd`03c62a70 00007fbd`090bf635     libc_so!wait4+0x57
  4. 01 00007fbd`03c62aa0 00007fbd`090c0580     libcoreclr!PROCCreateCrashDump+0x275 [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 2307]
  5. 02 00007fbd`03c62b00 00007fbd`090be22f     libcoreclr!PROCCreateCrashDumpIfEnabled+0x770 [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 2524]
  6. 03 00007fbd`03c62b90 00007fbd`090be159 (T) libcoreclr!PROCAbort+0x2f [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 2555]
  7. 04 (Inline Function) --------`-------- (T) libcoreclr!PROCEndProcess+0x7c [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 1352]
  8. 05 00007fbd`03c62bb0 00007fbd`08db667f (T) libcoreclr!TerminateProcess+0x84 [/__w/1/s/src/coreclr/pal/inc/pal_mstypes.h @ 1249]
  9. ...
  10. 09 00007fbd`03c63950 00007fbd`08d4524e     libcoreclr!UMEntryThunk::Terminate+0x38 [/__w/1/s/src/coreclr/inc/clrtypes.h @ 260]
  11. 0a (Inline Function) --------`--------     libcoreclr!InteropSyncBlockInfo::FreeUMEntryThunk+0x24 [/__w/1/s/src/coreclr/vm/syncblk.cpp @ 119]
  12. 19 00007fbd`03c63e30 00007fbd`092c91f5     libcoreclr!CorUnix::CPalThread::ThreadEntry+0x1fe [/__w/1/s/src/coreclr/pal/inc/pal.h @ 1763]
  13. 1a 00007fbd`03c63ee0 00007fbd`09348b00     libc_so!pthread_condattr_setpshared+0x515
  14. 1b 00007fbd`03c63f80 ffffffff`ffffffff     libc_so!_clone+0x40
  15. 1c 00007fbd`03c63f88 00000000`00000000     0xffffffff`ffffffff
复制代码
在上面的代码中我们看到了 libcoreclr!PROCAbort 函数,在 coreclr 中方法定义如下:
  1. /*++
  2. Function:
  3.   PROCAbort()
  4.   Aborts the process after calling the shutdown cleanup handler. This function
  5.   should be called instead of calling abort() directly.
  6. Parameters:
  7.   signal - POSIX signal number
  8.   Does not return
  9. --*/
  10. PAL_NORETURN
  11. VOID
  12. PROCAbort(int signal)
  13. {
  14.     // Do any shutdown cleanup before aborting or creating a core dump
  15.     PROCNotifyProcessShutdown();
  16.     PROCCreateCrashDumpIfEnabled(signal);
  17.     // Restore the SIGABORT handler to prevent recursion
  18.     SEHCleanupAbort();
  19.     // Abort the process after waiting for the core dump to complete
  20.     abort();
  21. }
  22. VOID PROCCreateCrashDumpIfEnabled(int signal, siginfo_t* siginfo, bool serialize)
  23. {
  24.         // If enabled, launch the create minidump utility and wait until it completes
  25.         if (!g_argvCreateDump.empty())
  26.         {
  27.                 std::vector<const char*> argv(g_argvCreateDump);
  28.     ...
  29.         }
  30. }
复制代码
卦中的代码逻辑非常清楚,在 abort 退出之前,先通过 PROCCreateCrashDumpIfEnabled(signal) 方法踩了一个dump,也就是说 dump 中看到的信息就是用他来填充的,可以观察 libcoreclr!g_argvCreateDump 全局变量,参考如下:
  1. 0:023> x libcoreclr!*g_argvCreateDump*
  2. 00007fbd`09192360 libcoreclr!g_argvCreateDump = {size=8}
  3. 0:023> dx -r1 (*((libcoreclr!std::vector<const char *, std::allocator<const char *> > *)0x7fbd09192360))
  4. (*((libcoreclr!std::vector<const char *, std::allocator<const char *> > *)0x7fbd09192360))                 : {size=8} [Type: std::vector<const char *, std::allocator<const char *> >]
  5.     [<Raw View>]     [Type: std::vector<const char *, std::allocator<const char *> >]
  6.     [size]           : 8
  7.     [capacity]       : 8
  8.     [0]              : 0x5555b5d71140 : "/usr/share/dotnet/shared/Microsoft.NETCore.App/8.0.15/createdump" [Type: char *]
  9.     [1]              : 0x7fbd08b61d8f : "--name" [Type: char *]
  10.     [2]              : 0x7ffd1b7e1cec : "/db/xxxx/crash.dmp" [Type: char *]
  11.     [3]              : 0x7fbd08b6ce5f : "--full" [Type: char *]
  12.     [4]              : 0x7fbd08b4c7ee : "--diag" [Type: char *]
  13.     [5]              : 0x7fbd08b58630 : "--crashreport" [Type: char *]
  14.     [6]              : 0x5555b5dd7230 : "1" [Type: char *]
  15.     [7]              : 0x0 [Type: char *]
复制代码
2. C代码眼见为实

为了能够让大家有一个更加贴切的眼见为实,我们用 C 代码亲自演示一下,为产生 core 文件,配置如下:
  1. root@ubuntu2404:/data2# ulimit -c unlimited
  2. root@ubuntu2404:/data2# echo /data2/core-%e-%p-%t  | sudo tee /proc/sys/kernel/core_pattern
  3. /data2/core-%e-%p-%t
复制代码
配置好之后,大家可以使用 abort,kill,raise 这三个方法的任何一个,这里我就用 kill 来演示吧。
  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <unistd.h>
  4. void sig_handler(int signo, siginfo_t *info, void *context)
  5. {
  6.     fprintf(stderr, "Received signal: %d (sent by PID: %d, UID: %d)\n",
  7.             signo, info->si_pid, info->si_uid);
  8. }
  9. int main()
  10. {
  11.     struct sigaction sa;
  12.     sa.sa_sigaction = sig_handler;
  13.     sa.sa_flags = SIGABRT;
  14.     sigemptyset(&sa.sa_mask);
  15.     if (sigaction(SIGSEGV, &sa, NULL) == -1)
  16.     {
  17.         perror("sigaction");
  18.         return 1;
  19.     }
  20.     printf("My PID: %d\n", getpid());
  21.     printf("Press Enter to send SIGABRT to myself...\n");
  22.     getchar();
  23.     kill(getpid(), SIGABRT);  // 第一种方式
  24.     // raise(SIGABRT);        // 第二种方式
  25.     //  abort();              //第三方方式
  26.     printf("This line may not be reached.\n");
  27.     return 0;
  28. }
复制代码
ternimal 如下:
  1. root@ubuntu2404:/data2# ./app
  2. My PID: 7403
  3. Press Enter to send SIGABRT to myself...
  4. Aborted (core dumped)
  5. root@ubuntu2404:/data2#
  6. root@ubuntu2404:/data2# ls -lh
  7. total 160K
  8. -rwxr-xr-x 1 root root  21K May 27 10:25 app
  9. -rw-r--r-- 1 root root  813 May 27 10:25 app.c
  10. -rw------- 1 root root 432K May 27 10:25 core-app-7403-1748312729
复制代码
用 windbg 打开 core-app-7403-1748312729 文件,熟悉的画面又回来了,哈哈。截图如下:
1.png

三:总结

要分析linux 上的.NET程序崩溃,理解Linux信号机制是一个必须要过的基础,调试之路艰难哈。。。
2.jpg


来源:新程序网络收集,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册