Intel X86 CPU寻址方式

Posted 1mpanda

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Intel X86 CPU寻址方式相关的知识,希望对你有一定的参考价值。

X86系列是指Intel从16位微处理器8086开始的整个CPU芯片系列,系列中的每种型号都保持与以前的各种型号的兼容。在x86系列中,8086和8088是16位处理器,从80386开始为32位处理器,80286则是该系列从8088到80386,是从16位到32位过渡的过程。80286虽然是16位处理器,但是在寻址方式上开始从"实地址模式"到"保护模式"的过渡。一个CPU是16位或32位时,指的是处理器中算术逻辑单元ALU的宽度。系统总线中的数据线部分,称为数据总线,通常与ALU具有相同的宽度。地址总线宽度通常与数据总线是一致的。这是因为从程序设计的角度,一个地址也就是一个指针,最好是与一个整数的长度一致的。但是如果从8位CPU寻址能力的角度来考虑,实际上是不现实的,因为一个8位的地址只能用来寻访256个不同的地址单元。所以一般8位CPU的地址总线都是16位的。造成了8位CPU在内部结构上的不均匀性。Intel使用分段的方式来解决ALU的宽度与内存地址不同的问题。
Intel在8086CPU中设置了四个段寄存器:CS、DS、SS和ES,分别用于可执行代码即指令、数据、堆栈和其他。每个段寄存器都是16位的,对应于地址总线中的高16位。每条访内指令中的内部地址都是16位的,但是在送上地址总线之前都在CPU内部自动地与某个段寄存器中的内容相加,形成一个20位的实际地址。这样就实现了从16位内部地址到20位实际地址的转换。段寄存器中的内容对应于20位地址总线中的高16位,所以在相加时实际上是拿内部地址中的高12位与段寄存器中的16位相加,而内部地址中的低4位保留不变。这个方法与操作系统的段式内存管理相似,差别是没有地址空间的保护机制。
对于每个段寄存器的内容确定的基地址,一个进程总是能够访问从此开始的64k字节的连续地址空间,而无法加以限制。通过改变段寄存器的内容,一个进程就可以任意访问内存中的任何一个单元,而不受限制。由于8086的这种内存寻址方式缺乏对内存空间的保护,所以为了区别后来出现的保护模式,就称为实地址模式。
因此,在实地址模式下,无法构建操作系统。
80386是个32位CPU,也就是它的ALU数据总线是32位的,大部分的地址总线宽度是与数据线一致的。当地址总线宽度达到32位时,其寻址能力达到4G。
Intel的保护模式构建在段寄存器的基础上,并且保留段寄存器为16位;且又增加两个段寄存器FS和GS。
为了实现保护模式,用段寄存器来确定一个基地址是不足的,至少还需要有一个地址段的长度和访问权限之类等信息。因此,需要一个数据结构,并非单纯的基地址。
在保护模式下改变段寄存器的功能,使其从一个单纯的基地址变成指向一个数据结构的指针。这时,当一条内存指令发出一个内存地址时,CPU可以计算出实际上放在数据总线的地址:
1.根据指令的性质来确定应该使用哪一个段寄存器。例如转移指令中的地址在代码段,而取数据指令中的地址在数据段。与实地址模式相同。
2.根据段寄存器的内容,找到相应的地址段描述结构
3.从地址段描述结构中得到基地址
4.将指令发出的地址作为位移,与段描述结构中规定的段长度相比,查看是否越界。
5.根据指令的性质和端描述符中的访问权限来确定是否越权。
6.将指令中发出的地址作为位移,与基地址相加得出实际的物理地址。
虽然段描述结构存储在内存中,在实际使用时将其装载入CPU中的一组特殊结构中,而CPU在运行时则使用其在CPU中的特殊结构。从保护的角度,在由内部地址转换为物理地址的过程中,必须要在某个环节上对访问权限进行对比,防止用户程序非法访问其他进程的空间或系统空间。
80836CPU的段式内存管理机制:
在80836CPU中增设两个寄存器:一个是全局性的段描述表寄存器GDTR和局部性的段描述表寄存器LDTR,分别可以用来指向存储在内存中的一个段描述结构数组或段描述表。访问这两个寄存器的专用指令即为特权指令。
在此基础上,段寄存器的高13位用作访问段描述表中具体描述结构的下标(index)
技术分享图片

 

技术分享图片
GDTR或LDTR中的段描述表指针和段寄存器中给出的下标结合在一起,才决定了具体的段描述表项在内存中的什么地方。也就是将段寄存器内容的低3位屏蔽掉以后与GDTR和LDTR中的基地址相加得到描述表项的起始地址。每个段描述表项的大小是8个字节,每个描述表项含有段的基地址和段的大小,再加上其他一些信息。
技术分享图片
技术分享图片
结构中的B31~B24和B23~B16分别是基地址的bit24~bit31和bit16~bit23。而L19~L16和L15~L0则为长度(limit)的bit16~bit19和bit0~bit15。其中DPL是个2位的位段,而type是一个4位的位段。
技术分享图片
 技术分享图片

 

整个段描述结构:
typedef    struct{
    unsigned int base24_31:8;    //基地址的最高8位
    unsigned int g:1;            //granularity,表段的长度单位,0表示字节,1表示4KB
    unsigned int d_b:1;            //default operation size,存取方式,0=16位,1表示32位
    unsigned int unused:1;        //固定设置为0
    unsigned int avl:1;            //avalable,可供系统软件使用
    unsigned int seg_limit_16_19:4;        //段长度的最高4位
    unsigned int p:1;            //segment present,为0表示该段的内容不在内存中
    unsigned int dpl:2;            //Descriptor privilege level,访问本段所需的权限
    unsigned int s:1;            //描述项类型,1表示系统,0表示代码或数据
    unsigned int type:4;        //段的类型,与上面的s标志位一起使用
    unsigned int base_0_23:24;    //基地址的低24位
    unsigned int seg_limit_0_15:16;    //段长度的低16位
}
在80386的段式内存管理的基础上,如果把每个段寄存器都指向同一个描述项,而在该描述项中将基地址设成0,并将段长度设为最大,这样便形成一个从0开始覆盖整个32位地址空间的一个整段。由于基地址为0,此时物理地址与逻辑地址相同,CPU放到地址总线上去的地址就是在指令中给出的地址,这样的地址称为Flat平面地址。Linux内核的源代码采用平面地址,平面地址是段式内存管理的特例。
利用80386对段式内存管理的硬件支持,可以实现段式虚存管理。当一个寄存器的内容中改变时,CPU要根据新的段寄存器内容以及GDTR或LDTR的内容找到相应的段描述项并将其装入CPU中。在此过程中,CPU会检查该描述项中的p标志位,如果p标志位为0,就表示该描述项所执行的那一段内容不再内存中,此时CPU会发生一次异常,而相应的服务程序便可以从磁盘交换区将这一段的内容读入内存的某个地方,并据此设置描述项中的基地址,再将p标志位设置成1。内存中暂时不用的存储段则可以写入磁盘,逼格将其描述项中p标志位改为0.

以上是关于Intel X86 CPU寻址方式的主要内容,如果未能解决你的问题,请参考以下文章

Intel 汇编寄存器寻址方式简单规则

Android逆向基础之ARM汇编语言知识总结

Android逆向基础之ARM汇编语言知识总结

汇编寻址方式记录

软考之CPU的寻址方式

x86汇编如何查看一个地址的值