跳转至

x86 记录

x86 记录

x86 基础

  • AT&T 汇编格式,和 intel 格式。
    • 简单区别:
      • AT&T 中根据操作数 size 添加不同 suffix,如:b, w, l, q
      • AT&T 中使用%表示寄存器,$表示立即数。
      • 源操作数和目的操作数顺序相反。AT&T 中第一个是源操作数,第二个是目的操作数。 (由于 x86 中,目的操作数又同时作为源操作数,所以这种写法感觉怪怪的。)
  • 大多数编译器混合使用 32 和 64 位模式,32 位寄存器用来做整数计算,而 64 位用于保存内存地址。

寄存器

常见指令

感觉比较重要的指令类别

  • 数据移动:mov
  • 基本计算指令:add, sub, imul, idiv
  • 分支跳转:cmp,jxx, jmp

寻址模式

  • 立即数
  • 寄存器
  • 内存地址
    • indirect
    • base-relative
    • offset-scaled-base-relative
movq (%rsi), %rax
movq    offset(%rsi), %rax
movq offset(%rbx, %rcx, scale), %rax

RISC 架构中一般采用 load/store 模式对内存进行读写,其它指令无法读写内存

函数调用

相关指令

  • call, ret
  • enter, leave

cheetsheet:Handout-CallReturn.graffle (wisc.edu)

ABI

需要知道调用约定(calling convention),也为 ABI(应用二进制接口)x86 calling conventions - Wikipedia

  • 影响函数参数是如何传递的:通过栈或寄存器

caller rules:

  • 保存 caller save 寄存器 (if use):%rax, %rcx, %rdx
  • 整数参数 (包含指针)依次放在%rdi, %rsi, %rdx, %rcx, %r8, 和 %r9 寄存器中。
  • 浮点参数依次放在寄存器%xmm0-%xmm7 中。
  • 寄存器不够用时,参数放到栈中。将函数的参数以相反的顺序推送到堆栈。
  • 可变参数函数 (比如 printf), 寄存器%eax 需记录下浮点参数的个数。
  • call
    • 将 rip 压入栈

callee rules:

  • 保存 caller 帧的基址寄存器%rbp:push %rbp
  • 调整 rbp 指向当前帧,movq %rsp, %rbp
  • 分配局部变量空间,通过调整 esp
  • 可以发现第一个参数位于 ebp+8 的位置(x86-32)
  • 保存 callee save 寄存器 (if use):%rbx, %rsi, %rdi, %rbp, %rsp, and %r12-%r15
  • 返回值存储在 %eax 中。
  • 恢复 callee save 寄存器(弹栈)
  • 取消分配的局部变量
  • 恢复%rbp
  • ret

enter, leave 合并部分操作

  • 函数开头:push ebp; mov ebp, esp; 分配局部变量空间;可以通过 enter a b 实现。a 为分配的字节数,b 为嵌套级别,通常为 0。
  • 函数结尾:mov esp, ebp; pop ebp,可以使用 leave 代替。

RISC 中没用专门的函数调用指令,而是使用分支跳转指令进行调用和返回,如 MIPS 中:beqal func, jr $31

动态链接

  1. 一个可执行文件生成的过程。 编译(预处理,汇编),链接(静态,动态)

  2. 可执行文件执行的过程 创建进程 (fork, exec) loader(静态,动态)

  3. 一个可执行文件哪里会出现绝对地址

  • MIPS
    • PC-relative
      • beq, beqal
      • b label (pseudo instruction)
    • indirect
      • jr:only used in call-return
    • pseudo-immediate
      • j
  • x86
    • jmp
    • call(实际是相对偏移)

CSAPP x86 汇编

x86 指令 reference 网页

x86 指令 ref:x86 and amd64 instruction reference (felixcloutier.com)

格式含义

  • MOV r/m8,r8
    • r8 表示大小为 8 位的寄存器
    • m8 表示大小为 8 位的操作数对应的地址
    • imm 表示立即数

数据格式

image-20220727184958311

  • x86_64 中,long 类型是 8 字节
  • x86 处理器曾实现过对80 位浮点数的全套浮点运算,在 C 中可以使用 long double 声明。不推荐使用。Double Extended Precision

数据移动指令

mov, movz, movs
  • movl 会将寄存器高 32 位清 0,因此代替了 movzlq
  • movabsq 用于 64 位立即数 mov
  • cltq 是 movslq %eax, %rax
cltd(alias)

cltd is an alias for cdq (reference), which sign-extends eax into edx:eax.

What this means in practice is that edx is filled with the most significant bit of eax (the sign bit). For example, if eax is 0x7F000000 edx would become 0x00000000 after cdq. And if eax is 0x80000000 edx would become 0xFFFFFFFF.

image-20220727190841956

image-20220727191456889

image-20220727191512098

算术和逻辑操作

lea
  • lea(加载有效地址(load effective address))可以用来执行简单的算术操作

image-20220727192028818

128 位乘除法
  • 八字 (oct word),o 为后缀
  • 上面的 imulq,双操作数:将两个 64 位操作数产生一个 64 位乘积(截断时,无符号乘和补码乘的位级行为是一样的)
  • idivq 时,rdx: rax 分别存余数和商。

image-20220727193931062

image-20220727193507768

控制

条件码
  • CF: 进位标志。最近的操作使最高位产生了进位。可用来检査无符号操作的溢出。
  • ZF: 零标志。最近的操作得出的结果为 0。
  • SF: 符号标志。最近的操作得到的结果为负数。
  • OF: 溢出标志。最近的操作导致一个补码溢出—— 正溢出或负溢出。

leaq 指令不改变任何条件码,因为它是用来进行地址计算的。除此之外,图 3-10 中 列出的所有指令都会设置条件码。对于逻辑操作,例如 XORÿ 进位标志和溢出标志会设 置成 〇。对于移位操作,进位标志将设置为最后一个被移出的位,而溢出标志设置为 0。 INC 和 DEC 指令会设置溢出和零标志,但是不会改变进位标志,至于原因,我们就不在 这里深人探讨了。

cmp, test

image-20220727194059694

set

image-20220727194325976

jmp 的 PC 相对寻址

函数调用

基指针作用

变长栈帧。一般情况下,栈帧的大小在编译时即可确定。但是对于 alloca 标准库函数,变长数组(int p[n]),栈帧大小是变化的。

浮点体系结构

  • MMX
  • SSE
  • AVX
    • AVX2:2013 年 Core i7 Haswell 引入
    • GCC 使用-mavx2

2000 年 Pentium 4 中引入了 SSE2。媒体指令开始包括那些对标量浮点数据进行操作的指令,使用 XMM 或 YMM 寄存器的低 32 位或 64 位中的单个值。这个标量模式提供了一组寄存器和指令,它们更类似于其他处理器支持浮点数的方式。所有能够执行 X86-64 代码的处理器都支持 SSE2 或更高的版本,因此 X86-64 浮点数是基于 SSE 或 AVX 的,包括传递过程参数和返回值的规则[77]。

浮点数据传送指令
  • GCC 只用标量传送操作从内存传送数据到 XMM 寄存器或从 XMM 寄存器传送数据到内存。
  • 在两个 XMM 寄 存器之间传送 数据时,GCC 使用 vmovaps 传送单精度数,用 vtnovapd 传送双精度数(对于这些情况,程序复制整个寄存器还是只复制低位值既不会影响程序功能,也不会影响执行速度)
  • 推测 vmovss 的 s 分别表示 scal
  • ar 标量和单精度,a 表示 aligned, p 表示 packed

image-20220727202131276

图 3-47 和图 3-48 给出了在浮点数和整数数据类型之间以及不同浮点格式之间进行转换的指令集合。

image-20220727202807490

  • 最后的两行时 GCC 在 float 和 double 间转换的代码,没有使用基本的 vctsd2ss(有没有这条指令呢?)

image-20220727203331930

浮点函数调用
  • XMM 寄存器%xmm_0~%xmm_7 最多可以传递 8 个浮点参数。按照参数列出的顺序使用这些寄存器。可以通过栈传递额外的浮点参数
  • 函数使用寄存器%xmm_0 来返回浮点值。
  • 有的 XMM 寄存器都是调用者保存的。被调用者可以不用保存就覆盖这些寄存器中任意一个。
浮点运算操作

image-20220727204128819

  • 浮点运算指令无法使用立即数。编译器必须为所有的常量值分配和初始化存储空间

image-20220727204247797

位级操作

image-20220727204906712

比较

image-20220727205010011

  • 浮点比较指令会设置三个条件码:零标志位 ZF,进位标志位 CF 和奇偶标志位 PF
    • 对于浮点比较,当两个操作数中任一个是 NaN 时,会设置 PF 位。
    • 当 x 为 NaN 时,比较 x==x 会得到 0

image-20220727205538097

x86 手册

内存管理

分段式管理

  • 段选择子:用于查找 GDT,找到段的基地址

中断处理

  • 中断类型。几种不同的说法,含义是一样的
    • 外部中断,内部中断
    • 异步中断,同步中断
    • 中断,异常
  • 中断描述符
  • 中断描述符表 IDT, IDTR

中断上下文切换过程