⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 virtual.asm

📁 windows汇编语言程序设计 吴中平著 一书所附所有源代码。 该书重点讲解了Windows环境下汇编语言程序设计的基本原理及其实现过程
💻 ASM
📖 第 1 页 / 共 2 页
字号:
TITLE      保护方式下的程序设计实例
; 本实例请不要在UCDOS下运行,Windows下也不可以运行,因为修改了系统地址寄存器。
; 本例综合运用保护方式下程序设计的基本知识,主要包括:
; 1)保护方式的进入和退出。
; 2)分页管理机制。
; 3)中断表。
; 4)通用保护异常。
; 5)特权级变化。
; 6)任务切换。
; 7)扩展内存的存取。
; 8)本实例要求您的机器至少要配置16MB内存。
; 9)阅读本例,重点是从保护方式的入口处开始:①→②→③→④→⑤→⑥。
PDT_ADDR   equ 00300000H; 页目录表物理页地址。
PT_0_ADDR  equ 00301000H; 页表0的物理页地址。
PTC_CODE   equ 003FF000H; 代码所在区域的表项,对应页表第3FF页(倒数第一项)。
PTC_TEST   equ 003FE000H; 供测试用表项,对应页表中第3FE页(倒数第二项)。
PTC_VIDEO  equ 003FA000H; 显示缓冲区在页表中的表项,物理地址为0B800H。
PhTestMem   equ 08FFF000H; 测试页表的表项用,PTC_TEST项对应该地址。
PhExeMem   equ 00FFF000H; 分页寻址,代码物理页(第16MB处,代码复制而来)。
include dos.inc
.386p
GDTSEG Segment PARA USE16
gdt LABEL BYTE; 全局描述符表。
   Dummy DESCRIPTOR <>; 空选择子。
   Normal DESCRIPTOR <0ffffH,0,0,ATTDW,0>
      NORMAL_SEL=Normal-gdt
   ExtMem DESCRIPTOR <0FFFFh,0eaa0h,010h,ATTDR,0>; FFFF:EAB0处,只读。
      EXTMEM_SEL=ExtMem-gdt
   EchoBuffer DESCRIPTOR <0ffffH,8000H,0BH,ATTDW,0>
      ECHO_BUFFER_SEL=EchoBuffer-gdt
   CopyCodeBuffer DESCRIPTOR <0FFFFH,PhExeMem and 0FFFFH,PhExeMem shr 16,ATTDW,0>
      COPY_SEL=CopyCodeBuffer-gdt; 第16MB的内存处的描述符。
   V_P_Code DESCRIPTOR <0FFFFH,PTC_CODE AND 0FFFFH,PTC_CODE shr 16,ATTCER,0>
      V_P_CODE_SEL=V_P_Code-gdt
   V_P_Video DESCRIPTOR <0FFFFH,PTC_VIDEO AND 0FFFFH,PTC_VIDEO shr 16,ATTDW,0>
      V_P_VIDEO_SEL=V_P_Video-gdt
   V_P_PDTable DESCRIPTOR <0FFFH,PDT_ADDR AND 0FFFFH,PDT_ADDR shr 16,ATTDW,0>
      V_P_PDT_SEL=V_P_PDTable-gdt; 页目录表。
   V_P_PT_0 DESCRIPTOR <0FFFH,PT_0_ADDR AND 0FFFFH,PT_0_ADDR shr 16,ATTDW,0>
      V_P_PT_0_SEL=V_P_PT_0-gdt; 页表0,以上为分页机制准备的。
Gdt_Init_Start LABEL BYTE; 以下为需初始化的GDT表项。
   SwitchCon DESCRIPTOR<0FFFFH,ControlSeg,?,ATTCER,?>; 分支子程序。
      SWITCH_SEL=SwitchCon-gdt
   MainCode DESCRIPTOR <MAINCODELENGTH-1,MainCodeSeg,?,ATTCER,?> 
      MAINCODE_SEL=MainCode-gdt; 主代码。
   MainData DESCRIPTOR <0FFFFH,MainDataSeg,?,ATTDW,?>; 提示信息及缓冲段。
      MAINDATA_SEL=MainData-gdt
   MainLdt DESCRIPTOR <MAINLDTLENGTH-1,MainLDTSeg,0,ATTLDT,?>
      MAINLDT_SEL=MainLdt-gdt
   MainTss DESCRIPTOR <MAINTSSLENGTH-1,MainTSSSeg,?,ATTTSS,?>
      MAINTSS_SEL=MainTss-gdt
   MainTSS_Alias DESCRIPTOR <MAINTSSLENGTH-1,MainTSSSeg,?,ATTDW,?>
      MAINTSS_ALIAS_SEL=MainTss_Alias-gdt; 别名。
   MainStack_0  DESCRIPTOR <MAINSTACK_POINTER_0-1,MainStackSeg_0,?,ATTDW,?>
      MAINSTACK_0_SEL=MainStack_0-gdt
   MainStack_2 DESCRIPTOR <MAINSTACK_POINTER_2-1,MainStackSeg_2,?,ATTDW+DPL2,?>
      MAINSTACK_2_SEL=MainStack_2-gdt+RPL2 
   DosTss DESCRIPTOR <DOSTSSLENGTH-1,DosTSSSeg,?,ATTTSS,?>; 实方式任务。
      DOSTSS_SEL=DosTss-gdt
   DosLdt DESCRIPTOR <DOSLDTLENGTH-1,DosLDTSeg,?,ATTLDT,?>
      DOSLDT_SEL=DosLdt-gdt
   GDT_INIT_NUM=($-Gdt_Init_Start)/(SIZE DESCRIPTOR); 初始化项数。
   GDTLENGTH=$-gdt
GDTSEG EndS
IDTSEG Segment para use16
; IDT只能为中断、陷阱或任务门。
; 主动调用中断,相当于陷阱事件,在堆栈中保存下一条指令的地址。
; 被动发生时相当于故障,在堆栈中保存了发生异常的指令地址,需处理。
; A、B、C、D、E类型的中断被动发生时,在堆栈中压入出错码。
idt LABEL BYTE
   rept 13; 重复宏定义,0~12号向量。
      GATE <Int_Pro,MAINCODE_SEL,0,ATTTGAT,0>; Int_Pro是默认中断程序。
   EndM
      GATE <Int_D,MAINCODE_SEL,0,ATTTGAT,0>; 0DH号向量,通用保护故障。
   rept 19
      GATE <Int_Pro,MAINCODE_SEL,0,ATTTGAT,0>; 14~32号向量。
   EndM
      INT_DOS21 GATE <?,DOSTSS_SEL,0,ATTTASKGAT,0>; 21H号向量。
   rept 220
      GATE <Int_Pro,MAINCODE_SEL,0,ATTTGAT,0>; 34~253号向量。
   EndM
      INT_PAGE  GATE <IntPage,MAINCODE_SEL,0,ATTIGAT,0>; 254号向量。
      GATE <Int_Pro,MAINCODE_SEL,0,ATTTGAT,0>; 255号向量。
   IDTLENGTH=$-idt
IDTSEG Ends
MainTSSSeg Segment para use16
   Main_TSS  taskss   <>
   DB 0ffh; 控制端口I/O结束标志。
   MAINTSSLENGTH=$
MainTSSSeg Ends
MainStackSeg_0 Segment para use16; 0级堆栈。
   MAINSTACK_POINTER_0 =1024
   db  MAINSTACK_POINTER_0 dup(0)
MainStackSeg_0 Ends
MainStackSeg_2 Segment para use16; 2级堆栈。
   MAINSTACK_POINTER_2 =1024
   db  MAINSTACK_POINTER_2 dup(0)
MainStackSeg_2 Ends
MainLDTSeg Segment para use16; Main任务的LDT。
   No_Init LABEL BYTE
   CallGate Gate <ReturnLevel_0_Entry,SWITCH_SEL,1,ATTCGAT+DPL2,0>                               
   LEVEL2GATE_SEL=CallGate-No_Init+TIL+RPL2
MainLdt_Init_Start LABEL BYTE
   GetExMemDe DESCRIPTOR <MAINCODELENGTH-1,MainCodeSeg,0,ATTCER,0>
   VISITEXMEM_SEL=GetExMemDe-No_Init+TIL
   Level2De DESCRIPTOR <LEVEL2LENGTH-1,CodeSeg_2,0,ATTCER+DPL2,0>
   LEVEL2_SEL=Level2De-No_Init+TIL+RPL2
   MAINLDT_INIT_NUM=($-MainLdt_Init_Start)/(SIZE DESCRIPTOR)
   MAINLDTLENGTH=$
MainLDTSeg Ends
MainDataSeg  Segment PARA use16
   ExtMemMess db "1----The Content of Memory at FFFF:EAB0(since 10EAA0 for 16 bytes)is :",0dh,0ah
   EMM_LEN=$-ExtMemMess
   Buffer_ExtMem db 50 dup(0)
   DPLConvert db "2----DPL is Changed(LEVEL0-->LEVEL2-->LEVEL0),Copy 12345678H from LEVEL2 stack!"
   DPL_LEN=$-DPLConvert
   Buffer_DPL db 16 dup(0)
   Int_D_Mess db "3----General Protect happend!" 
   D_LEN =$-Int_D_Mess
   Int_Pro_Mess db "4----Other Interrupt happened!"
   P_LEN=$-Int_Pro_Mess
   PageManage db "5----The Content of Page_0 Item:"
   PAGE_LEN=$-PageManage
   Buffer_Page db 8 dup(?)
MainDataSeg Ends
MainCodeSeg Segment PARA use16
   assume cs:MainCodeSeg
Dis_play proc; 显示信息用。另外,这段代码还需复制到第16MB内存处(分页用)。
   push di
   cld
again:
   lodsb
   mov ah,34h; 颜色值。
   stosw
   loop again
   pop di
   retf
   COPYLEN=$-Dis_Play; 这段代码的长度,即需要复制的字节数。
Dis_play endp
GetExMem proc; 读取扩展内存。
   mov ax,EXTMEM_SEL
   mov ds,ax
   mov si,0
   mov ax,MAINDATA_SEL
   mov es,ax      
   lea di,Buffer_ExtMem
   mov cx,16
   cld
changeasc:
   lodsb
   call  far ptr  ToAscii; 转换成ASCII码。
   stosw
   mov al,' '
   stosb
   loop changeAsc
   mov al,'$'
   stosb
   retf
GetExMem endp
ToAscii proc; AL的内容转化为ASCII码在AX中。
   push ax
   shr al,4
   and al,0fh
   add al,90h
   daa
   adc al,40h
   daa
   mov bl,al
   pop  ax
   and al,0fh
   add al,90h
   daa
   adc al,40h
   daa
   mov bh,al
   mov ax,bx
   retf
ToAscii endp
Int_Pro proc; 默认的中断执行程序。
; 主动中断,下列代码可正常返回。异常发生,请修改,确保EIP跳至下一条指令。
   mov ax,MAINDATA_SEL
   mov ds,ax
   lea si,Int_Pro_Mess
   mov ax,ECHO_BUFFER_SEL
   mov es,ax
   mov di,160*8
   mov cx,P_LEN
   call FAR ptr dis_play
   iretd
Int_Pro Endp
Int_D proc; 向只读数据段写数据,模拟异常发生。为使程序能正确返回,修改了EIP。
   mov ax,MAINDATA_SEL
   mov ds,ax
   Lea si,Int_D_Mess
   mov ax,ECHO_BUFFER_SEL
   mov es,ax
   mov di,160*6
   mov cx,D_LEN
   call  Far ptr dis_play
   pop eax; 针对本例,被动发生,弹出出错码。
   pop eax; 弹出EIP。
   add eax,7;  mov dword ptr ds:[0],0指令7个字节长。
   push eax
   iretd
Int_D Endp
IntPage Proc
   mov ax,MAINCODE_SEL
   mov ds,ax
   lea si,Dis_play
   mov ax,COPY_SEL
   mov es,ax
   xor di,di
   mov ecx,COPYLEN
   rep movsb; 复制Dis_play代码到16MB内存处,即00FFF000H处。
   mov ax,V_P_PDT_SEL; 页目录表选择子。
   mov es,ax
   xor di,di
   mov cx,1024
   xor eax,eax
   rep stosd; 初始化页目录表项,共1K。
   mov dword ptr es:[0],PT_0_ADDR+USU+RWW+PL; 页表0存在等属性。
   mov ax,V_P_PT_0_SEL; 页表0选择子。
   mov es,ax
   xor di,di
   mov cx,1024
   xor eax,eax
   mov  eax,3; 线性地址与物理地址页映射具有相同的空间。
init_item:
   stosd; 初始化页表项,共1K。
   add eax,1000H
   loop init_item
   mov di,(PT_0_ADDR shr 12)*4
   mov dword ptr es:[di],PT_0_ADDR +USU+RWR+PL
   mov di,(PTC_VIDEO shr 12)*4; 第3FAH作特别修改,重新建立映射。
   mov dword ptr es:[di],0B8000H+USu+RWW+PL
   mov di,(PTC_TEST shr 12)*4; 第3FEH作特别修改,重新建立映射。
   mov dword ptr es:[di],PhTestMem+USU+RWW+PL
   mov di,(PTC_CODE shr 12)*4; 第3FFH作特别修改,重新建立映射。
   mov dword ptr es:[di],PhExeMem+USU+RWR+PL
   mov eax,PDT_ADDR
   mov cr3,eax; CR3为页目录表的物理地址。
   mov eax,cr0 
   or eax,80000000H
   mov cr0,eax; 分页机制的启动。
; 显示出页表中部分项的内容。
   mov di,160*10; ×10,换10行。
   mov cx,6; 6个表项。
   mov ax,V_P_PT_0_SEL
   mov ds,ax
   mov bx,(PTC_VIDEO shr 12)*4
nextitem:
   push ds
   push bx
   push cx
   push di
   mov edx,dword ptr ds:[bx]; 虽然没有明确的页映射关系,但在初始化页表项时保证了线性地址与物理地址空间页映射相对应。实际上仍是通过分页来寻址的。
   mov ax,MAINDATA_SEL
   mov es,ax
   lea di,Buffer_Page
   mov cx,4
page_toa_c:; 取表项中的内容。
   rol edx,8
   mov al,dl
   Call_16 MAINCODE_SEL,ToAscii
   stosw
   loop page_toa_c
   mov ax,MAINDATA_SEL
   mov ds,ax
   lea si,Buffer_Page
   mov ax,V_P_VIDEO_SEL
   mov es,ax; 页表0中3FA表项的物理页地址是0B8000H。
   pop di
   add di,160
   mov cx,8
   call_16 < V_P_CODE_SEL>,<offset Dis_Play>; 页表0的3FFH项,第16MB内存处。
   pop cx
   pop bx
   add bx,4
   pop ds
   loop nextitem
   mov eax,cr0
   and eax,7fffffffh
   mov cr0,eax; 关闭分页机制。
   iretd
   MAINCODELENGTH=$
IntPage Endp
MainCodeSeg Ends
CodeSeg_2 Segment para use16; DPL2
   assume cs:CodeSeg_2
Level2:
   push 12345678H
   call_16 LEVEL2GATE_SEL, <offset ReturnLevel_0_Entry>; 由CPL=2转向DPL=0的代码段,必须通过CALL调用门才能实现。指令中偏移无效,用门内偏移来代替。
   LEVEL2LENGTH=$
CodeSeg_2 Ends
DosLDTSeg Segment para use16; DOS任务,以便能使用实方式下的中断。
DosLdt_Init_Start label byte
   DosData  DESCRIPTOR<DOSDATALENGTH-1,DosDataSeg,0,ATTDW,0>
      DOSDATA_SEL=DosData-DosLDT_Init_Start+TIL
   DosStack DESCRIPTOR<DOSSTACKLENGTH-1,DosStackSeg,0,ATTDW,0>
      DOSSTACK_SEL=DosStack-DosLDT_Init_Start+TIL
   DosCode DESCRIPTOR<0ffffh,DosCodeSeg,0,ATTCER,0>
      DOSCODE_SEL=DosCode-DosLDT_Init_Start+TIL
   DOSLDT_INIT_NUM=($-DosLdt_Init_Start)/(SIZE DESCRIPTOR)
      DOSLDTLENGTH=$-DosLdt_Init_Start
DosLDTSeg Ends
DosDataSeg Segment para use16
   DosMess db  0dh,0ah, "Press any key to Exit!"
   DOSMESSLEN=$-DosMess
   DOSDATALENGTH=$
DosDataSeg Ends
DosStackSeg Segment para use16
   DOSSTACK_POINTER =1024
   db DOSSTACK_POINTER dup(0)
   DOSSTACKLENGTH=$
DosStackSeg Ends
DosCodeSeg Segment para use16
   assume cs:DosCodeSeg,ds:DATA_SEG,ss:DosStackSeg
Input:
   pushad
   push ds
   push es
   push fs
   push gs
   mov ax,NORMAL_SEL
   mov ss,ax
   mov sp,ax
   mov eax,cr0

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -