背景
不管是C开发还是做pwn的ctf题,调试总是少不了的,gdb又是很多大佬经常用来分析的工具,不学是不行的啊,醉了。现在文章的内容只是gdb工具很少的一部分,但对于初学者仍有参考价值。
参考链接:
- 《Linux C编程:一站式学习》
测试环境:
- centos 7
- GNU gdb (GDB) Red Hat Enterprise Linux 8.2-15.el8
调试的基本思想分析现象-假设错误原因-产生新的现象去验证假设
。
基础命令
书籍中的实例:
#include <stdio.h>
int add_range(int low, int high)
{
int i, sum = 0;
for (i = low; i <= high; i++)
sum = sum + i;
return sum;
}
int main(void)
{
int result[100];
result[0] = add_range(1, 10);
result[1] = add_range(1, 100);
printf("result[0] = %d
result[1] = %d
", result[0], result[1]);
return 0;
}
从main函数开始阅读本程序,定义了一个int
型的数组,数组长度为100,add_range
函数的作用是将传入的值进行累加,之后将得出的值传入到result
数组中,但只传入两个值,最后将结果输出出来。
在程序编译时,需要加上-g
选项,生成的可执行文件才能进行源码级调试。参考链接:https://blog.csdn.net/qq_40860852/article/details/89388723
[root@i-co6yw0e5 c]# gcc -g for_gdb.c -o test
[root@i-co6yw0e5 c]# ./test
result[0] = 55
result[1] = 5050
[root@i-co6yw0e5 c]# gdb --version
GNU gdb (GDB) Red Hat Enterprise Linux 8.2-15.el8
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
注:gdb的-g
参数不是将源代码嵌入到可执行文件中,所以在调试的时候,源代码不可删除,不然会存在gdb找不到源文件的情况。
使用gdb开始调试编译后的可执行程序,命令gdb 可执行程序名
。使用help
命令,可以查看命令的类别。
[root@i-co6yw0e5 c]# gdb test
GNU gdb (GDB) Red Hat Enterprise Linux 8.2-15.el8
………………
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from test...done.
(gdb) help #help命令
List of classes of commands:
aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
………………
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands
也可以进一步查看某一类别有哪些命令,例如files:
(gdb) help files
Specifying and examining files.
List of commands:
add-symbol-file -- Load symbols from FILE
……
symbol-file -- Load symbol table from executable file FILE
……
使用list
命令,从第一行开始列出代码,一次只显示10行,如果要继续显示,可以再输入list
或者直接回车,直接回车表示重复上一条命令。
(gdb) list
1 #include <stdio.h>
2
3 int add_range(int low, int high)
4 {
5 int i, sum = 0;
6 for (i = low; i <= high; i++)
7 sum = sum + i;
8 return sum;
9 }
10
gdb很多命令有简写形式,list
可以简写为l
,列出一个函数的源代码,也可以使用函数名作为参数:
(gdb) l main
7 sum = sum + i;
8 return sum;
9 }
10
11 int main(void)
12 {
13 int result[100];
14 result[0] = add_range(1, 10);
15 result[1] = add_range(1, 100);
16 printf("result[0] = %d
result[1] = %d
", result[0], result[1]);
使用start
命令开始执行程序,gdb停在main
函数中变量定义后的第一条语句,列出的语句是即将执行的下一条语句,可以使用next(简写n)
控制语句一条一条的执行。
(gdb) start
Temporary breakpoint 1 at 0x4005d1: file for_gdb.c, line 14.
Starting program: /root/c/test
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-151.el8.x86_64
Temporary breakpoint 1, main () at for_gdb.c:14
14 result[0] = add_range(1, 10);
(gdb) n
15 result[1] = add_range(1, 100);
(gdb)
16 printf("result[0] = %d
result[1] = %d
", result[0], result[1]);
(gdb)
result[0] = 55
result[1] = 5050
17 return 0;
从结果上看,虽然控制了程序的执行过程,但是并不详细,可以使用step(简写s)
进入到add_range
函数中跟踪执行,使用start
命令重新执行可执行程序。
(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 2 at 0x4005d1: file for_gdb.c, line 14.
Starting program: /root/c/test
Temporary breakpoint 2, main () at for_gdb.c:14
14 result[0] = add_range(1, 10);
(gdb) s
add_range (low=1, high=10) at for_gdb.c:5
5 int i, sum = 0;
(gdb) backtrace
#0 add_range (low=1, high=10) at for_gdb.c:5
#1 0x00000000004005e0 in main () at for_gdb.c:14
这次停在了add_range
函数中变量定义之后的第一条语句,通过backtrace(简写bt)
,可以查看函数调用的栈帧。从bt命令返回的内容中可以看到,add_range
函数是被main
函数调用的,main
函数传进来的值是low=1,high=10
,main
函数栈帧编号为1
,add_range
的栈帧编号为0
。可以通过info(简写i)
来查看add_range
函数局部变量的值。info
的命令内容有很多,可以使用help info
命令查看使用方法。
(gdb) i locals
i = 0
sum = 0
如果想要查看main
函数当前局部变量的值,可以使用frame(简写f)
选择1
号栈帧,然后再查看局部变量的值。
(gdb) f 1
#1 0x00000000004005e0 in main () at for_gdb.c:14
14 result[0] = add_range(1, 10);
(gdb) i locals
result = {-134223960, 32767, 1, 0, 0, 0, 0, 0, 0, 0, 1700966438, 0, -7032, 32767, -6976, 32767, -134223080,…… , 0, 4195949, 0, -136447344, 32767, 0, 0, 4195872, 0, 4195504, 0, -6784, 32767, 0, 0}
result
数组中的值是杂乱的,因为未经初始化的局部变量具有不确定的值。使用s
命令继续运行程序,然后使用print(简写p)
打印变量sum的值。
(gdb) s
7 sum = sum + i;
(gdb)
6 for (i = low; i <= high; i++)
(gdb) p sum
$1 = 1
(gdb) s
7 sum = sum + i;
(gdb)
6 for (i = low; i <= high; i++)
(gdb) p sum
$2 = 3
如果程序运行一切正常,再继续往下跟踪没有意义,可以使用finish
命令让程序一直运行到当前函数返回为止,当前正在进行赋值操作。
(gdb) finish
Run till exit from #0 add_range (low=1, high=10) at for_gdb.c:6
main () at for_gdb.c:14
14 result[0] = add_range(1, 10);
Value returned is $3 = 55 #赋值
使用s
命令继续执行,将进入到add_range(1, 100)
的执行步骤中,如果在执行中发现变量有误,可以使用set
命令修改变量的值,再使用finish
命令查看有没有其他错误。
(gdb) s
15 result[1] = add_range(1, 100);
(gdb)
add_range (low=1, high=100) at for_gdb.c:5
5 int i, sum = 0;
(gdb) p sum
$7 = 55
(gdb) s
6 for (i = low; i <= high; i++)
(gdb)
7 sum = sum + i;
(gdb) p sum
$8 = 0
(gdb) set var sum = 100
(gdb) s
6 for (i = low; i <= high; i++)
(gdb) p sum
$9 = 101
(gdb) finish
Run till exit from #0 add_range (low=1, high=100) at for_gdb.c:6
main () at for_gdb.c:15
15 result[1] = add_range(1, 100);
Value returned is $10 = 5150
修改变量的值除了使用set
命令,也可以使用print
命令,因为print命令后面可以跟表达式,而赋值和函数调用也可以是表达式,所以可以用print
命令修改变量的值或者调用函数。(实验显示'printf' has unknown return type; )
总结本节所使用的命令:
命令 | 描述 |
---|---|
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 | 执行下一行语句,如果有函数调用则进入函数中 |
断点
书籍中的实例:
#include <stdio.h>
int main(void)
{
int sum = 0, i = 0;
char input[5];
while(1)
{
scanf("%s", input);
for (i = 0; input[i] !=' '; i++)
{
sum = sum * 10 + input[i] - '0';
}
printf("input = %d
", sum);
}
return 0;
}
首先从键盘读入一串数字存储到字符数组input
中,然后经过计算后再打印出来。数组的最后一位会添加