【GDB】__stack_chk_fail 栈溢出问题定位

一、问题描述

进程收到SIGABRT信号异常退出,异常调用栈显示__stack_chk_fail

二、原因分析和定位思路

原因分析: __stack_chk_fail说明发生了缓冲区溢出,canary被破坏。这说明代码设置GCC编译选项fstack-protector,开启了栈保护机制canary

定位思路:

  • 先通过反汇编找到canary在栈上的存放地址。
  • 用GDB对canary的存放地址打数据断点,定位出导致栈破坏的指令,再结合C代码具体分析。

三、定位过程

以下给出一个简化案例:一个可执行程序test, 依赖两个.so:libcomp1.so, libcomp2.so。执行test程序后会异常退出,调用栈显示__stack_chk_fail

├── CMakeLists.txt			—— 可执行程序 test, 依赖libcomp1.so, libcomp2.so
├── comp1					
│   ├── CMakeLists.txt		—— libcomp1.so
│   ├── comp1.c
│   ├── lua.h
├── comp2
│   ├── CMakeLists.txt		-- libcomp2.so
│   ├── comp2.c
│   ├── lua.h
├── main.c

C代码如下:

// main.c
extern void func1();	// defined in libcomp1.so
void main() {
    func1();
}

// comp1/comp1.c
#include "lua.h"
extern func2(struct lua_Debug *a, char *b, int c); // defined in libcomp2.so
void func1() {
    struct lua_Debug a = {0};
    char *b = 0x12345678;
    int c = 0xFFFFFFFF;
    func2(&a, b, c);
    return;
}

// comp2/comp2.c
#include "lua.h"
void func2(struct lua_Debug *a, char *b, int c) {
    a->i_ci = 1;
    return;
}

用GDB调试test程序,出现如下的异常调用栈

(gdb) bt
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1  0x00007ffff7e1c535 in __GI_abort () at abort.c:79
#2  0x00007ffff7e73508 in __libc_message (action=<optimized out>, fmt=fmt@entry=0x7ffff7f7e07b "*** %s ***: %s terminated\n")
    at ../sysdeps/posix/libc_fatal.c:181
#3  0x00007ffff7f0480d in __GI___fortify_fail_abort (need_backtrace=need_backtrace@entry=false,
    msg=msg@entry=0x7ffff7f7e059 "stack smashing detected") at fortify_fail.c:28
#4  0x00007ffff7f047c2 in __stack_chk_fail () at stack_chk_fail.c:29
#5  0x00007ffff7fca189 in func1 () at /home/pc/LUA/comp1/comp1.c:13
#6  0x0000555555555143 in main () at /home/pc/LUA/main.c:7

第4帧出现__stack_chk_fail,这表示程序出现了栈溢出。定位思路如下:

1. 先通过反汇编找到canary在栈上的存放地址。

用gdb查看出现__stack_chk_fail的前一个函数帧,即第5帧的汇编代码。通过frame命令切换函数帧,disassemble查看反汇编代码。

(gdb) frame 5
#5  0x00007ffff7fca189 in func1 () at /home/pc/LUA/comp1/comp1.c:13
13      }
(gdb) disassemble
Dump of assembler code for function func1:
   0x00007ffff7fca115 <+0>:     push   %rbp
   0x00007ffff7fca116 <+1>:     mov    %rsp,%rbp
   0x00007ffff7fca119 <+4>:     sub    $0x90,%rsp				# 栈上共分配144字节空间: (8字节canary + 120字节lua_Debug + 8字节char * + 4字节int, 4字节对齐 = 144字节)
   0x00007ffff7fca120 <+11>:    mov    %fs:0x28,%rax			# %fs:0x28说明,canary值是通过段寻址方式从内存中读入的
   0x00007ffff7fca129 <+20>:    mov    %rax,-0x8(%rbp)			# 将canary存储到栈中,位于$rbp - 0x8处
   0x00007ffff7fca12d <+24>:    xor    %eax,%eax
   0x00007ffff7fca12f <+26>:    lea    -0x80(%rbp),%rdx			# comp1.c/func1(): struct lua_Debug a = {0};
   0x00007ffff7fca133 <+30>:    mov    $0x0,%eax
   0x00007ffff7fca138 <+35>:    mov    $0xf,%ecx
   0x00007ffff7fca13d <+40>:    mov    %rdx,%rdi
   0x00007ffff7fca140 <+43>:    rep stos %rax,%es:(%rdi)
   0x00007ffff7fca143 <+46>:    movq   $0x12345678,-0x88(%rbp)	# comp1.c/func1(): char *b = 0x12345678;
   0x00007ffff7fca14e <+57>:    movl   $0xffffffff,-0x8c(%rbp)	# comp1.c/func1(): int c = 0xFFFFFFFF;
   0x00007ffff7fca158 <+67>:    mov    -0x8c(%rbp),%edx			# c
   0x00007ffff7fca15e <+73>:    mov    -0x88(%rbp),%rcx			# b
   0x00007ffff7fca165 <+80>:    lea    -0x80(%rbp),%rax			# a
   0x00007ffff7fca169 <+84>:    mov    %rcx,%rsi				
   0x00007ffff7fca16c <+87>:    mov    %rax,%rdi				
   0x00007ffff7fca16f <+90>:    callq  0x7ffff7fca040 <func2@plt>
   0x00007ffff7fca174 <+95>:    nop
   0x00007ffff7fca175 <+96>:    mov    -0x8(%rbp),%rax
   0x00007ffff7fca179 <+100>:   xor    %fs:0x28,%rax			 # 从段寄存器取出canary的值,和栈上$rbp - 0x8处比较,如果发现canary被修改, 调用__stack_chk_fail进行错误处理
   0x00007ffff7fca182 <+109>:   je     0x7ffff7fca189 <func1+116>
   0x00007ffff7fca184 <+111>:   callq  0x7ffff7fca030 <__stack_chk_fail@plt>
=> 0x00007ffff7fca189 <+116>:   leaveq # movq %rbp %rsp, popq %rbp
   0x00007ffff7fca18a <+117>:   retq
End of assembler dump.

0x00007ffff7fca119:sub $0x90,%rsp:表示栈上分配了144个字节,依次存放三个局部变量a, b, c

0x00007ffff7fca120 <+11>: mov %fs:0x28,%rax:其中指令参数$fs:0x28说明canary通过段寻址从内存中读入。canary值存放在一个特殊的段中,标志为只读,这样攻击者就无法覆盖canary的值。

0x00007ffff7fca129 <+20>: mov %rax,-0x8(%rbp):说明canary在栈上存放地址是$rbp - 0x8

根据x86_64过程调用的函数参数传递规则,可确定栈上所有局部变量和canary是如何存放的。func1的栈组织如下:

在这里插入图片描述

2. 用GDB对canary的存放地址打数据断点,定位出导致栈破坏的指令

func1打断点,重新执行程序,接着用watch命令,对canary的存放地址打数据断点,操作如下:

(gdb) disassemble
Dump of assembler code for function func1:
   0x00007ffff7fca115 <+0>:     push   %rbp
   0x00007ffff7fca116 <+1>:     mov    %rsp,%rbp
   0x00007ffff7fca119 <+4>:     sub    $0x90,%rsp
   0x00007ffff7fca120 <+11>:    mov    %fs:0x28,%rax
   0x00007ffff7fca129 <+20>:    mov    %rax,-0x8(%rbp)
=> 0x00007ffff7fca12d <+24>:    xor    %eax,%eax	
   0x00007ffff7fca12f <+26>:    lea    -0x80(%rbp),%rdx
   ......
End of assembler dump.
(gdb) p $rbp - 0x8
$8 = (void *) 0x7fffffffe4a8
(gdb) watch *0x7fffffffe4a8							# 此处对canary在栈中存放的地址打数据断点!
Hardware watchpoint 2: *0x7fffffffe4a8
(gdb) c
Continuing.

Hardware watchpoint 2: *0x7fffffffe4a8
Old value = 1303192064
New value = 1

(gdb) disassemble
Dump of assembler code for function func2:
   0x00007ffff7fc50f5 <+0>:     push   %rbp
   0x00007ffff7fc50f6 <+1>:     mov    %rsp,%rbp
   0x00007ffff7fc50f9 <+4>:     mov    %rdi,-0x8(%rbp)     # 第一个入参,lua_Debug *a
   0x00007ffff7fc50fd <+8>:     mov    %rsi,-0x10(%rbp)
   0x00007ffff7fc5101 <+12>:    mov    %edx,-0x14(%rbp)
   0x00007ffff7fc5104 <+15>:    mov    -0x8(%rbp),%rax	   # 第一个入参,lua_Debug *a
   0x00007ffff7fc5108 <+19>:    movq   $0x1,0x78(%rax)     # comp2.c/func2(): a->i_ci = 1; 这句导致canary被破坏
=> 0x00007ffff7fc5110 <+27>:    nop 	# 箭头指向的指令表示下一步即将执行的指令,也就是说是上一条指令movq $0x1,0x78(%rax)触发的数据断点!
   0x00007ffff7fc5111 <+28>:    pop    %rbp
   0x00007ffff7fc5112 <+29>:    retq
End of assembler dump.

数据断点被触发,定位出0x00007ffff7fc5108: $0x1,0x78(%rax)导致canary破坏,结合汇编上下文和C码确定,这条指令关联的C语句是a->i_ci = 1;

为什么对lua_Debug结构体的成员赋值会导致栈溢出?

$0x1,0x78(%rax)可以确定i_ci成员距离结构体首地址的偏移为0x78,即120字节。事实上,在func2的函数帧中,lua_Debug结构体大小为128字节,而func1中的lua_Debug结构体只有120字节。

结合func1的栈帧发现栈溢出的直接原因:a->i_ci = 1; 语句执行后,恰好导致canary的值被改写为1。

通过gdb也能发现两个.so中的结构体大小不一致问题:

(gdb) f 1			
#1  0x00007ffff7fca174 in func1 () at /home/pc/LUA/comp1/comp1.c:11
11          func2(&a, b, c);
(gdb) p sizeof(struct lua_Debug)
$24 = 120							# func1中结构体大小为120
(gdb) f 0
#0  func2 (a=0x7fffffffe430, b=0x12345678 <error: Cannot access memory at address 0x12345678>, c=-1) at /home/pc/LUA/comp2/comp2.c:5
5           return;
(gdb) p sizeof(struct lua_Debug)
$27 = 128							# func2中结构体大小为128

lua_Debug结构体在lua.h定义的,这个错误原因是**由于两个.so编译使用的lua.h头文件不一致,导致栈溢出问题。**以下分别给出两个.so的lua.h

在这里插入图片描述

根据64位结构体对齐规则,左边结构体大小为120,右边结构体大小为128,多出的这8个字节恰好覆盖了canary,导致栈溢出。

总结

  • 公司的C代码中,多个.so会依赖相同的开源头文件,如果不能保证每个.so各自依赖的头文件版本一致,就可能出现上述的栈溢出问题。

  • 通过设置GCC的编译选项fstack-protector开启栈保护机制,便于栈溢出问题的定位。

参考资料

《深入理解计算机系统 原书第3版》 3.10.4.2 栈破坏检测

#應用 a02 樣板自動產生(Version:2) AFTER FIELD pmdb006 #應用 a15 樣板自動產生(Version:3) #確認欄位值在特定區間內 IF NOT cl_ap_chk_range(g_pmdb_d[l_ac].pmdb006,"0.000","0","","","azz-00079",1) THEN NEXT FIELD pmdb006 END IF #add-point:AFTER FIELD pmdb006 name="input.a.page1.pmdb006" IF NOT cl_null(g_pmdb_d[l_ac].pmdb006) THEN IF NOT cl_null(g_pmdb_d[l_ac].pmdb007) THEN CALL apmt400_pmdb006_chk() #170713-00054#1 add CALL apmt400_unit_round(g_pmdb_d[l_ac].pmdb007,g_pmdb_d[l_ac].pmdb006) RETURNING g_pmdb_d[l_ac].pmdb006 DISPLAY BY NAME g_pmdb_d[l_ac].pmdb006 #add by lixiang 2015/10/15---begin---- #檢核料件AVL控管點(imaa044)設置 #IF NOT s_apmt400_item_avl_chk(g_pmda_m.pmdadocdt,g_pmdb_d[l_ac].pmdb004,g_pmdb_d[l_ac].pmdb005,g_pmdb2_d[l_ac].pmdb015,g_pmdb_d[l_ac].pmdb006) THEN #170927-00005#1 mark IF NOT s_apmt400_item_avl_chk(g_pmda_m.pmdadocdt,g_pmdb_d[l_ac].pmdb004,g_pmdb_d[l_ac].pmdb005,g_pmdb2_d[l_ac].pmdb015,g_pmdb_d[l_ac].pmdb006,g_pmda_m.pmdadocno,g_pmdb_d[l_ac].pmdbseq) THEN #170927-00005#1 add LET g_pmdb_d[l_ac].pmdb006 = g_pmdb_d_t.pmdb006 NEXT FIELD CURRENT END IF #add by lixiang 2015/10/15---end---- #160727-00025#9--s IF NOT cl_null(g_pmdb_d[l_ac].pmdb001) THEN #判断单据来源,通过oobx003单据性质来判断 CALL s_aooi200_get_slip(g_pmdb_d[l_ac].pmdb001) RETURNING l_flag,l_slip LET l_oobx003 = '' SELECT oobx003 INTO l_oobx003 FROM oobx_t WHERE oobxent = g_enterprise AND oobx001 = l_slip CASE l_oobx003 WHEN "axmt500" #160727-00025#9--e
03-20
<think>好的,我现在需要解决用户关于几个特定代码逻辑的功能说明或错误排查的问题,涉及到的函数或变量包括:cl_ap_chk_range、cl_null、apmt400_pmdb006_chk、apmt400_unit_round、s_apmt400_item_avl_chk、s_aooi200_get_slip。 首先,我需要分析这些名称的结构,可能来自某个特定的系统或项目。比如,“apmt400”可能指模块编号,而前缀如“cl_”或“s_”可能表示不同的类型,如公共库函数或静态函数。接下来,我会考虑可能的错误来源,例如编译选项或缓冲区溢出,参考用户提供的引用内容。 引用中提到栈保护机制的问题(如__stack_chk_fail),这可能与编译选项有关,比如启用-fstack-protector导致的问题。用户提供的解决方法包括调整编译选项或检查缓冲区溢出。另外,变量定义冲突的问题也可能影响编译,需要检查是否有重复定义的情况。 由于这些函数/变量可能是项目特定的,缺乏公开文档的话,需要用户提供更多上下文,比如代码片段或日志。同时,建议用户检查相关模块的代码,查看函数调用和变量使用情况,确保符合项目规范。如果涉及第三方库,如ffmpeg,可能需要调整编译选项,如使用-fPIC,如引用4所述。 总结来说,可能的解决步骤包括:检查编译选项,排查缓冲区溢出,确认变量定义是否冲突,查阅项目内部文档,以及联系模块维护者获取帮助。如果用户能提供更多上下文信息,可以更精准地定位问题。</think>关于您提到的代码逻辑功能说明或错误排查,由于这些函数/变量名称(如$cl\_ap\_chk\_range$、$s\_aooi200\_get\_slip$等)具有明显的项目或系统特定性,可能是某个内部框架、业务模块或自定义库的组成部分,以下提供通用排查思路和可能的方向: --- ### **1. 功能推测与代码逻辑分析** #### (1) **命名结构分析** - **前缀含义**: - `cl_`:可能表示公共库(Common Library)或核心层(Core Layer)的函数/变量。 - `apmt400_`:可能为模块编号(如APMT400代表某个业务模块),例如库存管理或订单处理。 - `s_`:可能表示静态(Static)作用域或某个子系统(Subsystem)。 - **功能推测**: - `*_chk`:通常与数据校验相关(如范围检查`cl_ap_chk_range`、空值检查`cl_null`)。 - `*_avl_chk`:可能与可用性检查(Availability Check)相关。 - `*_get_slip`:可能涉及单据/凭条获取逻辑(如订单单号生成)。 #### (2) **常见错误场景** - **编译错误**:若遇到`undefined reference`或链接错误,需检查: - 函数是否正确定义/声明(如头文件包含)。 - 编译选项是否冲突(如引用[5]提到的栈保护选项`-fstack-protector`可能导致符号缺失,需调整编译参数)[^5]。 - **运行时错误**: - 缓冲区溢出:若触发`__stack_chk_fail`,需检查数组越界或内存操作(参考引用[2]的栈保护机制分析)[^2]。 - 空指针访问:`cl_null`可能涉及空指针校验,需确认参数传递有效性。 --- ### **2. 排查步骤建议** #### (1) **代码与文档追溯** - **项目文档**:优先查阅内部设计文档或接口说明,确认函数职责。 - **代码上下文**:分析函数调用关系及参数传递,例如: ```c // 示例:s_apmt400_item_avl_chk 可能用于检查物料可用性 if (s_apmt400_item_avl_chk(item_id, quantity) != SUCCESS) { log_error("Item %s unavailable", item_id); } ``` #### (2) **依赖项检查** - **第三方库**:若涉及类似`ffmpeg`的编译问题(如引用[4]),需检查依赖库的编译选项(如`-fPIC`)[^4]。 - **环境变量**:某些函数可能依赖环境变量配置(如引用[1]中的`LIBC_DEBUG_MALLOC_OPTIONS`)[^1]。 #### (3) **调试与日志** - **GDB/LLDB**:通过调试器定位崩溃点,例如使用`print (char*)0x402622`解析内存地址(参考引用[3])[^3]。 - **日志输出**:在关键逻辑处添加日志,跟踪函数执行路径及参数状态。 --- ### **3. 典型问题解决示例** #### **场景:`undefined reference to s_aooi200_get_slip`** - **可能原因**: - 函数未实现(缺少源文件)。 - 链接顺序错误或库未正确链接。 - **解决步骤**: 1. 确认`s_aooi200_get_slip`的源文件(如`aooi200.c`)是否加入编译。 2. 检查Makefile中库的链接顺序,确保依赖关系正确。 3. 若为静态库,尝试清理并重新编译(避免缓存问题)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pcj_888

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值