garlic decompiler 的 native 反编译部分我起了一个新的名字:rosemary (迷迭香), rosemary 的设计思路就在链接的地方。
rosemary 是 ruby 和 c 到混合产物,ruby 负责生成解码器的静态数据,c 语言端负责解码。
这两个月完成的事情不算多,主要都给数据结构交学费了,因为一个 elf 动辄上千万的指令,数据结构处理不好的话,内存和运行速度就是巨坑。 这个过程中,我也逐渐的意识到原来的 garlic decompiler 到底有多少优化空间。如果重构,它能占用内存更小,运行的更快!
感触最深的就是:能用数组就用数组,能用整数不要用指针,这两个月我开始用 AI 写了,用 AI 写的速度跟原来的古法编程差不多,因为我没有 claude code ,都是我把内容喂 gemini ,gemini 帮忙生成方案,我来改。
arm 的 A profile 的反汇编花了接近 3 个月才支持完了,这块也是 AI 埋坑埋的最多的地方,我用 AI 去扣精细的 bit 位就是跳坑的开始,每一个 bit 位都是人工一点儿一点儿都对出来的,这里估计是人比 AI 靠谱的地方。
rosemary 现在支持 T32/A32/A64 的全系列解码,包括最新的指令集如 SVE/SVE2/SME/SME2 。
由于是 dsl 驱动的解码器,修改起来更方便,我在我的 macmini m4 上跟 capstone 对比毫不逊色。
测试的部分是最伤脑筋的地方,开始的时候我伪造了非常多的指令,根据解码规则,随机生成指令,但是生成的指令有很大一部分是校验不过去了,比如连续的两个寄存器这种,DSL 根本没有办法描述到。
后来我找到了 capstone 和 llvm 的测试数据,顺带着把自己生成的测试数据喂给 llvm-mc 来测试。花了接近半个月搞定了这块。
c 的测试真不是人写的,后来我把他们全迁移到 ruby 上,简直不要太爽。
静态分析有两个重要的环节:控制流和数据流
开始做控制流的时候,根本没有管什么数据结构,直接定义:
typedef struct {
u4 start_pc;
u4 end_pc;
list *in;
list *out;
struct jd_elf_basic_block *idom;
list *dom_frontier;
list *domnaces;
// ....
} jd_elf_basic_block;
一堆一堆的指针直接就堆出来,功能很快就写完了。测试的时候,bingo ! 40M 的 so ,770w 条指令,常驻内存 3G+,运行时间超过 20 秒。这里仅仅是解码和控制流,甚至连控制流的 edge 还没有连。
在这里 AI 就起了大作用了,AI 太强了,我是一边跟它聊,一遍修改逻辑。直到达到令人满意的结果为止。
数据流比控制流好做一些,数据流完全基于 ruby 的 DSL ,可以直接在汇编的操作数上标注数据的流向,然后在 c 语言部分解开存储就 ok 了。
数据流和 IR 是强绑定的,这里有过 garlic 的经验,这里过的要比控制流快。
现在的运行效果令人十分满意,微信的 libapp.so ,40M ,768W 的 intructions ,110w+的 basic block ,800w+的 ir expression ,在我的 Macmini M4 上线性的跑只需要 1.2 ~ 1.3 秒,常驻内存 750M 左右。
~/workspace/clang/garlic (fea/rosemary)$ time -v ./build/elf
[elf] file path: /Users/neo/workspace/elfs/weixin/libapp.so
[elf error]: elf entry addr: 0, has no region
----- elf memory start ------
[elf memory] elf's binary buffer: 42271680, 40 MB
[elf memory] elf's entry points: 105420, size: 32 memory: 3 MB
[elf memory] elf's entry hashmap: 105420 size: 16, memory: 1 MB
[elf memory] elf's region bitmap: 2 MB
[elf memory] elf's basic block count: 1116610, size: 96, memory: 102 MB, includes defs/uses
[elf memory] basic block's defs count: 1116610, size: 16, memory: 17 MB
[elf memory] basic block's uses count: 1116610, size: 16, memory: 17 MB
[elf memory] elf's edge count: 1783299, size: 8, memory: 13 MB
[elf memory] elf's expression count: 8456462, size: 40, memory: 322 MB
[elf memory] elf's instruction count: 7683159, size: 32, memory: 234 MB
[elf memory] elf's template count: 6000, size: 68, memory: 0 MB
----- elf memory end ------
----- elf info start ------
[elf info] elf's execute region 0: a30000 - 284c710
[elf info] elf's entry address size: 105420
----- elf info end ------
Command being timed: "./build/elf"
User time (seconds): 1.33
System time (seconds): 0.10
Percent of CPU this job got: 98%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.44
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 754400
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 39
Minor (reclaiming a frame) page faults: 49178
Voluntary context switches: 5
Involuntary context switches: 130
Swaps: 0
File system inputs: 0
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 16384
Exit status: 0