int3斷點指令的原理和示例

今天有人讓我解釋一下斷點調試的原理,我就想先解釋一下int3指令,就寫了一篇短文。

單字節指令int3的二進制碼是0xcc,它的效果是:

  • 處理器執行到0xcc時,會陷入內核,執行int3的異常處理代碼,比如給當前進程發送一個SIGTRAP信號。

就這麼簡單。剩下的就看信號處理程序如何發揮了。

我給出一個簡單的代碼,演示一下一個程序的 自我單步跟蹤 如何實現:

#include <stdio.h>
#include <sys/mman.h>
#include <signal.h>

unsigned char old;
unsigned char *inst;

// RIP距離stack的當前位置正好192字節,詳情參見rt_sigframe結構
#define PC_OFFSET		192

// value ++指令從main函數的第106字節開始,這個是從objdump -D看出來的。
static int i = 106;

// 這是我們單步跟蹤的變量。
int value = 0;

// 打斷點需要兩步:
// 1. 保存原始單字節指令。
// 2. 替換爲int3指令。
void breakpoint(char *inst)
{
	old = *inst;
	*inst = 0xcc;
}

void trap(int unused)
{
	unsigned long *p;

	// 恢復斷點需要兩步:
	// 1. 恢復單字節爲保存的指令。
	// 2. PC寄存器回退一個字節。
	p = (unsigned long*)((unsigned char *)&p + PC_OFFSET);
	// 可以在這裏來一個命令行查詢更詳細的程序當前狀態,我這裏僅僅一行打印,說明問題夠了。
	printf("current RIP: :%lx  value:%d\n", *p, value);
	inst[i] = old;
	*p = *p - 1;
	// 單步跟蹤value的值:
	// value ++正好需要15個字節的指令。
	i += 15;
	// 設置新的斷點爲下一個value ++
	breakpoint(&inst[i]);
}

int main(int argc, char **argv)
{
	unsigned char *page;
	unsigned long rip;
	signal(SIGTRAP, trap);

	inst = (unsigned char *)main;
	// addr按照4096對齊
	page = (unsigned char *)((unsigned long)inst & 0xfffffffffffff100);
	// 讓main函數可寫。
	mprotect((void *)page, 4096, PROT_WRITE|PROT_READ|PROT_EXEC);
	// 設置第一個斷點爲第一個value ++。
	breakpoint(&inst[i]); 

	value ++;
	value ++;
	value ++;
	value ++;
	value ++;
	value ++;
	printf("value is %d\n", value);
}

來吧,看看效果:

[root@localhost test]# ./int3test
current RIP: :4006c6  value:0
current RIP: :4006d5  value:1
current RIP: :4006e4  value:2
current RIP: :4006f3  value:3
current RIP: :400702  value:4
current RIP: :400711  value:5
current RIP: :400720  value:6
value is 6

文章很短,可以仔細體會一下。

關於int3指令,下面這個鏈接解釋了爲什麼int3是單字節指令:
http://www.cs.columbia.edu/~junfeng/09sp-w4118/lectures/int3/int3.txt


浙江溫州皮鞋溼,下雨進水不會胖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章