描述__x86.get_pc_thunk.bx对GDB打印局部变量的影响,顺便简单介绍gdb的原理。
问题简要描述
最近乘着项目的空余时间在做《MIT6.828》这个作业,久闻其名之后,我开始了真正的作业。 但是在做作业的过程中,却发现了一个异常场景,通过GDB打印函数中的局部变量的时候,却发现获取的变量的值并不是真正的变量地址。我目前使用的GDB版本号和GCC版本号如下: gcc版本号是7.5.0,gdb版本号是8.1.1。
问题详细描述
- 编写代码如下
//test1.c int add(int lhs, int rhs) { return lhs + rhs; } int glob = 400; void main() { int i = 0; int test123 = 200; int c = add(i, test123); for (; i < 300; ++i) { glob = glob + i; } return; }
-
编译为32位,且使用
stabs
调试信息gcc -gstabs -o test1 teest1.c -m32
- 通过gdb对
test1
程序进行分析,打印局部变量i
后,发现i
的值并不是0,而是打印了test123
的值,同时,test123
对应的值也不再正确
- gcc加入编译命令,
-no-pie -fno-pic
,去除了内部函数__x86.get_pc_thunk.bx
,之后,gdb能够正确打印局部变量。去除编译器为我们生成的__x86.get_pc_thunk.axgcc -gstabs -no-pie -fno-pic -o test1 test1.c -m32
原因分析
GDB原理简述
原来gdb的底层调试原理这么简单 How debugger works
GDB启动过程
系统首先会启动gdb进程,这个进程会调用系统函数fork()
来创建一个子进程,这个子进程做两件事情: 1. 调用系统函数ptrace(PTRACE_TRACEME,[其他参数]);
2. 通过execc
来加载、执行可执行程序test
,那么test
程序就在这个子进程中开始执行了。
ptrace
ptrace
系统函数是Linux内核提供的一个用于进程跟踪的系统调用,通过它,一个进程(gdb)可以读写另外一个进程(test)的指令空间、数据空间、堆栈和寄存器的值。而且gdb进程接管了test进程的所有信号,也就是说系统向test进程发送的所有信号,都被gdb进程接收到,这样一来,test进程的执行就被gdb控制了,从而达到调试的目的。
正是在ptrace
的帮助下,gdb才拥有了强大的调试能力。函数原型是:
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
gdb如何确定局部变量的值
以调试格式stab
为例。objdump -G test
可以获取stab
信息。
我们关注.stab
节中的n_value
列,对于局部变量i
和test123
来说,其n_type
的类型是LSYM
表示这是一个局部变量,其n_value
的值表示的是其距离栈底的距离,以负数显示。从调试信息来看,i
距离栈底为-12,test123
距离栈底为-8。因此当我们打印局部变量i
和test123
的时候,会取出寄存器ebp
的值,并加上n_value
的值,然后调用ptrace
系统调用,传入对应的地址,就可以获取得到相应的局部变量的值了。
test@iZuf6bogmr00a004vw0sbeZ:~/ctest$ objdump -G test1
test1: 文件格式 elf32-i386
.stab 节的内容:
Symnum n_type n_othr n_desc n_value n_strx String
-1 HdrSym 0 51 000003d1 1
0 SO 0 2 000004ed 1 test1.c
1 OPT 0 0 00000000 9 gcc2_compiled.
2 LSYM 0 0 00000000 24 int:t(0,1)=r(0,1);-2147483648;2147483647;
3 LSYM 0 0 00000000 66 char:t(0,2)=r(0,2);0;127;
4 LSYM 0 0 00000000 92 long int:t(0,3)=r(0,3);-2147483648;2147483647;
5 LSYM 0 0 00000000 139 unsigned int:t(0,4)=r(0,4);0;4294967295;
6 LSYM 0 0 00000000 180 long unsigned int:t(0,5)=r(0,5);0;4294967295;
7 LSYM 0 0 00000000 226 __int128:t(0,6)=r(0,6);0;-1;
8 LSYM 0 0 00000000 255 __int128 unsigned:t(0,7)=r(0,7);0;-1;
9 LSYM 0 0 00000000 293 long long int:t(0,8)=r(0,8);-0;4294967295;
10 LSYM 0 0 00000000 336 long long unsigned int:t(0,9)=r(0,9);0;-1;
11 LSYM 0 0 00000000 379 short int:t(0,10)=r(0,10);-32768;32767;
12 LSYM 0 0 00000000 419 short unsigned int:t(0,11)=r(0,11);0;65535;
13 LSYM 0 0 00000000 463 signed char:t(0,12)=r(0,12);-128;127;
14 LSYM 0 0 00000000 501 unsigned char:t(0,13)=r(0,13);0;255;
15 LSYM 0 0 00000000 538 float:t(0,14)=r(0,1);4;0;
16 LSYM 0 0 00000000 564 double:t(0,15)=r(0,1);8;0;
17 LSYM 0 0 00000000 591 long double:t(0,16)=r(0,1);12;0;
18 LSYM 0 0 00000000 624 _Float32:t(0,17)=r(0,1);4;0;
19 LSYM 0 0 00000000 653 _Float64:t(0,18)=r(0,1);8;0;
20 LSYM 0 0 00000000 682 _Float128:t(0,19)=r(0,1);16;0;
21 LSYM 0 0 00000000 713 _Float32x:t(0,20)=r(0,1);8;0;
22 LSYM 0 0 00000000 743 _Float64x:t(0,21)=r(0,1);12;0;
23 LSYM 0 0 00000000 774 _Decimal32:t(0,22)=r(0,1);4;0;
24 LSYM 0 0 00000000 805 _Decimal64:t(0,23)=r(0,1);8;0;
25 LSYM 0 0 00000000 836 _Decimal128:t(0,24)=r(0,1);16;0;
26 LSYM 0 0 00000000 869 void:t(0,25)=(0,25)
27 FUN 0 0 000004ed 889 add:F(0,1)
28 PSYM 0 0 00000008 900 lhs:p(0,1)
29 PSYM 0 0 0000000c 911 rhs:p(0,1)
30 SLINE 0 8 00000000 0
31 SLINE 0 9 0000000d 0
32 SLINE 0 10 00000015 0
33 GSYM 0 0 00000000 922 glob:G(0,1)
34 FUN 0 0 00000504 934 main:F(0,25)
35 SLINE 0 13 00000000 0
36 SLINE 0 14 00000012 0
37 SLINE 0 15 00000019 0
38 SLINE 0 16 00000020 0
39 SLINE 0 17 00000031 0
40 SLINE 0 18 00000033 0
41 SLINE 0 17 00000044 0
42 SLINE 0 17 00000048 0
43 SLINE 0 20 00000051 0
44 SLINE 0 21 00000052 0
45 LSYM 0 0 fffffff4 947 i:(0,1)
46 LSYM 0 0 fffffff8 955 test123:(0,1)
47 LSYM 0 0 fffffffc 969 c:(0,1)
48 LBRAC 0 0 00000000 0
49 RBRAC 0 0 00000057 0
50 SO 0 0 0000055b 0
原因定位
从.stab
节中可以看出局部变量i
和test123
距离栈底的位置。我们反汇编test1
程序,发现,i和test123局部变量的地址和stab
节中的地址不同。局部变量i
的地址距离栈底为-16,test123
距离栈底的距离为-12。所以我们通过gdb打印的时候,打印局部变量i
的值,结果就会显示局部变量test123
的值。
00000504 <main>:
int glob = 400;
void main() {
504: 55 push %ebp
505: 89 e5 mov %esp,%ebp
507: 53 push %ebx
508: 83 ec 10 sub $0x10,%esp
50b: e8 e0 fe ff ff call 3f0 <__x86.get_pc_thunk.bx>
510: 81 c3 cc 1a 00 00 add $0x1acc,%ebx
int i = 0;
516: c7 45 f0 00 00 00 00 movl $0x0,-0x10(%ebp)
int test123 = 200;
51d: c7 45 f4 c8 00 00 00 movl $0xc8,-0xc(%ebp)
int c = add(i, test123);
524: ff 75 f4 pushl -0xc(%ebp)
527: ff 75 f0 pushl -0x10(%ebp)
52a: e8 be ff ff ff call 4ed <add>
52f: 83 c4 08 add $0x8,%esp
532: 89 45 f8 mov %eax,-0x8(%ebp)
for (; i < 300; ++i) {
535: eb 15 jmp 54c <main+0x48>
glob = glob + i;
537: 8b 93 2c 00 00 00 mov 0x2c(%ebx),%edx
53d: 8b 45 f0 mov -0x10(%ebp),%eax
540: 01 d0 add %edx,%eax
542: 89 83 2c 00 00 00 mov %eax,0x2c(%ebx)
for (; i < 300; ++i) {
548: 83 45 f0 01 addl $0x1,-0x10(%ebp)
54c: 81 7d f0 2b 01 00 00 cmpl $0x12b,-0x10(%ebp)
553: 7e e2 jle 537 <main+0x33>
}
return;
555: 90 nop
}
556: 8b 5d fc mov -0x4(%ebp),%ebx
559: c9 leave
55a: c3 ret