如何关联LLVM IR分析结果与二进制最终地址实现调用类型匹配
IR层call类型信息与二进制地址关联的实现路径
已梳理的核心设计约束
- LLVM IR pass运行于编译中段,此时尚未完成链接、最终可执行文件的内存布局未确定,无法在pass执行阶段直接获取机器code call指令的最终运行地址
- IR层call指令与机器call指令虽不严格一一映射,但存在稳定的映射关联,可作为两层分析结果绑定的锚点
- 纯二进制侧反汇编分析方案存在天然短板:无法准确解析间接call的目标函数类型签名,这也是选择在IR层开展分析的核心动因
元数据注入留存的可落地方案
之前提及的“注入可在最终可执行文件中识别的元数据”方案,本质是通过固定锚点标记,建立IR call指令与机器call指令的稳定关联,具体可按以下步骤实现,解决元数据被优化、链接流程裁剪的问题:
- IR pass阶段为每个目标call点注入唯一不可丢失的标识
避免使用易被优化移除的临时变量或孤立全局变量,采用带sideeffect标记的内联汇编形式,为每个需要分析的call指令生成全局唯一的32位call_site_id,将ID值以立即数形式嵌入call指令相邻的代码段,参考IR插入代码如下:
带; 在call指令前插入带副作用标记的内联汇编,嵌入唯一ID call void asm sideeffect ".long $0", "i"(i32 12345) ; 原本的IR call指令 call void %func_ptr(i32 %arg)sideeffect修饰的内联汇编会被LLVM优化器认定为存在外部副作用,不会被移除、不会被重排位置,保证嵌入的ID值永远和对应call指令在机器码层面相邻。 - 全编译流程的元数据保全处理
多数实现踩坑的核心原因是元数据在后续流程被裁剪,需针对性处理:- 对内联汇编嵌入的ID值,不要添加可折叠、可消除的优化属性,靠
sideeffect标记阻止后端指令选择、寄存器分配阶段的位置挪动 - 不要将元数据存放至独立全局数据段,让标识直接嵌入代码段,避免链接器执行段垃圾回收时清理无引用的孤立全局对象
- 对内联汇编嵌入的ID值,不要添加可折叠、可消除的优化属性,靠
- 二进制侧的锚点匹配流程
最终可执行文件生成后,通过反汇编定位所有嵌入的32位立即数ID:- 每个ID值紧邻的下一条指令,就是对应IR call映射生成的机器call指令,读取该ID值即可反向关联IR pass阶段预先存储的、该ID对应的完整函数类型签名、调用约定等分析结果
- 该方案不依赖调试信息,即使是剥离调试符号的可执行文件也可正常工作
针对间接call场景的适配优势:IR层分析阶段已完整获取间接call的所有可能目标类型、虚表偏移对应的签名信息,不需要在二进制侧靠启发式规则猜测类型,仅通过锚点ID即可直接把IR层已完成的分析结果匹配到对应二进制call地址上,从根源上解决纯二进制分析对间接call类型识别率低的问题。
内容的提问来源于stack exchange,提问作者Tomfox
相关产品推荐
相关产品推荐

