调试利器GDB
目录
1、初探GDB
什么是GDB?
- GNU项目中的调试器(gnu debuger)
- 能够跟踪程序的执行,也能够恢复程序崩溃前的状态
为什么需要GDB?
- 软件不是一次性开发完成的(是软件就有bug,是程序就有问题)
- 调试是软件开发过程中不可或缺的技术(调试工具很重要)
binutils工具集属于静态分析工具,目标是可执行程序文件(事后分析)
GDB是动态分析工具,目标是进程
GDB的常规应用
- 自定义程序的启动方式(指定影响程序运行的参数如:命令行参数)
- 设置条件断点(在条件满足时暂停程序的执行)
- 回溯检查导致程序异常结束的原因(Core Dump)
- 动态改变程序执行流(定位问题的辅助方式)
GDB的启动方式
- 直接启动
gdb
gdb test.out
gdb test.out core
- 动态连接
gdb test.out pid //动态跟踪这一进程
GDB应用示例
实验分析
初步体验gdb的调试 func.c test.c
fun.c
#include <stdio.h>
int* g_pointer;
void func()
{
*g_pointer = (int)"D.T.Software";
return;
}
#include <stdio.h>
#include <unistd.h>
extern int* g_pointer;
extern void func();
void test_1()
{
printf("test_1() : %p\n", test_1);
}
void test_2()
{
printf("test_2() : %p\n", test_2);
}
void test_3()
{
printf("test_3() : %p\n", test_3);
}
int main(int argc, char *argv[])
{
typedef void(TFunc)();
TFunc* fa[] = {test_1, test_2, test_3};
int i = 0;
printf("main() : begin...\n");
for(i=0; i<argc; i++)
{
printf("argv[%d] = %s\n", i, argv[i]);
}
for(i=0; i<100; i++)
{
fa[i%3]();
sleep(argc > 1);//argc > 1休眠1s
}
printf("g_pointer = %p\n", g_pointer);
func();
printf("main() : end...\n");
return 0;
}
编译运行出现段错误,使用gdb调试分析错误
调试1
直接定位了错误之处
调试2
直接定位了错误之处,比静态分析工具强大多
演示设置命令行参数
演示gdb动态链接到一个进程
2、使用GDB 进行断点调试
断点类型
- 软件断点:由非法指令异常实现(软件实现)
- 硬件断点:由硬件特性实现(数量有限)
- 数据断点:由硬件特性实现(数量有限)
软件断点适用于运行于内存中的程序
硬件断点适用于运行于Flash中的程序
数据断点用于监视一段内存,若这段内存被访问(被读,被写)程序立即停下
软件断点的相关操作
- 通过函数名设置断点
break func_name [ if var = value ]
tbreak func_name [ if var = value ]
- 通过文件名行号设置断点
break file_name:line_num [ if var = value ]
tbreak file_name:line_num [ if var = value ]
(break设置的断点总是有效的,tbreak设置一次有效断点,若指明条件就为条件断点)
调试时的常用操作
硬件断点及其应用
- 当代码位于只读存储器(Flash)时,只能通过硬件断点调试
- 硬件断点需要硬件支持,数量有限
- GDB中通过 hbreak 命令支持硬件断点
- hbreak 与 break 使用方式完全一致
实验分析
使用gdb进行断点调试
我们可以尝试跳过43行func()的调用,若程序执行正常确定func函数有问题
接下来进行第二次调试
第三次调试
整个过程没有修改源代码...就成功定位错误,并解决问题
GDB中支持数据断点的设置
- watch 命令用于监视变量是否被改变(本质为硬件断点)
- watch命令的用法:watch var_name
GDB中的内存查看
- GDB中可以检查任意内存区域中的数据
- 命令语法:x /Nuf expression
N - 需要打印的单元数
u - 每个单元的大小
f - 数据打印的格式
示例:判断系统大小端
实验分析
变量断点和内存查看 test.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int g_var = 0;
void* thread_func(void* args)
{
sleep(5);
g_var = 1;
}
int main()
{
int i = 0;
pthread_t tid = 0;
pthread_create(&tid, NULL, thread_func, NULL);
for(i=0; i<10; i++)
{
printf("g_var = %d\n", g_var);
sleep(1);
}
}
下面调试分析 定位哪一行代码修改了全局变量
这样就可以得知11行代码改写了数据
3、函数调用栈的查看
函数调用栈的查看(backtrace和frame)
- backtrace 查看函数调用的顺序(函数调用栈的信息)
- frame N 切换到栈编号为N的上下文中
- info frame 查看当前函数调用的栈帧信息
深入info命令
实验分析
函数调用栈的查看 frame.c
#include <stdio.h>
int sum(int n)
{
int ret = 0;
if( n > 0 )
{
ret = n + sum(n-1);
}
return ret;
}
int main()
{
int s = 0;
s = sum(10);
printf("sum = %d\n", s);
return 0;
}
下面调试分析
ebp向后读4个字节是之前ebp指针指向的位置
ebp向前读4个字节就是esp返回的地址
4、调试中的小技巧
实验分析
#include <stdio.h>
int g_var = 1;
struct ST
{
int i;
int j;
};
int func()
{
struct ST st[5] = {0};
int i = 0;
for(i=0; i<5; i++)
{
st[i].i = i;
st[i].j = i * i;
}
for(i=0; i<5; i++)
{
printf("st[%d].i = %d\n", i, st[i].i);
printf("st[%d].j = %d\n", i, st[i].j);
}
}
int main()
{
static c_var = 2;
func();
return 0;
}
演示断点处自动打印
演示符号查看
5、小结
GDB是GNU项目中的调试器,能够跟踪或改变程序的执行
GDB能够根据Core Dump回溯检查导致程序异常结束的原因
GDB同时支持软件断点,硬件断点和数据断点
watch 用于监视变量是否被改变,x用于查看内存中的数据
GDB支持函数调用栈的查看(backtrace,info frames)
GDB支持运行时对程序中的符号进行查看(whatis,ptype)
GDB是嵌入式开发中必须掌握的重要工具