常用术语速查手册

2021/12/20 Tools 共 3004 字,约 9 分钟

汇编语言

>> CNAME=<name>
# 源代码编译成汇编
## -fno-omit-frame-pointer : 不使用ebp
>> gcc -O2 -m32 -S -masm=intel $CNAME.c -o $CNAME.S
# 汇编代码至二进制模块 
## -gstabs/ -gstabs+ : 以 stabs 格式声称调试信息
## --32 : 生成32位obj文件
>> as -o $CNAME.o $CNAME.S
# 链接二进制模块成可执行文件
## -m elf_i386: 生成i386架构的ELF文件
## 这一步如果有错误,则安装一下库
## sudo apt-get install g++-multilib
>> ld -o $CNAME $CNAME.o

计算机体系基本结构

指令集

  • CISC
    • 不定长指令
    • 一个字(word)= 2 Byte = 16 bits
    • x86
  • RISC
    • 大部分位定长指令, 只能通过LOADSTORE访问内存
    • 一个字(word)= 4 Byte = 32 bits
    • MIPS, ARM

机器字长

硬件架构寻址空间
32位4 GB
64位~
x86-6448位宽,256TB

逻辑运算符 | 符号 | 英文 | 名称 | 含义 | | :-: | :-: | :-: | :-: | | \& | and | 与 | 都真则真 | | | | or | 或 | 有真则真 | | ~ | not | 非 | 取反 | | \^ | xor | 异或 | 不同则真 相同则假 |

寄存器

通用寄存器

栈寄存器

指令寄存器

C语言与汇编

从C代码生成汇编代码

# 使用gcc生成 32位的Intel风格汇编代码
>> gcc -O2 -m32 -fno-omit-frame-pointer -S -masm=intel code.c -o code.S

汇编语言数据格式

第一条汇编指令

C代码:

int add(int x, int y) {
    int t = x + y;
    return t;
}

汇编代码:

# AT&T
addl 8(%ebp) %eax
# Intel
add  eax, [ebp+8]

内存寻址模式

间接寻址 Mem[Reg[R]]

# 寄存器R指向的内存地址
## AT&T
MOVL (%ecx) %eax
## Intel
MOV eax, [ecx]

基址+偏移量寻址 Mem[Reg[R]+D]

# 寄存器R指向内存起始地址, 常数D指定偏移量
## AT&T
MOVL 8(%ebp) %edx
## Intel
MOV edx, [ebp+8]

变址寻址 D(Rb,Ri,S), Mem[Reg[Rb]+S*Reg[Ri]+D]

# D:  常量, 即地址偏移量
# Rb: 基址寄存器: 8个寄存器都可以
# Ri: 索引寄存器: 除esp外都可以(通常不会使用ebp)
# S:  比例因子 1,2,4,8

1. 数据传送指令 MOV

将一个双字从Source移动到Dest

# AT&T
MOVL <源操作数> <目的操作数>
# Intel
MOV  <目的操作数>, <源操作数>

允许的操作数类型:

  • 立即数
    • AT&T: $0x400, $-533
    • Intel: 0xff
    • 可以被 1/2/4 字节表示
  • 寄存器
    • eax, edx, ecx, ebx, esi, edi, esp, ebp
  • 存储器(两个操作数不能同时位内存地址)
    • 四个连续字节, 支持多种寻址模式

变种

地址计算指令 LEA

将计算出来的地址赋值给Dest, 与MOV最大的区别是,它不会访问内存

常用于地址计算, 可以用于表达式x+k*y(k=1,2,4,8)的计算

// 地址计算
int *p = &x[i];
// 特定模式的整数计算
/*
MOV eax, [ebp+8]
MOV edx, [ebp+12]
*/
int f(int x, int y, int z) {
    // LEA ECX, [0 + EAX*4]
    int n1 = x + y;
    int n2 = z + n1;
}

常用算数指令

双操作数指令

格式计算说明
ADD dst, srcdst=dst + src 
SUB dst, srcdst=dst - src 
IMUL dst, srcdst=dst * src 
SAL dst, srcdst=dst « src逻辑左移
SHL dst, srcdst=dst « src逻辑左移
SAR dst, srcdst=dst » src算数右移
SHR dst, srcdst=dst » src逻辑右移
XOR dst, srcdst=dst ^ src 
AND dst, srcdst=dst & src 
OR dst, srcdst=dst | src 

单操作数指令

格式计算说明
INC dstdst=dst + 1自增
DEC dstdst=dst - 1自减
NEG dstdst=0 - dst取反
NOT dstdst= ~ dst取非

控制流

条件码

条件码英文名含义
CFCarry Flag无符号整数溢出, 则为1
SFSign Flag如果运算结果<0, 则为1
ZFZero Flag如果运算结果=0, 则为1
OFOverflow Flag带符号整数运算溢出, 则为1

比较指令 CMP

# 计算SRC2-SRC1, 并且将计算结果存储到条件码中, 不会改变目的操作数
CMP SRC1, SRC2

测试指令 TEST

# 计算SRC1&SRC2, 并设置相应条件码, 但是不改变目的操作数
# CF与OF为0
TEST SRC1, SRC2

读取条件码的指令 SET*

####

栈调用过程

压栈指令: PUSH

栈顶增长4字节(esp=esp-4), 然后将操作数放在栈顶

PUSH Src
# 等效于
MOV edx, src
SUB esp, 4
MOV [esp], edx

出栈指令: POP

将栈顶内容存入目的操作数中,然后栈顶减小4字节(esp=esp+4)

POP Dst
# 等效于
MOV dst, [esp]
ADD esp, 4

过程调用指令: CALL

将返回地址压入栈, 并且跳转到label

CALL Label
# 等效于
PUSH retaddr
JMP  label

eax寄存器通常用于存放函数返回值

过程返回指令: RET

调用ret指令时, 栈顶指向返回地址, ret会跳转至栈顶指向的返回地址

RET
# 等效于
POP edx
JMP edx

栈帧的创建与释放

  • 存储内容
    • 局部变量
    • 返回地址
    • 零时空间
  • 栈帧的分配与释放
    • 进入过程先分配栈帧空间
      • “Set-Up” Code
    • 过程返回时释放栈帧空间
      • “Finish” Code

当前栈帧中存储的内容

  • 子过程的参数
  • 局部变量(因为寄存器数目有限)
  • 父过程的寄存器值
  • 父过程栈帧的基地址(old ebp)

Set-Up过程

  • PUSH ebp 将父栈的基址压入栈
  • MOV ebp, esp 将ebp设置为栈顶地址
  • PUSH ebx 存储子过程会修改的寄存器值

Finish过程

  • MOV ebx, [ebp-4] 恢复父过程的ebx值
  • MOV esp, ebp 让esp指向当前栈的栈底
  • POP ebp 将esp指向的old-ebp存入ebp中,并让栈顶指向ret addr
  • RET 将eip寄存器设置为esp指向的地址

寄存器使用惯例

  • 栈寄存器
    • ebp: 栈底地址寄存器(栈基址寄存器)
    • esp: 栈顶地址寄存器
  • 被调用者(callee)保存寄存器
    • ebx, esi, edi
  • 调用者(caller)保存寄存器
    • eax, edx, ecx
    • eax: 用于存放函数返回值

####

C语言运行时内存结构

汇编指示

数据段 .section

  • .rodata
    • 只读数据段
  • .data
    • 声明带有初始值的数据内容
  • .bss
    • 声明无需初始化的内容
    • .comm 声明未初始化的全局内存区域
    • .lcomm 声明未初始化的本地内存区域
    • eg:
      # 声明1000字节长度的内存,外部模块无法访问
      .section .bss
          .lcomm buffer, 1000
      
  • .text
    • 代码段,存放汇编代码

参考

文档信息

搜索

    Table of Contents