gdb
请出山来是一个最为正确的选择 gdb
看起来是全命令行的东西,而且一启动就是一大片英文,实在让没用过的望而却步,其实真正常用的就只有那几个功能。 首先是安装,在( Debian )系的发行版 Linux 上,使用
apt-get install gdb
回车等一会就行,可能有的发行版已经装好了,但有的却没有。
首先不要打开这个 gdb
,因为这里面的东西的确看起来挺复杂的
main.c
在里面写好正确的语句后,对它进行编译,链接,也就是调用 GCC
,这里使用 GCC
的命令是
gcc -o main -g main.c
这个 -g
标志非常必要,意味着生成一些 符号信息 ,用人话说就是 gdb
所需要的东西,没有这个东西是无法使用 gdb
进行调试的。
紧接着,在当前目录下就有一个名为 main
的可执行文件,看好当前用户是否有权限执行它之后,使用如下命令开启调试旅程:
gdb ./main
紧接着,出现一大堆英文,不要慌,静静的等待它们装逼,最后会出现一个
(gdb)
光标在闪烁的时候,就证明 gdb
加载这个程序的符号信息完毕,你可以准备执行了
这时候,你只要输入一个字母 r
, 就能让这个 main
程序执行起来
(gdb) r
如果这个程序没有问题的话,几乎就和在外面正常运行时一样, gdb
没有起到任何作用,但是一旦有任何问题的话, gdb
就会暂停在那个出错的地方,而不是继续执行。
Linux
中并不少见,这对于某些特殊用途的程序而言,看到接收到的信号十分重要,例如网络程序。 对于 r
这个命令除了这么孤零零的使用以外,还可以对它进行传递参数,例如你的 main
程序需要两个参数,你可以这样
(gdb) r 192.168.141.149 8889
这就和在外面使用:
./main 192.168.141.149 8889
的效果一样
第二个命令自然是,最常用的设置断点,这也是十分简单, 命令就一个字母 b
最简单的用法就是直接接着一个数字,代表着要设置断点的地点( 行 ),例如
(gdb) b 10
表示在当前的执行文件中的第十行设置一个断点,设置完了会有成功信息:
Breakpoint 1 at 0x400556: file main.c, line 10.
意思就是,设置了第一个断点,在内存位置 0x400556
地方,人类能懂得地方在 main.c 文件的第十行
另一种情况就是,当你的程序是多个文件编译而成的时候,你如果不指定文件名,那么 默认就是在当前执行的这个地方所属的文件里设定
(gdb) b main.c:10
这就是完整的语法,十分简单,这就是设置断点最常用的方式
gdb
帮我们及时停了下来,我们可以借助三个常用命令(后面写到)来帮助我们知道,自己现在身处何处,再设置断点 上面说到的 三个常用命令 之一,二 where
, bt
并且这里说的函数栈并不只限于 程序员编写的函数 ,还包括所调用的系统函数。
(gdb) where
直接在光标处写就行,写完回车就能够看见函数栈,但是如果你的函数嵌套过深这也不是一个好办法,但聊胜于无
(gdb) bt
与上方一致。输出信息大概是这样(用我调试网络程序时收到一个SIGPIPE的情况为例)
#0 0x00007ffff727cbc0 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:81 #1 0x0000000000401ea6 in write_n (handle=5, out_buf=0x7fffffff1a10 "Http/1.0 200 OK/r/nServer: Wu shxin HTTP Server/r/nContent-length: 276/r/nContent-type: text/html/r/n/r/n", buf_len=94) at /root/ClionProjects/httpd2/iofunc.cpp:68 #2 0x0000000000402657 in serve_static (client_fd=5, filename=0x7fffffff9a80 "./home.html", file_size=276) at /root/ClionProjects/httpd2/deal_client/server_static.cpp:37 #3 0x00000000004021b4 in deal_client (client_fd=5) at /root/ClionProjects/httpd2/deal_client/dealclient.cpp:77 #4 0x0000000000401cac in deal_server (listen_fd=3, listen_fd_type=2) at /root/ClionProjects/httpd2/server.cpp:154 #5 0x00000000004017c4 in main (argc=3, argv=0x7fffffffe1b8) at /root/ClionProjects/httpd2/main.cpp:31
稍微有点长,但是还是很清楚的,每个 #
后面是一个栈, 括号中是参数名和参数真实值。
三个常用的最后一个就是 list
在你运行到某处,被 gdb
停下来的时候,除了使用 where
或者 bt
,总体浏览一下函数栈,还能具体的查看一下现在执行到哪行代码了
(gdb) list
当然这个可能对系统函数没什么作用,因为它可能会显示:
76 in ../sysdeps/unix/syscall-template.S
这个时候你就需要去它的上一层中查看错误了,但正常情况下, list
会将你所执行位置语句的上下几行一起显示出来。
回到最开始的 r
命令,现在可以知道它是让程序开始执行的命令,那么和他有相似效果,但是用处不同的另外两个命令分别是 c
和 n
c
代表着 continue
,也就是忽略这个断点或者错误继续执行的意思(比如收到某个信号) n
代表着 next
, 也就是执行到下一条语句,然后暂停。
最后就是怎么退出 gdb
(gdb) q
就行了。
r <para1> ...
是运行程序 c
是继续运行程序 n
是执行下一条语句 where
和 bt
是显示函数栈 list
是显示当前运行语句的上下文( 此上下文非操作系统概念中的上下文 ) q
是退出 gdb
调试 以上是逻辑清晰的 单线程/单进程 程序的调试,其实多进程/多线程的调试也是差不多的类型
有个命令首先要说: attach
gdb
中),启动后,在 shell
中用 ps
命令查看该进程延伸出的子进程,并且记下 子进程的进程号 启动 gdb
,并且使用 attach
,假设要调试的进程号是 4488
$ gdb ... (gdb) attach 4488 # ... 加载符号信息 # 加载完毕后,会停在有光标的状态,这时候你可以设置断点,或者选择继续 (gdb) ...
当然,如果一开始只有一个进程,但是在你调试的某个过程中会有 fork
操作,这时候可以使用另一个命令来明确说明要继续跟踪父进程还是调试子进程
(gdb) set follow-fork-mode child
这个命令有点长,不太好记,可以抄写在某个地方,用到的时候再看(我就是如此)
与之对应的自然就是 set follow-fork-mode parent
多线程,也很类似
info threads
和 thread <ID>
info threads
是显示当前程序的所有线程信息,并且会给每个线程配上一个相应的 ID
,也就是第一列显示的 ID
,而不是后面显示的。 ID
可以在调试线程之间进行切换 切换的命令则是 thread
(gdb) info threads Id Target Id Frame * 1 process 19515 "main" main (argc=1, argv=0x7fffffffe1f8) at main.c:12 ...
当前线程前方会有一个 *
显示。
(gdb) thread 2
表示切换到 2 号线程调试
最后,关于多线程调试有一个高级一点的命令,就是如果我们在调试的时候,不希望其它线程运行而干扰到本线程的调试,可以使用
(gdb) set scheduler-locking on
这样一来就只有所调试的线程才会执行,对于这个命令的其他两个参数 off
和 step
分别是 关闭这个功能 和 单步执行( n
)情况下才不允许其他线程运行 的效果。
gdb
常用的操作,记住了他们也就能满足绝大部分的调试工作了,比带图形界面的调试器更有效率。