gdb 相关调试技巧整理 收藏

gdb 相关调试技巧整理 收藏
启用gdb的方法种有2种,一种是启动core,还有是attach一个已经运行的进程

1.gdb core

       用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件。

2.gdb

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

 

wathc的几种变种

watch

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

       

    rwatch

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

       

    awatch

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

   

    info watchpoints

        列出当前所设置了的所有观察点。

 

暂时不要一个断点,并不一定需要删除他

 

可以disable他,以后要用时在enable

 

disable [breakpoints] [range...]

        disable所指定的停止点,breakpoints为停止点号。如果什么都不指定,表示disable所有的停止点。简写命令是dis.

 

    enable [breakpoints] [range...]

        enable所指定的停止点,breakpoints为停止点号。

 

可以为停止点设定运行命令

 

commands [bnum]

    ... command-list ...

    end

    为断点号bnum指写一个命令列表。当程序被该断点停住时,gdb会依次运行命令列表中的命令。

 

    例如:

 

        break foo if x>0

        commands

        printf "x is %d/n",x

        continue

        end

        断点设置在函数foo中,断点条件是x>0,如果程序被断住后,也就是,一旦x的值在foo函数中大于0,GDB会自动打印出x的值,并继续运行程序。

 

until 或 u

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

 

如果你程序是多线程的话,你可以定义你的断点是否在所有的线程上,或是在某个特定的线程。GDB很容易帮你完成这一工作。

 

    break thread

    break thread if ...

        linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,注意,这个ID是GDB分配的,你可以通过“info threads”命令来查看正在运行程序中的线程信息。如果你不指定thread 则表示你的断点设在所有线程上面。你还可以为某线程指定断点条件。如:

   

        (gdb) break frik.c:13 thread 28 if bartab > lim

 

    当你的程序被GDB停住时,所有的运行线程都会被停住。这方便你你查看运行程序的总体情况。而在你恢复程序运行时,所有的线程也会被恢复运行。那怕是主进程在被单步调试时。

 

info还可以表示的信息是

 

info args

        打印出当前函数的参数名及其值。

    

     info locals

        打印出当前函数中所有局部变量及其值。

       

     info catch

        打印出当前的函数中的异常处理信息。

 

list还可以显示具体行的范围

 

list命令还有下面的用法:

 

    list ,

        显示从first行到last行之间的源代码。

   

    list ,

        显示从当前行到last行之间的源代码。

       

    list +

        往后显示源代码。

 

搜索源代码

不仅如此,GDB还提供了源代码搜索的命令:

 

    forward-search

    search

        向前面搜索。

 

    reverse-search

        全部搜索。

       

其中,就是正则表达式,也主一个字符串的匹配模式,关于正则表达式,我就不在这里讲了,还请各位查看相关资料。

 

有时候,你需要查看一段连续的内存空间的值。比如数组的一段,或是动态分配的数据的大小。你可以使用GDB的“@”操作符,“@”的左边是第一个内存的地址的值,“@”的右边则你你想查看内存的长度。例如,你的程序中有这样的语句:

    

        int *array = (int *) malloc (len * sizeof (int));

       

    于是,在GDB调试过程中,你可以以如下命令显示出这个动态数组的取值:

        p *array@len

 

如果是静态数组的话,可以直接用print数组名,就可以显示数组中所有数据的内容了。

 

你可以使用examine命令(简写是x)来查看内存地址中的值。x命令的语法如下所示:

   

    x/

   

    n、f、u是可选的参数。

   

    n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。

    f 表示显示的格式,参见上面。如果地址所指的是字符串,那么格式可以是s,如果地十是指令地址,那么格式可以是i。

    u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4个bytes。u参数可以用下面的字符来代替,b表示单字节,h表示双字节,w表示四字节,g表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。

   

    表示一个内存地址。

 

    n/f/u三个参数可以一起使用。例如:

   

    命令:x/3uh 0x54320 表示,从内存地址0x54320读取内容,h表示以双字节为一个单位,3表示三个单位,u表示按十六进制显示。

 

set print null-stop

        如果打开了这个选项,那么当显示字符串时,遇到结束符则停止显示。这个选项默认为off。

       

    set print pretty on

        如果打开printf pretty这个选项,那么当GDB显示结构体时会比较漂亮。如:

 

            $1 = {

              next = 0x0,

              flags = {

                sweet = 1,

                sour = 1

              },

              meat = 0x54 "Pork"

            }

 

    set print pretty off

        关闭printf pretty这个选项,GDB显示结构体时会如下显示:

       

            $1 = {next = 0x0, flags = {sweet = 1, sour = 1}, meat = 0x54 "Pork"}

           

show print pretty

查看GDB是如何显示结构体的。

 

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

堆栈跟踪

程序“调用堆栈”是当前函数之前的所有已调用函数的列表(包括当前函数)。每个函数及其变量都被分配了一个“帧”,最近调用的函数在 0 号帧中(“底部”帧)。

backtrace / bt:要打印堆栈,发出命令 'bt'('backtrace' [回溯] 的缩写)。在显示帧信息的最后的行号表示了被调用的函数所在行,可以用list+行号的方式查看。

例如:
------------------------------------------------------------------------
(gdb) bt
#0 0x80483ea in wib (no1=8, no2=8) at eg1.c:7
#1 0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21
------------------------------------------------------------------------

此结果显示了在 main() 的第 21 行中调用了函数 wib()(只要使用 'list 21' 就能证实这一点),而且 wib() 在 0 号帧中,main() 在 1 号帧中。由于 wib() 在 0 号帧中,那么它就是执行程序时发生算术错误的函数。
实际上,发出 'info locals' 命令时,gdb 会打印出当前帧中的局部变量,缺省情况下,这个帧中的函数就是被中断的函数(0 号帧)。可以使用命令 'frame' 打印当前帧。要查看 main 函数(在 1 号帧中)中的变量,可以发出 'frame 1' 切换到 1 号帧,然后发出 'info locals' 命令查看。

frame:打印当前帧信息,'frame 帧号'切换到相应的帧。用法见上例。

up, down:可以通过如上所示在 'frame' 命令中明确指定号码,或者使用 'up' 命令在堆栈中上移以及 'down' 命令在堆栈中下移来切换帧。要获取有关帧的进一步信息,如它的地址和程序语言,可以使用命令 'info frame'。

core 文件

无法dump core文件的原因:

使用 ulimit -c 查看shell对core文件的限制,单位为块(512b)。如果为0,则表示系统关闭了dump core。可以通过ulimit -c unlimited来打开。

若发生了段错误,但没有core dump,是由于系统禁止core文件的生成!
$ulimit -c  ,若显示为0,则系统禁止了core dump

解决方法:
$ulimit -c unlimited  (只对当前shell进程有效)
或在~/.bashrc 的最后加入: ulimit -c unlimited (一劳永逸)

加载core文件:要使用 core 文件启动 gdb,在 shell 中发出命令 'gdb eg1 core' 或 'gdb eg1 -c core'。

加载后,可以发出 'info locals'、'print'、'info args' 和 'list' 命令来查看调试信息。'info variables' 命令将打印出所有程序变量的值,但这要进行很长时间,因为 gdb 将打印 C 库和程序代码中的变量。为了更容易地查明在调用 wib() 的函数中发生了什么情况,可以使用 gdb 的堆栈命令。

gdb连接到其它进程

除了调试 core 文件或程序之外,gdb 还可以连接到已经运行的进程(它的程序已经过编译,并加入了调试信息),并中断该进程。只需用希望 gdb 连接的进程标识替换 core 文件名就可以执行此操作。

以下是一个执行循环并睡眠的 示例程序:

eg2 示例代码
------------------------------------------------------------------------
#include
int main(int argc, char *argv[])
{
int i;
for(i = 0; i < 60; i++)
{
sleep(1);
}
return 0;
}
------------------------------------------------------------------------
使用 'gcc -g eg2.c -o eg2' 编译该程序并使用 './eg2 &' 运行该程序。请留意在启动该程序时在背景上打印的进程标识,在本例中是 1283:
------------------------------------------------------------------------
./eg2 &
[3] 1283
------------------------------------------------------------------------

连接到进程:'gdb 被调试文件 -c 进程号' 也可以不要 -c。
启动 gdb 并指定进程标识,在我举的这个例子中是 'gdb eg2 1283'。gdb 会查找一个叫作 "1283" 的 core 文件。如果没有找到,那么只要进程 1283 正在运行(在本例中可能在 sleep() 中),gdb 就会连接并中断该进程:
------------------------------------------------------------------------
...
/home/seager/gdb/1283: No such file or directory.
Attaching to program: /home/seager/gdb/eg2, Pid 1283
...
0x400a87f1 in __libc_nanosleep () from /lib/libc.so.6
(gdb)
------------------------------------------------------------------------
此时,可以发出所有常用 gdb 命令。可以使用 'backtrace' 来查看当前位置与 main() 的相对关系,以及 mian() 的帧号是什么,然后切换到 main() 所在的帧,查看已经在 "for" 循环中运行了多少次:
------------------------------------------------------------------------
(gdb) backtrace
#0 0x400a87f1 in __libc_nanosleep () from /lib/libc.so.6
#1 0x400a877d in __sleep (seconds=1) at ../sysdeps/unix/sysv/linux/sleep.c:78
#2 0x80483ef in main (argc=1, argv=0xbffff9c4) at eg2.c:7
(gdb) frame 2
#2 0x80483ef in main (argc=1, argv=0xbffff9c4) at eg2.c:7
7 sleep(1);
(gdb) print i
$1 = 50
------------------------------------------------------------------------
detach / kill:输入'detach' or 'kill',不需要进程号。
如果已经完成了对程序的修改,可以 'detach' 命令继续执行程序,或者 'kill' 命令杀死进程。
attach:先输入'file eg2',然后输入'attach 1283'
还可以首先使用 'file eg2' 装入文件,然后发出 'attach 1283' 命令连接到进程标识 1283 下的 eg2。

其它小技巧
shell:输入'shell'可以打开一个新的shell,或使用'shell [commandline]'在当前的shell中运行命令。
gdb 可以让您通过使用 shell 命令在不退出调试环境的情况下运行 shell 命令,调用形式是 'shell [commandline]',这有助于在调试时更改源代码。


set:命令修改变量的值,'set 变量=值'
最后,在程序运行时,可以使用 'set ' 命令修改变量的值。在 gdb 下再次运行 eg1,使用命令 'break 7 if diff==0' 在第 7 行(将在此处计算结果)设置条件断点,然后运行程序。当 gdb 中断执行时,可以将 "diff" 设置成非零值,使程序继续运行直至结束:
------------------------------------------------------------------------
Breakpoint 1, wib (no1=8, no2=8) at eg1.c:7
7 result = no1 / diff;
(gdb) print diff
$1 = 0
(gdb) set diff=1
(gdb) continue
Continuing.
0 wibed by 16 equals 10
Program exited normally.

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

编写服务器端程序,很容易遇到Crash问题,比较幸运的是Linux提供了core file,保留了Crash的现场。有时候,根据当前的调用栈,并且打印出当前栈的变量就可以分析出crash的原因,但是,有时候看到调用栈却束手无策。下面就介绍自己通过GDB的几个命令的结合,发现一个crash的原因的过程。
下面让我们一起进入现场,来逐步发现其中的原因。
首先,还是运行gdb 命令,gdb wbxgs core.5797,来看看现场。
[root@hfgs126 bin]# gdb wbxgs_crash core.5797
GNU gdb Red Hat Linux (6.3.0.0-1.132.EL4rh)
……
#0 0x00000038e8d70540 in strlen () from /lib64/tls/libc.so.6
(gdb) bt
#0 0x00000038e8d70540 in strlen () from /lib64/tls/libc.so.6
#1 0x000000000057cfc0 in T120_Trace::Text_Formator::advance (this=0x7e800a70, lpsz=0x1

)
    at ./t120trace.cpp:1464
#2 0x000000000057ceb1 in T120_Trace::Text_Formator::operator<< (this=0x7e800a70, lpsz=0x1
)
    at ./t120trace.cpp:1411
#3 0x0000000000407927 in ~func_tracer (this=0x7e804bd0) at ../h/t120trace.h:381
#4 0x00000000004432fd in CGSSocketServer::readHeader (this=0x8e4130, socketfd=1088,
    buf=0x7e806cc0 "GET /detectService?cmd=selfcheck HTTP/1.1/r/nConnection: Close/r/nHost: 10.224.122.94/r/n/r/n", bufsize=1024)
    at mgr/gssocketserver.cpp:337
#5 0x0000000000443981 in CGSSocketServer::handle (this=0x8e4130, socketfd=1088, strRet=@0x7e807190) at mgr/gssocketserver.cpp:424
#6 0x0000000000442f5e in CGSSocketServer::readThread (pArg=0x9ae9c0) at mgr/gssocketserver.cpp:304
#7 0x00000038e980610a in start_thread () from /lib64/tls/libpthread.so.0
#8 0x00000038e8dc68b3 in clone () from /lib64/tls/libc.so.6
#9 0x0000000000000000 in ?? ()
通过这个调用栈,可以看出,程序crash在打log的时候。虽然遇到过类似的crash,但是,当时的原因是有死循环,通过review code,没有发现死循环。但是当前的调用栈对于分析Crash的原因是一点用也没有,如果分析具体的原因呢?会不会是其他得线程出现错误导致程序Crash在这个线程呢?为了找到深一层的原因,尝试着通过GDB的一些关于线程的命令,来看看其他的线程是否有问题。于是,使用info threads,查看了一下当时线程的情况。
(gdb) info threads
21 process 5797 0x00000038e8d7186d in memset () from /lib64/tls/libc.so.6
20 process 5839 0x00000038e8dc6c8c in epoll_wait () from /lib64/tls/libc.so.6
19 process 5842 0x00000038e8d8f7d5 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
18 process 5845 0x00000038e8d8f7d5 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
17 process 5846 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
16 process 5847 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
15 process 5848 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
14 process 5849 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
13 process 5850 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
12 process 5852 0x00000038e8dbf946 in __select_nocancel () from /lib64/tls/libc.so.6
11 process 5854 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
10 process 5856 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
9 process 5857 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
8 process 5858 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
7 process 5859 0x00000038e8d8f7d5 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
6 process 5861 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
5 process 5862 0x00000038e980a66f in sem_wait () from /lib64/tls/libpthread.so.0
4 process 5863 0x00000038e8d8f7d5 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
3 process 5864 0x00000038e8d8f7d5 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
2 process 5883 0x00000038e8d8f7d5 in __nanosleep_nocancel () from /lib64/tls/libc.so.6
* 1 process 5853 0x00000038e8d70540 in strlen () from /lib64/tls/libc.so.6
对于线程如果停止在sleep或者wait的情况,都是正常的,但是我们看到thread 21有些异常,程序停止在memset,不管是否有问题,都需要看看这样的线程具体有没有出错。
于是通过命令thread 21,进入到thread 21的调用栈。
(gdb) thread 21
[Switching to thread 21 (process 5797)]#0 0x00000038e8d7186d in memset () from /lib64/tls/libc.so.6
(gdb) bt
#0 0x00000038e8d7186d in memset () from /lib64/tls/libc.so.6
#1 0x000000000049da0d in CGSPduFactory::streamStringFrom (is=@0x7fff9b436360, strFrom=@0x2aaaec979760) at common/pdu/gspdu.cpp:422
#2 0x00000000004d1f25 in CGSOthShardUserRspPdu::streamFrom (this=0x2aaaec951650, is=@0x7fff9b436360) at common/pdu/pdugs.cpp:2707
#3 0x000000000049cb2d in CGSPduFactory::derivePdu (is=@0x7fff9b436360, ulPDULen=30506) at common/pdu/gspdu.cpp:79
#4 0x000000000049c78e in CGSPduFactory::streamPduFrom (pDataPacket=0x2aaaeca31d70) at common/pdu/gspdu.cpp:35
#5 0x0000000000449681 in CGSWDMSManager::on_wdms_message_indication (this=0x8e3680, msg=0x2aaae9894360)
    at mgr/gswdmsmanager.cpp:344
……
#18 0x0000000000407733 in main (argc=1, argv=0x7fff9b44ac98) at gsmain.cpp:118
(gdb) f 3
#3 0x000000000049cb2d in CGSPduFactory::derivePdu (is=@0x7fff9b436360, ulPDULen=30506) at common/pdu/gspdu.cpp:79
79      common/pdu/gspdu.cpp: No such file or directory.
        in common/pdu/gspdu.cpp
使用命令 i locals,打印所有的变量的值。
(gdb) i locals
pPdu = (CBasePdu *) 0x2aaaec951650
pPduHeader = (CPduHeader *) 0x2aaaea1c4190
ulPduType = 50
到现在还没有看出有什么明显的异常,然后再把PDU的头打印出来如下:
(gdb) p *pPduHeader
$1 = {m_ulHeadLen = 61, m_ulVersion = 2080000, m_ulPduType = 50, m_ulSrcSvrType = WEBEX_CONNECT_GS, m_strSrcSvrAddr = {
    static npos = 18446744073709551615,
    _M_dataplus = {> = {<__gnu_cxx::new_allocator> = {}, },
      _M_p = 0x2aaaeca52a68 "10.224.95.109:9900"}}, m_strSubject = {static npos = 18446744073709551615,
    _M_dataplus = {> = {<__gnu_cxx::new_allocator> = {}, },
      _M_p = 0x2aaaec929b28 "qawin.qazone.GS"}}, m_ulSequence = 0}
从蓝色的字的部分可以看出,这个PDU是从10.224.95.109这台server上发过来的。
当时QA测试的环境,都是10.224.122开头的IP的server,怎么会有这个IP的PDU,于是,询问QA,发现10.224.95.109这个server是其他DataCenter的Server,而且还是老的版本,由于当前测试环境的版本删除了两个PDU,同时又增加了四个PDU,导致了老的PDU发来的时候,新的版本的把它当作新的PUD解析,从而导致不能正确解析,最终导致了解析出来的长度不对。可以通过f 1命令进入第一级调用栈查看所有的局部变量。
(gdb) f 1
#1 0x000000000049da0d in CGSPduFactory::streamStringFrom (is=@0x7fff9b436360, strFrom=@0x2aaaec979760) at common/pdu/gspdu.cpp:422
422                  in common/pdu/gspdu.cpp
(gdb) i locals
strTmp = 0x2aaaf1c00010 ""
iRet = 0
ulLen = 1179995975
可以看出解析出来的长度是一个很大的值1179995975,而线程21正式停止在分配内存之后,使用memset时,停止在那里。从Log中也可以看到,thread 21也一致阻塞在这里,而且没有再继续运行。
由于当时有两台server crash,通过查看另外一台server的core file,发现另外一台server也是和本台server一样的调用栈。在QA更新了10.224.95.109的版本后,crash没有再出现。
通过这个实例,可以看出,当server出现crash的时候,虽然当前的调用栈可能没有什么价值,但是,通过分析所有线程的调用栈,还是可能分析出蛛丝马迹的,从而对于解决Crash的问题带来帮助。
通过这个问题可以得到一个教训,在修改Server之间的接口时,一定要考虑到和老版本的兼容问题,即使这个PDU可能永远也不会使用,仍然需要保留,因为Production上,是先上GSB,然后再上Primary,肯定会存在两个版本同时运行的情况。如果出现删除或者改变PDU顺序的情况,可能会导致整个系统不能工作。
希望本文章,对解决Crash问题和避免类似的Crash问题有一定的借鉴作用。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/hiproz/archive/2010/07/11/5727197.aspx

你可能感兴趣的:(GDB)