汇编

程序编码

在编译时指定'-Og'选项让GCC产生符合原始程序结构的机器代码

机器级代码

对C语言隐藏, 但对汇编代码可见的:

输出c源码的机器表示

gcc -Og -S mstore.c

机器代码与反汇编的特性:

关于格式的注解

.file    "mstore.c"    .text    .globl    mulstore    .type    mulstore, @functionmulstore:.LFB0:    .cfi_startproc    pushq    %rbx    .cfi_def_cfa_offset 16    .cfi_offset 3, -16    movq    %rdx, %rbx    call    mult2    movq    %rax, (%rbx)    popq    %rbx    .cfi_def_cfa_offset 8    ret    .cfi_endproc.LFE0:    .size    mulstore, .-mulstore    .ident    "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"    .section    .note.GNU-stack,"",@progbits

以上是gcc完整生成的.s文件

所有. 开头的是伪指令, 可以忽略

ATT汇编代码格式

.file    "mstore.c"    .text    .globl    mulstore    .type    mulstore, @functionmulstore:.LFB0:    .cfi_startproc    pushq    %rbx    .cfi_def_cfa_offset 16    .cfi_offset 3, -16    movq    %rdx, %rbx    call    mult2    movq    %rax, (%rbx)    popq    %rbx    .cfi_def_cfa_offset 8    ret    .cfi_endproc.LFE0:    .size    mulstore, .-mulstore    .ident    "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"    .section    .note.GNU-stack,"",@progbits

数据格式

访问信息

x86-64的CPU包含一组16个存储64位的通用目的寄存器

63                     31         15          8          0%rax                   %eax       %ax         %ah         %al 返回值%rbx                   %ebx       %bx         %bh         %bl 被调用者保存%rcx                   %ecx       %cx         %ch         %cl 第4个参数%rdx                   %edx       %dx         %dh         %dl 第3个参数%rsi                   %esi       %si                     %sil 第2个参数%rdi                   %edi       %di                     %dil 第1个参数%rbp                   %ebp       %bp                     %bpl 被调用者保存%rsp                   %esp       %sp                     %spl 栈指针%r8                    %r8d       %r8w                    %r8b 第5个参数%r9                    %r9d       %r9w                    %r9b 第6个参数%r10                   %r10d      %r10w                   %r10b 调用者保存%r11                   %r11d      %r11w                   %r11b 调用者保存%r12                   %r12d      %r12w                   %r12b 被调用者保存%r13                   %r13d      %r13w                   %r13b 被调用者保存%r14                   %r14d      %r14w                   %r14b 被调用者保存%r15                   %r15d      %r15w                   %r15b 被调用者保存

每个寄存器都可以作为8位、16位、32位、64位来访问

操作数指示符

数据传送指令

指令效果描述
MOVE S,DD←S传送
movb传送字节
movw传送字
movl传送双字
movq传送四字
movabsq I,RR←I传送绝对的四字

零扩展:用于将较窄的整数类型(如8位或16位整数)扩展为较宽的整数类型(如32位或64位整数),原始数据的低位(等于或小于原始数据位数的位)保持不变,而高位被填充为零

符号扩展:用于将较窄的整数类型(如8位或16位整数)扩展为较宽的整数类型(如32位或64位整数),原始数据的低位(等于或小于原始数据位数的位)保持不变,而高位被填充为原始数据的符号位

指令效果描述
MOVEZ S,RR←零扩展(S)以零扩展进行传送
movzbw将做了零扩展的字节传送到字
movzbl将做了零扩展的字节传送到双字
movzwl将做了零扩展的字传送到双字
movzbq将做了零扩展的字节传送到四字
movzwq将做了零扩展的字传送到四字
MOVS S,RR←符号扩展(S)以符号扩展进行传送
movsbw将做了符号扩展的字节传送到字
movsbl将做了符号扩展的字节传送到双字
movswl将做了符号扩展的字传送到双字
movsbq将做了符号扩展的字节传送到四字
movswq将做了符号扩展的字传送到四字
movslq将做了符号扩展的整形传送到四字
cltq%rax←符号扩展(%eax)把%eax符号扩展到%rax

压入栈和弹出栈数据

. 将四字压入栈pushq S . 将四字弹出栈popq D

%rsp 是栈指针 %rax是返回值

算术和逻辑操作

指令效果描述
leaq S,DD←&S加载有效地址
INC DD←D+1加1
DEC DD←D-1减1
NEG DD←-D取反
NOT DD←~D取反
ADD S,DD←D+S
SUB S,DD←D-S
IMUL S,DD←D*S
XOR S,DD←D^S异或
OR S,DD←D
AND S,DD←D&S
SAL S,DD←D<<S左移
SHL S,DD←D<<S左移 = SAL
SAR S,D$D←D>>_AS$算术右移
SHR S,D$D←D>>_LS$逻辑右移

加载有效地址

. x= y+x*4leaq    (%rdi,%rsi,4), %rax

一元和二元操作

. 从%edi中减去%esisubl    %esi, %edi

移位操作

. 将x左移四位salq    $4, %rax

特殊的算术操作

指令效果描述
imulq SR[%rdx]: R[%rax]←S * R[%rax]有符号全乘法
mulq SR[%rdx]: R[%rax]←S * R[%rax]无符号全乘法
cltoR[%rdx]: R[%rax]← 符号扩展(R[%rax])转换为八字
idivq SR[%rdx]←R[%rdx]: R[%rax] mod/÷ S有符号除法
divq SR[%rdx]←R[%rdx]: R[%rax] mod/÷ S无符号除法

控制

条件码

cmp 指令在被执行时,会首先比较两个变量的大小,并根据比较结果,动态调整 CPU 上 FLAGS 寄存器中的相应位

test 指令的执行方式与 cmp 类似,只不过它会对传入的两个操作数做隐式的“与”操作,而非减法操作

标志位名称全称什么情况下置位(即变更为值1)
CF0Carry指令执行引起了进位或借位
PF2Parity指令执行结果的最低有效字节中值为1的位个数为偶数
ZF6Zero指令执行结果为0
SF7Sign指令执行结果的最高有效位为1
OF11Overtlow当操作致被当做有符号数时,指令的执行产生了溢出
指令基于描述
CMP S2,S1S1-S2比较
cmpb比较 byte
cmpw比较 word
cmpl比较 double word
TEST S2,S1S1&S2测试
testb测试 byte
testw测试 word
testl测试 double word

这些指令不修改寄存器的值,只设置条件码

cmp     DWORD PTR [rbp-4], 1        jne     .L2        mov     eax, 101        jmp     .L3.L2:        mov     eax, 10.L3:        pop     rbp        ret

对应于

if (num == 1) {    return 101;}return 10;

循环、选择等操作,都是通过 cmp + jmp 来实现的

读取条件码

指令别名效果设置条件
sete DsetzD←ZF相等或为0
setne DsetnzD←!ZF不相等或非0
sets DD←SF负数
setns DD←!SF非负数
setg DsetnleD←~(SF ^ OF) & ~ZF有符号大于
setge DsetnlD←~(SF ^ OF)有符号大于或等于
setl DsetngeD←SF ^ OF有符号小于
setle Dsetng`D←(SF ^ OF)ZF`
seta DsetnbeD←~CF & ~ZF无符号大于
setae DsetnbD←~CF无符号大于或等于
setb DsetnaeD←CF无符号小于
setbe Dsetna`D←CFZF`

跳转指令

指令别名跳转条件描述
jmp LABEL1直接跳转
jmp *Operand1间接跳转
je LABELjzZF相等或为0
jne LABELjnzZF不相等或不为0
js LABELSF负数
jns LABEL~SF非负数
jg LABELjnle~(SF ^ OF) & ~ZF有符号大于
jge LABELjnl~(SF ^ OF)有符号大于或等于
jl LABELjngeSF ^ OF有符号小于
jle LABELjng(SF ^ OF)ZF
ja LABELjnbe~CF & ~ZF无符号大于
jae LABELjnb~CF无符号大于或等于
jb LABELjnaeCF无符号小于
jbe LABELjna`CFZF`

用条件控制实现分支控制

cmpq    %rsi, %rdi        jg      .L4        movq    %rdi, %rax        subq    %rsi, %rax        ret.L4:        leaq    (%rdi,%rsi), %rax        ret

对应的c代码:

if (x > y){    return x+y;}else{    return x-y;}

用条件传送实现条件分支

分支预测

指令别名传送条件描述
cmov S,RcmovzZF相等或为0
cmovne S,Rcmovnz~ZF不相等或非0
cmovs S,RSF负数
cmovns S,R~SF非负数
cmovg S,Rcmovnle~(SF ^ OF) & ~ZF有符号大于
cmovge S,Rcmovnl~(SF ^ OF)有符号大于或等于
cmovl S,RcmovngeSF ^ OF有符号小于
cmovle S,Rcmovng`(SF ^ OF)ZF`
cmova S,Rcmovnbe~CF & ~ZF无符号大于
cmovae S,Rcmovnb~CF无符号大于或等于
cmovb S,RcmovnaeCF无符号小于
cmovbe S,Rcmovna`CFZF`

循环

switch语句

跳转表:一种用空间换时间的条件匹配策略,这种优化手段通过将每个case标签生成一个唯一的标号,然后创建一个跳转表,其中每个条目对应一个case标签,再通过 jmp 指令,对输入值进行计算,以计算出跳转表的实际索引,然后跳转过去

jmp qword ptr [8*rdi +.LJTIO_0].LJT10_0:  .quad .LBBO 4  ...

函数调用

运行时栈

转移控制

保存当前程序地址,将程序计数器设置为新过程地址 返回时读取保存的地址,继续执行

在 x86-64 架构下,其会通过 call 指令将当前的程序地址压入栈中,并跳转到指定的函数地址,被调用的函数通过 ret 指令返回结果

int foo(int n) {    return n +1;}int main() {    foo(1);    return 1;}
foo(int):        push    rbp        mov     rbp, rsp        mov     DWORD PTR [rbp-4], edi        mov     eax, DWORD PTR [rbp-4]        add     eax, 1        pop     rbp        retmain:        push    rbp        mov     rbp, rsp        mov     edi, 1        call    foo(int)        mov     eax, 1        pop     rbp        ret

参数传递

传递函数参数的寄存器

返回值传递

栈上的局部存储

寄存器中的局部存储空间

递归过程

尾递归优化

在一定条件下,编译器可以直接利用跳转指令取代函数调用指令。尾递归调用的一个重要条件是:递归调用语句必须作为函数返回前的最后一条语句

编译器会使用跳转指令(如je、jne、jle等)来替换函数调用时所使用的 call 指令,这样就

int f(int i, int sum) {    if (i == 0) {        return sum;    }    return (i - 1, sum * i);}
f(int, int):        mov     eax, esi        test    edi, edi        je      .L1        imul    eax, edi.L1:        ret

数组的分配和访问

基本原则

T A[N]

指针运算

&D [ i ] [ j ] = X

D

L(Ci+j)

定长数组

变长数组

异质的数据结构

都是对地址进行偏移得到的

在机器级程序中将控制与数据结合起来

理解指针

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值

对于这样的一条语句

int *p = &n;

其会通过 lea 指令找到 n 的地址,然后通过 mov 指令将 n 的值赋给 p

lea     rax, [rbp-12]mov     QWORD PTR [rbp-8], rax

GDB调试器

UNIX及UNIX-like下的调试工具

内存越界引用和缓冲区溢出

对抗缓冲区溢出攻击

变长帧

浮点代码

%ymm0 ~ %ymm15

浮点传送和转换操作

指令目的描述
vmovss$M_{32}$X传送单精度数
vmovssX$M_{32}$传送单精度数
vmovsd$M_{64}$X传送双精度数
vmovsdX$M_{64}$传送双精度数
vmovapsXX传送对齐的封装好的单精度数
vmovapdXX传送对齐的封装好的双精度数
指令目的描述
vcvttss2si$X/M_{32}$$R_{32}$用截断的方法把单精度数转换成整数
vevttsd2si$X/M_{64}$$R_{32}$用截断的方法把双精度数转换成整数
vcvttss2siq$X/M_{32}$$R_{64}$用截断的方法把单精度数转换成四字整数
vcvttsd2siq$X/M_{64}$$R_{64}$用截断的方法把双精度数转换成四字整数
指令源1源2目的描述
vcvtsi2ss$M_{32}/R_{32}$XX把整数转换成单精度数
vcvtsi2sd$M_{32}/R_{32}$XX把整数转换成双精度数
vcvtsi2ssq$M_{64}/R_{64}$XX把四字整数转换成单精度数
vcvtsi2sdq$M_{64}/R_{64}$XX把四字整数转换成双精度数

过程中的浮点代码

使用XMM寄存器来传递浮点参数

浮点运算操作

单精度双精度效果描述
vaddssvaddsdD←S2+S1浮点数加
vsubssvsubsdD←S2-S1浮点数减
vmulssvmulsdD←S2XS1浮点数乘
vdivssvdivsdD←S2/S1浮点数除
vmaxssvmaxsdD←max(S2,S1)浮点数最大值
vminssvminsdD←min(S2,S1)浮点数最小值
sgrtsssqrtsd$D←\sqrt{S1}$浮点数平方根

定义和使用浮点常数

浮点操作不能把立即数作为操作数

编译器必须为所有浮点常量初始化存储空间

在浮点代码中使用位级操作

单精度双精度效果描述
vxorpsvorpdD←S2·S1位级异或(EXCLUSIVE-OR)
vandpsandpdD←S2&S1位级与(AND)

浮点比较操作