基础用法

:::info 【 注意 】

  • 所有指令支持 tab补全
  • enter 默认重复执行上一次的指令 :::
  1. #include <stdio.h>
  2. void debug(char *str)
  3. {
  4. printf("debug info :%s\n", str);
  5. }
  6. void main(int argc, char *argv[])
  7. {
  8. int i, j;
  9. j = 0;
  10. for (i = 0; i < 10; i++)
  11. {
  12. j += 5;
  13. printf("now a=%d\n", j);
  14. }
  15. }
命令 命令缩写 命令说明
list l 显示多行源代码
break b 设置断点,程序运行到断点的位置会停下来
info i 描述程序的状态
run r 开始运行程序
display disp 跟踪查看某个变量,每次停下来都显示它的值
step s 执行下一条语句,如果该语句为函数调用,则进入函数执行其中的第一条语句
next n 执行下一条语句,如果该语句为函数调用,不会进入函数内部执行(即不会一步步地调试函数内部语句)
print p 打印内部变量值
continue c 继续程序的运行,直到遇到下一个断点
set var name=v 设置变量的值
start st 开始执行程序,在main函数的第一条语句前面停下来
file 装入需要调试的程序
kill k 终止正在调试的程序
watch 监视变量值的变化
backtrace bt 查看函数调用信息(堆栈)
frame f 查看栈帧 f n 切换到编号为n的栈
quit q 退出GDB环境

list | l


潜水镜样式的源码查看:

  1. 默认焦点对准的是程序的入口
  2. l — 指定聚焦位置到line_num行
  3. l — 指定聚焦位置到某个函数
  4. l — 多次执行,潜水镜依次后移动 :::info 源码路径指定:
    gdb —dir=<源码编译位置> 可执行文件 :::

break | b


断点的设置:

  1. b
  2. b — tab 可以自动补全或者列出可以读到的函数名称
  1. b funcA
  2. b 1038
  3. b file.c:funcA
  4. b file.c:1038
  5. b 1038 if i==99

断点信息查看:
info breakpoints | i b
断点删除:
**delete breakpoints | d breakpoints — 删除所有断点
delete | d
clear | cl

display | disp


设置变量监控点,执行流触发时变量的值
disp
观测信息查看:
info display | i disp

set =var


设置变量

print | p


打印变量名
格式化输出

  1. >> p/x x
  2. $2 = 0x2a

执行流的跟踪


continue | c — 执行到下一个断点或者程序结尾
step | s — 逻辑流的单步调试
finish | fin — 跳出函数调用
next | n — 空间流的单步调试

watch 智能蹲守


wahtch i — 会将i的改动作为一个断点触发条件,这样方便跟踪变量的变化

backtrace | bt — 程序调用逻辑的追溯


backtrace | bt
bt f — 堆栈详细信息
frame | f <> — 堆栈聚焦
up — 堆栈偏移量

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. void recurse(void)
  4. {
  5. static int i;
  6. if (++i == 3)
  7. abort();
  8. else
  9. recurse();
  10. }
  11. int main(int argc, char **argv)
  12. {
  13. recurse();
  14. }

进阶用法


已有进程的操作

gstack — ● 不冻结的进程的情况下查看堆栈
gdb -p pidof <Progress name>
gdb attach :::info 进程一旦被gdb通过-p 参数接管之后,整个进程的所有线程都是处在冻结状态 :::

汇编寄存器显微镜

检查内存
有些时候,仅仅输出一个数值还不能帮助我们查找出错误。我们需要一次性地打印出一整块内存来窥视全局。这时候我们就需要使用x命令。
x命令的格式是x/format address。其中address很简单,它通常是指向一块内存的表达式。但是format的语法就有点复杂了。它由三个部分组成:
第一个是要显示的块的数量;第二个是显示格式(如x代表16进制,d代表十进制,c代表字符);第三个是每个块的大小。值得注意的是第三部分,即块大小是用字符对应的。用b, h, w, g 分别表示1, 2, 4, 8 bytes。举例来说,用十六进制方式,打印从ptr开始的4个4-byte块应该这样写:

  1. (gdb) x/4xw ptr

接下来举一个比较实际的例子。我们看一下NSObject类的内容:

  1. (gdb) x/4xg (void *)[NSObject class] <br /> 0x7fff70adb468 : 0x00007fff70adb440 0x0000000000000000 <br /> 0x7fff70adb478 : 0x0000000100105ac0 0x0000000100104ac0

接下来再看看一个NSObject实例的内容:

  1. (gdb) x/1xg (void *)[NSObject new] <br /> 0x100105ba0: 0x00007fff70adb468

现在我们看到,在实例开头引用了类的地址。

image.png
info registers rip
x /16i 0x5633b1ba311d — 汇编聚焦,以指定内存地址为起点,到后面的调用
image.png
info reg eax — 查看寄存器中的地址
image.png

thread apply all backtrace | t a a bt — 线程堆栈打印

image.png

设置触发coredump

查看当前core设置:
a) ulimit -a
b) cat /proc/sys/kernel/core_pattern
开启core并修改其位置:
a) ulimit -c unlimited
b) echo “/home/corefile/core-%e-%p-%t” > /proc/sys/kernel/core_pattern
c) echo “1” > /proc/sys/kernel/core_uses_pid
其中第3个带pid,第2个带程序名、pid、时间戳。
注意:该方法是临时修改,系统重启后失效。永久修改可以在/etc/sysctl.conf里面按语法规则追加。

编译带调试信息的二进制

gcc -g app a.c b.c

生成符号表

objcopy —only-keep-debug app app.symbols

用gdb拉起进程

gdb —args app p1 p2 p3
gdb -s app.symbols -e app

多线程

info threads | info thr
多线程调试
info threads 显示当前可调试的所有线程
thread ID 切换当前调试的线程为指定ID的线程
attach process-id 在gdb状态下,开始调试一个正在运行的进程
thread apply all command 所有线程执行command

进程中追加参数

gdb —args 可执行文件 p1 p2