Hotdry.

Article

LLM协作实现二进制归档格式:55k行Rust代码的工程启示

拆解 bitplane 历时数周、耗资£40 利用 OpenAI Codex 与 Claude Opus 从零生成 RAR 归档格式完整实现的协作工程经验:类型系统边界、Borrow Checker 规避模式、边缘 case 处理质量与性能权衡。

2026-05-13compilers

当我们审视大语言模型在系统级代码生成领域的能力边界时,二进制文件格式解析是一个极具挑战性的测试场景 —— 它要求精确的字节级操作、复杂的内部状态机以及对未文档化行为的逆向工程推断。bitplane.net 的作者最近完成了一个这样的项目:利用 OpenAI Codex 5.5 与 Claude Opus 4.7,历时约五周的业余时间,生成了一套完整的 Rust RAR 归档格式实现(约 55,000 行代码),代价仅约 £40 的 token 消耗。这个案例为我们提供了一个难得的视角,去观察当 LLM 独自面对一个跨越二十余年的商业归档格式时,其在类型系统约束、安全性边界、性能优化以及协作工程流程方面的真实表现与局限。

问题的规模与规格缺失的挑战

RAR 格式的复杂性远超普通程序员的直觉预期。这个最初为 DOS 设计的 LZSS 压缩器,在其生命周期中逐步演化为支持多卷归档、恢复记录、内置虚拟机以及强加密功能的完整归档解决方案。在整个发展过程中,作者 Eugene Roshal 并未公开发布官方的格式规范文档,这意味着 bitplane 团队无法像处理 PNG 或 ZIP 那样依赖明确的规范文本进行实现,而是必须从多个免费解压缩器的源码(如 unar、libarchive、UNRARLIB)、零散的互联网资料以及口口相传的经验中进行拼凑式逆向工程。这本身就构成了一个独特的前提条件:当规范本身需要被发掘甚至部分杜撰时,LLM 的 “幻觉” 问题获得了额外的发挥空间,同时也对人类的判断力提出了更高要求。

在项目早期,作者采用了一种结构化的 LLM 协作流程。首先将 Claude 作为文档生成器,让它阅读各类来源的碎片化信息并进行整合;在每一轮对话后,对其进行追问以识别尚未覆盖的功能点,同时维护一份持续更新的 “差距文档”,记录那些难以从公开资料中获取的关键细节。这份差距文档在上下文被重置后仍然能够指导后续的迭代,因为作者刻意让它在每次会话开始时被重新引入。五万余行代码的规模意味着上下文窗口的压缩与续接是不可回避的操作挑战,而作者选择通过定期清理上下文、切换不同会话环境以及使用多台机器并行处理的方式来缓解上下文污染问题 —— 这本质上是一种分布式记忆管理策略,尽管笨拙但有效。

双模型协作的工作流设计

这个项目中最值得关注的工程实践是双模型协作模式的引入。作者发现,Claude Opus 与 OpenAI Codex 在协作中呈现出互补但非对称的能力特征。Claude 的优势在于战略层面的架构讨论与文档整理,它能够高效地吸收来自差距文档的碎片信息并进行系统性总结。然而,它也表现出典型的 “过于热情生成代码” 的倾向 —— 在理解整体架构之前就投入实现细节,导致生成的代码虽然在形式上正确,但往往与项目的整体设计方向产生偏离。相比之下,Codex 在获得明确的规格文档后,能够保持专注并在较长时间内稳定输出符合预期的代码,但当与人类开发者进行过多交互时,它会陷入 “兔子洞” 式的过度细化,偏离主线任务。

这种不对称性催生了一种分层的工作流设计:将 Claude 定位为架构师与文档维护者,Codex 作为执行者专注于代码生成。当 Codex 因为违反使用政策而随机中断时(项目中曾发生过因上下文包含逆向工程工具内容而触发安全审核的情况),作者需要手动压缩上下文以继续任务。这一经历也揭示了一个重要的安全边界问题:当 LLM 的上下文中积累了过多涉及安全绕过、注册破解等敏感信息时,它可能在文档生成过程中无意复述这些内容,从而触发服务提供方的安全机制。作者最终选择从 git 历史中删除了相关文档,并决定完全放弃该功能的实现。

Codex 的 /goal 功能是这个项目后期效率提升的关键因素。这个功能本质上实现了一个自主研究循环:模型可以在给定任务下持续运行,通过周期性的上下文压缩来突破固定窗口限制。作者报告说,单会话运行可以轻松达到 6 小时以上的持续工作时间,最长一次达到了 16 小时。这种自主工作模式使得模型能够在 RAR 2.9 之后的版本实现中完成约 40,000 行代码的批量生成,涵盖了恢复记录、加密、多卷支持等复杂功能。然而,这种自主性也带来了额外的监督成本 —— 模型在没有及时干预的情况下,容易陷入局部优化或产生难以察觉的回归错误。

类型系统约束与 Borrow Checker 的交互边界

在 Rust 实现中处理 RAR 这一类二进制格式时,最核心的技术挑战来自于格式本身的非对齐特性与 Rust 所有权系统的交互。RAR 格式在文件头中大量使用了位域(bit fields)来编码压缩参数、加密标志以及多卷状态信息,这些位域在内存中的布局往往是 13 位、7 位等非字节对齐的颗粒度。Rust 的类型系统原生不支持位域,而标准库中的位移操作虽然可以直接使用,但在处理跨越字节边界的多位域时,需要开发者自行设计健壮的抽象层。

作者在项目中采用了手工位操作的模式,直接使用位移掩码来提取各字段。这种做法在功能上是可行的,但随着 RAR 版本演进带来的格式复杂度增加,代码中开始出现大量重复的位操作模式,缺乏统一的抽象层进行封装。当 LLM 生成这些代码时,这种重复模式容易被进一步放大,因为它倾向于复制已有模式而非引入新的结构。类型系统的缺失在另一个层面上也表现为错误处理的粗糙:大多数函数使用简单的 unwrap() 进行非空断言,而非利用 Result 类型进行系统化的错误传播。这种模式在 LLM 生成的代码中尤为常见,因为它反映的是一种 “先让代码跑起来” 的原型心态。

Borrow Checker 的约束在二进制解析场景中产生了另一种有趣的影响。RAR 格式的流式处理要求在部分解析完成后立即释放已读数据所占用的内存,而在 Rust 中正确表达这种 “读即弃” 的语义需要精确的生命周期标注。作者发现,LLM 在处理跨函数的生命周期关系时尤其容易出错 —— 它倾向于生成看似合理但实际上无法通过编译的借用代码,特别是当涉及切片引用需要指向解析器缓冲区而非新分配内存时。这种错误的修复往往需要人工介入,指出具体的生命周期约束关系,而非仅要求模型重新生成代码。

性能权衡与优化能力的局限

作者对生成代码的性能表现给出了一个诚实但略显无奈的评估:生成代码的速度约为 WinRAR 的 “数倍慢”。这一结果并不令人意外 —— 它暴露了 LLM 在系统级性能优化方面的能力上限。Codex 能够有效地使用 valgrindhyperfine 等工具来定位热点代码并进行基础优化(消除重复内存访问、减少不必要的函数调用),但它无法提供那些需要深厚领域知识的 “新型性能技巧”—— 例如利用 SIMD 指令进行批量解压、利用缓存局部性优化压缩字典查找等。这些技巧往往需要数十年 C 语言系统编程经验的积累,而 LLM 的知识来自公开代码语料库,其中包含大量平庸甚至错误的实现。

更深层的问题可能在于 Rust 语言本身的语义约束。安全 Rust 代码通过编译器的动态检查来防止数据竞争与内存错误,这些检查在运行时转化为额外的边界检查与锁操作。当 LLM 生成 “符合 Rust 规范” 的代码时,它倾向于使用标准库提供的安全抽象而非绕过这些抽象以获取性能。在某些场景下,正确使用 unsafe 代码块可以显著提升性能,但 LLM 对于何时可以使用 unsafe 的判断往往过于保守,总是选择安全路径,即使这意味着数十倍的性能损失。作者在项目中观察到 Codex 在性能关键路径上使用了一致的 “安全优先” 策略,这直接导致了与手写 C 代码之间的数量级差距。

边缘 case 处理的质量控制机制

LLM 在处理边缘 case 时的表现是决定这个项目成功与否的关键因素。在二进制格式解析的语境下,边缘 case 包括但不限于:格式版本不兼容的降级处理、损坏归档的容错读取、多卷环境下跨文件边界的引用解析、以及特定压缩算法版本特有的行为差异。作者在项目中建立了一套严格的测试框架来应对这些挑战,但他的经验揭示了一个令人不安的观察:即使有充分的测试覆盖,LLM 仍然会系统性地在特定类型的边缘 case 上失败。

最典型的模式是回归错误的形式。当 Codex 使用新版本模型生成代码时,由于注释的缺失以及上下文压缩过程中历史信息的丢失,模型在 RAR 1.4 兼容性上多次出现回归问题 —— 这些版本涉及 DOS 平台特有的行为规范,与较新版本存在微妙的差异。每一次回归都需要人工介入来定位问题并重新注入相关的上下文信息。作者指出,如果他在源代码中保留了足够的注释来记录这些边界条件,这种回归是可以避免的。但讽刺的是,最新的 Codex 模型倾向于生成无注释代码 —— 这被作者评价为一种 “好习惯”,因为注释不会被执行且会快速腐化。这种设计哲学在人类团队中是合理的,但它与 LLM 协作场景下的上下文维护需求产生了根本性的冲突。

协作流程中的监督与干预机制

作者在项目中形成了一套精细的 LLM 监督策略。首先,他建立了一个 “质量阈值过滤机制”:不直接信任 LLM 生成的每一个代码片段,而是要求 Codex 将所有变更批量提交到 review 文档中,然后由作者进行分组审核,筛选出真正重要的质量问题并将其纳入计划文档。这种机制解决了 LLM 在代码审查过程中 “过度热情” 的问题 —— 它倾向于生成大量激光聚焦的挑剔性意见,而这些意见中绝大多数并不影响最终的功能正确性或可维护性。通过过滤,作者将注意力集中在真正值得修复的问题上,避免了无效的返工循环。

干预时机的选择同样至关重要。作者观察到,如果不及时在模型开始偏离正轨时进行干预,问题会迅速累积并在后续的上下文中被固化 —— 模型会通过特殊处理来规避已知的错误,而非真正修复根本原因。这种 “补丁叠加” 的模式会导致代码库变得臃肿且难以维护。作者的经验法则是:当模型的行为开始 “闻起来不对” 时立即介入,而不是等待问题完全暴露后再进行处理。这种直觉的培养需要大量的实践经验 —— 在项目进行期间,作者已经从事了约 15 个月的 LLM 协作开发,这使得他能够敏锐地捕捉到模型失控的早期信号。

关键参数与工程决策的启示

从 bitplane 的项目中,我们可以提炼出一系列可操作的工程参数与决策原则。首先,在 LLM 协作处理复杂二进制格式时,规格文档的质量直接决定了生成代码的下限 —— 如果规格本身存在歧义或遗漏,LLM 会在生成过程中引入大量 “幻觉” 式实现,这些实现可能在特定测试用例下通过,但在面对真实世界中的边缘 case 时迅速失效。因此,在开始代码生成之前,投入足够的时间进行规格整理与验证是必要的投资。

其次,多模型协作的策略需要针对每个模型的能力特征进行定制。Claude 系列模型在架构讨论与文档生成方面表现出色,但需要 “短绳拴住” 以防止其过度投入实现细节;Codex 系列模型在获得明确规格后能够稳定执行长期任务,但其过度交互会导致任务偏离。合理的实践是将前者用于前期探索与后期验证,将后者用于大规模代码生成与性能调优。

第三,在处理 Rust 等强类型语言时,需要预设好代码组织与错误处理策略的模板,让 LLM 在模板内工作而非从零生成所有结构。作者在项目中的教训表明,缺乏预设架构会导致后期需要进行昂贵的重构 —— 特别是在处理二进制格式特有的位操作、生命周期管理以及错误传播时,LLM 倾向于生成松散且难以统一的代码。

第四,测试与文档的质量与数量直接决定了模型在长期项目中的表现稳定性。项目中使用了 “过多测试”—— 包括脆弱测试、无意义的覆盖率指标以及超长测试名称 —— 来提供统计质量,迫使模型在生成过程中被拉回正确轨道。这种 “质量通过数量” 的策略在 LLM 协作场景下被证明是有效的,即使其中部分测试本身并不直接服务于功能验证。

结语

bitplane 的 rars 项目为我们提供了一个独特的视角,去观察当 LLM 被赋予实现一个跨越数十年演进的商业文件格式时,其在工程实践中展现的真实能力与局限。结果是混合的:一方面,现代 LLM 在 Rust 代码生成、架构建议以及大规模代码填充方面展现出了令人惊讶的能力;另一方面,其在性能优化、边缘 case 处理以及跨版本一致性维护方面的局限性同样明显。这些局限性并非不可逾越,但它们需要通过人类工程师的主动监督、架构约束的预设以及质量机制的精细设计来进行补偿。在这个意义上,LLM 协作开发的本质并非 “放手让模型做”,而是构建一个让模型在受控边界内持续产出的工程环境 —— 这对于系统级代码生成而言,仍然是一个需要大量实践与迭代才能完善的协作形态。

资料来源:bitplane.net - rars in Rust, bro

compilers

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com