You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

编译优化二进制文件中GDB错误显示函数参数值的问题咨询

问题分析与实战解答

背景回顾

先把你遇到的场景整理清楚,方便后续分析:

代码片段

static void func (<struct type1> *arg1, <struct type2> **arg2)
{
<struct type2> *var1;
// ... 中间代码省略
var1 = *arg2; /* arg2在该函数中未被其他地方使用 */
if (!var1) {
    return;
}
// ... var1被用于读写属性
// ...
my_free(); /* 程序在此行崩溃,my_free()内部调用__free() */
// ...
}

崩溃时的GDB调用栈

#2 0x000055ea29cceb76 in func [__be_func...] (arg1=arg1@entry=0x7f8bcc440098, arg2=arg2@entry=0x0) at myfile.c:121

函数反汇编片段

(gdb) disass
Dump of assembler code for function func:
0x000055ea29cce790 <+0>: push %rbp
0x000055ea29cce791 <+1>: mov %rsp,%rbp
0x000055ea29cce794 <+4>: push %r15
0x000055ea29cce796 <+6>: push %r14
0x000055ea29cce798 <+8>: push %r13
0x000055ea29cce79a <+10>: push %r12
0x000055ea29cce79c <+12>: push %rbx
0x000055ea29cce79d <+13>: sub $0x38,%rsp
0x000055ea29cce7a1 <+17>: mov %rdi,%r14
0x000055ea29cce7a4 <+20>: movabs $0x8000000000000000,%r15
0x000055ea29cce7ae <+30>: addq $0x1,0x1864a08a(%rip)
0x000055ea29cce7b6 <+38>: lea -0x1(%r15),%rdx
0x000055ea29cce7ba <+42>: mov %rdi,-0x38(%rbp)
0x000055ea29cce7be <+46>: and %rdx,%r14
0x000055ea29cce7c1 <+49>: and %rdx,%rsi
0x000055ea29cce7c4 <+52>: mov $0xffffffffffffff7f,%r13
0x000055ea29cce7cb <+59>: and 0x50(%r14),%r13
0x000055ea29cce7cf <+63>: movl $0x1000000,-0x50(%rbp)
0x000055ea29cce7d6 <+70>: movl $0x0,-0x48(%rbp)
0x000055ea29cce7dd <+77>: mov (%rsi),%rcx
0x000055ea29cce7e0 <+80>: bswap %rcx
0x000055ea29cce7e3 <+83>: mov %rcx,%rbx
0x000055ea29cce7e6 <+86>: mov %rdx,-0x40(%rbp)
0x000055ea29cce7ea <+90>: and %rdx,%rbx
0x000055ea29cce7ed <+93>: je 0x55ea29ccebcb
0x000055ea29cce7f3 <+99>: bswap %r13
0x000055ea29cce7f6 <+102>: testb $0x20,0x10d0(%r14)
0x000055ea29cce7fe <+110>: jne 0x55ea29cce80b
0x000055ea29cce800 <+112>: cmpl $0x0,0x48(%r14)
0x000055ea29cce805 <+117>: je 0x55ea29cce9e1
0x000055ea29cce80b <+123>: mov %rsi,-0x58(%rbp)
0x000055ea29cce80f <+127>: mov 0x1210(%r14),%r11
0x000055ea29cce816 <+134>: bswap %r11
// ... 后续代码省略

栈帧内存查看结果

(gdb) i r rbp
rbp 0x7f8bcd651730 0x7f8bcd651730
(gdb) x/13xg 0x7F8BCD6516D0
0x7f8bcd6516d0: 0x0000000b00000006 0x00007f8bcd651748 <--- 原始%rsi值,对应arg2
0x7f8bcd6516e0: 0x0000000001000000 0x0000000000000000
0x7f8bcd6516f0: 0x7fffffffffffffff 0x80007f8bcc440098 <--- 原始%rdi值,对应arg1
0x7f8bcd651700: 0x80007f8bd51a6230 0x00007f8bd51a6248
0x7f8bcd651710: 0x00007f8bcd651748 0x00007f8bd51a6230
0x7f8bcd651720: 0x80007f8bcc440098 0x0000000000000000
0x7f8bcd651730: 0x00007f8bcd651780

核心问题拆解与解答

1. 为什么GDB显示arg2为0x0,而不是<optimized out>

你猜的没错:arg2只用了一次,编译器确实做了优化——没有把它持久存在栈帧里,初始只存在%rsi寄存器中。但GDB显示0x0的锅不在“优化丢失值”,而是编译器插的地址掩码操作+调试信息的关联错误导致的:

看反汇编里这两行关键代码:

0x000055ea29cce7be <+46>: and %rdx,%r14
0x000055ea29cce7c1 <+49>: and %rdx,%rsi

先算%rdx的值:%r150x8000000000000000lea -0x1(%r15)%rdx = 0x7fffffffffffffff。这行and %rdx,%rsi会直接把%rsi的最高位(MSB)清零。

GDB解析参数时,默认靠调试信息找参数的存储位置:如果调试信息说arg2对应%rsi,那它就会读当前%rsi的值。但崩溃的时候,%rsi已经被后续代码复用改成0x0了(毕竟arg2之后再也没被用到),所以GDB就错误地显示了这个被覆盖后的寄存器值,而非arg2的原始传入值。

正常情况下,如果参数被完全优化掉,GDB会标<optimized out>,但这里调试信息还保留了arg2%rsi的关联,只是寄存器值已经被篡改,所以就出现了这个误导人的0x0。

2. 为什么地址MSB没设置时,GDB没法正确打印参数?

从栈帧信息能看出来,你的程序在用一种带标记的地址格式:最高位是自定义标记位(比如区分特殊对象、内核/用户空间之类的)。但编译器插的掩码操作会清除这个标记位,再加上GDB的参数解析逻辑,就导致了这个问题:

  • arg1的原始值是0x80007f8bcc440098(MSB为1),经过and %rdx,%r14%r14变成0x00007f8bcc440098,但栈里保存了原始的%rdi值(带MSB),GDB能正确找到这个栈里的原始值,所以显示正常。
  • arg2的原始值是0x00007f8bcd651748(MSB为0),掩码操作后值不变,但后续代码把%rsi改成了0x0,而调试信息没告诉GDB去栈里找原始的%rsi值,只让它读当前寄存器,所以就显示了错误的0x0。

验证与解决办法

  • 要查arg2的真实值,直接读栈里的对应位置就行:比如你已经找到栈里0x7f8bcd6516d8存了原始%rsi0x00007f8bcd651748),执行p (struct type2 **)0x00007f8bcd651748就能看到真实的arg2指向内容。
  • 如果想让GDB正确显示参数,临时关闭优化编译(比如加-O0),这样编译器不会过早复用寄存器,调试信息也更准确。
  • 另外,这种掩码操作大概率是编译器针对你的平台或自定义编译选项插的,你可以检查下编译参数里有没有相关的地址处理或自定义优化开关。

内容的提问来源于stack exchange,提问作者Darshan L

火山引擎 最新活动