gdb调试总结笔记

一、介绍

GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具

注意:利用gdb调试,需要在gcc编译过程中加上-g选项,这样编译生成的可执行文件才可以利用gdb进行源码调试。-g 选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb能找到源文件。如果把当前的gdb.c改名为g.c或者将gdb.c移动到其他地方,则gdb无法进行调试。

二、简单操作

1)启动gdb

gdb hello

或者

gdb
file hello

2)显示程序

list 或者 l

3)打断点

break(b) main()   //函数的入口处
break(b) 11       //行号
break(b) hello.c:14 //特定文件的行号
break(b) 12 if i=10 //if语句满足后在行号处打断点

4)获取断点信息

info break

5)删除断点

delete 13(断点编号)

6)运行程序

run(r)

7)单步(不进入子函数)

next(n)

8)单步(进入子函数)

step(s)

9)继续运行

continue(c)

10)查看变量

print(p) i(变量名)

11)运行程序到当前函数结束

finish

12)监控变量

watch i(变量名)

13)退出gdb

quit(q)

三、用gdb调试多进程程序

如果一个进程通过fork系统调用创建了子进程,gdb会继续调试原来的进程,子进程则正常运行。
那么如何调试子进程呢?

1、单独调试子进程
子进程本质也是一个进程,因此也可通过gdb来调试,首先找到目标子进程的PID,再将其附加(attach)到
gdb调试器
上,具体操作如下:

$ps -ef|grep 进程名 //找到待调试进程的PID
$gdb 
(gdb) attach "PID"

2、使用调试器选项follow-fork-mode

gdb调试器的选项follow-fork-mode允许我们
选择程序在执行fork系统调用后是继续调试父进程还是调试子进程

用法:

(gdb)set follow-fork-mode m
m 可以选parent 或 child, 分别表示调试父进程和子进程
举例:
$gdb XX
(gdb) set follow-fork-mode child
(gdb) b process.h:264
//打断点

四、用gdb调试多线程程序

gdb有一组命令可辅助多线程程序的调试。

info threads

显示当前可调试的所有线程。gdb会为每个线程分配一个ID,我们可以使用这个ID来操作对应的线程。
ID前面有“*”的线程是当前被调试的线程

thread ID
调试目标ID指定的线程。
查看所有线程的栈信息:thread apply all bt

调试多线程程序时,默认除了被调试的线程在执行外,其他线程也在继续执行,
但有的时候我们希望只让被调试的线程运行。这可以通过这个命令来实现

set scheduler-locking [off|on|step]

该命令设置sceduler-locking的值:

  • off表示不锁定任何线程,即所有线程都可以继续执行,这是默认值。
  • on表示只有当前被调试的线程会继续执行。
  • step表示在单步执行的时候,只有当前线程会执行。

五、用gdb工具分析core文件

在Unix系统下,应用程序崩溃,一般会产生core文件,如何根据core文件查找问题的所在,
 并做相应的分析和调试,是非常重要的。

许多程序出错时都会产生一个Core 文件,通过工具分析这个文件,
 我们可以定位到程序异常退出时对应的堆栈调用等信息,找出问题所在并进行及时解决

段错误,就是大名鼎鼎的Segmentation Fault,这通常是由指针错误引起的。

简而言之,产生段错误就是访问了错误的内存段,一般是你没有权限,
 或者根本就不存在对应的物理内存,尤其常见的是访问0 地址。

在编程中有以下几种做法容易导致段错误,基本都是错误地使用指针引起的:

  • 访问系统数据区,尤其是往系统保护的内存地址写数据,最常见的就是给指针以0地址
  • 内存越界(数据越界、变量类型不一致等)访问到不属于你的内存区域

程序在运行过程中如果出现段错误,那么就会收到SIGSEGV 信号,
SIGSEGV 默认handler 的动作是打印“段错误”的出错信息,并产生Core 文件。

1、core文件开关

ulimit -c 查看core开关,如果为0表示关闭,不会生成core文件;

使用 ulimit -c [filesize]

设置core文件大小,当最小设置为4之后才会生成core文件;
使用 ulimit -c unlimited 设置core文件大小为不限制,这是常用的做法;

如果需要开机就执行,则需要将这句命令写到 /etc/profile 等文件。
core文件路径一般在可执行文件同一目录

2、core文件命名和保存路径

core文件有默认的名称和路径,但为了方便,我们通常会自己命名和指定保存路径;
可以通过 /proc/sys/kernel/core_pattern 设置 core 文件名和保存路径,方法如下:

echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern

命名的参数列表:

  • %p - insert pid into filename 添加pid
  • %u - insert current uid into filename 添加当前uid
  • %g - insert current gid into filename 添加当前gid
  • %s - insert signal that caused the coredump into the filename 添加导致产生core的信号
  • %t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间
  • %h - insert hostname where the coredump happened into filename 添加主机名
  • %e - insert coredumping executable name into filename 添加命令名。
3、分析Core 文件

设置Core文件大小,运行程序生成Core文件
执行ulimit -c unlimited表示不限制生成的Core 文件的大小,

4、运行这个有bug 的程序,可以在当前目录下生成core文件

执行 gdb [exec file] [core file]启动gdb后,
调用gdb的where或bt命令可以查看当时的调用栈信息,
进入某个栈:f N,f是frame的缩写,N是栈号,如0、1、2、3…
进入到某个栈后,才能通过p命令查看这个栈的临时变量,否则只能查看全局变量。

六、调试一个正在运行的程序

使用gdb -p PID命令,PID即程序的pid
需要注意的是,gdb调试正在运行的程序会导致程序挂起,因此请记住不要gdb调试正在运行的在线服务。

七、设置断点的方式

有很多种,最常见的有两种:一是根据代码行位置设置断点,二是设置程序运行到某个函数。
设置程序运行到某一行,通过“文件名:行号”的形式

方式1、设置程序运行到某个函数

设置程序运行到某个函数,通过“名字空间::函数名”的形式:

b namespace_a::func

但并不是所有函数,任何时候都能设置断点的。比如优化后的静态函数,inline函数。在特定的情况下,通过函数名进行断点设置便不可为,而又需要查看运行时该函数的运行情况,这时就需要使用第二种方式。

方式2、根据代码行位置设置断点
b test.cpp:100
方式3、根据运行时的地址设置断点

此时有两种方式,一是通过直接指定地址进行,进行断点设置。二是通过print命令获得相关信息
例子1:b 0x5859c0。""号是必须加在地址前的,0x5859c0为函数指针的地址
例子2:展示变量内容

(gdb) p *thread_scheduler
$4 = {max_threads = 151, init = 0, init_new_connection_thread = 0x6a3310
<init_new_connection_handler_thread()>,
add_connection = 0x5859c0 <create_thread_to_handle_connection(THD*)>,
thd_wait_begin = 0, thd_wait_end = 0, post_kill_notification = 0,
end_thread = 0x57ea50 <one_thread_per_connection_end(THD*, bool)>, end = 0}

在打印thread_scheduler变量的内容时,保存函数指针的变量add_connection的内容被打印出来,保活函数的指针和函数的名字,通过指针可使用b *0x5859c0进行断点设置;通过函数名可使用方式1进行断点设置

八、断点操作

查看断点:info b

删除断点:d N,d是delete的缩写,N是断点的编号,可以通过info b查看。

无论哪种方式设置断点,都要执行c命令(continue),让程序继续运行。

九、在调试程序时,gdb常用命令总结

常用:n、s、p

  • n即next,单步执行,执行下一步的意思,遇到函数会调用函数。
  • s即step,也是单步执行,但是会进入函数内部,然后结合n命令来调试函数。
  • p即print,打印变量,最常用的命令。p可以打印普通变量、std::string字符串、指针、数组等。gdb打印字符串支持c_str()、length()等
    std::string str;
    p str,p str.c_str()查看字符串内容,p str.length(),查看字符串长度
    有时会遇到字符串太长不能显示全,最后显示"…",可以通过命令取消长度限制:
set print elements 0

这样就能打印完整的字符串。

命令描述
backtrace(或bt)查看各级函数调用及参数
finish连续运行到当前函数返回为止,然后停下来等待命令
frame(或f)帧编号 选择栈帧
info(或i)locals 查看当前栈帧局部变量的值
list(或l)列出源代码,接着上次的位置往下列,每次列10行
list 行号列出从第几行开始的源代码
list 函数名列出某个函数的源代码
next(或n)执行下一行语句
print(或p)打印表达式的值,通过表达式可以修改变量的值或者调用函数
quit(或q)退出gdb调试环境
set var修改变量的值
start开始执行程序,停在main函数第一行语句前面等待命令
step(或s)执行下一行语句,如果有函数调用则进入到函数中

最后说一下gdb中如何打印STL的vector和map,gdb默认不支持STL,

需要从网上下载一个txt文件,然后将其内容追加到.gdbinit文件中,就可以使用pvector命令查看vector容器数据。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值