注意:以下这黑带为上图的文字内容,复制到写字板就可以看到。完整的文档无法附加到文章,就不上传了。
深入x86的内存寻址 本文旨在全面解决寻址方面的疑问,解决一些教材对寻址问题解说不够全面的问题。包含以下主要内容: 4个数据寄存器: EAX,Extended Accumulator Register 累加寄存器; EBX,Extended Base Register 基址寄存器; ECX,Extended Counter Register 计数寄存器; EDX,Extended Data Register 数据寄存器; 2个变址寄存器: ESI,Extended Source Index Register 源索引寄存器; EDI,Extended Destination Index Register 目标索引寄存器; 2个指针寄存器: ESP,Extended Stack Pointer Register 堆栈指针寄存器; EBP,Extended Stack-frame Base Pointer 堆栈基址指针寄存器; 4个段寄存器+段寄存器与内存模型: CS,Code Segment Register 代码段寄存器; DS,Data Segment Register 数据段寄存器; ES,Extra Segment Register 额外数据段寄存器; SS,Stack Segment Register 堆栈寄存器; 1个指令指针寄存器: EIP,Execution Instruction Pointer Register 1个标志寄存器: EFlags,Executioin Status Flags Register 5种寻址方式+内存寻址的组合: Immediate Addressing 立即数寻址 Register Addressing 寄存器寻址 Direct Addressing 直接寻址 Register Indirect Addressing 寄存器间接寻址 I/O Port Addressing I/O 端口寻址 保护模式+内存模型+特权等级等 一、数据寄存器 数据寄存器主要用来保存操作数和运算结果等信息,从而节省读取操作数所需占用总线和访问 存储器的时间。32位CPU有4个32位的通用寄存器EAX、EBX、ECX和EDX。对低16位数据的存取,不会影响高16位的数据。这些低16位寄存 器分别命名为:AX、BX、CX和DX,它和 8086 的寄存器相一致。 4个16位寄存器又可分割成8个独立的8位寄存器(AX:AH-AL、BX:BH-BL、CX:CH-CL、DX:DH-DL),每个寄存器都有自己的名称,可独立存取。AX和AL通常称为累加器(Accumulator),用累加器进行的操作可能需要更少时间。累加器可用于乘、 除、输入/输出等操作,它们的使用频率很高; 寄存器BX称为基地址寄存器(Base Register),它可作为存储器指针来使用,此时将使用堆栈段来寻址数据; CX称为计数寄存器(Count Register),在循环和字符串操作时,要用它来控制循环次数;在位操作 中,当移多位时,要用CL来指明移位的位数;DX称为数据寄存器(Data Register),在进行乘、除运算时,它可作为默认的操作数参与运算,也 可用于存放I/O的端口地址。在16位CPU中,AX、BX、CX和DX不能作为基址和变址寄存器来存放存储单元的地址,但在32位CPU中,其32位寄 存器EAX、EBX、ECX和EDX不仅可传送数据、暂存数据保存算术逻辑运算结果,而且也可作为指针寄存器,所以,这些32位寄存器更具有通用性。 二、变址寄存器 2个32位通用寄存器ESI和EDI为变址寄存器,又称为索引寄存器。其低16位对应先前CPU中的SI和DI,对低16位数据的存取,不影响高16位的数据。(E)SI、(E)DI 统称为变址寄存器(Index Register),它们主要用于存放存储单元在段内的偏移量,使用(E)SI时表示相对堆栈段的偏移。用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。变址寄存器不可分割成8位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。它们可作一般的存储器指针使用。在字符串操作指令的执行过程中, 对它们有特定的要求,而且还具有特殊的功能。 三、指针寄存器 2个 32-bit 通用指针寄存器 EBP 和 ESP 分别用作基地指针和堆栈指针。其低16位对应 8086 中的BP和SP,对低16位数据的存取,不影响高16位的数据。(E)BP、(E)SP 统称为指针寄存器(Pointer Register),主要用于存放堆栈内存储单元的偏移量,用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。指针寄存器不可分割成8位寄存器。作为通用寄存器,也可存储算术逻辑运算的操作数和运算结果。它们主要用于访问堆栈内的存储单元,并且规定: BP为基指针(Base Pointer)寄存器,通过它减去一定的偏移值,来访问栈中的元素; SP为堆栈指针(Stack Pointer)寄存器,它始终指向栈顶。 虽然说始终指向栈顶,但并不是指它始终保持不变。因堆栈的增长方向是从高地址向低地址的,所以 PUSH 进栈时,数据就存入栈顶,然后 SP 按数据长度自减;POP 出栈时,反向操作,先按数据长度自增,再读出数据。 四、段寄存器与寻址 Intel 8086/8088 CPU File:KL Intel D8086.jpg 分段内存模型 1978年,Intel 8086 CPU 刚研制出来,这种16位CPU芯片使用40针的DIP封装。地址线只有20条,设计时认为地址线只要20条就够了,这样可以寻址 220=1MB 的内存。对当时,1MB内存就像现在了1TB的概念一样,也就是这个设计导致后来CUP升级过程中,为了兼容而带来软件开发的各种问题,其中就有 A20 Gate。 这时就引入了内存分段的概念了,因为1个16位的寄存器只能访问到216=64KB 的内存,为此就要使用一个额外的偏移值,这样就引入了16位的 CS、DS、ES、SS 段寄存器组合一个偏移值来寻址1MB内存的概念,偏移值也称为偏移地址 Offset Address。使用 CS 来访问代码段,DS 来访问数据段,ES 留给程序来访问额外数据段,SS 来访问堆栈。内存从第一个字节到最后一个字节都有一个唯一的号码连续的地址,称作内存的物理地址 Physical Address、有效地址 Effective Address。 引入分段概念后,CS、DS、ES、SS 就存储各个段的首地址的高16位,也称作段基址,偏移值则作为低16位值相加。偏移值是16-bit数据,最大值也只有65535,因此也可以认为每个段为64KB。用冒号来表示拼接偏移量, SEGMEMT:OFFSET 这样就表示了一个20位的有效地址,也就是1MB的寻址空间。计算方法,段基址左移4位+16位偏移=20位地址,如下: SEGMENT<<4 + OFFSET 注意,这种方式能够表示的最大内存为: FFFF:FFFF=FFFF0+FFFF=10FFEF=1MB+64KB-16Bytes 8086 时代的内存分区 因此,这种方法不只能寻址1M,还多余出近64KB,这部分被称做高端内存区High Memory Area (HMA),也就有了右边所示的内存分段机制模型 Segmentation Memory Model (SMM)。但8086/8088只有20位地址线,如果访问 100000~10FFEF 这部分内存,则必须有第21根地址线。CPU寻址时,系统并不认为其访问越界而产生异常,而是根据20根地址线来进行寻址,因此系统计算有效地址的时候对1M求模的方式进行的,这种技术被称为折回 Wrap-around。由于开来,对不同的内存区就形成了不同称谓,0~640KB 这部分内存就称为传统内存 Conventional Memory,用一个16-bit的寄存器就可以完成寻址;而640KB~1MB这部分内存就称为上位内存 Upper Memory Area (UMA);而高于HMA的部分就总称为扩展内存 Extended Memory。在较新版本的DOS系统是可以通过软件控制,将HMA当作传统内存使用的,以扩大程序的内存空间。 ntel 80286 CPU 1982年,Intel 80286 CPU被研制出来,共有24条地址线,寻址16MB。这种CPU引入了虚拟内存及内存保护技术,并对内存的分段概念进行了修改,而 8086 的运行方式被称为实地址模式 Real Address Mode。80286 兼容了实模式,同时引入了一种称为保护模式 Protected Mode,全称 Protected Virtual Address Mode。保护模式下,CPU运行情况要复杂得多,它装入段寄存器的不再是段值,而是称为“选择子”Selector 的某个值。我觉得“选择子”叫法挺怪异的,更情愿称之为“选择器”。保护模式下的内存寻址比较复杂,在后面的保护模式进行讲述。保持兼容本是好事,这样8086的程序可以直接在80286上运行。为了兼容 8086 内存分段机制的实模式,80286 可以进入实模式运行。问题是 80286 拥有24根地址线而不是20根,因此 80286 的指针就可以指向 100000 至 10FFEF 处的内存地址,这就是将近 64KB 的高位内存 HMA。也可以认为这是芯片的一个BUG:如果程序访问HMA内存,因为有24条地址线,系统将真实地访问这块内存,而不是像8086一样进行折回访问1MB的区域。 话说当其时,IBM作为全球PC生产厂商的龙头,它的PC市场份额占据了全球50%以上。作为利益的受损方,IBM想到一个解决方法。使用键盘控制器 8042 芯片来处理A20 Gate,利用控制器上剩余的一些输出线来管理第 A20 根地址线,即第21根地址线,这个功能就被称为A20 Gate:开启A20 Gate,A20 根地址线有效,则程序可以访问HMA内存区;关闭A20Gate,则程序访问HMA时,系统遵循8086的折回方式访问1MB寻址空间。IBM PC兼容机默认的 A20 Gate 是被禁止的。在当时似乎只能这样的方法来解决A20 Gate,这就是硬件 bug 的 Hack 行为,毕竟 A20 Gate 和键盘操作是没没有任何关系的。新型PC上引入了 BIOS 芯片,这样 A20 Gate 功能就集成到了 BIOS上面。因此,在保护模式下,尽管系统有24条地址线,但开启 A20 Gate 就意味着损失了一条,就只能访问内存的奇数段(2N+1)MB,寻址空间也只有16MB的一半。在BIOS中,这个功能就是Fast A20中断,INT 15h,AX=2401h。 Intel 80386 CPU 1985年10月,Intel 80386 32-bit CPU正式上市,全功能的386芯片到1986年上市。它更强大了,在硬件上有了一定的虚拟能力,此时兼容 8086 实模式的运行状态就是通过硬件虚拟技术实现的ÿ