x86_64平台SingleStep单步调试原理和示例
先看一个程序:
// simple.c
int value = 0;
int main(int argc, char **argv)
{
value ++;
value ++;
value ++;
value ++;
value ++;
value ++;
}
现在想单步调试它,跟踪value的变化,如何来做?
用gdb即可。但是如果想要理解背后发生了什么,还是要手工来一遍效果才更佳。
这就需要理解单步跟踪的本质(Linux为例):
- x86_64体系结构,FLAGS寄存器有一个TF标志位来使能和禁用单步。
- 每条指令执行完毕,处理器会检查TF标志,在使能单步时会将执行流陷入内核。
- 操作系统内核会发送SIGTRAP信号通知进程,进程收到信号处理单步流程。
来来来,我们为上面的simple.c添加一些逻辑实现单步跟踪:
// ssdebug.c
#include <stdio.h>
#include <sys/mman.h>
#include <signal.h>
#include <asm/processor-flags.h>
// RIP的偏移,距离stack top有192字节,详情参见rt_sigframe
#define PC_OFFSET 192
// CFLAGS的偏移,距离stack top有200字节,详情参见rt_sigframe
#define F_OFFSET 200
int value = 0;
void trap(int unused);
// force inline,以避开ret指令
void __attribute__((always_inline)) inline breakpoint()
{
unsigned long rip = 0, f = 0;
unsigned char *page;
signal(SIGTRAP, trap);
pass: // 获取自身RIP,打断点
asm volatile("mov $., %0" : "=r"(rip));
if (f == 0) {
page = (unsigned char *)((unsigned long)rip & 0xffffffffffff1000);
mprotect((void *)page, 4096, PROT_WRITE|PROT_READ|PROT_EXEC);
#define I_BRK 0xcc
*(unsigned char *)(rip) = I_BRK;
f ++;
goto pass;
}
}
void trap(int unused)
{
unsigned long *p;
static int fbrk = 0;
p = (unsigned long*)((unsigned char *)&p + F_OFFSET);
if (!fbrk) { // 断点处理,开启单步
*p = *p | X86_EFLAGS_TF;
p = (unsigned long*)((unsigned char *)&p + PC_OFFSET);
printf("Break at [RIP:0x%lx]\n", *p);
fbrk ++;
}
p = (unsigned long*)((unsigned char *)&p + PC_OFFSET);
printf("current RIP: 0x%lx value:%d\n", *p, value);
#define I_RET 0xc3
if (*(unsigned char *)*p == I_RET) { // 函数返回,单步结束
p = (unsigned long*)((unsigned char *)&p + F_OFFSET);
*p &= ~X86_EFLAGS_TF;
}
}
int main(int argc, char **argv)
{
breakpoint(); // 打断点,开单步
value ++;
value ++;
value ++;
value ++;
value ++;
value ++;
}
OK,看看效果:
[root@localhost probe]# ./a.out
Break at [RIP:0x40070f]
current RIP: 0x40070f value:0
current RIP: 0x400715 value:0
current RIP: 0x400719 value:0
current RIP: 0x40071e value:0
current RIP: 0x400752 value:0
current RIP: 0x400758 value:0
current RIP: 0x40075b value:0
current RIP: 0x400761 value:1
current RIP: 0x400767 value:1
current RIP: 0x40076a value:1
current RIP: 0x400770 value:2
current RIP: 0x400776 value:2
current RIP: 0x400779 value:2
current RIP: 0x40077f value:3
current RIP: 0x400785 value:3
current RIP: 0x400788 value:3
current RIP: 0x40078e value:4
current RIP: 0x400794 value:4
current RIP: 0x400797 value:4
current RIP: 0x40079d value:5
current RIP: 0x4007a3 value:5
current RIP: 0x4007a6 value:5
current RIP: 0x4007ac value:6
current RIP: 0x4007ad value:6
确实,单步跟踪了value的变化过程,要是能把汇编指令打出来就好了,不过这也不难。
浙江温州皮鞋湿,下雨进水不会胖。
还没有评论,来说两句吧...