一、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,ecx
mov 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    ;测试各种push
2
3 ;操作数是立即数,分为一字节、两字节、四字节
4 push 0x80
5 push byte 0x80
6
7 push 0x8000
8 push word 0x8000
9
10 push 0x87654321
11 push dword 0x87654321
12
13 ;操作数是寄存器,分为16位寄存器和32位寄存器
14 mov eax,0x86421357
15 push ax
16 push eax
17
18 ;操作数是内存单元,分为一字节、两字节、四字节
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 start
4
5 data db 0x12,0x34,0x56,0x78
6 message db 'Hello,PUSH!'
7
8 start:
9 mov ax,0x7c0 ;设置数据段的段基地址
10 mov ds,ax
11
12 mov ax,0xb800 ;设置附加段基址到显示缓冲区
13 mov es,ax
14
15 ;以下显示字符串
16 mov si,message
17 mov di,0
18 mov cx,start-message
19 @g:
20 mov al,[si]
21 mov [es:di],al
22 inc di
23 mov byte [es:di],0x02
24 inc di
25 inc si
26 loop @g
27
28 ;测试各种push
29 push 0x80
30 push byte 0x80
31
32 push 0x8000
33 push word 0x8000
34
35 push 0x87654321
36 push dword 0x87654321
37
38 mov eax,0x86421357
39 push ax
40 push eax
41
42 ;push [data]
43 push word [data]
44 push dword [data]
45
46 push ds
47 push gs
48
49 jmp near $
50
51
52 times 510-($-$$) db 0
53 db 0x55,0xaa

这段代码不是用的配书代码,是我自己写的。

第5行,定义了4字节的数据,这是为了后面验证“push + 内存操作数”这一情况。

第6行,定义了一个字符串,要把它显示在屏幕上。这样做是为了调试方便,让我们知道我们的程序已经RUN了。

第29行到47行,测试各种push,我会利用Bochs的调试功能,跟踪每条Push的执行情况,把结果总结出来。

好的,我们开始编译吧。

对于30行,有个警告:

push byte 0x80 ;warning: signed byte value exceeds bounds

既然是警告,那么30行不必去掉。相反我们更加好奇了,看看执行时会发生什么。

对于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位保护模式下)
2
3 ;设置堆栈段和栈指针
4 mov ax,cs
5 mov ss,ax
6 mov sp,0x7c00
7
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],0x00
18 mov dword [bx+0x04],0x00
19
20 ;创建#1描述符,保护模式下的代码段描述符
21 mov dword [bx+0x08],0x7c0001ff
22 mov dword [bx+0x0c],0x00009800
23
24 ;创建#2描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区)
25 mov dword [bx+0x10],0x8000ffff
26 mov dword [bx+0x14],0x0000920b
27
28 ;创建#3描述符,保护模式下的堆栈段描述符
29 mov dword [bx+0x18],0x00007a00
30 mov dword [bx+0x1c],0x00009600
31
32 ;初始化描述符表寄存器GDTR
33 mov word [cs: gdt_size+0x7c00],31 ;描述符表的界限(总字节数减一)
34
35 lgdt [cs: gdt_size+0x7c00]
36
37 in al,0x92 ;南桥芯片内的端口
38 or al,0000_0010B
39 out 0x92,al ;打开A20
40
41 cli ;保护模式下中断机制尚未建立,应
42 ;禁止中断
43 mov eax,cr0
44 or eax,1
45 mov cr0,eax ;设置PE位
46
47 ;以下进入保护模式... ...
48 jmp 0x0008:flush ;描述符选择子:16位偏移
49 ;清流水线并串行化处理器
50
51
52 flush:
53 mov cx,00000000000_10_000B ;加载数据段选择子(0x10)
54 mov ds,cx
55
56 ;以下在屏幕上显示"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
69
70 ;测试push
71 mov cx,00000000000_11_000B ;加载堆栈段选择子
72 mov ss,cx
73 mov sp,0x7c00
74
75
76 push 0x80
77 push byte 0x80 ; warning: signed byte value exceeds bounds
78
79 push 0x8000
80 push word 0x8000
81
82 push 0x87654321
83 ;warning: word data exceeds bounds
84 push dword 0x87654321
85
86
87 mov eax,0x86421357
88 push ax
89 push eax
90
91 ;push [0x00]error: operation size not specified
92 push byte [0x00]
93 push word [0x00]
94
95 push dword [0x00]
96
97 push ds
98 push gs
99 push es
100 push cs
101
102 ghalt:
103 hlt ;已经禁止中断,将不会被唤醒
104
105;-------------------------------------------------------------------------------
106
107 gdt_size dw 0
108 gdt_base dd 0x00007e00 ;GDT的物理地址
109
110 times 510-($-$$) db 0
111 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位保护模式)
2
3 ;设置堆栈段和栈指针
4 mov ax,cs
5 mov ss,ax
6 mov sp,0x7c00
7
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],0x00
18 mov dword [bx+0x04],0x00
19
20 ;创建#1描述符,保护模式下的代码段描述符
21 mov dword [bx+0x08],0x7c0001ff
22 mov dword [bx+0x0c],0x00409800
23
24 ;创建#2描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区)
25 mov dword [bx+0x10],0x8000ffff
26 mov dword [bx+0x14],0x0040920b
27
28 ;创建#3描述符,保护模式下的堆栈段描述符
29 mov dword [bx+0x18],0x00007a00
30 mov dword [bx+0x1c],0x00409600
31
32 ;初始化描述符表寄存器GDTR
33 mov word [cs: gdt_size+0x7c00],31 ;描述符表的界限(总字节数减一)
34
35 lgdt [cs: gdt_size+0x7c00]
36
37 in al,0x92 ;南桥芯片内的端口
38 or al,0000_0010B
39 out 0x92,al ;打开A20
40
41 cli ;保护模式下中断机制尚未建立,应
42 ;禁止中断
43 mov eax,cr0
44 or eax,1
45 mov cr0,eax ;设置PE位
46
47 ;以下进入保护模式... ...
48 jmp dword 0x0008:flush ;16位的描述符选择子:32位偏移
49 ;清流水线并串行化处理器
50 [bits 32]
51
52 flush:
53 mov cx,00000000000_10_000B ;加载数据段选择子(0x10)
54 mov ds,cx
55
56 ;以下在屏幕上显示"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
69
70 ;测试push
71 mov cx,00000000000_11_000B ;加载堆栈段选择子
72 mov ss,cx
73 mov esp,0x7c00
74
75
76 push 0x80
77 push byte 0x80 ;warning: signed byte value exceeds bounds
78
79 push 0x8000
80 push word 0x8000
81
82 push 0x87654321
83 push dword 0x87654321
84
85 mov eax,0x86421357
86 push ax
87 push eax
88
89
90 push word [0x00]
91 push dword [0x00]
92
93 push ds
94 push gs
95 push es
96 push cs
97
98 ghalt:
99 hlt ;已经禁止中断,将不会被唤醒
100
101;-------------------------------------------------------------------------------
102
103 gdt_size dw 0
104 gdt_base dd 0x00007e00 ;GDT的物理地址
105
106 times 510-($-$$) db 0
107 db 0x55,0xaa

如果对上面的代码不熟悉的话,可以参考我的博文 进入保护模式(一)——《x86汇编语言:从实模式到保护模式》读书笔记12 等文章。

(2)实验报告

根据测试报告,我们可以归纳出32位保护模式下,针对NASM编译器的push指令用法:

(完)

16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16的更多相关文章

  1. ASM:《X86汇编语言-从实模式到保护模式》第10章:32位x86处理器的编程架构

    ★PART1:32位的x86处理器执行方式和架构 1. 寄存器的拓展(IA-32) 从80386开始,处理器内的寄存器从16位拓展到32位,命名其实就是在前面加上e(Extend)就好了,8个通用寄存 ...

  2. mov sreg, r/m16 在16位和32位编程中的区别

    总结于<X86汇编语言 从实模式到保护模式> 仅适用于X86系列处理器 1. 两者的区别: 例:mov ds, ax A.在指定16位编译模式下,产生的二进制码是 8E D8 B.在指定3 ...

  3. 指令集架构 x86-64 x86架构的64位拓展,向后兼容于16位及32位的x86架构

    https://zh.wikipedia.org/wiki/X86 x86泛指一系列英特尔公司用于开发处理器的指令集架构,这类处理器最早为1978年面市的"Intel 8086"C ...

  4. TestComplete 64位和32位之间的区别

    在64位系统上,有两种版本的TestComplete:32位和64位.本主题描述了TestComplete x64及其32位版本之间的区别.关于TestComplete x64启动TestComple ...

  5. 图像转置的SSE优化(支持8位、24位、32位),提速4-6倍。

    一.前言 转置操作在很多算法上都有着广泛的应用,在数学上矩阵转置更有着特殊的意义.而在图像处理上,如果说图像数据本身的转置,除了显示外,本身并无特殊含义,但是在某些情况下,确能有效的提高算法效率,比如 ...

  6. SSE图像算法优化系列四:图像转置的SSE优化(支持8位、24位、32位),提速4-6倍

    一.前言 转置操作在很多算法上都有着广泛的应用,在数学上矩阵转置更有着特殊的意义.而在图像处理上,如果说图像数据本身的转置,除了显示外,本身并无特殊含义,但是在某些情况下,确能有效的提高算法效率,比如 ...

  7. win7_oracle11g_64位连接32位PLSQL_Developer

      工具/原料 已经装好的64位Oracle数据库 window7_64位的操作系统 PLSQL_Developer 9.0以上版本(目前只有32位的):下面有下载连接! 官方的 instantcli ...

  8. 什么是64位和32位internet explorer

    什么是64位和32位internet explorer 如果您使用 64 位版本的 Internet Explorer 时,您会遇到问题,请尝试使用 32 位版本的 Internet Explorer ...

  9. 如何在WIN2008或WIN2012 64位系统安装32位SQL2000

    如何在WIN2008或WIN2012 64位系统安装32位SQL2000 在日常服务器,云服务器或VPS中,因尔特网络工程师遇到部分使用了WIN2008 或WN2012 64位系统的用户需要安装SQL ...

随机推荐

  1. 谷歌浏览器插件开发入门-官方版Helloworld详解

    目录: 需求 原理 实现步骤: 一个空的插件 一个可以设置一种背景色的插件(可以设置百度首页的背景色为绿色) 一个可以设置多种背景色的插件 需求: 插件可以改变特定网址的背景颜色. 原理: 将各种ht ...

  2. linux bash命令行基本操作

      shell shell 我们叫做壳,我们知道操作系统底层是有一个内核kernel的,内核用来实现所有上层服务,所有上层命令,上层应用所需要的一些基本功能,比如说网络连接,网络通信,比如说键盘驱动, ...

  3. PHP开发实用-阿里短信服务(Short Message Service)

    步骤 1 使用阿里云短信服务正常发短信需要 短信签名 短信模板 1申请短信签名   根据用户属性来创建符合自身属性的签名信息.企业用户需要上传相关企业资质证明,个人用户需要上传证明个人身份的证明.   ...

  4. day03.1-函数编程

    python中函数的定义: def test (x,y): "The function definitions" z = x**y return z ""&qu ...

  5. luoguP3702 [SDOI2017]序列计数

    https://www.luogu.org/problemnew/show/P3702 题目让我们在 $ [1, m] $ 从中选出 $ n $ 个数,当中要有 > $ 0 $ 个质数,和是 $ ...

  6. CF666E Forensic Examination(后缀自动机+动态线段树)

    题意 给你一个串 $S$ 以及一个字符串数组 $T[1..m]$ , $q$ 次询问,每次问 $S$ 的子串 $S[p_l..p_r]$ 在 $T[l..r]$ 中的哪个串里的出现次数最多,并输出出现 ...

  7. 【FAQ】Could not extract response: no suitable HttpMessageConverter found for respo

    原因: 1:某些必须传入的参数没传 2:返回对象的接收类型不一致

  8. ArchLinux下Shell基础学习

    首先来认识脚本语言:通常指的是命令行界面的解析器.(来自维基的解释) 第一部分:认识Shell 大家可以看到这里使用了#!/bin/sh和!/bin/bash.可是俩者有什么区别呢?下图有解释. sh ...

  9. mysql 代价

    mysql cbo cost base optimizer 基于代价,数据是一直变化的oracle8 以前是rbo rule base optimizer 基于规则, 如果sql使用了索引,必须使用索 ...

  10. php中签名公钥、私钥(SHA1withRSA签名)以及AES(AES/ECB/PKCS5Padding)加密解密详解

    由于http请求是无状态,所以我们不知道请求方到底是谁.于是就诞生了签名,接收方和请求方协商一种签名方式进行验证,来取得互相信任,进行下一步业务逻辑交流. 其中签名用得很多的就是公钥私钥,用私钥签名, ...