第一章 一个GDB会话样例
第二章 进入和离开GDB
第三章 GDB 命令
第四章 在GDB里运行程序
第五章 中断和继续
第六章 检查栈
7 检查源文件
由于程序里记录的调试信息告诉GDB程序是由哪些文件编译的,GDB可以打印程序各部分源文件。程序
中断时,GDB同时自动打印是在哪一行上中断的。同样,当选择一个堆栈帧时(参见6.3节[选择帧],64页),
GDB也打印那个帧上的执行是在哪一行里中断的。用显式的命令可以打印源文件别的信息。
如果通过GNU Emacs接口来用GDB,可以优先选择Emacs工具来查看源代码;参见23章[在GNU Emacs里用GDB],
233页。
7.1 打印源代码行
要打印源文件里的代码行,用list命令(缩写为l)。缺省的,一次打印十行。有几个方式可以指定你希望
打印文件的哪部分;完整列表,参见7.2节[指定位置],68页。
下面是命令list最常用的形式:
list linenum
以当前行为中心,打印当前源文件linenum行。
list function
以函数function开头为中心打印源文件。
list 打印更多行。如果用list命令打印过源文件行,list命令将会在打印的最后一行代码下接着打印;否则
,如果最近打印的源代码行是单独的一行,作为显示堆栈帧的一部分的话,list命令将已这一行为中心
开始打印文件代码。
list - 只打印最近已打印的代码行前的代码。
缺省的,用上述形式的list命令,GDB打印十行源代码。可以用set listsize改变:
set listsize count
设置list命令显示count行源代码(除非list命令明确指定其它的数字)。
show listsize
显示list打印行的数量。
用回车键省略参数的重复list命令,和只输入list相等。和列出相同的代码行相比更有用。参数是’-'的list命
令是例外;这个参数是用来重复执行的,每次执行都会在源文件里向上打印。
总之,list命令期待用户提供0,1或者2的行定义。行定义指定源代码行;有多种方式来制定(参见7.2节[指定位置]
,68页),不过其效果都是制定某些源代码行。
下面是list命令的完整的参数描述:
list linespec
以linespec为中心打印源代码。
list first,last
从first开始打印到last。两个参数都是行定义。当list命令有两个行定义时,并且第二个行定义的源文
件省略了的话,两个行定义指的是在同一个文件里的代码。
list ,last
打印到last行。
list first,
从first开始打印
list + 只打印最近打印行后的代码
list - 只打印最近打印行前的代码
list 如前所述。
7.2 指定位置
有多个GDB命令接受指定程序代码位置的参数。由于GDB是源代码级的调试器,位置通常指的是源代码里的某一
行;因此,位置通常是指行定义。
下面是GDB所能识别的代码位置的指定方式:
linenum 指定当前源文件的行数量linenum
-offset
+offset 指定从当前行偏移offset。对于list命令,当前行是最经打印过的;对于断点命令,是当前选定的
堆栈帧上的执行中断处(堆栈帧的描述,参见6.1节[帧],61页)。要是用做list命令的两个行定义
的第二个参数,指定的是从第一个行定义开始的向上或向下的偏移。
filename:linenum
指定源文件filename的行号。
function
指定行从函数function为开始。例如,在C里,行是开括号{。
filename:function
指定行从文件filename里的函数function开始。要是有多个文件里有相同名字的函数的话,只需要用
文件名加上函数名就可以避免混淆了。
*address
指定程序地址address。对于行导向的命令,例如list和edit,这个参数指定位置为address的源代码行。
对于break和其它断点导向的命令,这个参数可以在不带调试信息或没有源文件的程序部分里设置断点。
这里address可以是当前工作语言(参见12章[语言],119页)的任意有效的指定代码位置的表达式。另外
,GDB扩展了用于位置的表达式的语义,以此包含在调试过程中经常碰到的情况。下面是address的各种
形式:
expression
当前工作语言的任意有效表达式。
funcaddr
函数的地址或源自名字的过程的地址。在C,C++,Java,Object-C,Fortran,微指令和汇编里
这个参数是函数名function(或者是一个有效表达式的一个特例)。在Pascal和Modula-2,是
&function。在Ada里,是function’Address(虽然Pascal形式也可用)。
这中形式指定函数第一个指令的位置,这个位置是在堆栈帧和参数建立前的。
‘filename’::funcaddr
和上面的funcaddr类似,不过还指定了源文件名。这个形式在只指定函数名会造成歧义的时候很
有用,例如,如果在不同的文件里有多个函数都具有相同的名字时。
7.3 编辑源文件
要编辑源文件里的内容,用edit命令。你选择的编辑程序将被调用,并将程序里的当前行设置为激活行。另外,
有几种方式可以指定你希望打印文件的哪部分,如果你要想看程序的其它部分的话。
edit location
编辑指定为location的源文件。编辑从location开始,例如,在指定文件的指定行赏。参见7.2节[指定位
置],68页,所有位置参数的可能形式;下面是edit命令最常用的形式:
edit number
将行number作为激活行编辑文件。
edit function
编辑包含函数function的文件,在其定义的开头开始编辑。
7.3.1 选择编辑器
GDB可以定制你所期望的任意编辑器(注1)。缺省的,是’/bin/ex’,但你可以在运行GDB前,设置环境变量
EDITOR来改变。例如,要配置GDB实用vi编辑器,在sh shell里用下面的命令:
EDITOR=/usr/bin/vi
export EDITOR
gdb …
(注1,唯一的限制在于,你的编辑器要识别如下命令行语义:ex +number file
可选的数值+number指定文件的行号。
)
或者在csh shell里,
setenv EDITOR /usr/bin/vi
gdb …
7.4 搜索源文件
有两个命令可以用正则表达式在当前文件里搜索。
forward-search regexp
search regexp
命令’forward-search regexp’检查每一行,从最近列出的行开始,查找正则表达式regexp的匹配项。命
令列出找到的行。也可以用同义词’search regexp’或缩写命令为fo.
reverse-search regexp
命令’reverse-search regexp’检查每一行,从最近列出的行往后,查找正则表达式regexp的匹配项。命
令列出找到的行。缩写为rev。
7.5 指定源文件目录
可执行程序有时并不记录源文件编译的目录,只记录名字。即使它们记录了,目录也可能在编译和调试期间被移
动了。GDB一个目录列表来搜索源文件;这些目录称为源代码路径。每次GDB需要源文件时,它都会尝试在所有的目
录列表里搜索,以它们在列表里的顺序进行,直到GDB找到所查询的文件。
例如,假设一个可执行文件引用了文件’/usr/src/foo-1.0/lib/foo.c’并且我们的源代码路径是’/mnt/cross’。
GDB首先会从字面上去查找;如果失败了,会在’/mnt/cross/usr/src/foo-1.0/lib/foo.c’里查找;如果还是失败
,会查找’/mnt/cross/foo.c’;如果再失败的话,会打印一个错误消息。GDB不会查找部分的路径名,例如
‘/mnt/cross/src/foo-1.0/lib/foo.c’。类似的,源代码路径的子目录也不会搜索:如果源代码路径是
‘/mnt/cross’,并且二进制文件引用了’foo.c’,GDB不会去’/mnt/cross/usr/src/foo-1.0/lib’路径下查找。
简单文件名,带先导路径的相对文件名,包含点的文件名(点文件)等等,都如上所述查找;比如,如果源代码
路径是’/mnt/cross’,并且像’../lib/foo.c’这样记录的话,GDB会首先尝试’../lib/foo.c’,接着
‘/mnt/cross/../lib/foo.c’,最后是’/mnt/cross/foo.c’。
注意,可执行文件搜索路径不用于定位源代码文件。
无论何时你重新设置或组织了源代码路径,GDB会清除任何缓存的关于路径和文件行的信息。
当你启动GDB时,它的源代码路径只包含’cdir’和’cwd’,且以此顺序排列。要增加其它目录,用directory命令。
搜索路径用于查找程序源文件和GDB脚本文件(读用’-command’选项和’source’命令)。
除了源文件路径,GDB提供了一些命令来管理源代码路径列表替换的规则。替换规则说明了在编译和调试期间目
录被移动的情况下,如何来重写存储于程序调试信息里的源代码目录。一个规则是由两个参数组成的,第一个参数
指定需要重写的路径,第二个参数指定这个路径如何重写。在[设置替换路径],72页里,我们将这两个部分命名为
from和to。GDB简单的用to将from的源文件名的开头目录部分替换掉,并用替换后的结果来搜索源文件。
还用前例,假设’foo-1.0′目录树已从’/usr/src’移动到’/mnt/cross’,然后让GDB用’/mnt/cross’替换所有路径
的’/usr/src’。首先在路径’/mnt/cross/foo-1.0/lib/foo.c’查询,替代原来的路径’/usr/src/foo-1.0/lib/foo.c’。
要定义一个源代码路径替换规则,用set substitute-path命令(参见[设置替换路径],72页)。
要避免意外的替换结果,规则只在目录名的from部分以目录分隔符结尾的情况下才应用。例如,用’/usr/source’
替换’/mnt/cross’的话,会得到’/usr/source/foo-1.0′,而不是’/usr/sourceware/foo-2.0′。并且替换规则只应
用于目录名的开头部分,这个规则不会得到’/root/usr/source/baz.c’的结果。
在很多情况下,可以用directory命令来达到相同的效果。然而,在源文件分布于一个复杂的,带有多个子目录
的目录树时,set substitute-path会更有效率。用directory命令的话,用户需要添加项目的每一个子目录。如果
将整个目录移动且保持内部代码组织的话,那么set substitute-path允许你只用一个命令就可以将调试器导向到
所有的源文件。
set substitute-path也不仅仅只是一个快捷命令。源代码路径只用于原目录下的文件不再存在的情况下。另一
方面,set substitute-path修改调试器行为模式来在重写的位置上搜索文件。所以,如果有任何原因导致源代码
文件相对于可执行程序的位置发生了改变,替换规则是唯一可以通知GDB新位置的方法。
directory dirname …
dir dirname …
在源代码路径前加上目录dirname。可以将几个目录名传递给这个命令,用’:'分隔(在MS-DOS和
MS-Windows是’;',’:'通常是一个绝对路径名的一部分),或者用空格分隔。可以指定已存在的目录;这
会将此目录前移,GDB就能更快的搜索到它了。
可以用字符串’$cdir’来指代编译目录(如果已记录的话),’$cwd’指代当前工作目录。’$cwd’和’.'不相
同—前者记录在GDB调试会话期间变动的当前工作目录,后者则在你将一个目录添加进源代码路径时立即
展开为当前目录。
directory
将源代码路径重置为默认值(Unix系统下是’$cdir:$cwd’)。这个命令要求确认。
show directories
打印源代码路径:显示包含那个目录。
set substitute-path from to
定义一个源代码路径替换规则,并将其填加到当前替换规则列表的尾部。如果有相同的from规则存在的话
,那么旧的规则就会被删除。
例如,如果文件’/foo/bar/baz.c’移动到’/mnt/cross/baz.c’,那么命令
(gdb) set substitute-path /usr/src /mnt/cross
告诉GDB用’/mnt/cross’替换’/usr/src’,这就可以让GDB查找到’baz.c’,即使它已经移走了。
如果定义了多个替换规则,那么GDB会以规则定义的顺序一个接一个的计算它们。如果有的话,第一个匹
配项就会进行替换。
例如,如果我们输入了下列命令:
(gdb) set substitute-path /usr/src/include /mnt/include
(gdb) set substitute-path /usr/src /mnt/src
GDB会用第一个规则将’/usr/src/include/defs.h’用’/mnt/include/defs.h’替换。不过,它会用第二个
规则将’/usr/src/lib/foo.c’用’/mnt/src/lib/foo.c’替换。
unset substitute-path [path]
如果指定了path的话,在当前替换规则列表里搜索要重置的规则。如果找到的话就删除之。如果没有找到
的话,调试器会输出一个警告信息。
如果没有指定path的话,那么所有的替换规则都将被删除。
show substitute-path [path]
如果指定了path,那么打印打印源代码路径替换规则,如果有的话。
如果没有指定path,那么打印所有的替换规则。
如果源代码路径混杂着一些不再有用的目录的话,GDB可能在某些情况下造成错误的代码版本的混淆。可以用下
列命令来纠正这种错误:
1.用不带参数的directory命令来重置源代码路径为默认值。
2.用带正确参数的directory来添加你需要的目录。可以用一个命令将所有的路径添加。
7.6 源代码和机器代码
可以用命令info line将源代码映射到程序地址上(反过来一样),命令disassemble可以显示一定范围的机器指
令。在GNU Emacs模式下运行时,info line命令会引起箭头指向指定的行。而且,info line打印符号形式的地址
,也打印16进制的地址。
info line linespec
打印指定的源代码行的编译代码,从开始到结尾的地址。可以指定源代码行,7.2节[指定位置],68页里
讨论的任意形式。
例如,我们可以用info line来查找函数m4_chagequote的第一行的目标代码:
(gdb) info line m4_changequote
Line 895 of “builtin.c” starts at pc 0×634c and ends at 0×6350.
我们也可以查询(用*addr作为linespec的形式)哪一行源代码对应一个特定的地址:
(gdb) info line *0×63ff
Line 926 of “builtin.c” starts at pc 0×63e4 and ends at 0×6404.
在info line后,x命令的缺省地址就变为这行的开头地址,所以’x/i’足以开始检查机器代码(参见8.5节[检查
内存],79页)。而且,这个地址也存储与变量$_里(参见8.9节[便利的变量],89页)。
disassemble
这个命令将一段内存作为机器指令转储。缺省的内存范围是选定堆栈帧的程序计数器所代表的函数。单个
参数的话是程序计数器的值;GDB转储值个值附近的函数。两个参数指定要转储的地址范围(第一个是开
始,第二个是结束)。
下面的例子显示了反汇编一个HP PA-RISC 2.0上的一段代码:
(gdb) disas 0×32c4 0×32e4
Dump of assembler code from 0×32c4 to 0×32e4:
0×32c4 <main+204>: addil 0,dp
0×32c8 <main+208>: ldw 0×22c(sr0,r1),r26
0×32cc <main+212>: ldil 0×3000,r31
0×32d0 <main+216>: ble 0×3f8(sr4,r31)
0×32d4 <main+220>: ldo 0(r31),rp
0×32d8 <main+224>: addil -0×800,dp
0×32dc <main+228>: ldo 0×588(r1),r26
0×32e0 <main+232>: ldil 0×3000,r31
End of assembler dump.
某些架构有多个通用的指令助记符或者同义词。
对于动态链接的和使用共享库的程序,调用函数或位于共享库的分支位置的指令可能显示伪地址–这个地址是重
定位表的位置。在某些架构里,GDB可以将这些伪地址映射到函数名上。
set disassembly-flavor instruction-set
用disassemble或x/i命令反汇编程序时,选择指令集。
目前这个命令只在Intel x86族平台上定义。可以设置指令集为intel或att。默认是att,基于x86系统的
Unix汇编器默认使用AT&T风格。
show disassembly-flavor
显示当前反汇编风格的设置。