一:背景

1. 讲故事

.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:
00007fbd`09313c17 483d00f0ffff cmp rax,0FFFFFFFFFFFFF000h
0:023> ? 1d
Evaluate expression: 29 = 00000000`0000001d
0:023> ~29s
*** WARNING: Unable to verify timestamp for libSystem.Native.so
libc_so!read+0x4c:
00007fbd`0933829c 483d00f0ffff cmp rax,0FFFFFFFFFFFFF000h

从字面上看是 kill,sigsend,raise 发出了携带 SI_USER 代码的 SIGABRT 信号,看起来和Linux信号机制有关,那具体是什么意思呢?这就是本篇和大家详聊的。

二:Linux 信号机制

1. 信号机制简介

简单的说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) 这句话了。

1) SIGABRT

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

2) 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 系统调用。

3) kill,sigsend,raise

熟悉 linux 的朋友应该对 killraise 方法非常熟悉,毕竟他们遵守 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_POSIX
extern 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 00007fbd`03c62a70 00007fbd`090bf635 libc_so!wait4+0x57
01 00007fbd`03c62aa0 00007fbd`090c0580 libcoreclr!PROCCreateCrashDump+0x275 [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 2307]
02 00007fbd`03c62b00 00007fbd`090be22f libcoreclr!PROCCreateCrashDumpIfEnabled+0x770 [/__w/1/s/src/coreclr/pal/src/thread/process.cpp @ 2524]
03 00007fbd`03c62b90 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 00007fbd`08db667f (T) libcoreclr!TerminateProcess+0x84 [/__w/1/s/src/coreclr/pal/inc/pal_mstypes.h @ 1249]
...
09 00007fbd`03c63950 00007fbd`08d4524e 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 00007fbd`03c63e30 00007fbd`092c91f5 libcoreclr!CorUnix::CPalThread::ThreadEntry+0x1fe [/__w/1/s/src/coreclr/pal/inc/pal.h @ 1763]
1a 00007fbd`03c63ee0 00007fbd`09348b00 libc_so!pthread_condattr_setpshared+0x515
1b 00007fbd`03c63f80 ffffffff`ffffffff libc_so!_clone+0x40
1c 00007fbd`03c63f88 00000000`00000000 0xffffffff`ffffffff

在上面的代码中我们看到了 libcoreclr!PROCAbort 函数,在 coreclr 中方法定义如下:


/*++
Function:
PROCAbort() Aborts the process after calling the shutdown cleanup handler. This function
should be called instead of calling abort() directly. Parameters:
signal - POSIX signal number Does not return
--*/
PAL_NORETURN
VOID
PROCAbort(int signal)
{
// Do any shutdown cleanup before aborting or creating a core dump
PROCNotifyProcessShutdown(); PROCCreateCrashDumpIfEnabled(signal); // Restore the SIGABORT handler to prevent recursion
SEHCleanupAbort(); // Abort the process after waiting for the core dump to complete
abort();
} VOID PROCCreateCrashDumpIfEnabled(int signal, siginfo_t* siginfo, bool serialize)
{
// If enabled, launch the create minidump utility and wait until it completes
if (!g_argvCreateDump.empty())
{
std::vector<const char*> argv(g_argvCreateDump);
...
}
}

卦中的代码逻辑非常清楚,在 abort 退出之前,先通过 PROCCreateCrashDumpIfEnabled(signal) 方法踩了一个dump,也就是说 dump 中看到的信息就是用他来填充的,可以观察 libcoreclr!g_argvCreateDump 全局变量,参考如下:


0:023> x libcoreclr!*g_argvCreateDump*
00007fbd`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 *]

2. C代码眼见为实

为了能够让大家有一个更加贴切的眼见为实,我们用 C 代码亲自演示一下,为产生 core 文件,配置如下:


root@ubuntu2404:/data2# ulimit -c unlimited
root@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; sa.sa_sigaction = sig_handler;
sa.sa_flags = SIGABRT;
sigemptyset(&sa.sa_mask); if (sigaction(SIGSEGV, &sa, NULL) == -1)
{
perror("sigaction");
return 1;
} printf("My PID: %d\n", getpid());
printf("Press Enter to send SIGABRT to myself...\n");
getchar(); kill(getpid(), SIGABRT); // 第一种方式
// raise(SIGABRT); // 第二种方式
// abort(); //第三方方式 printf("This line may not be reached.\n");
return 0;
}

ternimal 如下:


root@ubuntu2404:/data2# ./app
My PID: 7403
Press Enter to send SIGABRT to myself... Aborted (core dumped)
root@ubuntu2404:/data2#
root@ubuntu2404:/data2# ls -lh
total 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信号机制是一个必须要过的基础,调试之路艰难哈。。。

聊一聊 .NET Dump 中的 Linux信号机制的更多相关文章

  1. Linux信号机制

    Linux信号(signal) 机制分析 [摘要]本文分析了Linux内核对于信号的实现机制和应用层的相关处理.首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理.接着分析了内核 ...

  2. 利用linux信号机制调试段错误(Segment fault)

    在实际开发过程中,大家可能会遇到段错误的问题,虽然是个老问题,但是其带来的隐患是极大的,只要出现一次,程序立即崩溃中止.如果程序运行在PC中,segment fault的调试相对比较方便,因为可以通过 ...

  3. 利用linux信号机制调试段错误(Segment fault)【转】

    转自:http://blog.csdn.net/ab198604/article/details/6164517 版权声明:本文为博主原创文章,未经博主允许不得转载. 在实际开发过程中,大家可能会遇到 ...

  4. linux信号机制与python信号量

    1.信号本质 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件.在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的.信号是进程间 ...

  5. xenomai内核解析之信号signal(一)---Linux信号机制

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 1. Linux信号 1.1注册信号处理函数 ...

  6. linux信号机制 - 用户堆栈和内核堆栈的变化【转】

    转自:http://itindex.net/detail/16418-linux-%E4%BF%A1%E5%8F%B7-%E5%A0%86%E6%A0%88 此文只简单分析发送信号给用户程序后,用户堆 ...

  7. linux 信号机制

    文章目录 1. 实时信号非实时信号 2. 信号状态: 3. 信号生命周期: 4. 信号的执行和注销 信号掩码和信号处理函数的继承 信号处理函数的继承 信号掩码的继承 sigwait 与多线程 sigw ...

  8. Linux信号机制代码示例

    1 基本功能: 本Blog创建了两个进程(父子进程): 父进程: 执行文本复制操作,当收到 SIGUSR1信号后,打印出现在文件复制的进度: 子进程: 每个固定时间段向父进程发送一个 SIGUSR1 ...

  9. linux中的信号机制

    概述 Linux信号机制是在应用软件层次上对中断机制的一种模拟,信号提供了一种处理异步事件的方法,例如,终端用户输入中断键(ctrl+c),则会通过信号机制停止一个程序[1]. 这其实就是向那个程序( ...

  10. Linux信号(signal) 机制分析

    Linux信号(signal) 机制分析 [摘要]本文分析了Linux内核对于信号的实现机制和应用层的相关处理.首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理.接着分析了内核 ...

随机推荐

  1. Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!

    Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来! 1. 优势介绍 Obsidian 是一款强大的本地知识管理软件,它像一个积木盒,让你用 Markdown 笔记 ...

  2. 使用cy7c68013调试mt9v011 ov7670 摄像头测试 icamera视频采集调试

    使用cy7c68013调试mt9v011  ov7670 摄像头测试  icamera视频采集调试 采集底板选用cp601d,原理图参考icamera设计,使用cy7c68013a芯片设计,固件刷ic ...

  3. 阿里云Windows server 2016服务器Antimalware Service Executable进程占比高,cpu接近100%,强制关闭该进程实测

    问题描述:阿里云Windows server 2016服务器Antimalware Service Executable进程占比高,cpu接近100%,需要强制关闭该进程,排查问题,进入系统服务关闭, ...

  4. 【Ryan】: linux下安装ftp

    在 Linux 系统下安装 FTP 服务器可以使用多种软件,其中最常见的是 vsftpd(Very Secure FTP Daemon)和 ProFTPD(Professional FTP Daemo ...

  5. 如何用Forest方便快捷地在SpringBoot项目中对接DeepSeek

    ​ 一. 环境要求 JDK 8 / 17 SpringBoot 2.x / 3.x Forest 1.6.4+ Fastjson2 依赖配置 除了 SpringBoot 和 Lombok 等基础框架之 ...

  6. RP 点归入Set And Coupling 约束创建

    想用python脚本创建一个耦合coupling关系,需要定义control piont和被控制的surfaces.两者都可以先分别归入到set 和surface里,最后用set和surface作为c ...

  7. 【Python】ini解析ERROR:没有实例属性‘__getintem__’

    abaqus python 搭配ini 时,出现AttributeError: ConfigParser instance has no attribute 'getitem' 20230404 ed ...

  8. docker配置Nvidia环境,使用GPU

    前言 需要 nvdia driver 安装好,请参考 Ubuntu Nvidia driver驱动安装及卸载 docker 安装 配置 apt 阿里云的镜像源 sudo curl -fsSL http ...

  9. 130道基础OJ编程题之: 89~107

    130道基础OJ编程题之: 89~107 @ 目录 130道基础OJ编程题之: 89~107 89. BC101 班级成绩输入输出 99. BC102 矩阵元素定位 100. BC103 序列重组矩阵 ...

  10. 《HelloGitHub》第 108 期

    兴趣是最好的老师,HelloGitHub 让你对开源感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. github.com/521xueweihan/HelloG ...