5 对异常的支持(harib18e)
接下来我们要实现强制结束程序的功能,完成这个功能之后,我们就可以在真机环境下测试crack1.hrb了。
要想强制结束程序,只要在中断号0x0d中注册一个函数即可,这是因为在x86架构规范中,当应用程序试图破坏操作系统,或者试图违背操作系统的设置时,就会自动产生0x0d中断,因此该中断也被称为“异常”。
我们赶紧来写一个_asm_inthandler0d函数吧,代码和_asm_inthandler20大同小异。
本次的naskfunc.nas节选
_asm_inthandler0d:
STI
PUSH ES
PUSH DS
PUSHAD
MOV AX,SS
CMP AX,1*8
JNE .from_app
; 当操作系统活动时产生中断的情况和之前差不多
MOV EAX,ESP
PUSH SS ; 保存中断时的SS
PUSH EAX ; 保存中断时的ESP
MOV AX,SS
MOV DS,AX
MOV ES,AX
CALL _inthandler0d
ADD ESP,8
POPAD
POP DS
POP ES
ADD ESP,4 ; 在INT 0x0d中需要这句
IRETD
.from_app:
; 当应用程序活动时产生中断
CLI
MOV EAX,1*8
MOV DS,AX ; 先仅将DS设定为操作系统用
MOV ECX,[0xfe4] ; 操作系统的ESP
ADD ECX,-8
MOV [ECX+4],SS ; 保存产生中断时的SS
MOV [ECX ],ESP ; 保存产生中断时的ESP
MOV SS,AX
MOV ES,AX
MOV ESP,ECX
STI
CALL _inthandler0d
CLI
CMP EAX,0
JNE .kill
POP ECX
POP EAX
MOV SS,AX ; 将SS恢复为应用程序用
MOV ESP,ECX ; 将ESP恢复为应用程序用
POPAD
POP DS
POP ES
ADD ESP,4 ; INT 0x0d需要这句
IRETD
.kill:
; 将应用程序强制结束
MOV EAX,1*8 ; 操作系统用的DS/SS
MOV ES,AX
MOV SS,AX
MOV DS,AX
MOV FS,AX
MOV GS,AX
MOV ESP,[0xfe4] ; 强制返回到start_app时的ESP
STI ; 切换完成后恢复中断请求
POPAD ; 恢复事先保存的寄存器值
RET
这个函数与_asm_inthandler20的主要区别在于增加了STI/CLI这样控制中断请求禁止、恢复的指令和根据inthandler0d的结果来执行强制结束应用程序的操作。在强制结束时,尽管中断处理完成了但却没有使用IRETD指令,而且还把栈强制恢复到start_app时的状态,使程序返回到cmd_app。可能大家会问,这种奇怪的做法真的没问题吗?是的,完全没问题。
然后我们又写了inthandler0d这样一个函数。
本次的console.c节选
int inthandler0d(int *esp)
{
struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n");
return 1; /*强制结束程序*/
}
这里显示的信息“General Protection Exception”,翻译成中文就是“一般保护异常”,这其实是INT 0x0d的名称。“一般”就是一般性的意思,“保护”就是指对操作系统进行保护。除了一般性的异常,还有一些特殊的异常,这些异常并不是由应用程序的bug和破坏行为所引发的,而是其他种类的异常情况,特殊的的异常会由INT 0x0d以外的中断来处理。
接下来,我们将_asm_inthandler0d注册到IDT中。
本次的dsctbl.c节选
void init_gdtidt(void)
{
(中略)
/* IDT的设置*/
set_gatedesc(idt + 0x0d, (int) asm_inthandler0d, 2 * 8, AR_INTGATE32); /* 这里!*/
set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x40, (int) asm_hrb_api, 2 * 8, AR_INTGATE32);
return;
}
好了,大功告成。
■■■■■
我们来“make run”,并运行crack1.hrb。
异常快出来!……咦?
好奇怪啊,居然没有发生异常,应该出来才对嘛……难道是程序哪里写错了吗?嗯……看上去也没什么问题啊。那我们在真机环境下试试看吧……啊,出来了!
看来,是QEMU对异常的模拟有点bug,而并不是“纸娃娃系统”的bug,大家可以放心了。