Linux调试神器 -- gdb

时间:2021-08-04 15:31:25

(0)目录

VMware 下安装Ubuntu的吐血经历

零基础学习Shell编程

Linux下的makefile的妙用

Linux调试神器 -- gdb

十分钟学会Python的基本类型

Linux 静态链接库和动态连接库

一:起因

(1)也许我们非常熟悉Windows下的VC6.0 和 CodeBlocks的调试工具 —— 界面化的调试,但是你是否想过你的每一个按键或者快捷键的背后指令是什么,让我们一起走进Linux的gcc动态调试工具GDB

(2)程序调试无非就是:debug(gcc -g -o target source);设置断点(b n);观察变脸(info locals);观察特定值(print expr);执行(start);执行下一行(next);进入子函数(step);执行到当前函数结尾(finish);查看源代码(list)等等操作

(3)gcc 的过程:C程序的编辑 用到vim;  程序的编译和运行用到gcc;  程序的调试用到gdb (kdbg是可视化的调试工具)

二:实例讲解

(1)调试步骤分为两步

   1)编译时一定到 加上 -g 产生调试信息,让调试信息包含在可执行文件中exe —— 命令行下:

  gcc -g -o target source

   2)把可执行文件exe加载到调试环境中:   

 gdb target

   3) gdb为调试命令提示符,即可键入调试命令

(2)调试常用命令

   1)基本调试命令(重要)

        list 行号:列出产品从第几行开始的源代码;   list 函数名:列出某个函数的源代码

start:开始执行程序,停在main函数第一行语句前面等待命令;  next(n):执行下一列语句

step(s):执行下一行语句,如果有函数调用则进入到函数中  ;breaktrace(或bt):查看各级函数调用及参数

frame(f) 帧编号:选择栈帧 ;  info(i) locals:查看当前栈帧局部变量的值

finish:执行到当前函数返回,然后挺下来等待命令  ; print(p):打印表达式的值,通过表达式可以修改变量的值或者调用函数

set var:修改变量的值  ;quit:退出gdb

   2)高级调试命令

      break(b) 行号:在某一行设置断点 ;break 函数名:在某个函数开头设置断点 ;  break...if...:设置条件断点

continue(或c):从当前位置开始连续而非单步执行程序  ;  delete breakpoints:删除所有断点  ;delete breakpoints n:删除序号为n的断点

disable breakpoints:禁用断点 ;  enable breakpoints:启用断点

info(或i) breakpoints:参看当前设置了哪些断点; ;   run(或r):从开始连续而非单步执行程序

display 变量名:跟踪查看一个变量,每次停下来都显示它的值  ;undisplay:取消对先前设置的那些变量的跟踪

  3)格式化输入查看变量 print(p)

输出格式:一般来说,GDB会根据变量的类型输出变量的值。但你也可以自定义GDB的输出的格式。
例如,你想输出一个整数的十六进制,或是二进制来查看这个整型变量的中的位的情况。要
做到这样,你可以使用GDB的数据显示格式:
 

x 按十六进制格式显示变量。                      d 按十进制格式显示变量。 

u 按十六进制格式显示无符号整型。         o 按八进制格式显示变量。 

t 按二进制格式显示变量。                           a 按十六进制格式显示变量。 

c 按字符格式显示变量。                               f 按浮点数格式显示变量。

三,启动GDB的三种方法:

1gdb <program>       

program也就是你的执行文件,一般在当然目录下。

2gdb<program> core    

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

3gdb<program> <PID>       

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

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

   -symbols <file> 
    -s <file> 
    从指定文件中读取符号表。

   -se file 
    从指定文件中读取符号表信息,并把他用在可执行文件中。

   -core <file>
    -c <file> 

    调试时core dumpcore文件。

   -directory <directory>
    -d <directory>
   
加入一个源文件的搜索路径。默认搜索路径是环境变量中 PATH 所定义的路径。

四,gdb <program> <PID>  VS gdb attach 实例 (转载)多进程调试

 gdb调试正在运行的进程:有时会遇到一种很特殊的调试需求,对当前正在运行的其它进程进行调试(正是我今天遇到的情形)。这种情况有可能发生在那些无法直接在调试器中运行的进程身上,例如有的进程只能在系统启动时运行。另外如果需要对进程产生的子进程进行调试的话,也只能采用这种方式。GDB可以对正在执行的程序进行调度,它允许开发人员中断程序并查看其状态,之后还能让这个程序正常地继续执行。

    GDB提供了两种方式来调试正在运行的进程:一种是在GDB命令行上指定进程的PID,另一种是在GDB中使用“attach”命令。例如,开发人员可以先启动debugme程序,让其开始等待用户的输入。示例如下:

#./debugme

           Enter a string to count words: 

     接下去在另一个虚拟控制台中用下面的命令查出该进程对应的进程号:

# ps -ax | grep debugme

           555 pts/1 S 0:00 ./debugme

     得到进程的PID后,就可以使用GDB对其进行调试了:

方法一:# gdb debugme 555  (提倡用这个)

           GNU gdb Red Hat Linux(5.3post-0.20021129.18rh)

           Attaching to program: /home/xiaowp/debugme, process 555

           Reading symbols from /lib/libc.so.6...done.

           ……

     在上面的输出信息中,以Attachingto program开始的行表明GDB已经成功地附加在PID为555的进程上了。


方法二:另外一种连接到其它进程的方法是先用file命令加载调试时所需的符号表,然后再通过“attaché”命令进行连接: 

(gdb) file /home/xiaowp/debugme

           Reading symbols from /home/xiaowp/debugme...done.

           (gdb) attach 555

           ……

(gdb) bWXBaseRecommend::HWGetRecommendByCategory

 

Program received signal SIGINT, Interrupt.

0x000000323140b5bc in pthread_cond_wait@@GLIBC_2.3.2() from /lib64/libpthread.so.0

(gdb) c

 

调试结束后:

1,如果想知道程序现在运行到了哪里,同样可以使用“backtrace”命令。当然也可以使用“step”命令对程序进行单步调试。

2,在完成调试之后,不要忘记用detach命令断开连接,让被调试的进程可以继续正常运行。


五、gdb core文件函数出现问号

1,例子:

gdb ./novrecom_daemon ./core.7453 
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-64.el6_5.2)

[New Thread 13817]
Core was generated by `/search/odin/guanjian/RecSystem/RerankServer/.libs/lt-novrecom_daemon -d -k Sum'.
Program terminated with signal 11, Segmentation fault.
#0  0x0000003fe3e9d25b in ?? ()
(gdb) set solib-absolute-prefix "library path"
Missing separate debuginfo for 
Try: yum --disablerepo='*' --enablerepo='*-debug*' install /usr/lib/debug/.build-id/54/74f0d8daf3d6177e2c4b06f3892745cb43b4d5
Missing separate debuginfo for /search/odin/guanjian/RecSystem/ssplatform-encoding/_lib/.libs/libencoding-data.so.4
Try: yum --disablerepo='*' --enablerepo='*-debug*' install /usr/lib/debug/.build-id/6a/1023b0dff7010bba12ba6228d352145da27d6f
Missing separate debuginfo for /search/odin/guanjian/RecSystem/_lib/.libs/libnewsQuery.so.0
Try: yum --disablerepo='*' --enablerepo='*-debug*' install /usr/lib/debug/.build-id/0e/b26c40799ca94ce04a602d93590a7d65299bf8
Missing separate debuginfo for /search/odin/guanjian/RecSystem/ssplatform-encoding/_lib/.libs/libencoding.so.4
Try: yum --disablerepo='*' --enablerepo='*-debug*' install /usr/lib/debug/.build-id/fa/0733823bc55103dc38ed40ddab7cf5e9a88911
Missing separate debuginfo for /search/odin/guanjian/RecSystem/ssplatform-encoding/_lib/.libs/libunicode-encoding.so.4
Try: yum --disablerepo='*' --enablerepo='*-debug*' install /usr/lib/debug/.build-id/aa/8188e2e09c8ea7dfccdb246fa3a48a3a78e8ef
Missing separate debuginfo for /search/odin/guanjian/RecSystem/ssplatform-encoding/_lib/.libs/libunicode-encoding-data.so.4
Try: yum --disablerepo='*' --enablerepo='*-debug*' install /usr/lib/debug/.build-id/1f/37edd0a16848de6d6f3f47228609d0187fbcd8
Missing separate debuginfo for /search/odin/guanjian/RecSystem/_lib/.libs/libkv.so.0
Try: yum --disablerepo='*' --enablerepo='*-debug*' install /usr/lib/debug/.build-id/f6/844e3e38a8f778dd37163464a64e952d75a568
Missing separate debuginfo for /search/odin/guanjian/RecSystem/_lib/.libs/libaes.so.0
Try: yum --disablerepo='*' --enablerepo='*-debug*' install /usr/lib/debug/.build-id/0e/fccde98d71b0d7d6f5ff6a4eb9adcc7c36fbe7

(gdb) set solib-absolute-prefix "library path"
(gdb) set solib-absolute-prefix "/search/odin/guanjian/RecSystem/_lib/"
(gdb) set solib-search-path "/usr/lib/"
  

Core was generated by `/search/fangzi/code/zhouhuilei/novel_recommend_wd/RerankServer/.libs/lt-novreco'.
Program terminated with signal 11, Segmentation fault.
#0  0x000000348089c406 in ?? ()
(gdb) file .libs/lt-novrecom_daemon    ---- 进程号
Reading symbols from /usr/lib64/libprotobuf.so.6...(no debugging symbols found)...done.
Loaded symbols for /usr/lib64/libprotobuf.so.6
Reading symbols from /search/odin/guanjian/RecSystem/ssplatform-encoding/_lib/.libs/libencoding-data.so.4...(no debugging symbols found)...done.
Loaded symbols for /search/odin/guanjian/RecSystem/ssplatform-encoding/_lib/.libs/libencoding-data.so.4
Reading symbols from /usr/lib64/libthrift-0.8.0.so...(no debugging symbols found)...done.

(gdb) bt
#0  0x0000003fe3e9d25b in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) () from /usr/lib64/libstdc++.so.6
#1  0x00007fc73f2a2a58 in PRIPoolFiller_tHotWordEntry::getKey (this=0x7fc6a71e3300, data=0x3ca367d0) at ./PRIPoolFiller.h:69
#2  0x00007fc73f2ad947 in PRIPoolFiller<HotWordEntry>::fillOne (this=0x7fc6a71e3300, pri=0x7fc6a71e3350, out_pool=0x7fc6a71e33c0, io_set=0x7fc6a71e32d0) at ./PRIPoolFiller.h:33
#3  0x00007fc73f264afb in WXBaseRecommend::GetRecGuessWantQuery (this=0x7fc6e4018a80, userinfo=..., ranks=..., appendixs="") at WXBaseRecommend.cpp:3236
#4  0x000000000048fcb9 in ?? ()
#5  0x00007fc6a71e39f0 in ?? ()
#6  0x00007fc6a71e3a00 in ?? ()
#7  0x00007fc6a71e36b0 in ?? ()
#8  0x00007fc6e4018a80 in ?? ()
#9  0x000000000071bb00 in ?? ()
#10 0x0000000000000000 in ?? ()
(gdb) q

(2)问题分析

一些库找不到(/lib/libstdc++.so.6),或者版本不匹配。我不应该加载/lib/libdl..so.... 这些文件,这是针对X86的。所以两个命令至关重要:

set solib-absolute-prefix -- Set prefix for loading absolute shared library symbol files
set solib-search-path -- Set the search path for loading non-absolute shared library symbol files



Linux编程基础——GDB(设置断点)

启动GDB后,首先就是要设置断点,程序中断后才能调试。在gdb中,断点通常有三种形式:

断点(BreakPoint):

在代码的指定位置中断,这个是我们用得最多的一种。设置断点的命令是break,它通常有如下方式:

  • break <function>    在进入指定函数时停住
  • break <linenum>    在指定行号停住。
  • break +/-offset    在当前行号的前面或后面的offset行停住。offiset为自然数。
  • break filename:linenum    在源文件filename的linenum行处停住。
  • break ... if <condition>    ...可以是上述的参数,condition表示条件,在条件成立时停住。比如在循环境体中,可以设置break if i=100,表示当i为100时停住程序。

可以通过info breakpoints [n]命令查看当前断点信息。此外,还有如下几个配套的常用命令:

  • delete    删除所有断点
  • delete breakpoint [n]    删除某个断点
  • disable breakpoint [n]    禁用某个断点
  • enable breakpoint [n]    使能某个断点

观察点(WatchPoint):

在变量读、写或变化时中断,这类方式常用来定位bug。

  • watch <expr>    变量发生变化时中断
  • rwatch <expr>    变量被读时中断
  • awatch <expr>     变量值被读或被写时中断

可以通过info watchpoints [n]命令查看当前观察点信息

捕捉点(CatchPoint):

捕捉点用来补捉程序运行时的一些事件。如:载入共享库(动态链接库)、C++的异常等。通常也是用来定位bug。

捕捉点的命令格式是:catch <event>event可以是下面的内容

  • throw     C++抛出的异常时中断
  • catch     C++捕捉到的异常时中断
  • exec    调用系统调用exec时(只在某些操作系统下有用)
  • fork    调用系统调用fork时(只在某些操作系统下有用)
  • vfork    调用系统调用vfork时(只在某些操作系统下有用)
  • load 或 load <libname>     载入共享库时(只在某些操作系统下有用)
  • unload 或 unload <libname>    卸载共享库时(只在某些操作系统下有用)

另外,还有一个tcatch <event>,功能类似,不过他只设置一次捕捉点,当程序停住以后,应点被自动删除。

捕捉点信息的查看方式和代码断点的命令是一样的,这里就不多介绍了。

在特定线程中中断

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

  • break <linespec> thread <threadno>
  • break <linespec> thread <threadno> if ...

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

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

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

恢复程序运行和单步调试

在gdb中,和调试步进相关的命令主要有如下几条:

  • continue    继续运行程序直到下一个断点(类似于VS里的F5)
  • next        逐过程步进,不会进入子函数(类似VS里的F10)
  • setp        逐语句步进,会进入子函数(类似VS里的F11)
  • until        运行至当前语句块结束
  • finish    运行至函数结束并跳出,并打印函数的返回值(类似VS的Shift+F11)

PS:这些命令大部分可以简写为第一个字母,在日常使用过程中,往往只会输入第一个字符即可执行该命令,我标红的即是通常的使用方式。这几条命令使用非常频繁,并且可以带一些附加参数以实现高级功能,需要熟练掌握。

(2)对于Segmentfault(段错误)等不提示的信息的调试方法

大概三步走~~~( # ./core_dump_run运行时产生段错误,但是编译阶段gcc –o []不报错)

第一步:设置core文件大小为无限(默认为0,即不产生)如果想让系统在信号中断造成的错误时产生core文件,我们需要在shell中按如下设置:
#
设置core文件大小为无限
ulimit -c unlimited
#设置core文件大小为1000kb
ulimit –c 1000

#查看设置的大小

ulimit –c

[@sjs_37_33 t_core_gdb]# ulimit-c

100000

 

第二步:#gcc(g++)-g -o core_dump_run core_dump_test.c,并运行#./core_dump_run产生core.pid文件

 

第三步:#gdb core_dump_runcore.pid 即可调试 (gdb)start -à nextà bk直接到错误之处

 

附加:对于第三步中提示信息太少的,但是提供了错误地址的可以反编译:objdump

#objdump –d  core_dump_run

代码:

 1 #include <stdio.h>
2
3 const char str[5] = "test";
4
5 void core_test()
6 {
7 //str[4]='T';
8 unsigned char *ptr = 0x00; // ·Ç·¨µÄ
9 *ptr = 0x00;
10 printf("str=:%s,str[1]=%d, [str[7]=%d]\n",str,str[1],str[7]);
11 }
12
13
14 int main()
15 {
16 core_test();
17 return 0;
18 }

(3)gdb多线程调试

先介绍一下GDB多线程调试的基本命令。

info threads 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID前面有*的是当前调试的线程。

thread ID 切换当前调试的线程为指定ID的线程。

break thread_test.c:123 thread all 在所有线程中相应的行上设置断点

thread apply ID1 ID2 command 让一个或者多个线程执行GDB命令command 

thread apply all command 让所有被调试线程执行GDB命令command

set scheduler-locking off|on|step 估计是实际使用过多线程调试的人都可以发现,在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。off不锁定任何线程,也就是所有线程都执行,这是默认值。 on只有当前被调试程序会执行。 step在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。

 

gdb对于多线程程序的调试有如下的支持:

·        线程产生通知:在产生新的线程时, gdb会给出提示信息

(gdb) r
Starting program: /root/thread 

[New Thread 1073951360 (LWP 12900)] 
[New Thread 1082342592 (LWP 12907)]---以下三个为新产生的线程
[New Thread 1090731072 (LWP 12908)]
[New Thread 1099119552 (LWP 12909)]

·        查看线程:使用info threads可以查看运行的线程。

(gdb) info threads
  
Thread 1099119552 (LWP 12940)   0xffffe002 in ?? ()
  
3 Thread 1090731072 (LWP 12939)   0xffffe002 in ?? ()
  
2 Thread 1082342592 (LWP 12938)   0xffffe002 in ?? ()
1 Thread 1073951360 (LWP 12931)   main (argc=1,argv=0xbfffda04) at thread.c:21
(gdb)

注意,行首的蓝色文字为gdb分配的线程号,对线程进行切换时,使用该该号码,而不是上文标出的绿色数字。

另外,行首的红色星号标识了当前活动的线程

·        切换线程:使用 threadTHREADNUMBER 进行切换,THREADNUMBER 为上文提到的线程号。下例显示将活动线程从 1 切换至 4

(gdb) info threads
   4 Thread 1099119552 (LWP 12940)   0xffffe002 in ?? ()
   3 Thread 1090731072 (LWP 12939)   0xffffe002 in ?? ()
   2 Thread 1082342592 (LWP 12938)   0xffffe002 in ?? ()
* 1 Thread 1073951360 (LWP12931)   main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb) 
thread 4
[Switching to thread 4 (Thread 1099119552 (LWP 12940))]#0  0xffffe002 in ?? ()
(gdb) info threads
* 4 Thread 1099119552 (LWP12940)   0xffffe002 in ?? ()

   3 Thread 1090731072 (LWP 12939)   0xffffe002 in ?? ()
   2 Thread 1082342592 (LWP 12938)   0xffffe002 in ?? ()
   1 Thread 1073951360 (LWP 12931)   main (argc=1,argv=0xbfffda04) at thread.c:21
(gdb)

 

后面就是直接在你的线程函数里面设置断点,然后continue到那个断点,一般情况下多线程的时候,由于是同时运行的,最好设置 setscheduler-locking on

这样的话,只调试当前线程 

三:个人心得

(0)记录个人成长中的收获的点点滴滴,哪怕跌倒再次爬起来,也是一种难得的经验 —— 我一直为自己前几年没有留下成长记忆而苦恼,如今时时刻刻的督促自己写一写自己的声音。

(1)哪怕一周花一到两个小时的时间,整理一下自己当下的心得和学习所得;从短期看:这会加深自己对知识的理解;从长远来看:虽然花费了一两个小时,但是最终你是赚了的。

(2)也许我们都会有类似的经历 —— 我们今天遇到的难题,前几天刚刚遇到过,处理过;但是,遗憾的是,自己忘记如何处理的了,更悲催的是,我们都忘了是哪一个模块里的;仅仅记得刚刚处理过这个问题;纠结纠结 ~~~浪费同样的时间处理一个相似的问题。

(3)在此做个小小调查,有相似观点的,请说说你们的感想~~~不胜感激 观点不一的更是希望留言,留下宝贵意见!