内核与应用的分离(下)

问题

 

如何捕获应用程序的异常行为,并进一步处理?

内存保护设计一

当应用程序写入被保护的内存时,将产生 Page Fault (0x0E)

  • Page Fault 可以看作一种特殊的中断类型 (异常)

通过中断服务程序结束当前任务,并调度下一任务执行

  • 即:中断服务程序的主要工作是调用 KillTask()

原理剖析

 应用程序向内核的内存空间写入数据时会触发 Page Fault 异常,在此异常对应的中断服务程序中将触发此异常的任务消灭。

中断与异常的细微差异

中断由外部设备触发或指令主动触发 (不可预见)

异常是指令执行的过程中遇见的错误 (可预见)

中断处理结束后,返回到被中断的指令处执行 (不可预见)

异常处理结束后,返回到触发异常的指令处重新执行 (可预见)

中断不带错误参数,异常可能带有错误参数 (位于栈顶)

异常发生时的压栈

 异常发生时,和中断一样,需要特权级的转移,并且将相关寄存器的值入栈;除此之外,最后还会将错误异常号入栈。

通过 Page Fault 结束异常任务

kentry.asm

%macro BeginFSR 0
	cli
    
    pushad
    
    push ds
    push es
    push fs
    push gs
    
    mov dx, ss
    mov ds, dx
    mov es, dx
    
    mov esp, BaseOfLoader
%endmacro

;
;
PageFaultHandlerEntry:
BeginFSR
	call PageFaultHandler
EndISR

ihandler.c

void PageFaultHandler()
{
	SetPrintPos(0, 6);
	
	PrintString("PageFault  kill: ");
	
	PrintString(gCTaskAddr->name);
	
	KillTask();
}

app.c

void AppMain()
{
	RegApp("TaskA", TaskA, 255);
	RegApp("TaskB", TaskB, 230);
	//RegApp("TaskC", TaskC, 230);
	//RegApp("TaskD", TaskD, 255);
}

void TaskA()
{
    int i = 0;
    int* p = (void*)0xE000;
    
    SetPrintPos(0, 12);
    
    *p = 1000;
    
    PrintString(__FUNCTION__);
    
    PrintIntDec(*p);
    
    while(i < 5)
    {
        SetPrintPos(8, 12);
        PrintChar('A' + i);
        i = (i + 1) % 26;
        Delay(1);
    }
    
    SetPrintPos(8, 12);
}

首先我们注册了 PageFault 中断服务程序,它的中断向量为 0x0E;这个中断服务程序用于打印当前发生 PageFault 异常的任务名,并将此任务消灭。

因为异常发生时还需要将 error_code 入栈,所以我们将保护任务上下文的宏替换成 BeginFSR,这个宏会将 error_code 保存到 RegValue 任务结构体里的 error_code 里,而不是直接跳过它。

TaskA 执行过程中会向内核内存区写数据,当向内核内存区写数据时,会触发 PageFault 异常,从而调用对应的中断服务程序,将当前出现 PageFault 异常的任务消灭。其他任务会继续正常运行。

 问题

如果任务强行修改了页表所在内存区域,会发生什么?

我们可以将页表所在的页的属性设置为应用程序只读,但这样需要修改 ConfigPageTable 函数,我们不这样做。

内存保护设计二

缩小应用程序可使用的内存范围 (如:0xF000 - 4FFFF)

利用段越界所产生的异常保护高地址内存 (如:0x50000)

当发生段越界时 (0x0D)

  • 通过中断服务程序结束当前任务
  • 调度下一个任务执行

具体实现

编写段越界异常所对应的处理函数 ( SegmentFaultHandler() )

编写段越界异常处理入口 ( SegmentFaultHandlerEntry() )

注册 SegmentFaultHandlerEntry 为 0x0D 中断服务处程序

通过 Segment Fault 结束异常任务

ihandler.c

void SegmentFaultHandler()
{
	SetPrintPos(0, 7);
	
	PrintString("SegmentFault  kill: ");
	
	PrintString(gCTaskAddr->name);
	
	KillTask();
}

kentry.asm

;
;
SegmentFaultHandlerEntry:
BeginFSR
	call SegmentFaultHandler
EndISR

task.c

static void InitTask(Task* pt, uint id, const char* name, void(*entry)(), ushort pri)
{
    pt->rv.cs = LDT_CODE32_SELECTOR;
    pt->rv.gs = LDT_VIDEO_SELECTOR;
    pt->rv.ds = LDT_DATA32_SELECTOR;
    pt->rv.es = LDT_DATA32_SELECTOR;
    pt->rv.fs = LDT_DATA32_SELECTOR;
    pt->rv.ss = LDT_DATA32_SELECTOR;
    
    pt->rv.esp = (uint)pt->stack + sizeof(pt->stack);
    pt->rv.eip = (uint)TaskEntry;
    pt->rv.eflags = 0x3202;
    
    pt->id = id;
    
    StrCpy(pt->name, name, sizeof(pt->name) - 1);
    
    pt->tmain = entry;
    
    pt->current = 0;
    pt->total = 256 - pri;
    
    gTSS.ss0 = GDT_DATA32_FLAT_SELECTOR;
    gTSS.esp0 = (uint)&pt->rv + sizeof(pt->rv);
    gTSS.iomb = sizeof(TSS);
    
    SetDescValue(AddrOff(pt->ldt, LDT_VIDEO_INDEX),  0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL3);
    SetDescValue(AddrOff(pt->ldt, LDT_CODE32_INDEX), 0x00,    0x4FFFF, DA_C + DA_32 + DA_DPL3);
    SetDescValue(AddrOff(pt->ldt, LDT_DATA32_INDEX), 0x00,    0x4FFFF, DA_DRW + DA_32 + DA_DPL3);
    
    pt->ldtSelector = GDT_TASK_LDT_SELECTOR;
    pt->tssSelector = GDT_TASK_TSS_SELECTOR;
}

app.c

void TaskA()
{
    int i = 0;
    int* p = (void*)0x50000;
    
    SetPrintPos(0, 12);
    
    // *p = 1000;
    
    PrintString(__FUNCTION__);
    
    PrintIntDec(*p);
    
    while(i < 5)
    {
        SetPrintPos(8, 12);
        PrintChar('A' + i);
        i = (i + 1) % 26;
        Delay(1);
    }
    
    SetPrintPos(8, 12);
}

task.c 中,我们将应用程序的可访问的界限设置为 0x00 - 0x4FFFF,当要访问 0x4FFFF 地址处以上的内存空间时,会触发 SegmentFault 异常,它的中断向量为 0x0D,调用对应的中断服务程序,该中断服务程序将打印触发 SegmentFault 异常的任务名,并将这个任务消灭。

当应用程序想要读 0x50000 地址处的内存时,会触发 SegmentFault 异常,该异常对应的中断服务程序会将 TaskA 消灭,其他任务继续正常运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值