200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > ASM:《X86汇编语言-从实模式到保护模式》第13章:保护模式下内核的加载 程序的动态加

ASM:《X86汇编语言-从实模式到保护模式》第13章:保护模式下内核的加载 程序的动态加

时间:2023-08-28 18:34:43

相关推荐

ASM:《X86汇编语言-从实模式到保护模式》第13章:保护模式下内核的加载 程序的动态加

★PART1:32位保护模式下内核简易模型

1.内核的结构,功能和加载

每个内核的主引导程序都会有所不同,因为内核都会有不同的结构。有时候主引导程序的一些段和内核段是可以共用的(事实上加载完内核以后就不需要主引导程序了),和加载一般的用户程序一样,主引导程序也是需要从硬盘中读取程序到指定的内存空间中。

同时,作为一个内核,也是一个程序,而且是一个具有管理全局的能力的程序,应该有固定的段,一般来说,内核应该包括以下几个部分:

1. 公用例程段(实现API功能)

2. 内核数据区(用于预读一些数据和一些内核内置的保留的内容)

3. 内核代码区(用于执行内核自己的代码…)

PS:上述段都应该具有特权0级,特权级在教材的14章讲述

在主引导程序中,为了加载内核,首先应该在GDT中加载内核的所有的描述符。但是内核的描述符是不确定的(因为段界限和段的线性基地址不知道),所以要根据内核头部的信息来确定,内核的头部和普通的程序是一样的,也是应该把各个段的信息和入口点的相对地址列出来,同时还需要具有段的选择子(因为内核是固定的,选择子也应该固定)。然后主引导程序构建这些段的描述符(需要重定位,不过不需要再把段信息写入内核头部了,因为选择子有了),并且把描述符写入GDT并且重新加载GDT即可(主要是刷新GDT的大小)。

那怎么根据内核所给出的信息构建内核的描述符呢?教材给出了一种方法。(edi的内容是内核头部的地址)

其实原理很简单,就是给出需要加载的段的属性(因为加载什么段我们是知道的),然后只要填充好描述符的高32位和低32位就完了,教材这里用到了一个bswap(bswap r32)命令,简单来说这个命令就是将以某个寄存器中8位为一个段的首尾交换,如图:

2.保护模式下的用户程序的重定位和加载

首先要明白一个事情,在保护模式下(特别是在有权限管理的情况下),程序是很难进行自我加载的,必须通过内核来加载。当然了,用户程序必须符合固定的格式,才能被内核识别和加载。一般来说,内核需要给用户程序提供3个段,这三个段一般是:

1. 代码段,

2. 数据段(这里应该有两个区域:用户程序头部和文字常量区),

3. 栈段,其中栈段要根据用户程序的建议的栈段大小来定(一般不建议用户自己创建栈段)。

(正常来讲还要有堆和BSS段,但是先不搞那么复杂)

内核从硬盘读取了用户程序并加载到相应位置的时候,就读取用户的程序头。我们知道,内核有公用程序段,是专门用来给用户程序提供公用例程的,这就是现代操作系统的API(Application Programming Interface),更直观来说就是就是一堆库函数名(比如C中的scanf,printf…)。早期的系统,API是通过中断号的方式公布的(也就是要通过软中断进入),现在常用方法是使用符号名。正常来讲,一个现代的操作系统应该是具有一个专门的链接器(Linker)来构建文件头的。教材用一个比较原始的方式来构建文件头了,其实这只是一个简单的字符串的匹配的过程而已。

(内核数据区的符号表的样貌)

在我们的规定中,用户程序的符号表固定是256个字符,同时每个符号还需要预留一个区域来保存符号对应过程的选择子(本章过程的调用是通过GDT进行的,下一章将会介绍调用门)。两个字符串的比较可以使用cmpsb(字节比较),cmpsw(字比较),cmpsd(双字比较),在16为模式下,源字符串的首地址由DS:SI指定,目的字符串的首地址由ES:DI指定,在32位模式下,则分别是DS:ESI和ES:EDI,在处理器内部,cmps指令的操作是把两个操作数相减,然后根据结果设置标志寄存器中的标志位。

单纯的cmps(指令族)只比较一次,需要通过rep前缀来不断驱动(直到ecx为0),但是这里不能单纯地使用rep指令,否则就无法比较了

重定位符号表后,我们就要对用户程序的段进行重定位和内存的分配,当然了,理论上在读取用户头部的时候就应该开辟一个内存放置用户程序,但是内核的做法是,先预读头部到内核数据区,然后再给用户程序分配内存,在现代操作系统中,内存分配是一个很重要的工作。内存管理程序不仅要把内存分块管理以便给应用程序分配内存,还要负责回收,还要进行虚拟内存管理的工作,非常复杂。这里教材用的是一个非常简单的分配demo,没有检测内存是否越界的功能,而且也没有内存回收。我往上加了一点东西。

接下来就是段的重定位和描述符的创建了,当然这一章没有讲特权级,所以教材呢就直接把所有的程序都按特权0级的权限加载了。当然这是一种很不好的做法,教材在14章会介绍如何用特权3级来加载程序。其实也不难,懂怎么加载内核就知道怎么怎么加载用户程序了,主要是栈段的分配要用到内存分配函数而已。

这里要注意的地方是Set_New_GDT这个过程,要在GDT中安装描述符,必须知道他的物理地址和大小,要知道这些消息,可以用sdgt指令(Store Global Descriptor Table Regiser)它用于将GDT军训器的基地址和边界信息保存到指定的内存位置,sgdt的指令格式是sgdt m/48,这个指令不会影响任何标志位。另外还有一个新的指令movzx

movzx是一个带零拓展的传送(Move With Zero-Extend)指令格式为(其实就是相当于先xor一下16/32位寄存器再写入低位的寄存器)

movzx r16,r/m8

movzx r32,r/m8

movzx r32,r/m16

注意movzx的指令目的操作数只能是16位或者是32位的通用寄存器,源操作数只能是8位或者16位的寄存器或者内存地址,而且目的操作数和源操作数的大小是不一样的。和movzx指令差不多的指令是movsx指令,他是带符号的传送,这个指令就不像movzx一样只会拓展0了,而是根据数的最高位来拓展。

GDTR的界限部分在初始化的时候会初始化为0xFFFF,当+1时,如果看成16位数,则会是0x0000,如果看成32位数,则会变成0x00010000,为了避免不必要的麻烦,直接写成inc bx,那这样的话开始填充描述符的时候就不是从0x00010000这个偏移地址开始了,而是0x00000000开始了。

因为应用程序给出的都是栈的建议大小,所以一般都是直接给出的是建议大小,所以我们直接按建议大小来乘以4KB的大小来分配内存,要注意的是栈段是向下拓展的,高地址才是栈段的线性基地址。

2.用户程序的执行

一旦加载了用户程序,那么我们就可以直接直接一个跳转指令跳转到用户程序了

此时ds应该指向用户程序头部。0x10刚好是偏移地址(32位)+选择子(16位),接下来就是在用户程序执行一系列操作了,因为我们已经在用户程序填入了公用例程段的选择子,所以我们直接用fs指向用户头部,然后执行这些公用例程就可以了。

TerminateProgram是返回内核的公用例程,返回内核后就可以回收用户程序用的内存和创建的GDT了。

方法简单粗暴,当然学了14,15章以后我们就能学习到正确的任务切换方法了。

最后,教材还给出一种调试程序的方法,其实个人感觉没有什么用,还不如直接在bochs看呢。应该就是为了讲xlat这个查表指令而写的,该指令要求事先在DS:(E)BX出定义一个用于转换编码的表格,在16位下使用bx,在32位下使用ebx。指令执行的时候,处理器访问该表格,用AL寄存器作为偏移量,从表格中取出一个字节,传回AL寄存器。教材上写了一个调试函数。

每次将edx移动4次,总共需要移动8次,而且每次只取4位,Core_Data_Segement段的bin_hex中有16进制的对照表。Xlat不影响任何标志位。

★PART2:13章的代码

1.源代码:

1 ;========================保护模式主引导扇区代码======================== 2 core_phy_base: equ 0x00040000 ;内核加载地址 3 core_sector_address: equ 0x00000001 ;内核所在扇区 4 5 mov ax,cs 6 mov ss,ax 7 mov sp,0x7c00 8 9 mov eax,[cs:pgdt_base+0x7c00+0x02] 10 xor edx,edx 11 mov ebx,0x10 12 div ebx 1314 mov ds,eax ;让ds指向gdt位置进行操作 15 mov ebx,edx ;别忘了还有可能出现偏移地址 1617 ;---------------------描述符#0--------------------- 18 mov dword [ebx+0x00],0x00000000 ;空描述符 19 mov dword [ebx+0x04],0x00000000 20 ;---------------------描述符#1--------------------- 21 mov dword [ebx+0x08],0x0000ffff ;4GB向上拓展数据段 22 mov dword [ebx+0x0c],0x00cf9200 23 ;---------------------描述符#2--------------------- 24 mov dword [ebx+0x10],0x7c0001ff ;代码段 25 mov dword [ebx+0x14],0x00409800 26 ;---------------------描述符#3--------------------- 27 mov dword [ebx+0x18],0x7c00fffe ;栈段 28 mov dword [ebx+0x1c],0x00cf9600 29 ;---------------------描述符#4--------------------- 30 mov dword [ebx+0x20],0x80007fff ;屏幕显示段 31 mov dword [ebx+0x24],0x0040920b 3233 mov word[cs:pgdt_base+0x7c00],39 ;加载gdt 34 lgdt [cs:pgdt_base+0x7c00] 3536 in al,0x92 ;快速开启A20 37 or al,0x02 ;是写入2,不要搞错了,写入1就是重启了 38 out 0x92,al 39 cli;关掉BIOS中断 4041 mov eax,cr0 42 or eax,0x01 ;设置PE位 43 mov cr0,eax 4445 jmp dword 0x0010:flush;进入保护模式 4647 [bits 32] 48flush: 49 mov eax,0x0008 ;选择4GB的代码段直接给ds 50 mov ds,eax 51 mov eax,0x0018 52 mov ss,eax ;设置栈段 53 xor esp,esp5455 ;接下来开始读取内核头部 56 mov esi,core_sector_address 57 mov edi,core_phy_base 58 call read_harddisk_0 5960 mov eax,[core_phy_base];读取用户总长度 61 xor edx,edx 62 mov ebx,512 63 div ebx 6465 cmp edx,0 66 jne @read_last_sector 67 dec eax 68 @read_last_sector: 69 cmp eax,0 70 je @setup 71 mov ecx,eax 72 .read_last: 73 inc esi 74 call read_harddisk_0 75 loop .read_last 76 @setup: 77 mov edi,core_phy_base 78 mov esi,[pgdt_base+0x7c00+0x02] 79 80 ;重新构建描述符#1 81 mov eax,[edi+0x04];eax线性地址,ebx是下一个线性地址 82 mov ebx,[edi+0x08] 83 sub ebx,eax ;得到段的总长度 84 dec ebx ;段长度-1就是段界限 85 add eax,edi ;得到真正的段的加载地址 86 mov ecx,0x00409800;公用例程段 87 call make_gdt_descriptor 88 mov [esi+0x28],eax;gdt低32位 89 mov [esi+0x2c],edx;gdt高32位 90 91 ;重新构建描述符#2 92 mov eax,[edi+0x08];eax线性地址,ebx是下一个线性地址 93 mov ebx,[edi+0x0c] 94 sub ebx,eax ;得到段的总长度 95 dec ebx ;段长度-1就是段界限 96 add eax,edi ;得到真正的段的加载地址 97 mov ecx,0x00409200;内核数据段 98 call make_gdt_descriptor 99 mov [esi+0x30],eax;gdt低32位100 mov [esi+0x34],edx;gdt高32位101 102 ;重新构建描述符#3103 mov eax,[edi+0x0c];eax线性地址,ebx是下一个线性地址104 mov ebx,[edi+0x00];注意这里是程序的总长度(很容易搞错变成0x10)105 sub ebx,eax ;得到段的总长度106 dec ebx ;段长度-1就是段界限107 add eax,edi ;得到真正的段的加载地址108 mov ecx,0x00409800;内核代码段109 call make_gdt_descriptor110 mov [esi+0x38],eax;gdt低32位111 mov [esi+0x3c],edx;gdt高32位112 113 mov word[pgdt_base+0x7c00],63 ;现在ds指向的就是4GB的内存,不可以用cs(会引发中断的)114 lgdt [pgdt_base+0x7c00]115 116 jmp far [edi+0x10];可以这样跳的原因是因为在内核中,选择子都是固定的,所以不用自己操心117 118 ;=============================函数部分=================================119 read_harddisk_0: ;esi存了28位的硬盘号120 push ecx121 122 mov edx,0x1f2 ;读取一个扇区123 mov al,0x01124 out dx,al125 126 mov eax,esi ;0~7位,0x1f3端口127 inc edx128 out dx,al129 130 mov al,ah ;8~15位,0x1f4端口131 inc edx132 out dx,al133 134 shr eax,16 ;16-23位,0x1f5端口135 inc edx136 out dx,al137 138 mov al,ah ;24-28位,LBA模式主硬盘139 inc edx140 and al,0x0f141 or al,0xe0142 out dx,al 143 144 inc edx;读命令,0x1f7端口145 mov al,0x20 146 out dx,al147 148 .wait:149 in al,dx150 and al,0x88151 cmp al,0x08152 jne .wait153 154 mov dx,0x1f0155 mov ecx,256156 .read:157 in ax,dx158 mov [edi],ax159 add edi,2160 loop .read161 162 pop ecx163 164 ret165 ;----------------------------------------------------------------------166 make_gdt_descriptor:167 ;eax:线性基地址168 ;ebx:段界限169 ;ecx:属性170 mov edx,eax171 and edx,0xffff0000 ;得到线性基地址的16-31位172 rol edx,8173 bswap edx ;强行把0-7位和16-23位互换174 or edx,ecx ;装载属性175 176 shl eax,16177 or ax,bx ;配好段界限低16位178 179 and ebx,0x000f0000180 or edx,ebx ;装载段界限的16-19位181 182 ret183 ;======================================================================184pgdt_base dw 0185dd 0x00007e00;GDT的物理地址186 ;======================================================================187times 510-($-$$)db 0188dw 0xaa55

1 ;===============================内核程序================================= 2 ;定义内核所要用到的选择子 3 All_4GB_Segment equ 0x0008 ;4GB的全内存区域 4 Stack_Segement equ 0x0018 ;内核栈区 5 Print_Segement equ 0x0020 ;显存映射区 6 Sys_Routine_Segementequ 0x0028 ;公用例程段 7 Core_Data_Segement equ 0x0030 ;内核数据区 8 Core_Code_Segement equ 0x0038 ;内核代码段 9 ;---------------------------------------------------------------- 10 User_Program_Address equ 50 ;用户程序所在逻辑扇区 11 ;=============================内核程序头部=============================== 12 SECTION header vstart=0 13 Program_Length dd Program_end;内核总长度 14 Sys_Routine_Seg dd section.Sys_Routine.start ;公用例程段线性地址 15 Core_Data_Seg dd section.Core_Data.start ;内核数据区线性地址 16 Core_Code_Seg dd section.Core_Code.start ;内核代码区线性地址 17 Code_Entrydd start ;注意偏移地址一定是32位的 18 dw Core_Code_Segement 19;---------------------------------------------------------------- 20 [bits 32] 21 ;========================================================================= 22 ;============================公用例程区=================================== 23 ;========================================================================= 24 SECTION Sys_Routine align=16 vstart=0 25ReadHarddisk: ;esi:28位磁盘号 26;ebx:偏移地址 27 pushad 2829 mov dx,0x1f2 30 mov al,0x01 ;读一个扇区 31 out dx,al 3233 inc edx ;0-7位 34 mov eax,esi 35 out dx,al 3637 inc edx ;8-15位 38 mov al,ah 39 out dx,al 4041 inc edx ;16-23位 42 shr eax,16 43 out dx,al 4445 inc edx ;24-28位,主硬盘,LBA模式 46 mov al,ah 47 and al,0x0f 48 or al,0xe0 49 out dx,al 5051 inc edx 52 mov al,0x20 53 out dx,al 5455 _wait: 56 in al,dx 57 and al,0x88 58 cmp al,0x08 59 jne _wait 6061 mov dx,0x1f0 62 mov ecx,256 6364 _read: 65 in ax,dx 66 mov [ebx],ax 67 add ebx,2 68 loop _read 6970 popad 71 retf 72;---------------------------------------------------------------- 73put_string: ;ebx:偏移地址 74 pushad 7576 _print: 77 mov cl,[ebx] 78 cmp cl,0 79 je _exit 80 call put_char 81 inc ebx 82 jmp _print 83 _exit: 84 popad 85 retf ;段间返回 86 ;--------------------------------------------------------------87 put_char: ;cl就是要显示的字符 88 push ebx 89 push es 90 push ds 91 92 mov dx,0x3d4 93 mov al,0x0e ;高8位 94 out dx,al 95 mov dx,0x3d5 96 in al,dx 97 mov ah,al ;先把高8位存起来 98 mov dx,0x3d4 99 mov al,0x0f ;低8位100 out dx,al101 mov dx,0x3d5102 in al,dx ;现在ax就是当前光标的位置103 104 _judge:105 cmp cl,0x0a106 je _set_0x0a107 cmp cl,0x0d108 je _set_0x0d109 _print_visible:110 mov bx,ax111 mov eax,Print_Segement112 mov es,eax113 shl bx,1;注意这里一定要把ebx变成原来的两倍,实际位置是光标位置的两倍114 mov [es:bx],cl ;注意这里是屏幕!115 mov byte[es:bx+1],0x07 116 add bx,2117 shr bx,1118 jmp _roll_screen119 _set_0x0d: ;回车120 mov bl,80121 div bl122 mul bl123 mov bx,ax124 jmp _set_cursor125 _set_0x0a: ;换行126 mov bx,ax127 add bx,80128 jmp _roll_screen129 _roll_screen:130 cmp bx,2000131 jl _set_cursor132 mov eax,Print_Segement133 mov ds,eax134 mov es,eax135 136 cld137 mov edi,0x00138 mov esi,0xa0139 mov ecx,190 rep movsw141 _cls:142 mov bx,3840143 mov ecx,80144 _print_blank:145 mov word[es:bx],0x076 add bx,2147 loop _print_blank 148 mov bx,1920 ;别总是忘了光标的位置!149 _set_cursor: ;改变后的光标位置在bx上150 mov dx,0x3d4151 mov al,0x0f ;低8位152 out dx,al153 154 mov al,bl155 mov dx,0x3d5156 out dx,al157 158 mov dx,0x3d4159 mov al,0x0e;高8位160 out dx,al161 162 mov al,bh163 mov dx,0x3d5164 out dx,al165 166 pop ds167 pop es168 pop ebx169 ret170;---------------------------------------------------------------- 171allocate_memory: ;简易内存分配策略172 ;输入ecx:想要分配的总字节数173 ;输出ecx:分配的线性基地址174 push ds175 push eax176 push ebx177 178 mov eax,Core_Data_Segement179 mov ds,eax180 mov eax,[ram_alloc]181 mov edx,eax ;edx暂存一下eax182 add eax,ecx183 184 cmp eax,edx ;发现新分配的现地址比原来的还小,说明已经溢出185 jge _alloc186 mov ebx,mem_alloc_fail187 call Sys_Routine_Segement:put_string188 mov ecx,0 ;分配为0说明已经分配失败189 jmp _exit1190 _alloc:191 192 mov ebx,eax193 and ebx,0xfffffffc194 add ebx,4 ;强行向上取整195 test eax,0x00000003196 cmovnz eax,ebx197 mov ecx,[ram_alloc];要返回要分配的初始地址198 mov [ram_alloc],eax;下一次分配的线性基地址199 add [ram_recycled],eax200 sub [ram_recycled],ecx;记录大小201 202 _exit1:203 pop ebx204 pop eax205 pop ds206 207 retf208;----------------------------------------------------------------209recycled_memory_and_gdt:210 mov eax,[ram_recycled]211 sub [ram_alloc],eax212 mov dword[ram_recycled],0;因为我们还没学到多任务,先这样简单地清零213 214 sgdt [pgdt_base_tmp]215 sub word[pgdt_base_tmp],32;应用程序的4个段全部减掉216 lgdt [pgdt_base_tmp];重新加载内核217 retf218;---------------------------------------------------------------- 219PrintDword: ;显示edx内容的一个调试函数220 pushad221 push ds222 223 mov eax,Core_Data_Segement224 mov ds,eax225 226 mov ebx,bin_hex227 mov ecx,8228 229 _query:230 rol edx,4231 mov eax,edx232 and eax,0x0000000f233 xlat234 235 push ecx236 mov cl,al237 call put_char238 pop ecx239 240 loop _query241 242 pop ds243 popad244 245 retf246;----------------------------------------------------------------247Make_Descriptor: ;构造描述符248;输入:249;eax:线性基地址250;ebx:段界限251;ecx:属性252;输出:253;eax:描述符低32位254;edx:描述符高32位255 mov edx,eax256 and edx,0xffff0000257 rol edx,8258 bswap edx259 or edx,ecx260 261 shl eax,16262 or ax,bx263 and ebx,0x000f0000264 or edx,ebx265 retf 266;----------------------------------------------------------------267Set_New_GDT: ;装载新的描述符268;输入:edx:eax描述符269;输出:cx选择子270 push ds271 push es272 273 mov ebx,Core_Data_Segement274 mov ds,ebx275 276 mov ebx,All_4GB_Segment277 mov es,ebx278 279 sgdt [pgdt_base_tmp]280 281 movzx ebx,word[pgdt_base_tmp]282 inc bx ;注意这里要一定是inc bx而不是inc ebx,因为gdt段地址初始化是0xffff的283;要用到回绕特性284 add ebx,[pgdt_base_tmp+0x02] ;得到pgdt的线性基地址285 286 mov [es:ebx],eax287 mov [es:ebx+0x04],edx;装载新的gdt符288;装载描述符要装载到实际位置上289 290 add word[pgdt_base_tmp],8 ;给gdt的段界限加上8(字节)291 292 lgdt [pgdt_base_tmp];加载gdt到gdtr的位置和实际表的位置无关293 294 mov ax,[pgdt_base_tmp];得到段界限295 xor dx,dx296 mov bx,8 ;得到gdt大小297 div bx298 mov cx,ax299 shl cx,3 ;得到选择子,ti=0(全局描述符),rpl=0(申请特权0级)300 301 pop es302 pop ds303 retf 304 ;=========================================================================305 ;===========================内核数据区====================================306 ;=========================================================================307 SECTION Core_Data align=16 vstart=0308 ;-------------------------------------------------------------------------------309 pgdt_base_tmp:dw 0 ;这一章的用户程序都是从GDT中加载的310 dd 0311 312 ram_alloc: dd 0x00100000;下次分配内存时的起始地址(直接暴力从0x00100000开始分配了)313 ram_recycled dd 0;这里储存程序实际用的大小 314 salt:315 salt_1:db '@Printf';@Printf函数(公用例程)316 times 256-($-salt_1) db 0317 dd put_string318 dw Sys_Routine_Segement 319 320 salt_2:db '@ReadHarddisk';@ReadHarddisk函数(公用例程)321 times 256-($-salt_2) db 0322 dd ReadHarddisk323 dw Sys_Routine_Segement324 325 salt_3:db '@PrintDwordAsHexString' ;@PrintDwordAsHexString函数(公用例程)326 times 256-($-salt_3) db 0327 dd PrintDword328 dw Sys_Routine_Segement 329 330 salt_4:db '@TerminateProgram' ;@TerminateProgram函数(内核例程)331 times 256-($-salt_4) db 0332 dd _return_point333 dw Core_Code_Segement 334 335 salt_length: equ $-salt_4336 salt_items_sum equ ($-salt)/salt_length ;得到项目总数337 338 message_1db ' If you seen this message,that means we '339 db 'are now in protect mode,and the system '340 db 'core is loaded,and the video display '341 db 'routine works perfectly.',0x0d,0x0a,0342 343 message_5db ' Loading user program...',0344 345 do_statusdb 'Done.',0x0d,0x0a,0346 347 message_6db 0x0d,0x0a,0x0d,0x0a,0x0d,0x0a348 db ' User program terminated,control returned.',0349 message_7db 0x0d,0x0a,0x0d,0x0a350 db ' We have been backed to kernel.',0351 message_8db 0x0d,0x0a352 db ' The GDT and memory have benn recycled.',0353 354 bin_hex db '0123456789ABCDEF'355;put_hex_dword子过程用的查找表356 core_buf times 2048 db 0 ;内核用的缓冲区(2049个字节(2MB))357 358 esp_pointer dd 0;内核用来临时保存自己的栈指针359 360 cpu_brnd0db 0x0d,0x0a,' ',0361 cpu_brand times 52 db 0362 cpu_brnd1db 0x0d,0x0a,0x0d,0x0a,0 363 mem_alloc_fail db 'The Program is too large to load'364 ;=========================================================================365 ;===========================内核代码区====================================366 ;=========================================================================367 SECTION Core_Code align=16 vstart=0 368load_program: ;输入esi:磁盘号369 ;输出ax: 用户程序头部选择子370 push esi371 push ds372 push es373 374 mov eax,Core_Data_Segement375 mov ds,eax ;切换到内核数据段376 377 mov ebx,core_buf;ebx要在内核数据缓冲区(先读取头部在缓冲区,esi已经是有扇区号了)378 call Sys_Routine_Segement:ReadHarddisk379 380 mov eax,[core_buf];读取用户程序长度381 382 mov ebx,eax ;给ebx一个副本383 and ebx,0xfffffe00;清空低9位(强制对齐512)384 add ebx,512 ;加上512385 test eax,0x000001ff386 cmovnz eax,ebx;低9位不为0则使用向上取整的结果387 388 mov ecx,eax ;eax是整个程序的向上取整的大小389 call Sys_Routine_Segement:allocate_memory ;先分配内存给整个程序,再分配内存给栈区390 mov ebx,ecx;这个才是真正的程要用到的线性基地址391 392 push ebx ;ebx就是用户程序加载到内存的地址393 xor edx,edx394 mov ecx,512 ;千万不要改掉ebx395 div ecx396 mov ecx,eax397 398 mov eax,All_4GB_Segment ;切换到4GB段区域(平坦模式)399 mov ds,eax400 401 _loop_read:402 call Sys_Routine_Segement:ReadHarddisk ;esi还是User_Program_Address403 inc esi404 add ebx,512405 loop _loop_read406 407 ;加载用户程序头部段(下面的所有段的地址都要转化为选择子)408 pop edi ;程序被装载的基地址409 mov eax,edi410 mov ebx,[edi+0x04];用户程序头部的长度411 dec ebx ;段界限412 mov ecx,0x0040913 call Sys_Routine_Segement:Make_Descriptor414 call Sys_Routine_Segement:Set_New_GDT415 mov [edi+0x04],cx;放入选择子416 417 ;加载用户程序代码段418 mov eax,edi 419 add eax,[edi+0x14];别忘记重定位了420 mov ebx,[edi+0x18];用户程序代码段的长度421 dec ebx ;段界限422 mov ecx,0x00409800423 call Sys_Routine_Segement:Make_Descriptor424 call Sys_Routine_Segement:Set_New_GDT425 mov [edi+0x14],cx;放入选择子426 427 ;加载用户程序数据段428 mov eax,edi 429 add eax,[edi+0x1c];别忘记重定位了430 mov ebx,[edi+0x20];用户程序数据段的长度431 dec ebx ;段界限432 mov ecx,0x0040933 call Sys_Routine_Segement:Make_Descriptor434 call Sys_Routine_Segement:Set_New_GDT435 mov [edi+0x1c],cx;放入选择子436 437 ;加载用户程序栈段(用户程序给出的建议大小) 438 mov ecx,[edi+0x0c];确定栈段的建议大小439 mov ebx,0x000fffff440 sub ebx,ecx ;这就是段界限了441 mov eax,4096;4KB442 mul ecx443 add [ram_recycled],eax ;加上分配的内存444 445 mov ecx,eax ;这个时候eax是大小446 call Sys_Routine_Segement:allocate_memory ;分到内存447 add eax,ecx ;eax线性基地址,ebx段界限448 mov ecx,0x00c09600;4KB粒度向下拓展数据段449 call Sys_Routine_Segement:Make_Descriptor450 call Sys_Routine_Segement:Set_New_GDT451 mov [edi+0x08],cx452 453 ;现在开始重定位API符号表454 ;---------------------------------------------------------------------455 mov eax,[edi+0x04];先设es再ds,不要搞反了,现在es的0x04这个地方是头部的选择子456 mov es,eax457 mov eax,Core_Data_Segement458 mov ds,eax459 460 cld461 mov ecx,[es:0x24];得到用户程序符号表的条数462 mov edi,0x28;用户符号表的偏移地址是0x28463 464 _loop_U_SALT:465 push edi466 push ecx467 468 mov ecx,salt_items_sum469 mov esi,salt470 471 _loop_C_SALT:472 push edi473 push esi474 push ecx475 476 mov ecx,64;比较256个字节477 repe cmpsd478 jne _re_match ;如果成功匹配,那么esi和edi刚好会在数据区之后的479 480 mov eax,[esi] ;偏移地址481 mov [es:edi-256],eax ;把偏移地址填入用户程序的符号区482 mov ax,[esi+0x04] ;段的选择子483 mov [es:edi-252],ax ;把段的选择子填入用户程序的段选择区484 485 _re_match:486 pop ecx487 pop esi488 add esi,salt_length489 pop edi490 loop _loop_C_SALT491 492 pop ecx493 pop edi494 add edi,256495 loop _loop_U_SALT496 ;---------------------------------------------------------------------497 mov ax,[es:0x04];把头部的段选择子给ax498 499 pop es500 pop ds501 pop esi502 ret503start:504 mov eax,Core_Data_Segement505 mov ds,eax506 507 mov ebx,message_1508 call Sys_Routine_Segement:put_string509 510 mov eax,0511 cpuid512 cmp eax,0x80000004 ;判断是否有0x80000002-0x80000004功能 513 jl _@load514 515 ;显示处理器品牌信息,从80486的后期版本开始引入516 mov eax,0x80000002517 cpuid518 mov [cpu_brand+0x00],eax519 mov [cpu_brand+0x04],ebx520 mov [cpu_brand+0x08],ecx521 mov [cpu_brand+0x0c],edx522 523 mov eax,0x80000003524 cpuid525 mov [cpu_brand+0x10],eax526 mov [cpu_brand+0x14],ebx527 mov [cpu_brand+0x18],ecx528 mov [cpu_brand+0x1c],edx529 530 mov eax,0x80000004531 cpuid532 mov [cpu_brand+0x20],eax533 mov [cpu_brand+0x24],ebx534 mov [cpu_brand+0x28],ecx535 mov [cpu_brand+0x2c],edx536 537 mov ebx,cpu_brnd0538 call Sys_Routine_Segement:put_string539 mov ebx,cpu_brand540 call Sys_Routine_Segement:put_string541 mov ebx,cpu_brnd1542 call Sys_Routine_Segement:put_string543 544 _@load:545 mov ebx,message_5546 call Sys_Routine_Segement:put_string547 mov esi,User_Program_Address548 call load_program549 550 mov ebx,do_status551 call Sys_Routine_Segement:put_string552 553 mov [esp_pointer],esp ;临时保存一下栈指针554 mov ds,ax ;使ds指向用户程序头部555 556 jmp far [0x10]557 558 _return_point:559 560 mov eax,Core_Data_Segement561 mov ds,eax562 mov eax,Stack_Segement563 mov ss,eax ;重新设置数据段和栈段564 mov esp,[esp_pointer]565 mov ebx,message_7566 call Sys_Routine_Segement:put_string567 568 call Sys_Routine_Segement:recycled_memory_and_gdt569 mov ecx,[ram_alloc]570571 mov ebx,message_8572 call Sys_Routine_Segement:put_string573 cli574 hlt575 ;=========================================================================576 SECTION core_trail577 ;----------------------------------------------------------------578 Program_end:

1 ;==============================用户程序======================================= 2 SECTION header vstart=0 3 4 program_length dd program_end;程序总长度#0x00 5 6 head_len dd header_end ;程序头部的长度#0x04 7 8 stack_seg dd 0;用于接收堆栈段选择子#0x08 9 stack_len dd 1;程序建议的堆栈大小#0x0c10;以4KB为单位11 12 prgentry dd start;程序入口#0x10 13 code_seg dd section.code.start ;代码段位置#0x1414 code_len dd code_end ;代码段长度#0x1815 16 data_seg dd section.data.start ;数据段位置#0x1c17 data_len dd data_end ;数据段长度#0x ;-------------------------------------------------------------------------------19 ;符号地址检索表20 salt_items dd (header_end-salt)/256 ;#0x242122 salt: ;#0x2823 Printf: db '@Printf'24 times 256-($-Printf) db 025 26 TerminateProgram:db '@TerminateProgram'27 times 256-($-TerminateProgram) db 028 29 ReadHarddisk: db '@ReadHarddisk'30 times 256-($-ReadHarddisk) db 031 32 header_end:33 ;===============================================================================34 SECTION data align=16 vstart=0 3536 buffer times 1024 db 0 ;缓冲区37 38 message_1 db 0x0d,0x0a,0x0d,0x0a39 db '**********User program is runing**********'40 db 0x0d,0x0a,041 message_2 db ' Disk data:',0x0d,0x0a,042 43 data_end:44 45 ;===============================================================================46 [bits 32]47 ;===============================================================================48 SECTION code align=16 vstart=049 start:50 User_Data_Fileequ 100 ;数据文件存放地点51 mov eax,ds52 mov fs,eax5354 mov eax,[stack_seg]55 mov ss,eax56 mov esp,05758 mov eax,[data_seg]59 mov ds,eax6061 mov ebx,message_162 call far [fs:Printf]6364 mov esi,User_Data_File 65 mov ebx,buffer ;缓冲区偏移地址66 call far [fs:ReadHarddisk];相当于调用函数6768 mov ebx,message_269 call far [fs:Printf]7071 mov ebx,buffer 72 call far [fs:Printf] 7374 jmp far [fs:TerminateProgram] ;将控制权返回到系统 75 76 code_end:77 78 ;===============================================================================79 SECTION trail80 ;-------------------------------------------------------------------------------81 program_end:

2.课后习题

其实这一章的课后习题很无聊的,就是把栈段那里改一下就好了,用户程序按照第八章那样自己填一个区域。

1;加载用户程序栈段 2 mov eax,edi3 add eax,[edi+0x08]4 mov ebx,[edi+0x0c] ;用户程序栈段的长度 5 add eax,ebx ;得到栈段的线性基地址 6 mov edx,0xffffffff7 sub edx,ebx 8 mov ebx,edx ;得到段界限 9 10 mov ecx,0x0040960011 call Sys_Routine_Segement:Make_Descriptor12 call Sys_Routine_Segement:Set_New_GDT13 mov [edi+0x08],cx;放入选择子

1 ;==============================用户程序======================================= 2 SECTION header vstart=0 3 4 program_length dd program_end;程序总长度#0x00 5 6 head_len dd header_end ;程序头部的长度#0x04 7 8 stack_seg dd section.stack.start ;栈段位置#0x08 9 stack_len dd stack_end ;栈段长度#0x0c10 11 12 prgentry dd start;程序入口#0x10 13 code_seg dd section.code.start ;代码段位置#0x1414 code_len dd code_end ;代码段长度#0x1815 16 data_seg dd section.data.start ;数据段位置#0x1c17 data_len dd data_end ;数据段长度#0x ;-------------------------------------------------------------------------------19 ;符号地址检索表20 salt_items dd (header_end-salt)/256 ;#0x242122 salt: ;#0x2823 Printf: db '@Printf'24 times 256-($-Printf) db 025 26 TerminateProgram:db '@TerminateProgram'27 times 256-($-TerminateProgram) db 028 29 ReadHarddisk: db '@ReadHarddisk'30 times 256-($-ReadHarddisk) db 031 32 header_end:33 ;===============================================================================34 SECTION data align=16 vstart=0 3536 buffer times 1024 db 0 ;缓冲区37 38 message_1 db 0x0d,0x0a,0x0d,0x0a39 db '**********User program is runing**********'40 db 0x0d,0x0a,041 message_2 db ' Disk data:',0x0d,0x0a,042 43 data_end:44 45 ;===============================================================================46 [bits 32]47 ;===============================================================================48 SECTION code align=16 vstart=049 start:50 User_Data_Fileequ 100 ;数据文件存放地点51 mov eax,ds52 mov fs,eax5354 mov eax,[stack_seg]55 mov ss,eax56 mov esp,05758 mov eax,[data_seg]59 mov ds,eax6061 mov ebx,message_162 call far [fs:Printf]6364 mov esi,User_Data_File 65 mov ebx,buffer ;缓冲区偏移地址66 call far [fs:ReadHarddisk];相当于调用函数6768 mov ebx,message_269 call far [fs:Printf]7071 mov ebx,buffer 72 call far [fs:Printf] 7374 jmp far [fs:TerminateProgram] ;将控制权返回到系统 75 76 code_end:77 ;===============================================================================78 SECTION stack align=16 vstart=079 times 4096 db 080 stack_end:81 ;===============================================================================82 SECTION trail83 ;-------------------------------------------------------------------------------84 program_end:

ASM:《X86汇编语言-从实模式到保护模式》第13章:保护模式下内核的加载 程序的动态加载和执行...

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。