我有话说
因为时间和精力原因,本文写的虎头蛇尾了,除了启动调试与程序执行以外只有少量截图演示,只是简单的说明。如果有需要可以联系我,我有时间的话会把演示补上,谢谢理解。
启动调试与程序执行
启动调试并传递参数
命令(这三者在功能上没有任何区别 ) | 功能 |
---|---|
gdb --args <exe> <args> | 在启动dgb时传入参数 |
set args <args> | 在启动gdb后,运行程序前传入参数 |
r/run <args> | 在启动gdb后,运行程序时传入参数 |
测试代码:
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
cout << "i have "<< argc << " arguments " <<"there are my arguments: " << endl;
for (int i = 0; i < argc; ++i) {
cout << argv[i] << endl;
}
return 0;
}
不传入参数:
三种传入参数的方法:
附加进进程
附加进进程主要是用于调试已经启动的进程,在附加进进程后,进程会进入追踪暂停状态(tracing stop / t 状态),当退出gdb时进程会继续运行。
附加进进程 | 查看进程id的方法 | |
---|---|---|
gdb attach <pid> | ps -ajx | |
gdb --pid <pid> | ps -aux |
测试代码:
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
int main() {
size_t i = 1;
while (true) {
cout << "这是第 " << i++ << " 次循环" << endl;
this_thread::sleep_for(chrono::seconds(1));
}
return 0;
}
测试结果:
逐过程调试和逐语句调试以及退出当前函数
命令 | 功能 |
---|---|
next/n | 逐过程调试 单步执行(step-over),遇到函数跳过函数 |
step/s | 逐语句调试 单步执行(step-into),遇到函数进入函数 |
continue/c | 运行到下一个断点处 |
finish | 退出当前函数 |
测试代码:
#include <iostream>
#include <string>
using namespace std;
void test() {
string str = "gdb";
str += " welcome";
str += " you";
cout << str << endl;
}
int main() {
test();
return 0;
}
测试结果:
退出调试
命令 | 功能 |
---|---|
detach | 分离当前正在调试的进程(必须处于启动状态) |
quit/q | 退出gdb |
测试代码:
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
int main() {
cout << "enter main function" << endl;
int i = 0;
for (;;) {
cout << "i have looped " << ++i << " times" << endl;
this_thread::sleep_for(chrono::seconds(1));
}
return 0;
}
测试结果:
断点管理
设置断点
命令 | 功能 |
---|---|
break/b <filename : line> | 在源代码的某一行设置断点,当调试的程序为单文件程序时,可以省略文件名 |
b <func_name> | 为函数设置断点。如果有同名函数,就为所有同名函数设置断点 如果只想为特定的函数设置断点,就需要添加限定符,以便区分到底是为哪个函数设置断点 |
rb <regex> | 为满足正则表达式的函数设置断点 |
b <location> if <expression> | 设置条件断点,当条件成立时断点就会停止进程。常用在循环中,但其他场景也能用 |
tb <location> | 设置临时断点,该断点只会命中一次 |
b [+ -] <offest> | 通过偏移量设置断点,当前代码执行到某一行时,如果要为当前代码行的前面某一行或者后面某一行设置断点,就可以通过偏移量来达到快速设置断点的目的 |
测试代码:
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
void test() {
cout << "enter test()" << endl;
cout << "quit test()" << endl;
}
void test(int) {
cout << "enter test(int)" << endl;
cout << "quit test(int)" << endl;
}
void test(double) {
cout << "enter test(int)" << endl;
cout << "quit test(int)" << endl;
}
void test_func() {
cout << "enter test_func()" << endl;
cout << "quit test_func()" << endl;
}
int main() {
int temp;
cin >> temp;
while (temp < 100) {
cout << "temp: " << temp++ << endl;
this_thread::sleep_for(chrono::milliseconds(500));
}
return 0;
}
# b <func_name>
# 此时,只会对class中的test_func和test_func(int)两个函数设置断点
b class::test_func()
b test_func(int)
# rb <regex>
rb test_func* # 这样就为所有以test_func开头的函数设置了断点
查看、禁用、删除断点
命令 | 功能 |
---|---|
i b(info breakpoints / info break) | 查看所有断点 |
i b <id> | 查看某一个断点 |
disable/enable <id> | 禁用/启用断点 |
disable <id1 - id2> | 禁用id1到id2号断点 |
enable once <id> | 启动一次断点 |
enable delete <id> | 启动断点,在断点被击中后删除 |
enable count <times> <id> | 启动断点,在该断点被击中times次后自动禁用 |
ignore <times> <id> | 忽略前times次击中断点 |
delete / clear | 删除所有断点(clear 无法删除观察点和捕获点) |
delete <id> | 删除断点 |
delete <id1 id2 ......> | 删除id1、id2、......号断点 |
delete <id1 - id2> | 删除id1到id2号断点 |
delete <id1 - id2 id3 - id4 ......> | 删除id1到id2、id3到id4、......号断点 |
clear <func_name> | 删除函数内所有的断点,如果存在同名函数,那么所有同名函数中的断点都会被删除 |
clear <filename:line> | 删除文件中某一行的断点 |
查看、修改变量
查看变量
命令 | 功能 |
---|---|
show args | 查看命令行参数 |
info(i) args | 查看函数参数,参数必须要有名字才能查看 |
i locals | 查看局部变量 |
print(p) <variable> | 查看变量的值 |
set print null-stop | 设置字符串的显示规则,查看字符串变量时到''0'停止 |
set print pretty | 设置结构体显示规则,让结构体中每个字段占一行 |
set print array on | 设置数组显示规则,让数组中的每个元素各占一行 |
p <gdb内置函数> | 如 sizeof,strlen,strcpy |
测试代码:
#include <iostream>
using namespace std;
void test1(int, double, const char*) {
cout << "enter test(int, double, const char*)" << endl;
cout << "exit test(int, double, const char*)" << endl;
}
void test2(int i, const char* str) {
cout << "enter test2(int i, const char* str)" << endl;
cout << "exit test2(int i, const char* str)" << endl;
}
int main() {
test1(114514, 79856.33, "hello");
test2(222222, "world");
return 0;
}
修改变量
命令 | 功能 |
---|---|
print/p <variable> = <val> | 修改包括普通变量,成员变量,结构体,类等,可以用来控制程序的执行流程 |
p <gdb内置函数> | 通过调用gdb内置函数来修改变量 p strcpy(str, "this is string") |
查看、修改内存
# 查看内存
# n 是显示内存的长度,以f和u的读取方式显示n个数据
# f 是显示格式(x是十六进制,d是十进制,u是无符号十六进制,o是八进制,t是二进制,f是浮点,s是字符串)
# u 是单位(b是单字节,h是双字节,w是四字节,g是八字节)
# nfu都可省略 n省略时默认为1,f省略时为你上一次指定的格式(如果没有则为x),u省略时为你上一次指定的单位(如果没有则为w)
x /nfu <addr>
# 修改内存
# var可写可不写,写了是为了避免set与其他单词组成命令
# 如set width就是gdb内置命令
set(var)<addr> = <value>
寄存器的查看和修改
寄存器的查看和修改一般用在无调试符号的程序中(release版本)。
查看寄存器
命令 | 功能 |
---|---|
i registers | 查看所有通用寄存器 |
i all-registers | 查看所有寄存器 |
i r <name> | 查看某一个寄存器 |
当函数的参数小于等于6个时,会将参数放在寄存器中,否则会放入函数栈中。
如果查看的寄存器中的值是一个字符串,可以结合p (char*) <addr> 来查看其中的内容。
修改寄存器
命令 | 功能 |
---|---|
i line <行号> | 查看行号对应代码的汇编地址 |
disassemble | 反汇编 |
set var $pc/rip = <addr> | pc/rip(program counter)寄存器,用来保存程序中下一条要执行的指令,可以通过修改pc/rip寄存器来改变程序执行的流程 |
p $pc/rip = <addr> | 同上 |
源代码的查看和管理
命令 | 功能 |
---|---|
list / l | 显示源代码,第一次默认显示10行(前后各5行),之后每次都向后显示10行 |
l - | 向前显示 |
list <dir> : <line> | 查看指定文件指定行代码 |
set listsize <value> | 设置每次显示的行数 |
list <name> | 查看指定函数的代码,如果有同名函数,就会把所有同名函数显示出来。 可以添加域限定符::来指定显示哪一个同名函数 可以通过添加<dir> : <func name>来限定查看哪一个文件中的函数 |
search <regex> | 从当前行开始向后搜索第一个满足正则表达式的源代码,搜索到之后按回车表示以当前正则表达式继续搜索下一个 |
forward-search <regex> | 同search <regex> |
reverse-search <regex> | 从当前行开始向前搜索第一个满足正则表达式的源代码,搜索到之后按回车表示以当前正则表达式继续搜索下一个 |
show directories | 查看源代码的查找目录,一般是程序的工作目录和当前所在目录 |
directory <path> | 设置源代码的查找目录 |
函数调用栈管理
命令 | 功能 |
---|---|
backtrace/bt | 查看栈回溯信息 |
frame/f <frame id / frame addr> | 切换栈帧 |
info f <id> | 查看栈帧信息 |
这些命令用来检查死锁、无限递归等问题。