• gdb-1使用初步


    以前我用 VS2010 做开发,今年转到服务器开发。刚做的时候感觉不适应,后来慢慢就习惯了。不管是你说的大项目还是一般的项目(大项目也是有小模块堆积而成),平时用的基本上不会特别复杂,最多也不过线程堆栈切换而已。
    shell+vim+git+find+grep+收log,就这么几板斧,几十人维护数百万行code妥妥的。
    逻辑错误用log,内存错误用gdb,单元测试用gtest,编译器用clang,log框架用log4cplus,性能热点用gprof,这样就没有搞不定的bug
    补充一条,内存错误用valgrind,但我一直觉得习惯良好的C++代码永远不会犯内存错误

    gdb基本调试功能(内部原理是ptrace)
    http://rryy.org/reference/manuals/GDB%20Manual.htm
                   D    uninterruptible sleep (usually IO)
                   R    running or runnable (on run queue)
                   S    interruptible sleep (waiting for an event to complete)
                   T    stopped by job control signal
                   t    stopped by debugger during the tracing
                   W    paging (not valid since the 2.6.xx kernel)
                   X    dead (should never be seen)
                   Z    defunct ("zombie") process, terminated but not reaped by its parent
    启动后,暂停执行
    当前代码
    l
    set listsize <count>设置代码显示行数
    单步执行不陷入
    n
    单步执行陷入
    step <count>
    函数执行结束
    f finish
    打印符号
    p
    堆栈
    bt
    继续执行
    c
    执行shell命令
    !ls
    设置断点
    break或b
    使用make,cd, pwd
    make
    删除断点
    d breakpoints 6-7
    c [ignore-count] 继续运行,忽略后面的断点个数
    将循环执行完毕
    until 或 u
    bt --- 查看堆栈
    bt n栈顶上n层信息(最近执行的函数) bt -n栈底上n层(最远执行的函数)
    frame n或 f n---切换帧
    info frame
    info args
    打印出当前函数的参数名及其值。
    info locals
    打印出当前函数中所有局部变量及其值。
    info catch
    打印出当前的函数中的异常处理信息。
    p $1,打印被标记位1号的变量
    info register
    寄存器中放置了程序运行时的数据,比如程序当前运行的指令地址(ip),程序的当前堆栈地址(sp)等等。你同样可以使用print命令来访问寄存器的情况,只需要在寄存器名字前加一个$符号就可以了。如:p $eip。
    改变变量的值(gdb) set var width=47
    跳转执行 jump <linespec>,或者jump <address>,set $pc = 0x485
    产生信号量---语法是:signal <singal>,UNIX的系统信号量通常从1到15。所以<singal>取值也在这个范围。
    强制函数返回---return return <expression>
    强制调用函数---call <expr> print后面可以跟表达式
    info program-查看当前程序执行状态

    在gdb中,我们可以有以下几种暂停方式:断点(BreakPoint)、观察点(WatchPoint)、捕捉点(CatchPoint)、信号(Signals)、线程停止(Thread Stops)。如果要恢复程序运行,可以使用c或是continue命令。
    观察点一般来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有变化,马上停住程序。我们有下面的几种方法来设置观察点:
    你可设置捕捉点来补捉程序运行时的一些事件。如:载入共享库(动态链接库)或是C++的异常。设置捕捉点的格式为:
    catch <event>
    当event发生时,停住程序。event可以是下面的内容:
    1、throw 一个C++抛出的异常。(throw为关键字)
    2、catch 一个C++捕捉到的异常。(catch为关键字)
    3、exec 调用系统调用exec时。(exec为关键字,目前此功能只在HP-UX下有用)
    4、fork 调用系统调用fork时。(fork为关键字,目前此功能只在HP-UX下有用)
    5、vfork 调用系统调用vfork时。(vfork为关键字,目前此功能只在HP-UX下有用)
    6、load 或 load <libname> 载入共享库(动态链接库)时。(load为关键字,目前此功能只在HP-UX下有用)
    7、unload 或 unload <libname> 卸载共享库(动态链接库)时。(unload为关键字,目前此功能只在HP-UX下有用)

    四、维护停止点
    上面说了如何设置程序的停止点,GDB中的停止点也就是上述的三类。在GDB中,如果你觉得已定义好的停止点没有用了,你可以使用delete、clear、disable、enable这几个命令来进行维护。
    clear
    清除所有的已定义的停止点。
    clear <function>
    clear <filename:function>
    清除所有设置在函数上的停止点。
    clear <linenum>
    clear <filename:linenum>
    清除所有设置在指定行上的停止点。
    delete [breakpoints] [range...]
    删除指定的断点,breakpoints为断点号。如果不指定断点号,则表示删除所有的断点。range 表示断点号的范围(如:3-7)。其简写命令为d。

    八、恢复程序运行和单步调试

    当程序被停住了,你可以用continue命令恢复程序的运行直到程序结束,或下一个断点到来。也可以使用step或next命令单步跟踪程序。
    continue [ignore-count]
    c [ignore-count]
    fg [ignore-count]
    恢复程序运行,直到程序结束,或是下一个断点到来。ignore-count表示忽略其后的断点次数。continue,c,fg三个命令都是一样的意思。
    step <count>
    单步跟踪,如果有函数调用,他会进入该函数。进入函数的前提是,此函数被编译有debug信息。很像VC等工具中的step in。后面可以加count也可以不加,不加表示一条条地执行,加表示执行后面的count条指令,然后再停住。
    next <count>
    同样单步跟踪,如果有函数调用,他不会进入该函数。很像VC等工具中的step over。后面可以加count也可以不加,不加表示一条条地执行,加表示执行后面的count条指令,然后再停住。

    set step-mode
    set step-mode on
    打开step-mode模式,于是,在进行单步跟踪时,程序不会因为没有debug信息而不停住。这个参数有很利于查看机器码。

    backtrace <n>
    bt <n> 
    n是一个正整数,表示只打印栈顶上n层的栈信息。
    backtrace <-n> 
    bt <-n> 
    -n表一个负整数,表示只打印栈底下n层的栈信息。
    如果你要查看某一层的信息,你需要在切换当前的栈,一般来说,程序停止时,最顶层的栈就是当前栈,如果你要查看栈下面层的详细信息,首先要做的是切换当前栈。
    frame <n> 
    f <n> 
    n是一个从0开始的整数,是栈中的层编号。比如:frame 0,表示栈顶,frame 1,表示栈的第二层。
    up <n>
    表示向栈的上面移动n层,可以不打n,表示向上移动一层。 
    down <n> 
    表示向栈的下面移动n层,可以不打n,表示向下移动一层。 
    上面的命令,都会打印出移动到的栈层的信息。如果你不想让其打出信息。你可以使用这三个命令:

    四、源代码的内存
    你可以使用info line命令来查看源代码在内存中的地址。info line后面可以跟“行号”,“函数名”,“文件名:行号”,“文件名:函数名”,这个命令会打印出所指定的源码在运行时的内存地址,如:
    (gdb) info line tst.c:func
    Line 5 of "tst.c" starts at address 0x8048456 <func+6> and ends at 0x804845d <func+13>.
    还有一个命令(disassemble)你可以查看源程序的当前执行时的机器码,这个命令会把目前内存中的指令dump出来。如下面的示例表示查看函数func的汇编代码。
    (gdb) disassemble func

    查看程序中的变量
    在表达式中,有几种GDB所支持的操作符,它们可以用在任何一种语言中。
    @
    是一个和数组有关的操作符,在后面会有更详细的说明。
    ::
    指定一个在文件或是一个函数中的变量。
    {<type>} <addr>
    表示一个指向内存地址<addr>的类型为type的一个对象。
    在GDB中,你可以随时查看以下三种变量的值:
    1、全局变量(所有文件可见的)
    2、静态全局变量(当前文件可见的)
    3、局部变量(当前Scope可见的)
    如果你的局部变量和全局变量发生冲突(也就是重名),一般情况下是局部变量会隐藏全局变量,也就是说,如果一个全局变量和一个函数中的局部变量同名时,如果当前停止点在函数中,用print显示出的变量的值会是函数中的局部变量的值。如果此时你想查看全局变量的值时,你可以使用“::”操作符:
    file::variable
    function::variable
    可以通过这种形式指定你所想查看的变量,是哪个文件中的或是哪个函数中的。例如,查看文件f2.c中的全局变量x的值:
    gdb) p 'f2.c'::x
    当然,“::”操作符会和C++中的发生冲突,GDB能自动识别“::” 是否C++的操作符,所以你不必担心在调试C++程序时会出现异常。
    另外,需要注意的是,如果你的程序编译时开启了优化选项,那么在用GDB调试被优化过的程序时,可能会发生某些变量不能访问,或是取值错误码的情况。这个是很正常的,因为优化程序会删改你的程序,整理你程序的语句顺序,剔除一些无意义的变量等,所以在GDB调试这种程序时,运行时的指令和你所编写指令就有不一样,也就会出现你所想象不到的结果。对付这种情况时,需要在编译程序时关闭编译优化。一般来说,几乎所有的编译器都支持编译优化的开关,例如,GNU的C/C++编译器GCC,你可以使用“-gstabs”选项来解决这个问题。关于编译器的参数,还请查看编译器的使用说明文档。

    改变程序的执行
    一、修改变量值
    修改被调试程序运行时的变量值,在GDB中很容易实现,使用GDB的print命令即可完成。如:
    (gdb) print x=4
    x=4这个表达式是C/C++的语法,意为把变量x的值修改为4,如果你当前调试的语言是Pascal,那么你可以使用Pascal的语法:x:=4。
    在某些时候,很有可能你的变量和GDB中的参数冲突,如:
    (gdb) whatis width
    type = double
    (gdb) p width
    $4 = 13
    (gdb) set width=47
    Invalid syntax in expression.
    因为,set width是GDB的命令,所以,出现了“Invalid syntax in expression”的设置错误,此时,你可以使用set var命令来告诉GDB,width不是你GDB的参数,而是程序的变量名,如:
    (gdb) set var width=47
    另外,还可能有些情况,GDB并不报告这种错误,所以保险起见,在你改变程序变量取值时,最好都使用set var格式的GDB命令。

  • 相关阅读:
    gTest&gMock learning
    机器学习 delay learning
    c++ learning
    2017 湘潭邀请赛&JSCPC G&J
    mapreduce&GFS&bigtable learning
    golang learning
    高斩仙的北京历险记
    python learning
    Codeforces Round #448 (Div. 2) B
    python之callable
  • 原文地址:https://www.cnblogs.com/lizhensheng/p/11117228.html
Copyright © 2020-2023  润新知