《80X86汇编语言程序设计教程》十一 32位代码段和16位代码段切换实例

1、  演示32位代码段与16位代码段之间的切换。实现的功能是以十六进制和ASCII码字符两种形式显示从内存地址100000H开始的16个字节的内容。

2、  源代码如下:

  1 ;DosTest.Asm
  2 ;16位偏移的段间转移指令的宏定义
  3 ;使用于16位段,用于跳转到32位目的段
  4 ;注意:标号偏移必须在16位二进制符号数数能表示的范围之内
  5 JUMP16    macro    selector,offsetv
  6         db    0eah                        ;操作码
  7         dw    offsetv                     ;16位偏移
  8         dw    selector                    ;段值或者选择子
  9 endm
 10
 11 ;32位偏移的段间转移指令的宏定义
 12 ;使用于32位段,用于跳转到16位目的段
 13 JUMP32    macro    selector,offsetv
 14     db    0eah                            ;操作码
 15     dw    offsetv                         ;32位偏移
 16     dw    0
 17     dw    selector                        ;选择子
 18 endm
 19
 20 ;存储段描述符结构类型的定义
 21 DESCRIPTOR    struc
 22     LimitL        dw    0                    ;段界限(0~15)
 23     BaseL         dw    0                    ;段基地址(0~15)
 24     BaseM         db    0                    ;段基地址(16~23)
 25     Attributes    dw    0                    ;段属性
 26     BaseH         db    0                    ;段基地址(24~31)
 27 DESCRIPTOR ends
 28
 29
 30 ;伪描述符结构类型的定义
 31 PDESC    struct
 32     Limit        dw    0                    ;16位界限
 33     Base         dd    0                    ;基地址
 34 PDESC ends
 35
 36 ;7    6    5    4    3    2    1    0    7    6    5    4    3    2    1    0
 37 ;G    D    0    AVL    Limit(19…16)    P    DPL        DT        TYPE
 38 ;常量定义
 39 ATDR    =    0090h                        ;存在的只读数据段属性值(用于描述源数据段)
 40 ATDW    =    0092h                        ;存在的可读写数据段属性值(用于描述目的数据段)
 41 ATDWA   =    0093h                        ;存在的已访问可读写数据段属性值
 42 ATCE    =    0098h                        ;存在的只执行16位代码段属性值
 43 ATCE32  =    4098h                        ;存在的只执行32位代码段属性值
 44 DATALEN =    16                           ;源数据段长度
 45
 46
 47 ;须使用386特权指令
 48     .386P
 49
 50 ;-----------------------------------
 51 ;数据段
 52 dseg    segment    use16                    ;16位段
 53     ;GDT表
 54     GDT           label    byte
 55     DUMMY         DESCRIPTOR<>                ;空描述符
 56     CODE32_SEL    =    08h                    ;32位代码段描述符选择子
 57     CODE32        DESCRIPTOR<CODE32LEN-1,,,ATCE32,>
 58     CODE16_SEL    =    10h                    ;16位代码段描述符选择子
 59     CODE16        DESCRIPTOR<0ffffh,,,ATCE,>
 60     DATAS_SEL     =    18h                    ;源数据段描述符选择子
 61     DATAS         DESCRIPTOR<DATALEN-1,,10h,ATDR,>;段基地址100000h
 62     DATAD_SEL     =    20h                    ;目的数据段描述符选择子
 63     DATAD         DESCRIPTOR<DATALEN*8-1,80a0h,0bh,ATDW,0>;段基地址0b80a0h
 64     STACKS_SEL    =    28h                    ;堆栈段描述符选择子
 65     STACKS        DESCRIPTOR<0ffffh,,,ATDWA,>;段基地址0000h,栈顶基址0ffffh
 66     NORMAL_SEL    =    30h                    ;规范段描述符选择子
 67     NORMAL        DESCRIPTOR<0ffffh,0,0,ATDW,>;段基地址0000h,栈顶基址0ffffh
 68     GDTLEN        = $ - GDT                   ;GDT表长度
 69     ;
 70     VGDTR         PDESC<GDTLEN-1,>            ;GDT伪描述符
 71     VARSS         dw    ?                     ;用于保存SS变量
 72 dseg    ends
 73
 74 ;-----------------------------------
 75 ;实模式下代码段
 76 csegr    segment    use16    ‘real‘
 77     assume    cs:csegr,ds:dseg
 78 start:                                      ;程序入口
 79     mov        ax,dseg
 80     mov        ds,ax
 81     ;
 82     mov        bx,16                        ;写VGDTR(GDT首地址转线性地址)
 83     mul        bx
 84     add        ax,offset GDT
 85     adc        dx,0
 86     mov        word ptr VGDTR.Base,ax
 87     mov        word ptr VGDTR.Base + 2,dx
 88     ;
 89     mov        ax,cseg32                    ;写32位代码段段基址
 90     mul        bx
 91     mov        CODE32.BaseL,ax
 92     mov        CODE32.BaseM,dl
 93     mov        CODE32.BaseH,dh
 94     ;
 95     mov        ax,cseg16                    ;写16位代码段段基址
 96     mul        bx
 97     mov        CODE16.BaseL,ax
 98     mov        CODE16.BaseM,dl
 99     mov        CODE16.BaseH,dh
100     ;
101     mov        ax,ss                        ;写堆栈段段基址
102     mul        bx
103     mov        STACKS.BaseL,ax
104     mov        STACKS.BaseM,dl
105     mov        STACKS.BaseH,dh
106     mov        VARSS,ss                     ;保存实模式下段基址
107     ;
108     lgdt       fword ptr VGDTR              ;装载VGDTR到GDTR
109     ;
110     cli                                     ;关中断
111     call       ENABLEA20                    ;开地址线A20
112     ;
113     mov        eax,cr0                      ;CR0的PE位置1
114     or         eax,1
115     mov        cr0,eax
116     ;进入32位代码段
117     JUMP16     <CODE32_SEL>,<low offset SPM32>;切换到保护模式
118     ;此时已经回到实模式
119 TOREAL:
120     mov        ax,dseg
121     mov        ds,ax
122     mov        ss,VARSS                     ;恢复实模式下的SS
123     call       DISABLEA20                   ;关闭地址线A20
124     sti                                     ;开中断
125     mov        ah,07h                       ;等待按键终止程序
126     int        21h
127     mov        ah,4ch
128     int        21h
129
130
131 ;打开地址线A20号
132 ENABLEA20    proc
133     push       ax
134     in         al,92h
135     or         al,2
136     out        92h,al
137     pop        ax
138     ret
139 ENABLEA20 endp
140
141 ;关闭地址线A20号
142 DISABLEA20    proc
143     push       ax
144     in         al,92h
145     and        al,0fdh
146     out        92h,al
147     pop        ax
148     ret
149 DISABLEA20 endp
150
151 csegr    ends
152
153 ;-----------------------------------
154 ;32位代码段
155 cseg32    segment    use32    ‘pm32‘
156     assume    cs:cseg32
157 SPM32:
158     mov        ax,STACKS_SEL
159     mov        ss,ax                        ;装载堆栈段描述符选择子
160     mov        ax,DATAS_SEL
161     mov        ds,ax                        ;装载源数据段描述符选择子
162     mov        ax,DATAD_SEL
163     mov        es,ax                        ;装载目的数据段描述符选择子
164     ;以下开始以ASCII码形式显示源16个字节
165     ;目的数据段需要16 * (4 + 2) = 96个字节
166     xor        esi,esi                      ;设置指针和计数器
167     xor        edi,edi
168     mov        ecx,DATALEN                  ;16个数据,循环16次
169     cld                                     ;清方向标志位
170 NEXT:
171     lodsb                                   ;从源数据段装载一个byte数据到al并移动指针
172     push      ax
173     call      TOASCII                      ;低4位转ASCII码(一个byte)
174     mov        ah,7                         ;显示属性为黑底白字(再一个byte)
175     shl        eax,16                       ;暂存在eax高16位
176     pop        ax
177     shr        al,4                         ;高4位转ASCII码
178     call       TOASCII
179     mov        ah,7
180     stosd                                   ;eax的4个byte(dword)存入目的数据段并移动指针
181     mov        al,‘ ‘                       ;显示空格,属性为黑底白字,2个字节包含字符ASCII码和字符属性
182     stosw                                   ;ax的2个byte(word)入目的数据段并移动指针
183     loop       NEXT
184     ;变化到16位代码段
185     JUMP32     <CODE16_SEL>,<offset SPM16>
186     ;jmp       far ptr SPM16                ;这里取代完全没有问题
187
188 ;把AL低4位的十六进制数转换成对应的ASCII码,保存在AL中
189 TOASCII    proc
190     and        al,0fh
191     add        al,90h
192     daa
193     adc        al,40h
194     daa
195     ret
196 TOASCII endp
197
198     CODE32LEN  = $ - SPM32
199 cseg32    ends
200
201 ;-----------------------------------
202 ;16位代码段
203
204 cseg16    segment    use16    ‘pm16‘
205     assume    cs:cseg16
206 SPM16:                                      ;跳转过来时ss、es的值都没有改变,实际上还是在保护模式
207     ;以下开始以十六进制数形式显示源16个字节
208     ;目的数据段需要16 * 2 = 32个字节
209     xor        si,si                        ;源数据段指针归位
210     mov        di,DATALEN * 3 * 2           ;这个语句是多余的,这里重新设置di没有意义
211     mov        ah,7                         ;显示属性为黑底白字
212     mov        cx,DATALEN
213 AGAIN:
214     lodsb                                   ;从源数据段装载一个byte数据到al并移动指针
215     stosw                                   ;ax的2个byte(word)入目的数据段并移动指针
216     loop      AGAIN
217     ;
218     mov        ax,NORMAL_SEL                ;装载规范段描述符选择子到ds和es
219     mov        ds,ax                        ;这将引起高速缓存寄存器的刷新
220     mov        es,ax
221     ;
222     mov        eax,cr0                        ;切换到实模式下
223     and        eax,0fffffffeh
224     mov        cr0,eax
225     ;切换回实模式
226     jmp        far ptr TOREAL
227 cseg16    ends
228     end        start

3、  源代码有几处要说明的地方

  1)  原书中的“JUMP16 CODE32_SEL,<offset SPM32>”语句须改为“JUMP16     <CODE32_SEL>,<low offset SPM32> ”语句,原因是16位段不支持32位偏移(offset SPM32为32位立即数)

  2)  原书中的“CODE32LEN  = $”需要修改为“CODE32LEN     = $ - SPM32”,如果不是原书印刷等之类的错误,那么绝对是作者逻辑错误

4、  运行效果与相关说明

  1)  使用DiskGenuis复制.exe目标文件到DOS虚拟系统

  2)  打开虚拟机进入DOS7.1,使用“cls”指令清屏(否则将影响输出的视觉效果)

  3)  执行目标程序,将看到输出结果

  4)  使用adu.exe验证一下输出是正确的

5、  实现步骤的简单阐述

  1)  作切换到保护方式的准备

    2个16位数据段描述符、1个16位代码段描述符、1个16位堆栈段描述符、1个32位代码段描述符和1个规范段描述符。这里没必要再说了,只是注意下,32位代码段描述符在设置界限时采用的方法。另外,这个界限值不是长度,在以字节为粒度时是偏移量。

  2)  切换到保护方式一个32位代码段

    在上一个实例中已经提到过,这个JUMP宏实际上就是一条特殊的远跳转指令,这里要关注的一个东西是,跳转时的地址偏移问题。我简单说下:

    JUMP16用于16位段中,实现跳入32位段。由段间绝对跳转的性质可知,最大偏移是0FFFFH,也就是说,JUMP16实现的从16位段到32位段的跳转是有条件的:目标地址标号在32位段的段内偏移必须不大于0FFFFH。

    JUMP32用于32位段中,实现跳入16位段。这个也只需要注意同一个地方,那就是16位段最大偏移为0FFFFH,所以跳转时必须保证高16位为0,作者在这里使用了宏定义把双字类型拆分成两个字类型的域,并把高字强行设置为0,对安全性有一定的提高。

    从上面来看,跳转时不需要关注段寄存器的内容,此外,16位段与32位段之间的相互跳转与实模式还是保护模式没有半点关系,从这里也可以看到,实模式到保护模式的切换,在把VGDTR装载到GDTR寄存器以及将CR0的PE位置1后,就是一个简单的跳转指令,所以,也支持在模式切换的同时进行段类型的切换。

    如果能保证足够安全,可以完全不用作者的宏来实现这些跳转,就像在本例中最后由保护模式切换为实模式的“jmp far ptr TOREAL”那样,直接使用标号进行远跳,也是一样的,当然,这里要注意一些问题,这个问题也正体现之前一直说的这个远跳还是特殊的地方:这个宏中远跳指令可以重置自己设定的代码段值/代码段选择子和代码段内偏移分别到CS、IP/EIP。在保护模式下,装入段CS的是段选择子而不是段基地址,使用该指令来跳转是必须的,凡是装入的是段值而不是段选择子的情况,都可以使用远跳指令取代。

  3)  把源数据转十六进制数码的ASCII码,并直接填入显存

    采用的是直接写屏的方式(参考“《80X86汇编语言程序设计教程》四 输入输出与中断”)。显存开始地址为0B8000H,这里的0b80a0h表示在3号显示方式下,屏幕第2行开头的位置。

  4)  切换到16位段代码

  5)  把源数据直接作为ASCII码填入显存

  6)  切回实模式

6、  特别说明

  1)  本例没有建立专用堆栈,但是在原堆栈上建立了堆栈段,所以保护模式下可进行堆栈操作

  2)  同上个实例一样,大量简化处理,没有IDT和LDT,DPL都设置为了0。

  3)  关于远跳的特殊之处的说明,各个段寄存器所配的高速缓冲寄存器在实模式下依然起作用。实模式下要求它的内容应该如下表:



段基地址


段界限

(固定)


其它段属性





G



R


W


E




CS


当前CS*16


0000FFFFH


Y


0


Y


B


U


Y


Y


Y


-


N


SS


当前SS*16


0000FFFFH


Y


0


Y


B


U


Y


Y


N


W


-


DS


当前DS*16


0000FFFFH


Y


0


Y


B


U


Y


Y


N


-


-


ES


当前ES*16


0000FFFFH


Y


0


Y


B


U


Y


Y


N


-


-


FS


当前FS*16


0000FFFFH


Y


0


Y


B


U


Y


Y


N


-


-


GS


当前GS*16


0000FFFFH


Y


0


Y


B


U


Y


Y


N


-


-

    其中:“Y”表示“是”,“N”表示“否”,“B”表示字节,“U”表示向高扩展段,“W”表示字操作堆栈。由于实模式下不可以设置高速缓存寄存器(也就是说,即使改变段寄存器中的段值,也不会引起高速缓存寄存器中内容的刷新),所以我们必须在保护模式下提前刷新它们到符合要求的值,再切换回保护模式。源代码中的“规范段描述符选择子”做的就是这样一个事情。我测试过,如果将它们去掉,程序在切换回实模式后将死机。此外,需要说明的是,源代码中的JMP32完全没必要用,这个纯粹就是跳转,根本不需要刷新高速缓存寄存器,这里也没有进行模式切换,我不知道作者放这是为了什么,感觉容易误导到读者。

时间: 2024-10-25 02:58:29

《80X86汇编语言程序设计教程》十一 32位代码段和16位代码段切换实例的相关文章

《80X86汇编语言程序设计教程》十 实模式与保护模式的切换实例

1.  再次声明,需要纯DOS系统才能看到满意测试效果.内容是演示实模式与保护模式切换实例,实现功能是16进制显示从110000H开始的256个字节的值 2.  源代码如下: 1 ;功能:演示实模式与保护模式的切换,16进制显示从110000H开始的256个字节的值 2 ;16位偏移的段间直接转移指令的宏定义,这是一个JMP指令到所描述的地址 3 4 JUMP macro selector,offsetv 5 db 0eah ;操作码 6 dw offsetv ;16位偏移 7 dw selec

《80X86汇编语言程序设计教程》十五 任务切换实例

1.  理论知识参考"<80X86汇编语言程序设计教程>十二 任务状态段.控制门和控制转移",演示内容:直接通过TSS段的任务切换.通过任务门的任务切换.任务内特权级的变换及参数传递.实现的逻辑功能是:从Temp任务切换到Demo任务以后显示原任务(Temp)的挂起点EIP的值. 2.  源代码 "386scd.asm"不再贴上来.参考"<80X86汇编语言程序设计教程>十三 任务内无特权级变换转移实例",演示代码如下:

《80X86汇编语言程序设计教程》十九 操作系统类指令与输入输出保护

1.  通常只在操作系统代码中使用,80386支持4个特权等级,操作系统指令也可分3种:实模式和任何特权级下可执行指令.实模式及特权级0下可执行的指令和仅在保护模式下执行的指令. 1)  实模式和任何特权级下可执行的指令 a)存储全局和中断描述符表寄存器指令 GDT与IDT整个系统各只有一张,它们的定位信息分别保存在GDTR与IDTR中,这两个寄存器的值可以被保存.须注意,LDT表示任务私有,存储LDTR值的指令不属于这一类. i)存储全局描述符表寄存器指令:SGDT  DST DST是48位(

《80X86汇编语言程序设计教程》二十四 进入与离开V86模式实例

1.  这是我这本书调得最失败的一个实例,而且问题都是超出了这本书能教会我的范畴.作者对调试环境几乎只字不提,这让我有点费解.原作者使用TASM的编译代码,而我使用的MASM加虚拟机进行测试,不知道这两样东西哪里有问题还是源代码要做哪些修正.理论知识参考"<80X86汇编语言程序设计教程>二十二 分页管理机制与虚拟8086模式". 2.  进入和离开V86模式实例:各2种方式进入和离开V86模式.V86模式下8086程序调用实模式软中断处理程序.逻辑功能:以驻留方式结束程序

《80X86汇编语言程序设计教程》二十三 分页管理机制实例

1.  理论知识参考"<80X86汇编语言程序设计教程>二十二 分页管理机制与虚拟8086模式".演示分页机制实例:初始化页目录表和部分页表:启用分页管理机制:关闭分页管理机制等.逻辑功能:在屏幕上显示一条表示已启用分页管理机制的提示信息.大体步骤是:在实模式下拷贝显示串程序的代码到预定义区域,转保护模式,初始化页目录和2个页表,开启分页机制,转入预定义区执行显示代码,然后关闭分页机制,重新回到实模式,程序终止. 2.  源代码 "386scd.asm"

《80X86汇编语言程序设计教程》二十五 结语(读后感:这本书怎么样)

这本书的推荐星级是:5星.毕竟是经典书籍,没什么好说的. 就汇编本身而言,在编写高效率程序以及对程序的优化,调试,工程的逆向都是一门基础:就理论上的操作系统而言,汇编让你了解CPU,了解计算机的体系结构,它是阅读操作系统源码的前提,这也是<80X86汇编语言程序设计教程>做得比较好的一点,它对386的保护方式下的编程写得比较详实,读完整本书,会发现这学的不仅仅是汇编语言,还有CPU的体系架构,它让你基本猜测得到在编写基于80386CPU的操作系统时,大概要做一些什么事情. 阅读前,我选过几本书

《80x86汇编语言程序设计》保护模式第一个例题

<80x86汇编语言程序设计>保护模式第一个例题的一些个人理解和注释 ; 16位偏移的段间直接转移指令的宏定义 02.jump macro selector, offsetv 03. 04. db 0eah ; jmp far 的操作码 05. dw offsetv 06. dw selector 07. 08.endm 09. 10.; 字符显示宏指令定义 11.echoch macro ascii 12. 13. mov ah, 2 14. mov dl, ascii 15. int 21

如何将24位RGB颜色转换16位RGB颜色

有许多朋友第一次使用16位彩色显示屏会遇到如何将24位RGB颜色转换为对应的16位RGB颜色的问题,通过查阅相关资料,就写一下其中的转换原理吧,希望对大家会有所帮助. 我们知道24位RGB是分别由8位红色,8位绿色以及8位蓝色组成: RRRRRRRR GGGGGGGG BBBBBBBB 例如:24位RGB红色表示方法为 11111111 00000000 00000000        (十六进制表示为:0xFF0000) 而对应的16位RGB颜色则是由5位红色,6位绿色以及5位红色组成: RR

位运算实现ushort(16位)转化成long(64位)以及int(32位)

public static long ushortTolong(ushort pre48, ushort pre32, ushort pre16, ushort pre0) { ulong rt = 0; ulong temp = 0; temp = pre48; rt = temp << 48; temp = pre32; temp = temp << 32; rt = rt | temp; temp = pre16; temp = temp << 16; rt =