尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

用x64dbg动态调试从汇编还原C语言代码

用x64dbg动态调试从汇编还原C语言代码 我们这次来看一个非常实在的逆向话题用 x32dbg/x64dbg 对程序做动态分析然后反向还原出 C 语言代码。很多人在学逆向时会遇到一个断层反汇编窗口里每一行都认识mov、add、call、jmp都学过但是把它们串起来之后不知道这个函数原本是用 C 语言怎么写出来的。这篇文章的目标就是把这个断层填上。先说结论x64dbg 是目前做 Windows 用户态逆向最顺手的动态调试器之一界面直观、插件生态好、支持脚本自动化。它能做的事包括但不限于下断点逐行观察程序行为、查看寄存器与栈变化、识别函数调用约定、还原结构体和数组的访问方式、分析分支和循环逻辑。配合 C 语言基础完全可以从汇编反推出函数的源码结构。这篇文章会围绕一个实际可操作的流程展开准备测试程序、在 x64dbg 中加载并定位关键函数、分析栈帧与局部变量、还原分支循环与数组访问、最后把汇编整理成等价的 C 代码。适合的读者有两类一是正在学逆向、想从“能看懂汇编”进阶到“能还原逻辑”的初学者二是做 CTF 逆向、样本分析或软件兼容性研究需要快速提取算法逻辑的开发者。如果你平时用 IDA 只看静态反编译很少碰动态调试这篇文章可以作为补充静态看全貌动态验证细节两者结合才是高效的逆向方式。1. 核心能力速览在动手之前先把 x64dbg 这个工具的能力边界梳理清楚。能力项说明工具类型开源 Windows 用户态动态调试器两个版本x32dbg 调试 32 位程序x64dbg 调试 64 位程序主要功能断点、单步、内存读写、寄存器查看、栈回溯、脚本自动化定位方式可直接打开 EXE也可附加到正在运行的进程调试信息支持 PDB 符号加载也支持无符号分析扩展能力插件系统、脚本命令、条件断点、Trace 日志与 C 语言还原的关系通过观察栈帧、调用约定、变量偏移、循环跳转来反推源码结构适用系统Windows 7 到 Windows 11 均可使用学习门槛需要掌握基础汇编mov、lea、call、jmp、cmp 等这里要注意一个关键点x32dbg 和 x64dbg 不是同一个软件的“新旧版”而是应对不同位数程序的独立版本。调试 32 位程序用 x32dbg调试 64 位程序用 x64dbg。选错的话要么打不开文件要么看到的地址和模块结构完全对不上。还有一个常见误解觉得动态调试器能“一键反编译”。实际上 x64dbg 只负责给你运行时的真实状态比如某个变量的内存地址在哪、函数参数传了什么、循环执行了多少次。把汇编还原成 C 代码需要靠你自己的分析而不是工具自动生成伪代码。这一点和 IDA 的 Hex-Rays 不同也是 x64dbg 更适合用来打基础的原因你被迫真正理解每条指令而不是直接读伪代码。2. 适用场景与使用边界x64dbg 反向分析适合下面这些场景。第一学习 C 语言底层执行细节。很多人学了指针、结构体、数组但不知道它们在内存里长什么样。用 x64dbg 加载自己写的小程序观察局部变量在栈上的布局、数组的连续存储、结构体的内存对齐效果比单纯看书直观得多。第二CTF 逆向题。题目通常只给你一个二进制文件没有符号、没有源码。你需要定位关键函数、还原加密算法或校验逻辑。这种场景下动态调试可以精准看到每个变量的值变化比纯静态分析快很多。第三恶意样本分析或漏洞研究。这类场景必须强调只在自己的隔离测试环境中分析授权样本不要用于未授权软件。如果你正在做安全研究请确保手里样本的来源是合法的比如公开恶意样本库、自己编写的测试程序。第四软件兼容性分析。当你在研究某个老程序的行为或者没有源码的程序接口时动态调试能帮你确认它到底调用了哪些系统 API、处理了哪些输入。但 x64dbg 反向分析也有不适合的场景。大规模静态代码审计不适合用 x64dbg 做主力。它更偏向运行时观察如果你需要快速浏览整个程序的函数调用关系IDA 或 Ghidra 的静态视图效率更高。大型商业软件破解也不在合理范围内。未经授权的逆向、去除授权验证、绕过付费机制这类行为既违反软件许可协议也可能触犯法律。本文所有内容仅限学习、CTF、自有程序分析和授权安全研究。涉及人脸、声音、隐私数据采集的程序分析也要严格遵守平台和数据合规要求不要用逆向手段去获取未授权的用户数据。3. 环境准备与测试程序构建学习逆向最忌讳上来就分析复杂程序。建议先用自己的代码生成一个“目标程序”这样你知道源码是什么再去看汇编就能建立“汇编指令 - C 语句”的映射关系。3.1 下载 x64dbgx64dbg 是开源软件从官方 GitHub 仓库下载即可。下载后解压到本地目录不需要安装。目录里会同时出现 x32dbg.exe 和 x64dbg.exe直接对应调试 32 位和 64 位程序。如果你在 Windows 10/11 上运行注意首次启动时系统可能弹出 SmartScreen 提示选择“仍要运行”即可开源软件被拦截是常见现象。3.2 准备一个测试程序下面这段代码是一个典型的 C 程序包含结构体、全局数组、if/else 分支、for 循环、函数调用。我们用 -O0 编译也就是关闭优化让生成的汇编尽可能贴近源码结构。#include stdio.h #include string.h typedef struct { int id; char name[32]; int score; } Student; static int scores[] { 90, 85, 78, 92, 88 }; int calc_grade(Student *stu) { int base 60; int grade; if (stu-score base) { grade stu-score 5; } else { grade stu-score - 5; } return grade; } int sum_scores(int n) { int sum 0; for (int i 0; i n; i) { sum scores[i]; } return sum; } int main() { Student stu; stu.id 1; strcpy(stu.name, tom); stu.score 82; int grade calc_grade(stu); int total sum_scores(5); printf(grade%d, total%d\n, grade, total); return 0; }在本地用 MinGW 或 Visual Studio 编译成 exe。以 MinGW 为例gcc -O0 -g test.c -o test.exe这里注意-O0很关键。优化开得越高编译器会做常量折叠、寄存器复用汇编和源码的对应关系就越弱。初学阶段一定要用-O0。-g选项会让程序携带调试符号方便我们在调试器里直接看到函数名和变量名后面熟悉了再去掉符号分析。3.3 配置符号路径用 x64dbg 打开带-g编译的 exe 后可以在“选项 - 首选项 - 符号”里设置 PDB 符号路径。如果 MinGW 生成的调试信息和 Windows PDB 格式不完全兼容也不用担心仍然可以通过函数名导入表或直接下断点定位。实际上即使没有符号我们也可以从call指令调用的 CRT 函数比如printf的导入表地址和栈回溯来确认哪个函数是main。4. 启动 x64dbg 并加载程序4.1 打开目标程序先确认目标程序的位数。在命令行执行file test.exe或者直接看编译选项。如果是 32 位程序用 x32dbg.exe 打开如果是 64 位程序用 x64dbg.exe 打开。打开方式很简单File - Open - 选择 test.exe。程序会停在系统断点System Breakpoint也就是主程序真正执行之前。这里有个操作习惯要养好先按一次 F9让程序跑完系统初始化再定位到我们要分析的main函数。因为很多断点是在系统断点状态下设置的提前设置可能被初始化代码覆盖。4.2 定位 main 函数在反汇编窗口空白处右键选择“转到 - 表达式”输入main如果符号加载成功会直接跳到 main 函数的开头。如果没加载成功可以用另一个办法在 CPU 窗口查看“符号”页签找到 test.exe 模块展开后搜索 main。还有一种更通用的做法在反汇编窗口搜索文本字符串。main 函数里调用了printf而printf的格式化字符串“grade%d, total%d”会存放在只读数据区。我们通过搜索引用了这个字符串的代码就能向上反推出 main 函数的位置。这种“找字符串 - 找交叉引用 - 定位函数”的思路在无符号逆向里是最常用的入口手段。4.3 常用操作快捷键快捷键作用F2切换断点F7单步步入进入 call 内部F8单步步过不进入 call直接执行完F9继续运行CtrlG转到指定地址或表达式AltB查看断点窗口CtrlF8连续步过直到遇到断点或暂停设置断点的原则第一次分析不要在系统 API 内部反复单步那是浪费时间。先在main函数开头、calc_grade调用处、sum_scores调用处、printf调用处按下 F2然后用 F9 直接从一个断点跑到下一个断点。这样你能快速观察到每次函数调用前后的寄存器和栈变化而不是淹没在一堆系统 DLL 的汇编指令里。5. 栈帧分析与变量识别还原 C 代码的第一步不是看指令而是先分清“哪些是参数、哪些是局部变量、哪些是全局变量”。5.1 函数序言与栈帧先看calc_grade函数的开头。关闭优化后x86 版本的函数序言大致长这样push ebp mov ebp, esp sub esp, 0x8这三行的含义是保存上一个函数的栈底指针把 ebp 指向当前栈帧底部然后向下开辟 8 字节空间存放局部变量。在 x64 版本里函数序言通常变成push rbp mov rbp, rsp sub rsp, 0x10注意x64 程序默认使用sub rsp, 空间大小加上mov rbp, rsp来建立栈帧。参数优先通过 RCX、RDX、R8、R9 传递多余的参数才走栈。拿到函数序言后我们能立刻判断局部变量分布EBP/RBP 往正方向偏移函数参数EBP/RBP 往负方向偏移局部变量直接访问全局地址全局变量或常量数据5.2 从栈偏移还原变量用 x64dbg 单步到calc_grade函数内部假设你看到这样的指令mov dword ptr [ebp-0x4], 0x3C mov eax, dword ptr [ebp0x8] cmp eax, dword ptr [ebp-0x4] jl short loc_401019反推起来就是[ebp-0x4]存了一个立即数 0x3C也就是十进制的 60。对照源码这就是int base 60。[ebp0x8]是传入的第一个参数。在 x86 调用约定里此时参数是结构体指针stu但指针本身被读出来加上偏移后才是stu-score。cmp eax, [ebp-0x4]就是在比较stu-score和base。jl跳转对应 C 里的if条件不成立的分支。这里有一个重要技巧看到[ebp0x8]时先不要急着把它当成一个整型。它也可能是指针。如果后续有mov eax, [eax0x24]这样的代码说明先是取参数存入寄存器再使用寄存器加偏移量访问结构体成员。5.3 参数传递与返回值还原 C 函数时必须明确参数在哪里、返回值在哪里。x86 常见的调用约定有 cdecl、stdcall、fastcall约定参数传递位置清理栈方式cdecl全部压栈从右往左调用者清理stdcall全部压栈从右往左被调用者清理fastcallECX、EDX 传前两个参数其余压栈被调用者清理x64 比较统一前四个参数用 RCX、RDX、R8、R9其余参数压栈返回值放 RAX。我们在calc_grade返回前观察 RAX 的值就能确认返回值。看到mov eax, dword ptr [ebp-0x8] pop ebp ret就可以推断函数确实有一个整型返回值返回的是局部变量grade的值。6. 常见 C 语法对应的汇编模式这一节是全文的核心。后面做还原时就是不断把看到的汇编片段“翻译”成 C 语法。熟练这些模式还原速度会快很多。6.1 if / else 分支if / else 的汇编模式非常固定条件判断指令 条件跳转 跳转到 else 块或函数结尾。看一个典型例子cmp eax, dword ptr [ebp-0x4] jl short loc_401008 mov eax, dword ptr [ebp0x8] add eax, 0x5 mov dword ptr [ebp-0x8], eax jmp short loc_40101F loc_401008: mov eax, dword ptr [ebp0x8] sub eax, 0x5 mov dword ptr [ebp-0x8], eax loc_40101F: mov eax, dword ptr [ebp-0x8] pop ebp ret这段翻译成 C 就是if (score base) { grade score 5; } else { grade score - 5; } return grade;注意jl是“小于则跳转”。C 里写的是score base时进入 if 块但编译器生成的汇编却是score base时跳到 else 块。这是最常见的“条件反转”现象编译器的跳转目标往往指向 else 分支。还原时不要死板地看跳转条件要反过来想跳转不成立时落在哪里那个路径才是 if 真分支。6.2 for 循环for 循环的汇编结构是“初始化 - 条件判断 - 循环体 - 增量 - 跳回条件判断”。看下面这段mov dword ptr [ebp-0x4], 0x0 jmp short loc_401020 loc_401018: mov eax, dword ptr [ebp-0x4] add eax, 0x1 mov dword ptr [ebp-0x4], eax loc_401020: mov eax, dword ptr [ebp-0x4] cmp eax, dword ptr [ebp0x8] jge short loc_401036 mov eax, dword ptr [ebp-0x4] imul eax, eax, 0x4 mov ecx, dword ptr [全局数组地址] add ecx, eax mov edx, dword ptr [ebp-0x8] add edx, dword ptr [ecx] mov dword ptr [ebp-0x8], edx jmp short loc_401018翻译步骤如下[ebp-0x4]是循环变量 i初始值为 0。jmp short loc_401020跳到条件判断。循环体里i 乘以 4这是 int 数组的元素大小[全局数组地址] i*4就是scores[i]。add edx, [ecx]等价于sum scores[i]。最后[ebp-0x4]加 1跳回条件判断。当 i n 时jge跳出循环。还原成的 C 代码int i 0; while (i n) { sum scores[i]; i; }编译器把 for 循环翻译成了 while 结构先跳去判断再进循环体。还原时注意循环变量的偏移位置和数组元素宽度这是最容易出错的地方。6.3 switch / case 跳转表如果看到大量连续的cmp加je指令可能是多个 if 分支。但如果看到类似下面的结构mov eax, dword ptr [ebp0x8] mov edx, dword ptr [跳转表地址 eax*4] jmp edx这种是编译器为 switch 生成的跳转表。每个分支的地址被存放在一个数组中通过索引直接跳转无需逐个比较。还原 switch 时要先确定跳转表的范围然后从表中每个地址对应的代码块反推 case 值。跳转表通常存在于只读数据区x64dbg 的“数据窗口”可以直接查看。相比 if 分支链switch 的还原难度更高因为跳转表地址需要通过计算得到。不过一旦找到表case 数量、每个分支的处理逻辑反而更清晰。6.4 数组访问数组访问的核心是“基址 索引 * 元素大小”。char数组索引乘以 1short/wchar_t数组索引乘以 2int/float数组索引乘以 4double/ 8 字节结构体数组索引乘以 8任意结构体数组索引乘以 sizeof(结构体)看到imul eax, eax, 0x4再配合一个全局基址基本可以确定是在访问 int 数组。看到lea eax, [eax ecx*4]也一样只是写法不同。在还原 C 代码时把乘数和数组基址列出来就能反推出数组元素的类型。6.5 结构体与指针结构体的访问几乎都是“基址 固定偏移”。比如前面 Student 结构体typedef struct { int id; // 偏移 0x0 char name[32]; // 偏移 0x4长度 32 int score; // 偏移 0x24 } Student;在汇编里访问stu-score通常表现为mov eax, dword ptr [ebp-0x40] ; 取出结构体指针 stu mov ecx, dword ptr [eax0x24] ; 访问偏移 0x24 的成员[eax0x24]就是score字段。还原 C 代码时如果我们知道这是一个结构体指针变量就可以根据偏移推算出成员序和类型。不过要注意结构体可能发生内存对齐比如 32 位下 int 是 4 字节对齐64 位下 long 是 8 字节对齐实际偏移要以汇编看见的值为准。6.6 函数调用的参数布局在 x86 cdecl 约定下调用calc_grade(stu)对应汇编lea eax, [ebp-0x40] push eax call calc_grade add esp, 0x4也就是先把局部变量stu的地址 push 进栈再调用函数。调用结束后用add esp, 4清理参数。在 x64 下参数改走寄存器lea rcx, [rbp-0x40] call calc_grade看到lea call的组合就要意识到参数是指针而且大概率是结构体指针或数组首地址。如果在call之前有push和sub esp注意分析参数个数。7. 完整还原示例从汇编到 C 代码下面用一个完整的小例子把上一节的知识串起来。假设我们在 x64dbg 中定位到calc_grade函数看到如下汇编这里展示的是逻辑等价版本实际地址和偏移以本机调试为准push ebp mov ebp, esp sub esp, 0x8 mov dword ptr [ebp-0x4], 0x3C mov eax, dword ptr [ebp0x8] mov ecx, dword ptr [eax0x24] cmp ecx, dword ptr [ebp-0x4] jl short loc_401012 mov eax, dword ptr [ebp0x8] mov ecx, dword ptr [eax0x24] add ecx, 0x5 mov dword ptr [ebp-0x8], ecx jmp short loc_40101F loc_401012: mov eax, dword ptr [ebp0x8] mov ecx, dword ptr [eax0x24] sub ecx, 0x5 mov dword ptr [ebp-0x8], ecx loc_40101F: mov eax, dword ptr [ebp-0x8] mov esp, ebp pop ebp ret分析过程[ebp-0x4]初始化为 0x3C60这就是局部变量base。[ebp0x8]是参数被读出来后没有直接使用而是再取[eax0x24]说明参数是一个指针指向的对象偏移 0x24 处有一个 4 字节数据。对照 Student 结构体这就是score。cmp ecx, [ebp-0x4]是比较score和base。jl跳转到另一分支说明score base时走 else 逻辑。真分支里score 5假分支里score - 5。返回值[ebp-0x8]就是局部变量grade。最终还原int calc_grade(Student *stu) { int base 60; int grade; if (stu-score base) { grade stu-score 5; } else { grade stu-score - 5; } return grade; }再看main函数中调用calc_grade的部分。假设 x64dbg 停在如下位置lea eax, [ebp-0x40] push eax call calc_grade add esp, 0x4 mov dword ptr [ebp-0x44], eax这里[ebp-0x40]是一个 72 字节的局部区域正好对应 Student 结构体。lea eax, [ebp-0x40]取得结构体首地址push eax传递参数。调用后eax是返回值存入[ebp-0x44]对应源码中的int grade calc_grade(stu)。从这个例子可以看出一个完整还原流程先识别函数序言和栈帧再识别参数和局部变量接着分析条件跳转归属最后把偏移映射回结构体成员。8. 脚本、插件与 AI 辅助的批量分析纯手工分析适合学习但实际逆向任务往往有成百上千个函数不可能每个都用 F8 一步一步走。这时候可以用 x64dbg 的脚本和插件提效。8.1 x64dbg 脚本示例x64dbg 内置了一套脚本引擎可以在命令行窗口输入命令也可以写成脚本文件批量执行。下面是一个简单的脚本示例在每次命中calc_grade时打印第一个参数的值// trace_calc_grade.txt var addr mov addr, cip log hit calc_grade, cip{addr} // 读取第一个参数x86 下是 [esp4] mov eax, [esp4] log param1{eax}在 x64dbg 的命令行中输入bc bp test.exe0x1020 run实际使用时需要把test.exe0x1020替换为你在反汇编窗口看到的真实地址。脚本的价值在于把重复性的操作自动化。比如批量下断点、批量记录函数参数、批量导出内存数据。8.2 插件生态x64dbg 支持第三方插件常见的有Scylla用于脱壳后修复导入表xAnalyzer静态分析当前函数参数和局部变量各类 API 断点插件自动对 CreateFile、ReadFile、WriteFile 等关键 API 下断点插件能辅助我们快速标记函数边界、识别变量。但注意插件给出的分析结果仍然只是辅助最终要还原成 C 代码还是需要手动确认。8.3 AI 辅助逆向的现状最近社区里出现了不少“逆向 LLM”的工具链比如通过 MCP 协议把 x64dbg 的调试状态暴露给大模型让模型辅助阅读反汇编。从公开反馈看AI 在以下方面有一定帮助给汇编片段写注释把短小的、无优化的函数翻译成 C 伪代码识别常见的加密库函数和循环结构但 AI 辅助不能替代调试本身。原因很简单AI 只能基于你喂给它的片段判断无法自动理解整个程序的运行状态、堆数据、全局变量跨模块引用。所以更稳妥的用法是先用 x64dbg 动态调试拿到关键运行时数据再让 AI 帮忙做代码整理和语义归纳最后自己确认。9. 资源占用与性能观察x64dbg 本身非常轻量内存占用通常在几十到一百多 MB 级别CPU 占用在等待断点暂停时基本为零。它对分析机的硬件要求很低普通办公笔记本就能流畅运行。不过在动态分析时有几个性能相关的问题值得注意调试大型程序或系统 DLL 时符号加载会明显变慢。第一次加载 PDB 可能要等很久这是磁盘读写和符号解析的开销不是死机。建议只加载自己关注模块的符号在“符号”窗口右键选择“加载符号”而不是全部加载。连续单步执行大量指令时可以用CtrlF8但要注意设置步数上限防止程序进入无限循环导致界面卡死。Trace 日志功能会记录每条执行指令数据量非常大只在分析关键算法时短时间开启用完立刻关闭。如果你是在虚拟机中做恶意样本分析建议给虚拟机分配至少 2 核 CPU 和 2GB 以上内存。x64dbg 本身不占资源但被调试目标可能很吃内存。10. 常见问题与排查方法问题现象可能原因排查方式解决方案x32dbg 打不开 64 位程序选错调试器检查程序位数换 x64dbgF9 运行后程序直接退出断点未生效程序已经跑完检查断点是否设置在真实代码路径在 main 函数或关键 API 处重新下断找不到 main 函数符号未加载或程序加壳查看模块符号、搜索字符串引用手动通过字符串交叉引用定位单步时跳进系统 DLL 出不来F7 进入了系统 API 内部查看栈回溯改用 F8或对 API 设断点后 F9 跳过PDB 符号加载失败符号路径不对或编译器不生成标准 PDB检查编译选项、符号路径改用 -g 编译或在符号配置中添加路径地址不断变化ASLR 开启观察模块基址以模块基址 偏移计算或用“基址”标签显示结构体偏移和预想不一致内存对齐或编译器结构调整查看数据窗口中的内存布局以实际反汇编偏移为准程序检测调试器并退出存在反调试逻辑搜索 IsDebuggerPresent 等 API使用插件隐藏调试器或修改对应分支批量脚本执行失败地址写死、模块重定位检查脚本中的地址是否真实改用模块名加偏移方式调试时遇到问题不要急着怀疑工具先看栈回溯和寄存器。x64dbg 的“栈”窗口会显示调用链能帮你快速判断现在执行到哪里是不是调用了未预期的系统函数。11. 最佳实践与下一步最后给一套适合初学者的操作流程按这个顺序练效率最高。第一次拿到一个陌生程序不要直接开动态调试。先用 x64dbg 的静态分析功能看一眼导入表看看它调用了哪些关键 API。比如程序中大量出现strcmp、memcpy、printf、CreateFile基本能判断这个程序的功能框架。接着定位 main 函数或关键业务函数设置少量断点观察函数调用顺序。这一步的目标是画出程序的功能模块结构而不是进入某个函数深挖。确定目标函数后进入函数内部逐行记录以下信息函数参数在哪个寄存器或栈位置局部变量分配了多大栈空间哪些地址被反复读和写哪个分支是主路径、哪个分支是异常处理把这些信息整理成一张表格再开始还原 C 代码。比如地址或偏移类型用途[ebp-0x4]int局部变量 base[ebp-0x8]int计算结果 grade[ebp0x8]Student*结构体指针参数[eax0x24]intStudent.score顺手打开反汇编窗口的“标注”功能把识别出的变量名直接写在汇编指令后面。这个习惯能显著降低长篇分析时的记忆负担。磁盘目录建议这样组织D:\reverse\ ├── target\ # 被分析的目标样本 ├── notes\ # 分析笔记和截图 ├── scripts\ # x64dbg 脚本 └── output\ # 还原出的源码和文档被分析文件、分析笔记、还原代码分开存放后期回溯时非常有用。最后提醒两个容易踩的坑。第一个坑是过度依赖静态反编译。IDA 的伪代码很香但遇到花指令、反优化、间接跳转时静态分析可能给出错误结果。遇到这种场景回 x64dbg 单步验证一下答案往往在运行时才能确认。第二个坑是忽略调用约定。x64 和 x86 的参数传递方式完全不同寄存器数量也不同。你如果在一个 64 位程序上用[esp4]找第一个参数找到的可能是局部变量而不是真正的函数参数。分析之前先确认程序位数再选择对应的参数传递规则。下一步路线很清晰先把本文的示例程序完整跑一遍用 x64dbg 单步对比源码和汇编然后关掉-O0改用-O2编译观察优化后的汇编发生了什么变化接着去掉-g符号练习无符号定位 main 函数最后找一道 CTF 简单的逆向题不看源码直接尝试还原核心函数。这个过程练完之后你再看反汇编就不只是在读指令而是在读一段等价的 C 代码了。
返回列表