Assembly
5/23/26About 6 min
Assembly
寻址
偏移量(基址寄存器, 变址寄存器, 比例因子)
| 寻址方式 | 示例 | 说明 |
|---|---|---|
| 立即寻址 | movl $0x12, %eax | 操作数直接存放在指令中 |
| 寄存器寻址 | movl $0x12, %eax | 操作数存在寄存器中 |
| 直接寻址 | movl 0x1234, %eax | 指令中存放操作数的偏移地址 |
| 寄存器间接寻址 | movl (%ebx), %eax | 操作数的偏移地址存在寄存器中 |
| 变址寻址 | movl 0x12(,%esi,4), %eax | 地址偏移量为:常数+寄存器*F |
| 基址寻址 | movl 0x12(%ebp), %eax | 基址寄存器+位移 |
| 基址加变址寻址 | movl 0x12(%ebx,%esi,4), %eax | 地址偏移量为:常数+寄存器+寄存器*F |
| 相对寻址 | jmp *0x12(%rip) | X86-64,指令中存偏移量,加上 RIP 得到地址 |
指令
AT&T
操作数的字长由操作符的最后一个字母决定,后缀‘b’、‘w’、‘l’、‘q’分别表示操作数为字节、字、双字和四字
- 如果使用了寄存器寻址,宽度能够自动推导,可以省略
- movz, movs 只有目的操作数的宽度可以省略
数据传送指令
MOV:一般传送指令MOVS:符号扩展传送,后面跟两个表示字长的后缀,源操作数、目的操作数MOVZ:零扩展传送CMOV:条件传送(cmove, cmovc, cmovne, ...)LEA:地址传送(将计算出的地址传给目的操作数。不仅可以用于地址传送,还能用于数学计算!)
Note
数据扩展时,按源操作数的类型选择有无符号扩展。
堆栈操作指令
PUSH:ESP - 入栈的字长,数据 -> ESPPOP:ESP -> 目的操作数,ESP + 字长PUSHA/POPA:8个16位寄存器入/出栈PUSHAD/POPAD:8个32位寄存器入/出栈PUSHFD/POPFD:32位标志寄存器入/出栈(16位为PUSHF/POPF)
算术运算指令
- 加法指令:
INC(加一)、ADD、ADC(带进位加,加上CF) - 减法指令:
DEC(减一)、NEG(求补,取反加一)、SUB、SBB(带借位减,减CF)、CMP(比较指令,做减法只设置标志位) - 乘法指令:
IMUL(有符号乘)、MUL(无符号乘)- 单操作数:
(AL) * (src) → AX/(AX) * (src) → DX, AX/(EAX) * (src) → EDX, EAX - 双操作数:
(dest) * (src) → dest - 三操作数:
(src) * n → dest
- 单操作数:
- 除法指令:
IDIV、DIV- 单操作数:
(AX)/(src) → AL(商), AH(余)/(DX, AX)/(src) → AX(商), DX(余)/(EDX, EAX)/(src) → EAX(商), EDX(余)
- 单操作数:
- 符号扩展指令:
CBW(将AL中的符号扩展至AH中)、CWD(将AX中的符号扩展至DX中)、CWDE(AX扩展填满EAX)、CDQ(EAX扩展至EDX)
按位运算指令
- 求反:
NOT dest(~ (dest) → dest) - 逻辑乘:
AND src, dest((dest) & (src) → dest) - 测试指令:
TEST src, dest((dest) & (src),只设置标志位) - 逻辑加:
OR src, dest((dest) | (src) → dest) - 按位加:
XOR src, dest((dest) ^ (src) → dest)
移位指令
SAL: 算术左移 (shift arithmetic left)SHL: 逻辑左移 (shift logical left)SAR: 算术右移 (shift arithmetic right)SHR: 逻辑右移 (shift logical right)
Note
逻辑左移和算术左移对应的指令完全相同
CF:左移移出的最后一位的值OF:移位后符号位是否发生改变(仅在左移一位时有意义,移动多位时为未定义行为)
转移指令
无条件转移:JMP
- 直接转移:
JMP 标号 - 间接转移:
JMP *%eax或JMP *目标地址(AT&T中使用*前缀进行间接转移)
条件转移:
标志位判断
JZ/JE:ZF=1 时转移JNZ/JNE:ZF=0 时转移JS:SF=1 时转移JNS:SF=0 时转移JO:OF=1 时转移JNO:OF=0 时转移JC:CF=1 时转移JNC:CF=0 时转移JP/JPE:PF=1 时转移JNP/JPO:PF=0 时转移
无符号数比较
JA/JNBE:CF=0 且 ZF=0 时转移,无符号数大于 (Above)JAE/JNB:CF=0 或 ZF=1 时转移,无符号数大于等于JB/JNAE:CF=1 且 ZF=0 时转移,无符号数小于 (Below)JBE/JNA:CF=1 或 ZF=1 时转移,无符号数等于
有符号数比较
JG/JNLE:SF=OF 且 ZF=0 时转移,有符号数大于 (Greater)JGE/JNL:SF=OF 或 ZF=1 时转移,有符号数大于等于JL/JNGE:SF≠OF 且 ZF=0 时转移,有符号数小于 (Less)JLE/JNG:SF≠OF 或 ZF=1 时转移,有符号数小于等于
字节指令
SET***:条件***(同上面的转移条件)成立时,将操作数设为1,否则设为0
循环转移指令
LOOP 标号:ECX减一,若 ECX≠0,转到标号LOOPE/LOOPZ 标号:ECX减一,若 ECX≠0 且 ZF=1,转到标号LOOPNE/LOOPNZ 标号:ECX减一,若 ECX≠0 且 ZF=0,转到标号JECXZ:Jump if ECX is Zero
调用指令
call: 将返回地址(下一条指令地址)压栈,并跳转到目标地址
- 直接调用
call 函数名 - 间接调用
call *寻址表达式leave: 恢复调用前的栈状态,相当于
movl %ebp, %esp
popl %ebpret: 从栈顶弹出一个数值送给EIP,跳转回原程序,相当于
pop %eip调用过程
主程序执行 pushq $20,pushq $10,然后执行 CALL。此时,参数和返回地址都在栈里了。
地址(高->低) 内存/堆栈空间 (Stack) CPU 内部寄存器状态
============== ======================= ===================================
0x...e040 [ ... ] PC = 0x4005a1 (指向 CALL 指令)
0x...e038 [ 参数 b (20) ] RSP = 0x4005e020
0x...e030 [ 参数 a (10) ]
0x...e028 [ 返回地址 (0x4005a6) ] <---RSP (CALL 指令硬件自动压入的)进入 func 后,它会先执行 pushq %rbp; movq %rsp, %rbp 锁定新栈底。子程序通过 偏移(%rbp) 去栈里读取参数。
RSP 减少,开辟栈帧空间,存放局部变量
地址(高->低) 内存/堆栈空间 (Stack) CPU 内部寄存器状态
============== ======================= ===================================
0x...e038 [ 参数 b (20) ] <--- 24(%rbp) (找到参数b)
0x...e030 [ 参数 a (10) ] <--- 16(%rbp) (找到参数a)
0x...e028 [ 返回地址 (0x4005a6) ]
0x...e020 [ 老 RBP 备份 ] <--- RBP = 0x...e020 (func 的栈底基地)
0x...e018 [ func 局部变量/工作区 ]
...
0x...e008 [ ] <--- RSP = 0x...e018 (func 的栈顶)计算结果存入 RAX=30。子程序执行 leave 撤销自己的栈帧(把 RSP 弹回指向返回地址的地方),然后执行 RET。
地址(高->低) 内存/堆栈空间 (Stack) CPU 内部寄存器状态
============== ======================= ===================================
0x...e038 [ 参数 b (20) ] PC = 0x4005c2 (指向 RET 指令)
0x...e030 [ 参数 a (10) ] RAX = 30 (返回值放在寄存器里)
0x...e028 [ 返回地址 (0x4005a6) ] <---RSPRET 指令让 PC 跳回 0x4005a6。此时主程序接管,主程序执行 addq $16, %rsp,把参数占用的存储空间释放。
地址(高->低) 内存/堆栈空间 (Stack) CPU 内部寄存器状态
============== ======================= ===================================
0x...e040 [ ... ] PC = 0x4005a6 (回到 main)
0x...e038 [ 被废弃的参数 b (20) ] <---RSP RAX = 30 (main 直接拿走消费)
0x...e030 [ 被废弃的参数 a (10) ]
(此时 RSP 弹回到了压参前的位置,堆栈完美平衡!)串操作指令
不考
中断指令
INT n:软中断- 依次压入当前进程堆栈:EFLAGS,CS,RIP
- 读取中断号n,通过IDTR找到IDT,获取中断处理程序地址
- 获取的地址加载到CS、EIP跳转执行
IRET:中断返回,恢复EIP、CS、EFLAGS
SIMD
Single Instruction, Multiple Data,单指令多数据流 一种并行计算架构,它允许单条指令同时对多个数据点执行相同的操作,提高计算效率。e.g. vmovups, vaddpd.