IA-32汇编语言笔记(4)—— 段寄存器 & 寻址方式

  • 记录汇编语言课笔记,可能有不正确的地方,欢迎指出
  • 教材《新概念汇编语言》—— 杨季文
  • 这篇文章对应书第二章 IA32处理器基本功能 2.4~2.5部分

一、段寄存器及使用

1、存储器分段

(1)物理地址相关

  • 内存(逻辑存储器):CPU能通过CPU总线直接寻址访问的存储器

    1. 这里的内存不特指计算机安装的内存条,而是指所有能被cpu直接寻址访问的存储器。包括内存条、显存和一些ROM
    2. 简单说:CPU把所有与cpu总线相连接并受其控制的的存储器,都看作一个统一的整体,对他们统一编址,这其中包括内存、显存、系统ROM等等,统称它们为存储器,存储器中按编址取一个字长的大小,就是一个存储单元
      在这里插入图片描述
  • 物理地址内存(逻辑存储器)每一个字节单元有一个唯一的地址,称为物理地址。

    1. 这个物理地址就是各存储器在CPU总线上的地址,利用它CPU可以直接访问到对应的存储空间
    2. CPU的地址线数量决定可产生的最大物理地址,n根地址线—>最大地址2^n-1,32位CPU通常有32根地址线,因此32位CPU的电脑最大只能装4G的内存条
  • 物理地址空间:所有可形成的物理地址的集合

    1. 物理地址空间大小不等于实际安装物理内存大小
    2. Intel8086有20根地址线,物理地址的范围是0到FFFFF
      Intel80386有32根地址线,物理地址的范围是0到FFFFFFFF

(2)存储器分段

  • 为了有效地管理存储器,常常把

    1. 线性的物理地址空间划分为若干逻辑段
    2. 存储空间被划分为若干存储段

    可以认为逻辑段和存储段是对应的

  • 通常,运行的程序把不同的数据存储于存储器中的不同存储段,包括

    1. 代码:要执行的指令序列(存储于代码段
    2. 数据:要处理加工的内容(存储数据段
    3. 堆栈:按先进后出规则存取的区域(存储于堆栈段
  • 逻辑段是对程序逻辑意义上的一种划分,一组完整逻辑意义的程序被划分成一段,所以逻辑段的长度是不确定的。逻辑段之间可以相连,也可以不相连甚至重叠。

  • 利用这样的分段,可以实现代码和数据的隔离,也有利于实现程序的重定位

  • 为了简化对存储器的管理,也可以把代码、数据、堆栈安排在同一个逻辑段,占用同一个存储段的不同区域

在这里插入图片描述

2、逻辑地址

(1)逻辑地址

  • 分段后,程序中使用的某个存储单元总是属于某个段,所以可以 某某段 某某单元方式表示存储单元
  • 逻辑地址程序中用于表示存储单元的地址
    1. 由于采用分段存储管理方式,程序中使用的逻辑地址是二维的,第一维给出某某段,第二维给出段内的某某单元。
    2. 二维的逻辑地址可以表示为:段号∶段内地址
    3. (段内)偏移存储单元的物理地址与所在段起始地址的差值。这个差值恰好是段内地址,因此二维的逻辑地址又可以表示为:段号∶偏移
    4. 实方式下,段号是段值
      保护方式下,段号是段选择子
    5. 有效地址/偏移地址:逻辑地址中的偏移称作有效地址或偏移地址,汇编程序中,不同的数据往往固定存放在不同的段,有唯一对应的段寄存器,因而这个地址用的最多。比如指示代码的EIP、指示堆栈位置的ESP、EBP等都是存储的有效地址

(2)逻辑地址转为物理地址

  • 物理地址 = 段起始地址 + 偏移

  • 获得物理地址的过程:

    1. 由段号得到段起始地址
    2. 加上偏移
  • 对于IA32

    1. 保护方式下:物理地址32位,段起始地址32位,偏移32位
    2. 实方式下:物理地址20位,段起始地址20位,偏移16位
    3. 可以参考:我理解的逻辑地址、线性地址、物理地址和虚拟地址(补充完整了)

    对于8086:

    1. 只有实方式:物理地址20位,段起始地址20位,偏移16位
    2. 可以参考:逻辑地址、线性地址和物理地址之间的转换
  • 如果整个程序只有一个段,则二维逻辑地址退化为一维。段起始地址完全相同,偏移决定一切。如果用VS2010来编写嵌入汇编程序,那么就是这种情况,只考虑偏移即可

(3)三种地址小结

地址说明
物理地址各存储器在CPU总线上的地址,是实际上可以直接访问到存储器的地址
逻辑地址段号∶偏移 方式描述的地址,需要进行一些计算才能转换为物理地址
有效地址就是逻辑地址中的偏移量

3、段寄存器

  • 作用:段寄存器存放着当前使用的逻辑地址中的段号(段值/段选择子),用于获得段起始地址。
  • 段寄存器是16位的,在实方式下存储16位段值;在保护方式下存储16位段选择子
    在这里插入图片描述
  • 若代码段、堆栈段、数据段在同一个存储段,则CS、DS、SS给出相同的段起始地址
  • 若段寄存器给出的段起始地址为0,则偏移相当于物理地址

二、寻址方式

  • 寻址方式表示指令中操作数所在的方法
  • CPU常用的三种寻址方式:立即寻址寄存器寻址存储器寻址,此外还有固定寻址和I/O端口寻址
  • 以上三种方式都是对于偏移地址的不同描述,对于段基址:
    1. 基址寄存器是EBP或ESP时,默认的段寄存器是SS
    2. 否则,默认的段寄存器是DS

1、立即寻址

  • 立即寻址方式操作数本身就包含在指令中,直接作为指令的一部分给出,这样的操作数称为立即数

  • 注意:

    1. 只有源操作数可以使用立即寻址方式
    2. 如果立即数由多个字节构成,么在作为指令的一部分存储时,也采用“高高低低”规则。
  • 由于立即寻址方式的操作数是立即数,包含在指令中,所以执行指令时,不需要再到存储器中去取该操作数了。

    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内部寄存器,指令指定寄存器

  • 适用范围:

    1. 8个32位寄存器:EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP
    2. 8个16位寄存器:AX,BX,CX,DX,BP,SI,DI,SP
    3. 8个8位寄存器:AH,AL,BH,BL,CH,CL,DH,DL
  • 操作数在寄存器中,不需要访问存储器取得操作数,这样指令执行速度较快

  • 示例:

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位的偏移
  • 有多种存储器寻址方式
    1. 直接寻址
    2. 寄存器间接
    3. 寄存器相对
    4. 基址加变址
    5. 通用方法
  • 说明:
    1. 存储器操作数尺寸是字节/字/双字
    2. 默认指令中的寄存器操作数的尺寸决定了存储器操作数的尺寸;但也可以显式指定存储器操作数尺寸
修饰符功能
WORD PTR指定尺寸为“字”
BYTE PTR指定尺寸为“字节”
DWORD PTR指定尺寸为“双字”

(1)直接寻址方式

  • 直接寻址方式:操作数在存储器中,指令直接包含操作数所在的存储单元的有效地址
  • 示例:
MOV   ECX, [95480H]       //源操作数采用直接寻址
MOV   [9547CH], DX        //目的操作数采用直接寻址
ADD   BL, [95478H]        //源操作数采用直接寻址
  • 即寻址和直接寻址的区别:

    1. 直接寻址中十六进制数表示地址,要到此地址取出操作数;立即寻址中表示操作数
    2. 直接寻址的地址要写在方括号[]
  • 注意:

    1. 直接寻址时,[]给出的是被取出数据的最低地址
    2. 按 “高高低低” 规则取出数据
      在这里插入图片描述

(2)寄存器间接寻址方式

  • 寄存器间接寻址操作数在存储器中,由八个32位通用寄存器之一给出操作数所在存储单元有效地址。

  • 寄存器间接寻址和寄存器寻址的区别:

    1. 寄存器间接的Reg名称出现在方括号[]
    2. 寄存器间的Reg中存储的是操作数所在地址;寄存器寻址的Reg存储的是操作数
  • 注意:

    1. 操作数地址必须来自八个32位通用寄存器之一
  • 示例:

MOV   EAX, [ESI]    //源操作数寄存器间接寻址,ESI给出有效地址
MOV   [EDI], CL     //目的操作数寄存器间接寻址,EDI给出有效地址
SUB   DX, [EBX]     //源操作数寄存器间接寻址,EBX给出有效地址

(3)通用方式

  • 存储单元的有效地址可以由三部分内容相加构成

    1. 一个32位基地址寄存器
    2. 一个可乘上比例因子1/2/4/8的32位变址寄存器
    3. 一个8/16/32位位移量

    PS:可省去任意两部分
    在这里插入图片描述

  • 注意:

    1. 变址寄存器乘上的比例因子取值只能是1/2/4/8之一
    2. 三本分中可以任意省略。省略后的寻址方式又有不同名称,但都属于通用方式,前面提到的直接寻址方式和寄存器间接寻址方式也是属于通用方式
  • 示意图:
    在这里插入图片描述

  • 示例:

  1. 寄存器相对寻址方式[寄存器名+偏移]
MOV   EAX, [EBX+12H]      ;源操作数有效地址是EBX值加上12H
MOV   [ESI-4], AL         ;目的操作数有效地址是ESI值减去4
ADD   DX, [ECX+5328H]     ;源操作数有效地址是ECX值加上5328H
  1. 基址+变址寻址[寄存器名+寄存器名]
MOV   EAX, [EBX+ESI]      ;源操作数有效地址是EBX值加上ESI值
SUB   [ECX+EDI], AL       ;目的操作数有效地址是ECX值加上EDI值
XCHG  [EBX+ESI], DX       ;目的操作数有效地址是EBX值加上ESI值
  1. 基址+带放大因子的变址寻址[寄存器名+寄存器名*放大因子+偏移]
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] 这样的方法从内存取值

    1. address是地址尾,也就是取出值的最低字节地址
    2. 存入寄存器时,低地址对应寄存器低位;高地址对应高位(“高高低低”规则
    3. 初始化好的全局变量,占用内存空间是连续的
  • 使用尺寸修饰符的示例:
    在这里插入图片描述

  • 关于存储器寻址的说明

    1. 缺省段寄存器
      (1)如果基址寄存器不是EBP/ESP,缺省引用的段寄存器为DS
      (2)如果基址寄存器是EBP/ESP,缺省引用的段寄存器为SS
      (3)如果EBP作为变址寄存器(ESP不能做变址寄存器),缺省引用的段寄存器为DS
    2. 有效地址
      (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 

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云端FFF

所有博文免费阅读,求打赏鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值