内存布局里的一个区域被称为栈,它是栈框架的集合。每个栈框架表示一个函数调用。随着函数被调用,栈框架的数量会增加,栈也是增长。相反,当函数从它们的调用者那里返回,栈框架的数量减少,栈也会缩减。
一个程序由一个或多个通过调用对方来交互的函数组成。每当一个函数被调用时,内存的一块区域为这个新的函数调用分配好,被称为栈框架。这块区域拥有一些重要信息,比如;
1、新的被调用函数的所有自动变量的存储空间。
2、被调用函数返回值要返回到的调用者的行号。
3、被调用函数的参数。
每个函数都有它自己的栈。总的来看,所有栈框架组成了调用栈(call stack)。
调试之前,应该准备好可以调试的可执行程序。简单来说,我们需要在程序里加入信息。
一个符号(symbol)是一个变量或一个函数。符号表(Symbol Tables)就是在一个可执行程序里的变量和函数的一张表。通常,符号表只包含符号的内存地址,因为计算计不使用(或不关心)我们给变量和函数的命名。
但是为了让GDB对我们有用,它需要能够引用变量名和函数名,而不是它们的地址。人类使用main()或i这样的名字。计算机使用0x804b64d或0xbffff7784这样的地址。为了这个目的,我们可以用“调试信息”编译代码,来告诉GDB两件事:
1、如何把符号的地址和源码的名字关联起来;
2、如何把一个机器代码的地址和一行源码关联起来。
拥有这些额外调试信息的符号表被称为一个参数化的或增强的符号表。因为GCC和GDB在如此多的不同平台上运行,调试信息有许多不同的格式:
stabs:多数BSD系统上的DBX使用的格式。
coff:在System V Release 4之前的多数System V系统上的SDB使用的格式。
xcoff:IBM RS/6000系统上的DBX使用的格式。
dwarf:多种SVR4上的SDB使用的格式。
dwarf2:IRIX6上的DBX使用的格式。
vms:VMS系统上的DEBUG使用的格式。
除了调用格式外,GDB了解允许它使用GNU扩展的这些格式的增强变体。使用不是GDB的其它东西来调试一个带有GNU增强调试格式的可执行程序,会导致调试器崩溃。
不要被所有这些格式吓到,GDB会自动为你挑选最好的格式。极少情况下你才需要一个不同的格式,可能千分之一的概率。
使用gcc的-g选项可以为一个可执行程序产生一个增强的符号表。
正如之前讨论的,有许多调试格式。-g的确切含义是为你的系统以本地格式产生调试信息。
作为-g的替代,你还可以使用gcc的-ggdb选项。它以最昂贵的格式来产生调试信息,包括前面讨论的GNU增强变体。这很可能是你在多数情况下想要使用的选项。
你也可以给-g、-ggdb和所有其它调试格式选项一个数值参数。1表示最少量的信息而3表示最多量。没有一个数值参数时,调试等级默认为2。通过使用-g3你甚至可以访问预处理的宏,这非常棒。我们最好总是使用-ggdb3来产生增强符号表。
被编译进可执行程序的调试信息不会被读入内存,除非GDB载入这个可执行程序。这表示含有调试信息的可执行程序不会比不带调试信息的可执行程序运行得更慢(一个普遍误解)。尽管可调试的可执行程序占据更多的磁盘空间,可执行程序不会有更大的“内存使用量”,除非从GDB载入。相似的,除非你有GDB来运行调试可执行程序,否则它的载入时间也是近乎相同。
最后一点,我们可以在带有一个参数化的符号表的可执行程序上执行编译器优化。换句话说:gcc -g -O9 try1.c。事实上,GDB是少有的通常能很好调试被优化的可执行程序的符号调试器。然而,你通常应该在调试一个可执行程序前关闭优化,因为会有使GDB困惑的情况。变量可能因为优化而不存在,函数可能被内联,更多的可能或可能不困惑gdb的事情都会发生。为了安全起见,在调试一个程序时关闭优化。
给出下面的C代码try1.c:
- #include <stdio.h>
- static void display(int i, int *ptr);
- int main(void) {
- int x = 5;
- int *xptr = &x;
- printf("In main():\n");
- printf(" x is %d and is stored at %p.\n", x, &x);
- printf(" xptr points to %p which holds %d.\n", xptr, *xptr);
- display(x, xptr);
- return 0;
- }
- void display(int z, int *zptr) {
- printf("In display():\n");
- printf(" z is %d and is stored at %p.\n", z, &z);
- printf(" zptr points to %p which holds %d.\n", zptr, *zptr);
- }
用命令gcc -ggdb3 -O0 -o try1 try1.c来编译。先查看try1的size:
$ ls -l try1
-rwxrwxr-x 1 tommy tommy 24752 2012-03-28 10:55 try1
size -A try1
try1 :
section size addr
.interp 19 134512980
.note.ABI-tag 32 134513000
.note.gnu.build-id 36 134513032
.gnu.hash 32 134513068
.dynsym 96 134513100
.dynstr 81 134513196
.gnu.version 12 134513278
.gnu.version_r 32 134513292
.rel.dyn 8 134513324
.rel.plt 32 134513332
.init 46 134513364
.plt 80 134513424
.text 556 134513504
.fini 26 134514060
.rodata 182 134514088
.eh_frame_hdr 60 134514272
.eh_frame 228 134514332
.ctors 8 134520596
.dtors 8 134520604
.jcr 4 134520612
.dynamic 200 134520616
.got 4 134520816
.got.plt 28 134520820
.data 8 134520848
.bss 8 134520856
.comment 42 0
.debug_aranges 32 0
.debug_info 228 0
.debug_abbrev 157 0
.debug_line 429 0
.debug_str 138 0
.debug_loc 112 0
.debug_macinfo 15956 0
Total 18920
使用命令strip --only-keep-debug try1来移除除debug符号之外的符号。再看下它的size:
$ ls -l try1
-rwxrwxr-x 1 tommy tommy 21060 2012-03-28 11:25 try1
$ size -A try1
try1 :
section size addr
.interp 19 134512980
.note.ABI-tag 32 134513000
.note.gnu.build-id 36 134513032
.gnu.hash 32 134513068
.dynsym 96 134513100
.dynstr 81 134513196
.gnu.version 12 134513278
.gnu.version_r 32 134513292
.rel.dyn 8 134513324
.rel.plt 32 134513332
.init 46 134513364
.plt 80 134513424
.text 556 134513504
.fini 26 134514060
.rodata 182 134514088
.eh_frame_hdr 60 134514272
.eh_frame 228 134514332
.ctors 8 134520596
.dtors 8 134520604
.jcr 4 134520612
.dynamic 200 134520616
.got 4 134520816
.got.plt 28 134520820
.data 8 134520848
.bss 8 134520856
.comment 42 0
.debug_aranges 32 0
.debug_info 228 0
.debug_abbrev 157 0
.debug_line 429 0
.debug_str 138 0
.debug_loc 112 0
.debug_macinfo 15956 0
Total 18920
如果用命令strip --strip-debug try1来移除调试符号,它的size为:
$ ls -l try1
-rwxrwxr-x 1 tommy tommy 7154 2012-03-28 11:25 try1
看下此时的段信息:
$ size -A try1
try1 :
section size addr
.interp 19 134512980
.note.ABI-tag 32 134513000
.note.gnu.build-id 36 134513032
.gnu.hash 32 134513068
.dynsym 96 134513100
.dynstr 81 134513196
.gnu.version 12 134513278
.gnu.version_r 32 134513292
.rel.dyn 8 134513324
.rel.plt 32 134513332
.init 46 134513364
.plt 80 134513424
.text 556 134513504
.fini 26 134514060
.rodata 182 134514088
.eh_frame_hdr 60 134514272
.eh_frame 228 134514332
.ctors 8 134520596
.dtors 8 134520604
.jcr 4 134520612
.dynamic 200 134520616
.got 4 134520816
.got.plt 28 134520820
.data 8 134520848
.bss 8 134520856
.comment 42 0
Total 1868
如果用命令strip --strip-all try1来移除所有符号,它的size为:
$ ll try1
-rwxrwxr-x 1 tommy tommy 5520 2012-03-28 11:38 try1*
它的段信息:
try1 :
section size addr
.interp 19 134512980
.note.ABI-tag 32 134513000
.note.gnu.build-id 36 134513032
.gnu.hash 32 134513068
.dynsym 96 134513100
.dynstr 81 134513196
.gnu.version 12 134513278
.gnu.version_r 32 134513292
.rel.dyn 8 134513324
.rel.plt 32 134513332
.init 46 134513364
.plt 80 134513424
.text 556 134513504
.fini 26 134514060
.rodata 182 134514088
.eh_frame_hdr 60 134514272
.eh_frame 228 134514332
.ctors 8 134520596
.dtors 8 134520604
.jcr 4 134520612
.dynamic 200 134520616
.got 4 134520816
.got.plt 28 134520820
.data 8 134520848
.bss 8 134520856
.comment 42 0
Total 1868
在移除了上面的部分后,程序仍可以正常运行。但是执行strip --remove-section=.text try1的话,程序无法运行,报出错误“段错误”。看看它此时的信息:
$ ll try1
-rwxrwxr-x 1 tommy tommy 5472 2012-03-28 11:42 try1*
$ size -A try1
try1 :
section size addr
.interp 19 134512980
.note.ABI-tag 32 134513000
.note.gnu.build-id 36 134513032
.gnu.hash 32 134513068
.dynsym 96 134513100
.dynstr 81 134513196
.gnu.version 12 134513278
.gnu.version_r 32 134513292
.rel.dyn 8 134513324
.rel.plt 32 134513332
.init 46 134513364
.plt 80 134513424
.fini 26 134514060
.rodata 182 134514088
.eh_frame_hdr 60 134514272
.eh_frame 228 134514332
.ctors 8 134520596
.dtors 8 134520604
.jcr 4 134520612
.dynamic 200 134520616
.got 4 134520816
.got.plt 28 134520820
.data 8 134520848
.bss 8 134520856
.comment 42 0
Total 1312
可以看到移除.text段会连同debug信息一同移除。