X86汇编语言:从事模式到保护模式读书笔记

           ----------特权级保护

PS:此文并没有多长,只不过末尾附上了完整源代码占了90%的篇幅

<这是链接——保护模式下的RPL(一)——–>

1》只在第一次引用一个段的时候进行特权级检查
(你能进入就说明你用有访问权限,此后无需再检查)
2》不允许直接从特权级高的代码段转移到特权级
的代码段
(特权级比你还低,这样的程序信不过,所以禁止)
3》允许特权级高的代码段引用特权级低的数据段

整个内核程序组成

概念:
PL (Privilege Level) :特权级别
CPL(Current Privilege Level) :当前执行的代码段的特权级别,其
值就是段寄存器中的选择子的PL字段
RPL(Request Privilege Level) :选择子中的PL字段,它代表的是
调用者的特权级别
DPL(Description Privilege Level):段描述符中的DPL字段

特权级检查时刻:第一次引用一个段的时候即将一个选择子送到段寄存器之前。例如上图中在将选择子0x0008送入CS之前进行特权级检查

为什么需要RPL?
答:确保特权代码不会代替应用程序访问一个段,除非应用程序拥有访问那个段的权限。举个例子:特权级为3的user调用OS库函数read从磁盘读取数据到一个数据段中。如果user知道了OS数据段的段选择子并不怀好意的把它当作参数传给read,此时RPL就发挥作用了。具体如下:
1》检查当前特权级是否高于或等于数据段描述符的DPL
2》 检查请求特权级是否高于或等于数据段描述符的DPL
3》一旦上面任意一个条件不满足就引发异常中断。
为更好地理解上面那段话我们需要明白这样一个事实:处理器所做的只是机械的检查RPL,CPL,DPL是否满足条件,并不具备鉴别RPL的有效性的能力。什么意思呢?其实意思就是:处理器说我只负责检查RPL,不能判断RPL是否有效,所以OS你必须给我提供正确的RPL字段,如果OS你提供了错误的RPL给我,我也会按照上面的规则进行特权级检查,如果错误的选择子通过了上面的检查,那么就会代入不该代入的选择子,那么系统就很有可能因此崩溃,但这是你OS的错,不管我的事。接着上面的例子:user的PL为3,传递给read函数的数据段选择子的RPL应该为调用者的PL即(3),如果user想写OS的数据段,那么在把选择子送入DS之前进行特权级检查。因为此时已经转到os内核执行了,故CPL为0,OS数据段的DPL=0,第一个条件通过,接下来RPL为3 低于OS数据段的DPL,于是引发异常中断,如果OS提供了错误的RPL字段即RPL=0,那么就会通过上面的特权级检查,那么磁盘读取的数据就可以写到OS的数据段了,系统数据段被篡改,那么系统就很可能因此崩溃,但这一切都是OS自己的错。

接上面:OS一定能提供正确的选择子吗?又是如何提供正确的选择子呢 ?
答:OS一定能提供正确的选择子。什么意思呢?
来人,上代码!

;输入:EDX:EAX=描述符
;      EBX=TCB基地址
;返回:CX=描述符的选择子
;说明:此函数可能为多个不同特权级的
;      程序创建LDT描述符,故RPL字段
;      通通设为0,由OS去修改去
fill_descriptor_in_ldt:.........or  cx,0x0004  ;选择子返回之前设置TI位表示;描述符在LDT中并且将RPL设为00.....ret
;接下来看OS如何修改RPL
;功能:加载并重定位用户程序
;参数:push逻辑扇区号
;      push任务控制块基地址
;返回:无
load_relocate_program:.....mov ebx,esi;TCB的基地址call fill_descriptor_in_ldt;此调用返回的选择子的;RPL字段为0or cx,0x0003 ;设置选择子的RPL为3(用户程序)
;***************VeryImportant:************************
;** ;OS和CPU两个协议好了:OS向cpu保证提供的选
;**  择子中一定包含正确的RPL,而CPU负责检查RPL
;** ;OS很清楚当前加载的程序的特权级,于是OS
;** 就一定能填充正确的RPL到选择子中(457行)
;**;OS怎么就知道它的特权级呢?那是因为load_relocate_program
;** 就是专门用来加载特权级别为3的用户程序的,只要调用这个
;** 函数OS就知道RPL字段一定是3 。如果要加载特权级别为2
;** 的程序,那么可以写一个专门用来加载特权级为2的程序的函数
;** 只要调用此函数就知道特权级一定为2,于是OS就能保证提供
;** 选择子的RPL字段是正确的
;***************************************************

我们从上一篇文章知道如果用户程序通过存放在头部的选择子来引用一个段,那么选择子中的RPL字段就会在os加载用户程序时修改为用户程序的特权级,但是如果这样调用呢:还是上面的read调用
;假设read用到的OS的数据段选择子为0xf0,并通过eax传递
mov eax,0xf0
call far[fs:read];
;乍一看:数据段选择子的0xf0的RPL字段不是00吗
;不是说如果请求特权级高于或等于数据段描述符的DPL(00)
;就可以通过检查了吗?这里RPL刚好等于DPL,然后不就可以
;将数据读到OS数据段了吗 ? 别急 ,讲完调用门再解释

发生段间转移时存在特权级检查:
1》jmp非依从段:只能在相同特权级段之间转移,不改变CPL
2》jmp依从段 :从低特权级转移到相同特权级或高特权级,不改变 CPL(这个我也不大懂)
3》call非依从段:在相同特权级段之间转移,不改变CPL
4》call非依从段:低特权级别转移到高特权级,必须通过调用门转移,
会修改CPL为目标代码的特权级
5》call依从段 :同2》

调用门:用户程序的PL字段为3,而内核提供的系统调用的PL字段为0,
而处理器禁止从低特权级转移到高特权级,所以需要特殊的方
法,此即调用门。(注:调用门是cpu硬件支持并且也是cpu要
求的)
调用调用门时的特权级检查:
1》当前特权级(CPL)要高于或等于调用门的DPL字段
请求特权级(RPL)要高于或等于调用门的DPL字段
2》当前特权级(CPL)要低于或等于目标代码段
的描述符的DPL字段
注:条件1规定了当前程序是否有权调用该调用门
条件2规定了当前程序是否允许跳转到目标代码段执行

从内核转移到用户程序并执行这个过程是从高特权级转移到低特权级,但是前面说过不允许直接从特权级高的代码段转移到特权级低的代码段。乍一看,这不自相矛盾吗?仔细分析一下调用门就OK了。从低特权级调用调用门,然后转到高特权级代码执行,然后再返回。咦,返回?不就是从高特权级返回到低特权级吗?是的,也就是说处理器允许从高特权级返回到低特权级但必须是通过调用门的retf指令实现转移,也就是说我们只要模拟调用门的环境然后通过执行retf指令就可以实现把控制权从内核转移到用户程序了
话不多说,上代码:

;模拟从调用门返回将控制权转移到用户程序
;这里需要/模拟/需要改变特权级/的情况/(内核到用户,0到3)
;模仿处理器压入参数
push dword [0x08];调用者的堆栈段选择子
push dword 0    ;调用者的esp=0
push dword [0x14];调用者的代码段选择子
push dword [0x10];调用者的eip
;至此,调用门环境已经模拟出来了,接下来就该返回了
retf            ;核心指令:将控制权转移到用户程序

;模拟从调用门返回将控制权转移到用户程序
;类似于《王爽汇编语言》中模拟中断,然后再”调用”中断例程
;相同点:
;跳转前:在程序跳转前由硬件完成一些列操作
; 调用门:如果要改变特权级则硬件实现栈切换同时SS:SP入栈和
; 复制参数到新栈,再cs:ip入栈,跳转
; 中断 :硬件实现取中断号,cs:ip入栈,EFLAGSREG入栈,关IF关TF,跳转
;返回时:
; 调用门:retf返回,当执行retf指令时cpu自动进行各种检查
; 中断 :iret返回
; 方法 :模拟的关键点在于最后的返回指令,
; iret要求栈中必须含有CS:IP,retf则要求3~12行
; 就是说用软件完成与硬件相同的的操作,即可创建出返回指令
; 返回所需的条件,即完成软件模拟

调用门返回时进行特权级检查:
当一个用户程序通过调用门调用系统调用,调用结束后将要返回
到用户程序时”CPU”所做工作的最后一步便是检查DS,ES,FS,GS
段寄存器的内容,通过选择子找到段描述符。如果有任意一个
段描述符的DPL字段高于(没有等于)调用者的特权级,那么就会将00送往
该段寄存器。什么意思呢?举个例子:一个特权级为3的用户
程序调用特权级为0的系统调用read读取一段数据,read函数
需要用到操作系统的某些私有数据,read函数通过ES访问
该私有数据空间,于是乎ES中存放的便是OS私有数据段的
选择子,并且在read执行完成后OS私有数据段的选择子依
旧放在ES中,如果user知道了这个秘密,user就可能
会对OS私有数据段有点想法,便想通过ES访问私有数据段。
于是此时最后一步便派上用场了:作为read调用者的user
的特权级别为3,而OS私有数据段的特权级为0,
“CPU”通过这一步检查便知道ES所指的数据段是OS的,
不能随便给用户程序用,(OS和CPU互相帮助,共同抵御
邪恶的user)于是便把该ES段寄存器内的选择子置0
,因为使用全0的选择子访问内存会引发异常中断
,于是user便不敢再妄想通过ES修改OS数据段了
这正和CPU的意:随你用,只要你敢,你不崩溃
算我输(从这点来说:也许CPU比user更坏)

好了,讲完了调用门,我们现在来回答上面那个问题。
mov eax,0xf0
call far[fs:read];
在调用调用门时尽管你提供的数据段选择子的RPL字段为00,但是OS还是有办法知道你提供的RPL是否是正确的。OS是怎么做的呢?为此CPU专门提供了arpl指令来调整段选择子RPL字段的值(Adjust RPL Field of Segment Selector)。
arpl r/m16,r16 (arpl 目的操作数,源操作数)
该指令比较两个段选择子的RPL字段,如果目的操作数的RPL在数值上小于源操作数的RPL字段,则设置ZF标志,并且调正目的操作数的RPL使之和源操作数的RPL字段的值相同。arpl指令是典型的操作系统指令,它通常用于调整传递给操作系统的段选择子,使其RPL字段和应用程序的特权级相匹配,在这种情况下传递给OS的段选择子是作为目的操作数出现的,而应用程序的代码段选择子是作为源操作数出现的,这样,为了防止恶意的数据访问,操作系统应该从当前栈中取得用户程序的代码段选择子(PL=3),作为源操作数,并把当作参数传进来的数据段选择子0xf0(RPL=0)作为目的操作数来执行arpl指令,如果RPL字段不匹配(很明显3!=0),则把数据段选择子的RPL字段调整为应用程序的特权级别3(0xf0调整为0xf3),那么在read函数在将数据段选择子代入段寄存器时将不会通过特权级检查(前面说过:1》检查CPL是否高于数据段DPL 2》检查RPL是否高于数据段的DPL,很明显第一条可以通过,而第二条则通不过,就会引发异常中断,然后应用程序的阴谋便无法得逞了,不是吗?)
read函数可能是这样的

read:get_user_cs_from_stackput_it_at_ebxarpl  ebx,eaxdo_read

最后可能有人还有点小疑问:应用程序的CS怎么就会放到栈中呢?再回过头去看下我们是如何模拟调用门环境的:在通过调用门执行read之前,硬件会进行一系列操作以供日后从调用门返回时用,其中就包括将CS段寄存器中的选择子入栈,而这个选择子的PL字段代表的一定是应用程序的特权级,于是OS不就可以得到应用程序的特权级了吗?
V

————第14章读书笔记 · 完———

文章末尾附上“内核”(也许担不起内核这个名字)完整源代码:
四个文件:
引导程序负责从实模式进入保护模式并加载内核程序到内存,写入0号扇区
内核程序负责加载一个用户程序,写入1号扇区
用户程序完成从硬盘读一个文件然后在屏幕上显示,写入50号扇区
数据文件,写入100号扇区
//引导程序

;代码清单13-1;文件名:c13_mbr.asm;文件说明:硬盘主引导扇区代码 ;创建日期:2011-10-28 22:35        ;设置堆栈段和栈指针 core_base_address equ 0x00040000   ;常数,内核加载的起始内存地址 core_start_sector equ 0x00000001   ;常数,内核的起始逻辑扇区号 mov ax,cs      mov ss,axmov sp,0x7c00;计算GDT所在的逻辑段地址mov eax,[cs:pgdt+0x7c00+0x02]      ;GDT的32位物理地址 xor edx,edxmov ebx,16div ebx                            ;分解成16位逻辑地址 mov ds,eax                         ;令DS指向该段以进行操作mov ebx,edx                        ;段内起始偏移地址 ;跳过0#号描述符的槽位 ;创建1#描述符,这是一个数据段,对应0~4GB的线性地址空间mov dword [ebx+0x08],0x0000ffff    ;基地址为0,段界限为0xFFFFFmov dword [ebx+0x0c],0x00cf9200    ;粒度为4KB,存储器段描述符 ;创建保护模式下初始代码段描述符mov dword [ebx+0x10],0x7c0001ff    ;基地址为0x00007c00,界限0x1FF mov dword [ebx+0x14],0x00409800    ;粒度为1个字节,代码段描述符 ;建立保护模式下的堆栈段描述符      ;基地址为0x00007C00,界限0xFFFFE mov dword [ebx+0x18],0x7c00fffe    ;粒度为4KB mov dword [ebx+0x1c],0x00cf9600;建立保护模式下的显示缓冲区描述符   mov dword [ebx+0x20],0x80007fff    ;基地址为0x000B8000,界限0x07FFF mov dword [ebx+0x24],0x0040920b    ;粒度为字节;初始化描述符表寄存器GDTRmov word [cs: pgdt+0x7c00],39      ;描述符表的界限   lgdt [cs: pgdt+0x7c00]in al,0x92                         ;南桥芯片内的端口 or al,0000_0010Bout 0x92,al                        ;打开A20cli                                ;中断机制尚未工作mov eax,cr0or eax,1mov cr0,eax                        ;设置PE位;以下进入保护模式... ...jmp dword 0x0010:flush             ;16位的描述符选择子:32位偏移;清流水线并串行化处理器[bits 32]               flush:                                  mov eax,0x0008                     ;加载数据段(0..4GB)选择子mov ds,eaxmov eax,0x0018                     ;加载堆栈段选择子 mov ss,eaxxor esp,esp                        ;堆栈指针 <- 0 ;以下加载系统核心程序 mov edi,core_base_address mov eax,core_start_sectormov ebx,edi                        ;起始地址 call read_hard_disk_0              ;以下读取程序的起始部分(一个扇区) ;以下判断整个程序有多大mov eax,[edi]                      ;核心程序尺寸xor edx,edx mov ecx,512                        ;512字节每扇区div ecxor edx,edxjnz @1                             ;未除尽,因此结果比实际扇区数少1 dec eax                            ;已经读了一个扇区,扇区总数减1 @1:or eax,eax                         ;考虑实际长度≤512个字节的情况 jz setup                           ;EAX=0 ?;读取剩余的扇区mov ecx,eax                        ;32位模式下的LOOP使用ECXmov eax,core_start_sectorinc eax                            ;从下一个逻辑扇区接着读@2:call read_hard_disk_0inc eaxloop @2                            ;循环读,直到读完整个内核 setup:mov esi,[0x7c00+pgdt+0x02]         ;不可以在代码段内寻址pgdt,但可以;通过4GB的段来访问;建立公用例程段描述符mov eax,[edi+0x04]                 ;公用例程代码段起始汇编地址mov ebx,[edi+0x08]                 ;核心数据段汇编地址sub ebx,eaxdec ebx                            ;公用例程段界限 add eax,edi                        ;公用例程段基地址mov ecx,0x00409800                 ;字节粒度的代码段描述符call make_gdt_descriptormov [esi+0x28],eaxmov [esi+0x2c],edx;建立核心数据段描述符mov eax,[edi+0x08]                 ;核心数据段起始汇编地址mov ebx,[edi+0x0c]                 ;核心代码段汇编地址 sub ebx,eaxdec ebx                            ;核心数据段界限add eax,edi                        ;核心数据段基地址mov ecx,0x00409200                 ;字节粒度的数据段描述符 call make_gdt_descriptormov [esi+0x30],eaxmov [esi+0x34],edx ;建立核心代码段描述符mov eax,[edi+0x0c]                 ;核心代码段起始汇编地址mov ebx,[edi+0x00]                 ;程序总长度sub ebx,eaxdec ebx                            ;核心代码段界限add eax,edi                        ;核心代码段基地址mov ecx,0x00409800                 ;字节粒度的代码段描述符call make_gdt_descriptormov [esi+0x38],eaxmov [esi+0x3c],edxmov word [0x7c00+pgdt],63          ;描述符表的界限lgdt [0x7c00+pgdt]                  jmp far [edi+0x10]  ;-------------------------------------------------------------------------------
read_hard_disk_0:                        ;从硬盘读取一个逻辑扇区;EAX=逻辑扇区号;DS:EBX=目标缓冲区地址;返回:EBX=EBX+512 push eax push ecxpush edxpush eaxmov dx,0x1f2mov al,1out dx,al                       ;读取的扇区数inc dx                          ;0x1f3pop eaxout dx,al                       ;LBA地址7~0inc dx                          ;0x1f4mov cl,8shr eax,clout dx,al                       ;LBA地址15~8inc dx                          ;0x1f5shr eax,clout dx,al                       ;LBA地址23~16inc dx                          ;0x1f6shr eax,clor al,0xe0                      ;第一硬盘  LBA地址27~24out dx,alinc dx                          ;0x1f7mov al,0x20                     ;读命令out dx,al.waits:in al,dxand al,0x88cmp al,0x08jnz .waits                      ;不忙,且硬盘已准备好数据传输 mov ecx,256                     ;总共要读取的字数mov dx,0x1f0.readw:in ax,dxmov [ebx],axadd ebx,2loop .readwpop edxpop ecxpop eaxret;-------------------------------------------------------------------------------
make_gdt_descriptor:         ;构造描述符;输入:EAX=线性基地址;      EBX=段界限;      ECX=属性(各属性位都在原始;      位置,其它没用到的位置0) ;返回:EDX:EAX=完整的描述符mov edx,eaxshl eax,16                     or ax,bx            ;描述符前32位(EAX)构造完毕and edx,0xffff0000  ;清除基地址中无关的位rol edx,8bswap edx             ;装配基址的31~24和23~16  (80486+)xor bx,bxor edx,ebx          ;装配段界限的高4位or edx,ecx                      ;装配属性 ret;-------------------------------------------------------------------------------pgdt             dw 0dd 0x00007e00      ;GDT的物理地址
;-------------------------------------------------------------------------------                             times 510-($-$$) db 0db 0x55,0xaa

//接下来是内核源代码

         ;程序清单:特权级检查示例;文件说明:内核程序;创建日期:2017/4/21 10:59;作者      :邓朝丰;。。。此程序有BUG。。无法运行。。;BUG个头啊,见第352行user_sector           equ  0x32
;以下常量定义部分。内核的大部分内容都应当固定core_code_seg_sel     equ  0x38    ;内核代码段选择子core_data_seg_sel     equ  0x30    ;内核数据段选择子sys_routine_seg_sel   equ  0x28    ;系统公共例程代码段的选择子video_ram_seg_sel     equ  0x20    ;视频显示缓冲区的段选择子core_stack_seg_sel    equ  0x18    ;内核堆栈段选择子mem_0_4_gb_seg_sel    equ  0x08    ;整个0-4GB内存的段的选择子;-------------------------------------------------------------------------------;以下是系统核心的头部,用于加载核心程序core_length      dd core_end       ;核心程序总长度#00sys_routine_seg  dd section.sys_routine.start;系统公用例程段位置#04core_data_seg    dd section.core_data.start;核心数据段位置#08core_code_seg    dd section.core_code.start;核心代码段位置#0ccore_entry       dd start          ;核心代码段入口点#10dw core_code_seg_sel;===============================================================================[bits 32]
;===============================================================================
SECTION sys_routine vstart=0                ;系统公共例程代码段
;-------------------------------------------------------------------------------;字符串显示例程
put_string:                                 ;显示0终止的字符串并移动光标;输入:DS:EBX=串地址push ecx.getc:mov cl,[ebx]or cl,cljz .exitcall put_charinc ebxjmp .getc.exit:pop ecxretf                               ;段间返回;-------------------------------------------------------------------------------
put_char:                                   ;在当前光标处显示一个字符,并推进;光标。仅用于段内调用;输入:CL=字符ASCII码pushad;以下取当前光标位置mov dx,0x3d4mov al,0x0eout dx,alinc dx                             ;0x3d5in al,dx                           ;高字mov ah,aldec dx                             ;0x3d4mov al,0x0fout dx,alinc dx                             ;0x3d5in al,dx                           ;低字mov bx,ax                          ;BX=代表光标位置的16位数cmp cl,0x0d                        ;回车符?jnz .put_0amov ax,bxmov bl,80div blmul blmov bx,axjmp .set_cursor.put_0a:cmp cl,0x0a                        ;换行符?jnz .put_otheradd bx,80jmp .roll_screen.put_other:                               ;正常显示字符push esmov eax,video_ram_seg_sel          ;0xb8000段的选择子mov es,eaxshl bx,1mov [es:bx],clpop es;以下将光标位置推进一个字符shr bx,1inc bx.roll_screen:cmp bx,2000                        ;光标超出屏幕?滚屏jl .set_cursorpush dspush esmov eax,video_ram_seg_selmov ds,eaxmov es,eaxcldmov esi,0xa0                       ;小心!32位模式下movsb/w/dmov edi,0x00                       ;使用的是esi/edi/ecxmov ecx,1920rep movsdmov bx,3840                        ;清除屏幕最底一行mov ecx,80                         ;32位程序应该使用ECX.cls:mov word[es:bx],0x0720add bx,2loop .clspop espop dsmov bx,1920.set_cursor:mov dx,0x3d4mov al,0x0eout dx,alinc dx                             ;0x3d5mov al,bhout dx,aldec dx                             ;0x3d4mov al,0x0fout dx,alinc dx                             ;0x3d5mov al,blout dx,alpopadret;-------------------------------------------------------------------------------
read_hard_disk_0:                           ;从硬盘读取一个逻辑扇区;EAX=逻辑扇区号;DS:EBX=目标缓冲区地址;返回:EBX=EBX+512push eaxpush ecxpush edxpush eaxmov dx,0x1f2mov al,1out dx,al                          ;读取的扇区数inc dx                             ;0x1f3pop eaxout dx,al                          ;LBA地址7~0inc dx                             ;0x1f4mov cl,8shr eax,clout dx,al                          ;LBA地址15~8inc dx                             ;0x1f5shr eax,clout dx,al                          ;LBA地址23~16inc dx                             ;0x1f6shr eax,clor al,0xe0                         ;第一硬盘  LBA地址27~24out dx,alinc dx                             ;0x1f7mov al,0x20                        ;读命令out dx,al.waits:in al,dxand al,0x88cmp al,0x08jnz .waits                         ;不忙,且硬盘已准备好数据传输mov ecx,256                        ;总共要读取的字数mov dx,0x1f0.readw:in ax,dxmov [ebx],axadd ebx,2loop .readwpop edxpop ecxpop eaxretf                               ;段间返回;-------------------------------------------------------------------------------
;汇编语言程序是极难一次成功,而且调试非常困难。这个例程可以提供帮助
put_hex_dword:                              ;在当前光标处以十六进制形式显示;一个双字并推进光标;输入:EDX=要转换并显示的数字;输出:无pushadpush dsmov ax,core_data_seg_sel           ;切换到核心数据段mov ds,axmov ebx,bin_hex                    ;指向核心数据段内的转换表mov ecx,8.xlt:rol edx,4mov eax,edxand eax,0x0000000fxlatpush ecxmov cl,alcall put_charpop ecxloop .xltpop dspopadretf;-------------------------------------------------------------------------------
allocate_memory:                            ;分配内存;输入:ECX=希望分配的字节数;输出:ECX=起始线性地址push dspush eaxpush ebxmov eax,core_data_seg_selmov ds,eaxmov eax,[ram_alloc]add eax,ecx                        ;下一次分配时的起始地址;这里应当有检测可用内存数量的指令mov ecx,[ram_alloc]                ;返回分配的起始地址mov ebx,eaxand ebx,0xfffffffcadd ebx,4                          ;强制对齐test eax,0x00000003                ;下次分配的起始地址最好是4字节对齐cmovnz eax,ebx                     ;如果没有对齐,则强制对齐mov [ram_alloc],eax                ;下次从该地址分配内存;cmovcc指令可以避免控制转移pop ebxpop eaxpop dsretf;-------------------------------------------------------------------------------
set_up_gdt_descriptor:                      ;在GDT内安装一个新的描述符;输入:EDX:EAX=描述符;输出:CX=描述符的选择子push eaxpush ebxpush edxpush dspush esmov ebx,core_data_seg_sel          ;切换到核心数据段mov ds,ebxsgdt [pgdt]                        ;以便开始处理GDTmov ebx,mem_0_4_gb_seg_selmov es,ebxmovzx ebx,word [pgdt]              ;GDT界限inc bx                             ;GDT总字节数,也是下一个描述符偏移add ebx,[pgdt+2]                   ;下一个描述符的线性地址mov [es:ebx],eaxmov [es:ebx+4],edxadd word [pgdt],8                  ;增加一个描述符的大小lgdt [pgdt]                        ;对GDT的更改生效mov ax,[pgdt]                      ;得到GDT界限值xor dx,dxmov bx,8div bx                             ;除以8,去掉余数mov cx,axshl cx,3                           ;将索引号移到正确位置pop espop dspop edxpop ebxpop eaxretf
;-------------------------------------------------------------------------------
make_seg_descriptor:                        ;构造存储器和系统的段描述符;输入:EAX=线性基地址;      EBX=段界限;      ECX=属性。各属性位都在原始;          位置,无关的位清零;返回:EDX:EAX=描述符mov edx,eaxshl eax,16or ax,bx                           ;描述符前32位(EAX)构造完毕and edx,0xffff0000                 ;清除基地址中无关的位rol edx,8bswap edx                          ;装配基址的31~24和23~16  (80486+)xor bx,bxor edx,ebx                         ;装配段界限的高4位or edx,ecx                         ;装配属性retf;-------------------------------------------------------------------------------
;构造调用门描述符
;参数:EAX=门代码在段内的偏移地址
;      BX=门代码所在断的选择子
;      CX=段类型及属性
;返回:EDX:EAX=完整描述符
make_gate_descriptor:push ebxpush ecxmov edx,eaxand edx,0xffff0000or  dx,cxand eax,0x0000ffffshl ebx,16or eax,ebxpop ecxpop ebx;pop edx 照着他的敲,纠结了两三天为什么不对,;        原来是自己一不小心ebx写成了edx。;        郁闷。2017/4/22 retf  ;通过远调用调用
sys_routine_end:
;===============================================================================
section core_data vstart=0
;-------------------------------------------------------------------------------pgdt dw 00      ;用于设置和修改GDTdd 00ram_alloc       dd 0x00100000;符号检索表salt:salt_1          db"@PrintString"times 256-($-salt_1) db 00dd put_stringdw sys_routine_seg_sel;此处只是暂时填写段选择子,当内核运行时会先安装调用门;将调用门的选择子回填到此处,于是前面四字节的偏移地址;就没用了。;注意:用户程序调用是通过dw处的调用门选择子来调用的,;而操作系统则是通过sys_routine_seg_sel:函数名 来调用的salt_2          db  '@ReadDiskData'times 256-($-salt_2) db 0dd  read_hard_disk_0dw  sys_routine_seg_selsalt_3          db'@PrintDwordAsHexString'times 256-($-salt_3) db 0dd  put_hex_dworddw  sys_routine_seg_selsalt_4          db'@TerminateProgram'times 256-($-salt_4) db 0dd  return_pointdw  core_code_seg_selsalt_item_len   equ $-salt_4salt_items      equ ($-salt)/salt_item_lenmessage_1       db  '  If you seen this message,that means we 'db  'are now in protect mode,and the system 'db  'core is loaded,and the video display 'db  'routine works perfectly.',0x0d,0x0a,0message_2       db  '  System wide CALL-GATE mounted.',0x0d,0x0a,0message_3       db  0x0d,0x0a,'  Loading user program...',0do_status       db  'Done.',0x0d,0x0a,0message_6       db  0x0d,0x0a,0x0d,0x0a,0x0d,0x0adb  '  User program terminated,control returned.',0bin_hex         db '0123456789ABCDEF'   ;put_hex_dword子过程用的查找表core_buf        times 2048 db 0          ;内核用的缓冲区esp_pointer     dd 0                ;内核用来临时保存自己的栈指针cpu_brnd0       db 0x0d,0x0a,'  ',0cpu_brand       times 52 db 0cpu_brnd1       db 0x0d,0x0a,0x0d,0x0a,0;TCB链:链表实现tcb_chain       dd 0;链表首指针core_data_end:
;===============================================================================
section core_code   vstart=0
;-------------------------------------------------------------------------------
;功能:在LDT内安装一个新的描述符
;输入:EDX:EAX=描述符
;      EBX=TCB基地址//注意:此处的TCB的基地址是对于整个0-4G空间来说的即段地址为0000000
;返回:CX=描述符的选择子
fill_descriptor_in_ldt:push eaxpush edxpush edipush dsmov ecx,mem_0_4_gb_seg_selmov ds,ecx  ;在将ecx装入ds前会进行段描述符有效性和特权级检查;段保护检查:1》段是否存在2》段用途是否与type字段相同3》段是否在内存中;特权级检查:目标数据段的DPL要低于或者等于CPL 即数值上DPL》=CPl;            目标数据段的DPL要低于或等于RPL   即数值上DPL》=RPL;            这样可以防止特权级为3的user(RPL=3)利用特权级为0的os库函数;            来读写特权级为0的os数据段因为当RPL低于DPL时,因为当RPL低于;            DPL时,os就知道user想要读写os数据段了,于是阻止并引发异常中断;笔记:1》执行时进行存储器保护检查。;      2》第一次引用一个段时进行特权级检查和段描述符有效性检查mov edi,[ebx+0x0c]  ;从TCB中获取LDT的基地址xor ecx,ecxmov cx,[ebx+0x0a]   ;从TCB中获取LDT的界限inc cx              ;新安装的描述符的偏移地址mov [edi+ecx+0x00],eaxmov [edi+ecx+0x04],edxadd cx,7mov [ebx+0x0a],cx   ;将LDT界限写回and cx,0xfff8  ;求新安装的描述符的选择子or  cx,0x0004  ;此行VeryImportant:选择子返回之前设置TI位置1指向LDT并且将RPL设为00;?????????为什么要将RPL设为00,不是这是用户程序的段描述符吗????;见此程序法第544行:or cx,0x0003 ;因为这个函数可能要加载不同特权级别的程序;并为之创建段描述符,于是乎os便在这个函数里将RPL通通定为00;但是OS是清楚该程序的RPL是多少,于是乎调用完此程序后OS就;修改段选择子的RPL,将它设置成正确的值(即用户程序的特权级)pop dspop edipop edxpop eaxret         ;内核私有函数不提供给用户程序故近调用即可;-------------------------------------------------------------------------------
;功能:加载并重定位用户程序
;参数:push逻辑扇区号
;      push任务控制块基地址
;返回:无
load_relocate_program:pushadpush dspush esmov ebp,esp   ;为访问存放在堆栈上的参数做准备mov ecx,mem_0_4_gb_seg_selmov es,ecxmov esi,[ebp+11*4];从堆栈获取TCB的基地址;以下申请创建LDT所需要的内存mov ecx,160call sys_routine_seg_sel:allocate_memory;分配160个字节mov [es:esi+0x0c],ecx         ;登记LDT基地址到TCB中mov word[es:esi+0x0a],0xffff      ;登记LDT初始的界限到TCB中(0xffff+1=0000);开始加载用户程序mov eax,core_data_seg_selmov ds,eaxmov eax,[ebp+12*4];从堆栈获取用户程序起始扇区号mov ebx,core_bufcall sys_routine_seg_sel:read_hard_disk_0;;判断程序大小mov eax,[core_buf]mov ebx,eax,and ebx,0xfffffe00add ebx,512test eax,0x000001ffcmovnz eax,ebxmov ecx,eaxcall sys_routine_seg_sel:allocate_memorymov [es:esi+0x06],ecx ;程序在内存中的位置登记到TCB中mov ebx,ecx   ;申请到的内存的起始地址放到ebx中xor edx,edxmov ecx,512div ecxmov ecx,eax   ;要读取得扇区数mov eax,mem_0_4_gb_seg_selmov ds,eaxmov eax,[ebp+12*4];获取起始扇区号
.b1:call sys_routine_seg_sel:read_hard_disk_0inc eaxloop .b1;为用户程序安装段描述符(亦叫重定位)mov edi,[es:esi+0x06];TCB中获取程序加载基地址;建立头部段描述符 (因为user是通过头部段中的变量内容来访问段和调用系统函数,故头部需要建立一个段)mov eax,edimov ebx,[edi+0x04];段长度dec ebx;段界限mov ecx,0x0040f200;字节粒度的数据段描述符,特权级3(由os为user创建段描述符,由os决定用户程序特权级)call sys_routine_seg_sel:make_seg_descriptormov ebx,esi;TCB的基地址call fill_descriptor_in_ldtor cx,0x0003 ;设置选择子的RPL为3(用户程序)
;********************************VeryImportant:***************************************************
;***********    ;OS和CPU两个协议好了:OS向cpu保证提供的选择子中一定包含正确的RPL,而CPU负责检查RPL**
;***********    ;OS很清楚当前加载的程序的特权级,于是OS就一定能填充正确的RPL到选择子中(457行)*****
;*************************************************************************************************mov [es:esi+0x44],cx ;登记头部段选择子到TCB中mov [edi+0x04],cx    ;将修改后的选择子写回到用户程序头部;建立用户程序代码段描述符mov eax,ediadd eax,[edi+0x14];段基址mov ebx,[edi+0x18];段长度dec ebx           ;段界限mov ecx,0x0040f800;字节粒度的代码段描述符,特权为3call sys_routine_seg_sel:make_seg_descriptormov ebx,esi;TCB基地址call fill_descriptor_in_ldtor cx,0x0003mov [edi+0x14],cx ;登记代码段选择子到头部
;????????????????????????疑惑:为什么只需登记头部选择子到TCB中???????????????????????;建立用户程序数据段描述符mov eax,ediadd eax,[edi+0x1c]mov ebx,[edi+0x20]dec ebxmov ecx,0x0040f200;字节力度的数据段描述符描述符,特权级为3call sys_routine_seg_sel:make_seg_descriptormov ebx,esicall fill_descriptor_in_ldtor cx,0x0003mov [edi+0x1c],cx;建立堆栈段描述符mov ecx,[edx+0x0c]mov ebx,0x000fffff;4KB的倍率sub ebx,ecx ;得到段界限mov eax,4096mul ecxmov ecx,eaxcall sys_routine_seg_sel:allocate_memory;为堆栈分配内存add eax,ecx ;得到堆栈上界mov ecx,0x00c0f600;字节粒度的堆栈段描述符,特权级为3call sys_routine_seg_sel:make_seg_descriptor;mov ebx,esicall fill_descriptor_in_ldtor cx,0x0003mov [edi+0x08],cx;重定位SALTmov eax,mem_0_4_gb_seg_sel ;此处与13章不同,因为此处头部段的段描述符是安装在LDT中,;而此时LDT未加载到LDTR,故只能用4gb的段选择子来访问头部mov es,eaxmov eax,core_data_seg_selmov ds,eax mov ecx,[es:edi+0x24];需要重定位的表项数cld ;正方向匹配add edi,0x28 ;user-salt在4GB段中的偏移
.b2:       ;外层循环push ecxpush edimov ecx,salt_items;esi:内核salt表起始地址mov esi,salt      ;edi:用户salt表起始地址
.b3:        ;内层循环push edipush esipush ecxmov ecx,64repe cmpsdjnz .b4mov eax,[esi]   ;若匹配,则esi指向内核salt表中该字符串对应的选择子的地址mov [es:edi-256],eax ;将函数的偏移地址写到用户表中mov ax,[esi+4]or ax,0x0003 ;将调用者(即用户程序)的特权级写进选择子的RPL中(OS向CPU保证一定提供正确的RPL)mov [es:edi-252],ax ;将调用门的选择子回填到用户程序
.b4:pop ecxpop esiadd esi,salt_item_len  ;比较内核salt表的下一项pop edi    ;从头比较loop .b3pop ediadd edi,256;比较usersalt的下一项pop ecxloop .b2mov esi,[ebp+11*4]      ;从堆栈取得TCB基地址;创建0特权级堆栈mov ecx,4096mov eax,ecxmov [es:esi+0x1a],ecx ;将0级栈大小写回TCBshr dword [es:esi+0x1a],12 ;倍率为4KBcall sys_routine_seg_sel:allocate_memoryadd eax,ecx ;堆栈的基地址(向下生长)mov [es:esi+0x1e],eax ;将栈基地址写进TCBmov ebx,0xffffe       ;栈段界限mov ecx,0x00c09600    ;粒度4KB,读写,特权级0call sys_routine_seg_sel:make_seg_descriptormov ebx,esi           ;TCB基地址call fill_descriptor_in_ldt;在LDT中登记0栈段描述符;or cx,0x0003 设置0选择子的特权级为0mov [es:esi+0x22],cx ;将0栈段选择子回填到TCB中mov dword [es:esi+0x24],0;将0特权级堆栈的初始ESP设置为0;创建1特权级堆栈mov ecx,4096mov eax,ecx                        ;为生成堆栈高端地址做准备mov [es:esi+0x28],ecxshr dword[es:esi+0x28],12               ;登记1特权级堆栈尺寸到TCBcall sys_routine_seg_sel:allocate_memoryadd eax,ecx                        ;堆栈必须使用高端地址为基地址mov [es:esi+0x2c],eax              ;登记1特权级堆栈基地址到TCBmov ebx,0xffffe                    ;段长度(界限)mov ecx,0x00c0b600                 ;4KB粒度,读写,特权级1call sys_routine_seg_sel:make_seg_descriptormov ebx,esi                        ;TCB的基地址call fill_descriptor_in_ldtor cx,0000_0000_0000_0001          ;设置选择子的特权级为1mov [es:esi+0x30],cx               ;登记1特权级堆栈选择子到TCBmov dword [es:esi+0x32],0          ;登记1特权级堆栈初始ESP到TCB;创建2特权级堆栈mov ecx,4096mov eax,ecx                        ;为生成堆栈高端地址做准备mov [es:esi+0x36],ecxshr dword[es:esi+0x36],12               ;登记2特权级堆栈尺寸到TCBcall sys_routine_seg_sel:allocate_memoryadd eax,ecx                        ;堆栈必须使用高端地址为基地址mov [es:esi+0x3a],ecx              ;登记2特权级堆栈基地址到TCBmov ebx,0xffffe                    ;段长度(界限)mov ecx,0x00c0d600                 ;4KB粒度,读写,特权级2call sys_routine_seg_sel:make_seg_descriptormov ebx,esi                        ;TCB的基地址call fill_descriptor_in_ldtor cx,0000_0000_0000_0010          ;设置选择子的特权级为2mov [es:esi+0x3e],cx               ;登记2特权级堆栈选择子到TCBmov dword [es:esi+0x40],0          ;登记2特权级堆栈初始ESP到TCB;在GDT中登记LDTmov eax,[es:esi+0x0c]                   ;LDT起始地址movzx ebx,word [es:esi+0x0a]            ;LDT段界限mov ecx,0x00408200                      ;LDT描述符,特权级0call sys_routine_seg_sel:make_seg_descriptorcall sys_routine_seg_sel:set_up_gdt_descriptormov [es:esi+0x10],cx                    ;登记LDT选择子到TCB中;创建user的TSSmov ecx,104     ;TSS的基本尺寸mov [es:esi+0x12],cxdec word [es:esi+0x12];登记TSS基本尺寸到TCBcall sys_routine_seg_sel:allocate_memorymov [es:esi+0x14],ecx ;登记TSS基地址到 TCB中;登记基本的TSS表格内容mov word [es:ecx],0;前一个任务的TSS的指针值设为0mov edx,[es:esi+0x24]  ;esi:全局空间中TCB的首地址mov [es:ecx+4],edx     ;ecx:全局空间中TSS的首地址mov dx,[es:esi+0x22]               ;登记0特权级堆栈段选择子mov [es:ecx+8],dx                  ;到TSS中mov edx,[es:esi+0x32]              ;登记1特权级堆栈初始ESPmov [es:ecx+12],edx                ;到TSS中mov dx,[es:esi+0x30]               ;登记1特权级堆栈段选择子mov [es:ecx+16],dx                 ;到TSS中mov edx,[es:esi+0x40]              ;登记2特权级堆栈初始ESPmov [es:ecx+20],edx                ;到TSS中mov dx,[es:esi+0x3e]               ;登记2特权级堆栈段选择子mov [es:ecx+24],dx                 ;到TSS中mov dx,[es:esi+0x10]               ;登记任务的LDT选择子mov [es:ecx+96],dx                 ;到TSS中mov dx,[es:esi+0x12]               ;I/O位图地址字段设置为TSS大小mov [es:ecx+102],dx                ;表示不存在I/O位图mov word [es:ecx+100],0            ;T位清零 T=0 不产生中断 T=1:每次切换到此任务将产生一次调试异常中断;在GDT中登记TSSmov eax,[es:esi+0x14]              ;TSS的起始线性地址movzx ebx,word [es:esi+0x12]       ;段界限mov ecx,0x00408900                 ;TSS描述符(由TYPE字段区分),特权级0call sys_routine_seg_sel:make_seg_descriptorcall sys_routine_seg_sel:set_up_gdt_descriptormov [es:esi+0x18],cx               ;登记TSS选择子到 TCB中;LDT,TSS,调用门均属于系统段;cpu根据TYPE字段来区LDT段描述符,TSS段描述符,调用门描述符;LDT,TSS特权级均为0(不知为啥)pop espop dspopadret 8                              ;丢弃掉调用本过程前压入的参数;-------------------------------------------------------------------------------
;功能:在TCB链中添加一个任务控制块
;参数:ECX=TCB线性基地址
;返回:无
append_to_TCB_link:  ;头插法入链push eaxpush dspush esmov eax,core_data_seg_selmov ds,eax ;内核数据段mov eax,mem_0_4_gb_seg_selmov es,eax;全局数据段mov eax,[ds:tcb_chain]mov [es:ecx],eaxmov [ds:tcb_chain],ecxpop espop dspop eaxret;-------------------------------------------------------------------------------
;内核程序入口点
start:mov ecx,core_data_seg_sel;ds指向核心数据段 mov ds,ecx mov ebx,message_1call sys_routine_seg_sel:put_string ;显示处理器品牌信息 mov eax,0x80000002cpuidmov [cpu_brand + 0x00],eaxmov [cpu_brand + 0x04],ebxmov [cpu_brand + 0x08],ecxmov [cpu_brand + 0x0c],edxmov eax,0x80000003cpuidmov [cpu_brand + 0x10],eaxmov [cpu_brand + 0x14],ebxmov [cpu_brand + 0x18],ecxmov [cpu_brand + 0x1c],edxmov eax,0x80000004cpuidmov [cpu_brand + 0x20],eaxmov [cpu_brand + 0x24],ebxmov [cpu_brand + 0x28],ecxmov [cpu_brand + 0x2c],edxmov ebx,cpu_brnd0                  ;显示处理器品牌信息 call sys_routine_seg_sel:put_stringmov ebx,cpu_brandcall sys_routine_seg_sel:put_stringmov ebx,cpu_brnd1call sys_routine_seg_sel:put_string;开始安装整个系统服务的调用门。特权级之间转移必须使用门 ;1》jmp依从段不改变CPL;2》jmp非依从段则必有CPL=目标代码段的DPL;3》jmp调用门不改变CPL。。;4》call依从段则不改变CPL;5》call非依从段则必须通过调用门 mov edi,salt    ;core_salt表起始位置mov ecx,salt_items;core_salt条目数
.lopx:push ecx mov eax,[edi+256]           ;32位偏移地址 mov bx,[edi+260]            ;16位短选择子mov cx,0xec00               ;特权级为3的调用门(3以上的特权级别)才允许访问,;0个参数(因为用寄存器传递参数而没用栈)call sys_routine_seg_sel:make_gate_descriptorcall sys_routine_seg_sel:set_up_gdt_descriptormov [edi+260],cx            ;将门描述符选择子回填至core—salt表add edi,salt_item_len pop ecx loop .lopx;对门进行测试 mov ebx,message_2call far[salt_1+256];调用门:偏移量将被忽略mov ebx,message_3call sys_routine_seg_sel:put_string ;在内核中调用例程不需要调用门 ;创建任务控制块。这不是处理器的要求,但这是OS为了方便任务管理所必须的mov ecx,0x46                            ;先声明一个大小为70B的TCBcall sys_routine_seg_sel:allocate_memorycall append_to_TCB_link                 ;将任务控制块插入到TCB链头部 push dword 50                                   ;用户程序位于逻辑扇区50push ecx                                ;压入任务控制块起始线性地址call load_relocate_program              ;创建一个任务并初始化任务的TCBmov ebx,do_statuscall sys_routine_seg_sel:put_stringmov eax,mem_0_4_gb_seg_selmov ds,eaxltr [ecx+0x18]                          ;加载TSSlldt [ecx+0x10]                         ;加载LDTmovzx eax,word[ecx+0x44]                  mov ds,eax                              ;切换到用户程序头部;模拟从调用门返回将控制权转移到用户程序;模仿处理器压入参数push dword [0x08]           ;调用者的堆栈段选择子push dword 0                ;!!调用前的esp=0????????为什么,等下去看书;答:调用前的esp初始化为0,然后0-1就等于0xffffffff了;注意:只是在用esp访问存储器时才会进行段界限检查,此处不会检查esp;顶多检查ss段选择子 push dword [0x14]           ;调用者的代码段选择子push dword [0x10]           ;调用者的eipretf                        ;模拟从调用门返回将控制权转移到用户程序;类似于《王爽汇编语言》中模拟中断,然后再"调用"中断例程;相同点:跳转前:在程序跳转前由硬件完成一些列操作 ;        调用门:如果要改变特权级则硬件实现栈切换同时SS:SP入栈和;                复制参数到新栈,再cs:ip入栈,跳转;        中断  : 硬件实现取中断号,cs:ip入栈,EFLAGSREG入栈,关IF关TF,跳转   ;        返回时:;        调用门:retf返回,当执行retf指令时cpu自动进行各种检查;        中断  :iret返回 ;方法  :模拟的关键点在于最后的返回指令,;        iret要求栈中必须含有CS:IP,retf则要求866~873;        就是说用软件实现与硬件相同的的操作,创建相同的返回指令;        返回所需的条件即可return_point:                               ;用户程序返回点mov eax,core_data_seg_sel          ;因为c14.asm是以JMP的方式使用调 mov ds,eax                         ;用门@TerminateProgram,回到这 ;里时,特权级仍然为3,会导致异常(817)。                                         mov ebx,message_6               call sys_routine_seg_sel:put_stringhltcore_code_end:;===============================================================================
section  core_trail
core_end:

//接下来是用户程序
;代码清单13-3
;文件名:c13.asm
;文件说明:用户程序
;创建日期:2011-10-30 15:19

;===============================================================================
SECTION header vstart=0

     program_length   dd program_end          ;程序总长度#0x00head_len         dd header_end           ;程序头部的长度#0x04stack_seg        dd 0                    ;用于接收堆栈段选择子#0x08stack_len        dd 1                    ;程序建议的堆栈大小#0x0c;以4KB为单位prgentry         dd start                ;程序入口#0x10 code_seg         dd section.code.start   ;代码段位置#0x14code_len         dd code_end             ;代码段长度#0x18data_seg         dd section.data.start   ;数据段位置#0x1cdata_len         dd data_end             ;数据段长度#0x20

;——————————————————————————-
;符号地址检索表
salt_items dd (header_end-salt)/256 ;#0x24

     salt:                                     ;#0x28PrintString      db  '@PrintString'times 256-($-PrintString) db 0TerminateProgram db  '@TerminateProgram'times 256-($-TerminateProgram) db 0ReadDiskData     db  '@ReadDiskData'times 256-($-ReadDiskData) db 0

header_end:

;===============================================================================
SECTION data vstart=0

     buffer times 1024 db  0         ;缓冲区message_1         db  0x0d,0x0a,0x0d,0x0adb  '**********User program is runing**********'db  0x0d,0x0a,0message_2         db  '  Disk data:',0x0d,0x0a,0

data_end:

;===============================================================================
[bits 32]
;===============================================================================
SECTION code vstart=0
start:
mov eax,ds
mov fs,eax

     mov eax,[stack_seg]mov ss,eaxmov esp,0mov eax,[data_seg]mov ds,eaxmov ebx,message_1call far [fs:PrintString]mov eax,100                         ;逻辑扇区号100mov ebx,buffer                      ;缓冲区偏移地址call far [fs:ReadDiskData]          ;段间调用mov ebx,message_2call far [fs:PrintString]mov ebx,buffer call far [fs:PrintString]           ;too.jmp far [fs:TerminateProgram]       ;将控制权返回到系统

code_end:

;===============================================================================
SECTION trail
;——————————————————————————-
program_end:


//下面是数据文件

  The Intel386 Processor (1985)The Intel386 processor was the first 32-bit processor in the IA-32 architecture family. It introduced 32-bit registers for use both to hold operands and for addressing. The lower half of each 32-bit Intel386 register retains the properties of the 16-bit registers of earlier generations, permitting backward compatibility. The processor also provides a virtual-8086 mode that allows for even greater efficiency when executing programs created for 8086/8088 processors.    [END]

保护模式下的CPL,RPL,DPL与特权级检查(二)相关推荐

  1. 保护模式下的RPL(一)

    <x86汇编语言:从实模式到保护模式>读书笔记 源码面前,了无秘密 --jjhou <这是链接-保护模式下的CPL,RPL,DPL(二)(两篇文章一起看最好,由于先写的二,后写的一, ...

  2. 特权级——保护模式的特权级检查 DPL,RPL,CPL, 一致代码段,非一致代码段

    特权级是保护模式下一个重要的概念,CPL,RPL和DPL是其中的核心概念,查阅资料无数,总结如下. 一.CPL.RPL.DPL简单解释     CPL是当前进程的权限级别(Current Privil ...

  3. ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务...

    ★PART1:中断和异常概述 1. 中断(Interrupt) 中断包括硬件中断和软中断.硬件中断是由外围设备发出的中断信号引发的,以请求处理器提供服务.当I/O接口发出中断请求的时候,会被像8259 ...

  4. 访问数据段时的特权级检查,修改SS时的特权级检查——《x86汇编语言:从实模式到保护模式》读书笔记30

    1. 访问数据段时的特权级检查 为了访问数据段,数据段的选择子必须被加载进段寄存器(ES,ES,FS,GS,SS).在把一个段选择子加载进段寄存器之前,处理器会进行特权级检查(如下图所示). 在数值上 ...

  5. ASM:《X86汇编语言-从实模式到保护模式》第14章:保护模式下的特权保护和任务概述...

    ★PART1:32位保护模式下任务的隔离和特权级保护  这一章是全书的重点之一,这一张必须要理解特权级(包括CPL,RPL和DPL的含义)是什么,调用门的使用,还有LDT和TSS的工作原理(15章着重 ...

  6. 特权级转移 之 保护模式下数据段特权级保护依据

    提炼: 1 对于数据段 ,处理器进行特权级检查的时机 就是为段寄存器赋值的那一个时刻 在保护模式下 当处理器对段寄存器进行赋值的时候,处理器就会使用规则进行特权级的检查.如: mov ax, Data ...

  7. IA-32 Intel手册学习笔记(二)保护模式下的内存管理

    内存管理概述(Memory Management Overview) Inter体系结构的内存管理可分为两部分:分段和分页. 分段提供了一种机制,这种机制可以为每个程序或者任务提供单独的代码.数据和栈 ...

  8. 实地址模式与保护模式下的中断与异常处理

    在中断和异常的处理过程中,很重要的一件事是如何识别中断源,获取中断服务子程序的入口地址.在80486 CPU系统中,因为CPU的工作模式不同而获取中断向量的方式有所不同,本节讨论CPU工作在实地址模式 ...

  9. MIT-JOS系列1:实模式和保护模式下的段寻址方式

    实模式下的段寻址 以8086为例 8086 段寄存器16位(段地址/基地址),寄存器16位(偏移地址),地址总线20位(寻址1M:2^20) 实际物理地址 = (段寄存器 << 4) + ...

最新文章

  1. GridSearchCV 与 RandomizedSearchCV 调参
  2. kafka消息消费原理演示
  3. RecyclerView加载不同view实现效果--IT蓝豹
  4. 动态反射——Load,LoadFrom和LoadFile
  5. tf.metrics._将指标标签与MicroProfile Metrics 2.0一起使用
  6. python实现的、带GUI界面电影票房数据可视化程序
  7. django objects.filter().exists()
  8. FinSpy 发布 Mac 和 Linux OS 版本攻击埃及组织机构
  9. 【整理向】老板让我用SPSS做A/Btest,我偏要用python
  10. 网易视频云:新一代列式存储格式Parquet
  11. 关于在针对esp32进行编程时出现dl_lib.h: No such file or directory的解决办法
  12. 使用jq简单实现导航栏切换对应展现内容
  13. 数据挖掘技术的算法与应用读书报告
  14. golang 实现 pdf 转高清晰度 jpeg
  15. Java校验框架使用@Valid、@Validated、OVAL+Groovy
  16. C语言小游戏设计报告
  17. 北斗微信与服务器怎么联接,微信怎么绑定北斗导航
  18. 胶囊网络、边缘计算:2018年13个最新人工智能发展趋势
  19. 图像分割中OTSU算法
  20. 大一计算机课论文,大一计算机结课论文.docx

热门文章

  1. Office2016 Excel 快捷键备忘录
  2. Unity UGUI Rect
  3. 百度飞桨携手精诺数据打造智慧熔炼,AI让年轻人一秒变身“老师傅”
  4. 这个高仿微信,差点我就信了
  5. 2022-05-08 Unity核心5——Tilemap
  6. 安卓手机电池信息的获取与显示
  7. 计算机通信中ip,macid的主要作用和区别
  8. eclipse 是用来写客户端的,MyEclipse 是用来写服务器端的,谐音记忆法,My 买,买服务器这样就好记了。
  9. Office word编辑公式居中,编号右对齐的简单方法,非表格法和制表符法
  10. Adobe XD 下载和安装教程