安装gdb
安装:
apt install gdb
查看版本:
gdb --version
gdb的使用
我们将以下面的程序为例进行gdb的学习:
#include <stdio.h>
int main ()
{
unsigned long long int n, sum;
n = 1;
sum = 0;
while (n <= 100)
{
sum = sum + n;
n = n + 1;
}
return 0;
}
gdb调试器是在程序运行过程中工作的,因此首先首先要将需要调试的程序编译成一个可执行程序:
gcc main.c -o main.out -Og
不同于gcc中学到的编译生成可执行文件的过程,gdb需要的可执行文件还需要进行调试处理。所以上面的编译语句中新增了两个编译选项
O
:大写的O,表示gcc对编译过程进行优化,去除一些比如没有用到的变量之类的代码。这个选项分为五个等级,其中O0
表示不优化,O1~O4
优化力度逐渐增强。but我们常用的是O,表示力度在0-1之间,即保证对程序做了优化,又能不影响效率。
g
: 表示生成适合gdb的可执行文件。
启动gdb调试
在上面已经生成了一个可执行文件,接下来就直接启动gdb调试:
gdb main.out
直接执行上面的命令就可以进入gdb调试工具中,但是进入之前会打印一些gdb相关信息,这个可以使用--silent
将这些信息关掉
gdb工具提供了相当多的调试命令,先了解一些常用的命令:
调试指令 |
作 用 |
(gdb) break xxx<br />(gdb) b xxx |
在源代码指定的某一行设置断点,其中 xxx 用于指定具体打断点的位置。 |
(gdb) run<br />(gdb) r |
执行被调试的程序,其会自动在第一个断点处暂停执行。 |
(gdb) continue<br />(gdb) c |
当程序在某一断点处停止运行后,使用该指令可以继续执行,直至遇到下一个断点或者程序结束。 |
(gdb) next<br />(gdb) n |
令程序一行代码一行代码的执行。 |
(gdb) print xxx<br />(gdb) p xxx |
打印指定变量的值,其中 xxx 指的就是某一变量名。 |
(gdb) list<br />(gdb) l |
显示源程序代码的内容,包括各行代码所在的行号。 |
(gdb) quit<br />(gdb) q |
终止调试。 |
想起了第一次使用vim编辑器的困惑,所以先重点说说怎么终止调试。quit
或者q
在gdb工具中输入quit回车,即可终止调试。
然后再正式的来看gdb调试的常见手段:
list 或者 l
显示源代码内容,带行号
list
list命令一次性打印10行,,类似于more命令,继续按enter键到下一行。
break 或者 b
设置断点,程序运行至断点处停止运行,只是暂停,并没有终止运行代码
run 或者 r
执行正在被调试的程序,会在第一个断点处停止
print 或者 p
打印指定变量的值
continue 或者 c
然后可以设置下一个断点,这里设置12,然后使用continue命令继续执行代码,程序会在12行的断点处停止执行
接下来再次打印变量n的值,会发现经过循环,n的值已经改变了:
这样子,一次简单的调试就结束了,然后按执行quit退出调试:会问你是不是要退出调试,是的!
这就是一次简单的gdb调试,也是以后用gdb最多的命令了。
调用gdb的方式
上面的例子中已经调用过一次了,它直接调用c程序的可执行文件,命令如下:
gdb main.out --silent
这只是调用gdb调试的其中一种方式,常用的还有:进程调用、core文件调用,还有一种方式是直接执行gdb命令调用,当然这种方式调用因为没有可调式的文件,调用之后需要显示的指定相关的file,这个后面细说。
进程调用
进程调用也就是通过一个正在执行的程序去调用gdb,需要提供相关的进程号!所以第一个问题就是,怎么获取正在执行的程序的进程号?写一个代码看看。
#include <stdio.h>
int main () {
int i = 0;
while (1) {
i++;
}
return 1;
}
pid的获取
pidof <filename>
可以看到demo.out 的pid为389104
然后我们通过这个进程号来调用gdb:
gdb -p 389104
注意,当 GDB 调试器成功连接到指定进程上时,程序执行会暂停。如上所示,程序暂停至第 6 行代码 num++ 的位置,此时可以通过断点调试、逐步运行等方式监控程序的执行过程。例如:
先查看一下当前执行的代码前后逻辑,可以使用list或者l
然后设置个断点 break 5
继续执行continue
然后打印一下相关变量的值print i
当前变量的值就蛮有趣的,明明是i++,现在却是一个负数!为什么呢?
然后要退出当前调试的话,怎么办?分两步:
-
执行 detach 指令,使 GDB 调试器和程序分离;
-
执行 quit(或 q)指令,退出 GDB 调试。
core文件调用
也就是程序执行发生了异常崩溃,这种情况常见与空指针、内存泄漏、野指针等情况。对于linux系统和Android系统都可以使用core dump来调试。core dump是内核的一种转储机制,当程序发生异常的时候,就将发生异常时候的寄存器信息,内存信息都保存下来。
core dump功能
不过此功能在Android的user版本和一般用户的linux发行版当中都是没有打开的,可以通过ulimit -a
指令来看系统是否打开:
产生core dump文件
手动写一个野指针的例子看一下:
#include <stdio.h>
int main() {
char *p = NULL;
*p = 123;
return 0;
}
编译gcc -g main.c -o main.out
执行./main.out
之后产生段错误,并提示core文件已转储。段错误又称为访问权限冲突,指的是当前程序访问了不可访问的存储空间,比如访问的不存在的空间,又或者是受系统保护的内存空间。
就在同级目录下:
使用core调用gdb
gdb main.out core
会明确告诉你哪里出了问题。
直接使用gdb命令
效果如此:
但我们明确的知道现在我们没有绑定任何可执行程序。所以我们需要这么做:
file <file path>
然后就可以进行一些常规的debug了!
gdb调试器中启动程序
接着上面的动作看,我们通过gdb命令直接进入gdb调试器,然后通过file命令指定了要调试的可执行文件。然后怎么开始运行?gdb中有两种常用的开始运行命令,分别是run和start:
因为这个程序存在错误,我们不往下执行了,只需要输入kill
便可结束当前运行的程序!
指定参数
众所周知main函数是可以指定参数的,当某个可执行程序需要指定参数执行的时候就需要:
gdb main.out --args arg1 arg2 ...
写个程序来看看吧:
#include <stdio.h>
int print_args(char *str1, char *str2) {
printf("%s\n", str1);
printf("%s\n", str2);
return 0;
}
int main(int argc, char *argv[]) {
print_args(argv[0], argv[1]);
return 0;
}
显然这个函数需要给main函数多传一个参数才能执行,所以:
gdb --args demo.out klelee
或许还有别的方法指定参数?如果我是通过gdb命令进来的呢?啊,对了,确实有!通过下面的命令可以再gdb中指定参数:
set args <args list>
相关应用如下:
小秘密:gdb下可以用cd命令哦