gdb调试用法

@

一、gdb功能简介

GDB主要帮忙你完成下面四个方面的功能:

  • 1.启动你的程序,可以按照你的定制要求随心所欲的运行程序。
  • 2.可让被调试的程序在你所指定的调置的断点处停住。
  • 3.当程序被停住时,可以检查此时你的程序中所发生的事,以及内存状态等。
  • 4.动态的改变你程序的执行环境。

二、gdb使用前置条件:编译时加入debug信息。

online doc of Compiling for Debugging

gcc/g++是在编译时加入-g(注意 -g 参数要在 -o 之前,否则可能没有调试信息)
-g分4个等级:

  • -g0等于不加-g。即不包含任何信息
  • -g1只包含最小信息,一般来说只有你不需要debug,只需要backtrace信息,并且真的很在意程序大小,或者有其他保密/特殊需求时才会使用-g1。
  • –g2为gdb默认等级,包含绝大多数你需要的信息。
  • –g3包含一些额外信息,例如包含宏定义信息。当你需要调试宏定义时,请使用-g3

三、gdb最常见的几个用法:

1.gdb的启动,加载程序:

  1. gdb ${你的程序} 进入gdb后,输入run(简写r) ${arg1} ${arg2} … ${argN}
  2. gdb --args ${你的程序} ${arg1} ${arg2} … ${argN} 进入gdb后,运行run。
  3. gdb进入gdb后,输入file ${你的程序}。然后使用set args ${arg1} ${arg2} … ${argN} 设定好你的程序参数,再运行run。

2.调试正在运行的程序:

  • gdb ${你的程序} ${程序pid}
  • gdb -p PID

3. 查core:

  • gdb ${你的程序} ${core文件}
    查看生产core的命令
  • gdb -c ${core文件}
    或者
    file ${core文件}

4. 调试反汇编

窗口分解:laylout split or la sp
显示汇编:set disassemble-next-line on ,on/off/auto
下一条汇编:si
打印寄存器p $eax
汇编入门

常用寄存器

寄存器 16位 32位 64位
累加寄存器 AX EAX RAX
基址寄存器 BX EBX RBX
计数寄存器 CX ECX RCX
数据寄存器 DX EDX RDX
堆栈基指针 BP EBP RBP
变址寄存器 SI ESI RSI
堆栈顶指针 SP ESP RSP
指令寄存器 IP EIP RIP
  • mov
movb(8位)、movw(16位)、movl(32位)、movq(64位)

四、gdb命令分类:

info 命令

 info 简写为 i
 info locals :查看局部变量,可以简写: i lo.
 info args :查看函数入参 :i ar
 info break : 查看断点: i b
 info registers :查看寄存器的情况 
 info threads :当前已知线程

print

allows overriding the output format used by the command
/o - octal
/x - hexadecimal
/d - decimal
/u - unsigned decimal
/t - binary
/f - floating point
/a - address
/c - char
/s - string

打印动态数组

p arr[0]@len

设置打印数组不限长度

set max-value-size unlimited

ptype/pty 后跟结构体名,或结构体变量

以自动换行的方式打印结构体变量: set print pretty

backtrace

backtrace :显示栈信息(调用链)。简写为bt。
frame x  切换到第x帧。其中x会在bt命令中显示,从0开始。0表示栈顶。简写为f。
up/down x 往栈顶/栈底移动x帧。当不输入x时,默认为1。

layout

(gdb) layout src:显示源码窗口: la  sr
(gdb) layout asm:显示汇编窗口
(gdb) layout regs:显示寄存器窗口
(gdb) layout split:显示源码和汇编窗口
(gdb) layout next:显示下一个layout窗口
(gdb) layout prev:显示上一个layout窗口
Ctrl + L:刷新窗口
Ctrl + x,再按1:单窗口模式
Ctrl + x,再按2:双窗口模式
Ctrl + x,再按a:退出layout,回到执行layout之前的调试窗口。

源码路径设置

1, set substitute-path from_path to_path,替换源码文件路径。当编译机与运行程序的机器代码路径不同时,需要使用该指令替换代码路径,否则你无法在gdb中看到源码。
2, 为静态库设置源码路径: dir path

command 断点处附加命令

简写为: comm
command 后跟【断点编号】,(info b 查看)
然后进入命令接收模式,输入断点处要做的动作,最后一行end退出返回gdb。

调试跳转命令

  1. step 单步调试,步入当前函数。可简写为s
  2. next 单步调试,步过当前函数。可简写为n
  3. until 执行到当前循环完成。可简写为u
  4. finish 执行到当前函数返回
  5. continue 继续运行程序到下一个断点,或结束。可简写为c
  6. return: 强制函数返回。可以指定返回值
  7. jump使当前执行的程序跳转到某一行,或者跳转到某个地址。由于只会使程序跳转而不会改变栈值,因此若跳出函数到另外的地方 会导致return出错。另外,熟悉汇编的人都知道,程序运行时,有一个寄存器用于保存当前代码所在的内存地址。所以,jump命令也就是改变了这个寄存器中的值。于是,你可以使用“set $pc”来更改跳转执行的地址。如: set $pc = 0x485
  8. call 调用函数。
  9. command m 设置程序执行到断点m时要看的内容,例如:如果command后面没有参数n,则命令被赋给最后一个breakpoint,这其实是说break和command连在一起用,在脚本里用就非常方便了。
        command n
          >printf "x is %d
",x
          >c
          >end
  1. set var x=10 改变当前变量x的值。也可以这样用:set {int}0x83040 = 10把内存地址0x83040的值强制转换为int并赋值为10

五、程序中断机制: Breakpoints, Watchpoints, and Catchpoints:

online doc

1. 断点

是指当执行到程序某一步时,程序交出控制权进入调试器。值得注意的是,break会有一些变体:tbreak,hbreak,thbreak与rbreak。tbreak与break功能相同,只是所设置的断点在触发一次后自动删除。hbreak是一个硬件断点。thbreak则既是一个临时的硬件断点。注意硬件断点需要硬件支持,某些硬件可能不支持这种类型的断点。rbreak稍微特殊一些,它会在匹配正则表达式的全部位置加上断点,后面会有详细讲解。除去rbreak,其他break家族的使用方法如下:

查看断点: info break , 简写 i b

添加断点

break 可以缩写为 b

  1. break xxx.cpp:y 。在文件 xxx.cpp 的第 y 行加入断点。
    不指定文件时候,则会以当前执行的文件作为断点文件。
    若程序未执行,则以包含main函数的源代码文件作为断点文件。
    若x.cpp和y都不指定,则以当前debugger的点作为断点处。
  2. break x.cpp:func。在x.cpp的func函数入口处加入断点。x.cpp可以不提供直接使用break func。注意由于重载(overload)的存在,因此gdb可能会询问你希望在哪个函数加上断点。
  3. break 0xN。在地址N处加入断点。N必须为一个有效的代码段(code segment)地址。
  4. 删除多个断点 del 1-10

rbreak 正则匹配函数,多用于模板函数

条件断点

break xxx.cpp:y if var == value
只在满足条件时停下来

保存加载断点

1, save break file.bp 保存断点到 file.bp
2, source file.bp 加载断点(so file.bp)

2. 监视点(watch point)。

监视点是监视内存中某个地址,当该地址的数据被改变(或者被读取)时,程序交出控制权进入调试器。注意监视点分为软件模式和硬件模式:GDB 使用软件监视点的方式是在单步执行你的程序的同时测试变量的值,所以执行程序的速度会变慢。同时,软件监视点仅在当前线程有效。幸运的是,32 位的 Intel x86 处理器提供了 4 个特殊的调试寄存器用来方便调试程序,GDB 可以使用这些寄存器建立硬件监视点。GDB 总是会优先使用硬件监视点,因为这样不会减慢程序的执行速度。然而,可用的(enable的)硬件监视点的个数是有限的。如果你设置了过多的硬件监视点,当程序从中断的状态变为执行的状态(例如continue,until或者finish)时,GDB 可能无法把它们全部激活。另外,活动的硬件监视点的数量只有在试图继续执行程序时才能知道,也就是说,即使你设置了过多的硬件监视点,gdb在你运行程序之前也不会警告你。

设置监视点的命令有3个,watch(写监视),rwatch(读监视)以及awatch(读写监视)。他们的使用方法一样,皆为以下几种:

  1. (r/a)watch var。var是一个变量名。当x的值改变/被读取时,程序交出控制权进入调试器。

  2. (r/a)watch *0xdeadbeef 。0xdeadbeef为一个有效地址。当该地址的内容变化/被读取时,程序交出控制权进入调试器。

  3. (r/a)watch *(int *)0xdeadbeef。0xdeadbeef为一个有效地址。当该地址的中的int指针指向的内容变化/被读取时,程序交出控制权进入调试器。

  4. (r/a)watch -l *(int *)0xdeadbeef。0xdeadbeef为一个有效地址。当该地址的中的int指针指向的内容变化/被读取,或者该地址的内容变化/被读取时,程序交出控制权进入调试器。

注意3)和4)的区别在于,当加入-l选项后,会同时监视表达式本身以及表达式指向的内容。

注意:watch 地址的时候,要加解引用符号 *

If you watch for a change in a numerically entered address you need to dereference it, as the address itself is just a constant number which will never change. GDB refuses to create a watchpoint that watches a never-changing value
(gdb) watch 0x600850
Cannot watch constant value 0x600850.
(gdb) watch *(int *) 0x600850
Watchpoint 1: *(int *) 6293584

3、跟踪点(trace point)doc

跟踪点与上面三个断点不同之处在于,它只是跟踪记录信息而不会中断程序的运行。当你的程序是realtime程序,或者与其他的程序有交互时,你可能会希望使用跟踪点达到监视程序而又不破坏程序自身行为的目的。与断点相同的是,跟踪点会保存下在跟踪点时的一些内存信息供使用者查阅,例如数组或者对象。

另外,tracepoints可以通过save命令保存,以方便使用者下次再次进入程序调试时不需要重设这些跟踪点。
Here are some examples of using the trace command:

(gdb) trace foo.c:121    // a source file and line number
(gdb) trace +2           // 2 lines forward
(gdb) trace my_function  // first source line of function
(gdb) trace *my_function // EXACT start address of function
(gdb) trace *0x2117c4    // an address

You can abbreviate trace as tr.

4、检查点(checkpoint) doc

gdb可以通过fork当前进程的映像,并且稍后又可以返回到这个状态。这个称之为checkpoint。

每个检查点是进程的一个拷贝。这样当一个bug很难重现,而又担心调试过头了又要从头开始重现时,可以在估计要重现这个bug之前,做一个checkpoint,这样即使debug过头了,也可以从这个checkpoint开始,而不用重启整个程序并且期待它重现这个bug。
用法:
在当前位置设置检查点
(gdb) checkpoint

查看检查点
(gdb) info checkpoints

回到设置的检查点,
(gdb) restart checkpoint-id

六,调试core文件

core dump又叫核心转储, 当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump. (linux中如果内存越界会收到SIGSEGV信号,然后就会core dump)

1, 造成segment fault,产生core dump的可能原因

1.内存访问越界

a) 由于使用错误的下标,导致数组访问越界
b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符
c) 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。

2 多线程程序使用了线程不安全的函数。

3 多线程读写的数据未加锁保护。对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump

4 非法指针
a) 使用空指针
b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型 的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它 时就很容易因为bus error而core dump.

5 堆栈溢出.不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。

2, 配置操作系统使其产生core文件

首先通过ulimit命 令查看一下系统是否配置支持了dump core的功能。通过ulimit -c或ulimit -a,可以查看core file大小的配置情况,如果为0,则表示系统关闭了dump core。可以通过ulimit -c unlimited来打开。若发生了段错误,但没有core dump,是由于系统禁止core文件的生成。
解决方法:
$ulimit -c unlimited  (只对当前shell进程有效)
或在~/.bashrc 的最后加入: ulimit -c unlimited (一劳永逸)

3,调试core

gdb [exec file] [core file]

如: gdb ./test test.core
可以产生core的c语言语句: *(char *)0 = 0;

4, 控制core文件保存位置和文件名格式

修改文件命令:
把core文件与执行程序相同路径
echo "core-%e-%p" > /proc/sys/kernel/core_pattern
或者:
sysctl -w kernel.core_pattern=/corefile/core-%e-%p-%t kernel.core_pattern = /corefile/core-%e-%p-%t
可以将core文件统一生成到/corefile目录下,产生的文件名为core-命令名-pid-时间戳
以下是参数列表:

%e - insert coredumping executable name into filename 添加导致产生core的命令名
%p - insert pid into filename 添加pid(进程id)
%u - insert current uid into filename 添加当前uid(用户id)
%g - insert current gid into filename 添加当前gid(用户组id)
%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 添加主机名

在内核中还有一个与coredump相关的设置,就是/proc/sys/kernel/core_uses_pid。如果这个文件的内容被配置成1,
那么即使core_pattern中没有设置%p,最后生成的core dump文件名仍会加上进程ID。

emacs :https://www.cnblogs.com/gaowengang/p/5799292.html
https://www.cnblogs.com/xsln/p/gdb_instructions1.html

汇编:https://www.cnblogs.com/zhangyachen/p/9227037.html

原文地址:https://www.cnblogs.com/ims-/p/10529393.html