- 记录汇编语言课笔记,可能有不正确的地方,欢迎指出
- 教材《新概念汇编语言》—— 杨季文
- 这篇文章对应书第二章 IA32处理器基本功能 2.4~2.5部分
文章目录
一、段寄存器及使用
1、存储器分段
(1)物理地址相关
-
内存(逻辑存储器)
:CPU能通过CPU总线直接寻址访问的存储器- 这里的内存不特指计算机安装的内存条,而是指所有能被cpu直接寻址访问的存储器。包括内存条、显存和一些ROM
- 简单说:CPU把所有与cpu总线相连接并受其控制的的存储器,都看作一个统一的整体,对他们统一编址,这其中包括内存、显存、系统ROM等等,统称它们为
存储器
,存储器中按编址取一个字长的大小,就是一个存储单元
。
-
物理地址
:内存(逻辑存储器)
每一个字节单元有一个唯一的地址,称为物理地址。- 这个物理地址就是各存储器在CPU总线上的地址,利用它CPU可以直接访问到对应的存储空间
- CPU的地址线数量决定可产生的最大物理地址,n根地址线—>最大地址2^n-1,32位CPU通常有32根地址线,因此32位CPU的电脑最大只能装4G的内存条
-
物理地址空间
:所有可形成的物理地址
的集合- 物理地址空间大小不等于实际安装物理内存大小
- Intel8086有20根地址线,物理地址的范围是0到FFFFF
Intel80386有32根地址线,物理地址的范围是0到FFFFFFFF
(2)存储器分段
-
为了有效地管理存储器,常常把
- 线性的物理地址空间划分为若干逻辑段
- 存储空间被划分为若干存储段
可以认为逻辑段和存储段是对应的。
-
通常,运行的程序把不同的数据存储于存储器中的不同存储段,包括
代码
:要执行的指令序列(存储于代码段
)数据
:要处理加工的内容(存储数据段
)堆栈
:按先进后出规则存取的区域(存储于堆栈段
)
-
逻辑段是对程序逻辑意义上的一种划分,一组完整逻辑意义的程序被划分成一段,所以逻辑段的长度是不确定的。逻辑段之间可以相连,也可以不相连甚至重叠。
-
利用这样的分段,可以实现代码和数据的隔离,也有利于实现程序的重定位
-
为了简化对存储器的管理,也可以把代码、数据、堆栈安排在同一个逻辑段,占用同一个存储段的不同区域
2、逻辑地址
(1)逻辑地址
- 分段后,程序中使用的某个存储单元总是属于某个段,所以可以
某某段
某某单元
方式表示存储单元 逻辑地址
:程序中用于表示存储单元的地址- 由于采用分段存储管理方式,程序中使用的逻辑地址是二维的,第一维给出某某段,第二维给出段内的某某单元。
- 二维的逻辑地址可以表示为:
段号∶段内地址
(段内)偏移
:存储单元的物理地址与所在段起始地址的差值。这个差值恰好是段内地址,因此二维的逻辑地址又可以表示为:段号∶偏移
- 实方式下,段号是
段值
保护方式下,段号是段选择子
有效地址/偏移地址
:逻辑地址中的偏移称作有效地址或偏移地址,汇编程序中,不同的数据往往固定存放在不同的段,有唯一对应的段寄存器,因而这个地址用的最多。比如指示代码的EIP、指示堆栈位置的ESP、EBP等都是存储的有效地址
(2)逻辑地址转为物理地址
-
物理地址 = 段起始地址 + 偏移
-
获得物理地址的过程:
- 由段号得到段起始地址
- 加上偏移
-
对于IA32
- 保护方式下:物理地址32位,段起始地址32位,偏移32位
- 实方式下:物理地址20位,段起始地址20位,偏移16位
- 可以参考:我理解的逻辑地址、线性地址、物理地址和虚拟地址(补充完整了)
对于8086:
- 只有实方式:物理地址20位,段起始地址20位,偏移16位
- 可以参考:逻辑地址、线性地址和物理地址之间的转换
-
如果整个程序只有一个段,则二维逻辑地址退化为一维。段起始地址完全相同,偏移决定一切。如果用VS2010来编写嵌入汇编程序,那么就是这种情况,只考虑偏移即可
(3)三种地址小结
地址 | 说明 |
---|---|
物理地址 | 各存储器在CPU总线上的地址,是实际上可以直接访问到存储器的地址 |
逻辑地址 | 段号∶偏移 方式描述的地址,需要进行一些计算才能转换为物理地址 |
有效地址 | 就是逻辑地址中的偏移量 |
3、段寄存器
- 作用:段寄存器存放着当前使用的逻辑地址中的段号(段值/段选择子),用于获得段起始地址。
- 段寄存器是16位的,在实方式下存储16位段值;在保护方式下存储16位段选择子
- 若代码段、堆栈段、数据段在同一个存储段,则CS、DS、SS给出相同的段起始地址
- 若段寄存器给出的段起始地址为0,则偏移相当于物理地址
二、寻址方式
寻址方式
:表示指令中操作数所在的方法- CPU常用的三种寻址方式:
立即寻址
、寄存器寻址
、存储器寻址
,此外还有固定寻址和I/O端口寻址 - 以上三种方式都是对于偏移地址的不同描述,对于段基址:
- 基址寄存器是EBP或ESP时,默认的段寄存器是SS
- 否则,默认的段寄存器是DS
1、立即寻址
-
立即寻址方式
:操作数本身就包含在指令中,直接作为指令的一部分给出,这样的操作数称为立即数
-
注意:
- 只有源操作数可以使用立即寻址方式
- 如果立即数由多个字节构成,么在作为指令的一部分存储时,也采用“高高低低”规则。
-
由于立即寻址方式的操作数是立即数,包含在指令中,所以执行指令时,不需要再到存储器中去取该操作数了。
- 立即寻址,会自动配合目的操作数尺寸,不需要dword ptr等参数
-
示例:
//以下源操作数使用立即寻址方式
MOV EAX, 12345678H //给EAX寄存器赋初值
ADD BX, 1234H //给BX寄存器加上值1234H
SUB CL, 2 //从CL寄存器减去值2
//以下源操作数值一致,但是尺寸不同
MOV EDX, 1 //源操作数是32位
MOV DX, 1 //源操作数是16位
MOV DL, 1 //源操作数是8位
2、寄存器寻址
-
寄存器寻址方式
:操作数存在CPU内部寄存器,指令指定寄存器 -
适用范围:
- 8个32位寄存器:
EAX
,EBX
,ECX
,EDX
,ESI
,EDI
,EBP
,ESP
- 8个16位寄存器:
AX
,BX
,CX
,DX
,BP
,SI
,DI
,SP
- 8个8位寄存器:
AH
,AL
,BH
,BL
,CH
,CL
,DH
,DL
- 8个32位寄存器:
-
操作数在寄存器中,不需要访问存储器取得操作数,这样指令执行速度较快
-
示例:
MOV EBP, ESP //把ESP之值送到EBP
ADD EAX, EDX //把EAX之值与EDX之值相加,结果送到EAX
SUB DI, BX //把DI之值减去BX之值,结果送到DI
XCHG AH, DH //交换AH与DH之值
3、32位的存储器寻址方式
存储器寻址方式
:给出存储单元偏移的寻址方式(在某个段内,给出存储单元的偏移即可找到它)存储器操作数
:在指令中[xxx]
意味着从xxx
地址取数据,称这个[xxx]
为存储器操作数。有效地址
:要访问的存储单元的段内偏移。在32位的存储器寻址方式下,存储单元有效地址可达32位- 采用32位的存储器寻址方式,能够给出32位的偏移
- 有多种存储器寻址方式
- 直接寻址
- 寄存器间接
- 寄存器相对
- 基址加变址
- 通用方法
- 说明:
- 存储器操作数尺寸是字节/字/双字
- 默认指令中的寄存器操作数的尺寸决定了存储器操作数的尺寸;但也可以显式指定存储器操作数尺寸
修饰符 | 功能 |
---|---|
WORD PTR | 指定尺寸为“字” |
BYTE PTR | 指定尺寸为“字节” |
DWORD PTR | 指定尺寸为“双字” |
(1)直接寻址方式
直接寻址方式
:操作数在存储器中,指令直接包含操作数所在的存储单元的有效地址。- 示例:
MOV ECX, [95480H] //源操作数采用直接寻址
MOV [9547CH], DX //目的操作数采用直接寻址
ADD BL, [95478H] //源操作数采用直接寻址
-
即寻址和直接寻址的区别:
- 直接寻址中十六进制数表示地址,要到此地址取出操作数;立即寻址中表示操作数
- 直接寻址的地址要写在方括号
[]
中
-
注意:
- 直接寻址时,
[]
给出的是被取出数据的最低地址 - 按 “高高低低” 规则取出数据
- 直接寻址时,
(2)寄存器间接寻址方式
-
寄存器间接寻址
:操作数在存储器中,由八个32位通用寄存器之一给出操作数所在存储单元有效地址。 -
寄存器间接寻址和寄存器寻址的区别:
- 寄存器间接的Reg名称出现在方括号
[]
中 - 寄存器间的Reg中存储的是操作数所在地址;寄存器寻址的Reg存储的是操作数
- 寄存器间接的Reg名称出现在方括号
-
注意:
- 操作数地址必须来自八个32位通用寄存器之一
-
示例:
MOV EAX, [ESI] //源操作数寄存器间接寻址,ESI给出有效地址
MOV [EDI], CL //目的操作数寄存器间接寻址,EDI给出有效地址
SUB DX, [EBX] //源操作数寄存器间接寻址,EBX给出有效地址
(3)通用方式
-
存储单元的有效地址可以由三部分内容相加构成
- 一个32位基地址寄存器
- 一个可乘上比例因子
1/2/4/8
的32位变址寄存器 - 一个8/16/32位位移量
PS:可省去任意两部分
-
注意:
- 变址寄存器乘上的比例因子取值只能是
1/2/4/8
之一 - 三本分中可以任意省略。省略后的寻址方式又有不同名称,但都属于通用方式,前面提到的直接寻址方式和寄存器间接寻址方式也是属于通用方式
- 变址寄存器乘上的比例因子取值只能是
-
示意图:
-
示例:
寄存器相对寻址方式
:[寄存器名+偏移]
MOV EAX, [EBX+12H] ;源操作数有效地址是EBX值加上12H
MOV [ESI-4], AL ;目的操作数有效地址是ESI值减去4
ADD DX, [ECX+5328H] ;源操作数有效地址是ECX值加上5328H
基址+变址寻址
:[寄存器名+寄存器名]
MOV EAX, [EBX+ESI] ;源操作数有效地址是EBX值加上ESI值
SUB [ECX+EDI], AL ;目的操作数有效地址是ECX值加上EDI值
XCHG [EBX+ESI], DX ;目的操作数有效地址是EBX值加上ESI值
基址+带放大因子的变址寻址
:[寄存器名+寄存器名*放大因子+偏移]
MOV EAX, [ECX+EBX*4] ;EBX作为变址寄存器,放大因子是4
MOV [EAX+ECX*2], DL ;ECX作为变址寄存器,放大因子是2
ADD EAX, [EBX+ESI*8] ;ESI作为变址寄存器,放大因子是8
SUB ECX, [EDX+EAX-4] ;EAX作为变址寄存器,放大因子是1
MOV EBX, [EDI+EAX*4+300H] ;EAX作为变址寄存器,放大因子是4
(4)补充
-
用 [address] 这样的方法从内存取值
- address是地址尾,也就是取出值的最低字节地址
- 存入寄存器时,低地址对应寄存器低位;高地址对应高位(“高高低低”规则)
- 初始化好的全局变量,占用内存空间是连续的
-
使用尺寸修饰符的示例:
-
关于存储器寻址的说明
- 缺省段寄存器
(1)如果基址寄存器不是EBP/ESP,缺省引用的段寄存器为DS
(2)如果基址寄存器是EBP/ESP,缺省引用的段寄存器为SS
(3)如果EBP作为变址寄存器(ESP不能做变址寄存器),缺省引用的段寄存器为DS - 有效地址
(1)无论存储器寻址方式具体是哪种,如果基址寄存器、变址 寄存器、比例因子、位移量这些算出来超过32位,只有低32位有效
- 缺省段寄存器
三、取有效地址指令LEA
名称 | LEA(取有效地址指令) |
---|---|
格式 | LEA REG,OPRD |
动作 | 把操作数OPRD的有效地址传送到REG |
合法值 | OPRD:存储器操作数; |
REG:16/32位通用寄存器 | |
注意 | 此指令不影响标志寄存器 |
此指令是取地址,和mov有本质区别 |
- 示例1:基本操作
MOV EDI, 51234H //EDI=00051234H
MOV EAX, 6 //EAX=00000006H
LEA ESI, [EDI+EAX] //ESI=0005123AH
LEA ECX, [EAX*4] //ECX=00000018H
LEA EBX, [EDI+EAX*4+300H] //EBX=0005154CH
- 示例2:指针的实现
#include <stdio.h>
char chx, chy; //全局字符变量
int main( )
{
char *p1, *p2; //两字符型指针变量
//嵌入汇编代码之一
_asm {
LEA EAX, chx //取变量chx的存储单元有效地址
MOV p1, EAX //送到指针变量p1, p1 = &chx
LEA EAX, chy //取变量chy的存储单元有效地址
MOV p2, EAX //送到指针变量p2, p2 = &chy
}
printf("Input:"); //提示
scanf("%c", p1); //键盘输入一个字符
//嵌入汇编代码之二
_asm {
MOV ESI, p1 //取回变量chx的有效地址
MOV EDI, p2 //取回变量chy的有效地址
MOV AL, [ESI] //取变量chx之值
MOV [EDI], AL //送到变量chy中
}
printf("ASCII:%02XH\n", *p2); //显示之
return 0;
}
- 示例三:取一个double数据
#include <stdio.h>
int iarr[5] = {55, 87, -23, 89, 126};
double darr[5] = {9.8, 2.77, 3.1415926, 1.414, 1.73278};
int main()
{ int ival; //整型变量
double dval; //双精度浮点
//嵌入汇编
_asm {
LEA EBX, iarr //把整型数组首元素的有效地址送EBX
MOV ECX, 3
MOV EDX, [EBX+ECX*4] //取出iarr的第4个元素
MOV ival, EDX
;
LEA ESI, darr //把浮点数组首元素的有效地址送ESI
LEA EDI, dval //把变量dval的有效地址送EDI
MOV ECX, 2
MOV EAX, [ESI+ECX*8] //取darr的第3个元素的低双字
MOV EDX, [ESI+ECX*8+4] //取darr的第3个元素的高双字
MOV [EDI], EAX //保存低双字
MOV [EDI+4], EDX //保存高双字
}
printf("iVAL=%d\n",ival); //显示为iVAL=89
printf("dVAL=%.8f\n",dval); //显示为dVAL=3.14159260
return 0;
}
- 示例四:妙用LEA和存储器取值,进行多项式计算
//待翻译的c函数
int _fastcall cf212(int x, int y) //由寄存器传参数
{
return ( 3 * x + 7 * y + 200 );
}
//翻译成汇编后的核心代码(ECX传递x,EDX传递y)
lea eax, DWORD PTR [ecx+ecx*2] //eax=3*x
lea ecx, DWORD PTR [edx*8] //ecx=8*y
sub ecx, edx //ecx=7*y
lea eax, DWORD PTR [eax+ecx+200] //eax=3*x+7*y+200
ret