编译优化二进制文件中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的值:%r15是0x8000000000000000,lea -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存了原始%rsi(0x00007f8bcd651748),执行p (struct type2 **)0x00007f8bcd651748就能看到真实的arg2指向内容。 - 如果想让GDB正确显示参数,临时关闭优化编译(比如加
-O0),这样编译器不会过早复用寄存器,调试信息也更准确。 - 另外,这种掩码操作大概率是编译器针对你的平台或自定义编译选项插的,你可以检查下编译参数里有没有相关的地址处理或自定义优化开关。
内容的提问来源于stack exchange,提问作者Darshan L




