我一直觉得,反编译是这世上最不适合甩给AI干的活——它比考据一部残破古书还难,每一步都可能错,偏偏大模型又最擅长一本正经地给出错误答案。直到我接到一个非做不可的项目:某款老式第一人称射击游戏(FPS),官方早已停止维护,但玩家社区还想把它移植到现代引擎上继续对战。没有源码,没有文档,只剩一个十几年前的二进制。我先把这活儿交给老办法,结果纯人工逆向干了三个月才恢复完地图加载模块和一半武器逻辑;换自动化脚本,也只能处理模式足够固定的代码,一碰到引擎层虚表和状态机就全线翻车。最后我被逼着搭了一套AI智能体流水线,持续往里喂了整整500B tokens,居然真的把这款FPS项目核心骨架反了出来。
这篇文章想讲的不是“AI多神奇”,而是这500B tokens到底怎么烧掉的、智能体怎么分工协作、哪些地方惊艳、哪些地方让我差点砸键盘。如果你正准备用大模型做二进制分析、代码自动重构或者大型遗留项目的可读化改造,这里面的思路和踩坑记录应该能直接抄作业。
1. 为什么我从“纯人工逆向”改投AI智能体阵营
1.1 项目起点:一个没人维护的老FPS
先说清楚,这个项目有明确的现实需求。官方停更后,社区版的服务器代码缺失,地图文件也无法在新引擎上直接用。我们要做的,是用反编译手段恢复出关键逻辑,给社区版引擎的兼容层打基础。整个过程的边界很明确:只做兼容性恢复和学习用途,不碰任何需要绕过的防护措施,所有代码分析都围绕社区玩家自己拥有的合法副本展开。
一开始我完全低估了这款FPS的复杂度。它的代码量放到PC端不算大,但特点在于几乎每个子系统都在跑热路径——渲染、武器、敌方AI、网络同步。一个玩家开一枪,背后是一串多态调用:武器基类虚函数分派到具体枪械类,再触发弹道计算、动画状态机、命中检测、网络事件广播。这在静态反编译产物里就是一堆call [reg+offset],没有任何符号提示,人工看到这种代码只能顺着数据流一点点推。
1.2 传统反编译手段为什么在这里失效
传统路线有三条,我全试过,也全卡住了。
第一条是直接读反编译器的伪代码。反编译器擅长把汇编转成可读性尚可的C风格伪代码,但它丢掉的东西太多了:结构体字段名、类继承关系、函数调用意图、状态机的整体结构。你拿到的是一张没有图例的地图,街道都在,但不知道哪条路通向哪里。第二个问题是反编译产物里充斥着编译器生成的临时变量,一长串v1、v2、v3,靠人脑去逐个恢复语义,对百来个函数还行,对几千个函数就是灾难。
第二条是纯人工逆向。不是不会,是太慢。团队里有经验的逆向工程师一天最多恢复三五个中等复杂度的函数,还要反复调试验证。按这个速度,要把武器引擎的几百个函数恢复清楚,得大半年。而且人有一个致命弱点:连续工作几周后会产生“我已经猜对了”的错觉,很多结构体偏移错误就是这么漏进去的。
第三条是自动化脚本。脚本擅长处理模式固定的代码:识别字符串表、提取导入函数、统计调用关系,这些都很在行。但游戏引擎的代码不是模式固定的模板,同一个功能在不同模块里实现方式差异极大,脚本一旦遇到复杂的多态调用和回调链就直接歇菜。
1.3 为什么是“AI智能体”而不是“单次调用大模型”
有人会问:直接拿大模型当翻译器,把汇编丢给它,让它输出伪代码不行吗?我试过。单次调用确实能把一个短函数的汇编“翻译”得相当漂亮,比反编译器的默认输出更接近人能读的C++代码。但问题是,它没有记忆。你给它看第二个函数时,它完全不记得第一个函数的结论。如果一段逻辑横跨十个函数,你就得把前面九个函数的全部分析结果重复喂进去,上下文早就被撑爆了。
智能体的差别在于,它是奔着一个目标去的:拆解任务、带着记忆执行、调用工具、检查结果、根据反馈修正。我要做的,就是把一个大型逆向工程拆成几千个子任务,分配给不同角色的智能体,由它们在记忆库和验证环境的支撑下滚动推进。我的角色从“逐行读代码的人”变成了“维护流水线和真值验证环境的人”。
2. 500B tokens的账本:大头都花在哪了
2.1 先算一笔token开销模型
500B tokens是个一听就让人腿软的数字。但真相是,这个数字不是你拍脑袋定的,而是由任务规模自然推导出来的。先用一个粗粒度的公式理解它:
总token消耗 ≈ 输入token × 重复系数 + 输出token × 迭代次数
假设最终要恢复的可读伪代码规模在80万行左右。输出侧,如果只写一遍,几十B tokens确实够了。但问题出在输入侧和迭代次数上。每分析一个函数,智能体不仅要看这一段的汇编,还要带上周边函数的调用关系、调用方的约束、该函数在这轮之前的所有历史结论。平均每个函数相关的上下文会被重复读取5到10次。输入侧一放大,整体消耗很快就冲到几百B了。
再算上试错验证,500B完全在正常范围。后面我实测下来,单项任务的token消耗大概是这样的:
| 消耗方向 | 量级 | 约占比 | 说明 |
|---|---|---|---|
| 静态代码分析 | 约180B | 36% | 汇编块、反编译伪代码、上下文反复输入 |
| 试错与假设迭代 | 约100B | 20% | 智能体提出错误假设被驳回后重做 |
| 验证与交叉审查 | 约220B | 44% | 编译测试、调试器比对、双模型对读 |
| 知识沉淀与归档 | 约数B | 不足1% | 档案库写入与检索 |
这个账本不是说每个项目都会按这个比例分配,但它反映了一个规律:真正的花费大头不在“生成”,而在“验证”。你让AI多写一段猜测很容易,让AI证明这段猜测在真实运行环境下是对的,才烧钱。
2.2 为什么FPS项目比普通应用更费token
FPS项目的token消耗比同规模的普通业务应用高出三五倍,核心原因是隐式耦合。普通应用代码的逻辑边界比较清晰,函数之间通过显式参数传递信息。游戏引擎不是这样,大量状态藏在对象成员变量里,通过回调、事件、全局状态管理器来流转。
举一个实际例子。我在分析一个武器渲染函数时,这个函数只有300行伪代码,但为了确认其中一个字段到底代表“当前弹匣余量”还是“备用弹药量”,智能体把调用它的三个函数全拉了出来,又从其中一个回调函数的调用链回溯到了玩家输入系统,前后花了大约4M tokens,最后才锁定结论。300行代码,4M tokens,这个放大倍数在FPS项目里非常普遍,因为游戏永远是状态机套状态机。
2.3 三个“省token”的土办法
500B烧到最后,我总结出三个能显著省token的方法,都是在不牺牲质量的前提下硬抠出来的。
一是模块级增量复用。已经验证过的函数结论,全部沉淀为档案条目。新会话不再重新分析,而是直接读取档案。哪怕需要重新看,也只读该函数本身,不连带重读整个模块。
二是双层模型策略。让便宜的小模型先做粗筛:识别模式、命名变量、标记可疑位置。只有小模型标记为“高置信”或“疑难”的样本,才进入大模型精读。实测小模型初筛的准确率大概七成,剩下三成送给大模型后,整体token省了三成左右。
三是限制自我反思轮数。我给每个智能体设了硬约束:最多推翻自己两次,第三轮结论如果还没有验证通过,必须标记为“存疑”并提交给人。没有这个限制,智能体很容易在几个假设之间无限打转,一轮消耗几十万token,产出还是零。
3. 智能体协作架构:侦察、解码、顾问、审查四角色
3.1 整体架构设计
这套流水线我一开始就设计成四个角色,每个角色的输入输出和工具都不同,目的就是各管一段、互相制衡。各角色定位如下:
| 角色 | 核心输入 | 输出 | 关键工具 |
|---|---|---|---|
| 侦察兵 | 二进制元信息、字符串、导入表 | 模块划分与函数边界图 | 反编译套件、字符串扫描 |
| 解码者 | 汇编块、模块上下文 | 带临时标签的类型化伪代码 | 反编译器API、调试器 |
| 领域顾问 | 伪代码块 | 语义标注与结构建议 | 引擎知识库 |
| 审查官 | 伪代码、原始汇编、数据流 | 验收或驳回报告 | 编译环境、差分脚本 |
流水线的基本流程是:侦察兵先切模块,解码者逐模块产出伪代码,顾问做语义补全,审查官做反向验证。验证不通过的打回解码者重做,验证通过的写入档案库。这样每个函数至少经过两次独立视角的处理,单点错误被放大的概率低了很多。
3.2 侦察兵:从二进制汪洋里切出第一刀
侦察兵的核心任务不是读代码,而是画出地图。切入顺序是:先读二进制头,再整理导入表,扫描所有字符串常量,最后按调用图聚类,把几万个函数划分成渲染、武器、AI、物理、网络等模块。
这一步最管用的路标是字符串表。看到PlayerHealth、RespawnTimer、KillStreak这些字符串,基本可以断定附近的代码在管玩家状态和计分逻辑;看到图形API的调用名,马上标记为渲染模块。侦查智能体做的事,比我早年人工做的聪明之处在于,它会用调用图把这些标记扩散:邻近函数大概率属于同一个模块,因为它被同一个模块调用了几十次。这一个阶段的产出是整个项目的地基,后面所有角色的工作都依赖这张模块图。
3.3 解码者:把汇编“翻译”成有类型注释的伪代码
解码者是整个流水线里最核心的角色。它的工作不是逐指令翻译,而是识别高层模式:从jmp回退看出循环,从跳转表看出switch分支,从[reg+0x14]这类偏移访问看出结构体字段。
我给它设计的输出格式是带临时标签的伪代码,字段名先用偏移量占位,后续由顾问统一绑定。举个例子,一段武器伤害计算的汇编,经解码者恢复后大概长这样:
// 原始汇编片段经解码者恢复后的形式 int weapon_apply_damage(Player* target, int baseDamage) { int damage = baseDamage; // 0x14 偏移处的护甲值判定 if (target->armor > 0) { damage = (damage * 2) / 3; target->armor--; } return damage; }注意,armor这个名字不是解码者从汇编里读出来的,而是顾问在后续阶段结合玩家属性系统推断的。解码者的核心贡献是把控制流还原正确,把数据访问关系写清楚,语义命名交给更懂业务的人去补。这种分工可以避免解码者过度“发挥”导致幻觉泛滥。
3.4 顾问与审查官:领域知识和幻觉过滤器
领域顾问的任务是回答“这段代码在游戏里是干什么的”。它的知识库里存了引擎架构的常见模式描述:实体组件系统怎么组织、渲染队列怎么流转、AI状态机一般有哪些状态。当解码者产出一段“看起来像状态切换”的伪代码时,顾问会补上一句“这可能是从‘瞄准’状态到‘射击’状态的转换”,并把相邻函数中所有引用同一个状态变量的位置一起标出来。
审查官则负责反向验证。它的做法是:拿到解码者的伪代码后,先尝试把它转成一份简化汇编骨架,再和原始汇编做结构比对。不是逐字匹配,而是比对关键要素:控制流图是否同构、vtable槽位引用是否一致、循环边界是否吻合。不一致的地方,审查官会输出差异报告,打回解码者重做。
我后来统计了一下,经过审查官这一道关卡,幻觉率从初始的20%以上降到了5%以下。这个角色的价值不是锦上添花,是保命。
4. 反编译一款FPS时,AI必须翻过的那几座山
4.1 虚函数表与多态调用链的恢复
FPS的游戏逻辑几乎全部构筑在多态之上。武器有基类,不同枪械是派生类;敌人AI有基类,不同怪物是派生类;连UI里的按钮都是继承关系。汇编层面的多态调用特征非常明显:一个对象指针的头几个字节存着虚函数表指针(vptr),调用时通过call [reg+slot]间接跳转。
问题是,静态反编译后你只看到一堆call [reg+0x4],根本不知道这个slot对应的是哪个类、哪个方法。我让智能体专门扮演“vtable猎人”:扫描所有构造函数——通常是在分配内存后立即写入vptr的序列——然后把每个slot上的方法地址收集起来,再根据这些地址回溯函数名或字符串引用,最终还原出一张类层次关系表。
这个任务AI干得比我预期好得多,原因是构造函数里vptr的赋值模式在汇编中重复度极高,属于典型的模式识别问题。我大概只人工介入了20%的疑难类关系,其余都是智能体自己整理出来的。
4.2 玩家状态机与主循环识别
FPS的灵魂是状态机:玩家在空闲、移动、瞄准、射击、受伤、换弹、死亡这些状态之间切换,敌人AI也有巡逻、警戒、追击、攻击等状态。静态反编译之后,状态机是最难还原的一类逻辑,因为编译器的优化会把case分支拆得七零八落,一个状态的入口可能在函数A,处理逻辑却在函数B,出口又跳回函数C。
我的做法是让解码者先输出所有可疑的状态枚举值,再生成一张状态转换表。每个switch分支都标注“从哪个状态进入、经过什么条件、跳往哪个状态”。这些信息汇总后,再反查函数之间的跳转图,把零散的分支重新拼起来。这个过程原本特别消耗人工,但让AI按“先列状态→再连边→最后补条件”的顺序做,效率提升非常明显。最后我拿到了一张完整的玩家状态转换图,分支还原率达到85%左右。
4.3 渲染数学库的“宝藏识别”
FPS项目里有大量矩阵乘法、四元数插值、点积运算、视锥体裁剪、坐标变换。这类代码在汇编里有强烈的模式指纹,对AI来说简直是送分题。一堆连续的乘法和加法指令,前面带着特殊的内存排列,后面跟着一个投影函数的调用,AI很快就能识别出“这是4x4矩阵乘法”。
这部分是整个项目里token消耗最低、准确率最高的区域。原因很简单:数学代码没有隐藏状态,输入输出都是数值,不依赖全局变量的神秘变化。凡是这类模式清晰的代码,智能体几乎一遍过,不需要反复试错。我也因此得出一个经验:做这种大型逆向工程,第一个阶段应该优先让AI啃数学库和工具函数,先积累一批高置信度的“锚点函数”,后面的状态机恢复才有抓手。
5. 上下文窗口装不下500B:记忆分层与分段复卷
5.1 问题的本质:不是一次对话,而是一套档案体系
很多想做类似项目的人会问:500B tokens怎么塞进上下文窗口?这个问题的前提就错了。500B是整个项目从开始到结束的token总流量,不是某一次对话的长度。真正要解决的问题是:几万次会话之间,怎么让智能体始终记得曾经得出的结论,不至于每次都从零开始。
如果每次会话都从零开始,那么消耗的不只是token,还有一致性——昨天刚验证过的结构体定义,今天换个智能体可能又猜出另一个版本,项目越推进越混乱。所以我在项目第一个人工作包跑完之后就停了一天,专门重建记忆架构。
5.2 三层记忆架构:全局记忆、会话记忆与档案库
记忆系统最后设计成三层,每层的生命周期和写入时机都不一样:
| 记忆层 | 内容 | 写入时机 | 示例 |
|---|---|---|---|
| 全局记忆层 | 模块边界、函数清单、已验证事实 | 侦察兵完成模块划分后 | “武器系统包含37个函数,vtable索引4被基类占用” |
| 会话记忆层 | 当前工作包内的临时推断 | 解码者处理某个函数的过程中 | “疑似0x1C偏移是后坐力,待审查确认” |
| 档案库 | 工作包完成后的结论沉淀 | 函数族分析结束后 | “伤害函数族全部恢复,14个中12个正确,2个待修复” |
全局记忆层不会频繁改动,它是整个流水线的“宪法”。会话记忆层只存在于单个工作包执行期间,工作包一结束就把其中有价值的内容提炼进档案库,其他临时推断直接丢弃。档案库是跨会话共享的,任何角色开工前都要先读相关档案。
5.3 分段复卷:让智能体像读编年史一样读代码
代码不是孤立的集合,它有强烈的时序关系。同一个功能族的函数往往在运行期形成一条调用链,分析A函数时需要知道B函数会怎么调用它。我把项目按函数族切分成工作包,比如“武器射击族”包括触发、弹道、命中、换弹、特效五类函数,每次只分析一个工作包。
分析每个工作包时,我要求智能体先读档案库里所有与这个函数族相关的旧结论,再开始新的分析,结束后把新结论追加回档案库。这就像读编年史:先读前面已经写好的章节,再续写新的一章。如果只追结果不复卷,新结论很可能和旧结论在细节上打架。复卷操作本身也会消耗token,但它可以把后期的返工成本降到最低,这笔投资非常划算。
5.4 向量检索给了惊喜,也给了教训
我们还给档案库加了一套向量检索,让智能体能按语义搜索“可能相关的历史代码”。结果第一次上线就给了我一个教训:检索出来的内容是碎片化的,智能体知道“有某个函数提到了伤害衰减”,但不知道它在哪个模块、是什么状态、和当前分析有没有上下游关系。它拿着这个碎片,反而比不看更迷茫。
后来我加了两层索引:第一层是模块摘要索引,每个函数族有一段总结性描述;第二层才是代码段索引。检索结果必须带上模块路径、函数名、验证状态、修改时间这些元数据,智能体才能判断这段历史的可信度和关联度。加了这套改造之后,相关代码的召回率明显提升。我的经验是,做知识检索别急着上花哨的框架,先把档案的元数据写规范了,效果自然就出来了。
6. 真值锚点与纠错:AI胡说八道时怎么把它拽回来
6.1 可执行的真值锚点:编译、调试、数据流对照
智能体再聪明,写出的伪代码也可能是错的。怎么证明它对?我的答案只有一句:把输出交给机器执行,让机器当裁判。这个项目里我铺了三类真值锚点。
第一类是可编译锚点。AI输出的伪代码经过人工或辅助脚本转成C++后,要能成功编译成测试目标。能过编译不意味着逻辑正确,但至少说明类型、依赖关系和语法骨架是一致的,这是第一道大筛。
第二类是调试器锚点。对关键函数设置断点,单步执行,核对寄存器值、内存内容、调用顺序是不是符合AI注释里描述的语义。比如AI说某个函数“把准星从窗口坐标转为世界坐标”,但实际运行时输入只有鼠标增量,没有窗口坐标,那这个描述就是错的,立即打回。
第三类是数据流一致性检查。把每个变量的定义点、使用点、传递路径全部拉出来,检查是否有断链。这一步可以抓到大量“看似合理但实则无中生有”的字段访问。
6.2 双智能体交叉审查:让两个模型互相挑刺
除了机器验证,我还做了另一个设计:解码者产出的伪代码,不让同一个模型自我检查——自我检查在同一套“逻辑偏好”里容易自己骗自己。审查官是另一套独立配置,任务是从伪代码反推出简化的汇编执行骨架,再和原始汇编做控制流图同构比对、vtable槽位比对、循环边界比对。
这套方法抓错能力很强。因为解码者是从汇编到伪代码,审查官是从伪代码到汇编,两条路径只要有一个环节理解错了,最终比对一定会露出不一致的尾巴。我统计过,审查官平均能从一份伪代码里找出1.8处需要返工的问题,其中六成是寄存器依赖错误,三成是分支条件错误,剩下的是标注不准确。它已经成为整个流水线里不可替代的质检员。
6.3 实测中遇到的三类高发幻觉
AI生成的伪代码再漂亮,拿到机器上跑不通,一切白搭。这里我把项目里遇到最频繁的三类幻觉列出来,也写下对策:
| 幻觉类型 | 典型症状 | 根因 | 对策 |
|---|---|---|---|
| 寄存器复用读错 | 把旧值当成新值 | 没追踪寄存器生命周期 | 改用SSA式数据流表达 |
| 结构体字段错位 | 函数能编译但运行结果不对 | 根据可疑偏移随意猜测 | 交叉引用并用调试器实测 |
| 内联函数误判 | 把重复代码当成独立函数 | 未识别内联展开模板 | 对重复代码做相似度聚类 |
寄存器复用读错是最隐蔽的。在x86汇编里,同一个寄存器在不同阶段保存不同值,AI如果没有追踪每个寄存器的写入点,很容易把前一条指令的参数当成当前指令的返回值。改用SSA形式的数据流表达后,这个问题的发生率大幅下降。
结构体字段错位是最难防的。AI猜了一个偏移量,单独看好像讲得通,但程序一跑就错。对付这个我没有任何捷径,只能靠调试器实测和交叉引用比对,宁可慢一点也不能放过。
6.4 验证环节烧掉了一半token,但这笔钱省不得
账本里验证约占220B tokens,接近总消耗的一半。单看很心疼,但这是整个项目里最不该省的钱。原因很简单:这在工程上是一个连锁反应问题。符号表就像地基,地基歪了,后面每个智能体都会依赖错误结论,二次纠错的成本往往是初次验证的三四倍。
到项目后期,我干脆定了规矩:没有经过机器验证的结论,一律不进档案库,其他智能体无权引用。这条规矩一度让流程变慢,但也让最终产物的可信度保持在了一个相当高的水平。
7. 成果复盘:500B tokens烧得值不值
7.1 最终拿到了什么
整个项目结束后,我拿到了这样一组数据:
| 子系统 | 恢复函数数 | 正确率 | 可重建率 |
|---|---|---|---|
| 玩家状态与计分 | 约120 | 85% | 高 |
| 武器系统 | 约460 | 78% | 高 |
| 渲染与视锥 | 约210 | 63% | 中 |
| AI行为状态机 | 约180 | 70% | 中 |
| 网络同步 | 约90 | 45% | 低 |
整体来看,恢复出的可读伪代码有1700多个函数,判定了1300多个结构体,建立了2100多个符号名。这个产出足以支撑社区版引擎的兼容层开发。移植版最终能跑起单机和局域网对战,画面表现和弹道手感已经比较接近原版了。对一个没有源码的十几年前老游戏来说,这个结果远远超出了我最初的预期。
正确率是动态验证的口径,不是猜的:每个标注为“正确”的函数,都至少跑通了一次调试器单步验证。渲染和网络同步的准确率偏低,和项目里最难的部分基本重合。
7.2 AI擅长什么,人必须看住什么
复盘之后,我把AI和人工的能力边界划得很清楚了。AI真正擅长的是高频重复模式识别:数学库、构造函数、渲染状态设置、成组的赋值逻辑。这些代码有几万个相似片段,AI一遍就能学会,而且不会厌倦。项目里这部分代码的恢复速度,快到令人怀疑人生。
AI最露馅的地方是长程数据流和隐藏状态。典型例子是“存活玩家列表”的更新链路:它跨越十几个模块,通过异步回调和全局事件传递,中间还经过几个被其他子系统复用的公共函数。AI每次分析一段,都会觉得眼前逻辑完整,但它看不到全局,最终需要我从档案库里跨模块追踪,人工把整个链路补出来。
网络同步恢复率只有45%,这个项目让我深刻认识到协议逆向前置条件的重要性。机制代码的逻辑藏在代码结构里,而协议逻辑藏在二进制格式和时序约束里,后者恰好是纯静态分析最不占优势的领域。
7.3 如果重新来一次,我会改这三个地方
500B tokens不是最优解,它只是第一次跑通这条路时的成本。如果让我重做一遍,至少有三个改动可以把成本压到250B到300B。
第一步,先建风格样本库再开干。开工第一周不分析任何业务函数,只让智能体学习这套旧引擎的编码风格:函数命名习惯、结构体组织方式、异常处理模式。样本库建好后,后续所有智能体都会沿用同一套风格先验,猜对概率大幅上升。
第二步,粗恢复和精调分离。先用便宜小模型跑完整个二进制的粗恢复,只拿回函数边界、调用关系、基本控制流,搭起一个粗糙骨架;再让大模型集中精力攻坚骨架里标记为“高复杂度”和“待确认”的函数。大模型的token就只花在刀刃上。
第三步,验证前移。不要等一个模块全部恢复完才去验证。每拆完一个工作包就立刻建立最小测试环境,跑通一个包再进下一个。前期的验证投入会增加一点,但后期返工能省一大笔。
说实话,500B这个数字第一次出现在预算表上的时候,我自己都怀疑是不是疯了。但真把它跑完之后,我对AI做逆向这件事的看法彻底变了:它不是万能的,但它是第一个能把这种规模项目的初稿速度提升几十倍的工具。我现在更愿意把它当成一个手快、眼亮但需要盯着的实习生——给它工具,给它真值检查机制,它会还你一份可以继续打磨的初稿。而你要做的,就是替它守住那根不能错的底线。这是我从这个项目里踩完500B的坑之后,最想分享的一句话。