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
 13         
 14         mov ds,eax                                ;让ds指向gdt位置进行操作
 15         mov ebx,edx                                ;别忘了还有可能出现偏移地址
 16         
 17         ;---------------------描述符#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
 32         
 33         mov word[cs:pgdt_base+0x7c00],39        ;加载gdt
 34         lgdt [cs:pgdt_base+0x7c00]
 35         
 36         in al,0x92                                ;快速开启A20
 37         or al,0x02                                ;是写入2,不要搞错了,写入1就是重启了
 38         out 0x92,al
 39         cli                                        ;关掉BIOS中断
 40         
 41         mov eax,cr0
 42         or eax,0x01                                ;设置PE位
 43         mov cr0,eax
 44         
 45         jmp dword 0x0010:flush                    ;进入保护模式
 46         
 47         [bits 32]
 48     flush:
 49         mov eax,0x0008                            ;选择4GB的代码段直接给ds
 50         mov ds,eax
 51         mov eax,0x0018
 52         mov ss,eax                                ;设置栈段
 53         xor esp,esp                             
 54         
 55         ;接下来开始读取内核头部
 56         mov esi,core_sector_address
 57         mov edi,core_phy_base
 58         call read_harddisk_0
 59         
 60         mov eax,[core_phy_base]                    ;读取用户总长度
 61         xor edx,edx
 62         mov ebx,512
 63         div ebx
 64         
 65         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             ;重新构建描述符#3
103             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_descriptor
110             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 ecx
121         
122         mov edx,0x1f2                            ;读取一个扇区
123         mov al,0x01
124         out dx,al
125         
126         mov eax,esi                                ;0~7位,0x1f3端口
127         inc edx
128         out dx,al
129         
130         mov al,ah                                ;8~15位,0x1f4端口
131         inc edx
132         out dx,al
133         
134         shr eax,16                                ;16-23位,0x1f5端口                
135         inc edx
136         out dx,al
137         
138         mov al,ah                                ;24-28位,LBA模式主硬盘
139         inc edx
140         and al,0x0f
141         or al,0xe0
142         out dx,al                                
143         
144         inc edx                                    ;读命令,0x1f7端口
145         mov al,0x20                                
146         out dx,al
147         
148         .wait:
149             in al,dx
150             and al,0x88
151             cmp al,0x08
152             jne .wait
153         
154         mov dx,0x1f0
155         mov ecx,256
156         .read:
157             in ax,dx
158             mov [edi],ax
159             add edi,2
160             loop .read
161         
162         pop ecx
163         
164         ret
165 ;----------------------------------------------------------------------
166 make_gdt_descriptor:
167                                                 ;eax:线性基地址
168                                                 ;ebx:段界限
169                                                 ;ecx:属性
170         mov edx,eax
171         and edx,0xffff0000                        ;得到线性基地址的16-31位
172         rol edx,8
173         bswap edx                                ;强行把0-7位和16-23位互换
174         or edx,ecx                                ;装载属性
175         
176         shl eax,16
177         or ax,bx                                ;配好段界限低16位
178         
179         and ebx,0x000f0000
180         or edx,ebx                                ;装载段界限的16-19位
181         
182         ret
183 ;======================================================================
184     pgdt_base             dw 0
185                         dd 0x00007e00                    ;GDT的物理地址
186 ;======================================================================
187     times 510-($-$$)     db 0
188                         dw 0xaa55
  1 ;===============================内核程序=================================
  2         ;定义内核所要用到的选择子
  3         All_4GB_Segment         equ 0x0008        ;4GB的全内存区域
  4         Stack_Segement             equ 0x0018        ;内核栈区
  5         Print_Segement            equ 0x0020        ;显存映射区
  6         Sys_Routine_Segement     equ 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_Entry                dd    start                        ;注意偏移地址一定是32位的
 18                                 dw  Core_Code_Segement
 19     ;----------------------------------------------------------------
 20                             [bits 32]
 21 ;=========================================================================
 22 ;============================公用例程区===================================
 23 ;=========================================================================
 24 SECTION Sys_Routine align=16 vstart=0
 25     ReadHarddisk:                                                ;esi:28位磁盘号
 26                                                                 ;ebx:偏移地址
 27         pushad
 28         
 29         mov dx,0x1f2
 30         mov al,0x01        ;读一个扇区                                
 31         out dx,al
 32         
 33         inc edx            ;0-7位
 34         mov eax,esi
 35         out dx,al
 36         
 37         inc edx            ;8-15位
 38         mov al,ah
 39         out dx,al
 40         
 41         inc edx            ;16-23位
 42         shr eax,16
 43         out dx,al
 44         
 45         inc edx            ;24-28位,主硬盘,LBA模式
 46         mov al,ah
 47         and al,0x0f
 48         or al,0xe0
 49         out dx,al
 50         
 51         inc edx
 52         mov al,0x20
 53         out dx,al
 54         
 55         _wait:
 56             in al,dx
 57             and al,0x88
 58             cmp al,0x08
 59             jne _wait
 60         
 61         mov dx,0x1f0
 62         mov ecx,256
 63         
 64         _read:
 65             in ax,dx
 66             mov [ebx],ax
 67             add ebx,2
 68             loop _read
 69         
 70         popad
 71         retf
 72     ;----------------------------------------------------------------
 73     put_string:                                                    ;ebx:偏移地址
 74         pushad
 75         
 76         _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,al
101             mov dx,0x3d5
102             in al,dx        ;现在ax就是当前光标的位置
103             
104             _judge:
105                 cmp cl,0x0a
106                 je _set_0x0a
107                 cmp cl,0x0d
108                 je _set_0x0d
109             _print_visible:
110                 mov bx,ax
111                 mov eax,Print_Segement
112                 mov es,eax
113                 shl bx,1     ;注意这里一定要把ebx变成原来的两倍,实际位置是光标位置的两倍
114                 mov [es:bx],cl            ;注意这里是屏幕!
115                 mov byte[es:bx+1],0x07        
116                 add bx,2
117                 shr bx,1
118                 jmp _roll_screen
119             _set_0x0d:        ;回车
120                 mov bl,80
121                 div bl
122                 mul bl
123                 mov bx,ax
124                 jmp _set_cursor
125             _set_0x0a:        ;换行
126                 mov bx,ax
127                 add bx,80
128                 jmp _roll_screen
129             _roll_screen:
130                 cmp bx,2000
131                 jl _set_cursor
132                 mov eax,Print_Segement
133                 mov ds,eax
134                 mov es,eax
135                 
136                 cld
137                 mov edi,0x00
138                 mov esi,0xa0
139                 mov ecx,1920
140                 rep movsw
141             _cls:
142                 mov bx,3840
143                 mov ecx,80
144                 _print_blank:
145                     mov word[es:bx],0x0720
146                     add bx,2
147                     loop _print_blank    
148                 mov bx,1920    ;别总是忘了光标的位置!
149             _set_cursor:        ;改变后的光标位置在bx上
150             mov dx,0x3d4
151             mov al,0x0f        ;低8位
152             out dx,al
153             
154             mov al,bl
155             mov dx,0x3d5
156             out dx,al
157             
158             mov dx,0x3d4
159             mov al,0x0e     ;高8位
160             out dx,al
161             
162             mov al,bh
163             mov dx,0x3d5
164             out dx,al
165             
166             pop ds
167             pop es
168             pop ebx
169             ret
170     ;----------------------------------------------------------------        
171     allocate_memory:                            ;简易内存分配策略
172                                                 ;输入ecx:想要分配的总字节数
173                                                 ;输出ecx:分配的线性基地址
174         push ds
175         push eax
176         push ebx
177             
178         mov eax,Core_Data_Segement
179         mov ds,eax
180         mov eax,[ram_alloc]
181         mov edx,eax                                ;edx暂存一下eax
182         add eax,ecx
183         
184         cmp eax,edx                                ;发现新分配的现地址比原来的还小,说明已经溢出
185         jge _alloc
186             mov ebx,mem_alloc_fail
187             call Sys_Routine_Segement:put_string
188             mov ecx,0                        ;分配为0说明已经分配失败
189             jmp _exit1
190         _alloc:
191             
192         mov ebx,eax
193         and ebx,0xfffffffc
194         add ebx,4                             ;强行向上取整
195         test eax,0x00000003
196         cmovnz eax,ebx
197         mov ecx,[ram_alloc]                    ;要返回要分配的初始地址
198         mov [ram_alloc],eax                    ;下一次分配的线性基地址
199         add [ram_recycled],eax
200         sub [ram_recycled],ecx                ;记录大小
201             
202         _exit1:
203         pop ebx
204         pop eax
205         pop ds
206         
207         retf
208     ;----------------------------------------------------------------
209     recycled_memory_and_gdt:
210         mov eax,[ram_recycled]
211         sub [ram_alloc],eax
212         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         retf
218     ;----------------------------------------------------------------    
219     PrintDword:                                ;显示edx内容的一个调试函数
220         pushad
221         push ds
222         
223         mov eax,Core_Data_Segement
224         mov ds,eax
225         
226         mov ebx,bin_hex
227         mov ecx,8
228         
229         _query:
230             rol edx,4
231             mov eax,edx
232             and eax,0x0000000f
233             xlat
234             
235             push ecx
236             mov cl,al
237             call put_char
238             pop ecx
239             
240         loop _query
241             
242         pop ds
243         popad
244         
245         retf
246     ;----------------------------------------------------------------
247     Make_Descriptor:                        ;构造描述符
248                                             ;输入:
249                                             ;eax:线性基地址
250                                             ;ebx:段界限
251                                             ;ecx:属性
252                                             ;输出:
253                                             ;eax:描述符低32位
254                                             ;edx:描述符高32位
255         mov edx,eax
256         and edx,0xffff0000
257         rol edx,8
258         bswap edx
259         or edx,ecx
260         
261         shl eax,16
262         or ax,bx
263         and ebx,0x000f0000
264         or edx,ebx
265         retf                                
266     ;----------------------------------------------------------------
267     Set_New_GDT:                            ;装载新的描述符
268                                             ;输入:edx:eax描述符
269                                             ;输出:cx选择子
270         push ds
271         push es
272         
273         mov ebx,Core_Data_Segement
274         mov ds,ebx
275         
276         mov ebx,All_4GB_Segment
277         mov es,ebx
278         
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],eax
287         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,dx
296         mov bx,8                            ;得到gdt大小
297         div bx
298         mov cx,ax
299         shl cx,3                            ;得到选择子,ti=0(全局描述符),rpl=0(申请特权0级)
300         
301         pop es
302         pop ds
303         retf                            
304 ;=========================================================================
305 ;===========================内核数据区====================================
306 ;=========================================================================
307 SECTION Core_Data align=16 vstart=0
308 ;-------------------------------------------------------------------------------
309         pgdt_base_tmp:          dw  0                             ;这一章的用户程序都是从GDT中加载的
310                                 dd  0
311 
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    0
317                                 dd    put_string
318                                 dw    Sys_Routine_Segement    
319                                 
320         salt_2:                    db    '@ReadHarddisk'                ;@ReadHarddisk函数(公用例程)
321         times 256-($-salt_2)    db    0
322                                 dd    ReadHarddisk
323                                 dw    Sys_Routine_Segement
324                                 
325         salt_3:                    db    '@PrintDwordAsHexString'    ;@PrintDwordAsHexString函数(公用例程)
326         times 256-($-salt_3)    db    0
327                                 dd    PrintDword
328                                 dw    Sys_Routine_Segement    
329                                 
330         salt_4:                    db    '@TerminateProgram'            ;@TerminateProgram函数(内核例程)
331         times 256-($-salt_4)    db    0
332                                 dd    _return_point
333                                 dw    Core_Code_Segement        
334                                 
335         salt_length:            equ    $-salt_4
336         salt_items_sum            equ    ($-salt)/salt_length        ;得到项目总数
337         
338         message_1                db  '  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,0
342 
343         message_5                db  '  Loading user program...',0
344 
345         do_status                db  'Done.',0x0d,0x0a,0
346 
347         message_6                db  0x0d,0x0a,0x0d,0x0a,0x0d,0x0a
348                                 db  '  User program terminated,control returned.',0
349         message_7                db  0x0d,0x0a,0x0d,0x0a
350                                 db  '  We have been backed to kernel.',0
351         message_8                db  0x0d,0x0a
352                                 db  '  The GDT and memory have benn recycled.',0
353 
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_brnd0                db 0x0d,0x0a,'  ',0
361         cpu_brand         times 52 db 0
362         cpu_brnd1                db 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        
368     load_program:                        ;输入esi:磁盘号
369                                         ;输出ax: 用户程序头部选择子
370         push esi
371         push ds
372         push es
373         
374         mov eax,Core_Data_Segement
375         mov ds,eax                        ;切换到内核数据段
376         
377         mov ebx,core_buf                ;ebx要在内核数据缓冲区(先读取头部在缓冲区,esi已经是有扇区号了)
378         call Sys_Routine_Segement:ReadHarddisk
379         
380         mov eax,[core_buf]                ;读取用户程序长度
381         
382         mov ebx,eax                        ;给ebx一个副本
383         and ebx,0xfffffe00                ;清空低9位(强制对齐512)
384         add ebx,512                        ;加上512
385         test eax,0x000001ff                
386         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,edx
394         mov ecx,512                        ;千万不要改掉ebx
395         div ecx
396         mov ecx,eax
397         
398         mov eax,All_4GB_Segment            ;切换到4GB段区域(平坦模式)
399         mov ds,eax
400         
401         _loop_read:
402             call Sys_Routine_Segement:ReadHarddisk    ;esi还是User_Program_Address
403             inc esi
404             add ebx,512
405         loop _loop_read
406         
407         ;加载用户程序头部段(下面的所有段的地址都要转化为选择子)
408         pop edi                            ;程序被装载的基地址
409         mov eax,edi
410         mov ebx,[edi+0x04]                ;用户程序头部的长度
411         dec ebx                            ;段界限
412         mov ecx,0x00409200
413         call Sys_Routine_Segement:Make_Descriptor
414         call Sys_Routine_Segement:Set_New_GDT
415         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,0x00409800
423         call Sys_Routine_Segement:Make_Descriptor
424         call Sys_Routine_Segement:Set_New_GDT
425         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,0x00409200
433         call Sys_Routine_Segement:Make_Descriptor
434         call Sys_Routine_Segement:Set_New_GDT
435         mov [edi+0x1c],cx                ;放入选择子
436         
437         ;加载用户程序栈段(用户程序给出的建议大小)                        
438         mov ecx,[edi+0x0c]                ;确定栈段的建议大小
439         mov ebx,0x000fffff
440         sub ebx,ecx                        ;这就是段界限了
441         mov eax,4096                    ;4KB
442         mul ecx
443         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_Descriptor
450         call Sys_Routine_Segement:Set_New_GDT
451         mov [edi+0x08],cx
452         
453         ;现在开始重定位API符号表
454         ;---------------------------------------------------------------------
455         mov eax,[edi+0x04]                ;先设es再ds,不要搞反了,现在es的0x04这个地方是头部的选择子
456         mov es,eax
457         mov eax,Core_Data_Segement
458         mov ds,eax
459         
460         cld
461         mov ecx,[es:0x24]                ;得到用户程序符号表的条数
462         mov edi,0x28                    ;用户符号表的偏移地址是0x28
463 
464         _loop_U_SALT:                    
465             push edi
466             push ecx
467             
468             mov ecx,salt_items_sum
469             mov esi,salt
470             
471             _loop_C_SALT:
472                 push edi
473                 push esi
474                 push ecx
475                 
476                 mov ecx,64                ;比较256个字节
477                 repe cmpsd
478                 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 ecx
487                 pop esi
488                 add esi,salt_length
489                 pop edi
490             loop _loop_C_SALT
491             
492             pop ecx
493             pop edi
494             add edi,256
495         loop _loop_U_SALT
496         ;---------------------------------------------------------------------
497         mov ax,[es:0x04]                ;把头部的段选择子给ax
498             
499         pop es
500         pop ds
501         pop esi
502         ret
503     start:
504         mov eax,Core_Data_Segement
505         mov ds,eax
506         
507         mov ebx,message_1
508         call Sys_Routine_Segement:put_string
509         
510         mov eax,0                    
511         cpuid
512         cmp eax,0x80000004            ;判断是否有0x80000002-0x80000004功能    
513         jl _@load
514         
515         ;显示处理器品牌信息,从80486的后期版本开始引入
516         mov eax,0x80000002
517         cpuid
518         mov [cpu_brand+0x00],eax
519         mov [cpu_brand+0x04],ebx
520         mov [cpu_brand+0x08],ecx
521         mov [cpu_brand+0x0c],edx
522         
523         mov eax,0x80000003
524         cpuid
525         mov [cpu_brand+0x10],eax
526         mov [cpu_brand+0x14],ebx
527         mov [cpu_brand+0x18],ecx
528         mov [cpu_brand+0x1c],edx
529         
530         mov eax,0x80000004
531         cpuid
532         mov [cpu_brand+0x20],eax
533         mov [cpu_brand+0x24],ebx
534         mov [cpu_brand+0x28],ecx
535         mov [cpu_brand+0x2c],edx
536         
537         mov ebx,cpu_brnd0
538         call Sys_Routine_Segement:put_string
539         mov ebx,cpu_brand
540         call Sys_Routine_Segement:put_string
541         mov ebx,cpu_brnd1
542         call Sys_Routine_Segement:put_string
543 
544         _@load:
545         mov ebx,message_5
546         call Sys_Routine_Segement:put_string
547         mov esi,User_Program_Address
548         call load_program
549         
550         mov ebx,do_status
551         call Sys_Routine_Segement:put_string
552         
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_Segement
561         mov ds,eax
562         mov eax,Stack_Segement
563         mov ss,eax                        ;重新设置数据段和栈段
564         mov esp,[esp_pointer]
565         mov ebx,message_7
566         call Sys_Routine_Segement:put_string
567         
568         call Sys_Routine_Segement:recycled_memory_and_gdt
569         mov ecx,[ram_alloc]
570     
571         mov ebx,message_8
572         call Sys_Routine_Segement:put_string
573         cli
574         hlt
575 ;=========================================================================
576 SECTION core_trail
577 ;----------------------------------------------------------------
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                    ;程序建议的堆栈大小#0x0c
10                                                  ;以4KB为单位
11                                                   
12         prgentry         dd start                ;程序入口#0x10 
13         code_seg         dd section.code.start   ;代码段位置#0x14
14         code_len         dd code_end             ;代码段长度#0x18
15 
16         data_seg         dd section.data.start   ;数据段位置#0x1c
17         data_len         dd data_end             ;数据段长度#0x20
18 ;-------------------------------------------------------------------------------
19         ;符号地址检索表
20         salt_items       dd (header_end-salt)/256 ;#0x24
21          
22         salt:                                     ;#0x28
23         Printf:           db  '@Printf'
24                      times 256-($-Printf) db 0
25                      
26         TerminateProgram:db  '@TerminateProgram'
27                      times 256-($-TerminateProgram) db 0
28                      
29         ReadHarddisk:    db  '@ReadHarddisk'
30                      times 256-($-ReadHarddisk) db 0
31                  
32 header_end:
33 ;===============================================================================
34 SECTION data align=16 vstart=0    
35                          
36         buffer times 1024 db  0         ;缓冲区
37 
38         message_1         db  0x0d,0x0a,0x0d,0x0a
39                           db  '**********User program is runing**********'
40                           db  0x0d,0x0a,0
41         message_2         db  '  Disk data:',0x0d,0x0a,0
42 
43 data_end:
44 
45 ;===============================================================================
46       [bits 32]
47 ;===============================================================================
48 SECTION code align=16 vstart=0
49 start:
50         User_Data_File     equ 100            ;数据文件存放地点
51         mov eax,ds
52         mov fs,eax
53      
54         mov eax,[stack_seg]
55         mov ss,eax
56         mov esp,0
57      
58         mov eax,[data_seg]
59         mov ds,eax
60      
61         mov ebx,message_1
62         call far [fs:Printf]
63      
64         mov esi,User_Data_File              
65         mov ebx,buffer                      ;缓冲区偏移地址
66         call far [fs:ReadHarddisk]          ;相当于调用函数
67      
68         mov ebx,message_2
69         call far [fs:Printf]
70      
71         mov ebx,buffer 
72         call far [fs:Printf]           
73      
74         jmp far [fs:TerminateProgram]       ;将控制权返回到系统 
75       
76 code_end:
77 
78 ;===============================================================================
79 SECTION trail
80 ;-------------------------------------------------------------------------------
81 program_end:

2. 课后习题

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

   

 1     ;加载用户程序栈段
 2         mov eax,edi     
 3         add eax,[edi+0x08]              
 4         mov ebx,[edi+0x0c]              ;用户程序栈段的长度
 5         add eax,ebx                     ;得到栈段的线性基地址
 6         mov edx,0xffffffff              
 7         sub edx,ebx
 8         mov ebx,edx                     ;得到段界限
 9         
10         mov ecx,0x00409600
11         call Sys_Routine_Segement:Make_Descriptor
12         call Sys_Routine_Segement:Set_New_GDT
13         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            ;栈段长度#0x0c
10                                                 
11                                                   
12         prgentry         dd start                ;程序入口#0x10 
13         code_seg         dd section.code.start   ;代码段位置#0x14
14         code_len         dd code_end             ;代码段长度#0x18
15 
16         data_seg         dd section.data.start   ;数据段位置#0x1c
17         data_len         dd data_end             ;数据段长度#0x20
18 ;-------------------------------------------------------------------------------
19         ;符号地址检索表
20         salt_items       dd (header_end-salt)/256 ;#0x24
21          
22         salt:                                     ;#0x28
23         Printf:           db  '@Printf'
24                      times 256-($-Printf) db 0
25                      
26         TerminateProgram:db  '@TerminateProgram'
27                      times 256-($-TerminateProgram) db 0
28                      
29         ReadHarddisk:    db  '@ReadHarddisk'
30                      times 256-($-ReadHarddisk) db 0
31                  
32 header_end:
33 ;===============================================================================
34 SECTION data align=16 vstart=0    
35                          
36         buffer times 1024 db  0         ;缓冲区
37 
38         message_1         db  0x0d,0x0a,0x0d,0x0a
39                           db  '**********User program is runing**********'
40                           db  0x0d,0x0a,0
41         message_2         db  '  Disk data:',0x0d,0x0a,0
42 
43 data_end:
44 
45 ;===============================================================================
46       [bits 32]
47 ;===============================================================================
48 SECTION code align=16 vstart=0
49 start:
50         User_Data_File     equ 100            ;数据文件存放地点
51         mov eax,ds
52         mov fs,eax
53      
54         mov eax,[stack_seg]
55         mov ss,eax
56         mov esp,0
57      
58         mov eax,[data_seg]
59         mov ds,eax
60      
61         mov ebx,message_1
62         call far [fs:Printf]
63      
64         mov esi,User_Data_File              
65         mov ebx,buffer                      ;缓冲区偏移地址
66         call far [fs:ReadHarddisk]          ;相当于调用函数
67      
68         mov ebx,message_2
69         call far [fs:Printf]
70      
71         mov ebx,buffer 
72         call far [fs:Printf]           
73      
74         jmp far [fs:TerminateProgram]       ;将控制权返回到系统 
75       
76 code_end:
77 ;===============================================================================
78 SECTION stack align=16 vstart=0
79         times 4096 db 0
80         stack_end:
81 ;===============================================================================
82 SECTION trail
83 ;-------------------------------------------------------------------------------
84 program_end:

 

 

 

posted @ 2016-02-29 10:41  PhiliAI  阅读(1254)  评论(0编辑  收藏  举报