gdb系列之四 在GDB里运行程序

来源:互联网 发布:淘宝订单编号前13位 编辑:程序博客网 时间:2024/06/09 18:35
在你开始在GDB里运行程序前,你需要在编译的时候产生调试信息。
  你可以在你选定的环境里带参数(如果有的话)的启动GDB。如果你是在本地调试,你可以重定向输入输出,调试一个已运行
的进程,或者结束一个进程。

4.1 为调试而编译
  为了有效的调试程序,你需要在编译的时候产生调试信息。调试信息存储在目标文件里;调试信息描述了数据和函数的类型,
源代码和可执行代码的对应关系。
  编译时指定编译器的’-g’选项可以产生调试信息。
  在编译给你的客户发布的程序时,可以用’-O’选项指定编译器进行优化。然而,许多编译器不能同时处理’-g’和’-o’选项。如果用的
是这些编译器,你得不到带有调试信息的优化过的可执行程序。
  GCC,GNU C/C++编译器,带有或不带’-O’选项都可以用’-g’选项,因此可以让GDB调试优化过的代码。我们推荐你在编译程序时总
是用’-g’。也许你认为你的程序是正确的,但决不要去碰运气。
  请记住在你调试一个用’-g -o’编译的程序时,优化器已经重排了你的代码;调试器显示的是真正编译成的代码。在执行路径和你
的源代码不一致时,不要太惊讶!一个极端例子:如果定义了一个变量,但从来也没用过,GDB发现不了它—-因为优化器已经把
它优化掉了。
  用’-g -o’和只用’-g’编译的程序有时候不大一样,特别是在有些具有指令调度的机器上。如有疑问,再用只带’-g’编译,如果这个版
本修正了问题,请给我们发送一个报告(包含一个测试用例)。更多调试优化过的代码,参见8.2节[变量],76页。
  较早前的GNU C编译器允许一个’-gg’变体选项来产生调试信息。GDB不再支持这个格式;如果你的GNU C编译器有这个选项,别
再用了。
  GDB知道预编译宏,可以显示宏的展开式(参见第九章[宏],101页)。由于’-g’选项产生的调试信息过多,大多数编译器不会产生
与编译宏的信息。GCC3.1版本以及此后的可以提供宏信息,如果在编译的时候指定了’-gdwarf-2’和’-g3’选项;前一个选项产生Dwarf
2格式的调试信息,后者产生”额外信息”。我们希望在将来能够找到一个精简的方式来表示宏信息,这样只要一个’-g’选项就可以了

4.2 开始程序
run
r    在GDB里用run命令开始你的程序。你在启动GDB时指定要调试的程序名(VxWorks例外)(参见第二章[进入和离开GDB]),
    或者用file或者exec-file命令(参见15.1节[指定文件的命令],155页)。
  如果你是在一个支持多进程的环境里运行GDB的话,run命令创建一个子进程来运行你的程序。在某系不支持多进程的环境下,
run跳到被调试程序的开头。其他的目标,比如’remote’,总是在运行的。如果你的到一个类似如下的错误信息:
    The “remote” target does not support “run”.
    Try “help target” or “continue”.
接着用’continue’继续运行你的程序。你可能需要先load(参见[加载],169页)。
  程序的执行总会受某些从它的上级那里得到的信息的影响。GDB可以指定那些虚啊哟你在启动程序之前就要设置的信息(你
可以在启动程序之后改变,但是只有在下一次运行的时候才起效)。这些信息可以划分为4类:
The arguments.
    将你的程序的参数作为run命令的参数。如果在你的环境里有shell,shell可以用来传递参数,那样的话你就可以用普通的方式
(比如wildcard展开和变量替换)来描述参数了。在Unix系统里,你可以用环境变量SHELL来控制使用那个shell。参见4.3节[程序
参数],27页。
The environment.
    你的程序会从GDB里继承环境变量,而你也可以用GDB命令set environment和unset environment改变某些影响你的程序的
    环境变量。参见4.4节[程序的环境],28页。
The working directory.
    你的程序从GDB里继承工作目录。你可以用GDB命令cd来改变工作目录。参见4.5节[程序的工作目录],29页。
The standard input and output.
    你的程序使用和GDB一样的标准输入输出。你可以在run命令行里重定向输入和输出,或者你可以用tty命令来为你的程序
    设置不同的设备。参见4.6节[程序的输入输出],29页。
    警告:输入输出重定向起效之后,你不能再用管道将程序的产生的输出传递到另外一个程序里去;如果你要这样试的话,
    GDB 很有可能结束调试这个错误的程序。
    在你执行run命令后,你的程序马上就开始执行。参见第五章[停止和继续],39页,那里讨论了如何筹划中断你的程序。一
   旦你的程序中断下来,你就可以在你的程序里调用函数,用print或者call命令。参见第八章[检验数据],75页。
    如果在最近一次GDB读入符号表之后符号文件的修改时间发生了改变,GDB会丢弃现有的符号表然后重新读入。重新读入
符号表的时候,GDB会试图保留你当前的断点设置。
start    不同的语言可能有不同的主函数的名称。对于C/C++来说,主函数的名称一直都是”main”,而有些语言例如Ada就不需
    要为他们的主函数指定一个特定的名称。依赖于编程语言,调试器可以方便的开始执行程序并且在主函数的入口点中
    断。
    ‘start’命令的功能和在主函数入口点里设置一个临时断点后执行’run’命令相当。有些程序包含一个在主程序之前执
    行的加工期,加工期会执行一些启动代码。这依赖于你使用哪种语言写程序。例如,C++,构造函数会在main之前为静
    态和全局变量调用。因此调试器有可能在主函数之前就中断程序。而临时断点还会保留来中断程序的执行。
    ‘start’的参数将会传递给你的程序。这些参数会以字符形式给’run’命令。注意要是下次不带参数调用’start’或
    ‘run’的时候,这些参数将会被重用。
    有些时候必须在加工期调试程序。这样的话,start命令在主函数入口点的中断就太迟了,唯一此时已经过了加工期
    。在这种情况下,在运行情在加工期代码上设置断点就可以了。

4.3 程序参数
  参数可以藉由run命令的参数来指定。参数由shell传递给你的程序,shell会扩展通配符和执行重定向。你的SHELL环境变量
(如果有的话)会决定GDB使用哪种shell.如果你没有定义SHELL的话,GDB使用默认的shell(Unix下’bin/sh’)。
  在非Unix系统里,程序通常都是由GDB直接调用的,GDB会用相应的

系统调用来模拟I/O重定向,通配符的宽展由程序的加
工期代码完成,不是由shell来做的。
  不带参数的run命令使用前一次的run命令的参数,或者由set args命令设置的参数。
set args
    为你的下一次执行程序设置参数。如果set args不带参数的话,run就不带参数的执行程序。一旦你带参数的run程序
    的话,要想下一次不带参数的执行程序就只有用不带参数的set args命令了。
show args
    显示在启动的时候传递给程序的参数。

4.4 程序的环境
  环境由一系列的环境变量和环境变量的值的组合组成。环境变量可以很方便地记录一些东西,例如你的用户名,
home目录,终端类型,程序执行的搜索路径等等。通常通过shell来设置环境变量,这些变量就可以被别的程序继
承了。这在调试的时候很有用,GDB就不必重新启动来试验一个改变了的环境。
path directory
    在PATH环境变量的前头加上diretory(可执行程序的搜索路径)。GDB用的PATH将不会改变。你可以指定多个路径名
    称,用空格符或者系统依赖(Unix':’,MS_DOS、MS-Windows';’)的分隔符类分割如果directory已经在PATH里了,
    这个directory会移到PATH的前面以加快搜索。
    在GDB搜索路径的时候,你可以用’$cwd’来代指当前工作目录。如果你用’.’代替,它代表在你执行path命令时的目
    录。GDB在把路径加入PATH前用当前路径替代’.’.
show paths
    显示可执行程序的搜索路径列表(PATH环境变量)。
show environment [varname]
    打印名为varname的环境变量的值。如果你没提供varname,打印所有的环境变量的名称和值。你可以缩写
    environment为env.
set environment varname [=value]
    设置环境变量varname的为value.这个值只为你的程序改变,GDB本身不改变。value可以使任意字符串;环境变量
    都是字符串,你的程序负责转译;如果被删除了,变量的值就被设置为null值。例如,命令:
        set env USER = foo
    告诉被调试的程序,下一次执行run的时候,它的用户是’foo’.(‘=’附近的空格是为了清楚些;空格不是必须的)
unset environment varname
    删除传递给程序的环境变量。和‘set env varname =’不同,unset environment是从环境变量里删除变量,而不是
    为它设置一个空值。
  警告:在Unix系统,GDB用shell执行程序,shell由SHELL决定(如果有的话,否则用’/bin/sh’)。如果SHELL环境变
量指定的shell有初始化文件的话–例如C-shell的’.cshrc’,BASH的’.bashrc’—这些文件里定义的变量都将影响你的程序。
你也可以将那些只在登录时用到的变量移到别的文件里,例如’.login’或者’.profile’。

4.5 程序的工作目录
  每次你用run启动程序时,程序都从GDB的当前工作目录继承工作目录。GDB从它的父进程(通常是shell)那里继承工作
目录,而你可以用cd在GDB里指定一个新的工作目录。
  GDB的工作目录是GDB操作文件时的默认目录。参加按15.1节[文件命令],155页。
cd directory
    指定GDB的工作目录为directory
pwd    打印GDB的工作目录
  通常很难找到被调试程序的当前工作路径(因为它可以在运行的时候改变)。如果你是在支持’/proc’文件系统的系统下
运行GDB的,你可以用info proc命令来查找当前被调试程序的工作目录。(参见18.1.3节,[SVR4进程信息],183页)

4.6 程序的输入输出
  缺省情况下,GDB里运行的程序在与GDB相同的终端上输入输出。GDB会在和你交互的时候切换到它自己的终端模式,
不过它会记住你的程序的终端模式然后在继续运行程序切换到那个模式上。
info terminal
    显示GDB记录的你的程序使用的终端模式。
  你可以用run命令重定向程序的输入/输出。例如:
    run > outfile
  开始运行程序,将打印输出到文件’outfile’。
  另外一个指定程序输入输出的命令是tty命令。这个命令接受一个文件名作为参数,然后将这个文件作为接下来的
run命令的缺省值。它也可以为子进程重置控制终端。例如:
    tty /dev/ttyb
将接下来run命令运行的进程的输入输出定向到’/dev/ttyb’,并将此作为控制终端。
run命令将改变tty命令对于输入输出的设备的设置,但不改变其控制终端。
用tty命令或者在run命令里重定向输入只会影响你调试的程序。GDB的输入仍然来自于你的终端。tty是set inferior-tty的别名。
  你可以用show inferior-tty命令来趟GDB显示程序将要使用终端名。
set inferior-tty /dev/ttyb
    将被调试程序的tty设备设置为/dev/ttyb
show inferior-tty
    显示被调试程序目前的tty设备名

4.7 调试一个已经在运行的进程
attach process-id
    这个命令attach到一个从GDB外启动的进程上。(info files显示你当前活跃的目标)这个命令需要一个进
    程id作为参数。通常用ps工具来找到一个Unix进程的ID,或者用’jobs -l’shell命令。
    在你执行attach命令之后,按下回车键attach将不会再次执行。
  只有在支持进程的环境下,attach命令才有效;例如,attach在没有操作系统的裸机上市无效的。你必须有发给
进程送信号的权限。
  在你执行attach命令的时候,调试器首先在当前工作目录下查找进程的可执行程序,如果没有找到,接着会用源
代码文件搜索路径(参见7.5节[指定源代码目录],70页)。你也可以用file命令来加载可执行文件。参见15.1节[
指定文件的命令],155页。
  GDB在准备好要调试的进程后第一件事就是中断这个进程。可以在run启动的进程上的使用的命令也可以用在你
attach的进程上,你可以检查,修改这个进程。你可以插入一个断点;你可以step和continue;你可以修改存储器。
如果你希望进程继续执行,你可以在attach之后用continue命令来继续。
detach
    在完成了调试之后,可以用detach来释放GDB对进程的控制。detach进程后,进程继续执行。detach命令之
    后,进程和GDB就没有关系了,你还可以attach到另外一个进程或者用run启动一个程序。detach执行之后
    ,按下回车键不会再重复。
  如果你attach过一个进程,退出GDB会detach这个进程。如果你是用run命令启动的话,你将kill这个进程。缺省
的,GDB会要求得到你的确认;你可以用set confirm命令来控制是否需要确认(参见19.7节[可选的警告和消息],213页)。

4.8 杀死子进程
kill    杀死在GDB里运行的子进程
  在你希望调试一个core dump而不是进程的时候,这个命令很有用。在程序运行期间的时候,GDB会忽略core dump

  在某系操作系统,如果你在GDB里为这个程序设置了断点,这个程序就不能在GDB外运行了。你可以用kill命令来<br
/>让程序在GDB外运行。
  在你运行程序的时候,kill命令也有助于重编和重新连接程序,而有些系统是不可能做到这个的。在这种情况下
,在下次执行run命令的时候,GDB可以知道程序已经发生变化了,就会重新读取符号表(同时也会保留你目前的
断点设置)。

4.9 调试多线程进程
  在某些操作系统里,例如HP-UX和Solaris,一个程序可能有多个线程。线程精确概念随着各个操作系统而不一样
,但大体上,一个有多个线程的进程和多进程相似,除了多线程共享一个地址空间(就是说,他们可以检查和修改
同一个变量)。另一方面,每个线程有它自己的寄存器和执行栈,也可能有自己私有的存储空间。
  GDB提供了多个调试多线程的工具:
新线程的自动通知
‘thread threadno’,切换线程
‘info threads’,查询线程
‘thread apply [threadno] [all] args’,对线程列表执行命令
线程特定断点
‘set print thread-events’,控制线程开始和结束时打印消息
  警告:在各个支持线程的操作系统里,不是所有的GDB配置都支持这些工具的。如果你的GDB不支持线程,这个命
令就无效。例如,不支持线程的系统里’info threads’命令就不能输出信息,也会拒绝thread命令,如下:
(gdb) info threads
(gdb) thread 1
Thread ID 1 not known. Use the “info threads” command to
see the IDs of currently known threads.
  GDB线程调试工具可以观察进程的所有线程,而一旦GDB控制线程的话,这个线程就总是调试的焦点了。这个线程
称为当前线程。调试命令从当前线程的角度来显示进程的信息。
一旦GDB察觉到进程的新线程,GDB就会用‘[New systag]’的方式显示目标系统的标识。systag是线程的标识,各
个系统不一样。例如,当GDB发现一个新线程的时候,在GNU/Linux你可能看到
    [New Thread 46912507313328 (LWP 25582)]。
而在SGI系统里,systag就简单的形如‘process 368’,没有更多信息。
  出于调试的目的,GDB自己会给线程一个编号–总是一个整数。
info threads
    显示当前进程里的线程的总概要。GDB显示每个线程(以此为序):
    1.GDB分配的线程号
    2.目标系统的线程标识(systag)
    3.线程当前栈的概要
    线程号左边的星号’*’代表此线程是当前线程。例如:
(gdb) info threads
3 process 35 thread 27 0x34e5 in sigpause ()
2 process 35 thread 23 0x34e5 in sigpause ()
* 1 process 35 thread 13 main (argc=1, argv=0x7ffffff8)
at threadtest.c:68
  在HP-UX系统里:
  出于调试目的, GDB为进程里每个线程分配一个线程号(以线程创建顺序分配小整数)。
  无论何时GDB察觉到一个新线程,它会用‘[New systag]’的形式显示GDB自己的线程号和目标系统的线程标志 。
systag是线程标识,各个系统下可能不同。例如,GDB察觉到新线程,在HP-UX,你能看到
    [New thread 2 (system thread 26594)]。
info threads
    显示所有线程的概要。GDB显示每一个线程(以此为序):
    1.GDB分配的线程 号
    2.目标系统的线程标识(systag)
    3.线程当前栈的概要
    线程号左边的星号’*’代表此线程是当前线程。例如:
(gdb) info threads
    * 3 system thread 26607 worker (wptr=0x7b09c318 “@”)
        at quicksort.c:137
    2 system thread 26606 0x7b0030d8 in __ksleep ()
        from /usr/lib/libc.2
    1 system thread 27905 0x7b003498 in _brk ()
        from /usr/lib/libc.2
  在Solaris系统,那你可以用一个Solaris特有的命令来显示更多的信息:
maint info sol-threads
    显示Solaris用户线程的信息。
thread threadno
    将threadno指向的线程设置为当前线程。这个命令的参数threadno是GDB内部的线程号,就是’info threads’
    命令显示第一列。
    GDB会显示你选择的线程的系统标识和它当前栈的概要:
    (gdb) thread 2
    [Switching to process 35 thread 23]
    0x34e5 in sigpause ()
    伴随着'[New…]’消息,’Switching to’之后的文本形式由你的系统线程标识表示方式决定。
thread apply [threadno] [all] command
    thread apply命令可以让你在一个或多个线程上执行名为command命令。用参数threadno指定你希望操作的
    线程数目。可以是单个线程号,’info threads’显示的第一列;或者可以是线程范围,像2-4.要操作所有
    线程,敲入thread apply all command。
set print thread-events
set print thread-events on
set print thread-events off
    GDB察觉到新线程启动或线程结束的时候,set print thread-events命令可以开启或关闭打印信息。缺省
    下,如果目标系统支持的话,这些事件发生的时候,这些信息会打印出来。注意,这些信息不一定在所有目
    标系统里都可以关闭的。
show print thread-events
    显示是否在GDB察觉线程启动或结束时打印信息。
  由断点或者信号决定,无论何时GDB停止程序,它都会选择断点或信号发生的线程。GDB会用‘[Switching to syst
ag]’形式标识线程提示线程上下文的切换。
  更多关于GDB在停止启动多线程程序的行为的信息,参见5.4节[停止核启动多线程程序],59页。
  更多多线程程序观察点的信息,参见5.1.2[设置观察点],44页。

4.10 调试多个程序
  在多数系统下,GDB没有为能用fork调用创建附加进程的程序提供特殊的支持。程序创建子进程时,GDB会继续调试
父进程,而子进程则不受影响。如果你此前在子进程的代码上设置了一个断点,则子进程会被SIGTRAP信号结束。
  不过,如果你想调试子进程的话,有一个不那么麻烦的替代方案。在fork调用之后的子进程代码里调用sleep调
用。如果sleep代码的调用由某些环境变量或者某个文件的存在与否来决定,那就很方便了:如果你不想调试子进
程,不设置这些变量或者删除文件就可以了。在子进程休眠的时候,用ps程序来得到进程id.接着用GDB attach到这
个子进程上,如果你正在调试父进程,你需要新启动一个GDB实例,参见4.7[Attach],30页。你就可以如同attach到别的
进程那样开始调试子进程了。
  在某些系统上,GDB提供调试用fork或vfork调用创建子进程的程序的支持。目前,只有HP-UX(11.x和以后版本)和
GNU/Linux(2.5.60内核版本及后续)提供这个功能的支持。
  缺省的,在创建子进程的时候,GDB会继续调试父进程,而子进程不受影响。
  如果你要调试子进程,用命令
set follow-fork-mode.
set follow-fork-mode mode
    设置调试器对于fork或vfork调用的反应。fork或vfork创建一个子进程。mode参数可以是:
    parent    fork之后调试原进程。子进程不受影
响。这是缺省方式。
    child    fork之后调试新的进程。父进程不受影响。
show follow-fork-mode
    显示当前调试器对于fork/vfork调用的反应。
  在Linux下,如果你要调试父进程和子进程,用命令
set detach-on-fork.
set detach-on-fork mode
     设置GDB在fork之后是否detach进程中的其中一个,或者继续保留控制这两个进程。
    on    子进程(或者父进程,依赖于follow-fork-mode的值)会被detach然后独立运行。这是缺省mode。
    off    两个进程都由GDB控制。一个进程(子进程或者父进程,依赖于follow-fork-mode)被调试,另外
        一个则被挂起。
show detach-on-fork
    显示detach-on-fork mode
如果你选择了设置‘detach-on-fork’为off,那么GDB会保持控制所有被创建的子程序(包括被嵌套创建的)。你
可以用info forks命令来显示在GDB里创建的子进程,然后用fork命令来从一个进程切换到另一个。
info forks
    打印在GDB控制下被创建的子进程列表。这个表包括fork id, 进程id和当前进程的位置(程序计数器)。
fork fork-id
    切换到fork-id指定的进程。参数fork-id是GDB内部为fork分配的,如命令’info forks’所显示列表的第一
    列。
process process-id
    切换到process-id指定的进程。参数process-id必须是’info forks’输出的。
  要想结束调试一个被创建的进程,可以用detach fork命令(允许这个进程独立的运行),或者用删除(也杀死)
的方法delete fork命令。
detach fork fork-id
    detach一个由GDB标识的fork-id指定的进程,然后从fork列表里删除。这个进程会被允许继续独立运行。
delete fork fork-id
    杀死一个由GDB标识的fork-id指定的进程,然后从fork列表里删除。
  如果你要调试一个vfork创建接着exec的进程的话,GDB会在这个新的目标上执行到底一个断点。如果你在原来程序
的主函数上设置了一个断点,子进程上的主函数上也有一个同样的断点。
  如果子进程正在执行vfork调用,你不能调试子进程或者父进程。
  如果你在exec调用执行之后运行GDB的run命令,新目标会重新启动。要重启父进程,运行file命令,父进程可执行程序
名作参数。
  你可以用catch命令来在fork,vfork或者exec调用的时候让GDB中断。

4.11 为跳转设置书签
  在某些操作系统(目前只在GNU/Linux上),GDB可以保存一个程序状态的快照,称为检查点,以后可以跳回。
  跳回到检查点会撤销所有在检查点之后的变化。这些变化包括内存,寄存器,甚至系统状态(有些限制)。这样可以
有效的及时回到在检查点设置的状态。
  因此,如果你单步调试到你认为你接近到快要发生错误的地方,你就可以保存一个检查点。接着,如果你不经意的走的
太远错过了关键的状态,你可以回到检查点后再从那里开始,而不需要从头启动程序。
  检查点对于需要很长时间或者单步调试里bug发生地方很远的情况下很有帮助。
  用checkpoint/restart方法调试:
checkpoint
    保存被调试程序当前执行状态的快照。checkpoint命令不需要参数,但每个检查点都分配一个小整数标识,如同
    breakpoint标识一样。
info checkpoints
    列出在当前被调试会话的检查点。对于每个检查点,信息显示如下:
    Checkpoint ID
    Process ID
    Code Address
    Source line, or label
restart checkpoint-id
    在检查点号的状态上重新启动。所有程序变量,寄存器,栈帧等等都恢复到检查点上保存的状态。本质上将,gdb会
    把时钟回拨到检查点所记录的时间。
    注意,断点,GDB变量,命令历史等不受检查点重置的影响。通常,检查点只重置被调试程序内部的状态,不影响调试器
    本省的状态。
delete checkpoint checkpoint-id
    删除以前保存的检查点
  回到以前保存的检查点,会重置程序的用户状态,加上相当数量的系统状态,包括文件指针。重置不会撤销已写入文件的数据,但
是会把文件指针指向以前的文职,因此以前写入的数据就可以被覆盖。对于以只读模式打开的文件,指针也会回到以前的位置,因此
可以重新读取数据。
  当然,送到打印机(或者其它外设)的字符不能收回,而从别的设备(比如串口设备)里读取的数据可以从内部程序缓冲里撤销,
但是不能被塞回到串行管道里去,然后再读取他们。相似的是文件的内如如果被改变了,也不能被重置。
  然而,在这些约束条件下,你可以重新回到以前保存的程序状态去,重新调试–然后你可以改变事件的过程来执行一个不同的路径
调试。
  最后,在你回到检查点的时候,有些内部程序状态会不一样—程序的进程id。每个检查点会有一个独立的进程id(pid),每个都和原
来的pid不一样。如果你的程序保存了一个进程id的本地副本,这会有一个潜在的问题。

4.11.1 使用检查点的隐含好处
  在某些系统里(例如GNU/Linux),出于安全考虑,每个新进程的地址空间都要随机确定。这就很难或者说不可能在一个绝对地址上
设置一个断点或者观察点,因为一个符号的绝对位置每次执行都不一样。
  然而,一个检查点是一个进程的相同的副本。因此如果你在主函数的入口点创建了一个检查点,你可以避开地址空间的随机化的影
响,而且符号也会呆在相同的位置。

 

转载   http://zhiwei.li/text/2010/01/gdb%e6%89%8b%e5%86%8c4%e5%9c%a8gdb%e9%87%8c%e8%bf%90%e8%a1%8c%e7%a8%8b%e5%ba%8f/

如有版权问题,请联系QQ   858668791

0 0