保护模式进阶
之前讲述了进入保护模式的步骤!
复习一下:
- 定义GDT
- 用lgdt 加载 gdtr
- 打开A20 地址线
- 设置CR0 寄存器PE位为1
- 执行跳转,进入保护模式
在保护模式下,我们能访问4G 的内存!
所以试验一下读写大地址内存! 思路: 新建一个段,以5mb为基址,这个远超出1MB 的界限了。 先读出开始处8字节的内容,然后写入一个字符串,再从中读出8个字节。 这个读出来的字节就跟写入的字符串一样! 字符串保存在数据段中,数据段属于新加的,因为上一章只定义了三个描述符,一个空描述符,一个代码段描述符,一个显存描述符。
书中也提供了代码 chapter3/b/pmtest2.asm
代码清单:
1 ; ==========================================
2 ; pmtest2.asm
3 ; 编译方法:nasm pmtest2.asm -o pmtest2.com
4 ; ==========================================
5
6 %include "pm.inc" ; 常量, 宏, 以及一些说明
7
8 org 0100h
9 jmp LABEL_BEGIN
10
11 [SECTION .gdt]
12 ; GDT
13 ; 段基址, 段界限 , 属性
14 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
15 LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
16 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32; 非一致代码段, 32
17 LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代码段, 16
18 LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW ; Data
19 LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32; Stack, 32 位
20 LABEL_DESC_TEST: Descriptor 0500000h, 0ffffh, DA_DRW
21 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址
22 ; GDT 结束
23
24 GdtLen equ $ - LABEL_GDT ; GDT长度
25 GdtPtr dw GdtLen - 1 ; GDT界限
26 dd 0 ; GDT基地址
27
28 ; GDT 选择子
29 SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
30 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
31 SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT
32 SelectorData equ LABEL_DESC_DATA - LABEL_GDT
33 SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
34 SelectorTest equ LABEL_DESC_TEST - LABEL_GDT
35 SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
36 ; END of [SECTION .gdt]
37
38 [SECTION .data1] ; 数据段
39 ALIGN 32
40 [BITS 32]
41 LABEL_DATA:
42 SPValueInRealMode dw 0
43 ; 字符串
44 PMMessage: db "In Protect Mode now. ^-^", 0 ; 在保护模式中显示
45 OffsetPMMessage equ PMMessage - $$
46 StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
47 OffsetStrTest equ StrTest - $$
48 DataLen equ $ - LABEL_DATA
49 ; END of [SECTION .data1]
50
51
52 ; 全局堆栈段
53 [SECTION .gs]
54 ALIGN 32
55 [BITS 32]
56 LABEL_STACK:
57 times 512 db 0
58
59 TopOfStack equ $ - LABEL_STACK - 1
60
61 ; END of [SECTION .gs]
62
63
64 [SECTION .s16]
65 [BITS 16]
66 LABEL_BEGIN:
67 mov ax, cs
68 mov ds, ax
69 mov es, ax
70 mov ss, ax
71 mov sp, 0100h
72
73 mov [LABEL_GO_BACK_TO_REAL+3], ax
74 mov [SPValueInRealMode], sp
75
76 ; 初始化 16 位代码段描述符
77 mov ax, cs
78 movzx eax, ax
79 shl eax, 4
80 add eax, LABEL_SEG_CODE16
81 mov word [LABEL_DESC_CODE16 + 2], ax
82 shr eax, 16
83 mov byte [LABEL_DESC_CODE16 + 4], al
84 mov byte [LABEL_DESC_CODE16 + 7], ah
85
86 ; 初始化 32 位代码段描述符
87 xor eax, eax
88 mov ax, cs
89 shl eax, 4
90 add eax, LABEL_SEG_CODE32
91 mov word [LABEL_DESC_CODE32 + 2], ax
92 shr eax, 16
93 mov byte [LABEL_DESC_CODE32 + 4], al
94 mov byte [LABEL_DESC_CODE32 + 7], ah
95
96 ; 初始化数据段描述符
97 xor eax, eax
98 mov ax, ds
99 shl eax, 4
100 add eax, LABEL_DATA
101 mov word [LABEL_DESC_DATA + 2], ax
102 shr eax, 16
103 mov byte [LABEL_DESC_DATA + 4], al
104 mov byte [LABEL_DESC_DATA + 7], ah
105
106 ; 初始化堆栈段描述符
107 xor eax, eax
108 mov ax, ds
109 shl eax, 4
110 add eax, LABEL_STACK
111 mov word [LABEL_DESC_STACK + 2], ax
112 shr eax, 16
113 mov byte [LABEL_DESC_STACK + 4], al
114 mov byte [LABEL_DESC_STACK + 7], ah
115
116 ; 为加载 GDTR 作准备
117 xor eax, eax
118 mov ax, ds
119 shl eax, 4
120 add eax, LABEL_GDT ; eax <- gdt 基地址
121 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
122
123 ; 加载 GDTR
124 lgdt [GdtPtr]
125
126 ; 关中断
127 cli
128
129 ; 打开地址线A20
130 in al, 92h
131 or al, 00000010b
132 out 92h, al
133
134 ; 准备切换到保护模式
135 mov eax, cr0
136 or eax, 1
137 mov cr0, eax
138
139 ; 真正进入保护模式
140 jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处
141
142 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
143
144 LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
145 mov ax, cs
146 mov ds, ax
147 mov es, ax
148 mov ss, ax
149
150 mov sp, [SPValueInRealMode]
151
152 in al, 92h ; `.
153 and al, 11111101b ; | 关闭 A20 地址线
154 out 92h, al ; /
155
156 sti ; 开中断
157
158 mov ax, 4c00h ; `.
159 int 21h ; / 回到 DOS
160 ; END of [SECTION .s16]
161
162
163 [SECTION .s32]; 32 位代码段. 由实模式跳入.
164 [BITS 32]
165
166 LABEL_SEG_CODE32:
167 mov ax, SelectorData
168 mov ds, ax ; 数据段选择子
169 mov ax, SelectorTest
170 mov es, ax ; 测试段选择子
171 mov ax, SelectorVideo
172 mov gs, ax ; 视频段选择子
173
174 mov ax, SelectorStack
175 mov ss, ax ; 堆栈段选择子
176
177 mov esp, TopOfStack
178
179
180 ; 下面显示一个字符串
181 mov ah, 0Ch ; 0000: 黑底 1100: 红字
182 xor esi, esi
183 xor edi, edi
184 mov esi, OffsetPMMessage ; 源数据偏移
185 mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。
186 cld
187 .1:
188 lodsb
189 test al, al
190 jz .2
191 mov [gs:edi], ax
192 add edi, 2
193 jmp .1
194 .2: ; 显示完毕
195
196 call DispReturn
197
198 call TestRead
199 call TestWrite
200 call TestRead
201
202 ; 到此停止
203 jmp SelectorCode16:0
204
205 ; ------------------------------------------------------------------------
206 TestRead:
207 xor esi, esi
208 mov ecx, 8
209 .loop:
210 mov al, [es:esi]
211 call DispAL
212 inc esi
213 loop .loop
214
215 call DispReturn
216
217 ret
218 ; TestRead 结束-----------------------------------------------------------
219
220
221 ; ------------------------------------------------------------------------
222 TestWrite:
223 push esi
224 push edi
225 xor esi, esi
226 xor edi, edi
227 mov esi, OffsetStrTest ; 源数据偏移
228 cld
229 .1:
230 lodsb
231 test al, al
232 jz .2
233 mov [es:edi], al
234 inc edi
235 jmp .1
236 .2:
237
238 pop edi
239 pop esi
240
241 ret
242 ; TestWrite 结束----------------------------------------------------------
243
244
245 ; ------------------------------------------------------------------------
246 ; 显示 AL 中的数字
247 ; 默认地:
248 ; 数字已经存在 AL 中
249 ; edi 始终指向要显示的下一个字符的位置
250 ; 被改变的寄存器:
251 ; ax, edi
252 ; ------------------------------------------------------------------------
253 DispAL:
254 push ecx
255 push edx
256
257 mov ah, 0Ch ; 0000: 黑底 1100: 红字
258 mov dl, al
259 shr al, 4
260 mov ecx, 2
261 .begin:
262 and al, 01111b
263 cmp al, 9
264 ja .1
265 add al, '0'
266 jmp .2
267 .1:
268 sub al, 0Ah
269 add al, 'A'
270 .2:
271 mov [gs:edi], ax
272 add edi, 2
273
274 mov al, dl
275 loop .begin
276 add edi, 2
277
278 pop edx
279 pop ecx
280
281 ret
282 ; DispAL 结束-------------------------------------------------------------
283
284
285 ; ------------------------------------------------------------------------
286 DispReturn:
287 push eax
288 push ebx
289 mov eax, edi
290 mov bl, 160
291 div bl
292 and eax, 0FFh
293 inc eax
294 mov bl, 160
295 mul bl
296 mov edi, eax
297 pop ebx
298 pop eax
299
300 ret
301 ; DispReturn 结束---------------------------------------------------------
302
303 SegCode32Len equ $ - LABEL_SEG_CODE32
304 ; END of [SECTION .s32]
305
306
307 ; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
308 [SECTION .s16code]
309 ALIGN 32
310 [BITS 16]
311 LABEL_SEG_CODE16:
312 ; 跳回实模式:
313 mov ax, SelectorNormal
314 mov ds, ax
315 mov es, ax
316 mov fs, ax
317 mov gs, ax
318 mov ss, ax
319
320 mov eax, cr0
321 and al, 11111110b
322 mov cr0, eax
323
324 LABEL_GO_BACK_TO_REAL:
325 jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
326
327 Code16Len equ $ - LABEL_SEG_CODE16
328
329 ; END of [SECTION .s16code]
330
对比之前的代码,如图所示:
新增的描述符:
- 第15行
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
- 第17-20行:
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代码段, 16
LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW ; Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32; Stack, 32 位
LABEL_DESC_TEST: Descriptor 0500000h, 0ffffh, DA_DRW
我们这次增加的描述符比较多,第一个是normal 描述符,第二个是16位非一致性代码描述符 ,第三个是数据段描述符,第四个是堆栈描述符,第五个是测试描述符!
总共新增加了5个描述符,描述符的分析就不详细说明了,上一章节已经有详细的说明了!
增加的描述符自然有相应的选择子和代码!
首先先看看在32位段,我们做了什么!
代码第163行处
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
PS再次复习一下寄存器的用法:
AH&AL=AX:累加寄存器,常用于运算; BH&BL=BX:基址寄存器,常用于地址索引; CH&CL=CX:计数寄存器,常用于计数; DH&DL=DX:数据寄存器,常用于数据传递。
CS(Code Segment):代码段寄存器; DS(Data Segment):数据段寄存器; SS(Stack Segment):堆栈段寄存器; ES(Extra Segment):附加段寄存器。
DS是段寄存器 一般放的是数据段的段地址至于BX 是一个灵活的寄存器 可以用它来做许多事情 当然也可以用来当指针 楼主所谓数据段的基地址是这个段的起始地址 要说他是基地址也没有错 但是bx里放的是某个字节或字的地址用[bx]来访问。DS放段地址,BX是通用寄存器.
ds与bx配合,es与dx配合,cx作为计数器。这既是cpu硬件设计使然,也是软件设计的标准用法,就如围棋中的定式,你必须这么用。bx是提供偏移地址。
- IP(Instruction Pointer):指令指针寄存器,与CS配合使用,可跟踪程序的执行过程;
- SP(Stack Pointer):堆栈指针,与SS配合使用,可指向目前的堆栈位置。
- BP(Base Pointer):基址指针寄存器,可用作SS的一个相对基址位置;
- SI(Source Index):源变址寄存器可用来存放相对于DS段之源变址指针;
- DI(Destination Index):目的变址寄存器,可用来存放相对于 ES 段之目的变址指针。
从代码看出ds指向数据段 SelectorData,es指向了SelectorTest (也即是5MB 内存的段),gs指向了SelectorVideo 也就是显存,ss指向了新增的堆栈SelectorStack。
- 第181行显示一个字符串,
mov ah,och
0000 是黑底,1100 是红字! - 第182-183行 ` xor esi, esi xor edi, edi ` esi 和 edi清零!
- 第184-185行
mov esi, OffsetPMMessage ; 源数据偏移 mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。
把 OffsetPMMessage 的地址赋值给esi,(80 * 10 + 0) * 2 偏移量赋值给edi!
- 第186行 cld 指令将 DF 标志清零,以指示传送是正方向的。
- 第187行 .1 这个是标记而已
- 第188行 loadsb:用于目的地址的内容读到源地址。 即目标地址为:ES:DI,源地址为DS:SI
汇编语言中,串操作指令LODSB/LODSW是块装入指令,其具体操作是把SI指向的存储单元读入累加器,LODSB就读入AL,LODSW就读入AX中,然后SI自动增加或减小1或2.其常常是对数组或字符串中的元素逐个进行处理。
还记得X86汇编的写法吗,就是直接设置cx 计数器,然后使用rep movsw 来实现的!
- 第189行
test al, al
汇编test 指令执行后, OF=CF=0;对 ZF、 SF 和 PF 的影响视测试结果而定;对AF 的影响未定义。 al为00 进行and运算的时候ZF变成1 00000000 and 00000000 结果为0,所以ZF变成1 al为01 进行and运算的时候ZF变成0 00000001 and 00000001 结果为1,所以ZF变成0
所以主要影响ZF 标志位!ZF(Zero Flag),即零标志!
所以第190行 jz .2 Jump if Zero 就是要看ZF标志位! jz 的意思是 ZF 标志为 1 则转移; jnz 的意思是 ZF 标志不为 1(为 0)则转移。
所以遇到字符串为0,就认为拷贝完毕,直接跳到.2 程序代码段!
- 第191 行
mov [gs:edi], ax
,若al不为0 则将ax的值(已经有源地址的数据)赋值给显存地址! 注意因为ah已经存储了字符属性! - 第192 行 ` add edi, 2` 地址偏移2个字节!
- 第193行 ` jmp .1 ` 跳回.1 继续执行! jmp无条件跳转指令
- 第196-203行 如下:
call DispReturn call TestRead call TestWrite call TestRead ; 到此停止 jmp SelectorCode16:0
首先说明 DispReturn 代码段!
; ------------------------------------------------------------------------
DispReturn:
push eax
push ebx
mov eax, edi
mov bl, 160
div bl ; eax/bl 执行后al=当前行号 al = ax / 160, ah = ax mod 160
and eax, 0FFh ; 只保留行号,列号清0 即 eax = al
inc eax ; eax+=1,使eax为当前行的下一行
mov bl, 160
mul bl ; eax*bl,eax为当前行的下一行的开始
mov edi, eax ; 使edi指向当前行的下一行的开始
pop ebx
pop eax
ret
; DispReturn 结束---------------------------------------------------------
DispReturn模拟一个回车的显示,(让下一个要显示的字符在下一行的开头处显示),其中edi始终指向要显示的下一个字符的位置。
注1: 8025彩色字模式的显示显存在内存中的地址为B8000h~BFFFH,共32k.向这个地址写入的内容立即显示在屏幕上边.在8025彩色字模式 下共可以显示25行,每行80字符,每个字符在显存中占两个字节,第一个字节是字符的ASCII码.第二字节是字符的属性,(80字符占160个字节)。 这段程序的功能是: 计算 edi = ( edi / 160(保留最后8位)+ 1) * 160! 就是回车功能!
书中提到一个细节就是
在程序的整个执行过程中,edi始终指向要显示的下一个字符的位。所以,如果程序中除显示字符外还用刀edi,一定要事先保存好edi的值!!!免得显示混乱~
- 下一步是调用TestRead
; ------------------------------------------------------------------------
TestRead:
xor esi, esi
mov ecx, 8
.loop:
mov al, [es:esi] ; 从新增的5MB开始地址处开始
call DispAL
inc esi
loop .loop
call DispReturn
ret
; TestRead 结束-----------------------------------------------------------
这段代码使用cx作为计数器,使用loop循环! 因为我们的目的是读取8个字节,所以ecs设置为8,es已经指向新增的5Mb的LABEL_DESC_TEST ! 下面要调用 DispAL! 注意edi的值没有动!
DispAL 代码如下:
; ------------------------------------------------------------------------
; 显示 AL 中的数字 (以16进制显示出来)
; 默认地:
; 数字已经存在 AL 中
; edi 始终指向要显示的下一个字符的位置
; 被改变的寄存器:
; ax, edi
; ------------------------------------------------------------------------
DispAL:
push ecx
push edx
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov dl, al ; 用dl临时存储al的值
shr al, 4 ; 右移4位,相当于al / 16,目的是取高4位计算
mov ecx, 2 ; 计数器设置2
.begin:
and al, 01111b ; 将AL 的高4位清零
cmp al, 9 ; al和9(1001)比较
ja .1 ; 若al大于9,就跳转到.1
add al, '0' ; '0' 0x30 转换成ASCII码
jmp .2 ; 无条件跳转
.1:
sub al, 0Ah ; al = al- 0x0A
add al, 'A' ; al = al + 'A'
.2:
mov [gs:edi], ax ;ax此时已经存储了转换后的ASCII码
add edi, 2 ; 让edi指向下一个位置
mov al, dl ; dl把值赋值给al
loop .begin ; 从begin开始,此时al计算切显示低4位
add edi, 2
pop edx
pop ecx
ret
; DispAL 结束-------------------------------------------------------------
这段代码就不详细分析了! 就复习一下上面提到的指令
逻辑右移指令 shr
or可以将指定的位设置成1且 该指令会置CF=OF=0,其结果影响SF、ZF、PF ,and可以将指定的位设置成0 且 该指令会置CF=OF=0,其结果影响SF、ZF、PF。
cmp 指令在功能上和 sub 指令相同,唯一不同之处在于, cmp 指令仅仅根据计算的结果设置相 应的标志位,而不保留计算结果,因此也就不会改变两个操作数的原有内容。 cmp 指令将会影响到 CF、 OF、 SF、 ZF、 AF 和 PF 标志位。
拿指令 cmp ax,bx 来说,我们关心的是 AX 中的内容是否等于 BX 中的内容, AX 中的内容是否大于 BX 中的内容, AX 中的内容是否小于 BX 中的内容,等等, AX 是被测量的对象, BX 是测量的基准。
ja 若al大于9,就跳转到.1
DispReturn 代码前面已经讲得很详细了!
下一个执行代码出就是
call TestWrite
再次附上代码:
; ------------------------------------------------------------------------
TestWrite:
push esi
push edi ; 保存现场
xor esi, esi
xor edi, edi ; SI DI 都清零
mov esi, OffsetStrTest ; 源数据偏移
cld ; cld 指令将 DF 标志清零,以指示传送是正方向的。
.1:
lodsb
test al, al
jz .2
mov [es:edi], al
inc edi
jmp .1
.2:
pop edi
pop esi
ret
; TestWrite 结束----------------------------------------------------------
要写的数据的地址是OffsetStrTest 在第46-47行!
StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest equ StrTest - $$
另外,保护模式下用到堆栈了,push 和 pop 都是堆栈指令的!这个是因为我们建立了堆栈,在前面提到的新增的描述符中!
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32; Stack, 32 位
代码在第52-61行
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
; END of [SECTION .gs]
下一步就是跳转到实模式!
jmp SelectorCode16:0
这个会跳到 [SECTION .s16code] 代码段!
书上说的,我打字写一下:
我们从实模式进入保护模式时直接用一个跳转就可以了,但是返回的时候稍微复杂一些。 因为在准备结束保护回到实模式之前,需要加载一个合适的描述符选择子到有关的段寄存器,以使对应段描述符高速缓冲寄存器中含有合适的段界限和属性。
而且我们不能从32位代码段返回实模式,只能从16位代码段中返回。 这是因为无法实现从32位代码段返回时cs高速缓冲寄存器的中的属性符合实模式的要求(实模式不能改变段属性)。
这就是为何前面我说的新增了normal 描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
返回实模式之前把对应的选择子selectorNormal 加载到ds es ss!
; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
; 跳回实模式:
mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, 11111110b
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
代码说明: SelectorNormal 赋值给ds es fs gs ss! 然后清除cr0 的PE 位! 然后跳转,注意这个跳转语句后面有注释! 书上说是 程序刚开始的16位代码段已经设置正确了!
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
mov [LABEL_GO_BACK_TO_REAL+3], ax
mov [LABEL_GO_BACK_TO_REAL+3], ax
从代码可以看出ax 的值就是16位代码开始处(实模式)的段地址!
所以之前的 jmp 0:LABEL_REAL_ENTRY ;
其实就变成
jmp cs_real_mode:LABEL_REAL_ENTRY
跳转到LABEL_REAL_ENTRY
代码如下:
重新设置各个寄存器ds es ss,恢复了SP, 关闭A20 地址线,打开终端!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [SPValueInRealMode] ; 恢复sp的值
in al, 92h ; `.
and al, 11111101b ; | 关闭 A20 地址线
out 92h, al ; /
sti ; 开中断
mov ax, 4c00h ; `.
int 21h ; / 回到 DOS
; END of [SECTION .s16]
后面从实模式跳转到保护模式就不讲了!!
编译nasm pmtest2.asm -o pmtest2.com
启动bochs 后,自动启动freedos 输入b:\pmtest2.com 显示一堆字符串(16进制格式的) 显示完毕自动返回到freedos界面了!
如图所示: