汇编学习笔记(24) - x64位的世界

时间:2024-03-06 09:26:30

前言

  我们首先来理清一些名词 x86, x64, IA-32, IA-32E, IA-64, AMD64, Intel64。

  x86指的就是之前说的从8086发展起来的80X86系列架构包括80286,80386,80486.., 现在指32位架构。之后Intel抛弃x86搞了个独立的64位架构称之为IA-64(安腾系列),  同时将之前的32位架构称之为IA-32,而AMD呢他在之前x86架构的基础上加入了64位的支持称之为x86-64简称x64也称AMD64,之后由于市场的因素,Intel跟着 AMD也在自己的x86基础上加上64位的支持称IA-32E或者Intel64。

  总结一下

  X86 = IA-32

  X64 = AMD64 = INTEL64 = IA-32E (Intel和AMD实现上略有差异)

  IA-64 自成一脉

  注意:我知识粗略记录基本的知识点,所以AMD和Intel的差异部分的细节不会过多深究。

 

重要变化

   1.  所有的通用寄存器都扩展成了64位的寄存器,扩展方式和16位扩展到32位时一样,新的寄存器以R开头并且原来的寄存器还是都保留

    比如

       

 

  2. 段寄存器没变还是16位。

  3. CRX控制寄存器变成了16个 CR0 - CR15,但是大部分过被保留了 只用到 CR0 CR2 CR3 CR4 CR8.  CR8是中断优先级寄存器

     4. 虚拟地址被扩展到了64位,但是呢CPU的物理地址线只有48条,所以虚拟地址的高16位是不表示地址的,只是作为标记存在

     a. 当 虚拟地址   < 8000 0000 0000 的时候高16位必须是0 (0X0000)

     b. 当 虚拟地址   >= 8000 0000 0000 的时候高16位必须是1 (0XFFFF)

                     

 

   5. 寻址方式增加了一个RIP相对寻址

    mov ax, [rip + 32位偏移值]    (NASM语法   mov ax, [REL  32位偏移值])

    引入这个寻址方式的目的是方便程序在内存中的游离,之前的寻址方式多多少少受到 程序在内存中的加载地址的影响实际上相当于绝对寻址,是用这种方式寻址是相当于当前代码的偏移,那么只要保持程序结构破坏,程序随便加载到哪个物理地址都没问题。

 

  6. CALL 立即数:立即数

    64位不支持使用 立即数进行FAR CALL

    必须将数放到内存中, 使用 CALL [EAX] , 内存中依次存放64位offset 和16位选择子

 

  7. 任务调度

    64位下取消了CPU直接支持的任务切换,将任务切换完全交给了操作系统

    所以64位模式下取消了任务门,TSS结构也做了精简,只用来保存一些堆栈的地址。

                      

    首先是特权级切换是的三个堆栈RSP0 RSP1 PRS2,已经IO位图这是没变的

    接下来的ISTX堆栈就是程序自定义的,在中断门和陷阱门中,增加了IST 属性占3位就是用来指向TSS中的ISTX堆栈的,这样在使用中断门和陷阱门的时候就能根据提示切换堆栈,0表示不切换堆栈。

    TR 使用LTR切换内容。

MSR寄存器

  x64架构的CPU 加入了一类称之为MSR(Model Specific Register)的寄存器,MSR 是包含 控制寄存器 和 状态寄存器两类功能的一组寄存器。它即可以控制CPU的一些行为,也存储一些CPU的状态。

  MSR每个寄存器是64位宽的,MSR是一组寄存器,他们没有自己的名字,他们的访问方式和 使用 IN/OUT指令的IO操作是一样的都是使用地址来进行访问。

  使用 RDMSR 和 WRMSR 两个指令来进行读写,相当于IO操作的IN/OUT指令,

  ECX 存放读写地址

  EDX 存放64位值的低32位

  EAX 存放64位值的高32位

  之所以明明通用寄存器已经是64位一个64位的数据还要分两个寄存器存是因为MSR寄存器是为了兼容16位和32位。这个指令在16位和32位都可以使用。

  EFER(C0000080H): 这是一个比较特殊的MSR寄存器的名字, 他控制着是否进入64位模式

               

 

 

  MSR大致包括如下几种功能类型:

1. Thermal
2. Frequency
3. C State
4. Microcode
5. EIST
6. TM
7. Key Features Of CPU
8. Voltage
9. Cache Control
10. MTRR
11. DCA(Direct Cache Access)
12. Machine Check
13. 硬件联机控制
14.other 

 

进入64位模式

  64位模式相当于是保护模式的一个子模式,所以保护模式现在有两个子模式 分别就是32位模式和64位模式,CPU一开始运行的时候是处于16位的实模式,之后可以切换到保护模式,切换到保护模式的时候可以选是32位的保护模式还是64位的保护模式。所有模式都是可以相互切换的。

  AMD将64位模式称为Long Mode模式,Intel称之为IA-32E模式。

  32位下的内存也管理是可选的,而64位模式内存的页管理是必须启用的。

  所以64位模式的启用条件是

  1. 启用段内存管理进入保护模式 CR0.PE = 1

  2. 启用PAE内存也管理模式  CR4.PAE = 1

  3. 启用64位模式 EFER.LME = 1 

  4. 启用页内存管理 CR0.PG = 1 

  5.  上述四点必须安装描述的顺序启用

  如果条件全部满足,CPU会置 EFER.LMA = 1表示64位模式启动了 

   

内存段管理

   64位段管理是在32位段管理的基础上增加了64位访问的能力。

  1.  GDTR,  LDTR, IDTR, TR 寄存器的地址不封被扩展为64位,所以他们总体上从 48 位扩展为 80位寄存器,16位的limit没变。

  2.  数据段的方向被取消了,也就是说现在的数据段只能是从低地址往高地址涨,有效范围是0-limit

  3.  段描述符(数据段与代码段)中的段基地址和界限会被忽略,实际64位下的段描述符仅仅描述的属性,不在描述大小位置,而是可以访问所有内存地址。

  4.  代码段描述符增加一个L属性位,用于表明这个是要一个64位代码段。

      L = 1 : 64位代码段

      L = 0:

        D = 1 32位代码段

        D = 0 16位代码段

  5. 门描述符在原先的基础上新增8字节被扩展到了16字节,主要原因是采用64位地址之后原先的偏移地址的描述就不够了。

    所以GDTR和LDTR的表项是8字节和16字节混合的,所以为了防止误将16字节的门描述符的高8字节解释成段描述符,其对应的属性字段位置的数据必须设置为0

 

  额外介绍几个指令

 

LDS,LES,LFS,LGS,LSS

格式
  LDS reg16,mem32

说明
  其意义是同时给一个段寄存器和一个16位通用寄存器同时赋值
  具体如下:reg16=mem32的低字,DS=mem32的高字

 

内存页管理

  内存的也管理也是在32位的PAE模式下扩展而来,如下图

  

X64位采用4级页表, 32位NoPAE是2级页表 32位PAE是3级页表,所以X64是在X32PAE下有增加了一级页表

X64位下一个也可以是1G, 2M, 4K大小的。分别由PDPE.PS, PDE.PS来控制

  PDPE.PS = 1 : 表明当前PDPE就已经描述了一个1G的页了,不用再向下级页表查询,否则PDPE描述的下一级页表的页表地址

  PDE.PS = 1:  表明当前PDE就已经描述了一个2M的页了,不用再向下级页表查询,否则PDE描述的下一级页表的页表地址

CR3寄存器,也被扩展为64位,低12位中3,4位作为PWT()和PCD标志,其他位被忽略,高地址作为物理地址指向PML4T, 高地址的有效为同样受地址线影响,无效位必须是0.

PWT: Page Write Through
PWT= 1时,写数据到缓存(Cache)的时候也要将数据写入内存中。

PCD: Page Cache Disable
PCD = 1时,禁止某个页写入缓存,直接写内存。
比如,做页表用的页,已经存储在TLB中了,可能不需要再缓存了

各级页表项结构如下

 

首先注意到的是所有的页都是字节对齐的,比如1G的页是1G对齐的,那么地址的低30位则必然为零,所以PDPTE的1G模式下1G的物理地址是从30位开始描述,低30位会被自动补0,同理根据页大小的不同的地址的描述也从不同的位开始

其次注意到的是 "根据地址线扩展” 这一字段以及位描述中的 ? , 这是因为,不同的CPU 的有效地址线位数不同,所以到底有多少位有效是根据CPU来决定的

举个例子,假设CPU地址线是48位,那么PTE描述的低12位肯定是0,那么就有48-12 = 36 位需要描述,那么 PTE的   47 - 12位就描述了这个地址,而62-48位则会被忽略。

 

标志位解释

  P:    页存在内存标志位

  R/W: 表示页是否可写 R/W = 1 表示页可写, 当R/W = 0 是表示页只读 ,当CR0.WP = 0 是 CPL=0的情况下不收R/W控制,CR0.WP=1的时候无论何时都收R/W控制

  U/S:  U/S = 0  只有特权级可以对页进行任何访问   U/S = 1 所有特权级可以对页进行读写访问, 当开启SMEP功能时 只有CPL=3 可以进行执行访问,如果SMEP未开启,则所有权限都可以执行访问

  PWT: 页缓存类型??

  PCD: 也是否可以Cache

  A: 页已经被访问标识,由CPU自动置位

  D: 页被写过标志,由CPU自动置位

  PAT:  Page Attribute Table 标志

  PS: PS =1 标识此表项直接指向物理页了,不用查询下一级表

  G: 当 CR4.PGE=1时此位有效

    G = 1 标识是全局页,在切换CR3的指定时候此页的换页描述缓存不会被自动刷新

    G =0 普通页,切换的CR3的时候此 页的换页描述缓存会被自动刷新

  XD:  只有当EFER.NXE = 1时 此控制生效,否则忽略此控制开关

     XD = 1 代码不允许执行

     XD = 0 允许代码执行

页的缓存

   就像段寄存器有内部缓存和页表项缓存TLB一样,内存页也有缓存,就是CPU内部的多级高速缓存。也都缓存由3个开关来控制

   PWT: Page Write Through, 当PWT = 1 时 CPU写Cache的同时同步更新内存值,这会比较慢, 当PWT =0 时 写Cache时不会更新内存值,只有当Cache换出时才更新内存. 

   PCD: Page Cache Disable, 当PCD = 1 的时候 静止页缓存。

      PAT:  当CPU支持PAT模式的时候 PWT 和PCD将不再单独解释,而是和PAT组成一个3位的指针 指向 MSR寄存器中的 8 个PATX寄存器,然后在这个8个寄存中存储对应的行为类型

      排列顺序 PAT PCD PWT  一个单位可以表示0-7,对应PAT0-PAT7

      

 

 

    我们注意到 除CR3寄存器不带PAT 之外,其余表项都是带PAT PWT PCD,这里要区分他们描述的是他们地址所指向的内存页,而不是寻址经过他的所有页。

    举个例子就是,CR3的PWT和PCD只决定CR3地址指向的存储着PLM4T表的那页的属性

    同样PLM4T的 PAT PWT PCD 值只决定它直接指向的1G页或者PDTT所在的那一页的属性,而不是说他所指向的PDTT中所有下级表指定的也都受它控制。

    

特权级快速切换

SYSENTER/SYSEXIT

  SYSENTER/SYSEXIT 是intel 所以amd64下不能在long mode下使用 ,使用msr

注意: sysenter 是CPU硬编码的直接进ring0,无论此时处于ring几,sysexit则是是CPU硬编码的直接进ring3,无论此时是几。

这里需要注意的是IA32 SYSTEMENTER_CS, 他值存储一个值,那就是进入时的CS的选择子,其余选择子默认是顺序存放的。

而 IP 和 SP 两指针寄存器则是进入时使用MSR的寄存器,退出时使用CX 和 DX寄存器

 

 

 

 

 

SYSCALL/SYSRET

syscall/sysret是amd的所以intel64下只能在long mode 下使用,使用star系列寄存器

syscall通过从IA32_LSTAR MSR加载RIP(syscall的下一条指令地址会保存到RCX).RFLAGS保存到R11寄存器,然后用IA32_FMASK MSR(MSR 地址:C0000084H)屏蔽RFLAGS的值,更具体的说是清除在IA32_FMASK MSR中设置的每一位.

 

 

 

 

syscall

syscall会从IA32_STAR[47:32]中加载CS和SS.

syscall不会保存堆栈指针(RSP).

RCX ← RIP;保存syscall下一条指令地址到RCX

RIP ← IA32_LSTAR;RIP=IA32_LSTAR

R11 ← RFLAGS;R11=RFLAGS

RFLAGS ← RFLAGS AND NOT(IA32_FMASK);//根据IA32_FMASK屏蔽RFLAGS的相关位

CS.Selector ← IA32_STAR[47:32] AND FFFCH;确保CS的RPL为0

SS.Selector ← IA32_STAR[47:32] + 8;

 

sysret

RIP=RCX

RFLAGS=R11

返回32位代码:CS=CS_Selector|3 SS=(CS_Selector+8)|3

返回64位代码:CS=(CS_Selector+16)|3 SS=(CS_Selector+8)|3

返回时不会修改(ESP or RSP)

 

 其他指令

swapgs

MSR 中有两个 寄存器IA32_KERNEL_GS_BASE 和 IA32_GS_BASE

之前说过,段寄存器的缓存中的 基地址全部被强制设成了0, 而GS是个例外, 可以通过设置 IA32_GS_BASE的值来改变 GS寄存器缓存中BASE的值,也就是说在改变 IA32_GS_BASE的值的时候CPU会把数据同步到GS寄存器的BASE值中,

而 swapgs 的作用是交换  IA32_KERNEL_GS_BASE 和 GS.BASE的值

比如我在IA32_KERNEL_GS_BASE 中保存了内核模式下我要使用的全局数据的指针,

那么我在进入内核后直接 使用 swapgs,那么此时GS寄存器就直接指向我内核的全局数据了,当我要退出内核之前

我使用 swapgs 吧值换回来,这样就还原成原来的样子了。

 

 

monitor/mwait

IA32_MISC_ENABLE , 开始这个功能

使用 monitor 设置一个内存地址,CPU的监视器就是监视 从这个地址开始的一个固定范围的内存写操作(这个方位是CPU固定的使用CPUID可以查看方位大小), 如果监视到写操作就是设置相应的标志位,而mwait的作用就是用来等待标志位被置位,在标志位被置位前CPU将暂停工作(中断操作也会唤醒mwait操作, 唤醒后会继续执行下去,还是重新睡眠?可以通过标志位判断唤醒的原因?)