《自己动手写操作系统》第三章pmtest2源码解析
2014-03-03 20:51
405 查看
摘要:本节,通过代码解析,帮你解决如下问题:保护模式和实模式下面的偏移量有什么不同?保护膜是和实模式下段基地址是一个意思吗?长跳转指令jmp 0:entry为什么能够在一个代码段中更改另一个代码段的代码?
一、总体思路剖析:
pmtest2,其实实现的是从实模式到保护模式,然后从保护模式回到实模式,最后回归到dos。其中,一开始就进入了实模式,然后在实模式下初始化段描述符,处理GDT等,进入保护模式,在保护模式下完成了一些显示字符串和拷贝读取字符串的功能;最后通过一个normal段,回到实模式,然后通过中断,回到dos程序中。这个过程具体代码如下:
; ==========================================; pmtest2.asm; 编译方法:nasm pmtest2.asm -o pmtest2.com; ==========================================%include "pm.inc" ; 常量, 宏, 以及一些说明org 0100h jmp LABEL_BEGIN[SECTION .gdt]; GDT; 段基址, 段界限 , 属性LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代码段, 32LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代码段, 16LABEL_DESC_DATA: Descriptor 0, DataLen - 1, DA_DRW ; DataLABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA + DA_32 ; Stack, 32 位LABEL_DESC_TEST: Descriptor 0500000h, 0ffffh, DA_DRWLABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址; GDT 结束GdtLen equ $ - LABEL_GDT ; GDT长度GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址; GDT 选择子SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDTSelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDTSelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDTSelectorData equ LABEL_DESC_DATA - LABEL_GDTSelectorStack equ LABEL_DESC_STACK - LABEL_GDTSelectorTest equ LABEL_DESC_TEST - LABEL_GDTSelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT; END of [SECTION .gdt][SECTION .data1] ; 数据段ALIGN 32[BITS 32]LABEL_DATA:SPValueInRealMode dw 0; 字符串PMMessage: db "In Protect Mode now. ^-^", 0 ; 进入保护模式后显示此字符串OffsetPMMessage equ PMMessage - $$StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0OffsetStrTest equ StrTest - $$DataLen equ $ - LABEL_DATA; END of [SECTION .data1]; 全局堆栈段[SECTION .gs]ALIGN 32[BITS 32]LABEL_STACK: times 512 db 0TopOfStack equ $ - LABEL_STACK - 1; END of [SECTION .gs][SECTION .s16][BITS 16]LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h mov [LABEL_GO_BACK_TO_REAL+3], ax mov [SPValueInRealMode], sp ; 初始化 16 位代码段描述符 mov ax, cs movzx eax, ax shl eax, 4 add eax, LABEL_SEG_CODE16 mov word [LABEL_DESC_CODE16 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE16 + 4], al mov byte [LABEL_DESC_CODE16 + 7], ah ; 初始化 32 位代码段描述符 xor eax, eax mov ax, cs shl eax, 4 add eax, LABEL_SEG_CODE32 mov word [LABEL_DESC_CODE32 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE32 + 4], al mov byte [LABEL_DESC_CODE32 + 7], ah ; 初始化数据段描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_DATA mov word [LABEL_DESC_DATA + 2], ax shr eax, 16 mov byte [LABEL_DESC_DATA + 4], al mov byte [LABEL_DESC_DATA + 7], ah ; 初始化堆栈段描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_STACK mov word [LABEL_DESC_STACK + 2], ax shr eax, 16 mov byte [LABEL_DESC_STACK + 4], al mov byte [LABEL_DESC_STACK + 7], ah ; 为加载 GDTR 作准备 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_GDT ; eax <- gdt 基地址 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址 ; 加载 GDTR lgdt [GdtPtr] ; 关中断 cli ; 打开地址线A20 in al, 92h or al, 00000010b out 92h, al ; 准备切换到保护模式 mov eax, cr0 or eax, 1 mov cr0, eax ; 真正进入保护模式 jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里 mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, [SPValueInRealMode] in al, 92h ; ┓ and al, 11111101b ; ┣ 关闭 A20 地址线 out 92h, al ; ┛ sti ; 开中断 mov ax, 4c00h ; ┓ int 21h ; ┛回到 DOS; END of [SECTION .s16][SECTION .s32]; 32 位代码段. 由实模式跳入.[BITS 32]LABEL_SEG_CODE32: mov ax, SelectorData mov ds, ax ; 数据段选择子 mov ax, SelectorTest mov es, ax ; 测试段选择子 mov ax, SelectorVideo mov gs, ax ; 视频段选择子 mov ax, SelectorStack mov ss, ax ; 堆栈段选择子 mov esp, TopOfStack ; 下面显示一个字符串 mov ah, 0Ch ; 0000: 黑底 1100: 红字 xor esi, esi xor edi, edi mov esi, OffsetPMMessage ; 源数据偏移 mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。 cld.1: lodsb test al, al jz .2 mov [gs:edi], ax add edi, 2 jmp .1.2: ; 显示完毕 xchg bx,bx call DispReturn call TestRead call TestWrite call TestRead ; 到此停止 jmp SelectorCode16:0; ------------------------------------------------------------------------TestRead: xor esi, esi mov ecx, 8.loop: mov al, [es:esi] call DispAL inc esi loop .loop call DispReturn ret; TestRead 结束-----------------------------------------------------------; ------------------------------------------------------------------------TestWrite: push esi push edi xor esi, esi xor edi, edi mov esi, OffsetStrTest ; 源数据偏移 cld.1: lodsb test al, al jz .2 mov [es:edi], al inc edi jmp .1.2: pop edi pop esi ret; TestWrite 结束----------------------------------------------------------; ------------------------------------------------------------------------; 显示 AL 中的数字; 默认地:; 数字已经存在 AL 中; edi 始终指向要显示的下一个字符的位置; 被改变的寄存器:; ax, edi; ------------------------------------------------------------------------DispAL: push ecx push edx mov ah, 0Ch ; 0000: 黑底 1100: 红字 mov dl, al shr al, 4 mov ecx, 2.begin: and al, 01111b cmp al, 9 ja .1 add al, '0' jmp .2.1: sub al, 0Ah add al, 'A'.2: mov [gs:edi], ax add edi, 2 mov al, dl loop .begin add edi, 2 pop edx pop ecx ret; DispAL 结束-------------------------------------------------------------; ------------------------------------------------------------------------DispReturn: push eax push ebx mov eax, edi mov bl, 160 div bl and eax, 0FFh inc eax mov bl, 160 mul bl mov edi, eax pop ebx pop eax ret; DispReturn 结束---------------------------------------------------------SegCode32Len equ $ - LABEL_SEG_CODE32; END of [SECTION .s32]; 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, eaxLABEL_GO_BACK_TO_REAL: jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值Code16Len equ $ - LABEL_SEG_CODE16; END of [SECTION .s16code]
二、代码解惑
1.align 32是什么意思?为什么要写align 32?答:align是一个让数据对齐的宏。通常align的对象是1、4、8等。这里的align 32是没有意义的,本来就是只有32b的地址总线宽度,怎么还32对齐?不可能。2.既然我们说PMMessage是表示段内offset,那么我们为什么还要定义一个变量OffsetPMMessage呢?$、$$、offsetpmmessage、pmmessage的地址分别是什么?$:当前行被汇编之后的地址,是实际的线性地址$$:一个section的开始地方被汇编以后的地址,也是实际的线性地址pmmessage:偏移地址(相对段的首地址)既然offset是偏移地址,为什么不直接用offset呢?要解答这些疑惑,我们首先去寻找段基址:你有没有注意到一个问题,在“初始化段描述符”的部分,我们用来初始化段基地址首地址的寄存器,都是cs和ds(实际上二者的数值相等);从某种角度上说,他们处在“同一个段”中。不过分属于不同的offset对应的部分,我们通过段描述符,实现了不同区域的不同权限管理。因为现在程序很小,我们可以在20b的offset之内包括所有的代码和数据,所以这样做是没有任何问题的。我们再看看保护模式下发生的变化:本来在实模式下,它们属于同一个段,但是最后段基地显然在初始化段描述符的时候悄然发生了变化(都采用了base*16+offset,而offset是不同的)。好了,再来看看“pmmessage-$$”的真实含义,pmmessage和$$都表示实模式下相对与段基地址的offset;但后来随着段基地址的漂移,$$变成了首地址,所以pmmessage对应于保护模式下的偏移自然也就发生了变化,需要减去$$对应的地址才行。大部分时候,我们在编程的时候,只需要关注offset就可以了。3.section和段之间有什么区别和联系?程序代码段执行的第一句,mov ax,cs对应的offset会是0吗?section和段之间没有必然的联系,一般我们习惯将一个section放在一个段里面,不过这是用户习惯,不是语法要求——我们可以把两个section放在段里面。mov ax,cs在这里,实模式下offset一般不等于0,保护模式下,这一句的偏移一般是0.4.这里,我们需要根据打印字符串部分总结一下字符串操作的原理、技巧?汇编中判断和循环语句都有哪些?参见汇编语言语法简要总结。5.换行的原理是什么?实际上就是靠操作edi来实现,edi=[edi/160]+16.保护模式下长跳转指令的原理?jmp 0:xx是如何跳转的。从指令结构上来讲,书上的解释已经很明确了——但是jmp 0:xx中的0是如何被改变的呢?因为jmp指令的机器码在编译的时候已经产生了,但是运行之后,它的机器码被改掉了。这样看起来解释得通,但是再仔细想想,是不是有什么地方不对劲?——代码段的内容怎么会可以更改呢?仔细看看代码,又发现了猫腻——原来对代码的改动发生在实模式,这时分段机制还没对代码起到保护作用。7.PMMessage后面定义的dd,但是内容不止4b,该如何处理?dd还是db,表示的是后面的一个单位,而不是所有的内容。例如dd 3,2,4,6,5:这样定义的就是20个字节。8.movzx指令mov eax,bx是非法的,所以要显示的高位补09.带有.的loop和一般的label有什么不同 这是本地label的意思:NASM对于那些以一个句点开始的符号会作特殊处理,一个以单个句点开始的 Label会被处理成本地label, 这意味着它会跟前面一个非本地label相关联.比如: label1 ; some code .loop ; some more code jne .loop ret label2 ; some code .loop ; some more code jne .loop ret 上面的代码片断中,每一个'JNE'指令跳至离它较近的前面的一行上,因为'.loop'的两个定义通过与它们前面的非本地Label相关联而被分离开来了。 10.32b数据段和16b数据段有什么区别?32b的堆栈段呢对于stack,位数不同将导致压栈和出战的位数不同;对于数据段,没有什么区别;对于代码段,将决定是ecs还是cs等信息;所以,在32b的物理机器上,你可以都定义成16b的段;但是你不能在16b的机器上,定义32b的段11.保护模式和实模式段地址有什么区别?保护模式下32b,不用进行偏移就直接和offset相加;实模式下16b,需要左移四位然后在+offset。三、调试情况:
打印read和write位置和内容都出现异常,无法回到dos程序之下。
解决方法:调整test段的地址,使得它的地址范围降低,然后像其他段描述符一样进行初始化。
遗留问题:没有解决test段在原文情况下不可写的情况,最后证明,这是一个随机会出现问题的地方。
相关文章推荐
- 使用drawBitmapMesh扭曲图片
- 《产品经理那些事儿》
- 【JBPM4】流程分支fork - join
- 【JBPM4】判断节点decision 方法3 handler
- 【JBPM4】判断节点decision 方法2 condition
- 【JBPM4】判断节点decision 方法1
- 【JBPM4】任务form表单
- 【JBPM4】任务节点-任务分配candidate-groups
- 【JBPM4】任务节点-任务分配swimlane
- 【JBPM4】任务节点-任务分配assignment-Handler
- 【JBPM4】任务节点-任务分配assignee
- 【JBPM4】State 节点
- 【JBPM4】流程任务变量存取
- 【JBPM4】流程实例变量存取
- 《自己动手写操作系统》第三章 pmtest1——从实模式到保护模式
- 未安装RPM包内文件树及脚本获取
- 改变npm安装路径
- 利用JBPM4.4的AssignmentHandler实现用户角色整合另一种构思
- jbpm4.4表结构
- JBPM (六) compare decision with state