Linux C/C++代码调试之深入熟悉使用GDB

Coredump文件是什么?
1.1 产生根因
1.2 如何打开?
1.3 如何设置文件名以及路径

GDB理论部分
2.1 基本命令
2.2 被忽略但实用的命令
2.3 实用的GDB设置

GDB操作部分
3.1 coredump文件是否任何时候都产生?
3.2 系统函数是否可以打印coredump信息?
3.3 调试案列(基本使用)
3.4 调试案列(多进程)
3.5 调试案列(多线程以及线程锁问题)
3.6 调试案列(容器调试)
3.7 调试类接口(重载函数以及虚函数)
3.8 调试案列(反汇编查看DUMP信息)

Coredump文件是什么?

当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在文件中,这种行为就叫做核心转储。Coredump文件包括寄存器信息(包括程序指针、栈指针等)、内存管理信息等。
产生根因:
如果信号均是采用默认操作,以下列的几种信号,它们在发生时会产生 core dump,如下图:

  1. 内存访问越界 (数组越界、字符串无\0结束符、字符串读写越界)
  2. 多线程程序中使用了线程不安全的函数,如不可重入函数(函数体内使用了静态的数据结构,函数体内调用 标准I/O函数,函数体内调用了malloc()或者free()函数)
  3. 多线程读写的数据未加锁保护(临界区资源需要互斥访问)
  4. 非法指针(如空指针异常或者非法地址访问)
    堆栈溢出
    程序调用 abort() 函数
    在这里插入图片描述

进程退出都会产生coredump?

使用 Ctrl+z 来挂起一个进程或者 Ctrl+C 结束一个进程均不会产生 core dump,因为会向进程发SIGTSTP 信号,该信号的默认操作为暂停进程(Stop Process;后者会向进程发出SIGINT 信号,该信号默认操作为终止进程(Terminate Process), kill -9 命令会发出 SIGKILL 命令,该命令默认为终止进程。而如果我们使用 Ctrl+\ 来终止一个进程,会向进程发出 SIGQUIT 信号,默认是会产生 core dump 的。

Coredump 如何打开

在终端中输入命令 ulimit -c ,输出的结果为 0,说明默认是关闭 core dump 的,即当程序发生崩溃,也不会生成 core dump 文件。打开两种办法:
1.Shell 命令行执行 ulimit -c unlimited 来开启 core dump 功能,只是本此生效,本用户生效,并且不限制 core dump 文件的大小; 如果需要限制文件的大小,将 unlimited 改成你想生成 core 文件最大的大小,注意单位为 blocks(KB)
2.vi /etc/profile 修改如下图蓝色所示,之后使用 . /etc/profile 或source /etc/profile 让其配置生效。

如何设置文件名以及路径

默认生成的 core 文件保存在可执行文件所在的目录下,文件名就为core。通过修改/proc/sys/kernel/core_uses_pid 文件可以让生成 core 文件名是否自动加上 pid 号。

例如 echo 1 > /proc/sys/kernel/core_uses_pid ,生成的 core 文件名将会变成 core.pid,其中 pid 表示该进程的 PID。
还可以修改 /proc/sys/kernel/core_pattern 来控制生成 core 文件保存的位置以及文件名格式。
例如: echo “/var/coredump/coredump_for_%p_%t_%e” > /proc/sys/kernel/core_pattern
设置生成的 core 文件保存在 “/var/coredump/” 目录下,文件名格式为 “coredump_for_pid-时间戳-命令名”,如下图红色所示:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200424112903651.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyNzQ0MDA1,size_16,color_FFFFFF,t_70

基本命令

run 运行程序®
continue 中断后继续运行到下一个断点©,等同(fg)
step 单步执行,进入函数(s)
next 单步执行(n)
return 函数未执行完,忽略未执行的语句,返回(ret)
finish 函数执行完毕返回
backtrace 显示栈桢 (bt)
list 显示源码(l)
pwd 当前的工作目录
cd 切换当前工作目录
print 打印数值(p)
break 打断点(b)
delete 删除断点
clear 删除断点
disable 使断点失效
enable 使断点生效
info 查看信息
info breakpoints 查看目前所有断点信息
info stack 查看此刻函数栈信息
info locals 查看当前函数中所有局部变量以及其值
set args 设置运行前参数
info args 查看此刻函数栈入参信息
set x=1 设置变量或表达式值
help 查看帮助文档
sysbol-file 加载符号表信息
until 结束当前循环(u)
quit 退出gdb

  • 被忽略但实用的命令
    bt n 显示开头n个栈桢
    bt -n 显示最后n个栈桢
    up n 向上选择n帧
    down n 向下选择n帧
    frame n 显示第n层栈桢(f)
    shell cmd 调用linux命令
    shell clear/reset 清屏
    shell top/pmap 可以调用任何查看系统信息的命令
    whatis 查看变量类型
    watch 观察某个变量或者表达式值是否变化
    info watchpoints 列出所有观察点信息
    examine 查看内存地址中的值(x)
    call 调用某一个函数
    disassemble 反汇编指定函数,默认当前函数
    attach PID 调试正在运行的进程
    p $$ 打印print使用的历史列表
    p $1 打印倒数第一个(最近的一次打印)
    thread apply all bt 打印所有线程堆栈信息
    display 程序停止时显示变量和表达式
    undisplay display 不显示表达式
    addr2line -Cfe core 崩溃地址 查看进程崩溃源文件文件行号
    bt full 打印所有栈帧的本地变量
    bt full n 打印n栈帧的本地变量
    info frame 帧描述
    info frame addr 打印地址addr的帧描述
    info inferiors 查看进程号
    info threads 查看线程号
    info registers 打印所有寄存器名字和值
    info all-registers 打印所有寄存器
    ptype 显示一个数据结构(结构体或类)
    @ 打印数组连续内存
    where 和bt区别不大

GDB 一些实用的设置

  • 打印设置
    set print null-stop on/off 如果打开了这个选项,那么当显示字符串时,遇到结束符则停止显示。这个选项默认为off
    set print element 0 输出完整的字符串
    set print pretty on/off 设置GDB打印结构体,每行一个成员,并且有相应的缩进,缺省是关闭的
    set print array on/off 数组显示时,每个元素占一行,如果不打开的话,每个元素则以逗号分隔,默认是关闭set print array-indexes on/off 数组显示时,是否展示下标
    set print sevenbit-strings <on/off> 设置字符显示,是否按“/nnn”的格式显示,如果打开,则字符串或字符数据按

  • 日志保存设置
    使用GDB的记录功能,可以将调试信息放在日志文本中,便于记录以及提供开发人员定位。
    set logging on
    打开记录功能。
    set logging off
    关闭记录功能。
    set logging file
    改变当前日志文件的名称。默认的日志文件是`gdb.txt’。
    set logging overwrite [on|off]
    默认情况下,GDB会添加到日志文件中。如果你set logging on想复盖掉日志文件, 就设置overwrite。
    set logging redirect [on|off]
    默认情况下,GDB会输出到终端和日志文件。如果你想仅仅输出到日志文件,设置redirect。
    show logging
    显示日志设置的当前值

  • 多进程设置
    GDB 没有对多进程程序调试提供直接支持。例如,使用GDB调试某个进程,如果该进程fork了子进程,GDB会继续调试该进程,子进程会不受干扰地运行下去。如果你事先在子进程代码里设定了断点,子进程会收到SIGTRAP信号并终止。
    缺省方式:fork/vfork之后,GDB仍然调试父进程,与子进程不相关,GDB>v7.0支持多进程分别以及同时调试。
    show follow-fork-mode:查看当前GDB多进程跟踪模式的设置
    set follow-fork-mode [parent|child] :默认是parent
    所以如果想要调试子进程,进入gdb后设置set follow-fork-mode child,然后设置子进程的断点
    detach-on-fork mode [on|off] on: 只调试父进程或子进程的其中一个(根据follow-fork-mode来决定),这是默认的模式。
    off: 父子进程都在gdb的控制之下,其中一个进程正常调试(根据follow-fork-mode来决定),另一个进程会被设置为暂停状态。
    parent on 只调试主进程(GDB默认)
    child on 只调试子进程
    parent off 同时调试两个进程,gdb主进程,子进程block在fork位置
    child off 同时调试两个进程,gdb子进程,主进程block在fork位置

  • 多线程设置
    show scheduler-locking
    off:不锁定任何线程,所有线程可以在任何时间运行,这是默认值。
    on:只有当前被调试程序会执行,锁定其他线程。
    step:该模式是对single-stepping模式的优化。此模式会阻止其他线程在当前线程单步调试时,抢占当前线。因此调试的焦点不会被以外的改变。其他线程不可能抢占当前的调试线程。其他线程只有下列情况下会重新获得运行的机会:
    1.next一个函数调用
    2.使用诸如continue\until\finish命令
    3.其他线程遇到设置好的断点,当程序继续运行的时候如果有断点,那么就把所有的线程都停下来,直到你指定某个线程继续执行(thread thread_no apply continue),但是如果直接在当前线程执行continue的话,默认是会启动所有线程。这种模式有一种副作用,如果多个线程都断在同一个函数,这时候调试会出问题。
    thread find [regexp],其中regexp可以是线程的systag,例如,LWP 25582中的25582,或线程名(系统定义的或用户自定义的),之后使用thread thread_no apply threadID continue。

  • gdb不停模式设置
    set non-stop on|off 默认为off,表示当一个线程停止时其他线程全部停止。在on模式下,当一个线程停止时,
    其他线程可以继续运行。独立地对每个线程进行操作,在需要的时候停止或自由运行,除
    了断点有关的线程会被停下来,其他线程会执行。
    set pagination on|off 在使用backtrace时,在分页时是否停止,off停止,on继续
    set target-async on|off 同步和异步。on同步,gdb在输出提示符之前等待程序报告一些线程已经终止的信息。
    off异步的则是直接返回。

  • 启动参数
    调试未启动的进程且不带参数
    gdb core
    run
    调试未启动的进程且带参数
    gdb core
    set args $1 $1
    run
    调试已经崩溃的进程
    gdb core coredump文件
    调试已经启动的进程:
    a.gdb
    shell ps –ef | grep core
    attach PID
    b.gdb –p pidof core
    读取文件描述符:
    symbols XXX.sym
    使用源码显示模式:
    gdb –tui core

  • watch的使用
    a. 整形变量: int i; watch i;
    b. 指针类型: char *p; watch p, watch *p; watch p 是查看 *(&p), 是p 变量本身,watch (*p) 是 p 所指的内存的
    内容。
    c. watch 一个数组或内存区间 char buf[128], watch buf, 是对buf 的128个数据进行了监视.。
    watch :为表达式(变量)expr设置一个观察点。一旦表达式值有变化时,马上停住程序。
      rwatch :当表达式(变量)expr被读时,停住程序。
      awatch :当表达式(变量)的值被读或被写时,停住程序。
      info watchpoints:列出当前所设置了的所有观察点。

  • checkpoint
      在Linux,gdb支持保留程序在某个时候的快照,除了进程号和已经输出的内容,所有状态恢复到快照状态。
      通过以下命令实现快照。
      checkpoing: 生成当前状态的快照类似shell命令(gcore 进程名)
      info checkpoint:显示快照信息
      restart checkpoint-id:恢复到某个checkpoint
      delete checkpoint checkpoint-id:删除某个checkpoint

  • 查看堆栈信息:
    gdb core coredump
    bt
    f n
    info args
    info stack
    info locals
    addr2line -Cfe core 崩溃地址
    print 打印
    X 查看内存信息

  • 调试已经在运行的程序:
    attach pid:从ps获得进程号,通过attach命令连接到该进程。attach一个进程后,gdb首先stop该进程,这样就可以设置断点,执行step、continue等命令;如果执行r命令,会杀掉原来的进程。
    detach:释放该进程,原进程会继续执行。

  • 查看线程有无死锁问题
    ps
    pstree -p core | grep co*
    pstack 进程号(多次使用)
    在这里插入图片描述在这里插入图片描述

  • 查看具体是那个线程死锁问题:
    1.info threads 查看所有线程大概信息
    thread n 切换到某个线程
    bt(where) 打印线程堆栈信息
    2.thread apply all bt 打印所有线程堆栈信息
    线程比较多,也可以搜索线程, thread find 线程名字

调试案列(容器调试)

容器里面数据的,第二张使用.gdbinit脚本后打印效果。

在这里插入图片描述
在这里插入图片描述

  1. gdbinit文件重命名为.gdbinit
  2. gdb起来后,执行source ,souce .gdbinit所在的绝对路径

std::vector pvector
std::list plist or plist_member command
std::map<T,T> pmap or pmap_member command
std::multimap<T,T> pmap or pmap_member command
std::set pset command
std::multiset pset command
std::deque pdequeue command
std::stack pstack command
std::queue pqueue command
std::priority_queue ppqueue command
std::bitset pbitset command
std::string pstring command
std::widestring pwstring command

gdbinit文件源码:

#                                                                                                        
#   STL GDB evaluators/views/utilities - 1.03
#
#   The new GDB commands:                                                         
# 	    are entirely non instrumental                                             
# 	    do not depend on any "inline"(s) - e.g. size(), [], etc
#       are extremely tolerant to debugger settings
#                                                                                 
#   This file should be "included" in .gdbinit as following:
#   source stl-views.gdb or just paste it into your .gdbinit file
#
#   The following STL containers are currently supported:
#
#       std::vector<T> -- via pvector command
#       std::list<T> -- via plist or plist_member command
#       std::map<T,T> -- via pmap or pmap_member command
#       std::multimap<T,T> -- via pmap or pmap_member command
#       std::set<T> -- via pset command
#       std::multiset<T> -- via pset command
#       std::deque<T> -- via pdequeue command
#       std::stack<T> -- via pstack command
#       std::queue<T> -- via pqueue command
#       std::priority_queue<T> -- via ppqueue command
#       std::bitset<n> -- via pbitset command
#       std::string -- via pstring command
#       std::widestring -- via pwstring command
#
#   The end of this file contains (optional) C++ beautifiers
#   Make sure your debugger supports $argc
#
#   Simple GDB Macros writen by Dan Marinescu (H-PhD) - License GPL
#   Inspired by intial work of Tom Malnar, 
#     Tony Novac (PhD) / Cornell / Stanford,
#     Gilad Mishne (PhD) and Many Many Others.
#   Contact: dan_c_marinescu@yahoo.com (Subject: STL)
#
#   Modified to work with g++ 4.3 by Anders Elton
#   Also added _member functions, that instead of printing the entire class in map, prints a member.

set auto-load safe-path /

#
# std::vector<>
#

define pvector
	if $argc == 0
		help pvector
	else
		set $size = $arg0._M_impl._M_finish - $arg0._M_impl._M_start
		set $capacity = $arg0._M_impl._M_end_of_storage - $arg0._M_impl._M_start
		set $size_max = $size - 1
	end
	if $argc == 1
		set $i = 0
		while $i < $size
			printf "elem[%u]: ", $i
			p *($arg0._M_impl._M_start + $i)
			set $i++
		end
	end
	if $argc == 2
		set $idx = $arg1
		if $idx < 0 || $idx > $size_max
			printf "idx1, idx2 are not in acceptable range: [0..%u].\n", $size_max
		else
			printf "elem[%u]: ", $idx
			p *($arg0._M_impl._M_start + $idx)
		end
	end
	if<
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值