fuqiuluo’s blog

记录美好生活

心情随笔

LLVM - 开发属于自己的llvm plugin(1)

#LLVM#Rust
type
Post
status
Published
date
Aug 3, 2025
slug
summary
tags
LLVM
Rust
category
心情随笔
icon
password

开始

这里使用的语言是Rust!
LLVM 插件仅仅是一个 dylib,LLVM 工具(例如opt、lld )加载它时会为其提供PassBuilder。因此,你必须在你的Cargo.toml 添加下面这行:
PassBuilder允许注册 LLVM 工具执行的特定操作的回调,更多详细细节和原理可以看 https://github.com/jamesmth/llvm-plugin-rs 仓库的介绍!
这里需要注意一个问题:官方的 Windows 版 LLVM 工具链并未内置插件支持。所以说Windows必须修改Clang源代码重新编译生成新的clang.exe! -- 2025.07.29 PS
首先导入我们需要的包
截至到2025.8.2 llvm20-1的相关pr依旧没有合并进去,所以说这里我说通过cargo patch提前体验了,这两个仓库是比较前沿的两个分支!

Hello World

以上是官方示例的标准例子,当然这个例子肯定是啥用没有... 这里你可以看见他调用了add_function_pipeline_parsing_callback 注册函数级别的pipeline解析回调(一个函数/方法就是一个Function),当然函数级别就还有别的级别add_module_analysis_registration_callback :用于向pass管理器注册模块级别的分析!(一个.c文件编译后就是一个模块)......
这里llvm-plugin-rs尚未支持注册:
  • CGSCC级别分析注册 PassBuilder.registerCGSCCAnalyses()
  • Loop级别分析注册PassBuilder.registerLoopAnalyses()
具体的用途:
  • Loop: 分析循环特性(是否可向量化、循环次数等)
  • Function: 分析函数内部(变量生存期、控制流等)
  • CGSCC: 分析相互递归函数(尾递归优化等)
  • Module: 分析整个编译单元(全局优化、链接时优化等)
首先把您需要的.c文件转换成ll文法文件, 再执行opt进行优化。--passes指定需要打开的pass
如果正常输出了日志说明你的插件已经完成了基础的配置了,这时候你想实现什么字符串加密都是可以的!这里就必须得提到SsageParuders大牛的SsagePass了!给我提供了完整的思路,项目有清晰的注释!

实现一个基础的字符串加密

这里我们先熟悉一下LLVM Pass Pipeline中各个扩展点的执行顺序:
notion image
这里Ssage大佬没有选择registerPipelineParsingCallback而是使用了registerPipelineStartEPCallback,原因也在他的源代码里面,这里懒得说了!
添加一个Pass需要提供了一个LlvmModulePass类型的玩意,
notion image
notion image
这里补充一下返回值的意思,All就是你没有修改新增任何东西,如果修改了新增了就要返回None使得之前的分析和优化全部无效(这个时候有优化吗?LLVM的API非常不稳定,每个版本几乎大概,虽然他们会贴心的写个markdown表示改变了什么,该怎么做,但是很难有人去盯着这个啊
这里先定义一个枚举表示一下加密的类型,还有一些别的什么东西?!不过我觉得需要扩展的话,应该使用bitflags,这个功能不是很复杂先用枚举占位吧。
叽里呱啦的,我不想写博客,奖励一个源代码:

简单说一下官方例子

官方例子代码:https://github.com/jamesmth/llvm-plugin-rs/blob/master/examples/strings.rs#L52
把所有的字符串加密当然,他只加密了.str字符串,然后
create_decode_stub 函数使用了 inkwell(LLVM 的 Rust 语言绑定)来在 IR 级别动态生成一个新的 LLVM 函数 decode_stub。该函数的任务是在运行时依次调用用户传入的 decode_fn,对所有全局字符串进行“解码”处理。核心流程可以概括为:
  1. 创建一个无参、返回值为 的新函数 decode_stubvoid
  1. 在新函数里为每个 取得指向其第一个字节的 i8* 指针,以及字符串长度 lenGlobalString
  1. 把这两个参数传给已存在的 decode_fn(形如 void decode(u8* ptr, u32 len))。
  1. 最后在 decode_stub 末尾插入 ret void 指令并返回函数句柄。
decode_stub 的意义就在于“程序真正开始执行之前先把所有静态密文解码”,因此常见的两种接入方式是:
  1. main(或等价的入口函数)的 第一条指令之前 直接插入一次调用(示例代码的方式
  1. decode_stub 注册到 llvm.global_ctors,让它在运行时以 全局构造函数 的身份自动执行,而无需改动 main
这样实现的话,非常简单,但是也不够安全,对于一个Passes插件来说,扩展性太差了!

参考项目

  • https://github.com/SsageParuders/SsagePass
Loading...