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的变化过程,要是能把汇编指令打出来就好了,不过这也不难。


浙江温州皮鞋湿,下雨进水不会胖。

猜你喜欢

转载自blog.csdn.net/dog250/article/details/106528185