原址:http://wiki.chinaunix.net/6._%E8%A7%82%E5%AF%9F%E5%A0%86%E6%A0%88
6. 观察堆栈
当程序停止时,你要知道的第一件事情就是程序停在了哪儿、程序是如何运行到那儿的。
每次程序执行函数调用的时候,就产生关于该调用的消息。这个消息包括这个函数调用在程序中的位置、该调用的参数和被调用函数的局部变量。这个消息被保存在一块叫做“栈帧(stack frame)”的数据区,这个栈帧分配在一块叫做“调用栈(call stack)”的内存区上。
当程序停止时,用于查看栈内信息的GDB命令可以让你看到所有的这些消息。
其中的一个栈帧被GDB选定,而且很多GDB命令隐式地参照这个帧。特别是,无论何时,你要查看程序中某个变量的值,该值就会在这个帧中找到。特定的GDB命令用来指定你感兴趣的帧。参见Selecting a frame。
当你的程序停止时,GDB自动选择当前正在执行的帧并予以简短地描述,类似于frame命令(参见Information about a frame)
[ 编辑]
6.1 栈帧
函数调用栈被分成了连续的部分,叫做栈帧,或者简称为帧。每一个帧对应着一个函数调用。帧包含着给这个函数传递的参数,些函数的局部变量和函数开始执行的地址。
当你的程序开始时,栈中只有一个帧,就是main函数的帧。它被称作初始帧,或者最外层帧。每当一个函数被调用时,一个新的帧产生。每当一个函数返回时,那个函数所对应的帧就消失。如果一个函数是递归的,就会存在一个函数的多个帧。现在正在运行的那个函数所对应的帧叫作最内层帧,它是现在还存在的所有帧里面最后一个创建的。
在你的程序里面,栈帧是由它的地址来识别的。一个栈帧由许多有自己地址的字节构成。每种计算机有一个选择哪一个字节的地址来当作帧地址的规定。当程序运行在某个帧时,通常这个帧的地址被放在一个叫作帧指针寄存器的寄存器中。
GDB给所有存在的栈帧分配一个编号,从代表最内层帧的0开始,1代表调用它的帧,依此向上推。这些数字并不真正的存在在你的程序中,它们是GDB给你的在GDB命令中指定栈帧的一个方法。
有些编译器提供一种可以避免栈帧的编译函数的方法。(比如,gcc选项
-fomit-frame-pointer
就会产生没有栈帧的函数。)这有时会因为调用了大量的库函数而要节省设置帧的时间时采用。GDB只有有限的工具来处理这种函数。当最内层函数没有栈帧时,GDB还是会当作它有一全像平时那样的编号为零的帧,也可以正确地跟踪函数调用链。但是GDB没有明确的规定怎么处理栈中其它地方的函数。 (这段不是很通顺,不大知道应该怎么译)
frame args
frame命令让你可以从一个帧移动到另一个帧,并打印你所选择的帧。args可以是帧的地址或者编号。如果没有指定参数,frame会输出当前栈帧。
select-frame
使用select-frame命令你可以从一个帧移动到另一个帧但是不会输出任何信息,它是frame的无输出版本。
[ 编辑]
6.2 回溯
一个回溯(backtrace)是你的程序如何到达现在位置的摘要。如果有很多帧的话,它每行显示一个帧,从现在正在执行的帧(编号为零的帧)开始,紧随着它的是调用它的帧,依此类推。
backgrace bt
输出整个栈:对于栈中所有的帧每行输出一帧。
你可以通过键入系统中断符(通常是C-c)来停止回溯。
backtrace n bt n
同backtrace,不过只输出最内n层帧。
backgrace -n bt -n
同backtrace,不过只输出最外n层帧。
bactrace full
连同局部变量也一起输出。
bt full
where和info stack(简写为info s)也是backtrace的别名。
在一个多线程的程序中,GDB默认只输出当前线程的回溯。要输出多个或所有线程的回溯,要用命令 thread apply(参见thread apply一节)。比如,如果你键入thread apply all backtrace,GDB就会输出所有线程的回溯;当你在调试core dump(不会译了)或者多线程的程序时这会给带来很大的便利。
回溯中的每一行显示了帧的编号和函数名。如果你没有设置 set print address off 的话,程序计数器(program counter value)同样会显示。回溯也会显示源代码的文件名,所在的行和给函数传递的参数。程序计数器会被省略如果你的程序正停止在这一行代码的开始处。
这里有一个回溯的实例。它是通过命令 "bt 3" 得到的,所以它只显示了最内三层帧。
#0 m4_traceon (obs=0x24eb0, argc=1, argv=0x2b8c8) at builtin.c:993 #1 0x6e38 in expand_macro (sym=0x2b600) at macro.c:242 #2 0x6840 in expand_token (obs=0x0, t=177664, td=0xf7fffb08) at macro.c:71 (More stack frames follow...)
编号为零的帧并没有以程序计数器开始,说明你的程序正停在builtin.c的第993行代码的开始处。
如果你的程序在编译时进行了优化,有些编译器会把传递给函数的但是在那次调用中没有用到的参数优化掉。那会产生通过寄存器传递参数的代码,但是它不会把这些参数保存在栈帧中。GDB没有办法显示那些没有出现在最内层帧的这类参数。这种类型的回溯看上去可能会是这样的:
#0 m4_traceon (obs=0x24eb0, argc=1, argv=0x2b8c8) at builtin.c:993 #1 0x6e38 in expand_macro (sym=<value optimized out>) at macro.c:242 #2 0x6840 in expand_token (obs=0x0, t=<value optimized out>, td=0xf7fffb08) at macro.c:71 (More stack frames follow...)
没有被保存在栈帧中的参数会被显示为 "<value optimized out>"。
如果你需要显示那些被优化掉了的参数,要么从其它决定于它的变量那里计算得到,要么就去不加优化地重新编译你的程序。
大部分程序都有一个用户入口(user entry point)。它是系统库函数和起始程序段切换到用户代码的地方。对于C就是main函数。当GDB在回溯中发现用户入口时就会中止回溯来避免跟踪到高度系统相关(通常也比较乏味)的代码中。
如果你需要检查起动代码,或者限制回溯的层数,你可以改变这种行为:
set backtrace past-main set backtrace past-main on
回溯会从用户入口继续执行。
set backtrace past-main off
回溯会停止在用户入口处,这是默认的。
show backtrace past-main
显示当前的关于用户入口的设定。
set backtrace past-entry set backtrace past-entry on
当到达应用程序内部入口(internal entry point)时回溯会继续进行。当这个构建这个程序时这个入口会被连接器编码,很有可能它会在用户入口这前被调用(不通顺,不大明白这段的意思)。
set backtrace past-entry off
当遇到应用程序内部入口时回溯会停止。这是默认的。
show backtrace past-entry
显示当前的关于内部入口的设定。
set backtrace limit n set backtrace limit 0
限定回溯的最大层数为n。零表示没有限制。
show backtrace limit
显示当前回溯层数限制。
[ 编辑]
6.3 选择帧
大多数检查栈和其它数据的命令都对当然选中的帧有效。下面是一些选择并输出一些所选栈帧简明信息的命令。
frame n f n
选择编号为n的帧。回忆一下,编号为零的帧是最内层(当前正在执行的)帧,编号为一的帧是调用最内层帧的帧,依此类推。编号最大的帧是main函数所对应的帧。
frame addr f addr
选择在地址addr处的帧。当栈帧链被臭虫(bug)所破坏,从而使GDB没法正确地给所有帧分配编号时,它很有用。
在SPARC架构的机器上,需要有两个地址才能选中一个帧,它们分别是帧指针(地址)和栈指针。
在MIPS和Alpha架构的机器上,也需要两个地址:一个栈指针和一个程序计数器。
在29k架构上,需要三个地址:一个寄存器地址,一个程序计数器和一个内存栈指针。
up n
在栈中向上移动n帧。对于正整数n,向最外层的,编号更大的,存在了更长时间的帧移动。n默认是一。
down n
在栈中向下移动n帧。对于正整数n,向最内层的,编号更小的,更晚创建的帧移动。你可以把down简写作do.
这三个命令以两行帧的描述结束。第一行显示了帧的编号,函数名,参数,源文件和在那一帧中正在执行的行的行号。第二行显示源文件正在执行的那行程序的内容。 例如:
(gdb) up #1 0x22f0 in main (argc=1, argv=0xf7fffbf4, env=0xf7fffbfc) at env.c:10 10 read_input_file (argv[i]);
在这样一个输出以后,不带参数的list命令输出以帧中正在执行处为中心的十行。你也可以用你最喜欢的编辑器在你程序正在执行处编辑你的程序。参考Printing source lines来获得更多的细节。
up-silentely n down-silentely n 这两个命令分别是up和down的变种。不同之处是他们不会输出新帧的信息。它们一般被用在不需要也不方便输出的GDB脚本中。
[ 编辑]
6.4 帧的信息
有一些其它的命令来输出选中栈帧的信息。
frame f
当不加参数地用这个命令时,不会改变所选的帧,但是会输出一段当前选中的帧的简要信息。它可以被简写作f。当有参数时,这个命令被用来选择一个栈帧。参见选择帧。
info frame info f
这个命令输出一段所选栈帧的详细信息,包括:
- 帧的地址
- 下一个(此帧调用的)帧的地址
- 上一个(调用此帧的)帧的地址
- 编写此帧所用的语言
- 帧参数的地址
- 帧的局部变量的地址
- 保存的程序计数器(调用此帧的帧中执行到的地址)
- 保存在帧中的寄存器
在栈的格式不符合常规时这些详细的信息会很有用(不通顺)
info frame addr info f addr
输出在地址addr的帧的详细信息,但是不选择此帧。在执行此命令后,被选中的帧不会改变。它需要指定跟用在 frame 命令中的一样的地址(在有的架构需要大于一个地址)。参见选择帧。
info args 分别在每一行输出一个选中帧的参数。
info locals 分别在每一行输出一个选中帧的局部变量。它们是运行到此帧中当前位置所分配的所有变量(包括静态的和自动的)。
info catch 输出一个在帧中运行处所有活跃的例外处理器(exception handler)的列表。如果想看到其它的例外处理器,去查看相应的帧(用up, down和frame命令),然后输入info catch。参见Setting catchpoints。