由于之前一直对gdb的原理感到一定兴趣,所以最近小研究了一下调试器相关的东西(Linux平台)。gdb有一篇介绍其内幕的文档,《gdb 内幕》,传说里面有详细的gdb原理相关的东西。我也下来一看,全是E文,从目录到结尾一共用了大概五分钟左右,看得十分痛苦。幸好网上有一博文,对调试器基本原理有了很好的说明,而且是Z文,下面隆重推荐之:
http://blog.jobbole.com/23463/
实际上本篇文章也以此为蓝本,围绕其介绍的基本原理展开,查缺补漏而已。为了简单性,调试器目前只实现了如下几个功能:
b OOXX:以函数名为参数设置断点
r:运行目标程序
c:继续执行
n:函数内单步执行
p:(限制级)查看普通类型的变量值
一:准备工作:
1)PTRACE
Ptrace系统调用可以跟踪控制子进程,查看修改变量,监控子进程动作等等,是用户态下一神器,比如strace就是利用ptrace来搞的。同理,调试器的核心也是使用ptrace来开展工作。关于ptrace的详细介绍可以参考以下资料:
http://www.cisco-club.com.cn/space-167847-do-blog-id-11448.html
2)libdwarf
因为调试信息是被编译器以dwarf(x)格式打入可执行文件中,所以调试器如果想要读取这些调试信息,需要配合相关函数库完成。这里使用libdwarf。注意libdwarf编译时需要libelf库的支持,所以同时最好也把libelf也装上去。
3)dwarfdump
用来打印dwarf信息的专门工具,是由libdwarf实现的一个应用。该工具有以下好处:<1>可以打印dwarf节点信息,方便自己在解析dwarf的时候进行比照。<2>源码开放,在不知道如果获取dwarf某些信息的时候可以参考源码对类似信息的处理。该工具与libdwarf包放在一起,不用另行下载。
4)双手打字
二:读取调试信息:
1)首先看看待调试的程序源码:
test.h:
#ifndef TEST_H_
#define TEST_H_
extern int ak88;
#endif /* TEST_H_ */
test.c:
char global_char = 'a';
char buff[NAME_LEN];
char buff_gg[10][20];
int ak88 = 10;
union union_def
{
char nice;
int k;
};
struct struct_def
{
char in_buff[10];
int bb;
char c;
union union_def union_member;
struct struct_def *pdef;
};
struct struct_def *ptest;
union union_def union_var;
struct struct_def struct_var , struct_var2;
int global_int = 5;
char *global_str = "nice";
int print(int i)
{
int local_var = 0;
local_var = i + i;
global_int++;
printf("it is:%d\n" , local_var);
return 0;
}
static int local_print(char *s)
{
char *str;
str = s;
printf("string is:%s\n" , str);
return 0;
}
int main(int argc , char **argv)
{
ptest = &struct_var;
ptest->pdef = &struct_var2;
ptest->pdef->bb = 9;
ptest->c = 'b';
int count = 0;
while(1)
{
if(count >= 3)
{
break;
}
local_print("hello , mdb!\n");
sleep(2);
count++;
}
print(5);
return 0;
}
test2.c:
#include "test.h"
int test2_global_var = 9;
static int local_print(int number)
{
int k;
k += number;
retu