进入保护模式(三)——《x86汇编语言:从实模式到保护模式》读书笔记17

(十)保护模式下的栈

76         ;以下用简单的示例来帮助阐述32位保护模式下的堆栈操作 
77         mov cx,00000000000_11_000B         ;加载堆栈段选择子
78         mov ss,cx
79         mov esp,0x7c00

第77~79行用来初始保护模式下的栈。栈段描述符是GDT中第3个(从0开始数)描述符,这个描述符的线性基地址是0x0000_0000,段界限是0x0000_7a00,粒度是字节,B=1,属于可读可写、向下扩展的数据段。

我在博文数据段描述符和代码段描述符(一)——《x86汇编语言:从实模式到保护模式》读书笔记10中已经说过,

对于向上扩展的段(E=0),逻辑地址中的偏移值范围可以从0到(界限值*粒度);

对于向下扩展的段(E=1),逻辑地址中的偏移范围可以从(界限值*粒度)到0xFFFF(当B=0时)或者0xFFFF_FFFF(当B=1时)。

所以,对于描述符中的栈段,偏移范围是0x0000_7a00~0xffff_ffff. 仔细琢磨一下,这和我们的想法不是那么一致,因为代码79行,令ESP的初值是0x7c00,也就是说,我们本打算定义一个偏移范围是0x0000_7a00~0x0000_7c00的栈段。

因为线性基地址是0x0000_0000,也就是说描述符定义的栈段,实际可以访问的物理空间是0x0000_7a00~0xffff_ffff,但是我们却希望这个栈可以访问的物理空间是0x0000_7a00~0x0000_7c00。示意图(根据原书的图11-14改编而成)如下图所示。虽然这个栈不完美,但是不用担心,我们会在后面的学习中用更好的方法来创建栈。眨眼

New0003栈

(十一)验证32位下的栈操作

隐式的栈操作(如push、pop、call、ret、iret等)涉及两个段,一个是指令所在的代码段,一个是栈段。之前的博文我们说过,对于可执行代码段,

D=1:默认是32位地址和32位或8位的操作数

D=0:默认是16位地址和16位或8位的操作数

注意:指令前缀0x66可以用来选择非默认值的操作数大小;前缀0x67可以用来选择非默认值的地址大小

对于栈段,

B=1:栈指针使用ESP

B=0:栈指针使用SP

就本文的实验代码,其代码段描述符的D位是1,其栈段描述符的B位也是1. 所以,当进行隐式的栈操作时,默认是32位操作数(比如压栈的时候,压入的是双字),且用ESP进行操作。

所以,下面的代码就用来验证这个事实。

81         mov ebp,esp                        ;保存堆栈指针 
82         push byte '.'                      ;压入立即数(字节)
83         
84         sub ebp,4
85         cmp ebp,esp                        ;判断压入立即数时,ESP是否减4 
86         jnz ghalt                          
87         pop eax
88         mov [0x1e],al                      ;显示句点 
89      
90  ghalt:     
91         hlt                                ;已经禁止中断,将不会被唤醒

在阅读这段代码的时候,我多少有点怀疑:书上说的到底是不是真的?我想通过实践来检验:探究一下PUSH指令在16位模式和32位模式下的执行规律。经过一番折腾,终于有了结果。请参考我的博文  16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16

第81行,复制esp的值给ebp;第82行,压入一个字节(byte关键字不能省略);理论上,把ebp的值减去4后(第84行),应该和此时esp的值相等。为了证明这一点,第85行比较ebp和esp的值,如果不相等,就跳转到91行执行停机指令;如果相等,就把字符“.”显示在之前的字符串后面。

OK,第11章的内容已经学习完了。最后我们看一下代码的运行结果吧,结果就是在屏幕的左上角显示“Protect mode OK.”

执行结果

(完)

posted @ 2016-01-28 20:52  漂泊的指针  阅读(1015)  评论(0编辑  收藏  举报