day21保护操作系统

1.某大佬精心总结 链接

2.为何要从16位实模式 进入到 保护模式

①实模式下 你可以随心所欲 使用任何一块地址 ,写入任何你想写入的数据
这个地址 甚至可能是OS的代码你都可以随意修改
②进入到保护模式,是对内存进行了等级划分,防止你有问题的程序去破坏其他的程序 或者 OS的代码
③GDT就是一个保护的例子,他限制了你的最大偏移长度,权限等

3.段描述符如何实现保护模式

在段描述符中,主要将有三类字段来协同参与实现保护功能:

①.段限长—LIMIT。该字段规定了每个定义段的长度,比如你要跳转的程序长度超过了的CS定义的段长度或者要访问的数据内存地址超过了DS的定义的段长度,就会报错。

②.段类型—TYPE。我们知道,每个段都可以定义了可执行、可读写等类型,如果你要在只读的内存段写数据,无疑会报错。

③.描述符特权级—DPL。这个字段除了存在于普通的段描述符中外,还存在于各类门描述符中。该字段用来表示本段的特权级别,显然操作系统段的特权级最高,应用程序段的特权级最低,如果你要在应用程序中对操作系统的段做写入操作,就会被阻止。

4.无保护模式下

破坏OS的代码

void HariMain(void)
{
	*((char *) 0x00102600) = 0;
	return;
}

①直接往内存地址0x00102600写成0,0x00102600操作实际就是软盘第一个文件名被修改成了0。操作系统看到文件名是0x00开头的,认为是异常(其内核程序是这么设计的),因此再执行任何dir,type或运行任何应用程序都不会有反应,这样就相当于操作系统被坏了。

5.给应用程序分配专门的段

上面应用程序搞瘫操作系统,最的问题就是操作系统和应用程序的数据段没有做区分所致,操作系统和应用程序的数据段、堆栈段都混为一团,其实就和实模式的管理方法没有本质区别。所以,为应用程序分配独立的段,是最基本的要求。
应用程序的内存空间都是临时申请的,并把这个临时内存空间注册到GDT中就形成了应用程序的代码段和数据段。需要注意的是:代码段的长度是应用程序的实际大小,而数据段的长度由于不好估计,因此这里固定申请为64KB。可以看到,无论是操作系统还是应用程序都只有代码段和数据段,并没有再设置堆栈段。这是因为我们的堆栈寄存器SS一直是和数据类寄存器DS\ES\FS\GS保持的同一个值,可以理解为堆栈段就是用的是数据段,那么在同一个程序中,就只需要重新定义ESP栈顶的指针值就可以了,由于这次我们给应用程序申请的数据区长度是64KB,那么我们把ESP栈顶指针设置在这64KB的最顶端最适合了

p = (char *)MemoryManagerMalloc4K(MemMan, pFileInfo->u32FileSize);

pAppDataBuf = (char *)MemoryManagerMalloc4K(MemMan, 64*1024);

*((int *) 0xfe8) = (int) p;//把段的首地址放到0xfe8

FileLoadFile(pFileInfo->u16WriteClustno, pFileInfo->u32FileSize,
 p, pFat, (char *)(ADR_DISKIMG + 0x003e00));

//应用程序代码段 数据段
SetSegmDesc(pGdt + 1003, pFileInfo->u32FileSize - 1,
 (int)p, AR_CODE32_ER);
SetSegmDesc(pGdt + 1004, 64*1024-1, (int)pAppDataBuf,
 AR_DATA32_RW);
start_app(0, 1003*8, 64*1024, 1004*8);
//app和操作系统的代码不在一个段内
//我们需要传递过去 让OS能切换到应用所在的段 
 MemoryManagerFree4K(MemMan, (int)p, pFileInfo->u32FileSize);
 MemoryManagerFree4K(MemMan, (int)pAppDataBuf, 64*1024-1);

这里用start_app()来启动应用程序,对应参数分别为应用程序启动之后新的EIP值和新的CS,ESP和DS(SS)段地址之值。这样,我们再执行应用程序:*((char *) 0x00102600) = 0的时候,它操作的内存区就不再是操作系统数据段了,而是应用程序的数据段,所以不会对操作系统造成破坏,我们达到了初级保护的目的

6.OS调用应用程序,应用程序又需要调用OS的函数,所以二者需要来回切换,而且在应用程序运行的过程当中,OS的一些中断服务仍在运行,也需要被切回

① 应用程序的启停

我们需要在操作系统里写一个启动应用程序的内核程序start_app(),策略是在应用程序启动之前,通过操作系统的堆栈先保存好操作系统的数据段寄存器(DS和SS等)和操作系统的堆栈指针ESP,再把应用程序的数据段和应用程序的堆栈指针ESP写入相应寄存器,然后开始调用应用程序。应用程序执行完成之后返回之后,操作系统还原之前的各个寄存器值即可。这里为什么不需要将操作系统的代码段CS和EIP入栈保护和恢复呢?这是因为这两个值是通过call和ret等配合调用自动切换的,CPU会自动管理,不需要我们操心。

保存和恢复操作系统普通的段寄存器DS和SS等非常容易,用传统的PUSH和POP即可。但是要保存和恢复操作系统堆栈指针ESP,是不能简单的使用PUSH和POP就行的,:POP ESP 指令不但没有意义,反而它会使程序异常,严重的话计算机会崩溃,应绝对禁用!

;由于OS系统的代码 和 APP的代码不在一个段中 需要进行切换
_start_app:		; void start_app(int EIP, int CS, int ESP, int DS);
		PUSHAD		; 将OS 32位寄存器的值全部保存起来
		MOV		EAX,[ESP+36]	; 应用程序的EIP 0
		MOV		ECX,[ESP+40]	; 应用程序的CS 1003*8
		MOV		EDX,[ESP+44]	; 应用程序的ESP 64*1024
		MOV		EBX,[ESP+48]	; 应用程序的DS/SS 1004*8
		MOV		[0xfe4],ESP		; OS的ESP 保存在内存0xfe4 这个位置

		CLI			; 进行切换的时候 禁止所有的中断
		MOV		ES,BX
		MOV		SS,BX
		MOV		DS,BX
		MOV		FS,BX
		MOV		GS,BX
		MOV		ESP,EDX
		STI			; 切换完成后 恢复中断请求

		PUSH	ECX				; far-CALL push(cs)
		PUSH	EAX				; far-CALL  push(eip)
		CALL	FAR [ESP]		; 调用应用程序

;	应用程序结束后 返回此处
;然后我们返回到操作系统的段中

		MOV		EAX,1*8			; OS的DS/SS

		CLI			; 切换中禁止所有的中断
		MOV		ES,AX
		MOV		SS,AX
		MOV		DS,AX
		MOV		FS,AX
		MOV		GS,AX
		MOV		ESP,[0xfe4]
		STI			; 切换完成后 恢复中断请求

		POPAD	;   恢复所有的寄存器
		RET

② 应用程序调用操作系统API
我们一个应用程序要调用OS的字符串显示API,他两又不在一个段中,如何解决呢?
操作系统的字符串显示API

//字符串显示API
void HRB_API(int EDI, int ESI, int EBP, int ESP, int EBX, int EDX, int ECX, int EAX)
{
    struct CONSOLE* pConsoleInfo = (struct CONSOLE*)*( (int *) 0x0fec);

    int CS_Base = *((int *) 0xfe8);

    if(1 == EDX)
    {
        ConsolePutChar(pConsoleInfo, EAX&0xff, 1);
    }
    else if(2 == EDX)
    {
        ConsolePutString0(pConsoleInfo, (char *)EBX + CS_Base);
    }
    else if(3 == EDX)
    {
        ConsolePutStringLen(pConsoleInfo, (char *)EBX + CS_Base, ECX);
    }

    return;
}

**如何从应用程序 调用API **

;_HRB_API这个函数是OS系统代码 而 _asm_hrb_api 是由应用程序调用的
_asm_hrb_api:
		; 从开始 就禁止中断请求
		PUSH	DS  ;此时我们保存应用程序的DS和ES
		PUSH	ES

		PUSHAD		; 我们把这 八个参数全部压栈
		;void HRB_API(int EDI, int ESI, int EBP, int ESP, 
		;int EBX, int EDX, int ECX, int EAX)
		
        MOV		EAX,1*8
		MOV		DS,AX			; 我们仅将DS改成操作系统的数据段
		MOV		ECX,[0xfe4]		; OS的ESP
		ADD		ECX,-40
		MOV		[ECX+32],ESP	; 保存应用程序的ESP
		MOV		[ECX+36],SS		; 保存应用程序的SS

; 上面的PUSHAD把八个参数都保存在了应用程序的栈中 ,
;我们现在把他写到 OS的栈中
		MOV		EDX,[ESP   ]
		MOV		EBX,[ESP+ 4]
		MOV		[ECX   ],EDX	; hrb_api的第一个参数
		MOV		[ECX+ 4],EBX	; hrb_api的第二个参数
		MOV		EDX,[ESP+ 8]
		MOV		EBX,[ESP+12]
		MOV		[ECX+ 8],EDX	; hrb_api的第三个参数
		MOV		[ECX+12],EBX	; hrb_api的第四个参数
		MOV		EDX,[ESP+16]
		MOV		EBX,[ESP+20]
		MOV		[ECX+16],EDX	; hrb_api的第五个参数
		MOV		[ECX+20],EBX	; hrb_api的第六个参数
		MOV		EDX,[ESP+24]
		MOV		EBX,[ESP+28]
		MOV		[ECX+24],EDX	; hrb_api的第七个参数
		MOV		[ECX+28],EBX	; hrb_api的第八个参数

		MOV		ES,AX			; 把剩余寄存器也设为操作系统使用
		MOV		SS,AX
		MOV		ESP,ECX
		STI			; 恢复中断请求

		CALL	_HRB_API

		MOV		ECX,[ESP+32]	; 恢复应用程序的ESP
		MOV		EAX,[ESP+36]	; 恢复应用程序的SS
		
        CLI
		MOV		SS,AX
		MOV		ESP,ECX
		POPAD
		POP		ES
		POP		DS
		IRETD		; 这个命令会自动执行STI 

HRB_API是操作系统的字符串显示函数,他需要八个参数,这八个参数存在于应用程序的栈中,我们要做的就是要把这八个参数 拷贝到OS的栈中,并且切换到操作系统
这个程序的步骤分为以下几步:

(1) 保存应用程序的所有段。偶先保存数据段DS等,这个和我们之前说的思路是一致的。但是这里需要注意一个问题,就是刚开始进入程序的时候,所有的段和寄存器内容都是应用程序的,所有这些内容也只能暂时存在应用程序的堆栈区。

(2) 切换到操作系统所有段。这个过程里面最复杂的就是操作系统和应用程序之间的栈切换,需要具体来仔细分析。我们最终的目的是要调用操作系统C程序函数hrbapi,由于这个函数hrbapi的8个入口参数是在应用程序里的8个32位寄存器,我们首先需要把8个32位寄存器压入操作系统的栈才行。但现在SS和ESP指向的是应用程序,故应将SS和ESP先切换到操作系统。SS不能直接赋值,只能通过AX等中转,因此我们需要用mov ss, ax指令;要切换到操作系统ESP,方法是从上一节start_app()启动应用程序中暂存在内存地址[0xfe4]中取,所以得采用mov esp [0xfe4]的形式,但是要访问到操作系统的[0xfe4],又要先把DS切换到操作系统:mov ds, 18,但是这样的指令是不运行的,必须借助寄存器:mov ds, ax。所以要实现将SS和ESP先切换到操作系统,必须要用以下条指令:mov ax, 18, mov ss, ax,mov ds, ax。这样,我们还没来得及将应用程序的8个32位寄存器入到操作系统的栈区,就已经修改了EAX了,所以这个方法不行。我们只有换一种方法:先把应用程序的8个32位寄存器存入应用程序的堆栈区,然后将SS和ESP切换到操作系统,再将这个8个32位寄存器从应用程序的堆栈区复制到操作系统的堆栈区
(3) 恢复应用程序所有段。当操作系统所有的事情都完成之后,就要恢复所有的应用程序所有段了,注意顺序是先恢复栈类数据SS和ESP,因为8位32的寄存器还全部放在应用程序的栈内呢,程序的最后才恢复数据段。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值