加载中…
个人资料
HorkyChen
HorkyChen
  • 博客等级:
  • 博客积分:0
  • 博客访问:13,702
  • 关注人气:4
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
相关博文
推荐博文
谁看过这篇博文
加载中…
正文 字体大小:

Mac OS及Linux下运用GDB进行代码调试

(2008-08-23 23:30:00)
标签:

杂谈

上次见Bolin,被问到好久没有在网上写东西,想想确实很久了,平时都关注内部的Google Group了。现贴上一篇我上周写的东西,也算是更新了一下博客了,有点长! ^_^

在Mac OS及Linux最强大的Debug工具就是GDB了,许多IDE都集成了GDB进行调试。使用源代码级调试能够更直接的进行

调试,效率明显高于输出Log信息。但目前无论是Mac下的XCode,还是Linux下的其它集成工具,对于调试库函数都是相当困难的,如果直接使用GDB这些问题就迎刃而解。我们首先来探讨一下GDB的基础知识。

GDB调试流程

GDB调试依赖于编译器输出的调试信息,所以进行调试前必须确定GCC输出了调试信息。

1.生成符号文件

使用GCC编译时需要生成相应的调试信息,编译时可以使用-g选项:

<<详细内容参GCC Manual>> Section 3.9

-g  表示将Symbol Table以系统原生格式直接生成到可执行文件中

-g选项最好不要同-O一起使用,因为代码经过优化后有时会同源代码差异很大,可能找不到指定的变量等等。

-ggdb  表示将专门为GDB调试使用生成调试信息,它会包括很多GDB的扩展信息

其它的Symbol Table的格式还有COFF,DWARF,Stabs等. (Mac OS默认为DWARF. DWARF也是基于COFF实现[Common Object File Format])

输出的调试信息的多少由三个等级:

-g[level]  默认为2

  表示不生成任何调试信息

  表示生成最少的调试信息,不提供局部变量及源代码行列等信息。

  标准模式

  较2而言,包含了宏定义等额外信息

其它同调试相关的编译选项还有:

-p 生成额外的代码用于输出profile信息,用于另一个工具程序使用:prof

--coverage 用于统于代码的覆盖率.  

--ftest-coverage 类似上面的--coverage

-d*  用于Dump一些有用的信息,详细内容参GDB Manual.

2.启动GDB进行调试

下面的过程,我都以下面的工程进行解释:

目标程序: text2bin 

源代码:text2bin.c

功能: 将文本文件转成二进制文件

  使用方法: ./text2bin txt_file_name  [offset]

                    txt_file_name为源文本文件名

                    offset指定忽略左侧多少字节

A.调试应用程序

(1)启动 

  直接在命令行下输入gdb ./text2bin 或者运行gdb后输入file ./text2bin都可以加载指定的应用程序.

GDB会显示加载Symbols的过程,注意如果没有出现加载text2bin的调试信息的过程,就是表明无法获取调试信息!

Reading symbols for shared libraries ... done

Reading symbols from /Horky/Project/WINBASE/WINBASE/TextToRaw/text2bin...

warning: UUID mismatch detected between:

/Horky/Project/WINBASE/WINBASE/TextToRaw/text2bin

(2)设置断点

  *除了断点外还有Watchpoints(观测点)及Catchpoints (异常捕捉点)

  输入b或break 加上断点位置或断点函数名,如

  b main  #在main函数入口设置断点

  b text2bin.c:50  在源代码第50行设置断点

如果需要查看断点信息可以使用指令:

info breakpoints

清除所有断点使用指令:

clear

清除特定的断点使用指令:

clear text2bin.c:50

或 clear main

在调试过程可以使用disable及enable开关某个特定的断点.如enable 2 及 disable 2开关第2个断点。

在使用info b查看断点时,注意其中Enb栏位的变化。

对于观测点(Watchpoints),是指在某个条件下触发的断点,如text2bin中77行:

Buffer2[nCount++] = ConvertTextToInt(sData);

我们要查看当nCount为10时的运行状况,我们可以通过下面的步骤完成:

a. 执行b 77,返回这个断点号是3

b. 执行condition 3 nCount=10

过程如下:

(gdb) b 77

Breakpoint 3 at 0x1c73: file text2bin.c, line 77.

(gdb) condition 3 nCount=10 

(gdb) info breakpoints

Num Type           Disp Enb Address    What

  breakpoint     keep y   0x00001e1e in ConvertTextToInt at text2bin.c:124

breakpoint already hit 1 time

  breakpoint     keep y   0x00001e25 in ConvertTextToInt at text2bin.c:125

  breakpoint     keep y   0x00001c73 in main at text2bin.c:77

stop only if nCount = 10

这样就可以控制当nCount为10时在77行处中断.

如果在调试时,需在下面若干行代码后追加一个断点,在指定位置可以使用偏移量来指定断点,

如b +5 及b -5,即表示在当前行的后五行及前五行位置设置断点

(3)控制调试过程

在开始时需要告诉GDB目标程序是哪一个,可以用gdb ./text2bin,也可以在启动gdb后使用指令:

file ./text2bin来指定。

运行则使用r/run指令,可以同时带上参数,如

r ./expert.txt 7

在调试过程中需要有一系列操作控制调试的过程:

c / continue  /fg

*fg是foreground的缩写

从断点状态恢复程序的执行.

s / step [count]

单步执行 (step in)

n / next [count]

单步执行,跳过函数 (step out)

u / until [location]

执行到某个位置。 当遇到循环时可以使用此指令方便地跳转到指定的位置。

finish

执行到当前函数结束位置为止,同时显示函数返回值

backtrace

查看当前位置的被调用路径

(4)监测变量及内存

简单地显示变量的值可以使用print指令直接输出.

p / print [expression]

如输出main函数中的文件名变量 p argv[1]

    输出ConvertTextToInt函数中n的值 p n或p ConvertTextToInt::n

查看内存内容时则使用x指令:

x /nfu addr  以指定格式显示内存内容

x addr  显示指定地址处理内存内容

x 显示当前数据段内容

如以字串形显示某内存内容的指令为:

   x /sb 0xbffff80e

以数据形式显示某内存中5个字节的内容的指令为:

  x /5db 0xbffff80e

设定local variable watch,用来在每条执行后显示某些变量的值,可以使用display指令来指定,如:

display S

display /sb 0xbffff707

去除时使用undisplay #  (#为display列表中的序号)

display后面所带的参数同x指令:

n 表示repeat count

f 表示格式,分为:

  十六制数据

  带符号之整型数据

  无符号整型数据

  八进制整型数据

  二进制数据

  a 以地址格式显示,包括十六进制及偏移量

  c 以字符形式输出

  小数位输出

  以字串形式输出

u表示大小单位:

   Bytes

   Halfwords

  Words

  Giant words (eight bytes)  

(5)查看源代码

使用l / list指令就可查看源代码了,

如:

l 150 查看当前代码的第150行

l text2bin.c:150  查看text2bin.c的150行.

l main  查看main函数内容

查看时如果需要翻页,直接回车.

默认GDB一次显示10行,我们也可以通过set linesize [count]进行调整。

B.如何调试动态库或静态库

当调试库函数时,需要透过主程序调用的形式来挂载,所以不能直接使用GDB对目标库进行调试,而是需要attach指定的父进程,然后再进行测试. 这里有两种情况:

(1)主程序启动时自动加载库,此时使用GDB挂载时也会自动加载相应的调试信息.

(2)主程序动态加载库,对于这种情况则需要另外使用symbol filename来加载特定的调试信息。

以Mac TWAIN Driver中的NetScan Module为例,它属于第二种情况,所以整个流程为:

a. 启动TWAIN,Scan Manager应用程序出现

b. 使用ps或Activity Monitor获得Scan Manager的Process ID,设为3354

c. 启动GDB,并执行attach 3255,如下:

(gdb) attach 3354

Attaching to process 3354.

Reading symbols for shared libraries ...+ done

   此Scan Manager会被挂起,等待恢复。

d.使用指令symbol加载NetScan的符号表(Symbol Table),如下:

symbol ./Ricoh\ NetScan

Load new symbol table from "/Library/Application Support/R7ET/R7ET NetScan.bundle/Contents/MacOS/ Ricoh NetScan"? (y or n) y

Reading symbols from /Library/Application Support/R7ET/R7ET NetScan.bundle/Contents/MacOS/Ricoh  NetScan...Reading symbols from /ObjectCodes/PMXNetScan/Debug/PMXNetScan.bundle.dSYM/Contents/ Resources/DWARF/PMXNetScan...done.

e.下断点。可以在DeviceIOControl,TCPRead,TCPWrite等处下断点,使用指令如下:

           b DeviceIOControl

           b TCPRead

           b TCPWrite

f.使用c恢复Scan Manager的执行,然后当进行网络通讯时就可以进行单步执行了。

*提醒:当进行多线程调试时,一定要确保能找出真正的主线程。

*断开主线程时,使用detach指令来完成

C.如何调试多线程的程序

我们在写程序时常常会有多线程的运用, 比如有些程序中读取数据及数据处理,就是通过两个线程来完成的。对

多线程进行调试最大的难点在于线程的同步问题.

GDB提供了一套指令针对多线程:

info threads

  查看当前有多少线程

thread #  

  切换到指定线程

set print thread-events on/off

  设定是否打印线程状态

当设置中断时,也可以专为某个线程设置,如

b/break [location]  thread #

即表示为#线程在location处设置断点,这样就可以进行线程别的调试。

3. 技巧

(1)在GDB中如果需要调用外部程序可以使用shell [command]来完成

(2)当源代码目录被移动了,或者在另一台PC上调试,GDB不能通过Debug信息找到源代码时,可以使用directory/dir来指搜索的目录

4.GDB的前台程序(GDB frontend)

  使用命令行是显得不方便了,所以我们可以选择一些GDB前端程序:

DDD   [GNU] (http://www.gnu.org/software/ddd  目前功能最为强大的GDB前端程序)   

  Nemiver  [GNOME]  (http://home.gna.org/nemiver/)

  Kdbg   [KDE] (www.kdbg.org)

  Insight  [Wirte in Tcl/Tk] (http://sourceware.org/insight)

  Emacs (不用介绍了!)

  一般的IDE也带有GDB frontend程序,如XCode,KDevelop,Anjuta,Eclipse.

*GDB Frontend都是通过伪终端(pseudo-terminal)的方式来实现,有兴趣可以了解一下. 

扩展GDB的功能

已经有人在通GDB进行代码覆盖率测试,事实上我们也可以通过类似GDB的方式读取Debug信息中的符号表来进行语法检查。有关Debug信息的存放,可以使用objdump -x或readelf -a来查看其中的不同,这有助更好的理解程序的结构.

需求是多样的,GDB本身提供了两种方式来扩展GDB,一种为组合GDB的指令,类似宏的方式,另一种方式则是功能强大的python脚本。

(1)在GDB环境下使用define指令来定义一个指令,如

define localv

  info scope $arg0

end

这样我们在使用时,想查看main函数中的所有的变量,就可以通过下面的指令完成:

(gdb) localv main

Scope for main:

Symbol argc is at the address (reg 5 + 8), length 4.

Symbol argv is at the address (reg 5 + 12), length 4.

Symbol fpSrc is at the address (reg 5 + -44), length 4.

......

如果这样的指令非常好用,每次调试时都定义一次不太现实。所以GDB允许将这些操作定义在一个文本文件中,然后在GDB中使用source [command_file]来执行,如source /TestData/localv.cmd 。

在执行过程中GDB不会显示每个指令的执行结果,如果需要显示就在source加-v来打开。

除了组织指令集外,还有另一种有用的自定义指令: Hooks. GDB允许用户指定在特定的GDB指令执行前后执行一段自定义指令。比如,如果希望在设置断点前后都显示当前断点状况,就可以定义两个如下指令:

(gdb) define hook-break

Type commands for definition of "hook-break".

End with a line saying just "end".

>info b

>end

(gdb) define hookpost-break

Type commands for definition of "hookpost-break".

End with a line saying just "end".

>info b

>end

然后执行break / b指令时就可以看到类似下面的输出:

(gdb) b GetFileSize

Num Type           Disp Enb Address    What

  breakpoint     keep y   0x00001a68 in main at text2bin.c:25

  breakpoint     keep y   0x00001e1e in ConvertTextToInt at text2bin.c:124

Breakpoint 3 at 0x1d86: file text2bin.c, line 108.

Num Type           Disp Enb Address    What

  breakpoint     keep y   0x00001a68 in main at text2bin.c:25

  breakpoint     keep y   0x00001e1e in ConvertTextToInt at text2bin.c:124

  breakpoint     keep y   0x00001d86 in GetFileSize at text2bin.c:108

hook及hookpost即表示在某个指令的前后。后面的指令一定要使用GDB指令的全写,如上面就不能写成define hook-b或define hookpost-b。

*如果需要更为详细的资料,请参考GDB Manual,20. Extending GDB

(2)在GDB环境可以直接调用python,如在GDB环境下执行python print 23

使用Python编写脚本同上面定义指令集是类似的,可以执行指令python, GDB就会要求输入python脚本,并以end为结束标志。

GDB为编写Python提供了一个新的模块gdb, 在脚本中可以进行引用,其中包括了几个主要的指令:

execute command    command是GDB CLI(Command Line Interface)指令字串.

get_parameter parameter  获取一项GDB的参数,诸如上面提到的linesize.

write string  输出一个字串到GDB输出窗口

flush    Flush当前GDB输出流

*只有当编译GDB时指定了—with-python时,GDB才会支持python指令.

帮你管好内存

实际编程中最令人头痛的便是内存问题,当然在Mac OS及Linux也各有其解决方案。

1. Mac OS下的Memory Leaks检查工具

XCode自带了一个MallocDebug(/Developer/Applications/Performance Tools)工具,用来帮助我们检查代码中的Memory leaks的问题,使用前需要将目标程序Link一个动态库(静态库只能使用于10.4之前的系统)libMallocDeubg.A.dylib(/Developer/usr/lib).这样就可以开始测试了。

叫出MallocDebug程序,在菜单File->New Windows,然后选择我们的目标程序,点Lanuch执行就可以查看结果了。

MallocDebug可以分析出其中Memory Leaks发生的状况,不过仅能指出发生问题时所在的函数 。

另外一个命令行工具leaks,可以简单地使用leaks <pid>直接查看进程号为<pid>的程序。

有关Mac OS下的内存性能检测的更多内容,参考下面的网址:

http://developer.apple.com/documentation/Performance/Conceptual/ManagingMemory/Articles/FindingLeaks.html

2. Linux下的内存检测工具

Valgrind是Unix/Linux下最为出色的内存检测工具,号称Unix/Linux开发人员人人必备的工具之一,目前还没有Mac OS版,不过已经列为其下一代的首要任务之一。 (*Valgrind的官网为:http://valgrind.org)

使用时简单的使用命令valgrind –leak-check=full –show-reachable=yes ./txt2bin  就会得出相应的报告了。

报告中详细列出了访问越界及内存泄露的问题及发生问题时所在的源代码位置。

附录. 参考文档:

(1) 使用GDB进行代码覆盖率测试

http://www.ibm.com/developerworks/cn/linux/l-cn-gdb/

(2)  使用 GDB 调试 Linux 软件

http://www.ibm.com/developerworks/cn/linux/sdk/gdb/

(3)  掌握 Linux 调试技术

http://www.ibm.com/developerworks/cn/linux/sdk/l-debug/

(4)  用GDB调试程序

http://docs.chinalinuxpub.com/doc/pro/gdb.html

(5) GDB调试精粹及使用实例

http://fanqiang.chinaunix.net/program/other/2006-07-14/4834.shtml

(6) GDB的官方文档

http://www.gnu.org/software/gdb/documentation/

(7) GDB指令参考 (可以打出来方便查询)

http://users.ece.utexas.edu/~adnan/gdb-refcard.pdf

0

阅读 评论 收藏 转载 喜欢 打印举报/Report
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4000520066 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有