backtrace和backtrace_symbols函数原理解析

本文详细解析了glibc中的backtrace()和backtrace_symbols()函数,介绍了如何获取并翻译程序的函数调用关系。通过示例展示了如何使用这两个函数,并解释了在编译时需要添加-rdynamic选项的原因,以及如何处理静态函数的问题。
部署运行你感兴趣的模型镜像

backtrace()是glibc(>=2.1)提供的函数,用于跟踪函数的调用关系。

以下对backtrace()函数的说明以及实例,都来自其man page。


函数定义
       #include <execinfo.h>

       int backtrace(void **buffer, int size);

       char **backtrace_symbols(void *const *buffer, int size);

       void backtrace_symbols_fd(void *const *buffer, int size, int fd);

函数说明

backtrace()函数用来获取程序中当前函数的回溯信息,即一系列的函数调用关系,获取到的信息被放在参数buffer中。buffer是一个数组指针,数组的每个元素保存着每一级被调用函数的返回地址。参数size指定了buffer中可存放的返回地址的数量。如果函数实际的回溯层级数大于size,则buffer中只能存放最近的函数调用关系,所以,想要得到完整的回溯信息,就要确保size参数足够大。
backtrace()函数的返回值为buffer中的条目数量,这个值不一定等于size,因为如果为得到完整回溯信息而将size设置的足够大,则该函数的返回值为buffer中实际得到的返回地址数量。
 
通过backtrace()函数得到buffer之后,backtrace_symbols()可以将其中的返回地址都对应到具体的函数名,参数size为buffer中的条目数。backtrace_symbols()函数可以将每一个返回值都翻译成“函数名+函数内偏移量+函数返回值”,这样就可以更直观的获得函数的调用关系。
经过翻译后的函数回溯信息放到backtrace_symbols()的返回值中,如果失败则返回NULL。需要注意,返回值本身是在backtrace_symbols()函数内部进行malloc的,所以必须在后续显式地free掉。
 
backtrace_symbols_fd()的buffer和size参数和backtrace_symbols()函数相同,只是它翻译后的函数回溯信息不是放到返回值中,而是一行一行的放到文件描述符fd对应的文件中。

注意,在编译的时候需要加上-rdynamic选项让链接器将所有符号添加到动态符号表中,这样才能将函数地址翻译成函数名。另外,这个选项不会处理static函数,所以,static函数的符号无法得到。

示例:
 

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
void
myfunc3(void)
{
   int j, nptrs;
#define SIZE 100
   void *buffer[100];
   char **strings;
 
   nptrs = backtrace(buffer, SIZE);
   printf("backtrace() returned %d addresses\n", nptrs);
 
   /* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
	  would produce similar output to the following: */
 
   strings = backtrace_symbols(buffer, nptrs);
   if (strings == NULL) {
	   perror("backtrace_symbols");
	   exit(EXIT_FAILURE);
   }
 
   for (j = 0; j < nptrs; j++)
	   printf("%s\n", strings[j]);
 
   free(strings);
}
 
static void   /* "static" means don't export the symbol... */
myfunc2(void)
{
   myfunc3();
}
 
void
myfunc(int ncalls)
{
   if (ncalls > 1)
	   myfunc(ncalls - 1);
   else
	   myfunc2();
}
 
int
main(int argc, char *argv[])
{
   if (argc != 2) {
	   fprintf(stderr, "%s num-calls\n", argv[0]);
	   exit(EXIT_FAILURE);
   }
 
   myfunc(atoi(argv[1]));
   exit(EXIT_SUCCESS);
}

进行编译:

cc -rdynamic prog.c -o prog
代码运行结果为:

$ ./prog 3
           backtrace() returned 8 addresses
           ./prog(myfunc3+0x5c) [0x80487f0]
           ./prog [0x8048871]
           ./prog(myfunc+0x21) [0x8048894]
           ./prog(myfunc+0x1a) [0x804888d]
           ./prog(myfunc+0x1a) [0x804888d]
           ./prog(main+0x65) [0x80488fb]
           /lib/libc.so.6(__libc_start_main+0xdc) [0xb7e38f9c]
           ./prog [0x8048711]
 

 

采用信号量进行崩溃日志打印:

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
 
void trace(int signo)
{
	int j, nptrs;
#define SIZE 100
	void *buffer[100];
	char **strings;
 
	printf("signo: %d\n", signo);
 
	nptrs = backtrace(buffer, SIZE);
	printf("backtrace() returned %d addresses\n", nptrs);
 
	/* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
	 *               would produce similar output to the following: */
 
	strings = backtrace_symbols(buffer, nptrs);
	if (strings == NULL) {
		perror("backtrace_symbols");
		exit(EXIT_FAILURE);
	}
 
	for (j = 0; j < nptrs; j++)
		printf("%s\n", strings[j]);
 
	free(strings);
 
	if (SIGSEGV == signo || SIGQUIT == signo) {
		exit(0);
	}
}
 
void segfault(void)
{
	int *p = NULL;
	*p = 1;
}
 
int main(int argc, char *argv[])
{
	signal(SIGSEGV, trace);
	signal(SIGINT, trace);
	signal(SIGQUIT, trace);
 
	while (1) {
		sleep(1);
		if (time(0) % 7 == 0) {
			segfault();
		}
	}
 
	return 0;
}

编译方法  gcc -rdynamic seg.c -o seg -g


段错信息


    signo: 11
    backtrace() returned 6 addresses
    ./seg(trace+0x3c) [0x400aac]
    /lib64/libc.so.6() [0x3c39635690]
    ./seg(segfault+0x10) [0x400b64]
    ./seg(main+0x83) [0x400bef]
    /lib64/libc.so.6(__libc_start_main+0xf5) [0x3c39621b45]
    ./seg() [0x4009a9]


使用  objdump -d seg  查看出错地址  0x400b64  是一个赋值操作

    0000000000400b54 <segfault>:
      400b54:       55                      push   %rbp
      400b55:       48 89 e5                mov    %rsp,%rbp
      400b58:       48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
      400b5f:       00
      400b60:       48 8b 45 f8             mov    -0x8(%rbp),%rax
      400b64:       c7 00 01 00 00 00       movl   $0x1,(%rax)
      400b6a:       5d                      pop    %rbp
      400b6b:       c3                      retq   

 

使用  addr2line 0x400b64 -e seg -afs  查看段错函数和对应的代码行数

    0x0000000000400b64
    segfault
    seg.c:41

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

### ✅ 回答: 你提供的 SQL: ```sql DELETE FROM T_ATML_BACKTRACK_RECORD_1112 WHERE TROW_ID = '20240313000004183734'; ``` **语法上是正确的**,但在实际使用中是否“有问题”,取决于以下几个关键因素。我们来逐一分析。 --- ## 🔍 一、语法检查 ✅ | 部分 | 是否合法 | |------|----------| | `DELETE FROM 表名` | ✅ 合法 | | `WHERE 条件` | ✅ 存在,避免全表删除 | | `TROW_ID = '...'` | ✅ 字符串匹配,假设 `TROW_ID` 是字符类型 | 👉 所以从 **SQL 语法角度** 来看:✅ **没有问题** --- ## ⚠️ 二、潜在风险与注意事项 ### 1. **数据不可逆!DELETE 是永久删除** - 一旦执行成功,数据将从表中移除。 - 如果没有开启闪回(Flashback)、没备份、没有事务回滚(ROLLBACK),**无法恢复**! > ❗ 建议:在执行前先做 `SELECT` 确认要删的是哪条记录: ```sql SELECT * FROM T_ATML_BACKTRACK_RECORD_1112 WHERE TROW_ID = '20240313000004183734'; ``` --- ### 2. **TROW_ID 是否为主键或唯一键?** - 如果 `TROW_ID` 不是主键或唯一索引,可能匹配多条记录。 - 即使你认为它是唯一的,也建议确认一下表结构。 > 🛠 检查是否存在唯一约束: ```sql SELECT constraint_name, constraint_type FROM user_constraints WHERE table_name = 'T_ATML_BACKTRACK_RECORD_1112' AND constraint_type IN ('P', 'U'); ``` --- ### 3. **字段类型是否匹配?** - 你在用 `'20240313000004183734'` 这个字符串去比较 `TROW_ID` - 如果 `TROW_ID` 是 **数值型**(如 NUMBER),虽然 Oracle 会自动隐式转换,但: - 可能导致性能下降(无法使用索引) - 大数可能导致精度丢失(尤其是超过 15 位的数字) > ✅ 推荐做法:确认字段类型 ```sql DESCRIBE T_ATML_BACKTRACK_RECORD_1112; -- 或 SELECT column_name, data_type FROM user_tab_columns WHERE table_name = 'T_ATML_BACKTRACK_RECORD_1112' AND column_name = 'TROW_ID'; ``` #### 情况建议: | TROW_ID 类型 | 写法建议 | |--------------|---------| | VARCHAR2 / CHAR | `'20240313000004183734'` ✅ 正确 | | NUMBER | 可写为 `20240313000004183734`(不加引号)更安全 | > 💡 示例(如果为 NUMBER 类型): ```sql DELETE FROM T_ATML_BACKTRACK_RECORD_1112 WHERE TROW_ID = 20240313000004183734; ``` --- ### 4. **表名是否正确?** 你写的是:`T_ATML_BACKTRACK_RECORD_1112` 注意: - 是否应为 `T_ATML_BACKTRACK_RECORD`? - `_1112` 是不是测试表或临时分区表? > ❗ 确保你在操作的是正确的生产/目标表。 --- ### 5. **是否有外键依赖?** 如果其他表引用了这张表的 `TROW_ID`,直接删除可能会违反外键约束,导致报错: > ORA-02292: integrity constraint (...) violated - child record found > ✅ 解决方案: - 先查子表是否有相关数据 - 或设置级联删除(需提前定义) --- ## ✅ 最佳实践建议 ```sql -- Step 1: 先查询,确认数据 SELECT * FROM T_ATML_BACKTRACK_RECORD_1112 WHERE TROW_ID = '20240313000004183734'; -- Step 2: 开启事务(可回滚) BEGIN; DELETE FROM T_ATML_BACKTRACK_RECORD_1112 WHERE TROW_ID = '20240313000004183734'; -- Step 3: 查看影响行数,确认无误后再提交 COMMIT; -- 或 ROLLBACK; 回滚测试 ``` ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值