xdzleo/xenon-jumptables
GitHub: xdzleo/xenon-jumptables
针对 Xbox 360 PowerPC 代码的静态重编译场景,专门恢复 IDA / Ghidra 及重编译器遗漏的 switch 跳转表,消除因未解析 bctr 指令导致的运行时陷阱崩溃。
Stars: 1 | Forks: 0
# xenon-jumptables
从 Xbox 360 / Xenon PowerPC 代码中恢复计算跳转(`switch`)表,用于
静态重编译。它分析 IDA Pro 中的原始代码映像,并输出用于
[XenonRecomp](https://github.com/hedge-dev/XenonRecomp)
(`switch_table_file_path`)或 ReXGlue(`[[switch_tables]]`)的 switch 表 TOML。
未解析的跳转表是导致刚重编译的游戏发生陷阱并崩溃的主要原因之一:
重编译器会将其无法解析的每个 `bctr` 转换为陷阱。
XenonRecomp 自己的文档也指出了这一点——带有跳转表的函数“看起来像没有足够信息的尾调用”,除了手动标注函数边界外,“目前没有解决方案”。该工具能够解析这些表(即目标本身),这正是重编译器以及 IDA 和 Ghidra 中现成的 switch 分析所遗漏的。
## 为什么需要专用工具
Xbox 360 编译器会发出常规 switch 恢复无法匹配的跳转表惯用语,因此它会默默地将 `bctr` 视为尾调用:
- **通过条件 *return*** (`cmplwi r,N` / `bgelr`) 进行边界检查,而不是分支到默认标签——IDA 和 Ghidra 的匹配器期望的是后者。
- **原位缩放的索引** (`rlwinm r11, r11, 2`),并 **重新加载** 到另一个寄存器以进行表访问。
- **两级“相对”表**:一个打包的字节/半字偏移量表被添加到锚点 (`target = anchor + table[i]*scale`),而不是指针表。
- **表内联嵌入在 `.text` 中**,紧跟在 `bctr` 之后。
在一个零售游戏(约 7 MB 映像,约 1.1 万个函数)上,差异非常明显:
| | 已解析的 switch 表 |
|---|---|
| IDA 9.2 自动分析 + Hex-Rays | 6 |
| Ghidra 12 自动分析 | 7 |
| **xenon-jumptables** | **74** |
这 74 个表是根据原始字节独立重新推导并进行交叉检查的,并且与重编译器自身(更小)的集合在共享子集上保持一致。计数是针对单个游戏的;它们所使用的惯用语则不然。某个游戏是否真的需要此工具完全取决于它的编译器——有些编译器只发出重编译器已经处理过的教科书式惯用语;而其他编译器(如上表所示)则依赖于它无法处理的惯用语。
## 工作原理
它利用感知惯用语的数据流来驱动 IDA 的反汇编器,而不是依赖 IDA 的 switch 启发式算法:
1. 从重编译器的函数列表中定义每个函数,以便分析 *所有* `bctr`——而不仅仅是那些从入口点可达的 `bctr`。
2. 对于每个 `bctr`,往回遍历具体化惯用语:找到表基址 (`lis`/`addi`)、缩放的索引和边界检查——在索引通过副本、原位缩放和重载时对其进行跟踪,并在块边界处停止,这样来自其他路径的比较就永远不会被误认为是边界。
3. 读取表(绝对指针,或锚点 + 相对偏移量),验证每个目标都是 `.text` 内部 4 字节对齐的地址。
4. 迭代:一个已解析的表将其 case 主体作为代码公开,这些代码可能包含更多的 `bctr`——循环至不动点。
有关完整的惯用语目录和棘手情况(原位破坏、重载、内联 `.text` 表、重叠表),请参阅 [docs/idioms.md](docs/idioms.md)。
## 环境要求
- **IDA Pro** 及 PowerPC 处理器模块(基于 IDA 9.2 开发;分析阶段使用纯 IDAPython,避免使用特定版本的 API)。IDA Pro 是商业软件——这是一个 IDA 处理阶段,而不是独立的反汇编器。
- **Python 3**,用于驱动程序和转换器。
- 游戏的 **原始代码映像** 和 **函数地址列表**(每行一个十六进制地址)。两者都直接来自您的重编译器:它会在启动时转储映像并打印地址范围,而每个函数的来源提供了函数列表(参见 [`extract_funcs.py`](src/extract_funcs.py))。
## 用法
编写配置(地址完全就是重编译器在启动时打印的内容):
```
{
"image": "game.bin", // raw code image dump
"image_base": "0x82000000",
"image_end": "0x826D0000",
"text_start": "0x82080000",
"text_end": "0x8230EC00",
"functions": "functions.txt", // one hex function address per line
"format": "xenonrecomp", // or "rexglue"
"toml": "switch_tables.toml"
}
```
(上文为了可读性添加了注释——请复制 [`examples/config.example.json`](examples/config.example.json),
它是纯 JSON。)然后运行驱动程序:
```
python src/recover.py config.json --ida "/path/to/idat"
```
它将映像封装为 ELF,无头运行 IDA 处理阶段,并写入
`switch_tables.toml`。要自行运行这些步骤,请参阅
[`recover.py`](src/recover.py) 中的注释;每个阶段(`make_elf.py`、`ida_jumptables.py`、
`gen_toml.py`)都可以独立使用。
从重编译器检出中获取函数列表:
```
python src/extract_funcs.py -o functions.txt
```
## 输出
`gen_toml.py` 按任一重编译器的 schema 为每个表写入一个条目
(`--format`,默认为 `xenonrecomp`):
```
# format = xenonrecomp # format = rexglue
[[switch]] # [[switch_tables]]
base = 0x820B12D0 # address = 0x820B12D0
r = 11 # register = 11
labels = [0x820B4428, 0x82308398, 0x820B34D0, 0x820B3DC0, 0x820B3ED8, 0x820B45B8]
```
`labels` 是已排序的 case 目标列表;`r` / `register` 是索引 GPR。
这两个重编译器都基于该寄存器生成 `switch` 键,因此建议使用
[`switch-on-ctr`](patches/) 补丁(它使得寄存器变得无关紧要)。如果需要,原始的 `jumptables.json` 会携带更多详细信息(类型、锚点、缩放比例、边界)。
有关将 TOML 接入 XenonRecomp / ReXGlue 的信息,请参阅 [docs/integration.md](docs/integration.md)。
## 代码生成补丁
[`patches/switch-on-ctr.patch`](patches/) 是对重编译器的 `build_bctr` 的一个微小但推荐的更改:基于计算出的 `ctr` 值进行 switch,而不是基于索引寄存器(编译器通常在 `bctr` 之前将其破坏),并将超出范围的情况设为运行时间接调用,而不是 `__builtin_trap()`。
连同恢复的表一起,它不仅修复了一类潜在的崩溃,还使不完美的表能够优雅降级。请参阅 [patches/README.md](patches/README.md)。
## 适用范围与限制
- 32 位大端序 PowerPC,Xbox 360 布局。惯用语识别器假设使用 Xenon 编译器的模式。
- 它解析 `bctr` **跳转表**。间接 `bctr`/`bctrl` *调用*(虚分发、函数指针尾调用)不是跳转表,留给重编译器的运行时间接调用路径处理,这对它们来说是正确的。
- 只有在表被证明有效时才会发出目标列表:静态基址、边界,并且每个条目都是 `.text` 中有效的指令地址。模棱两可的情况将被跳过,而不是盲目猜测。
- 它不需要在此处检入任何游戏二进制文件,您也不应该提交您的文件——不要将转储文件放入仓库中。
## 许可证
BSD 3-Clause。请参阅 [LICENSE](LICENSE)。
标签:IDA Pro, PowerPC, Python安全, Xbox 360, 云资产清单, 控制流恢复, 逆向工具, 逆向工程, 静态重编译