Hotdry.
ai-engineering

构建Photoshop 1.0源代码的逆向工程工具链:从二进制到AST的自动化流水线

针对Photoshop 1.0.1源代码的逆向工程工具链实现,涵盖资源提取、68K反汇编、符号恢复与AST重建的完整技术方案。

2013 年,计算机历史博物馆发布了 Adobe Photoshop 1.0.1 的完整源代码,这份包含约 128,000 行 Pascal 代码和 68K 汇编的遗产,为软件考古学提供了珍贵的研究材料。然而,直接分析这些源代码仅是逆向工程的第一步,构建完整的工具链以自动化处理二进制到高级语言表示的转换,才是工程化的核心挑战。本文将深入探讨针对 Photoshop 1.0 的逆向工程工具链实现,从资源提取到抽象语法树(AST)重建的完整技术方案。

技术背景与挑战

Photoshop 1.0 诞生于 1988-1990 年间,其技术栈具有典型的时代特征:使用 THINK Pascal 编译器针对 Motorola 68000 处理器,运行在经典 Mac OS 系统上。经典 Mac OS 采用独特的资源分支(Resource Fork)架构,将代码、图像、字符串等资源统一管理。这种架构为逆向工程带来了特定挑战:

  1. 资源压缩格式:Mac OS 资源管理器使用 dcmp(Decompressor)资源进行透明压缩,这些解压器本身是 68K 或 PowerPC 代码片段
  2. 编码转换:文本资源使用 Mac OS Roman 编码,需要转换为 UTF-8
  3. 混合语言:Pascal 主体代码中嵌入大量 68K 汇编优化关键路径
  4. 工具链过时:原始编译工具链在现代系统上难以运行

工具链核心组件设计

完整的逆向工程工具链需要多个协同工作的组件,形成从二进制到高级表示的转换流水线。

1. 资源提取器(Resource Extractor)

基于resource_dasm工具的实现,这是处理经典 Mac OS 资源分支的核心。该工具能够:

  • 读取资源分支、AppleSingle/AppleDouble 文件、MacBinary 文件等多种格式
  • 透明解压使用 dcmp 资源压缩的内容
  • 将超过 100 种资源类型转换为现代格式

关键实现细节包括对 dcmp 资源的仿真执行。如resource_dasm项目文档所述:“资源管理器压缩方案从未由 Apple 正式记录或公开,因此这些解压器的实现基于对 ResEdit 和其他经典 Mac OS 代码的逆向工程。” 工具内置了 68K 和 PowerPC 仿真器,能够运行非默认解压器,包括系统内置的 DonnDecompress、GreggyDecompress 以及第三方如 After Dark 的自修改 dcmp 128。

2. 68K 反汇编器与符号恢复

对于 CODE 资源中的 68K 汇编代码,需要专门的处理器。m68kdasm组件提供了以下能力:

  • 反汇编原始 68K、PowerPC、x86 或 SH-4 二进制代码
  • 解析 PEF(经典 Mac OS PowerPC 可执行)文件、DOL(任天堂 Gamecube 可执行)文件等多种格式
  • 通过解析 CODE 0 资源中的跳转表恢复导出函数符号

符号恢复的关键在于理解经典 Mac OS 的 A-Trap 机制。系统调用通过 A-Trap 指令(如_A_Line)实现,反汇编器需要维护完整的 Trap 表映射,将二进制操作码转换为有意义的 API 名称。例如,_NewHandle调用对应特定的 A-Trap 编号,反汇编器应输出可读的符号而非原始操作码。

3. Pascal 语法分析与 AST 重建

从反汇编代码重建 Pascal AST 需要处理语言特定的语义:

// 原始Pascal代码特征
procedure ProcessImage(var buffer: Ptr; width, height: Integer);
type
  PixelArray = array[0..32767] of Byte;
var
  pixels: ^PixelArray;
  i: Integer;
begin
  pixels := Pointer(buffer);
  for i := 0 to width * height - 1 do
    pixels^[i] := 255 - pixels^[i];  // 反相操作
end;

AST 重建器需要识别 Pascal 特有的语言结构:

  • begin/end块而非大括号
  • procedure/function声明语法
  • 类型安全的枚举和子范围类型
  • with语句的记录字段访问

4. 跨平台编译适配层

为使逆向工程结果可在现代系统上编译,需要适配层处理平台差异:

  1. 内存模型转换:从经典 Mac OS 的 Handle-based 内存管理转换为现代堆分配
  2. API 映射:QuickDraw 图形 API 到现代图形库的映射
  3. 数据类型对齐:68K 的字节对齐要求与现代处理器的差异

实现步骤:四阶段转换流水线

第一阶段:二进制资源提取

# 使用resource_dasm提取Photoshop资源
./resource_dasm "Photoshop 1.0.1" ./photoshop.out \
  --save-raw=yes \
  --image-format=png \
  --text-encoding=utf-8

此阶段输出包括:

  • CODE资源:反汇编为 68K 汇编文本
  • PICT资源:转换为 PNG 格式
  • TEXT/STR资源:转换为 UTF-8 文本
  • 原始二进制资源备份

第二阶段:控制流分析与符号恢复

对反汇编的 68K 代码进行控制流分析,识别基本块、函数边界和调用图。关键算法包括:

  1. 递归下降反汇编:从已知入口点开始,跟踪所有可能的分支目标
  2. 数据 / 代码分离:通过启发式方法区分代码段和数据段
  3. 类型恢复:通过使用模式推断变量类型(指针、整数、记录等)

符号恢复的启发式规则:

  • 遵循 68K 调用约定的参数传递模式
  • 全局变量通常通过 A5 相对寻址访问
  • 局部变量使用 A6(帧指针)相对寻址

第三阶段:Pascal AST 生成

将低级中间表示转换为 Pascal AST 的过程:

class PascalASTBuilder:
    def visit_68k_instruction(self, instr):
        # 将68K指令映射到Pascal结构
        if instr.opcode == 'MOVE.L' and instr.src.startswith('(A6'):
            # 局部变量访问
            return LocalVarAccess(self.extract_offset(instr.src))
        elif instr.opcode == 'BSR' or instr.opcode == 'JSR':
            # 过程调用
            return ProcedureCall(self.resolve_symbol(instr.target))
    
    def reconstruct_control_flow(self, basic_blocks):
        # 重建if/then/else, for, while等控制结构
        # 基于分支模式和循环检测算法

第四阶段:代码优化与规范化

生成的 Pascal 代码需要进一步处理以提高可读性和可维护性:

  1. 常量传播:替换已知常量的计算
  2. 死代码消除:移除不可达的代码路径
  3. 表达式简化:简化复杂的算术和逻辑表达式
  4. 注释生成:基于反汇编上下文添加解释性注释

工程挑战与解决方案

挑战一:资源压缩格式的多样性

经典 Mac OS 支持多种压缩格式,且允许应用程序提供自定义 dcmp 资源。解决方案:

  1. 内置解压器库:实现系统标准解压器(dcmp 0, 1, 2, 3)
  2. 仿真执行:对于自定义解压器,使用内置 68K 仿真器执行
  3. 回退机制:当解压失败时保存原始二进制供手动分析

挑战二:混合语言代码的协调处理

Photoshop 代码中 Pascal 与 68K 汇编紧密交互。处理策略:

  1. 内联汇编识别:通过特定模式识别INLINE指令嵌入的汇编
  2. 调用约定适配:确保 Pascal 与汇编间的参数传递正确转换
  3. 寄存器使用分析:跟踪汇编代码对 Pascal 变量的寄存器分配

挑战三:平台特定 API 的抽象

QuickDraw、Toolbox 等 API 在现代系统中不存在。解决方案:

  1. API 兼容层:实现关键 API 的现代替代
  2. 功能子集:专注于核心图像处理功能,非关键 UI 代码可简化
  3. 条件编译:使用编译指令隔离平台相关代码

工具链配置与使用示例

完整的工具链配置需要多个组件协同工作:

# 工具链配置文件 reverse_engineer.yml
stages:
  - name: resource_extraction
    tool: resource_dasm
    args:
      - "--save-raw=yes"
      - "--image-format=png"
      - "--text-encoding=utf-8"
  
  - name: disassembly
    tool: m68kdasm
    args:
      - "--architecture=m68k"
      - "--resolve-symbols"
      - "--output-format=pascal"
  
  - name: ast_generation
    tool: pascal_ast_builder
    args:
      - "--reconstruct-types"
      - "--generate-comments"
  
  - name: code_emission
    tool: pascal_emitter
    args:
      - "--modern-syntax"
      - "--platform-adaptation=modern"

使用流程:

# 完整逆向工程流程
./reverse_engineer --config reverse_engineer.yml \
  --input "Photoshop 1.0.1" \
  --output ./reconstructed/
  
# 结果验证:尝试编译重建的代码
cd ./reconstructed/
fpc main.pas -o photoshop_reconstructed

质量评估与验证机制

为确保逆向工程结果的正确性,需要建立验证机制:

  1. 行为一致性测试:使用原始二进制和重建代码处理相同输入,比较输出
  2. 代码覆盖率分析:确保所有反汇编的代码路径都在重建代码中体现
  3. 手动审查点:对关键算法(如卷积滤波、颜色转换)进行重点审查
  4. 历史版本比对:与后来版本的 Photoshop 源代码进行语义比对

验证指标包括:

  • 代码行数匹配度(原始 vs 重建)
  • 函数接口一致性
  • 关键算法输出位精确性
  • 资源引用完整性

扩展应用与未来方向

此工具链不仅适用于 Photoshop,还可应用于其他经典 Mac OS 软件的逆向工程:

  1. 其他创意软件:Adobe Illustrator 早期版本、MacPaint 等
  2. 游戏逆向:经典 Mac 游戏资源提取与修改
  3. 系统软件研究:操作系统组件分析

未来改进方向:

  • 机器学习辅助:使用神经网络改进控制流分析和类型推断
  • 多语言输出:支持将 Pascal 代码转换为 C、Rust 等现代语言
  • 交互式分析环境:集成可视化工具,支持探索性逆向工程
  • 云化处理:将计算密集型分析任务迁移到云端

结语

构建 Photoshop 1.0 的逆向工程工具链不仅是对历史代码的技术复原,更是对软件工程方法论的实践检验。通过系统化的资源提取、反汇编、符号恢复和 AST 重建,我们能够将二进制遗产转化为可读、可维护、可演进的代码库。这一过程揭示的不仅是 30 多年前的编程实践,更是软件系统长期演化的普遍规律。

工具链的成功实现证明了,即使面对过时的技术栈和复杂的系统交互,通过分层的架构设计和自动化的处理流程,仍然能够有效地进行大规模代码逆向工程。这为处理其他历史软件系统提供了可复用的方法论和工具基础。

参考资料:

  1. Hacker News 讨论:Adobe Photoshop 1.0.1 Source Code (2013) - https://news.ycombinator.com/item?id=17132058
  2. resource_dasm 工具:经典 Mac OS 资源反汇编器 - https://github.com/fuzziqersoftware/resource_dasm
查看归档