Lecture 06: Machine-Level Programming III: Procedures
Stack
rsp 记录栈顶的地址. 栈是从上往下长的,即 push 操作意味着
rsp 变小.
pushq Src:将 rsp 减少 8,然后将 Src
的内容填写到 rsp 的地址上.
popq Dest:将 rsp 地址的内容填写到 Dest
上,然后 rsp+8. 注意内存不发生变化.
stack frame 怎么构造是写在系统的 Application Binary Interface 的,不同架构下可以很不一样. 下面都是 x84-64 Linux 的.
Calling Conventions
用栈管理 procedure control flow.
call label
的时候将当前的下一步(即回来之后应该到达的那步)的程序地址给
push 到栈里面,然后将 rip 设为
label 的地址.
ret 的时候就 popq rip.
可以用 rbp 来存储 frame
pointer(即这个函数的底部的栈指针)(是 optional 的).
如何传递参数?前六个参数分别在 rdi,rsi,rdx,rcx,r8,r9 中传输,然后后面的参数会依次压入栈中. return 的东西会存到 rax 中传输.
对于一次函数的调用,首先会在 caller 的 frame 里面 push 进更多的 arguments(argument build),然后 push return address. 然后就进入 callee frame,这里存入 saved registers 以及 local variables 之后,然后继续新的可能的 argument build.
caller saved registers: 六个传递参数的寄存器,rax,以及 r10, r11
callee saved registers: rbx, r12, r13, r14, r15, rbp. rsp 确实很特殊,但是看得出来可以算作 callee saved. rbp 类似.
Recursion 和 Mutual recursion 在这套机制下显然直接弄就可以了,不会出现任何问题.
Lecture 07: Data
Array 很平凡.
Structure
aligned data:要求每个 data 的 address 都必须是其 primitive data type 的大小的倍数.
Structure: 要求每个内部的 element 都是 aligned 的,并且本身的 initial address & length 是 largest alignment 的倍数. 所以就可能有内部的 internal padding 和结尾的 external padding. 为了节省空间,将越大的 type 放到前面总会不错.
Floating Point
浮点数运算有自己的运算单元和寄存器,分别是 xmm0 到
xmm15.
一个 xmm 有 16 个 byte,也就是可以放 1或4 个 float,或者 1或2 个 double.
在运算的时候,可以用 addss 表示两个有 1 个 float 的 xmm
的加法,addps 表示两个有 4 个 float 的 xmm
的加法,会并行处理这四个加法. addsd 和 addpd
同理.
xmm0 到 xmm7 可以传递参数,而
xmm8 到 xmm15 在 Linux 下也同样都是 caller
saved.
movss / movsd 可以在内存和 xmm 之间 move.
movaps / movapd 可以在 xxm 之间 move.
vcvttss2siq / vcvttsd2siq 可以从
xmm,用截断的方式转化为整数然后存到内存/整数寄存器中.
vcvtsi2ssq / vcvtsi2sdq 可以反过来将整数存储到 xmm
中.
在传递参数的时候,整型管整型传,浮点数管浮点数传.
比较操作 ucomiss x1 x2 和 ucomisd x1 x2
运算 x2-x1. 同样我们有 ZF CF 和 PF.
当两个数中有 NaN 就会有 PF,表示无序. 此时 ZF CF 都 \(=1\).
在 = 的时候,ZF 会 \(=1\). 而 \(x_2<x_1\) 的时候 CF=1,否则 CF=0.