精选文章

Android下使用TCPDUMP抓包Wireshark分析数据 如果想分析Android下某个APP的网络数据交互,需要在Android手机上抓包,最常用的抓包工具非tcpdump莫属,用tcpdump生成Wireshark识别的pcap文件,然后将pcap文件下载到电脑上,用电脑上的Wireshark加载pcap文件,通过Wireshark分析tcpdump抓取的数据。...

继续阅读

Mac下部署Android开发环境附加NDK 作为开发者,我们深有体会,不管是进行什么开发,为了部署开发环境,我们往往需要折腾很长时间、查阅很多资料才能完成,而且这次折腾完了,下次到了另一台新电脑上又得重新来过,整个部署过程记得还好,要是不记得又得重新开始,而且遇到Android这种GFW阻隔了开发资源下载链接的环境部署,又尤其浪费时间。所以这也是我写下这篇教程的初衷跟动力源泉,希望大家参考了这篇教程以后可以轻轻松松在Mac系统下将Android环境部署好。...

继续阅读

稍顯嚴肅的台中 坦白說,留在腦海中的台中影像並不多,來台灣之前在Booking上只訂到了台中的一家青旅,第一次住青旅有些不習慣,幹什麼都放不開。 同屋的一個男生是台灣人,不過一年中四分之三的時間在上海跟北京,這麼說來跟我還是比較有共同話題的。得之我準備花15天的時間環島,覺得太倉促了,他們大學時期花一個半月的時間也不見得能將台灣島給逛完。我只能無奈地表示,兩岸允許的簽證時間有限,自己的空閒時間更有限,只能用打卡式的旅行了,我深知正真地旅行應該慢下來,融入當地的環境,感受他們的風土人情,但第一次只能這樣作罷,以後換成民進黨上台,形勢會變成怎樣還不得而知,能否再過來還是個未知數。而我一向信奉的人生格言是秉燭夜遊,活在當下,所以,理解自己吧。...

继续阅读

為之留戀的新竹 來新竹之前本沒有對她有過高的期待,慢慢對她加分要從桃園火車站出發前往新竹開始。 在桃園火車站的候車月台上,有醒目的旅遊資料發放處,這上面的擺放的全是新竹的旅遊宣傳資料,關鍵的是資料做得非常簡潔易懂,而接下來一天的新竹之行就全部是依據這份寶典的指引來完成的。...

继续阅读

從桃園開始台灣之行 初到台灣恰逢華夏銀行系統升級,特意準備的華夏銀聯卡在桃園機場沒能派上用場,只好用建行在機場5000塊,算下來是很不划算的,但是沒辦法,誰叫我出機場就得花錢呢。 從機場打車到桃園的酒店,花了將近六百塊新台幣,到酒店時五點多,天已經漸亮了,洗漱完等到七點吃過早餐就開始補覺囉,一覺醒來已是中午,帶著換下來的衣服外出找自助洗衣店,順便覓食。...

继续阅读

  • Prev
  • Next

Linux下的调试器

文章分类 : Linux, 应用与编程, 教程

程序编写完毕以后,可能或多或少会存在一些问题,为了解决这些问题,我们就需要对程序进行调试。最基本最明了的调试办法通常是使用 printf 函数在关心的代码位置打印关心的信息,但是出于代码简洁性的考虑,我们只会在必要的位置加上 printf 打印语句,这样就可能忽略很多潜在的问题,为了找出这些潜在的问题,我们需要用到调试器。    

Linux 下的调试工具很多,常见的有 strace、gdb、valgrind 等。strace 工具用于跟踪进程执行时的系统调用和所接收的信号,包括参数、返回值、执行时间;gdb 工具用于单步、多步运行程序,可以在指定位置设置断点,可以实时的查看堆栈信息,对于附加调试信息的可执行程序,还可以打印当前运行位置的源代码,查看任意变量内的值;valgrind 用于程序的内存调试和代码剖析,它可以跟踪程序的内存使用情况,检测出内存管理方面的 bug 。

调试 Linux 下的程序,使用最广泛,功能最全面的就属 gdb 了,这里便以 gdb 为例介绍一下程序的调试方法。

一、GDB

1、GDB 初体验

GDB 全称 The GNU Project Debugger,即 GNU 的调试工具,它是一个用来调试程序的调试器,支持 GCC 所支持的所有语言,包括C语言、C++、Fortran、PASCAL、Java、Chill、assembly 和 Modula-2,可以使程序开发者在程序运行时观察程序的内部结构和内存的使用情况等信息。

gdb 主要提供如下一些功能:1、运行程序,设置所有的能影响程序运行的参数和环境;2、控制程序在指定的条件下停止运行;3、当程序停止时,可以检查程序的状态;4、修改程序的错误,并重新运行程序;5、动态监视程序中变量的值;6、可以单步执行代码,观察程序的运行状态。

gdb 程序调试的对象是可执行文件,而不是程序的源代码文件。然而,并不是所有的可执行文件都可以用 gdb 调试。如果要让产生的可执行文件可以用来调试,需在执行 gcc 指令编译程序时,加上 -g 选项,指定程序在编译时包含调试信息。调试信息包含程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。gdb 利用这些信息使源代码和机器码相关联。

2、gdb 接触

下面我们使用 gdb 来调试一下一个简单的加法程序,程序源代码如下。

1 /* main.c */
2 #include <stdio.h>
3
4 int main(void)
5 {
6     int a, b, c;
7
8      a = 5;
9      b = 6;
10      c = 0;
11      c = add(a, b);
12      printf("%d + %d = %d \n", a, b, c);
13
14      return 0;
15 }

1 /* add.c */
2 int add(int i, int j)
3 {
4      int k;
5
6      k = i + j;
7
8      return k;
9 }

编译源代码,为了附加调试信息,gcc 编译是应该使用 -g 选项。

trevor@trevor-PC:~/linux/linux100$ gcc add.c main.c -g -o add

使用 GDB 调试 add 程序,跟踪 add 的执行流程。

trevor@trevor-PC:~/linux/linux100$ gdb add <---------- 启动 GDB
GNU gdb (GDB) 7.2-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/trevor/linux/linux100/add...done.
(gdb) l <-------------------- l命令相当于list,从第一行开始例出源代码
1 /* main.c */
2 #include <stdio.h>
3
4 int main(void)
5 {
6      int a, b, c;
7
8      a = 5; <--------------------- 退出gdb
9      b = 6;
10      c = 0;
(gdb) <-------------------- 直接回车表示,重复上一次命令
11      c = add(a, b);
12      printf("%d + %d = %d \n", a, b, c);
13
14      return 0;
15 }
(gdb) l add <-------------------- l 命令跟函数名,可以列出指定函数定义处的源代码
1 /* add.c */
2 int add(int i, int j)
3 {
4      int k;
5
6      k = i + j;
7
8      return k;
9 }
(gdb) b main <-------------------- 设置断点,在函数 main 入口处,b 是 break 命令的简写
Breakpoint 1 at 0x80483e5: file main.c, line 8.
(gdb) b add.c:6 <-------------------- 设置断点,在源程序 add.c 文件的第6行处
Breakpoint 2 at 0x80483ca: file add.c, line 6.
(gdb) info b <-------------------- 查看断点信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x080483e5 in main at main.c:8
2 breakpoint keep y 0x080483ca in add at add.c:6
(gdb) r <--------------------- 运行程序,r 是 run 命令的简写
Starting program: /home/trevor/linux/linux100/add

Breakpoint 1, main () at main.c:8 <---------- 在断点处停住
8 a = 5;
(gdb) p a <--------------------- 打印变量 a 的值,p 是 print 命令的简写
$1 = 2658292
(gdb) p b
$2 = 134513771
(gdb) p c
$3 = 1174400
(gdb) n <--------------------- 单条语句执行,n 是 next 命令的简写
9 b = 6;
(gdb) n
10 c = 0;
(gdb) n
11 c = add(a, b);
(gdb) p a
$4 = 5
(gdb) p b
$5 = 6
(gdb) p c
$6 = 0
(gdb) c <--------------------- 继续运行程序,c 是 continue 命令的简写
Continuing.

Breakpoint 2, add (i=5, j=6) at add.c:6 <---------- 在断点处停住
6 k = i + j;
(gdb) bt <--------------------- 查看函数堆栈
#0 add (i=5, j=6) at add.c:6
#1 0x08048411 in main () at main.c:11
(gdb) n
8 return k;
(gdb) p k
$7 = 11
(gdb) finish <--------------------- 退出当前所在函数
Run till exit from #0 add (i=5, j=6) at add.c:8 <--------------------- 此行以及其下 4 行显示退出函数时的相关信息
0x08048411 in main () at main.c:11
11 c = add(a, b);
Value returned is $8 = 11
(gdb) n
12 printf("%d + %d = %d \n", a, b, c);
(gdb) n
5 + 6 = 11 <----------程序输出
14 return 0;
(gdb) c
Continuing.

Program exited normally. <--------程序退出,调试结束
(gdb) q <--------------------- 退出 GDB,q 是 quit 命令的简写
trevor@trevor-PC:~/linux/linux100$

到此,我们使用 GDB 跟踪了一遍 add 程序的执行流程,对 gdb 的功能及使用有了一个基本的了解,详细使用方法将在后面陆续介绍。

3、gdb 启动、退出

启动 GDB 的方法有以下几种:

(1) gdb program program 也就是你的执行文件,一般在当前目录下;

(2) gdb program core 用 gdb 同时调试一个运行程序和 core 文件,core 是程序非法执行后系统为了记录错误信息而产生的文件;

(3) gdb program PID 如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的 PID。gdb会自动attach上去,并调试它。program 应该在 PATH 环境变量中搜索得到。

GDB启动时,可以加上一些 GDB 的启动开关,详细的开关可以用gdb -help查看。下面只列举一些比较常用的参数:

--symbols=SYMFILE:从指定文件中读取符号表。
--se=FILE:从指定文件中读取符号表信息,并把他用在可执行文件中。
--core=COREFILE:调试时系统产生的core文件。
--directory=DIR:加入一个源文件的搜索路径。默认搜索路径是环境变量中PATH所定义的路径。

退出 GDB 可以是使用 q 或 quit 命令,也可以使用快捷键 “Ctrl+d”。如果退出时程序处于运行或者暂停状态,将输出“Quit anyway? (y or n) ”提示是否退出,如果确定要退出,输入 y 即可。

二、GDB常规接触

1、程序调试法宝之运行程序

当以“gdb program”的方式启动 gdb 以后,gdb 会在 PATH 路径和当前目录中搜索源文件。如要想知道 gdb 是否找到源文件,可使用 l 或 list 命令,看看 gdb 是否能够显示出源代码。在 gdb 中,运行程序使用 r 或是 run 命令。运行程序之前,可能需要进行下面四方面的设置。

(1) 程序运行参数

set args 可指定运行时参数,如:set args 10 20 30 40 50。
show args 命令可以查看设置好的运行参数。

(2) 运行环境

path可设定程序的运行路径。
show paths 查看程序的运行路径。
set env environmentVarname=value 设置环境变量,如:set env USER=benbenshow。
env [varname] 查看环境变量,不带 varname 则打印出当前所有环境变量。

(3) 工作目录

cd相当于shell的cd命令。
pwd 显示当前的所在目录。

(4) 程序的输入输出

info terminal 显示你程序用到的终端的模式。
使用重定向控制程序输出,如:run > outfile。
tty命令可以设置输入输出使用的终端设备,如:tty /dev/tty1。

(5) 调试已运行的程序

a、在UNIX下用ps查看正在运行的程序的PID(进程ID),然后用gdb PID process-id 格式挂接正在运行的程序。
b、先用gdb 关联上源代码,并进行gdb,在gdb中用attach process-id命令来挂接进程的PID。并用detach来取消挂接的进程。

2、程序调试法宝之单步运行

倘若使用 gdb 设置了断点,执行 run 命令以后程序将停在第一次遇到的断点处,此时我们可以使用 continue 命令恢复程序的运行直到遇到下一个断点或者程序结束,也可以使用 step 或 next 命令单步运行程。

continue [ignore-count]
c [ignore-count]
fg [ignore-count]

以上命令用于恢复程序运行,直到下一个断点到来,或是程序结束。ignore-count 表示忽略其后的断点次数,不指定则表示不忽略。continue,c,fg 三个命令的意思一样。

step [count]
s [count]

以上命令用于单步跟踪程序,如果有函数调用,gdb 会进入该函数,进入函数的前提是此函数被编译有调试信息。count 可以不加,不加表示一条条地执行,加表示执行后面的 count 条指令后再停住。

next [count]
n [count]

以上命令同样用于单步跟踪程序,如果有函数调用,gdb 不会进入该函数。count 也可以不加,不加表示一条条地执行,加表示执行后面的 count 条指令后再停住。

finish

以上命令用于运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址、返回值、参数值等信息。

until 或 u

当你厌倦了在一个循环体内单步跟踪时,以上命令可以运行程序直到退出循环体。

3、程序调试法宝之设置断点

我们用 break 命令来设置断点,break 通常简写为 b,下面有几种设置断点的方法:

break function

在进入指定函数 function 时停住,C++ 中可以使用 class::function 或 function(type,type) 格式来指定函数名。

break linenum

在指定行号 linenum 处停住。

break +offset
break -offset

在当前行号的前面或后面的 offset 行处停住。

break filename:linenum

在源文件 filename 的 linenum 行处停住。

break filename:function

在源文件 filename 的 function 函数的入口处停住。

break ... if condition

“…”可以是上述的参数,condition 表示条件,在条件成立时停住。比如在循环境体中,可以设置“break if i=100”,表示当 i 为 100 时停住程序。

info breakpoints [n]
info b [n]

如上命令用于查看断点,n 表示断点号,不指定 n 则显示所有的断点。

delete breakpoints [n]

如上命令用于删除断点,n 表示断点号,不指定 n 则删除所有的断点。当有多个断点时,gdb 会提示“删除所有断点吗? (y or n)”,确定要删除,输入 y 即可。

4、程序调试法宝之设置观察点

观察点一般来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有变化,马上停住程序。我们有下面的几种方法来设置观察点:

watch expr

为表达式(变量)expr 设置一个观察点。一量表达式值有变化时,马上停住程序。

rwatch expr

当表达式(变量)expr被读时,停住程序。

awatch expr

当表达式(变量)的值被读或被写时,停住程序。

info watchpoints [n]

如上命令用于查看观察点信息,n 表示观察点号,不指定 n 则显示所有的观察点。

由于观察点属于断点的一类,查看断点时会将观察点也显示出来,删除观察点的方法跟删除断点的方法一样。

5、程序调试法宝之设置捕捉点

我们可设置捕捉点来捕捉程序运行时的一些事件,如载入共享库(动态链接库)或是 C++ 的异常。设置捕捉点的格式为:

catch event

当event发生时,停住程序。event可以是下面的内容:

catch assert:捕捉 Ada 语言的 assert 断言失败
catch catch :捕捉C++收到到异常
catch exception:捕捉到 Ada 语言的 exception
catch exec:捕捉 exec 调用
catch fork:捕捉 fork 调用
catch syscall:捕捉 syscall 调用
catch throw:捕捉C++抛出异常
catch vfork:捕捉 vfork 调用
tcatch event

只设置一次捕捉点,当程序停住以后,捕捉点被自动删除。

6、程序调试法宝之维护停止点

断点、观察点跟捕捉点统称停止点,前面讲到了如何在 GDB 中设置程序的停止点。如果你觉得已经设置的停止点没有用了,可以使用 delete、clear、disable、enable 等命令来维护停止点。

clear

清除所有的已定义的停止点。

clear function

清除所有设置在 function 函数上的停止点。

clear linenum

清除所有设置在 linenum 行上的停止点。

clear filename:linenum

清除所有设置在 filename 文件 linenum 行上的停止点。

delete [breakpoints]
delete [range...]

删除指定的断点,breakpoints 为断点号。如果不指定断点号,则表示删除所有的断点。range 表示断点号的范围,如“3-7”。其简写命令为 d。

disable [breakpoints]
disable [range...]

比删除更好的一种方法是让停止点失效,停止点失效以后,GDB 不会删除它,当你还需要时,将其设置为有效即可,就好像回收站一样。如上两条命令用于让指定的停止点失效,breakpoints 为停止点号。如果什么都不指定,表示让所有的停止点失效,简写命令是dis。

enable [breakpoints]
enable [range...]

如上命令让指定的停止点生效。breakpoints 为停止点号,range 表示断点号的范围。

enable [breakpoints] once
enable [range...] once

如上命令让指定的停止点只生效一次,当程序停止后,该停止点马上被 GDB 自动设置为失效。breakpoints 为停止点号,range 表示断点号的范围。

enable [breakpoints] delete
enable [range...] delete

如上命令让指定的停止点只生效一次,当程序停止后,该停止点马上被 GDB 自动删除。breakpoints 为停止点号,range 表示断点号的范围。

7、程序调试法宝之停止条件维护

前面在讲到设置断点时,我们提到过可以设置一个条件,当条件成立时,程序自动停止,这是一个非常强大的功能,这里,我们来了解一下这个条件的相关维护命令。

一般来说,为断点设置一个条件,我们使用 if 关键词,后面跟其断点条件。并且,条件设置好后,我们可以用 condition 命令来修改断点的条件。

condition bnum expression

修改停止点号为 bnum 的停止条件为 expression。

condition bnum

清除停止点号为 bnum 的停止条件。

还有一个比较特殊的维护命令 ignore,可以指定程序运行时忽略停止条件几次。

ignore bnum count

表示忽略停止点号为 bnum 的停止条件 count 次。

除非注明,文章均为CppLive 编程在线原创,转载请注明出处,谢谢。

本文地址:https://www.cpplive.com/html/1765.html

这里因为你的留言而存在!!!

You must be logged in to post a comment.