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的地址在链接前是相对地址,需重定位) | 只读(链接用) | 链接阶段修正指令地址 |

相关推荐
embrace995 小时前
【C语言学习】scanf函数
c语言·开发语言·汇编·学习·青少年编程·c#·编辑器
麦兜*5 小时前
【算法】十大排序算法超深度解析,从数学原理到汇编级优化,涵盖 15个核心维度
java·汇编·jvm·算法·spring cloud·ai·排序算法
CHANG_THE_WORLD1 天前
NEG指令说明
汇编·逆向·neg
南玖yy6 天前
Linux 桌面市场份额突破 5%:开源生态的里程碑与未来启示
linux·运维·服务器·汇编·科技·开源·gradle
GeekMax6 天前
(笔记)U-boot 2012.10 armv7启动汇编解析
汇编
南玖yy8 天前
Linux权限管理:从“Permission denied“到系统安全大师
linux·运维·汇编·后端·架构·系统安全·策略模式
Kira Skyler9 天前
c++,从汇编角度看lambda
汇编·c++
暗流者9 天前
学习pwn需要的基本汇编语言知识
汇编·学习·网络安全·pwn
单车少年ing12 天前
ARM64---C中调用汇编指令
汇编