分享

Debugging with GDB学习记录(二)

 娱天乐 2014-06-30
在GDB下运行程序
需要在GDB下运行程序时,必须首先在编译时产生调试信息


为调试而做的编译(-g)
如果需要使用GDB调试程序,则需要调试信息。调试信息被写入对象文件,调试信息描述了每个变量和函数的类型以及源代码行号和可执行代码中的地址的相关性。

为产生编译信息,使用'-g'选项

递交给客户的程序需要使用'-O'优化编译,但有的编译器无法同时处理'-g'和'-O',GCC支持同时使用'-g'和'-O',建议使用'-g'编译代码。

GDB知道预处理宏并可以展开显示,但是单独使用'-g',大多数编译器不会在调试信息中包含预处理宏的信息。如果GCC指定'-gdwarf-2'和'-g3'
就会提供宏信息,前者要求以Dwarf 2格式产生调试信息,后者'extra information'。


启动程序
run或r
可以用run运行程序或使用file加载,或命令行中使用gdb -e指定
如果在支持进程的可执行环境下运行,则run创建一个子进程执行被调试的程序;不支持进程的环境下,run跳到程序开始处执行。
其他情况下,例如remote,如果显示错误信息:
The "remote" target does not support "run".
Try "help target" or "continue".
则使用continue执行程序,必须先load程序。


程序的执行受到上层给的信息的影响,可以在启动程序后修改这些信息,但修改后的值仅对下一次启动程序时有影响。
1、参数:指定参数给你的程序
2、环境:程序继承GDB的环境,但可以使用GDB命令set environment和unset environment来改变环境的部分设置来影响你的程序
3、工作目录:你的程序继承GDB的工作目录,可以在GDB命令行中使用cd命令来改变GDB工作目录
4、标准输入/输出:一般情况下与GDB使用的相同。可以在run命令行重定向输入输出或者使用tty命令为你的程序设置另外的设备
                 注意:当输入/输出重定向工作时,就不能将正在调试的程序输出通过pipes输出给其他的程序。如果你试图这么做会,
                 GDB可能会调式错误程序。
自从上次GDB读取符号后,一旦符号文件已经改变,GDB就会重新读取这个符号文件,并保留原来的断点。


1、start  
功能:在主过程main之前设置一个临时断点,然后停止,等待激活run命令,有些程序在主过程之前会执行一些语句。
     可以在start后指定程序的参数,但要注意:如果在后续的start或run调用时没有指定参数,则会重用上一相同参数。
     但有的时候需要调试main之前的这些代码,这时候使用start不太合适,需要在这些代码中提前设置断点
2、set exec-wrapper wrapper
   show exec-wrapper
   unset exec-wrapper
   当'exec-wrapper'设置后,指定的wrapper用于加载被调试的程序。GDB使用shell命令exec wrapper program来启动你的程序。引号被
   加到程序和其参数上,wrapper不会带引号对。以下语句使用env来将一个环境变量传递给被调试的程序。
   (gdb) set exec-wrapper env ’LD_PRELOAD=libtest.so’
   (gdb) run
3、set disable-randomization
   set disable-randomization on
   该选项在GDB中默认使能。该选项会关闭启动的程序虚拟地址空间的native randomization。该选项在multiple debugging sessions中比较有用,可以更好的
   再现和在调试sessions之间重用内存地址。(gdb) set exec-wrapper setarch ‘uname -m‘ -R具有同样行为。
4、set disable-randomization off
   有些bug仅在程序被加载到特定地址时才会出现,当在GDB下运行程序时bug消失了,那么有可能是禁用了平台地址随机化,使用该命令改变之即可。
   虚拟地址空间随机化仅在GNU/Linux实现了,用户保护程序,使黑客无法通过在特定地址插入跳转指令。
   Prelinking shared 库启动时性能较好,但是却使得这些库在代码中的地址是可以预测的,不太安全,而没有prelinked的共享库是随机选择地址的。
   Position independent executables(PIE)包含位置无关代码,这些代码加载道内存后也是随机的地址,可以gcc -fPIE -pie。PIE也经常把已经prelinked
   共享库放在一个随机地址。


   只要随机化使能,Heap、Stach和用户mmap区域经常随机存放。
5、show disable-randomization
   显示当前native randomization情况




给你的程序传递参数:
指定给被调试的程序的参数可以通过run命令的参数指定,这些参数会被转发给被调试程序。
如果run命令无参数运行,则其实际上使用的是上一个run命令的参数或者使用set args命令来指定的参数。
1、set args   
为下次程序运行指定程序参数,如果set args无参数,则run执行的被调试程序也无参数。一旦以参数运行程序,如果想无参数运行,则只有一个
方法,使用set args清空参数
2、show args
显示指定给被调试程序的参数。




程序运行环境
一般情况下是在shell里面设置环境变量,而在该shell中启动的程序都会继承这些环境变量,环境变量一般包括:
1、path directory
将directory加在PATH环境变量的开头
2、show paths
(gdb) show paths
Executable and object file path: /root/directory:/usr/java/jre1.6.0_12/bin:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
显示可执行文件的搜索路径(PATH环境变量)
3、show environment [varname]
   (gdb) show environment PATH
    PATH = /root/directory:/usr/java/jre1.6.0_12/bin:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
如果没有提供[varname],则会打印所有的环境变量
4、set environment varname [=value]
改变环境变量varname的值为value,注意:这个值的改变仅对被调试的程序有用,而不是针对GDB的。
5、unset environment varname
从环境变量中删除varname环境变量,不再传递给被调试的程序,注意:这是从环境中删除变量,而不是'set env varname='来赋空值。


注意:GDB使用shell运行你的程序,所以根据使用的Shell,其配置,例如:.bashrc会影响到程序


程序工作目录
程序的当前工作目录继承自GDB,GDB继承自运行它的shell
1、pwd
(gdb) pwd
Working directory /.
打印GDB当前工作目录
2、cd directory
设置GDB当前工作目录为directory
很难查看当前进程的工作目录,因为程序执行过程中可能会改变当前工作目录。如果某个系统GDB被配置为支持/proc,那么可以用info proc命令来查看被调试
程序的当前工作目录


程序输入输出
默认情况下,调试的程序使用GDB的输入输出。GDB将自己的终端模式与程序的对接来通信,而且GDB记录了被调试的程序使用的terminal模式,并在继续运行程序时,转到该终端模式下
1、info terminal
显示GDB记录的被调试程序使用的终端模式信息,可以使用重定向输入输出:run > outfile
2、tty /dev/ttyb
另外一个重定向输入输出的方式是使用tty命令,该命令接受一个文件名作为参数,并且将该文件作为将来run命令的输出,同时也为将来的run命令重置了子进程的控制终端。
使用run >方式可以覆盖tty命令在输入输出设备上的作用,但无法覆盖子进程的控制终端。


当使用tty或run重定向时,仅影响被调试程序的输入,GDB的输入依然来自终端,tty是set inferior-tty的别称


使用show inferior-tty来告知GDB显示程序即将运行使用的终端


调试已经运行的进程
attach process-id
该命令attach到一个正在运行的进程,该进程在GDB之外启动。当按回车键希望重复执行上一个attach,就会发现attach不支持这种方式。
如果希望使用attach,则环境必须支持进程,并且你必须有权限发送给该进程一个信号。

使用attach时,调试器会在当前工作目录--->源文件搜索路径去查找源代码文件,也可以用file命令加载程序。
GDB attach到指定进程后做的第一件事情就是stop it,然后可以用GDB命令来检查和修改被attached的进程:run、step、continue等等。



detach
调试完指定进程后,可以运行detach命令来让GDB释放该进程,该进程得以继续运行。当回车时,detach不会重复。当执行完detach后,进程和GDB不再相关,GDB可以attach其他进程。

当有一个attached进程时,离开GDB,会detach该进程。如果正在run该程序,则会kill掉该进程。一般情况下,上述情况GDB会请求你确认,你可以用
set confirm命令来决定是否需要给出确定提示。


杀死子进程
kill
杀死GDB产生的用于运行程序的子进程
这个在调试core dump时有用,GDB在你的程序运行时会忽略掉所有的core dump文件。


在一些OS上,如果你在GDB调试它时设置了断点,则该程序不会运行,可以用kill命令允许其在GDB之外运行。


kill命令在希望重新编译链接程序使也比较有用,在大多数系统上,当在进程下运行程序时不允许修改可执行文件,在这种情况下,当你kill掉
进程,然后修改,然后run时,GDB会发现程序改变,然后重新读取符号表(会试图保留之前设置的断点)。


调试多个子程序
GDB允许在一个单独的session里面调试多个程序。有些系统允许GDB同时运行多个程序,更一般的情况,在每一个进程中有多个线程执行。
GDB用inferior来表示每个程序执行,inferior与进程对应,也可用于没有进程的target。Inferiors在进程运行之前创建,在进程退出之后
保留。Inferior也有自己的标记,这个标记与PID不同。


通常,每个inferior都有自己的不同地址空间,一些嵌入式targets可以在一个地址空间的不同部分运行不同的inferior。每个inferior内可以有
多个线程运行。


1、info inferiors
打印GDB当前管理的inferiors列表,对每个inferior,GDB以如下顺序打印信息:
1. the inferior number assigned by gdb
2. the target system's inferior identifier
3. the name of the executable the inferior is running.
而数字前的*表示当前inferior
(gdb) info inferiors
Num Description Executable
  2 process 2307 hello
* 1 process 3401 goodbye


2、inferior infno
使inferior号infno成为当前inferior,这个inferior号就是上面由GDB赋给的那个(1).


3、add-inferior [-copies n] [-exec executable]
在同一个session里增加n个inferiors来运行程序,使用executable来作为可执行文件,n默认值为1。如果没有指定executable,则inferior是
空的。可以用file executable来分配给inferior可执行文件。


4、clone-inferior [-copies n] [infno]
增加n个inferiors来执行inferior号为infno的可执行程序,n默认为1,infno默认为当前inferior。


在一些系统上,GDB会在执行fork或exec调用时自动将inferiors加进debug session。


5、remove-inferiors infno ...
删除inferior。不过不会remove掉执行该命令的inferior,如果想,可使用kill或detach。
如果想离开当前inferior,可以用detach inferior(允许其独立运行)或使用kill inferiors命令
detach inferior infno...
Detach掉inferior或infno指定的inferiors,注意这些inferiors仍会显示在info inferiors条目里面,但是描述会显示'<null>'。


kill inferiors infno...
杀死inferior或infno指定的inferiors,注意这些inferiors仍会显示在info inferiors条目里面,但是描述会显示'<null>'。


在detach、 detach inferiors、kill或kill inferiors或进程执行完后,该inferior仍然可用,显示在info inferiors条目里面


6、set print inferior-events
   set print inferior-events on
   set print inferior-events off
在GDB发现新的inferiors启动或inferiors退出或被detach后,是否打印消息。默认不打印。


7、show print inferior-events
   当GDB发现inferiors启动、退出或被detached后,是否打印信息。
8、maint info program-spaces
打印GDB当前管理的所有程序空间列表,按如下顺序打印每个程序空间:
1. the program space number assigned by gdb
2. the name of the executable loaded into the program space, with e.g., the
file command.


当前程序空间前面加*
(gdb) maint info program-spaces
Id Executable
  2 goodbye
    Bound inferiors: ID 1 (process 21561)
* 1 hello
这里,没有inferior运行hello程序,进程21561运行程序goodbye.一些平台上,可能多个inferiors绑定到一个程序空间,更加常见的例子是:
调试vfork产生的父子进程:
(gdb) maint info program-spaces
Id Executable
* 1 vfork-test
  Bound inferiors: ID 2 (process 18050), ID 1 (process 18045)


这里inferior 2 和inferior 1都运行在同一程序空间,因为inferior 1执行了一个vfork调用


调试多线程程序
多线程和多进程最大的不同在于:多线程共享同一个地址空间。此外,每个线程有自己的寄存器、stack,以及可能有私有内存。
这里提供的命令在支持线程的系统上才有用。


GDB允许监视所有进程,但GDB控制时,仅能控制一个,即当前线程。GDB能自动发现新的线程,并以[New systag]格式给出提示,例如:GNU/Linux下的显示:
[New Thread 0x41e02940 (LWP 25582)]


为了调试目的,GDB会分配属于自己定义的线程号。
1、info threads [id...]
显示系统中所有线程,或由id列表指定的线程信息,id之间使用空格隔开:
(gdb) info threads 
  2 Thread 0x4226f940 (LWP 30603)  0x0000003dd80cced2 in select () from /lib64/libc.so.6
* 1 Thread 0x2b66a424c900 (LWP 30601)  0x0000003dd80d4018 in epoll_wait () from /lib64/libc.so.6


GDB为每个线程显示:
 1. the thread number assigned by gdb
 2. the target system's thread identifier (systag)
 3. the thread's name, if one is known. A thread can either be named by theuser (see thread name, below), or, in some cases, by the program itself.
 4. the current stack frame summary for that thread
*表示当前线程:
(gdb) info threads
  Id Target Id Frame
  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
2、maint info sol-threads
Solaris专用,显示其用户线程信息


3、thread threadno
使线程号为threadno的线程成为当前线程。这里的线程号是GDB线程号,即info threads中的第一列的值。GDB会显示线程系统标记以及其当前
栈帧summary:
(gdb) thread 2
[Switching to thread 2 (Thread 0xb7fdab70 (LWP 12747))]
#0 some_function (ignore=0x0) at example.c:8
8 printf ("hello\n");
GDB变量$_thread包含了当前线程的线程号。


4、thread apply [threadno | all] command
将命令command应用到1个或多个线程。threadno代表线程号,是info threads的第一列显示的内容,可以是单个线程,也可以是一个线程范围,如2-4.
thread apply all代表将命令应用到所有线程。


5、thread name [name]
给当前线程分配名字,如果没指定[name],则现有的用户指定的名字被删除。线程名出现在info threads显示里面
在一些系统,比如GNU/Linux,GDB能够决定OS给定的线程名,使用thread name给定的名字可以覆盖系统给定的名字,删除用户指定名字会重新显示系统指定名。


6、thread find [regexp]
寻找名字或标记符和正则表达式regexp的线程,这个命令同样允许使用t线程target systag来标记自身,例如GNU/Linux就是用LWP id。
(gdb) thread find 26688
Thread 4 has target id ’Thread 0x41e02940 (LWP 26688)’
(gdb) info thread 4
Id Target Id Frame
4 Thread 0x41e02940 (LWP 26688) 0x00000031ca6cd372 in select ()


7、set print thread-events
   set print thread-events on
   set print thread-events off
当GDB发现新的线程启动或退出时,使能或禁止打印信息。默认打印,但并不是所有平台都支持禁止打印该信息


8、show print thread-events
显示:规则制定为是打印还是禁止打印信息,当GDB发现新的线程启动或退出时


9、set libthread-db-search-path [path]
如果设置了该变量,则path就是以冒号:分割的路径列表,GDB用于从中搜索libthread_db,如果没有设置,则该变量为空。
在GNU/Linux和Solaris系统中,GDB使用libthread_lib库来获取在inferior进程中的线程信息。首先从该路径读取,如果没找到,则从默认路径中
查找,最终会将libpthread加载进inferior process。


对上述路径查找到的libthread_db,GDB试图在当前inferior进程中对其初始化,如果初始化失败,则GDB会卸载这个目录下找到的libthread_db,进入
下一个目录查找,直到成功。如果都不行,则GDB会发出警告,并禁止线程调试。


仅部分平台支持。


10、show libthread-db-search-path
显示当前libthread_db搜索路径


11、set debug libthread-db
    show debug libthread-db
显示或不显示libthread_db相关的事件,1=enable,0=disable。




调试Forks
在大多数系统上,GDB对使用fork产生子进程的程序调试没有特殊的支持。当一个程序fork时,GDB仍继续调试父进程,子进程的运行没有任何影响。
如果在子进程将要执行的代码上设置了断点,则子进程会收到SIGTRAP信号并停止(除非它能够catch并处理该信号)。


如果想要调试子进程,则可以在fork子进程后,在子进程要执行的代码里调用sleep函数(可以在某全局变量被设置或某个文件存在时睡眠),然后获取
该子进程的PID,让GDB使用attach来调试子进程。


目前只有HP-UX(11.x~)和GNU/Linux(2.5.60~)支持GDB调试使用fork或vfork产生子进程的程序,但默认情况下,当一个程序fork时,GDB仍继续调试父进程,
子进程的运行没有任何影响。可以用set follow-fork-mode来跟踪子进程而非父进程。


1、set follow-fork-mode mode
在fork或vfork产生子进程时,使用如下mode决定跟踪谁:
parent :默认情况,当一个程序fork时,GDB仍继续调试原进程,子进程的运行没有任何影响。
child  :GDB调试新进程,父进程不受影响的运行。


2、show follow-fork-mode
显示是跟踪parent还是child


在Linux上,如果你想同时调试parent和child进程,则使用命令set detach-on-fork。


3、set detach-on-fork mode
告诉GDB:在fork后是否detach掉一个进程,还是继续保留调试父子两个进程。
on  : 默认。根据follow-fork-mode,来决定detach掉父进程还是子进程。
off :父子两个进程都在GDB控制之下,根据follow-fork-mode,一个进程被正常调试,一个被GDB held挂起


4、show detach-on-fork
显示3中是on还是off


如果detatch-on-fork模式off,则同时控制所有fork的进程,可以用info inferiors来查看情况,并使用inferior来切换当前被调试的进程。


如果想离开调试的进程,可以用detach inferiors或使用kill inferiors命令


If you ask to debug a child process and a vfork is followed by an exec, gdb executes
the new target up to the first breakpoint in the new target. If you have a breakpoint set on
main in your original program, the breakpoint will also be set on the child process’s main.


在一些系统上,如果使用了vfork产生子进程,则在exec调用结束之前无法调试子进程和父进程。


如果在执行了exec函数之后,对GDB运行了run命令,则目标进程重启。
If you issue a run command to gdb after an exec call executes, the new target restarts.
To restart the parent process, use the file command with the parent executable name
as its argument. By default, after an exec call executes, gdb discards the symbols of the
previous executable image. You can change this behaviour with the set follow-exec-mode
command.


5、set follow-exec-mode mode
使GDB响应程序对exec()调用,所有的exec调用会替换进程的程序映像。mode可以有:
new  GDB创建一个新的inferior,并使进程绑定到该新的inferior。在exec之前的本来进程使用的程序,可以在重启原来的inferior后重启运行。
(gdb) info inferiors
(gdb) info inferior
  Id Description Executable
* 1 <null> prog1
(gdb) run
process 12020 is executing new program: prog2
Program exited normally.
(gdb) info inferiors
  Id Description Executable
* 2 <null> prog2
  1 <null> prog1


same : GDB使进程依然绑定到原来的同一个inferior。在该inferior中新执行映像替代了原来的程序映像。在exec调用后重启inferior,如run,会重启
         在exec调用后进程运行的映像。
(gdb) info inferiors
  Id Description Executable
* 1 <null> prog1
(gdb) run
process 12020 is executing new program: prog2
Program exited normally.
(gdb) info inferiors
  Id Description Executable
* 1 <null> prog2
可以使用catch命令来使GDB停止,在无论fork、vfork还是exec调用后都停止

Setting a Bookmark to Return to Later
在一些系统上(当前只有GNU/Linux),GDB可以设置程序状态快照,即checkpoint,然后在以后可以回到这个点。


返回checkpoint意味着undo所有的检查点之后的事情,包括内存、寄存器的改变,甚至一些系统状态。


在需要很长时间或很多步才能到猜想的bug可能发生的点时,这个非常有用。


1、checkpoint
为调试的当前程序执行状态保存快照。该命令没参数,但会自动分配一个小的数字编号。


2、info checkpoints
列出当前debugging session中设置的checkpoints列表,对每一个checkpoint,会列出下列信息:
Checkpoint ID
Process ID
Code Address
Source line, or label


3、restart checkpoint-id
Restore程序状态到检查点checkpoint-id保存的状态。所有的程序变量、寄存器、栈帧都会恢复到检查点时的值。


但是断点、GDB变量、命令行历史不会改变,因为检查点保存的仅仅是被调试程序的信息,而是GDB调试器的信息。


4、delete checkpoint checkpoint-id
删除checkpoint-id标记的检查点。


返回到某个检查点意味着恢复被调试程序的用户状态,加上OS状态的子集(包括文件指针)。它不会恢复检查点时的
文件数据,但是会将文件指针指回检查点时的位置,这样先前写入的数据就会被覆盖了。读模式打开的文件,文件指针
也会恢复,这样就可以读之前的数据了。


Of course, characters that have been sent to a printer (or other external device) cannot
be “snatched back”, and characters received from eg. a serial device can be removed from
internal program buffers, but they cannot be “pushed back” into the serial pipeline, ready
to be received again. Similarly, the actual contents of files that have been changed cannot
be restored (at this time).
当返回到检查点时,进程id会变化。


使用检查点的好处:由于安全性原因,很多系统,如GNU/Linux采用随机性地址空间,所以需要重启程序时,
很难对一个绝对地址设置断点和watchpoint,因为绝对地址在重启后变了。而checkpoint,是一个进程独一
无二的拷贝,如果创建了一个checkpoint,这样就可以简单的回到检查点而不是重启程序,这样就免去了地址
空间随机性的的影响。


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多