调试器
利用ptrace实现一个简单的debugger调试器,支持断点,单步调试,continue继续运行,print打印变量。
同时依赖于libdwarf库,dwarf是Linux下调试结构,当我们使用gcc -g编译程序时,生成的.debug节的结构就是dwarf格式的,包含一定调试符号信息,行号信息,行号和源代吗对应信息等。使用dwarfdump命令可以查看带有.debug节的ELF可执行文件的格式详情。
完整代码
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/types.h>
#include <fcntl.h>
#include <libdwarf.h>
#include <dwarf.h>
#define MAX_BREAKPOINTS 10
#define MAX_CMD_LEN 256
typedef struct {
long addr;
long original_data;
} breakpoint;
breakpoint breakpoints[MAX_BREAKPOINTS];
int bp_count = 0;
long get_load_address(pid_t pid) {
char maps_path[256];
snprintf(maps_path, sizeof(maps_path), "/proc/%d/maps", pid);
FILE* maps_file = fopen(maps_path, "r");
if (!maps_file) {
perror("fopen /proc/pid/maps");
return 0;
}
long load_address = 0;
char line[512];
if (fgets(line, sizeof(line), maps_file)) {
load_address = strtol(line, NULL, 16);
}
fclose(maps_file);
return load_address;
}
void enable_breakpoint(pid_t pid, long addr) {
if (bp_count >= MAX_BREAKPOINTS) {
printf("Too many breakpoints.\n");
return;
}
long original_data = ptrace(PTRACE_PEEKDATA, pid, addr, NULL);
if (original_data == -1) {
perror("ptrace PEEKDATA");
return;
}
breakpoints[bp_count].addr = addr;
breakpoints[bp_count].original_data = original_data;
bp_count++;
long trap = (original_data & ~0xFF) | 0xCC;
if (ptrace(PTRACE_POKEDATA, pid, addr, trap) == -1) {
perror("ptrace POKEDATA");
}
}
void restore_breakpoint(pid_t pid, int bp_index) {
if (bp_index < 0 || bp_index >= bp_count) return;
long addr = breakpoints[bp_index].addr;
long original_data = breakpoints[bp_index].original_data;
if (ptrace(PTRACE_POKEDATA, pid, addr, original_data) == -1) {
perror("ptrace POKEDATA restore");
}
}
int find_breakpoint(long addr) {
for (int i = 0; i < bp_count; ++i) {
if (breakpoints[i].addr == addr) {
return i;
}
}
return -1;
}
Dwarf_Addr get_line_addr(Dwarf_Debug dbg, const char* file, int line_no, long load_address) {
Dwarf_Unsigned cu_header_length, abbrev_offset, next_cu_header;
Dwarf_Half version_stamp, address_size;
Dwarf_Error error;
Dwarf_Die cu_die;
while (dwarf_next_cu_header(dbg, &cu_header_length, &version_stamp, &abbrev_offset, &address_size, &next_cu_header, &error) == DW_DLV_OK) {
if (dwarf_siblingof(dbg, NULL, &cu_die, &error) != DW_DLV_OK) {
continue;
}
Dwarf_Line *lines;
Dwarf_Signed line_count;
if (dwarf_srclines(cu_die, &lines, &line_count, &error) != DW_DLV_OK) {
dwarf_dealloc(dbg, cu_die, DW_DLA_DIE);
continue;
}
for (int i = 0; i < line_count; ++i) {
Dwarf_Addr line_addr;
Dwarf_Unsigned line_num;
char *file_name;
dwarf_lineaddr(lines[i], &line_addr, &error);
dwarf_lineno(lines[i], &line_num, &error);
dwarf_linesrc(lines[i], &file_name, &error);
if (line_num == line_no && strstr(file_name, file)) {
dwarf_dealloc(dbg, lines, DW_DLA_LINE);
dwarf_dealloc(dbg, cu_die, DW_DLA_DIE);
return line_addr + load_address;
}
dwarf_dealloc(dbg, file_name, DW_DLA_STRING);
}
dwarf_dealloc(dbg, lines, DW_DLA_LINE);
dwarf_dealloc(dbg, cu_die, DW_DLA_DIE);
}
return 0;
}
// 获取框架基地址
Dwarf_Addr get_frame_base(Dwarf_Debug dbg, Dwarf_Die cu_die, pid_t pid, Dwarf_Addr pc, Dwarf_Error *error) {
Dwarf_Attribute fb_attr;
Dwarf_Die current_die = cu_die;
struct user_regs_struct regs;
if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1) {
perror("ptrace GETREGS 失败");
return 0;
}
// 遍历当前 DIE 和所有父 DIE
while (current_die) {
Dwarf_Half tag;
if (dwarf_tag(current_die, &tag, error) == DW_DLV_OK) {
printf("检查 DIE 标签: 0x%x\n", tag);
}
if (dwarf_attr(current_die, DW_AT_frame_base, &fb_attr, error) == DW_DLV_OK) {
Dwarf_Half form;
if (dwarf_whatform(fb_attr, &form, error) == DW_DLV_OK) {
printf("找到 DW_AT_frame_base,格式: %d\n", form);
}
Dwarf_Locdesc **llbuf;
Dwarf_Signed listlen;
if (dwarf_loclist_n(fb_attr, &llbuf, &listlen, error) == DW_DLV_OK) {
Dwarf_Addr frame_base = 0;
for (Dwarf_Signed i = 0; i < listlen; ++i) {
Dwarf_Locdesc *loc = llbuf[i];
if (loc->ld_cents == 0) continue;
// 检查 PC 是否在位置描述的范围内
if (loc->ld_lopc == 0 && loc->ld_hipc == 0 ||
(pc >= loc->ld_lopc && pc < loc->ld_hipc)) {
for (int j = 0; j < loc->ld_cents; ++j) {
Dwarf_Small op = loc->ld_s[j].lr_atom;
Dwarf_Signed offset = loc->ld_s[j].lr_number;
printf("DW_AT_frame_base 表达式: 操作 0x%x, 偏移 %ld\n", op, offset);
if (op == DW_OP_regx) {
if (offset == 6) frame_base = regs.rbp;
else if (offset == 7) frame_base = regs.rsp;
} else if (op == DW_OP_fbreg) {
frame_base = regs.rbp + offset;
} else if (op >= DW_OP_reg0 && op <= DW_OP_reg31) {
int reg_num = op - DW_OP_reg0;
if (reg_num == 6) frame_base = regs.rbp;
else if (reg_num == 7) frame_base = regs.rsp;
}
}
break;
}
}
for (Dwarf_Signed i = 0; i < listlen; ++i) {
dwarf_dealloc(dbg, llbuf[i], DW_DLA_LOCDESC);
}
dwarf_dealloc(dbg, llbuf, DW_DLA_LIST);
dwarf_dealloc(dbg, fb_attr, DW_DLA_ATTR);
if (frame_base != 0) {
printf("找到 DW_AT_frame_base,基地址 = 0x%lx\n", frame_base);
return frame_base;
}
} else {
printf("解析 DW_AT_frame_base 失败: %s\n", dwarf_errmsg(error));
}
dwarf_dealloc(dbg, fb_attr, DW_DLA_ATTR);
}
// 查找父 DIE
Dwarf_Die parent_die;
if (dwarf_siblingof(dbg, current_die, &parent_die, error) != DW_DLV_OK) {
break;
}
if (current_die != cu_die) {
dwarf_dealloc(dbg, current_die, DW_DLA_DIE);
}
current_die = parent_die;
}
// 回退到 RBP 或 RSP
printf("警告: 未找到 DW_AT_frame_base,尝试使用 RBP = 0x%lx\n", regs.rbp);
return regs.rbp;
}
void evaluate_location(Dwarf_Debug dbg, Dwarf_Locdesc *loc, pid_t pid, Dwarf_Addr frame_base, Dwarf_Addr pc, const char* var_name) {
struct user_regs_struct regs;
if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1) {
perror("ptrace GETREGS 失败");
return;
}
if (loc->ld_cents == 0) {
printf("变量 %s 的位置表达式为空\n", var_name);
return;
}
Dwarf_Small op = loc->ld_s[0].lr_atom;
Dwarf_Signed offset = loc->ld_s[0].lr_number;
if (op == DW_OP_addr) {
long var_addr = offset;
printf("尝试读取地址 0x%lx\n", var_addr);
errno = 0;
long var_value = ptrace(PTRACE_PEEKDATA, pid, var_addr, NULL);
if (var_value != -1 || errno == 0) {
printf("变量 %s 的值(地址 0x%lx):%ld (0x%lx)\n", var_name, var_addr, var_value, var_value);
} else {
perror("ptrace PEEKDATA 失败");
}
} else if (op == DW_OP_fbreg) {
long var_addr = frame_base + offset;
printf("尝试读取框架基址 0x%lx + 偏移 %ld = 地址 0x%lx\n", frame_base, offset, var_addr);
errno = 0;
long var_value = ptrace(PTRACE_PEEKDATA, pid, var_addr, NULL);
if (var_value != -1 || errno == 0) {
int int_value = (int)var_value;
printf("变量 %s 的值(框架基址 + %ld,地址 0x%lx):%d (0x%x)\n",
var_name, offset, var_addr, int_value, int_value);
} else {
perror("ptrace PEEKDATA 失败");
}
} else if (op >= DW_OP_reg0 && op <= DW_OP_reg31) {
int reg_num = op - DW_OP_reg0;
long var_value = 0;
if (reg_num == 0) var_value = regs.rax;
else if (reg_num == 1) var_value = regs.rdx;
else if (reg_num == 2) var_value = regs.rcx;
else if (reg_num == 3) var_value = regs.rbx;
else if (reg_num == 4) var_value = regs.rsi;
else if (reg_num == 5) var_value = regs.rdi;
else if (reg_num == 6) var_value = regs.rbp;
else if (reg_num == 7) var_value = regs.rsp;
printf("变量 %s 的值(寄存器 %d):%ld (0x%lx)\n", var_name, reg_num, var_value, var_value);
} else if (op == DW_OP_regx) {
long var_value = 0;
if (offset == 6) var_value = regs.rbp;
else if (offset == 7) var_value = regs.rsp;
printf("变量 %s 的值(扩展寄存器 %ld):%ld (0x%lx)\n", var_name, offset, var_value, var_value);
} else {
printf("变量 %s 的位置操作 0x%x 不支持\n", var_name, op);
}
}
// 评估 DW_FORM_exprloc 表达式
void evaluate_exprloc(Dwarf_Debug dbg, Dwarf_Attribute attr, pid_t pid, Dwarf_Addr frame_base, const char* var_name) {
Dwarf_Error error;
struct user_regs_struct regs;
if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1) {
perror("ptrace GETREGS 失败");
return;
}
Dwarf_Locdesc *locdesc;
Dwarf_Signed loccount;
if (dwarf_loclist(attr, &locdesc, &loccount, &error) != DW_DLV_OK) {
printf("解析 DW_FORM_exprloc 失败: %s\n", dwarf_errmsg(error));
return;
}
if (loccount == 0) {
printf("变量 %s 的表达式位置为空\n", var_name);
dwarf_dealloc(dbg, locdesc, DW_DLA_LOCDESC);
return;
}
// 处理所有位置描述
for (Dwarf_Signed i = 0; i < loccount; ++i) {
Dwarf_Locdesc *loc = locdesc + i;
if (loc->ld_cents == 0) {
printf("变量 %s 的位置描述 %ld 为空\n", var_name, i);
continue;
}
// 遍历所有操作
for (int j = 0; j < loc->ld_cents; ++j) {
Dwarf_Small op = loc->ld_s[j].lr_atom;
Dwarf_Signed offset = loc->ld_s[j].lr_number;
printf("解析 DW_FORM_exprloc: 操作 %d/%d, 操作码 0x%x, 偏移 %ld\n",
j + 1, loc->ld_cents, op, offset);
if (op == DW_OP_addr) {
long var_addr = offset;
printf("尝试读取地址 0x%lx\n", var_addr);
errno = 0;
long var_value = ptrace(PTRACE_PEEKDATA, pid, var_addr, NULL);
if (var_value != -1 || errno == 0) {
int int_value = (int)var_value;
printf("变量 %s 的值(地址 0x%lx):%d (0x%x)\n",
var_name, var_addr, int_value, int_value);
} else {
perror("ptrace PEEKDATA 失败");
}
} else if (op == DW_OP_fbreg) {
long var_addr = frame_base + offset;
printf("尝试读取框架基址 0x%lx + 偏移 %ld = 地址 0x%lx\n",
frame_base, offset, var_addr);
errno = 0;
long var_value = ptrace(PTRACE_PEEKDATA, pid, var_addr, NULL);
if (var_value != -1 || errno == 0) {
int int_value = (int)var_value;
printf("变量 %s 的值(框架基址 + %ld,地址 0x%lx):%d (0x%x)\n",
var_name, offset, var_addr, int_value, int_value);
} else {
perror("ptrace PEEKDATA 失败");
}
} else if (op >= DW_OP_reg0 && op <= DW_OP_reg31) {
int reg_num = op - DW_OP_reg0;
long var_value = 0;
if (reg_num == 0) var_value = regs.rax;
else if (reg_num == 1) var_value = regs.rdx;
else if (reg_num == 2) var_value = regs.rcx;
else if (reg_num == 3) var_value = regs.rbx;
else if (reg_num == 4) var_value = regs.rsi;
else if (reg_num == 5) var_value = regs.rdi;
else if (reg_num == 6) var_value = regs.rbp;
else if (reg_num == 7) var_value = regs.rsp;
printf("变量 %s 的值(寄存器 %d):%ld (0x%lx)\n",
var_name, reg_num, var_value, var_value);
} else if (op == DW_OP_regx) {
long var_value = 0;
if (offset == 6) var_value = regs.rbp;
else if (offset == 7) var_value = regs.rsp;
printf("变量 %s 的值(扩展寄存器 %ld):%ld (0x%lx)\n",
var_name, offset, var_value, var_value);
} else {
printf("变量 %s 的表达式位置操作 0x%x 不支持\n", var_name, op);
}
}
}
dwarf_dealloc(dbg, locdesc, DW_DLA_LOCDESC);
}
// 递归遍历 DIE 树查找变量
void get_die_and_siblings(Dwarf_Debug dbg, Dwarf_Die in_die, int level, const char* var_name, long load_address, pid_t pid, Dwarf_Addr pc) {
Dwarf_Die cur_die = in_die;
Dwarf_Error error;
Dwarf_Die child_die;
// 检查父 DIE 的作用域
Dwarf_Addr low_pc, high_pc;
bool in_scope = true;
Dwarf_Half tag;
if (dwarf_tag(cur_die, &tag, &error) == DW_DLV_OK) {
if (tag == DW_TAG_subprogram || tag == DW_TAG_lexical_block) {
if (dwarf_lowpc(cur_die, &low_pc, &error) == DW_DLV_OK &&
dwarf_highpc(cur_die, &high_pc, &error) == DW_DLV_OK) {
in_scope = (pc >= low_pc + load_address && pc < high_pc + load_address);
printf("作用域检查: low_pc=0x%lx, high_pc=0x%lx, pc=0x%lx, in_scope=%d\n",
low_pc + load_address, high_pc + load_address, pc, in_scope);
}
}
}
// 处理当前 DIE
char* die_name = NULL;
if (dwarf_diename(cur_die, &die_name, &error) == DW_DLV_OK) {
if (dwarf_tag(cur_die, &tag, &error) == DW_DLV_OK && tag == DW_TAG_variable && strcmp(die_name, var_name) == 0) {
printf("找到变量 %s 的 DIE\n", var_name);
Dwarf_Attribute attr;
if (dwarf_attr(cur_die, DW_AT_location, &attr, &error) == DW_DLV_OK) {
Dwarf_Half form;
if (dwarf_whatform(attr, &form, &error) == DW_DLV_OK) {
printf("变量 %s 的 DW_AT_location 格式: %d\n", var_name, form);
if (form == DW_FORM_exprloc) {
Dwarf_Addr frame_base = get_frame_base(dbg, cur_die, pid, pc, &error);
evaluate_exprloc(dbg, attr, pid, frame_base, var_name);
} else if (form == DW_FORM_block || form == DW_FORM_block1 || form == DW_FORM_data4 || form == DW_FORM_data8) {
Dwarf_Locdesc **llbuf;
Dwarf_Signed listlen;
if (dwarf_loclist_n(attr, &llbuf, &listlen, &error) == DW_DLV_OK) {
Dwarf_Addr frame_base = get_frame_base(dbg, cur_die, pid, pc, &error);
bool found_valid_loc = false;
for (Dwarf_Signed i = 0; i < listlen; ++i) {
Dwarf_Locdesc *loc = llbuf[i];
if (loc->ld_lopc == 0 && loc->ld_hipc == 0 ||
(pc >= loc->ld_lopc + load_address && pc < loc->ld_hipc + load_address)) {
if (in_scope) {
printf("变量 %s 在作用域内,PC = 0x%lx\n", var_name, pc);
evaluate_location(dbg, loc, pid, frame_base, pc, var_name);
found_valid_loc = true;
}
}
}
if (!found_valid_loc) {
printf("变量 %s 的位置不在当前 PC 范围内\n", var_name);
}
for (Dwarf_Signed i = 0; i < listlen; ++i) {
dwarf_dealloc(dbg, llbuf[i], DW_DLA_LOCDESC);
}
dwarf_dealloc(dbg, llbuf, DW_DLA_LIST);
} else {
printf("变量 %s 无有效位置列表(dwarf_loclist_n 失败: %s)\n", var_name, dwarf_errmsg(error));
}
} else {
printf("变量 %s 的 DW_AT_location 格式不受支持: %d\n", var_name, form);
}
} else {
printf("无法获取变量 %s 的 DW_AT_location 格式: %s\n", var_name, dwarf_errmsg(error));
}
dwarf_dealloc(dbg, attr, DW_DLA_ATTR);
} else {
printf("变量 %s 无 DW_AT_location 属性: %s\n", var_name, dwarf_errmsg(error));
}
}
dwarf_dealloc(dbg, die_name, DW_DLA_STRING);
}
// 递归处理子节点
if (dwarf_child(cur_die, &child_die, &error) == DW_DLV_OK) {
get_die_and_siblings(dbg, child_die, level + 1, var_name, load_address, pid, pc);
}
// 处理兄弟节点
Dwarf_Die sib_die;
if (dwarf_siblingof(dbg, cur_die, &sib_die, &error) == DW_DLV_OK) {
get_die_and_siblings(dbg, sib_die, level, var_name, load_address, pid, pc);
}
dwarf_dealloc(dbg, cur_die, DW_DLA_DIE);
}
void print_variable(Dwarf_Debug dbg, const char* var_name, long load_address, pid_t pid) {
Dwarf_Unsigned cu_header_length, abbrev_offset, next_cu_header;
Dwarf_Half version_stamp, address_size;
Dwarf_Error error;
struct user_regs_struct regs;
if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1) {
perror("ptrace GETREGS 失败");
return;
}
Dwarf_Addr pc = regs.rip;
printf("当前程序计数器(RIP)= 0x%lx\n", pc);
bool found = false;
while (dwarf_next_cu_header(dbg, &cu_header_length, &version_stamp, &abbrev_offset, &address_size, &next_cu_header, &error) == DW_DLV_OK) {
Dwarf_Die cu_die;
if (dwarf_siblingof(dbg, NULL, &cu_die, &error) != DW_DLV_OK) {
printf("无法获取编译单元 DIE: %s\n", dwarf_errmsg(error));
continue;
}
found = true;
get_die_and_siblings(dbg, cu_die, 0, var_name, load_address, pid, pc);
dwarf_dealloc(dbg, cu_die, DW_DLA_DIE);
}
if (!found) {
printf("未找到任何编译单元或 DWARF 信息无效: %s\n", dwarf_errmsg(error));
} else {
printf("完成变量 %s 的搜索\n", var_name);
}
}
void run_debugger(pid_t child_pid, const char* prog_name) {
int wait_status;
long load_address = 0;
Dwarf_Debug dbg = 0;
Dwarf_Error err;
int fd = -1;
wait(&wait_status);
load_address = get_load_address(child_pid);
printf("加载地址: 0x%lx\n", load_address);
fd = open(prog_name, O_RDONLY);
if (fd < 0) {
perror("open 失败");
return;
}
// 检查 ELF 文件头
char buffer[4];
if (read(fd, buffer, 4) < 4 || strncmp(buffer, "\177ELF", 4) != 0) {
fprintf(stderr, "目标文件 %s 不是有效的 ELF 文件\n", prog_name);
close(fd);
return;
}
lseek(fd, 0, SEEK_SET);
Dwarf_Handler errhand = 0;
if (dwarf_init(fd, DW_DLC_READ, errhand, NULL, &dbg, &err) != DW_DLV_OK) {
fprintf(stderr, "DWARF 初始化失败: %s\n", dwarf_errmsg(err));
close(fd);
return;
}
char command[MAX_CMD_LEN];
while (1) {
printf("(dbg) ");
fgets(command, MAX_CMD_LEN, stdin);
if (strncmp(command, "b ", 2) == 0) {
int line_no = atoi(command + 2);
char file[100];
sscanf(command + 2, "%s %d", file, &line_no);
Dwarf_Addr addr = get_line_addr(dbg, file, line_no, load_address);
if (addr != 0) {
printf("设置断点在地址 0x%lx\n", addr);
enable_breakpoint(child_pid, addr);
} else {
printf("无法在 %s:%d 设置断点\n", file, line_no);
}
} else if (strncmp(command, "c", 1) == 0) {
struct user_regs_struct regs;
if (ptrace(PTRACE_GETREGS, child_pid, NULL, ®s) == -1) {
perror("ptrace GETREGS 失败");
continue;
}
int bp_index = find_breakpoint(regs.rip - 1);
if (bp_index != -1) {
restore_breakpoint(child_pid, bp_index);
regs.rip -= 1;
if (ptrace(PTRACE_SETREGS, child_pid, NULL, ®s) == -1) {
perror("ptrace SETREGS 失败");
continue;
}
if (ptrace(PTRACE_SINGLESTEP, child_pid, NULL, NULL) == -1) {
perror("ptrace SINGLESTEP 失败");
continue;
}
wait(&wait_status);
enable_breakpoint(child_pid, breakpoints[bp_index].addr);
}
if (ptrace(PTRACE_CONT, child_pid, NULL, NULL) == -1) {
perror("ptrace CONT 失败");
continue;
}
wait(&wait_status);
if (WIFSTOPPED(wait_status)) {
if (ptrace(PTRACE_GETREGS, child_pid, NULL, ®s) == -1) {
perror("ptrace GETREGS 失败");
continue;
}
printf("子进程停止在 RIP = 0x%llx\n", regs.rip);
}
} else if (strncmp(command, "s", 1) == 0) {
if (ptrace(PTRACE_SINGLESTEP, child_pid, NULL, NULL) == -1) {
perror("ptrace SINGLESTEP 失败");
continue;
}
wait(&wait_status);
if (WIFEXITED(wait_status)) {
printf("子进程已退出\n");
break;
}
struct user_regs_struct regs;
if (ptrace(PTRACE_GETREGS, child_pid, NULL, ®s) == -1) {
perror("ptrace GETREGS 失败");
continue;
}
printf("子进程停止在 RIP = 0x%llx\n", regs.rip);
} else if (strncmp(command, "p ", 2) == 0) {
char var_name[100];
sscanf(command + 2, "%s", var_name);
print_variable(dbg, var_name, load_address, child_pid);
} else if (strncmp(command, "q", 1) == 0) {
kill(child_pid, SIGKILL);
break;
}
}
dwarf_finish(dbg, &err);
close(fd);
}
int main(int argc, char* argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <program>\n", argv[0]);
return 1;
}
pid_t child_pid = fork();
if (child_pid == 0) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
execl(argv[1], argv[1], NULL);
} else if (child_pid > 0) {
run_debugger(child_pid, argv[1]);
} else {
perror("fork");
return 1;
}
return 0;
}
简单测试文件
#include <stdio.h>
void loop_function(int count) {
int i = 0;
int total = 0;
for (i = 1; i <= count; ++i) {
total += i;
printf("i = %d, total = %d\n", i, total);
}
}
int main() {
int start_value = 5;
printf("Debugger test program started.\n");
loop_function(start_value);
printf("Debugger test program finished.\n");
return 0;
}
编译和运行
编译:gcc -o debugger debugger.c -ldwarf -lelf
测试文件gcc -g -o test_program test.c
运行:./debugger test_program
简单效果展示: