gdb调试入门

目标

  • GDB基本命令
  • 高级插件的使用
  • 多线程多进程调试
  • GDB破解和防破解

概念

  • GDB是一种基于命令行的调试方法
    • gcc -g -o test test.c 需要在编译和链接的时候都加-g才能方便被GDB调试
      • g的作用:添加符号表,函数名,不然调试的时候只能阅读汇编代码
    • readelf -s test:读取符号表
  • 所有的调试都是可以进行脚本编写的
  • 能够调试所有架构的代码
  • 有三种调试方法
  • GDB支持远程调试,支持与IDA进行联调

基本命令

GDB的启动方式

  • gdb test
  • gdb attach test
  • gdb test core
    • 用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件
  • gdb test pid
    • 如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。

查看代码l/list

  • l func
    • 显示函数名为func的函数的源程序。
  • l 18
    • 显示程序第18行的周围的源程序
  • l test-1.c:18

设置每次list的行数

  • set listsize 20

设置断点b/break

  • b 行数
  • info breakpoints :查看断点信息
  • disable\enable [断点号] :取消断点
  • delete [断点号] :删除断点

条件断点

  • b 8 if sum>100
    • 当循环sum大于100时自动设置第八行为断点
 	  1	 #include <stdio.h>
      2	 
      3	 int func(int n)
      4	 {
          // sum=-0x8001e98, i=0x7fff5	 	int sum=0,i;
      6	 	for(i=0;i<n;i++)
      7	 	{
      8	 		sum +=i;
      9	 	}
     10	 	return sum;

跳到下一个断点

  • c(continue)

开始调试

  • r(run) 运行程序
  • s(step) 步进,进入函数
  • n(next) 步出,跳过函数

打印结果p/print

  • p i 查看变量i当前的值
  • p/d i 以十进制显示
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十六进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。p/a i 
c 按字符格式显示变量。p/c i 
f 按浮点数格式显示变量。

跳出循环

  • u(until)

查看内存x

  • x &i :查看变量i的内存地址
  • x /nfu nfu是参数 用来查看内存地址中的值
    • n :显示内存的长度,也就是从当前地址向后显示几个地址的内容
    • u :表示内存的单位,表示从当前地址往后请求的字节数,GDB默认是4bytes,可以用b,h,w,g来代替(1,2,4,8字节)
    • f :表示显示的格式,和print的格式参数相同
	x 按十六进制格式显示变量。
	d 按十进制格式显示变量。
	u 按十六进制格式显示无符号整型。
	o 按八进制格式显示变量。
	t 按二进制格式显示变量。
	a 按十六进制格式显示变量。p/a i 
	c 按字符格式显示变量。p/c i 
	f 按浮点数格式显示变量。
  • x/d &i :以十进制显示
  • x/d20 &i :以十进制显示20个字节
		for(i=1;i<100;i++)
		{
			result +=i;
			a[i] = i;
		}

gef➤  x/100dw a
0x7fffffffda80:	0	1	2	3
0x7fffffffda90:	4	5	6	7
0x7fffffffdaa0:	8	9	10	11
0x7fffffffdab0:	12	13	14	15
  • p *array@len :也可以看到内存,如p *a@100,查看100个a的内存的值
gef➤  p/d *a@100
$2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99}

设置参数

  • set args 10 20 30 40 50 设置参数
gef➤  bt
#0  main (argc=0x6, argv=0x7fffffffde58) at test-1.c:20
  • 默认第0个参数是程序路径
gef➤  x/20xw 0x7fffffffde58
0x7fffffffde58:	0xffffe23d	0x00007fff	0xffffe260	0x00007fff
0x7fffffffde68:	0xffffe263	0x00007fff	0xffffe266	0x00007fff
0x7fffffffde78:	0xffffe269	0x00007fff	0xffffe26c	0x00007fff
0x7fffffffde88:	0x00000000	0x00000000	0xffffe26f	0x00007fff
0x7fffffffde98:	0xffffe2aa	0x00007fff	0xffffe2b5	0x00007fff
# 64位系统八个字节为一组,小端序排列,如第一组地址为0x00007fff-ffffe23d
# 由此可以看出main函数传入参数是双重指针
gef➤  x/100cb 0x00007fffffffe23d
0x7fffffffe23d:	0x2f	0x72	0x6f	0x6f	0x74	0x2f	0x6c	0x65
0x7fffffffe245:	0x61	0x72	0x6e	0x5f	0x6c	0x69	0x6e	0x75
gef➤  x/8db 0x00007fffffffe260
0x7fffffffe260:	49	48	0	50	48	0	51	48

查看函数调用栈

  • bt(break trace) :查看函数调用流程
  • info locals :查看当前栈局部变量
  • info frame :打印当前栈的全部信息

运行shell命令

  • shell ls

修改内存

  • p i=100 :将i的值修改为100

结束当前函数

  • finish

退出gdb

  • quit

安装GDB高级插件(gef)

  • git clone https://github.com/gatieme/GdbPlugins.git ~/GdbPlugins
  • echo “source ~/GdbPlugins/peda/peda.py” > ~/.gdbinit
  • echo “source ~/GdbPlugins/gef/gef.py” > ~/.gdbinit
  • echo “source ~/GdbPlugins/gdbinit/gdbinit” > ~/.gdbinit
  • apt-get install python3-pip cmake
  • sudo pip3 install keystone-engine unicorn capstone ropper

Linux程序发布流程(分离符号表)

  • readelf -s test

    • 确定是否存在符号表
    • 有符号表才能被gdb正常调试
  • 生成/拷贝符号表

    • objcopy --only-keep-debug test test.symbol
  • 去除符号表,生成发布程序

  • objcopy --strip-debug test test.release

  • 使用符号表进行程序debug

    • 载入符号表
      • gef> file ./test.symbol
      • root> gdb -q --symbol=test.symbol --exec=test.release
    • strip test.release

GDB中暂停/恢复程序的方式

  • 断点

    • b [xxx.c]:line_num
    • b [xxx.c]:func_name
    • info breakpoints:查看断点
      • keep:保持显示,只要到达这个断点就停掉
  • 条件断点

    • b 18 if i==60
    • condition 1 i==60
    • condition 1
  • 观察点:在某个变量被修改的时候停掉

    • watch

      • 为表达式(变量)expr设置一个观察点。一量表达式值有变化时,马上停住程序
      • watch i :局部就近原则观察变量名对应的内存,发生改变就立即断下来
      • watch main::i :观察指定函数内的变量名
      • watch *0x00007fffffffddb0 (栈顶地址指向的内存)
        • Hardware watchpoint 4: *0x00007fffffffddb0
    • rwatch

      • 当表达式(变量)expr被读时,停住程序。
    • awatch

      • 当表达式(变量)的值被读或被写时,停住程序。
  • 捕捉点:捕捉程序运行时的一些事件。如:载入共享库(动态链接库)或是C++的异常。

    • catch [event] event的取值如下
      • throw :一个C++抛出的异常
      • catch :一个C++捕捉到的异常
      • exec :调用系统调用exec(在一个进程中启动另一个程序)时停止
      • fork :调用系统调用fork时停止,查看系统在哪个地方会创建新的进程(两个一模一样的程序)
      • load/load libname :载入动态链接库时停掉
      • unload/unload libname :卸载库时停掉
    • tcatch :只设置一次捕捉点,当程序停住以后,应点被自动删除。

搜索源代码

  • reverse-search :全局搜索
    • 是一个正则表达式,即字符串的匹配模式
    • reverse-search func
  • search :基于内存的搜索,函数被调用后才能搜索到
  • forward-search :向前面搜索

自动化打印命令

  • display a :每次调试自动打印变量a,gef插件的功能默认提供此功能

自定义gdb命令

  • 实现自定义断点功能
commands 断点号
	print sum
	print i
end

你可能感兴趣的:(Linux运维)