Windows x64汇编深入:RDI寄存器与REP MOVSB内存复制实战
发布时间:2026/10/2 14:05:49来源:尧图网络
很多学习 Windows 底层编程的朋友第一次打开 Visual Studio 的寄存器窗口或者用 WinDbg 输入r命令时都会看到一排以 R 开头的寄存器RAX、RBX、RCX、RDX、RSI、RDI…… 其中 RDI 的全称是 Destination Index Register也就是“目标索引寄存器”。光看名字很难直观理解它到底负责什么它和 C 语言的指针有什么关系为什么 Windows 反汇编里经常看到rep movsb配合 RDI 使用为什么网上很多 Linux 汇编教材说第一个参数放在 RDI而你在 Windows 上看到的第一个参数却在 RCX这篇文章以 Windows x64 为主战场把 RDI 彻底讲透。我们会先梳理寄存器的基础概念再对比 Windows 和 Linux 两套调用约定的差异然后从 C 指针编译成汇编的过程来理解 RDI 的作用最后手写一个基于REP MOVSB的汇编 memcpy 函数并用调试器单步观察 RDI 的变化。全文配有完整可运行的代码和常见报错排查表适合刚接触 x64 汇编、想打通 C 语言与底层原理的读者。1. 背景与核心概念1.1 寄存器是什么RDI 解决什么问题在 CPU 和内存之间有一批容量很小但速度极快的存储单元叫做寄存器。x64 指令集一共提供了 16 个通用寄存器General Purpose RegistersGPR。CPU 不能直接对“内存里的两个数做加法”绝大多数指令必须先操作数加载到寄存器计算完再存回内存。所以寄存器是 C 语言变量、指针、函数参数在机器码层面的“临时房间”。RDI 就是这 16 个通用寄存器之一。它在设计之初的主要用途是作为“字符串操作的目标地址”。比如REP MOVSB这条指令会把RSI指向的内存数据复制到RDI指向的内存区域。只要你在做内存复制、缓冲区填充、字符串比较RDI 大概率会出场。从 C 语言的角度看RDI 不是一个固定对应某个变量的寄存器。编译器会根据优化策略自由分配寄存器。但当我们写char *p并通过p遍历内存时底层完全可能被编译成inc rdi这样的指令。换句话说C 指针是抽象层RDI 是实现层。理解 RDI就是理解指针在机器码里到底怎么走。1.2 从 DI、EDI 到 RDI 的命名演变这个寄存器不是一开始就叫 RDI。x86 体系经历了 16 位、32 位、64 位三个阶段寄存器的名字也随之变化寄存器名称位数说明RDI64 位完整的目标索引寄存器EDI32 位RDI 的低 32 位DI16 位RDI 的低 16 位DIL8 位RDI 的最低 8 位x64 新增在 16 位时代目标索引寄存器叫 DI用来存放段内偏移地址。进入 32 位保护模式后寄存器扩展到 32 位改名 EDI可以覆盖 4GB 地址空间。到了 x64 时代地址宽度变成 64 位改名 RDI。命名规则有一个规律R前缀表示 64 位E前缀表示 32 位无前缀表示 16 位。这里有一个容易忽略的新特性x64 模式下通过 REX 前缀可以访问DIL也就是 RDI 的最低 8 位。32 位时代无法单独访问 DI 的低字节x64 把 8 个低字节寄存器全部补齐了这也让寄存器分配的粒度更细。1.3 RDI 与 RSI一对“搬运工”组合RDI 通常不会单独行动它的老搭档是RSI。RSI 的全称是 Source Index Register源索引寄存器。两者的分工非常明确RSI 指向“源”RDI 指向“目标”。x86/x64 提供了一批针对这对寄存器的字符串操作指令常见的有指令作用RDI/RSI 的变化MOVSB/W/D/Q从 RSI 复制数据到 RDI两者都自动递增或递减STOSB/W/D/Q把 AL/AX/EAX/RAX 写入 RDI 指向的内存只改变 RDILODSB/W/D/Q从 RSI 加载数据到 AL/AX/EAX/RAX只改变 RSICMPSB/W/D/Q比较 RSI 与 RDI 指向的内存数据两者都自动递增或递减SCASB/W/D/Q用 AL/AX/EAX/RAX 扫描 RDI 指向的数据只改变 RDI在早期的 x86 内存模型里这些指令默认使用 DS:RSI 作为源地址、ES:RDI 作为目标地址。现代 Windows 运行在平坦内存模型下段寄存器基本不再参与寻址但 RSI/RDI 的分工被保留了下来。所以只要看到一路反汇编中出现rep movsb你基本可以确定 RDI 就是目标缓冲区的地址。1.4 REP 前缀让 RDI 自动“行走”单纯执行一次MOVSB只能复制一个字节。如果要在汇编里复制一整块内存最笨的办法是用循环一条条执行。x86 为此提供了REPRepeat前缀放在字符串指令前表示重复执行直到RCX变为 0。每执行一次带REP的字符串指令硬件会自动完成三件事按操作数据宽度修改地址指针复制 1 字节时 RDI/RSI 各加 1复制 4 字节时各加 4。自动修改方向如果方向标志 DF 0地址递增如果 DF 1地址递减。RCX 减 1直到 RCX 为 0指令结束。这就是 RDI 最迷人的地方它不是一个需要手动维护的循环变量而是被 CPU 微码自动推进的地址游标。编译器在优化memcpy、memset、strlen这类函数时经常把地址放入 RSI/RDI再配合rep movsb或rep stosb完成批量操作。2. 环境准备与工具链2.1 本文实验环境本文示例依赖 Windows 10 或 Windows 11 的 x64 环境。所有寄存器名称、调用约定、汇编指令都是针对 64 位模式的如果你用 32 位环境会看到EDI而不是RDI。需要安装的软件如下Visual Studio 2022 社区版或者 Visual Studio Build Tools。Windows SDK其中包含 WinDbg 调试工具。版本不需要完全一致重点是掌握思路。如果你已经有 Visual Studio 2019 或 2017示例代码同样能编译。2.2 cl.exe 与 ml64.exeMSVC 编译器的主程序是cl.exe64 位汇编器是ml64.exe。这两个工具都在 Visual Studio 安装目录下但环境变量默认不配置。最省事的方法是从开始菜单打开x64 Native Tools Command Prompt for VS 2022。打开后先验证一下cl正常情况下会输出版本信息以及用法说明。再验证汇编器ml64如果系统提示“不是内部或外部命令”说明没有进入正确的命令行环境。此时可以手动调用 vcvars64.bat路径大致如下需要按你的安装目录调整call C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat2.3 寄存器窗口与 WinDbg调试阶段需要观察 RDI 的实时变化最方便的是 Visual Studio 自带的寄存器窗口菜单栏选择调试 - 窗口 - 寄存器。快捷键是 Ctrl Alt G。在寄存器窗口中找到 RDI 行右键可以切换显示格式。如果之后你打算深入内核调试、分析异常上下文可以安装 WinDbg。调试用户态程序时输入r查看全部寄存器输入r rdi只查看 RDIr r rdiWinDbg 的显示格式和 Visual Studio 略有差异但寄存器名称是一致的。2.4 示例项目结构本文实战部分会用到两个文件rdi_demo/ ├── main.c └── memcpy_asm.asmmain.c负责声明外部汇编函数并调用memcpy_asm.asm是手写的汇编代码用 RDI/RSI 完成内存复制。3. Windows x64 调用约定中的 RDI3.1 参数寄存器别搞混Windows 和 Linux 是两个世界这是最容易踩坑的地方。很多汇编教材以 Linux 下的 System V AMD64 ABI 为基准其中第 1 个整数参数放在 RDI第 2 个放在 RSI第 3 个放在 RDX第 4 个放在 RCX。于是大量读者形成惯性思维RDI 是参数寄存器。但 Windows x64 的调用约定完全不同。Microsoft x64 calling convention 规定前四个整数参数依次放在RCX、RDX、R8、R9从第 5 个参数开始才压栈。参数序号Windows x64Linux/macOS System V第 1 个整数参数RCXRDI第 2 个整数参数RDXRSI第 3 个整数参数R8RDX第 4 个整数参数R9RCX第 5 个整数参数栈R8第 6 个整数参数栈R9第 7 个以后栈栈所以当你用 Visual Studio 反汇编一个 Windows API 函数时如果看到开头是mov rcx, qword ptr [rsp8]不要奇怪。这正是 Windows 的传参方式。如果你拿 Linux 的规则去读 Windows 代码很容易把参数完全看反。3.2 RDI 是“被调用者保存”寄存器Windows x64 不仅规定参数放哪里还规定寄存器由谁负责保护。寄存器被分成两类类型寄存器列表含义volatile调用者保存RAX、RCX、RDX、R8-R11、XMM0-XMM5子函数内部可以随意修改调用方自己备份好需要的数据non-volatile被调用者保存RBX、RBP、RDI、RSI、R12-R15、XMM6-XMM15子函数如果要使用必须先把旧值压栈保存返回前恢复原值RDI 和 RSI 都属于 non-volatile也就是被调用者保存寄存器。这意味着只要你在自己写的汇编函数里使用了 RDI却没有在函数开头push rdi、返回前pop rdi调用方看到 RDI 被改变后程序很可能立刻崩溃或产生隐蔽的数据错误。这个规则不是汇编语言强制要求的而是 ABI 层面的“契约”。CPU 不会阻止你修改 RDI但操作系统、编译器、调试器都遵守这套契约。破坏契约的代码在单独运行时可能没事一旦集成到大型项目里就会出诡异问题。3.3 编译器与操作系统如何利用 RDI看到这里你可能会问参数寄存器都用 RCX/RDX/R8/R9 了RDI 是不是就闲下来了并不是。RDI 在 Windows x64 环境下仍然非常活跃主要用途有三个方面第一字符串和内存操作指令的默认目标寄存器。rep stosb、rep movsb、scasb这些指令仍然硬编码使用 RDI编译器优化内存复制时无法绕过它。第二编译器自由分配寄存器时会把 RDI 当作可持续跨语句保存的“持久变量容器”。因为它属于 non-volatile在整个函数生命周期内不会被子调用破坏很多循环变量、长期指针会被安排在 RDI 里。第三Windows 内核的异常分发和调试机制会保存寄存器上下文RDI 是CONTEXT结构里的固定成员。分析蓝屏 dump 时RDI 的值往往能告诉我们“程序执行到哪一步、目标地址是什么”。4. C 指针在汇编层是如何变成 RDI 的4.1 指针本质是内存地址在 C 语言里int *p是一个保存内存地址的变量。无论它指向栈、堆还是全局区最终在机器码层面就是一个 64 位整数值。当编译器把*p value翻译成汇编时它首先要让某个寄存器持有p的值然后用间接寻址写入内存。看一个最简单的例子void write_int(int *p, int value) { *p value; }用 Windows x64 的 MSVC 编译并反汇编可能看到这样的机器码; Windows x64 下RCX p, RDX value mov [rcx], edx ret这里没有使用 RDI因为编译器判断这个函数足够简单没有必要动用字符串指令。但如果程序里出现连续读写、内存复制、缓冲区填充RDI/RSI 就会登场。4.2 编译器为何偏爱 RDI/RSI考虑下面的 C 代码#include string.h void copy_n(char *dst, const char *src, size_t n) { memcpy(dst, src, n); }在 Release 模式下MSVC 会对memcpy进行内联。生成的核心汇编往往长这个样子; RCX dst, RDX src, R8 n mov rdi, rcx ; 目标地址放入 RDI mov rsi, rdx ; 源地址放入 RSI mov rcx, r8 ; 长度放入 RCX cld ; 清除方向标志从低地址向高地址复制 rep movsb ; 重复复制直到 RCX 0为什么编译器要把地址搬到 RDI/RSI因为rep movsb是硬件级别的字符串复制指令它不要求编译器生成循环体CPU 微码会自动完成“读内存 - 写内存 - 地址递增 - 计数递减”的循环过程。在较新的 CPU 上rep movsb内部会采用宽位传输复制大块内存时的效率往往高于简单循环。把这段汇编和 C 源码对比你就能理解dst指针没有老老实实地待在栈上而是被编译器放进了 RDI。RDI 就是dst在汇编世界的变体。4.3 手写 C 循环与 REP 指令的对比如果不调用memcpy自己写逐字节复制void copy_loop(char *dst, const char *src, size_t n) { while (n--) { *dst *src; } }编译器在开启优化后通常也会把这段循环优化成rep movsb而不是真的生成“逐条 mov inc dec jnz”的循环。背后的原因是手工循环每条指令都要经过解码、执行、跳转预测而rep movsb在微码层面可以更高效地完成批量数据移动。这个现象非常有启发高级语言的指针操作在底层可能被映射到专用的寄存器组合和专用指令。我们不需要在 C 代码里显式写 RDI但理解 RDI 能帮你在阅读反汇编、优化热点代码、排查崩溃问题时快速定位。5. 汇编实战在 Windows x64 下用 RDI 写一个 memcpy5.1 设计思路为了亲手触摸 RDI我们用一个纯粹的汇编函数实现内存复制。函数设计如下函数名asm_memcpy参数void *dst放入 RCX、const void *src放入 RDX、size_t n放入 R8返回值无内部实现保存 RDI/RSI - 搬运地址 - 设置长度 - 清除方向标志 -rep movsb- 恢复 RDI/RSI - 返回这个函数虽然功能上等价于 C 的memcpy但它不处理内存重叠。如果源和目标区间有重叠需要另外设计后面会讲。5.2 编写 memcpy_asm.asm在项目目录下创建memcpy_asm.asm内容如下; 文件memcpy_asm.asm ; 汇编函数asm_memcpy ; 功能把 src 指向的 n 字节复制到 dst ; ; Windows x64 调用约定 ; RCX 第一个参数 dst ; RDX 第二个参数 src ; R8 第三个参数 n ; ; 注意 ; RDI、RSI 属于 non-volatile 寄存器 ; 函数内使用前必须先保存旧值返回前恢复。 .code asm_memcpy PROC push rdi ; 保存调用者的 RDI push rsi ; 保存调用者的 RSI mov rdi, rcx ; 目标地址放入 RDI mov rsi, rdx ; 源地址放入 RSI mov rcx, r8 ; 复制长度放入 RCX cld ; 清除方向标志 DF保证地址递增 rep movsb ; 逐字节复制直到 RCX 0 pop rsi ; 恢复调用者的 RSI pop rdi ; 恢复调用者的 RDI ret asm_memcpy ENDP END逐行解释一下这段代码push rdi和push rsi是保护现场。如果不做这一步当函数返回后调用方的 RDI/RSI 已经被我们改掉违反了 Windows x64 调用约定。mov rdi, rcx把第一个参数目标地址放入 RDI。mov rsi, rdx把第二个参数源地址放入 RSI。mov rcx, r8把第三个参数长度放入 RCX。注意这里复用 RCX 存放长度原参数已经用完可以覆盖。cld清除方向标志。保证每复制一个字节后 RDI/RSI 向高地址方向前进。rep movsb是真正干活的核心指令。pop rsi和pop rdi恢复现场。压栈和弹栈的顺序是反过来的不要写错。ret返回调用者。这里有一个细节x64 模式下 MASM 汇编函数名不需要下划线前缀。32 位时代asm_memcpy在目标文件里叫_asm_memcpy64 位时代就叫asm_memcpyC 代码里的extern声明可以直接匹配。5.3 编写 C 语言调用代码创建main.c// 文件main.c #include stdio.h #include string.h // 声明外部汇编函数 extern void asm_memcpy(void *dst, const void *src, size_t n); int main(void) { const char *src Hello, Windows x64 RDI!; char buf[64]; // 先把缓冲区清空便于观察结果 memset(buf, 0, sizeof(buf)); // 调用汇编实现的 memcpy // 注意要连同字符串结束符 \0 一起复制 asm_memcpy(buf, src, strlen(src) 1); printf(src %s\n, src); printf(buf %s\n, buf); if (strcmp(buf, src) 0) { printf(asm_memcpy works!\n); return 0; } else { printf(copy failed!\n); return 1; } }代码逻辑很直接定义源字符串定义栈上缓冲区调用汇编函数复制再对比结果。strlen(src) 1是为了把字符串结尾的\0也一并复制过去否则printf(%s)会继续读取缓冲区后面的内容。5.4 编译链接步骤在 x64 Native Tools Command Prompt 里进入项目目录执行cl /c memcpy_asm.asm cl main.c memcpy_asm.obj第一行用ml64.exe把汇编文件编译成memcpy_asm.obj第二行用cl.exe编译 C 文件并链接生成的 obj。也可以一步到位cl main.c memcpy_asm.asmcl遇到.asm文件会自动调用ml64这种方式更省事。编译完成后运行main.exe预期输出如下src Hello, Windows x64 RDI! buf Hello, Windows x64 RDI! asm_memcpy works!如果控制台输出中文乱码可以先用chcp 65001把代码页切到 UTF-8不过本文输出都是英文一般不会遇到这个问题。5.5 在 Visual Studio 项目中引入 .asm 文件如果你不想用命令行也可以把汇编文件加入 Visual Studio 项目新建一个 C 控制台应用项目。添加main.c和memcpy_asm.asm。右键memcpy_asm.asm选择“属性”。在“项类型”里选择“Microsoft Macro Assembler”。如果项目里看不到这个选项先在菜单栏的“项目 - 生成依赖项 - 生成自定义”里勾选 MASM。配置完成后直接按 CtrlF5 运行效果和命令行一样。5.6 扩展用 REP STOSB 实现 memset学会 RDI 配合rep movsb后可以顺手再写一个填充函数。memset的思路是把某个字节值写入一片连续内存使用rep stosb最合适。在memcpy_asm.asm末尾追加; 功能把 dst 开始的 n 个字节填充为 value ; C 声明void asm_memset(void *dst, unsigned char value, size_t n); ; ; Windows x64 ; RCX dst ; RDX value低 8 位是 DL ; R8 n asm_memset PROC push rdi mov rdi, rcx ; 目标地址放入 RDI mov al, dl ; 要填充的字节值放入 AL mov rcx, r8 ; 长度放入 RCX cld rep stosb ; 重复写字节直到 RCX 0 pop rdi ret asm_memset ENDP对应 C 接口声明extern void asm_memset(void *dst, unsigned char value, size_t n);这里用mov al, dl的原因是rep stosb每次把AL的值写入 RDI 指向的字节。第二个参数是通过 RDX 传入的取它的低 8 位DL赋值给AL后面就能反复填充了。这个函数同样需要保存 RDI因为stosb也会推进 RDI 指针。6. 用调试器观察 REP 循环中 RDI 的变化6.1 Visual Studio 寄存器窗口操作汇编代码写出来是一回事亲眼看到 RDI 变化是另一回事。调试能帮你建立最直观的“寄存器与内存”对应感。操作方法如下在main.c中调用asm_memcpy的那一行设置断点。按 F5 开始调试程序停住后按 F11 单步进入。打开寄存器窗口调试 - 窗口 - 寄存器快捷键 CtrlAltG。在汇编代码窗口里把断点设置在rep movsb这一行。反复按 F10 单步执行。单步执行时关注三个寄存器RDI、RSI、RCX。每按一次 F10你会看到RDI 增加 1因为它指向的目标缓冲区地址向后移动了一个字节。RSI 增加 1因为源地址也向后移动了一个字节。RCX 减少 1因为剩余复制字节数变少了。如果 RDI 的显示是十六进制右键 RDI 行可以切换成十进制。你还可以在内存窗口里输入rdi直观看到当前 RDI 指向的字节内容。6.2 RDI 自动增量背后的硬件逻辑你可能会有疑问rep movsb明明是单条指令为什么每次按 F10 只执行一个字节原因在于 CPU 把带 REP 前缀的字符串指令当成一个“可中断的重复序列”。调试器每执行一个单步实际上会完成一组“复制一个字节并更新指针和计数”的微操作然后停在同一条指令上直到 RCX 变成 0指令才真正执行完毕。这说明 RDI 的推进不是软件循环做的而是 CPU 硬件微码自动完成的。汇编程序员不需要写inc rdi也不需要在循环里判断 RCX 是否为 0。硬件帮你把这些低级操作全部封装好了。试想一下如果没有rep movsb你要手动写多少条指令才能完成同样的事loop_start: movzx eax, byte ptr [rsi] mov byte ptr [rdi], al inc rsi inc rdi dec rcx jnz loop_start这大概是五条指令的内层循环还涉及标志位、跳转预测。相比之下rep movsb的代码更短语义更清晰现代 CPU 对它的内部实现也做了大量优化。6.3 方向标志 DF 的作用正常情况下cld清除了方向标志所以 RDI 是递增的。如果把cld替换成std那么 RDI 和 RSI 会递减。此时复制从高地址向低地址进行rep movsb结束时 RDI/RSI 会指向目标区域和源区域的起始位置之前。这个特性在处理重叠内存区域时很有用。比如你要把一块内存向后移动几个字节如果仍然从低地址向高地址复制源数据可能还没读就被覆盖反过来从高地址向低地址复制就能避免覆盖。做实验时可以这样修改memcpy_asm.asmstd ; 设置方向标志让 RDI/RSI 递减 rep movsb ; 观察 RDI 的变化方向你会立刻看到 RDI 每次不是加 1而是减 1。实验完成后记得改回cld。实际工程中代码里只要依赖于递增方向就必须在进入字符串指令前明确执行cld不要依赖“默认就是递增”的假设。操作系统在异常、中断处理过程中完全可能改变 DF 值。7. 常见问题与排查思路初学 x64 汇编时遇到的报错通常集中在符号解析、寄存器保护和指令语义上。下面整理一份高频问题表问题现象常见原因解决思路编译 .asm 文件报错Visual Studio 项目未启用 MASM在“生成依赖项 - 生成自定义”里勾选 MASM链接报 unresolved external symbolasm 函数名与 C 声明不一致检查拼写x64 下 C 函数名不带前导下划线C 调用汇编函数后崩溃函数内修改了 RDI/RSI/RBX 等 non-volatile 寄存器且未保存恢复进入函数先 push返回前 pop复制结果不正确RDI 和 RSI 位置搞反MOVSB是 RSI 源、RDI 目标别写反REP 指令死循环RCX 初始值不对或者 RCX 被其他指令意外修改单步观察 RCX确保在执行 REP 前 RCX 等于长度复制重叠区域数据错乱没有处理源与目标重叠改用反向复制或直接调用 C 库 memmove想在 VS 的 x64 工程里写内联汇编VS 不支持 x64 内联汇编改用 .asm 外部汇编文件或使用 Intrinsics 函数这里面要特别强调一下内联汇编的问题。32 位时代Visual C 允许在函数里写__asm { ... }但 x64 模式下这个功能被移除了。如果你在网上看到__asm的教程先确认它是否针对 32 位编译。64 位项目做底层控制要么走外部 .asm 文件要么使用编译器提供的 intrinsic例如__movsb、__stosb。符号解析的问题也很常见。Windows x64 的 C 符号名不附加前导下划线这和 32 位时代不同。如果你的汇编函数叫asm_memcpyC 里声明extern void asm_memcpy(...)即可。如果链接器报unresolved external symbol _asm_memcpy多半是因为你的 .obj 是 32 位汇编器产出的或者函数名写错。崩溃问题优先检查寄存器保护。一个最简单的自查办法在汇编函数入口处记录 RDI 的值返回前用pop rdi恢复。如果函数里压栈和弹栈不配对RSP 会被破坏返回地址就会被弹错程序直接在ret处崩溃。遇到莫名其妙的崩溃先用调试器看调用栈是否指向了错误地址。8. 最佳实践与工程建议8.1 寄存器使用纪律写任何 Windows x64 汇编函数第一个要记住的就是寄存器归属表。进入函数前先问自己我要用哪些寄存器其中哪些是 non-volatile如果要用必须在入口处压栈保存返回前弹栈恢复。压栈和弹栈的顺序必须严格对称。压入push rdi; push rsi之后弹栈必须是pop rsi; pop rdi。这是栈的后进先出特性决定的。如果顺序颠倒RDI 被恢复成了 RSI 的旧值RSI 被恢复成了 RDI 的旧值程序行为会变得非常诡异。还要注意 x64 一个特殊规则对 32 位寄存器写入会自动把 64 位寄存器的高 32 位清零。例如mov edi, 1会同时把 RDI 高 32 位清零。而写入dil或di不会影响其他位。这在混用不同宽度操作数时非常容易踩坑建议统一使用 64 位操作。8.2 调用约定是底层编程的硬门槛Windows 和 Linux 的寄存器传参规则不同这是底层开发必须接受的现实。建议把常用调用约定表放在手边写汇编函数之前先确定平台Windows x64RCX、RDX、R8、R9 传参RDI/RSI/RBX/RBP/R12-R15 为 callee-saved。Linux x64RDI、RSI、RDX、RCX、R8、R9 传参R12-R15、RBP、RBX 为 callee-savedRDI/RSI 是参数寄存器。从 Windows 汇编转到 Linux或者从 Linux 资料转到 Windows 项目必须重新检查参数位置。反汇编调试时先通过栈回溯和函数入口指令判断调用约定不要先入为主。8.3 正确处理重叠区域rep movsb适合处理源与目标不重叠或目标地址低于源地址的情况。如果源区域和目标区域重叠就得复制方向上下功夫。标准做法是分两类目标地址高于源地址时从内存末尾向前复制也就是设置 DF 1把 RDI/RSI 指向区域末端。目标地址低于源地址时从内存起始处向后复制也就是保持 DF 0。这个逻辑和 C 标准库中的memmove完全一致。自己实现时建议先判断地址关系再选择复制方向否则数据会被覆盖。最简单的规避方式是在非性能敏感代码里直接调用memmove让系统库处理这些边界条件。8.4 安全边界与合法使用字符串指令在底层编程中非常强大但也常被用于恶意代码。很多 shellcode 会利用rep movsb解密字符串、复制恶意负载。这里必须提醒学习底层知识的目的应该是编写正常软件、驱动、调试器、安全分析工具而不是构造攻击代码。所有实验都应该在你自己的 Windows 测试环境、虚拟机或专门准备的开发机中完成。涉及系统级调试、内核操作时要保证有合法授权遵循最小权限原则。不要拿未授权的系统做实验更不要把这套知识用于绕过安全限制。8.5 后续学习路线RDI 只是 x64 寄存器体系的冰山一角。下一步可以按以下顺序深入学习完整寄存器体系RAX、RBX、RCX、RDX、RDI、RSI、RBP、RSP、R8-R15 各自的特殊用途。研究栈帧布局RBP 与 RSP 的关系、局部变量的位置、shadow space 的作用。分析 MSVC 生成的汇编列表用cl /FAsc编译 C 代码逐行对照 C 源码与汇编建立双向映射能力。在 Linux 子系统中编译同样的代码观察调用约定差异体会 ABI 对汇编代码的影响。学习 WinDbg 的基本调试命令把寄存器、内存、反汇编结合起来做现场分析。一旦你熟练掌握了“C 语言变量/指针 - 寄存器分配 - 汇编指令”这条映射链阅读反汇编代码的速度会有质的提升排查崩溃和性能问题时也会更有底气。本文从概念到实战完整走了一遍 RDI它是目标索引寄存器与 RSI 组成字符串操作的核心搬运工在 Windows x64 调用约定里它属于被调用者保存寄存器不参与参数传递C 指针在优化后可能直接被放进 RDI配合rep movsb完成内存复制。建议你把memcpy_asm.asm和main.c亲手敲一遍然后在调试器里一格格观察 RDI 的变化。看到 RDI 随着rep movsb递增和直接从书上背结论是完全不同的体验。到那时候你才算真正跨过了 x64 汇编入门的第一道门槛。
网站建设高端定制企业官网