x86_64汇编

发布于:2025-07-31 ⋅ 阅读:(18) ⋅ 点赞:(0)

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)文件是二进制文件,无法直接执行,缺少库依赖和入口地址。

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

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

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

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

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


网站公告

今日签到

点亮在社区的每一天
去签到