手把手教你玩转GDB

手把手教你玩转GDB(一)——牛刀小试:启动GDB开始调试

       写在最前面:GDB是unix相关操作系统中C/C++程序开发必不可少的工具,它的功能之强大,是其它调试器所不能匹敌的。但是,现实的工作中,有很多开发者因为GDB本身入门门槛比较高,而被拒之门,与如此强大的失之交臂。笔者在近两年的C/C++开发工作中,对GDB本身的有一点研究,在这里总结出一系列《手把手教你玩转GDB》的文章,一方面权当是对自己经验的一个总结,一方面也是真的想能够对刚接触GDB的开发者朋友带去一些帮助,让更多的人来使用如此强大的工具。今天推出第一篇:

第一部分 牛刀小试:启动GDB开始调试

1. 启动GDB开始调试:

(1)gdb program           ///最常用的用gdb启动程序,开始调试的方式
(2)gdb program core   ///用gdb查看core dump文件,跟踪程序core的原因
(3)gdb program pid     ///用gdb调试已经开始运行的程序,指定pid即可

2. 应用程序带命令行参数的情况,可以通过下面两种方法启动:

(1)启动GDB的时候,加上–args选项,然后把应用程序和其命令行参数带在后面,具体格式为:gdb –args program args
(2)先按1中讲的方法启动GDB, 然后再执行run命令的时候,后面加上参数

3. 退出GDB:

(1)End-of-File(Ctrl+d)
(2)quit或者q

4. 在GDB调试程序的时候执行shell命令:

(1)shell command args(也可以先执行shell命令,GDB会退出到当前shell, 执行完command后,然后在shell中执行exit命令,便可回到GDB)
(2)make make-args(等同于shell make make-args

5. 在GDB中获取帮助:

(1)在GDB中执行help命令,可以得到如图1所示的帮助信息:

图1 GDB帮助菜单
由图1可以看出,GDB中的命令可以分为八类:别名(aliases)、断点(breakpoints)、数据(data)、文件(files)、内部(internals)、隐含(obscure)、运行(running)、栈(stack)、状态(status)、支持(support)、跟踪点(tracepoints)和用户自定义(user-defined)。
(2)help class-name:查看该类型的命令的详细帮助说明
(3)help all:列出所有命令的详细说明
(4)help command:列出命令command的详细说明
(5)apropos word:列出与word这个词相关的命令的详细说明
(6)complete args:列出所有以args为前辍的命令

6. info和show:

(1)info:用来获取和被调试的应用程序相关的信息
(2)show:用来获取GDB本身设置相关的一些信息

http://www.wuzesheng.com/?p=1327

 

手把手教你玩转GDB(二)——Breakpoint, Watchpoint和Catchpoint

       本文是《手把手教你玩转GDB》系列的第二篇,主要内容是用GDB调试程序中比较常用到的断点(breakpoint)、监视点(watchpoint)和捕捉点(catchpoint)。虽然说这三类point的功能是不一样的,但它们的用法却极为相似。因此,本文将以断breakpoint为例,进行详细的介绍,关于watchpoint和catchpoint的介绍就相对比较粗略,相信读者朋友如果能够理解breakpoint的部分,那么便可以触类旁通,学会watchpoint和catchpoint的用法。

1. Breakpoint: 作用是让程序执行到某个特定的地方停止运行

  • (1)设置breakpoint:
  • a. break function: 在函数funtion入口处设置breakpoint
    b. break +offset: 在程序当前停止的行向前offset行处设置breakpoint
    c. break offset: 在程序当前停止的行向衙offset行处设置breakpoint
    d. break linenum: 在当前源文件的第linenum行处设置breakpoint
    e. break filename:linenum: 在名为filename的源文件的第linenum行处设置breakpoint
    f. break filename:function: 在名为filename的源文件中的function函数入口处设置breakpoint
    g. break *address: 在程序的地址address处设置breakpoint
    h. break if cond: …代表上面讲到的任意一个可能的参数,在某处设置一个breakpoint, 但且仅但cond为true时,程序停下来
    i. tbreak args: 设置一个只停止一次的breakpoints, args与break命令的一样。这样的breakpoint当第一次停下来后,就会被自己删除
    k. rbreak regex: 在所有符合正则表达式regex的函数处设置breakpoint

  • (2)info breakpoints [n]:
  • 查看第n个breakpoints的相关信息,如果省略了n,则显示所有breakpoints的相关信息

  • (3)pending breakpoints:
  • 是指设置在程序开始调试后加载的动态库中的位置处的breakpoints
    a. set breakpoint pending auto: GDB缺省设置,询问用户是否要设置pending breakpoint
    b. set breakpoint pending on: GDB当前不能识别的breakpoint自动成为pending breakpoint
    c. set breakpoint pending off: GDB当前不能识别某个breakpoint时,直接报错
    d. show breakpoint pending: 查看GDB关于pending breakpoint的设置的行为(auto, on, off)

  • (4)breakpoints的删除:
  • a. clear: 清除当前stack frame中下一条指令之后的所有breakpoints
    b. clear function & clear filename:function: 清除函数function入口处的breakpoints
    c. clear linenum & clear filename:linenum: 清除第linenum行处的breakpoints
    d. delete [breakpoints] [range…]: 删除由range指定的范围内的breakpoints,range范围是指breakpoint的序列号的范围

  • (5)breakpoints的禁用、启用:
  • a. disable [breakpoints] [range…]: 禁用由range指定的范围内的breakpoints
    b. enable [breakpoints] [range…]: 启用由range指定的范围内的breakpoints
    c. enable [breakpoints] once [range…]: 只启用一次由range指定的范围内的breakpoints,等程序停下来后,自动设为禁用
    d. enable [breakpoints] delete [range…]: 启用range指定的范围内的breakpoints,等程序停下来后,这些breakpoints自动被删除

  • (6)条件breakpoints相关命令:
  • a. 设置条件breakpoints可以通过break … if cond来设置,也可以通过condition bnum expression来设置,在这里首先要通过(1)中介绍的命令设置好breakpoints,然后用condition命令来指定某breakpoint的条件,该breakpoint由bnum指定,条件由expression指定
    b. condition bnum: 取消第bnum个breakpoint的条件
    c. ignore bnum count: 第bnum个breakpoint跳过count次后开始生效

  • (7)指定程序在某个breakpoint处停下来后执行一串命令:
  • a. 格式:commands [bnum]
    … command-list …
    end
    b. 用途:指定程序在第bnum个breakpoint处停下来后,执行由command-list指定的命令串,如果没有指定bnum,则对最后一个breakpoint生效
    c. 取消命令列表: commands [bnum]
    end
    d. 例子:
    break foo if x>0
    commands
    silent
    printf “x is %d\n”,x
    continue
    end
    上面的例子含义:当x>0时,在foo函数处停下来,然后打印出x的值,然后继续运行程序

    2. Watchpoint: 它的作用是让程序在某个表达式的值发生变化的时候停止运行,达到‘监视’该表达式的目的

  • (1)设置watchpoints:
  • a. watch expr: 设置写watchpoint,当应用程序写expr, 修改其值时,程序停止运行
    b. rwatch expr: 设置读watchpoint,当应用程序读表达式expr时,程序停止运行
    c. awatch expr: 设置读写watchpoint, 当应用程序读或者写表达式expr时,程序都会停止运行

  • (2)info watchpoints:
  • 查看当前调试的程序中设置的watchpoints相关信息

  • (3)watchpoints和breakpoints很相像,都有enable/disabe/delete等操作,使用方法也与breakpoints的类似
  • 3. Catchpoint: 的作用是让程序在发生某种事件的时候停止运行,比如C++中发生异常事件,加载动态库事件

  • (1)设置catchpoints:
  • a. catch event: 当事件event发生的时候,程序停止运行,这里event的取值有:

    • 1)throw: C++抛出异常
      2)catch: C++捕捉到异常
      3)exec: exec被调用
      4)fork: fork被调用
      5)vfork: vfork被调用
      6)load: 加载动态库
      7)load libname: 加载名为libname的动态库
      8)unload: 卸载动态库
      9)unload libname: 卸载名为libname的动态库
      10)syscall [args]: 调用系统调用,args可以指定系统调用号,或者系统名称

    b. tcatch event: 设置只停一次的catchpoint,第一次生效后,该catchpoint被自动删除

  • (2)catchpoints和breakpoints很相像,都有enable/disabe/delete等操作,使用方法也与breakpoints的类似
  • http://www.wuzesheng.com/?p=1362

     

    手把手教你玩转GDB(三)——常用命令

    本文是手把手教你玩转GDB的第三篇,主要内容是介绍一些在程序调试过程中最常用的GDB命令,废话不多话,开始今天的正题。
    1.attach process-id/detach

  • (1)attach process-id: 在GDB状态下,开始调试一个正在运行的进程,其进程ID为process-id
  • (2)detach: 停止调试当前正在调试有进程,与attach配对试用
  • 2.kill

  • (1)基本功能:杀掉当前GDB正在调试的应用程序所对应的子进程
  • (2)如果想不退出GDB而对当前正在调试的应用程序重新编译、链接,可以在GDB中执行kill杀掉子进程,等编译、链接完后,再重新执行run,GDB便可加载新的可执行程序启动调试
  • 3.多线程程序调试相关:

  • (1)thread threadno:切换当前线程到由threadno指定的线程
  • (2)info threads:查看GDB当前调试的程序的各个线程的相关信息
  • (3)thread apply [threadno] [all] args:对指定(或所有)的线程执行由args指定的命令
  • 4.多进程程序调试相关(fork/vfork):

  • (1)缺省方式:fork/vfork之后,GDB仍然调试父进程,与子进程不相关
  • (2)set follow-fork-mode mode:设置GDB行为,modeparent时,与缺省情况一样;modechild时,fork/vfork之后,GDB进入子进程调试,与父进程不再相关
  • (3)show follow-fork-mode:查看当前GDB多进程跟踪模式的设置
  • 5.step & stepi

  • (1)step [count]: 如果没有指定count, 则继续执行程序,直到到达与当前源文件不同的源文件中时停止;如果指定了count, 则重复行上面的过程count
  • (2)stepi [count]: 如果没有指定count, 继续执行下一条机器指令,然后停止;如果指定了count,则重复上面的过程count
  • (3)step比较常见的应用场景:在函数func被调用的某行代码处设置断点,等程序在断点处停下来后,可以用step命令进入该函数的实现中,但前提是该函数编译的时候把调试信息也编译进去了,负责step会跳过该函数。
  • 6.next & nexti

  • (1)next [count]: 如果没有指定count, 单步执行下一行程序;如果指定了count,单步执行接下来的count行程序
  • (2)nexti [count]: 如果没有指定count, 单步执行下一条指令;如果指定了count, 音频执行接下来的count条执行
  • (3)stepinexti的区别:nexti在执行某机器指令时,如果该指令是函数调用,那么程序执行直到该函数调用结束时才停止。
  • 7.continue [ignore-count] 唤醒程序,继续运行,至到遇到下一个断点,或者程序结束。如果指定ignore-count,那么程序在接下来的运行中,忽略ignore-count次断点。
    8. finish & return

  • (1)finish: 继续执行程序,直到当前被调用的函数结束,如果该函数有返回值,把返回值也打印到控制台
  • (2)return [expression]: 中止当前函数的调用,如果指定了expression,把expresson值当做当前函数的返回值;如果没有,直接结束当前函数调用
  • 9.信号的处理

  • (1)info signals & info handle:打印所有的信号相关的信息,以及GDB缺省的处理方式:
  • (2)handle signal action: 设置GDB对具体某个信号的处理方式。signal可以为信号整数值,也可以为SIGSEGV这样的符号。action的取值有:
  • a. stopnostop: nostop表示当GDB收到指定的信号,不会应用停止程序的执行,只会打印出一条收到信号的消息,因此,nostop也暗含了下面的print; 而stop则表示,当GDB收到指定的信号,停止应用程序的执行。
    b. printnoprint: print表示如果收到指定的信号,打印出一条信息; noprintprint表示相反的意思
    c. passnopasspass表示如果收到指定的信号,把该信号通知给应用程序; nopass表示与pass相反的意思
    d. ignorenoignore: ignorenopass同义,同理,noignorepass同义

    http://www.wuzesheng.com/?p=1386

     

    C/C++程序在GDB调试状态时的信号响应

    相信用GDB调试过程序的朋友都知道,C/C++程序在GDB调试状态是不能直接响应外部信号的。比如,你正在用GDB运行一个程序,然后,你按了Ctrl+C,GDB收到SIGINT信号,程序本身并不会收到这个信号。那么,倒底如何让GDB把信号传递给应用程序本身呢?且听我一一道来。

    GDB中有一个handle命令,可以指定如何处理收到信号,GDB支持的对信号的处理主要有以下几种:
    (1)stop和nostop: nostop表示当GDB收到指定的信号,不会应用停止程序的执行,只会打印出一条收到信号的消息,因此,nostop也暗含了下面的print; 而stop则表示,当GDB收到指定的信号,停止应用程序的执行。
    (2)print和noprint: print表示如果收到指定的信号,打印出一条信息; noprint与print表示相反的意思
    (3)pass和nopass:pass表示如果收到指定的信号,把该信号通知给应用程序; nopass表示与pass相反的意思
    (4)ignore和noignore: ignore与nopass同义,同理,noignore与pass同义

    GDB中handle命令的具体用法为:handel signal action
    (1)signal:可以为数字1-15,也可以为符号类型的,比如:SIGSEGV, SIGINT。
    (2)action: 为上面介绍的stop/nostop, print/noprint, pass/nopass和ignore/noignore中的其中一种。
    (3)在gdb中,用info signals或者info handle,可以查看哪些信号被GDB处理,并且可以看到缺省的处理方式,下图是1-15号信号的:

    http://www.wuzesheng.com/?p=1188

     

    C++结构类型在GDB中的强制类型转换

    今天在调试程序的过程中遇到的一个小问题,在这里记录一下,希望能对遇到同样问题的朋友有所帮助。

    以下面的程序为例程进行说明:

    class Base
    {
    public:
     
        Base(int nNum) :
            m_nNum(nNum)
        {}
     
    private:
     
        int m_nNum;
    };
     
    class A : public Base
    {
    public:
     
        A(int nNum, char bCh) :
            Base(nNum),
            m_bCh(bCh)
        {}
     
    private:
     
        char m_bCh;
    };
     
    namespace Test
    {
        class Base
        {   
            public:
     
                Base(int nNum) :
                    m_nNum(nNum)
            {}  
     
            private:
     
                int m_nNum;
        };  
     
        class A : public Base
        {   
            public:
     
                A(int nNum, char bCh) :
                    Base(nNum),
                    m_bCh(bCh)
            {}  
     
            private:
     
                char m_bCh;
        };
    }
     
    int main()
    {
        Base * pObj1 = new A(10, 'A');
        Test::Base * pObj2 = new Test::A(10, 'A');
     
        delete pObj1;
        delete pObj2;
    }

    相信只要有点C++基础的人都能够看懂上面的程序,如果看不懂的话,还是先回去学C++吧,嘿嘿!好了,言规正传,现在抛出问题:用gdb启动编译好的程序,在63行处设置断点,如何查看pObj1和pObj2的m_bCh的值?

    1. 第一次尝试,直接p *pObj1,结果如下所示:

    (gdb) p *pObj1
    $1 = {
      m_nNum = 10
    }

    2. 由第一次尝试的结果,想到了强制类型转型,转换为子类A的指针类型,然后再print, 结果如下:

    (gdb) p *(A*)pObj1 
    $3 = {
        Base = {
        m_nNum = 10
      }, 
      members of A: 
      m_bCh = 65 'A'
    }
    这里便可以看到pObj1的m_bCh的值了。
    3. 有了上面的经验,对于pObj2的m_bCh,就依上面2中的方法,直接上了,结果如下:
    (gdb) p *(Test::A*)pObj2
    A syntax error in expression, near `)pObj2'.
    汗啊,居然提示说是语法错误,于是在网上找啊找,终于找到了解决的方法。
    4. 下面是找到的方法,说是带namespace的强制转型要加单引号的,好诡异的语法:
    (gdb) p *('Test::A'*)pObj2
    $5 = {
       test ::Base = {
        m_nNum = 10
      }, 
      members of Test::A: 
      m_bCh = 65 'A'
    }
    通过上面一步步的介绍,相信读者朋友对GDB中结构类型的强制类型转换已经有了较为深入的了解,这里特别注意的就是第4步中提到的带namespace的结构类型,其它的跟C++基本中的类型的强制转换是一致的。

    http://www.wuzesheng.com/?p=694

     

    介绍几个关于C/C++程序调试的函数

    最近调试程序学到的几个挺有用的函数,分享一下,希望对用C/C++的朋友有所帮助!

    1. 调用栈系列
    下面是函数原型:

    #include "execinfo .h"
    int backtrace(void **buffer, int size);
    char **backtrace_symbols(void *const *buffer, int size);
    void backtrace_symbols_fd(void *const *buffer, int size, int fd);
    接下来,对上面三个函数进行介绍:
    (1)backtrace用来获得当前程序的调用栈,把结果存在buffer中。通常,我们用gdb调试程序,设置合适的断点,停下来之后,用backtrace(bt)命令,就可以看到当前的调用栈。但是,有的时候,用到条件断点的时候,gdb的功能就没有程序本身的强大了,这个时候,可以考虑在程序中调用backtrace函数,来获取调用栈。
    (2)backtrace_symbols把用backtrace获取的调用栈转换成字符串数组,以字符串数组的形式返回,使用者需要在外面释放返回的字符串数组所占用的内存
    (3)backtrace_symbols_fd把用backtrace获取的调用栈信息写到fd所指定的文件中
    void * __builtin_return_address (unsigned int level)
    这个函数用来得到当前函数,或者调用它的函数的返回地址,得到这个地址后,通过gdb反汇编,便可得到调用函数相关的信息,这也是在应用中获取调用栈的一种方法。

    2. 内存分配、释放系列

    #include "malloc .h"
    size_t malloc_usable_size((void *__ptr));
    这个函数的用法是返回调用malloc后实际分配的可用内存的大小。我们都知道在C++中,operator new()可以重载各种各样的版本,可以传入调用时的相关信息来跟踪内存分配情况,但是operator delete()却只有两种形式,不能随意重载,尤其是全局的operator delete(),只有一种版本,这个时候就比较痛苦了,究竟释放了多少内存呢? 这时候malloc_usable_size()这个函数就有用武之地了,调用它便可以获取当前释放的内存的大小。这里需要注意的是,如果在delete中用了malloc_usable_size来计算释放的内存大小,那么在new中也请用它来统计开辟的内存,这样才能对应起来。因为在调用malloc时,很多时候实际分配的内存会比用户申请的要大一些,所以如果两边的统计方法对应不起来的话,统计结果也会有比较大的判别。
    这里关于new/delete重载的一些细节我不做说明,之前我写过一篇文章的,不明白的朋友可以去看一下这篇文章《 细说C++中的new与delete》。

     

    http://www.wuzesheng.com/?p=1123

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值