16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16
来源:互联网 发布:手机订单软件 编辑:程序博客网 时间:2024/06/05 05:46
一、Intel 32 位处理器的工作模式
如上图所示,Intel 32 位处理器有3种工作模式。
(1)实模式:工作方式相当于一个8086
(2)保护模式:提供支持多任务环境的工作方式,建立保护机制
(3)虚拟8086模式:这种方式可以使用户在保护模式下运行8086程序(比如cmd打开的console窗口,就是工作在虚拟8086模式)
有几点需要特别说明:
(1)保护模式可分为16位和32位的,由段描述符中的D标志指明。对于32位代码段和数据段,这个标志总是设为1;对于16位代码和数据段,这个标志被设置为0.
D=1:默认使用32位地址和32位或8位的操作数。
D=0:默认使用16位地址和16位或8位的操作数。(主要是为了能够在32位处理器上运行16位保护模式的程序)
指令前缀0x66用来选择非默认值得操作数大小,0x67用来选择非默认值的地址大小。
(2)在实模式下,也可以使用32位的寄存器,比如
mov eax,ecxmov ebx,0x12345678
(3)在书中,把实模式和16位的保护模式统称为“16位模式”;把32位保护模式称为“32位模式”。我的博文也沿用这种叫法。
(4)32位处理器可以执行16位的程序,包括实模式和16位保护模式。
(5)当处理器在16位模式下运行时,可以使用32位的寄存器,执行32位运算。
(6)在16位模式下,数据的大小是8位或者16位的;控制转移和内存访问时,偏移量也是16位的。
(7)32位保护模式兼容80286的16位保护模式。
(8)在16位模式下,处理器把所有指令都看成是16位的。
结合(5)和(8),我们发现一个问题:当处理器运行16位模式下,既然把所有指令都看成16位的,那么怎么使用32位的寄存器,执行32位的运算呢?答案是利用指令前缀0x66和0x67.前面已经说过,指令前缀0x66用来选择非默认值的操作数大小,0x67用来选择非默认值的地址大小。
比如说,指令码0x40在16位模式下对应的指令是
inc ax
如果加上前缀0x66,也就是指令码66 40,当处理器在16位模式下运行,66 40对应的指令是
inc eax
同理,如果处理器运行在32位模式下,处理器认为指令是32位的,如果加了0x66,那么就表示指令的操作数是16位的。
在编写程序的时候,我们应该考虑指令的运行环境。为了指令默认的运行环境,NASM提供了伪指令bits,用于指明其后的指令是被编译成16位的还是32位的。比如:
[bits 16] mov cx,dx ;89 D1 mov eax,ebx ;66 89 D8 [bits 32] mov cx,dx ;66 89 D1 mov eax,ebx ;89 D8
注意,[bits 16]和[bits 32]的方括号是可以省略的。
二、PUSH指令探究
由于32位的处理器都拥有32位的寄存器和算术逻辑部件,而且同内存芯片之间的数据通路至少是32位的,因此,所有以寄存器或者内存单元为操作数的指令都被扩充,以适应32位的算术逻辑操作。而且,这些扩展的操作即使是在16位模式下(实模式和16位保护模式)也是可用的。
我在博文 32位x86处理器编程导入——《x86汇编语言:从实模式到保护模式》读书笔记08 中已经总结了一般指令的扩展,在这里,我仅对PUSH指令进行实验和总结。
实验目的就是测试在3种模式下,PUSH指令的工作行为(比如SP或ESP到底怎么变化,压入的数到底是多少)。所以,我列了一个单子,把所有能想到的形式都列出来了,其中有的我也不确定(或许这样写编译都会报错)。不管那么多,先写出来,然后让编译器筛选吧
1 ;测试各种push23 ;操作数是立即数,分为一字节、两字节、四字节4 push 0x805 push byte 0x80 67 push 0x80008 push word 0x80009 10 push 0x8765432111 push dword 0x876543211213 ;操作数是寄存器,分为16位寄存器和32位寄存器 14 mov eax,0x8642135715 push ax16 push eax1718 ;操作数是内存单元,分为一字节、两字节、四字节 19 push [data]20 push byte [data]21 push word [data]22 push dword [data]
是不是有的写法明显就不对呢?
首先,第20行,肯定不对。因为如果是内存操作数的话,不能用byte修饰。剩下来的错误,我会在后文揭晓答案。
1.在实模式下的实验
(1)实验代码
1 ;PUSH 指令实验2 3 jmp near start4 5 data db 0x12,0x34,0x56,0x786 message db 'Hello,PUSH!'7 8 start:9 mov ax,0x7c0 ;设置数据段的段基地址 10 mov ds,ax11 12 mov ax,0xb800 ;设置附加段基址到显示缓冲区13 mov es,ax14 15 ;以下显示字符串 16 mov si,message 17 mov di,018 mov cx,start-message19 @g:20 mov al,[si]21 mov [es:di],al22 inc di23 mov byte [es:di],0x0224 inc di25 inc si26 loop @g27 28 ;测试各种push29 push 0x8030 push byte 0x8031 32 push 0x800033 push word 0x800034 35 push 0x8765432136 push dword 0x8765432137 38 mov eax,0x8642135739 push ax40 push eax41 42 ;push [data]43 push word [data]44 push dword [data]45 46 push ds47 push gs48 49 jmp near $ 50 51 52 times 510-($-$$) db 053 db 0x55,0xaa
这段代码不是用的配书代码,是我自己写的。
第5行,定义了4字节的数据,这是为了后面验证“push + 内存操作数”这一情况。
第6行,定义了一个字符串,要把它显示在屏幕上。这样做是为了调试方便,让我们知道我们的程序已经RUN了。
第29行到47行,测试各种push,我会利用Bochs的调试功能,跟踪每条Push的执行情况,把结果总结出来。
好的,我们开始编译吧。
对于30行,有个警告:
push byte 0x80 ;warning: signed byte value exceeds bounds
对于35行,还是一个警告:
push 0x87654321 ;warning: word data exceeds bounds
对于42行,呵呵,就是一个错误了。
push [data] ; error: operation size not specified
好吧,看来这样不指定操作数的大小是不行的,所以我们把42行注释掉。
然后再编译,好的,可以了。
调试的过程就是不断用n命令,反复用print-stack命令,还有reg命令等,仔细观察栈的变化和SP的变化。(此处省略2000字)
(2)实验报告
小二,上实验报告!
通过上面的实验,我们可以知道,如果CPU运行在实模式,如果用NASM编译,push指令可以这么用:
2.在16位保护模式下的实验
(1)关于16位保护模式
请参考我的博文 关于80286——《x86汇编语言:从实模式到保护模式》读书笔记15
(2)实验代码
实验代码由配书代码(代码清单11-1 (文件名:c11_mbr.asm))修改而成。目的就是我们要从实模式进入16位的保护模式,然后测试16位保护模式下PUSH指令的行为。
1 ;test push (16位保护模式下)23 ;设置堆栈段和栈指针 4 mov ax,cs 5 mov ss,ax6 mov sp,0x7c007 8 ;计算GDT所在的逻辑段地址 9 mov ax,[cs:gdt_base+0x7c00] ;低16位 10 mov dx,[cs:gdt_base+0x7c00+0x02] ;高16位 11 mov bx,16 12 div bx 13 mov ds,ax ;令DS指向该段以进行操作14 mov bx,dx ;段内起始偏移地址 15 16 ;创建0#描述符,它是空描述符,这是处理器的要求17 mov dword [bx+0x00],0x0018 mov dword [bx+0x04],0x00 1920 ;创建#1描述符,保护模式下的代码段描述符21 mov dword [bx+0x08],0x7c0001ff 22 mov dword [bx+0x0c],0x00009800 2324 ;创建#2描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区) 25 mov dword [bx+0x10],0x8000ffff 26 mov dword [bx+0x14],0x0000920b 2728 ;创建#3描述符,保护模式下的堆栈段描述符29 mov dword [bx+0x18],0x00007a0030 mov dword [bx+0x1c],0x000096003132 ;初始化描述符表寄存器GDTR33 mov word [cs: gdt_size+0x7c00],31 ;描述符表的界限(总字节数减一) 34 35 lgdt [cs: gdt_size+0x7c00]36 37 in al,0x92 ;南桥芯片内的端口 38 or al,0000_0010B39 out 0x92,al ;打开A204041 cli ;保护模式下中断机制尚未建立,应 42 ;禁止中断 43 mov eax,cr044 or eax,145 mov cr0,eax ;设置PE位46 47 ;以下进入保护模式... ...48 jmp 0x0008:flush ;描述符选择子:16位偏移49 ;清流水线并串行化处理器 50 5152 flush:53 mov cx,00000000000_10_000B ;加载数据段选择子(0x10)54 mov ds,cx5556 ;以下在屏幕上显示"ABCDEFGHIJK" 57 mov byte [0x00],'A' 58 mov byte [0x02],'B'59 mov byte [0x04],'C'60 mov byte [0x06],'D'61 mov byte [0x08],'E'62 mov byte [0x0a],'F'63 mov byte [0x0c],'G'64 mov byte [0x0e],'H'65 mov byte [0x10],'I'66 mov byte [0x12],'J'67 mov byte [0x14],'K'68 6970 ;测试push71 mov cx,00000000000_11_000B ;加载堆栈段选择子72 mov ss,cx73 mov sp,0x7c007475 76 push 0x8077 push byte 0x80 ; warning: signed byte value exceeds bounds78 79 push 0x800080 push word 0x800081 82 push 0x8765432183 ;warning: word data exceeds bounds84 push dword 0x8765432185 86 87 mov eax,0x8642135788 push ax89 push eax90 91 ;push [0x00]error: operation size not specified92 push byte [0x00]93 push word [0x00]94 95 push dword [0x00]96 97 push ds98 push gs99 push es100 push cs101 102 ghalt: 103 hlt ;已经禁止中断,将不会被唤醒 104105;-------------------------------------------------------------------------------106 107 gdt_size dw 0108 gdt_base dd 0x00007e00 ;GDT的物理地址 109 110 times 510-($-$$) db 0111 db 0x55,0xaa
对比32位保护模式的代码,就会发现16位保护模式的代码略有不同。
首先,比如说22行,段描述符的定义是
22 mov dword [bx+0x0c],0x00009800
因为80286中,段描述符的格式是
所以,高4字节的16~32位全部为0.
其次,
47 ;以下进入保护模式... ...48 jmp 0x0008:flush ;描述符选择子:16位偏移49 ;清流水线并串行化处理器
这里,没有加伪指令[bits 32],而且,偏移flush没有用dword修饰。因为操作数和偏移是16位的。
好了,代码就说到这里,我们看实验报告吧。
(3)实验报告
通过和实模式的对比,可以发现,除了9、10两行中的指令码的偏移不一样(这和数据存放的位置有关系,和PUSH没有关系),PUSH指令的行为是惊人的相同。所以我们可以得出结论,16位保护模式下,PUSH的用法和实模式是一样的。我想,这也是在原书中,作者把实模式和16位的保护模式统称为“16位模式”,把32位保护模式称为“32位模式”的原因吧。
3.在32位保护模式下的实验
(1)实验代码
实验代码由配书代码(代码清单11-1 (文件名:c11_mbr.asm))修改而成。
1 ;test push (32位保护模式)23 ;设置堆栈段和栈指针 4 mov ax,cs 5 mov ss,ax6 mov sp,0x7c007 8 ;计算GDT所在的逻辑段地址 9 mov ax,[cs:gdt_base+0x7c00] ;低16位 10 mov dx,[cs:gdt_base+0x7c00+0x02] ;高16位 11 mov bx,16 12 div bx 13 mov ds,ax ;令DS指向该段以进行操作14 mov bx,dx ;段内起始偏移地址 15 16 ;创建0#描述符,它是空描述符,这是处理器的要求17 mov dword [bx+0x00],0x0018 mov dword [bx+0x04],0x00 1920 ;创建#1描述符,保护模式下的代码段描述符21 mov dword [bx+0x08],0x7c0001ff 22 mov dword [bx+0x0c],0x00409800 2324 ;创建#2描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区) 25 mov dword [bx+0x10],0x8000ffff 26 mov dword [bx+0x14],0x0040920b 2728 ;创建#3描述符,保护模式下的堆栈段描述符29 mov dword [bx+0x18],0x00007a0030 mov dword [bx+0x1c],0x004096003132 ;初始化描述符表寄存器GDTR33 mov word [cs: gdt_size+0x7c00],31 ;描述符表的界限(总字节数减一) 34 35 lgdt [cs: gdt_size+0x7c00]36 37 in al,0x92 ;南桥芯片内的端口 38 or al,0000_0010B39 out 0x92,al ;打开A204041 cli ;保护模式下中断机制尚未建立,应 42 ;禁止中断 43 mov eax,cr044 or eax,145 mov cr0,eax ;设置PE位46 47 ;以下进入保护模式... ...48 jmp dword 0x0008:flush ;16位的描述符选择子:32位偏移49 ;清流水线并串行化处理器 50 [bits 32] 5152 flush:53 mov cx,00000000000_10_000B ;加载数据段选择子(0x10)54 mov ds,cx5556 ;以下在屏幕上显示"ABCDEFGHIJK" 57 mov byte [0x00],'A' 58 mov byte [0x02],'B'59 mov byte [0x04],'C'60 mov byte [0x06],'D'61 mov byte [0x08],'E'62 mov byte [0x0a],'F'63 mov byte [0x0c],'G'64 mov byte [0x0e],'H'65 mov byte [0x10],'I'66 mov byte [0x12],'J'67 mov byte [0x14],'K'68 6970 ;测试push71 mov cx,00000000000_11_000B ;加载堆栈段选择子72 mov ss,cx73 mov esp,0x7c007475 76 push 0x8077 push byte 0x80 ;warning: signed byte value exceeds bounds78 79 push 0x800080 push word 0x800081 82 push 0x8765432183 push dword 0x8765432184 85 mov eax,0x8642135786 push ax87 push eax88 89 90 push word [0x00]91 push dword [0x00]92 93 push ds94 push gs95 push es96 push cs97 98 ghalt: 99 hlt ;已经禁止中断,将不会被唤醒 100101;-------------------------------------------------------------------------------102 103 gdt_size dw 0104 gdt_base dd 0x00007e00 ;GDT的物理地址 105 106 times 510-($-$$) db 0107 db 0x55,0xaa
如果对上面的代码不熟悉的话,可以参考我的博文 进入保护模式(一)——《x86汇编语言:从实模式到保护模式》读书笔记12 等文章。
(2)实验报告
根据测试报告,我们可以归纳出32位保护模式下,针对NASM编译器的push指令用法:
(完)
- 16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16
- 32位x86处理器编程导入——《x86汇编语言:从实模式到保护模式》读书笔记08
- 进入保护模式(一)——《x86汇编语言:从实模式到保护模式》读书笔记12
- 进入保护模式(二)——《x86汇编语言:从实模式到保护模式》读书笔记14
- 进入保护模式(三)——《x86汇编语言:从实模式到保护模式》读书笔记17
- x86分页机制——《x86汇编语言:从实模式到保护模式》读书笔记42
- 存储器的保护(二)——《x86汇编语言:从实模式到保护模式》读书笔记19
- 存储器的保护(一)——《x86汇编语言:从实模式到保护模式》读书笔记18
- 存储器的保护(三)——《x86汇编语言:从实模式到保护模式》读书笔记20
- 《X86汇编语言:从实模式到保护模式》读书笔记之引言
- 《x86汇编语言:从实模式到保护模式》读书笔记之后记
- 8086处理器的无条件转移指令——《x86汇编语言:从实模式到保护模式》读书笔记13
- 开启分页机制———《x86汇编语言:从实模式到保护模式》读书笔记44
- 8086中断系统——《x86汇编语言:从实模式到保护模式》读书笔记04
- 8086键盘输入实验——《x86汇编语言:从实模式到保护模式》读书笔记07
- 关于80286——《x86汇编语言:从实模式到保护模式》读书笔记15
- RPL的故事 ——《x86汇编语言:从实模式到保护模式》读书笔记31
- TSS详解 ——《x86汇编语言:从实模式到保护模式》读书笔记33
- 工作中的技巧
- [MATLAB] Matlab hints for Machine Learning by Anderw Ng
- 蓝桥杯 - 删除数组零元素
- android 运行中 java.lang.NoClassDefFoundError:问题报错的一个原因
- Swift中的元组(Tuple)
- 16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16
- UI视图挖坑
- vi/vim使用进阶: lookupfile插件
- LeetCode120. Triangle
- 树莓派新手入门流程
- Service
- 【NYOJ】[60]谁获得了最高奖学金
- Flask
- 浏览器环境下JavaScript脚本加载与执行探析之defer与async特性