.NET Dump 中的 Linux 信号机制
一:背景
讲故事当 .NET 程序 在 Linux 上崩溃时,我们可以配置一些参考拿到对应程序的 core 文件,拿到 core 文件后用 windbg 打开,往往会看到这样的一句信息 Signal SIGABRT code SI_USER (Sent by kill, sigsend, raise),参考如下:
(1.1d): Signal SIGABRT code SI_USER (Sent by kill, sigsend, raise)libc_so!wait4+0x57:00007fbd09313c17 483d00f0ffff cmp rax,0FFFFFFFFFFFFF000h 0:023> ? 1d Evaluate expression: 29 = 00000000
0000001d0:023> ~29s*** WARNING: Unable to verify timestamp for libSystem.Native.solibc_so!read+0x4c:00007fbd`0933829c 483d00f0ffff cmp rax,0FFFFFFFFFFFFF000h
从字面上看是 kill,sigsend,raise 发出了携带 SI_USER 代码的 SIGABRT 信号,看起来和 Linux 信号机制有关,那具体是什么意思呢?这就是本篇和大家详聊的。
二:Linux 信号机制
信号机制简介简单的说 Linux 信号是一种进程间通信机制,大概可以做三件事情。
通知进程发生了某种事件,比如:段错误。允许进程间发送简单的消息。控制进程行为,比如:终止、暂停、继续等。在 linux 上有 60 多个信号,默认能产生 core 文件的有 11 个,这也是我们最关心的,整理成表格如下:
信号名称 信号编号 说明 SIGQUIT 3 通常由 Ctrl+\ 触发 SIGILL 4 非法指令 SIGABRT 6 由 abort() 函数产生 SIGFPE 8 浮点异常 SIGSEGV 11 段错误(非法内存访问)SIGBUS 7 总线错误(内存访问对齐问题等)SIGSYS 31 错误的系统调用 SIGTRAP 5 跟踪/断点陷阱 SIGXCPU 24 超出 CPU 时间限制 SIGXFSZ 25 超出文件大小限制 SIGEMT 7 EMT 指令(某些架构)有了这些基础之后就可以解读 Signal SIGABRT code SI_USER (Sent by kill, sigsend, raise) 这句话了。
SIGABRT 全称 signal abort ,是一种能产生 core 的信号。
SI_USER 在 linux 源码中有这样一句代码(type == PIDTYPE_PID) ? SI_TKILL : SI_USER,参考如下:
static void prepare_kill_siginfo(int sig, struct kernel_siginfo *info,enum pid_type type){clear_siginfo(info);info->si_signo = sig;info->si_errno = 0;info->si_code = (type == PIDTYPE_PID) ? SI_TKILL : SI_USER;info->si_pid = task_tgid_vnr(current);info->si_uid = from_kuid_munged(current_user_ns(), current_uid());}
代码中的 kernel_siginfo.si_code 字段用来表示信号的来源,比如说 SI_USER 表示信号来源于用户进程,而后者的 SI_TKILL 表示信号来源于 tgkill,tkill 系统调用。
kill,sigsend,raise 熟悉 linux 的朋友应该对 kill 和 raise 方法非常熟悉,毕竟他们遵守 POSIX 标准,至于他们有什么区别,看签名就知道了。。。
/* Raise signal SIG, i.e., send SIG to yourself. */extern int raise (int __sig) __THROW;
/* Send signal SIG to process number PID. If PID is zero,send SIG to all processes in the current process's process group.If PID is < -1, send SIG to all processes in process group - PID. /#ifdef __USE_POSIXextern int kill (__pid_t __pid, int __sig) __THROW;#endif / Use POSIX. */
相比前面的函数,这个 sigsend 就不是 POSIX 标准了,只在部分 Unix 上可用,比如 Solaris,SunOS,不过功能还是很强大的,不仅可以指定 pid,还可以指定 pidgroup 以及 user 来批量的 kill 进程,这里做个了解即可,签名如下:
int sigsend(idtype_t idtype, id_t id, int sig);
这些信息汇总之后更准确的意思就是:你的程序可能调用了 kill(SIGABRT) ,raise(SIGABRT),abort 引发的程序崩溃,那是不是这样的呢?可以用 windbg 的 ~* k 观察每个线程的调用栈,最终还真给找到了。
0:023> k
Child-SP RetAddr Call Site
00 00007fbd03c62a70 00007fbd
090bf635 libc_so!wait4+0x5701 00007fbd03c62aa0 00007fbd
090c0580 libcoreclr!PROCCreateCrashDump+0x275 [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 2307]02 00007fbd03c62b00 00007fbd
090be22f libcoreclr!PROCCreateCrashDumpIfEnabled+0x770 [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 2524]03 00007fbd03c62b90 00007fbd
090be159 (T) libcoreclr!PROCAbort+0x2f [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 2555]04 (Inline Function) ---------------- (T) libcoreclr!PROCEndProcess+0x7c [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 1352] 05 00007fbd
03c62bb0 00007fbd08db667f (T) libcoreclr!TerminateProcess+0x84 [/__w/1/s/src/coreclr/pal/inc/pal_mstypes.h @ 1249] ... 09 00007fbd
03c63950 00007fbd08d4524e libcoreclr!UMEntryThunk::Terminate+0x38 [/__w/1/s/src/coreclr/inc/clrtypes.h @ 260] 0a (Inline Function) --------
-------- libcoreclr!InteropSyncBlockInfo::FreeUMEntryThunk+0x24 [/__w/1/s/src/coreclr/vm/syncblk.cpp @ 119]19 00007fbd03c63e30 00007fbd
092c91f5 libcoreclr!CorUnix::CPalThread::ThreadEntry+0x1fe [/__w/1/s/src/coreclr/pal/inc/pal.h @ 1763]1a 00007fbd03c63ee0 00007fbd
09348b00 libc_so!pthread_condattr_setpshared+0x5151b 00007fbd03c63f80 ffffffff
ffffffff libc_so!_clone+0x401c 00007fbd03c63f88 00000000
00000000 0xffffffff`ffffffff
在上面的代码中我们看到了 libcoreclr!PROCAbort 函数,在 coreclr 中方法定义如下:
/*++Function:PROCAbort()
Aborts the process after calling the shutdown cleanup handler. This functionshould be called instead of calling abort() directly.
Parameters:signal - POSIX signal number
Does not return--*/PAL_NORETURNVOIDPROCAbort(int signal){// Do any shutdown cleanup before aborting or creating a core dumpPROCNotifyProcessShutdown();
}
VOID PROCCreateCrashDumpIfEnabled(int signal, siginfo_t* siginfo, bool serialize){// If enabled, launch the create minidump utility and wait until it completesif (!g_argvCreateDump.empty()){std::vector<const char*> argv(g_argvCreateDump);...}}
卦中的代码逻辑非常清楚,在 abort 退出之前,先通过 PROCCreateCrashDumpIfEnabled(signal) 方法踩了一个 dump,也就是说 dump 中看到的信息就是用他来填充的,可以观察 libcoreclr!g_argvCreateDump 全局变量,参考如下:
0:023> x libcoreclr!g_argvCreateDump00007fbd`09192360 libcoreclr!g_argvCreateDump = {size=8}0:023> dx -r1 (*((libcoreclr!std::vector<const char *, std::allocator<const char *> > )0x7fbd09192360))(((libcoreclr!std::vector<const char *, std::allocator<const char *> > *)0x7fbd09192360)) : {size=8} [Type: std::vector<const char *, std::allocator<const char *> >][<Raw View>] [Type: std::vector<const char *, std::allocator<const char *> >][size] : 8[capacity] : 8[0] : 0x5555b5d71140 : "/usr/share/dotnet/shared/Microsoft.NETCore.App/8.0.15/createdump" [Type: char *][1] : 0x7fbd08b61d8f : "--name" [Type: char *][2] : 0x7ffd1b7e1cec : "/db/xxxx/crash.dmp" [Type: char *][3] : 0x7fbd08b6ce5f : "--full" [Type: char *][4] : 0x7fbd08b4c7ee : "--diag" [Type: char *][5] : 0x7fbd08b58630 : "--crashreport" [Type: char *][6] : 0x5555b5dd7230 : "1" [Type: char *][7] : 0x0 [Type: char *]
C 代码眼见为实为了能够让大家有一个更加贴切的眼见为实,我们用 C 代码亲自演示一下,为产生 core 文件,配置如下:
root@ubuntu2404:/data2# ulimit -c unlimitedroot@ubuntu2404:/data2# echo /data2/core-%e-%p-%t | sudo tee /proc/sys/kernel/core_pattern/data2/core-%e-%p-%t
配置好之后,大家可以使用 abort,kill,raise 这三个方法的任何一个,这里我就用 kill 来演示吧。
#include <stdio.h>#include <signal.h>#include <unistd.h>
void sig_handler(int signo, siginfo_t *info, void *context){fprintf(stderr, "Received signal: %d (sent by PID: %d, UID: %d)\n",signo, info->si_pid, info->si_uid);}
int main(){struct sigaction sa;
}
ternimal 如下:
root@ubuntu2404:/data2# ./appMy PID: 7403Press Enter to send SIGABRT to myself...
Aborted (core dumped)root@ubuntu2404:/data2#root@ubuntu2404:/data2# ls -lhtotal 160K-rwxr-xr-x 1 root root 21K May 27 10:25 app-rw-r--r-- 1 root root 813 May 27 10:25 app.c-rw------- 1 root root 432K May 27 10:25 core-app-7403-1748312729
用 windbg 打开 core-app-7403-1748312729 文件,熟悉的画面又回来了,哈哈。截图如下:
三:总结要分析 linux 上的.NET 程序崩溃,理解 Linux 信号机制是一个必须要过的基础,调试之路艰难哈。。。
行业拓展
分享一个面向研发人群使用的前后端分离的低代码软件——JNPF。
基于 Java Boot/.Net Core 双引擎,它适配国产化,支持主流数据库和操作系统,提供五十几种高频预制组件,内置了常用的后台管理系统使用场景和实用模版,通过简单的拖拉拽操作,开发者能够高效完成软件开发,提高开发效率,减少代码编写工作。
JNPF 基于 SpringBoot+Vue.js,提供了一个适合所有水平用户的低代码学习平台,无论是有经验的开发者还是编程新手,都可以在这里找到适合自己的学习路径。
此外,JNPF 支持全源码交付,完全支持根据公司、项目需求、业务需求进行二次改造开发或内网部署,具备多角色门户、登录认证、组织管理、角色授权、表单设计、流程设计、页面配置、报表设计、门户配置、代码生成工具等开箱即用的在线服务。
评论