x86_64汇编

1.CPU分类

目前主要就是两种架构,分别是x86架构和arm架构。

  • x86架构:

    • x86、x86_64,分别是x86架构的32位版本和64位版本,后者也兼容32位

    • amd64,跟x86_64一样

  • arm架构

    • arm64架构

    • aarch64架构

2.x86_64架构

2.1寄存器

寄存器是计算机中央处理器(CPU)内部的一种高速存储部件,用于临时存放数据、指令和地址等信息,是 CPU 进行运算和控制的核心组件之一。

2.1.1通用寄存器

x86_64 共有 16 个 64 位通用寄存器,每个寄存器可根据操作数长度(64 位、32 位、16 位、8 位)使用不同的名称(通过寄存器名后缀区分)。这些寄存器既可以用于数据运算,也可用于地址存储,部分寄存器还保留了特殊用途。

|--------|----------|----------|----------|-----------------------------------------------------------|
| 64 位名称 | 32 位名称 | 16 位名称 | 低 8 位名称 | 主要用途(通用 + 特殊) |
| rax | eax | ax | al | 累加器,默认用于乘法 / 除法结果;函数返回值(ABI 约定) |
| rbx | ebx | bx | bl | 基址寄存器,可作为内存访问的基地址;被调用者保存寄存器(ABI 约定) |
| rcx | ecx | cx | cl | 计数器,用于循环(如rep指令)和移位操作(cl存放移位位数) |
| rdx | edx | dx | dl | 数据寄存器,乘法 / 除法时存放高半部分结果;I/O 指令中作为端口地址 |
| rsi | esi | si | sil | 源索引寄存器,字符串操作中指向源数据;函数参数传递(ABI 约定) |
| rdi | edi | di | dil | 目的索引寄存器,字符串操作中指向目标地址;函数第一个参数(ABI 约定) |
| rbp | ebp | bp | bpl | 基址指针,传统上用于栈帧基地址(栈回溯);被调用者保存寄存器 |
| rsp | esp | sp | spl | 栈指针,始终指向栈顶,用于栈操作(压栈 / 弹栈),不可随意修改 |
| r8-r15 | r8d-r15d | r8w-r15w | r8b-r15b | 新增 64 位寄存器,功能通用,r8-r11为调用者保存寄存器,r12-r15为被调用者保存寄存器(ABI 约定) |

通用寄存器的特殊用途说明:

  1. ABI 约定:在 Linux/UNIX 系统的函数调用中,寄存器的使用遵循 System V AMD64 ABI 标准:

    1. 函数参数传递:rdi(第 1 个)、rsi(第 2 个)、rdx(第 3 个)、rcx(第 4 个)、r8(第 5 个)、r9(第 6 个),超出部分通过栈传递。

    2. 返回值:rax(64 位)、rdx:rax(128 位,如乘法结果)。

    3. 寄存器保存规则:rbxrbprspr12-r15由被调用者保存(需恢复原值),其余由调用者自行保存。

  2. 字符串操作:rsi(源地址)、rdi(目标地址)、rcx(操作长度)配合movs(移动)、cmps(比较)等指令实现高效字符串处理。

  3. 栈操作:rsp是栈顶指针,每次push/pop指令会自动修改其值;rbp可选作为栈帧基地址,用于定位函数参数和局部变量。

2.1.2标志位寄存器

标志位寄存器(64 位为rflags)是一个特殊寄存器,用于存储运算结果的状态和 CPU 的控制信息,共包含 19 个标志位(部分为保留位)。每个标志位占 1 位(0 或 1),主要分为状态标志和控制标志两类。

  1. 状态标志(Status Flags)

记录算术 / 逻辑运算的结果特征,用于条件跳转等指令判断:

|----------|--------------------------------|-----------------------------------------------------|
| 标志位(位位置) | 名称 | 含义 |
| 0 位 | CF(Carry Flag) | 进位 / 借位标志:无符号运算结果超出范围时置 1(如 32 位加法结果≥2³²);减法借位时置 1。 |
| 2 位 | PF(Parity Flag) | 奇偶标志:运算结果的低 8 位中 1 的个数为偶数时置 1(用于数据校验)。 |
| 4 位 | AF(Auxiliary Carry Flag) | 辅助进位标志:低 4 位向高 4 位进位 / 借位时置 1(主要用于 BCD 码运算)。 |
| 6 位 | ZF(Zero Flag) | 零标志:运算结果为 0 时置 1(如a - b = 0则ZF=1)。 |
| 7 位 | SF(Sign Flag) | 符号标志:结果为负数时置 1(以补码最高位为判断依据)。 |
| 8 位 | TF(Trap Flag) | 陷阱标志(控制标志):置 1 时 CPU 进入单步调试模式,每条指令执行后触发中断。 |
| 9 位 | IF(Interrupt Enable Flag) | 中断允许标志(控制标志):置 1 时允许 CPU 响应可屏蔽中断(如外部设备中断)。 |
| 10 位 | DF(Direction Flag) | 方向标志(控制标志):字符串操作中,DF=0时地址递增(从低到高),DF=1时地址递减。 |
| 11 位 | OF(Overflow Flag) | 溢出标志:有符号运算结果超出范围时置 1(如 32 位有符号数运算结果>2³¹-1 或<-2³¹)。 |
| 14 位 | NT(Nested Task Flag) | 嵌套任务标志:用于多任务切换,指示当前任务是否嵌套在另一个任务中(现代系统较少使用)。 |
| 16 位 | RF(Resume Flag) | 恢复标志:调试相关,置 1 时暂时忽略调试异常(避免调试断点重复触发)。 |
| 17 位 | VM(Virtual-8086 Mode) | 虚拟 8086 模式标志:置 1 时 CPU 运行在虚拟 8086 模式(兼容 16 位程序)。 |
| 18 位 | AC(Alignment Check) | 对齐检查标志:置 1 时检测内存访问的地址对齐错误(仅在特权级 3 有效)。 |
| 19 位 | VIF(Virtual Interrupt Flag) | 虚拟中断标志:与VIP配合,在虚拟 8086 模式下模拟IF标志。 |
| 20 位 | VIP(Virtual Interrupt Pending) | 虚拟中断挂起标志:指示虚拟中断是否挂起。 |
| 21 位 | ID(Identification Flag) | 识别标志:用于检测 CPU 是否支持 CPUID 指令(可通过指令修改)。 |

  1. 控制标志(Control Flags)

用于控制 CPU 的运行模式或行为,上述TFIFDF均属于控制标志,其值可通过指令(如sti/cli修改IFcld/std修改DF)主动修改。

2.1.3指令指针寄存器

  1. 指令指针寄存器(rip

    1. 64 位指令指针,存放下一条要执行的指令的地址,CPU 执行完当前指令后自动根据指令长度递增rip

    2. 不可通过mov直接修改,需通过jmpcallretjcc(条件跳转)等指令间接更新,是程序流程控制的核心。

2.1.4段寄存器

段寄存器是 x86 架构历史遗留的重要组件,最初用于解决 16 位 CPU 的内存寻址限制(通过 "段基址 + 偏移量" 扩展地址空间)。在 64 位模式下,段寄存器的功能被大幅简化,但仍保留用于内存保护和线程局部存储(TLS)。

x86_64 有 6 个 16 位段寄存器:csdsesfsgsss,每个寄存器存储一个 "段选择子"(Segment Selector),用于索引全局描述符表(GDT)或局部描述符表(LDT),获取段的基地址、限长和权限信息。

各段寄存器的作用:

  1. cs(Code Segment,代码段寄存器)

    1. 指向当前执行代码所在的内存段,包含代码的基地址和执行权限(如特权级)。

    2. 在 64 位模式下,cs的特权级(CPL)决定 CPU 当前运行级别(0 为内核态,3 为用户态),是系统安全的核心控制之一。

    3. 不可通过mov指令直接修改(需通过jmpcallret等控制转移指令间接更新)。

  2. ss(Stack Segment,栈段寄存器)

    1. 指向当前栈所在的内存段,与rsp配合定义栈的地址范围和权限。

    2. 64 位模式下,栈段的基地址通常被忽略(视为 0),但段的权限检查仍有效(如防止用户态访问内核栈)。

  3. ds(Data Segment,数据段寄存器) es(Extra Segment,附加数据段寄存器)

    1. 传统上用于访问数据段内存,64 位模式下默认基地址为 0,主要用于兼容 16/32 位程序。

    2. 现代系统中,dses通常不主动修改,默认指向全局数据段。

  4. fsgs(额外段寄存器)

    1. 功能灵活,无固定用途,由操作系统自定义:

      • 在 Linux 中,fs用于用户态线程局部存储(TLS),通过fs:[0]可访问当前线程的局部变量;gs用于内核态(如存放当前进程描述符task_struct指针)。

      • 在 Windows 中,gs用于用户态 TLS 和异常处理(如gs:[0x30]指向线程信息块 TEB)。

2.2常见汇编指令

x86_64 架构的汇编指令集非常丰富,涵盖数据传输、算术运算、逻辑操作、控制流、栈操作等多个类别。以下是最常用的汇编指令及其功能说明,以 AT&T 语法(Linux 环境常用)为例:

2.2.1数据传输指令

用于在寄存器、内存和立即数之间传递数据。

|------|-------------------|-----------------------------------------------------------------------------|
| 指令 | 示例 | 功能说明 |
| mov | mov %rax, %rbx | 将rax的值复制到rbx(寄存器→寄存器) mov 0x10(%rbp), %rdx(内存→寄存器) mov $0x20, %ecx(立即数→寄存器) |
| push | push %rax | 将rax的值压入栈(栈顶指针rsp减 8) |
| pop | pop %rbx | 从栈顶弹出值到rbx(栈顶指针rsp加 8) |
| lea | lea 4(%rdi), %rsi | 计算内存地址(rdi+4)并存储到rsi(常用于地址计算,非数据加载) |
| xchg | xchg %rax, %rbx | 交换rax和rbx的值 |

2.2.2算数与逻辑指令

用于数值计算和位操作。

  • 算数运算

|------|----------------|------------------------------------|
| 指令 | 示例 | 功能说明 |
| add | add 5, %eax | eax += 5(结果影响进位标志CF、零标志ZF等) | | sub | sub %rbx, %rax | rax -= rbx(结果影响借位标志CF等) | | mul | mul %rbx | 无符号乘法:rdx:rax = rax \* rbx(64 位结果) | | imul | imul 3, %ecx | 有符号乘法:ecx *= 3 |
| div | div %rbx | 无符号除法:rax = (rdx:rax) / rbx,余数存rdx |
| idiv | idiv %rcx | 有符号除法:rax = (rdx:rax) / rcx,余数存rdx |
| inc | inc %edi | edi += 1(不影响进位标志CF) |
| dec | dec %edx | edx -= 1(不影响进位标志CF) |

  • 逻辑操作

|-----|----------------|----------------------------------|
| 指令 | 示例 | 功能说明 |
| and | and 0xFF, %al | al \&= 0xFF(位与,常用于清高位) | | or | or %rbx, %rax | rax = rbx(位或,常用于置位) | | xor | xor %rax, %rax | rax \^= rax(结果为 0,常用于清零寄存器) | | not | not %rdx | rdx = \~rdx(位取反) | | shl | shl 2, %eax | eax <<= 2(逻辑左移,低位补 0,影响进位标志CF) |
| shr | shr 1, %ebx | ebx \>\>= 1(逻辑右移,高位补 0) | | sar | sar 3, %ecx | ecx >>= 3(算术右移,高位补符号位) |

2.2.3控制流指令

用于改变程序执行顺序(分支、循环、函数调用)。

  • 无条件跳转

|-----|-----------|---------------|
| 指令 | 示例 | 功能说明 |
| jmp | jmp label | 跳转到标签label处执行 |

  • 条件跳转

|---------|------------|-------------------|
| 指令 | 依赖标志 | 跳转条件 |
| je/jz | ZF=1 | 相等(Equal)或零(Zero) |
| jne/jnz | ZF=0 | 不相等或非零 |
| jg/jnle | ZF=0且OF=SF | 有符号大于(Greater) |
| jl/jnge | OF≠SF | 有符号小于(Less) |
| ja/jnbe | CF=0且ZF=0 | 无符号大于(Above) |
| jb/jnae | CF=1 | 无符号小于(Below) |

  • 函数调用和返回

|------|-----------|--------------------------------|
| 指令 | 示例 | 功能说明 |
| call | call func | 调用函数func:压入返回地址(rip)到栈,跳转到函数入口 |
| ret | ret | 函数返回:从栈弹出返回地址到rip,继续执行调用处后续指令 |

  • 循环控制

|---------------|--------------|--------------------------------|
| 指令 | 示例 | 功能说明 |
| loop | loop label | rcx -= 1,若rcx≠0则跳转到label(循环计数) |
| loope/loopz | loope label | rcx -= 1,若rcx≠0且ZF=1则跳转 |
| loopne/loopnz | loopne label | rcx -= 1,若rcx≠0且ZF=0则跳转 |

2.2.4栈与帧的操作

用于函数调用时的栈帧管理。

|-------|-----------------|------------------------------------------------------------------|
| 指令 | 示例 | 功能说明 |
| enter | enter 0x10, 0 | 创建栈帧:等价于push %rbp; mov %rsp, %rbp; sub $0x10, %rsp(分配 16 字节局部变量) |
| leave | leave | 销毁栈帧:等价于mov %rbp, %rsp; pop %rbp |

2.2.5字符串操作指令

配合rsi(源)、rdi(目标)、rcx(计数)寄存器高效处理字符串

|-------------|------------|--------------------------------------------------------|
| 指令 | 示例 | 功能说明 |
| movsb | rep movsb | 复制字节:[rdi] = [rsi],rsi±1,rdi±1,rcx-1(rep重复直到rcx=0) |
| movsw/movsq | rep movsq | 复制字(2 字节)/ 双字(8 字节),步长为 2/8 |
| cmpsb | repe cmpsb | 比较字节:[rsi] - [rdi],更新标志位,repe重复直到rcx=0或ZF=0 |
| stosb | rep stosb | 存储字节:[rdi] = al,rdi±1,rcx-1(常用于填充内存) |

2.3汇编相关的一些命令

Linux下

gcc 相关

  • 预处理操作:gcc -E main.c -o main.i

    直接cat或者more查看

    • 展开#include,将头文件内容插入当前文件中

    • 处理#define,替换宏定义

    • 条件编译,处理#if/#else/#endif等,根据条件保留删除代码块

    • 删除注释,将//或者/*注释删除

    • 添加行号和文件名表示,便于后面编译阶段报错定位

  • 编译操作:gcc -S main.i -masm=intel -o main.s 直接more、cat、vi查看

    • 词法分析,语法分析,语义分析

    • 代码优化:常量折叠,循环展开,根据优化级别决定

    • 生成汇编代码

  • 汇编操作:gcc -c main.s -o main.o

    (.o)文件是二进制文件,无法直接执行,缺少库依赖和入口地址。

    bash 复制代码
    objdump -d main.o #反汇编目标文件,查看机器码对应的汇编命令。 
    readelf -S main.o #查看目标文件的段信息。
    • 汇编指令翻译:翻译为对应的机器码。

    • 符号表构建:记录变量函数的地址。

    • 段划分,将代码、数据分别放入text段、data段。

  • 链接操作:gcc main.o -o main

    bash 复制代码
    objdump -d main # 反汇编可执行文件,查看完整机器码 
    readelf -h main # 查看ELF文件头(包含入口地址等信息)
    • 符号解析:将目标文件中未定义的符号与库文件中实现关联。

    • 重定位:修改目标文件中的相对地址为绝对地址。

    • 合并段:将所有目标文件中的段合并为统一的段。

    • 条件ELF头:包含程序入口地址main,运行时信息等。

|-----------------|------------------|------------------------------------------------------------|---------|-------------------------------------------|
| 段类型 | 核心作用 | 存储内容 | 访问权限 | 典型场景 |
| .text | 存放可执行指令 | 函数体的机器码(如main、printf的实现)、跳转指令、运算指令等 | 只读、可执行 | int add(int a, int b) { return a+b; }的机器码 |
| .data | 存放已初始化的全局 / 静态变量 | 全局变量(如int g_var = 10;)、静态变量(如static int s_var = 20;) | 可读、可写 | 程序启动时就有初始值的变量 |
| .bss | 存放未初始化的全局 / 静态变量 | 未初始化全局变量(如int g_uninit;)、未初始化静态变量(如static int s_uninit;) | 可读、可写 | 初始值为 0 或未指定的变量 |
| .rodata | 存放只读常量 | 字符串常量(如"hello, world")、const修饰的全局变量(如const int MAX = 100;) | 只读 | printf("hello")中的字符串 |
| .stack | 函数调用时的临时数据区 | 局部变量(如int a = 5;)、函数参数、返回地址、栈帧信息 | 可读、可写 | 函数调用时的参数传递和局部变量存储 |
| .heap | 动态内存分配区域 | 程序运行时通过malloc/new申请的内存(如int* p = malloc(4);) | 可读、可写 | 动态大小的数据(如链表、数组) |
| .symtab/.strtab | 符号表与字符串表 | 函数名、变量名及其地址(如main函数的地址、g_var的地址) | 只读(调试用) | 调试器(如gdb)查找变量 / 函数位置 |
| .rel.text | 重定位信息 | 代码段中需要修正的地址(如call printf的地址在链接前是相对地址,需重定位) | 只读(链接用) | 链接阶段修正指令地址 |

相关推荐
我在人间贩卖青春6 天前
汇编之伪指令
汇编·伪指令
我在人间贩卖青春7 天前
汇编之伪操作
汇编·伪操作
济6177 天前
FreeRTOS基础--堆栈概念与汇编指令实战解析
汇编·嵌入式·freertos
myloveasuka7 天前
汇编TEST指令
汇编
我在人间贩卖青春7 天前
汇编编程驱动LED
汇编·点亮led
我在人间贩卖青春7 天前
汇编和C编程相互调用
汇编·混合编程
myloveasuka8 天前
寻址方式笔记
汇编·笔记·计算机组成原理
请输入蚊子8 天前
《操作系统真象还原》 第六章 完善内核
linux·汇编·操作系统·bochs·操作系统真像还原
myloveasuka8 天前
指令格式举例
汇编·笔记·计算机组成原理
我在人间贩卖青春8 天前
汇编之分支跳转指令
汇编·arm·分支跳转