捕捉信号SIGSEGV并回溯栈帧

本文介绍了一种在嵌入式应用程序开发过程中,当程序发生段错误时,通过捕捉信号SIGSEGV并在其处理函数中打印出函数栈帧链的方法来辅助调试程序的技术。适用于ARM和X86平台。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引出问题

在嵌入式应用程序开发过程中,调试一直是个老大难问题 -- 由于环境的限制,当程序发生段错误时不能很好的定位到底是哪里出现了错误,如果在程序发生段错误时能够清晰明了地看到程序的栈帧链,那无疑是雪中送炭。本文就捕捉信号SIGSEGV并在该信号的处理函数中打印出函数栈帧链来帮助我们调试程序。

本文的程序适合ARM和X86平台。

回溯栈帧原理

理解函数栈帧的布局后,那么自然明白回溯栈帧的原理了,这里不多解释了,直接上图(来自网络):

                                                                 x86函数栈帧结构

 

                                                                ARM函数栈帧结构

代码sigsegv.c

#ifndef _GNU_SOURCE
	#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <ucontext.h>


/* 纯C环境下,不定义宏NO_CPP_DEMANGLE */
#if (!defined(__cplusplus)) && (!defined(NO_CPP_DEMANGLE))
#define NO_CPP_DEMANGLE
#endif

#ifndef NO_CPP_DEMANGLE
    #include <cxxabi.h>
    #ifdef __cplusplus
    	using __cxxabiv1::__cxa_demangle;
    #endif
#endif

#ifdef HAS_ULSLIB
	#include <uls/logger.h>
	#define sigsegv_outp(x)	sigsegv_outp(, gx)
#else
	#define sigsegv_outp(x, ...) 	fprintf(stderr, x"\n", ##__VA_ARGS__)
#endif

#if (defined __x86_64__)
	#define REGFORMAT   "%016lx"	
#elif (defined __i386__)
	#define REGFORMAT   "%08x"
#elif (defined __arm__)
	#define REGFORMAT   "%lx"
#endif

static void print_reg(ucontext_t *uc) 
{
#if (defined __x86_64__) || (defined __i386__)
	int i;
	for (i = 0; i < NGREG; i++) {
		sigsegv_outp("reg[%02d]: 0x"REGFORMAT, i, uc->uc_mcontext.gregs[i]);
	}
#elif (defined __arm__)
	sigsegv_outp("reg[%02d]		= 0x"REGFORMAT, 0, uc->uc_mcontext.arm_r0);
	sigsegv_outp("reg[%02d]		= 0x"REGFORMAT, 1, uc->uc_mcontext.arm_r1);
	sigsegv_outp("reg[%02d]		= 0x"REGFORMAT, 2, uc->uc_mcontext.arm_r2);
	sigsegv_outp("reg[%02d]		= 0x"REGFORMAT, 3, uc->uc_mcontext.arm_r3);
	sigsegv_outp("reg[%02d]		= 0x"REGFORMAT, 4, uc->uc_mcontext.arm_r4);
	sigsegv_outp("reg[%02d]		= 0x"REGFORMAT, 5, uc->uc_mcontext.arm_r5);
	sigsegv_outp("reg[%02d]		= 0x"REGFORMAT, 6, uc->uc_mcontext.arm_r6);
	sigsegv_outp("reg[%02d]		= 0x"REGFORMAT, 7, uc->uc_mcontext.arm_r7);
	sigsegv_outp("reg[%02d]		= 0x"REGFORMAT, 8, uc->uc_mcontext.arm_r8);
	sigsegv_outp("reg[%02d]		= 0x"REGFORMAT, 9, uc->uc_mcontext.arm_r9);
	sigsegv_outp("reg[%02d]		= 0x"REGFORMAT, 10, uc->uc_mcontext.arm_r10);
	sigsegv_outp("FP		= 0x"REGFORMAT, uc->uc_mcontext.arm_fp);
	sigsegv_outp("IP		= 0x"REGFORMAT, uc->uc_mcontext.arm_ip);
	sigsegv_outp("SP		= 0x"REGFORMAT, uc->uc_mcontext.arm_sp);
	sigsegv_outp("LR		= 0x"REGFORMAT, uc->uc_mcontext.arm_lr);
	sigsegv_outp("PC		= 0x"REGFORMAT, uc->uc_mcontext.arm_pc);
	sigsegv_outp("CPSR		= 0x"REGFORMAT, uc->uc_mcontext.arm_cpsr);
	sigsegv_outp("Fault Address	= 0x"REGFORMAT, uc->uc_mcontext.fault_address);
	sigsegv_outp("Trap no		= 0x"REGFORMAT, uc->uc_mcontext.trap_no);
	sigsegv_outp("Err Code	= 0x"REGFORMAT, uc->uc_mcontext.error_code);
	sigsegv_outp("Old Mask	= 0x"REGFORMAT, uc->uc_mcontext.oldmask);
#endif
}

static void print_call_link(ucontext_t *uc) 
{
	int i = 0;
	void **frame_pointer = (void **)NULL;
	void *return_address = NULL;
	Dl_info	dl_info = { 0 };

#if (defined __i386__)
	frame_pointer = (void **)uc->uc_mcontext.gregs[REG_EBP];
	return_address = (void *)uc->uc_mcontext.gregs[REG_EIP];
#elif (defined __x86_64__)
	frame_pointer = (void **)uc->uc_mcontext.gregs[REG_RBP];
	return_address = (void *)uc->uc_mcontext.gregs[REG_RIP];
#elif (defined __arm__)
/* sigcontext_t on ARM:
        unsigned long trap_no;
        unsigned long error_code;
        unsigned long oldmask;
        unsigned long arm_r0;
        ...
        unsigned long arm_r10;
        unsigned long arm_fp;
        unsigned long arm_ip;
        unsigned long arm_sp;
        unsigned long arm_lr;
        unsigned long arm_pc;
        unsigned long arm_cpsr;
        unsigned long fault_address;
*/
	frame_pointer = (void **)uc->uc_mcontext.arm_fp;
	return_address = (void *)uc->uc_mcontext.arm_pc;
#endif

	sigsegv_outp("\nStack trace:");
	while (frame_pointer && return_address) {
		if (!dladdr(return_address, &dl_info))	break;
		const char *sname = dl_info.dli_sname;	
#ifndef NO_CPP_DEMANGLE
		int status;
		char *tmp = __cxa_demangle(sname, NULL, 0, &status);
		if (status == 0 && tmp) {
			sname = tmp;
		}
#endif
		/* No: return address <sym-name + offset> (filename) */
		sigsegv_outp("%02d: %p <%s + %lu> (%s)", ++i, return_address, sname, 
			(unsigned long)return_address - (unsigned long)dl_info.dli_saddr, 
													dl_info.dli_fname);
#ifndef NO_CPP_DEMANGLE
		if (tmp)	free(tmp);
#endif
		if (dl_info.dli_sname && !strcmp(dl_info.dli_sname, "main")) {
			break;
		}

#if (defined __x86_64__) || (defined __i386__)
		return_address = frame_pointer[1];
		frame_pointer = frame_pointer[0];
#elif (defined __arm__)
		return_address = frame_pointer[-1];	
		frame_pointer = (void **)frame_pointer[-3];
#endif
	}
	sigsegv_outp("Stack trace end.");
}

static void sigsegv_handler(int signo, siginfo_t *info, void *context)
{
	if (context) {
		ucontext_t *uc = (ucontext_t *)context;

   		sigsegv_outp("Segmentation Fault!");
    	sigsegv_outp("info.si_signo = %d", signo);
    	sigsegv_outp("info.si_errno = %d", info->si_errno);
    	sigsegv_outp("info.si_code  = %d (%s)", info->si_code, 
			(info->si_code == SEGV_MAPERR) ? "SEGV_MAPERR" : "SEGV_ACCERR");
    	sigsegv_outp("info.si_addr  = %p\n", info->si_addr);

		print_reg(uc);
		print_call_link(uc);
	}

	_exit(0);
}

#define SETSIG(sa, sig, fun, flags)     \
        do {                            \
            sa.sa_sigaction = fun;      \
            sa.sa_flags = flags;        \
            sigemptyset(&sa.sa_mask);   \
            sigaction(sig, &sa, NULL);  \
        } while(0)

static void __attribute((constructor)) setup_sigsegv(void) 
{
	struct sigaction sa;

	SETSIG(sa, SIGSEGV, sigsegv_handler, SA_SIGINFO); 
#if 0
	memset(&sa, 0, sizeof(struct sigaction));
	sa.sa_sigaction = sigsegv_handler;
	sa.sa_flags = SA_SIGINFO;
	if (sigaction(SIGSEGV, &sa, NULL) < 0) {
		perror("sigaction: ");
	}
#endif
}

#if 1
void func3(void)
{
	char *p = (char *)0x12345678;
	*p = 10;
}

void func2(void)
{
	func3();	
}

void func1(void)
{
	func2();
}

int main(int argc, const char *argv[])
{
	func1();	
	exit(EXIT_SUCCESS);
}
#endif
编译时请加上-rdynamic -ldl选项!

代码地址:https://github.com/astrotycoon/sigsegv/blob/master/sigsegv.c


2016-1-17 18:29:33补充知识如下:

今天无意中发现原来gcc

相关阅读:

《 linux下利用backtrace追踪函数调用堆栈以及定位段错误

Stack Backtracing Inside Your Program

Obtaining a stack trace in C upon SIGSEGV

参考链接:

利用堆栈回溯、addr2line和Graphviz生成运行时函数调用图

Stack backtrace 的实现

linux backtrace()详细使用说明,分析Segmentation fault

Backtracing from code in ARM》(需要翻墙)

通过截取signal输出程序崩溃时的backtrace

Programmatic access to the call stack in C++

Stack unwinding (stack trace) with GCC

浅析ARM汇编语言子例程设计方法

自己动手实现arm函数栈帧回溯

Pre-mortem Backtracing

C++ Code Snippet - Print Stack Backtrace Programmatically with Demangled Function Names

内存泄露调试代码 可以解析C++ symbol

将信号用作 Linux 调试工具

Get an exception from a segfault on linux (x86 and x86_64), using some black magic !

broadcom6838开发环境实现函数栈追踪

<think>好的,我现在要回答用户关于C++中处理SIGSEGV信号时siginfo_t参数的详细说明。首先,我需要回忆一下相关的知识,确保信息的准确性。 用户的问题是关于在捕获SIGSEGV信号时,处理函数中的siginfo_t结构体的详细说明。我需要先确认siginfo_t的结构以及各个字段的含义,特别是与SIGSEGV相关的部分。 首先,SIGSEGV是段错误信号,通常是由于无效的内存访问引起的,比如访问未分配的内存、写入只读内存等。当使用sigaction函数设置信号处理函数,且使用了SA_SIGINFO标志时,处理函数会接收到三个参数:int signo, siginfo_t *info, void *context。用户关心的是其中的siginfo_t *info参数。 接下来,我需要详细说明siginfo_t结构体中与SIGSEGV相关的字段。根据POSIX标准,siginfo_t包含多个成员,但不同的信号可能填充不同的字段。对于SIGSEGV来说,主要的字段包括: - si_signo:信号编号,这里应该是SIGSEGV的值。 - si_errno:错误码,通常为0,除非有相关的系统错误。 - si_code:指示错误的类型,比如SEGV_MAPERR(地址未映射到对象)或SEGV_ACCERR(无效的权限访问)。 - si_addr:引发错误的内存地址。 此外,可能还有其他字段,比如发送信号的进程ID等,但对于SIGSEGV来说,这些可能不适用。需要确认这些信息是否正确,可能需要查阅相关文档或手册页。 然后,我需要解释每个字段的具体含义和可能的取值。例如,si_code的不同值对应的错误类型,SEGV_MAPERR表示访问的地址没有映射到进程的地址空间,而SEGV_ACCERR表示试图以不允许的方式访问内存(如写入只读区域)。si_addr字段则给出了导致错误的地址,这对于调试非常有用。 还需要提醒用户,在处理SIGSEGV时,信号处理函数应该尽量简单,避免调用非异步信号安全的函数,以免导致未定义行为。此外,段错误通常意味着程序存在严重问题,处理完信号后可能无法安全地继续执行,可能需要终止程序或记录信息后退出。 可能需要检查是否有遗漏的字段或需要进一步解释的部分。比如,是否还有其他si_code的可能值,或者si_addr在某些情况下的有效性。例如,在某些架构或系统上,si_addr可能无法正确获取,或者某些情况下si_code可能有其他值,但通常对于SIGSEGV来说,主要是这两个si_code。 最后,总结这些信息,以清晰的结构呈现给用户,确保他们能够理解每个字段的作用和如何利用这些信息进行调试或错误处理。</think>在C++中处理SIGSEGV信号时,通过`siginfo_t`参数可以获取详细的错误信息。以下是关键字段说明及使用示例: --- ### **siginfo_t 核心字段(SIGSEGV专用)** 1. **si_signo** - 类型:`int` - 说明:信号编号,固定为`SIGSEGV`(值为11)。 2. **si_code** - 类型:`int` - 说明:错误类型标识,具体取值: - `SEGV_MAPERR`:访问未映射的内存地址(如空指针解引用) - `SEGV_ACCERR`:权限错误(如写入只读内存) 3. **si_addr** - 类型:`void*` - 说明:触发错误的访问地址(如`0x0`表示访问NULL指针) 4. **si_errno** - 类型:`int` - 说明:通常为0,系统错误码(较少使用) --- ### **代码示例:信号处理器** ```cpp #include <signal.h> #include <stdio.h> #include <stdlib.h> void segv_handler(int signo, siginfo_t* info, void* context) { fprintf(stderr, "=== SIGSEGV 错误信息 ===\n"); fprintf(stderr, "信号编号: %d\n", info->si_signo); fprintf(stderr, "错误地址: %p\n", info->si_addr); switch (info->si_code) { case SEGV_MAPERR: fprintf(stderr, "原因: 访问未映射的内存\n"); break; case SEGV_ACCERR: fprintf(stderr, "原因: 内存访问权限错误\n"); break; default: fprintf(stderr, "未知错误类型\n"); } // 立即终止程序(避免不可恢复状态) abort(); } int main() { struct sigaction sa{}; sa.sa_sigaction = segv_handler; sa.sa_flags = SA_SIGINFO; // 必须设置此标志以获取siginfo sigaction(SIGSEGV, &sa, nullptr); // 触发段错误(示例) int* ptr = nullptr; *ptr = 42; // 触发SIGSEGV return 0; } ``` --- ### **输出示例** ``` === SIGSEGV 错误信息 === 信号编号: 11 错误地址: 0x0 原因: 访问未映射的内存 Aborted (core dumped) ``` --- ### **注意事项** 1. **异步信号安全** 处理函数中只能调用异步信号安全函数(如`write`,避免`printf`)。示例中使用`fprintf`仅为演示,实际应谨慎处理。 2. **程序终止** SIGSEGV通常意味着程序状态已损坏,建议在记录信息后调用`abort()`或`_exit()`终止。 3. **调试用途** 结合`si_addr`和调试器(如GDB)可快速定位问题代码。 通过分析`siginfo_t`参数,可精准定位段错误根源,提升调试效率。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值