目录
- 一、 GDB简介
- 二、安装与基本配置
- 三、 基本命令与使用
- 四、GDB初步使用
- 五、进阶调试技巧
- 5.1 调试core dump程序
- 5.2 调试正在运行中的程序
- 5.3 调试多进程程序
- 5.4 调试多线程
- 5.5 死锁调试
- 5.6 内存泄漏调试
- 六、更多调试例子可以看这篇博客
- 参考文献
一、 GDB简介
GNU Debugger简称GDB是GNU项目开发的一款功能强大的命令行调试工具。GDB支持多种编程语言(C、C++、Go、Rust等)和多种处理器架构(x86、ARM等),是系统程序员和应用程序开发者的必备工具。学习GDB调试最好的教程就是官方文档 https://sourceware.org/gdb/documentation/。但对于小白直接上手常看官方文档是有难度的,通关这篇博客带大家入个门,使大家能对GDB调试有个基本的掌握,然后再通过官方文档进行查漏补缺。
二、安装与基本配置
1、安装GDB
在大多数Linux发行版上,GDB可以通过包管理器安装:
sudoapt-getinstallgdb# Debian/Ubuntusudoyuminstallgdb#CentOS/Redhat2、编译可调试的程序
要使用GDB调试程序,需要在编译时加入调试信息(使用-g选项):
gcc-g program.c-o program g++-g program.cpp-o program三、 基本命令与使用
1、启动GDB
gdb[可执行文件]# 直接启动gdb-p[进程ID]# 附加到运行中的进程gdb[可执行文件][core dump文件]# 分析核心转储文件2、常用命令
| 命令 | 命令缩写 | 命令说明 |
|---|---|---|
| set args | 设置主程序的参数 例如:./program “张三” “李四” 设置参数的方法:gdb ./program (gdb) set args “张三” “李四” | |
| break | b | 设置断点,b 20表示在第20行设置断点,可以设置多个断点 |
| info b | 查看设置的断点信息 | |
| run | r | 开始运行程序,程序运行到断点的位置会停下来,如果没有遇到断点,程序一直运行下去。 |
| next | n | 执行当前行语句,如果该语句为函数调用,不会进入函数内部执行。 |
| step | s | 执行当前行语句,如果该语句为函数调用,则进入函数执行其中的第一条语句。 注意了,如果函数是库函数或第三方提供的函数,用s也是进不去的,因为没有源代码,如果是您自定义的菡数,只要有源码就可以进去。 |
| p | 显示变量值,例如:p name表示显示变量name的值 | |
| continue | c | 继续程序的运行,直到遇到下一个断点。 |
| set var name=val | 设置变量的值,假设程序有两个变量:inticharname[21]; set var ii=10 把ii的值设置为10; set var name=“西施” 把name的值设置为"西施",注意,不是strcpy。 | |
| quit | q | 退出 |
| breaktrace | bt | 查看函数调用栈 |
四、GDB初步使用
下面我们通过一个简单的程序来使用一下gdb调试功能
#include<iostream>usingnamespacestd;intmain(intargc,char*argv[]){if(argc!=3){cout<<"算上自身,总共参数个数不为3"<<endl;}cout<<"argv[0]"<<argv[0]<<endl;cout<<"argv[1]"<<argv[1]<<endl;cout<<"argv[2]"<<argv[2]<<endl;for(inti=0;i<1000;i++){cout<<i<<endl;}return0;}首先使用g++ 编译命令编译程序
g++-g-oprogram program.cpp#注意一定要有-g然后使用gdb调试命令,进入gdb调试状态,命令前面会出现(gdb)
gdb ./program设置参数
setargs"张三""李四"设置断点
b5#在第五行暂停运行程序
r运行下一行
n打印变量
p argv[0]退出
q五、进阶调试技巧
5.1 调试core dump程序
程序挂掉时,系统缺省不会生成core 文件
ulimit -a查看系统参数ulimit -c unlimit把core文件的大小设置为无限制- 查看core文件大小限制命令:
ulimit -c - 设置core文件生成位置:
echo "./core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern这个很多博主都没讲!! - 运行程序,生成core文件
- 使用gdb调试命令:
gdb [程序名] [core文件名]
下面我们通过一个程序尝试一下
#include<iostream>usingnamespacestd;intmain(){int*p=nullptr;*p=100;return0;}g++-g-otestCore testCore.cppulimit-cunlimited#放开core文件限制./testCore#运行程序崩溃,生成core.testCore.13487 注意进程号不一样后面那串数字也不一样gdb ./testCore core.testCore.134875.2 调试正在运行中的程序
首先我们写一个for循环程序,然后让程序每输出一个数就沉睡1秒
#include<iostream>#include<unistd.h>usingnamespacestd;voidfunc(inta){cout<<a<<endl;return;}voidfunc2(){for(inti=0;i<10000;i++){func(i);sleep(1);//沉睡1秒}return;}intmain(){func2();return0;}首选我们编译程序
g++-g-otest3 test3.cpp然后重新打开一个终端,查看当前运行程序的进程
ps-ef|greptest3启用gdb调试正在运行的程序
gdb ./test-p进程号5.3 调试多进程程序
调试父进程:set follow-fork-mode parent(缺省)
调试子进程:set follow-fork-mode child
设置调试模式:set detach-on-fork[on|off],缺省是on,表示调试当前进程的时候,其它的进程继续运行;如果用off,调试当前进程的时候,其他的进程被gdb挂起。
查看调试的进程 :info inferiors
切换当前调试的进程inferior 进程id
下面我们同样可以用一个程序来进行尝试
/* process.cpp */#include<iostream>#include<cstdio>#include<cstdlib>#include<unistd.h>usingnamespacestd;intmain(){cout<<"begin"<<endl;if(fork()!=0){cout<<"我是父进程:pid="<<getpid()<<",ppid="<<getppid()<<endl;for(inti=0;i<10;i++){cout<<"i="<<i<<endl;sleep(1);}exit(0);}else{cout<<"我是子进程:pid="<<getpid()<<",ppid="<<getppid()<<endl;for(intj=0;j<10;j++){{cout<<"j="<<j<<endl;sleep(1);}}exit(0);}}g++-g-oprocess process.cpp#编译程序gdb ./process#启动调试程序b11#设置断点b14#设置断点b24#设置断点r#运行到断点处setfollow-fork-mode child#调试子进程n#运行到下一个断点5.4 调试多线程
/* thread.cpp */#include<iostream>#include<unistd.h>#include<pthread.h>usingnamespacestd;pthread_t pthid1,pthid2;void*pth1_main(void*arg);void*pth2_main(void*arg);intmain(){if(pthread_create(&pthid1,NULL,pth1_main,(void*)0)!=0){cout<<"pthread_create pthid1 failed."<<endl;return-1;}if(pthread_create(&pthid2,NULL,pth2_main,(void*)0)!=0){cout<<"pthread_create pthid2 failed."<<endl;return-1;}cout<<"1111"<<endl;pthread_join(pthid1,NULL);cout<<"2222"<<endl;pthread_join(pthid2,NULL);cout<<"3333"<<endl;}void*pth1_main(void*arg){for(intx=0;x<100;x++){cout<<"x="<<x<<endl;sleep(1);}pthread_exit(NULL);}void*pth2_main(void*arg){for(inty=0;y<100;y++){cout<<"y="<<y<<endl;sleep(1);}pthread_exit(NULL);}在shell中查看进程的方法:
- 查看当前运行的进程:
ps aux |grep thread - 查看当前运行的轻量级进程:
ps -aL| grep thread - 查看主线程和新线程的关系:
pstree -p 主线程id
多线程调试相关命令:
- 查看线程
info threads - 切换线程:
thread 线程id - 只运行当前线程:
set scheduler-locking on - 运行全部线程:
set scheduler-locking off - 指定某线程执行gdb命令:
thread apply 线程id cmd - 全部的线程执行gdb命令:
thread apply all cmd
这里不加赘述,大家设置断点后多试试
5.5 死锁调试
https://blog.csdn.net/m0_46967529/article/details/151147410
5.6 内存泄漏调试
ASan(Address Sanitizer)
https://blog.csdn.net/wohu1104/article/details/125085228
GDB调试
https://www.cnblogs.com/TechNomad/p/17982660
valgrind 调试
https://blog.csdn.net/u012770580/article/details/103065995
六、更多调试例子可以看这篇博客
【Linux】GDB调试器完全指南:从入门到高级技巧(实战增强版)
参考文献
- 【Linux】GDB保姆级调试指南(什么是GDB?GDB如何使用?)
- gdb调试
- 【Linux】GDB调试器完全指南:从入门到高级技巧(实战增强版)