使用 opt 优化 LLVM IR,定制 clang 实现函数名加密
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
1. LLVM IR
LLVM IR(Intermediate Representation) 是 LLVM 编译框架中的一种中间表示形式,它是一种面向低级的中间代码,是 LLVM 架构的核心部分。LLVM IR 既可以用作 LLVM 编译器的输入,也可以用作输出,供其他编译器或工具链使用。
LLVM IR 的两种存储形式:
LLVM IR 语言(文本形式,扩展名 .ll):可读的 LLVM IR 代码。
LLVM 位码(Bitcode,扩展名 .bc):二进制形式的 LLVM IR,主要用于高效存储和传输。
LLVM IR 是平台无关的,可以跨不同的硬件架构进行移植。
LLVM IR 语言参考手册:https://llvm.org/docs/LangRef.html
1.1 将 C 文件转换为 LLVM IR
生成文本形式的 LLVM IR(.ll 文件)
clang -S -emit-llvm hello.c -o hello.ll
以 hello.ll 为例,生成的内容大概如下
; Function Attrs: noinline nounwind optnone uwtable
; 这行注释了函数的属性
; - noinline: 该函数不会被内联(inline)。
; - nounwind: 该函数不会引发异常(不会 unwind stack)。
; - optnone: 编译器不会对此函数进行任何优化。
; - uwtable: 该函数有一个可用的异常处理表(unwind table)。
define dso_local i32 @main() #0 {
; define: 定义一个函数。
; dso_local: 该函数在本地动态库中可见(仅限于当前编译单元)。
; i32: 返回类型为 32 位整数。
; @main: 函数名为 'main'。
; #0: 函数使用属性组 0 中定义的属性(即上面注释的那一行)。
entry:
; entry: 函数的入口基本块(Basic Block)的标签。
%retval = alloca i32, align 4
; alloca: 在栈上分配内存。这里分配了一个 32 位整数(i32)。
; align 4: 分配的内存对齐到 4 字节边界。
; %retval: 变量的名称(SSA 变量),用于存储 main 函数的返回值。
store i32 0, ptr %retval, align 4
; store: 将一个值存储到内存地址中。
; i32 0: 要存储的值是 32 位整数 0(即 main 函数的返回值)。
; ptr %retval: 存储的位置是之前分配的栈变量 %retval。
; align 4: 存储操作按 4 字节对齐。
%call = call i32 (ptr, ...) @printf(ptr noundef @"??_C@_0P@MHJMLPNF@Hello?0?5World?$CB?6?$AA@")
; call: 调用一个函数,这里调用了 C 标准库的 printf 函数。
; i32 (ptr, ...): printf 函数的原型。它接受一个指针(通常是 C 字符串格式化字符串)和可变参数列表(...)。
; ptr noundef @"??_C@_0P@MHJMLPNF@Hello?0?5World?$CB?6?$AA@": 这是一个字符串常量的地址(指针)。
; noundef: 表示传递给函数的参数不会是未定义的(undefined)。
; %call: 保存 printf 的返回值(返回打印的字符数)。
ret i32 0
; ret: 返回指令,用于结束函数的执行。
; i32 0: main 函数返回 0(表示正常退出)。
}
1.2 生成二进制 LLVM IR(.bc 文件)
生成二进制 LLVM IR(.bc 文件)
clang -emit-llvm -c hello.c -o hello.bc
可以使用 llvm-dis 转换回文本形式
llvm-dis hello.bc -o hello.ll
1.3 使用 clang 编译 IR 文件
运行以下命令将 .ll 文件编译为可执行程序
clang hello.ll -o hello.exe
2. opt
opt 是 LLVM 工具链中的一个优化工具。它用于读取、优化和转换 LLVM IR 文件。
opt 工具通过应用各种优化 passes,可以显著提高程序的性能。
opt 的功能:
优化:对 LLVM IR 进行一系列优化(如常量折叠、循环展开、函数内联等)。
分析:生成各种分析报告(如控制流图、数据流分析)。
转换:将 LLVM IR 从一种形式转换为另一种形式。
显示所有可用的优化 passes
opt --help
优化 LLVM IR 文件
opt -O3 hello.ll -o hello_opt.bc
参数说明:
-O3:表示应用最高级别的优化(与 clang 的 -O3 类似)。
-o hello_opt.bc:输出优化后的文件
生成可读的 LLVM IR 文件
opt -O3 hello.bc -S -o hello_opt.ll
参数说明:
- -S:表示输出可读的文本格式(.ll 文件)。
应用特定的优化 pass,并查看优化效果。例如
opt -passes=mem2reg hello.ll -S -o hello_mem2reg.ll
参数说明:
-passes:应用特定的优化 pass
mem2reg:将栈上的内存变量提升为寄存器变量(即“提升” alloca 指令)。
-S:输出可读的 .ll 文件。
函数内联优化,将函数调用替换为函数体,从而减少函数调用的开销。
opt -passes=inline hello.ll -S -o hello_inline.ll
生成函数的控制流图(Control Flow Graph),输出为 .dot 文件,可以用 Graphviz 进行可视化。
opt -passes=dot-cfg hello.ll
3. 使用 Clion 调试 opt
使用 CLion 打开 llvm-project\llvm\CMakeLists.txt
作为项目打开
打开 CMake 设置,工具链使用 Visual Studio,点击应用
等待 Cmake 执行完成后,可以看到 opt 的运行/调试配置
编辑 opt 运行/调试配置,添加程序实参,比如
-O3 "D:\Projects\llvm-project\build\hello.ll" -o "D:\Projects\llvm-project\build\hello_opt.bc"
找到 main 函数(llvm/tools/opt/opt.cpp),并下断点
调试 opt,并成功在 main 函数断点
4. LLVM Pass
4.1 Pass简介
LLVM Pass 是 LLVM 编译框架中的一种重要机制,用于对 IR 代码进行分析和转换。
通过编写自定义 Pass,开发者可以插入自己的逻辑来优化代码、分析性能或插入调试信息等。
4.2 Pass 的分类
- Module Pass
作用于整个 llvm::Module 。
适用于需要全局视角的操作,例如链接优化或全局变量分析。
- Function Pass
针对每个函数 llvm::Function。
适用于优化单个函数内的代码,例如循环优化、死代码删除等。
- Basic Block Pass
针对函数内的每个基本块(Basic Block)。
通常用于优化基本块内部,例如指令合并、无用指令消除等。
4.3 Pass 的实现
4.3.1 实现 Module Pass
创建 MyModulePass.cpp,定义一个模块级别的 Pass,run 函数实现了对模块的遍历,并输出每个函数的名称。
#include "llvm/IR/PassManager.h" // 包含 LLVM 新 Pass Manager 的头文件
#include "llvm/Passes/PassBuilder.h" // 提供 PassBuilder,用于注册和管理 Pass
#include "llvm/Passes/PassPlugin.h" // 用于实现动态插件的接口
#include "llvm/IR/Module.h" // 定义了 Module 类,用于表示 LLVM IR 的顶层结构
#include "llvm/Support/raw_ostream.h" // 提供 LLVM 的输出支持,如 errs() 输出到标准错误流
using namespace llvm; // 使用 LLVM 命名空间简化代码
// 定义一个 Module Pass
// Module Pass 是对整个模块进行操作的 Pass
struct MyModulePass : PassInfoMixin<MyModulePass> {
// run 函数:Pass 的主入口,用于实现实际的操作逻辑
// 参数说明
// Module &M: 表示当前处理的模块
// ModuleAnalysisManager &MAM: 提供对模块级分析的访问
PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM) {
// 输出当前模块的名称
errs() << "Processing Module: " << M.getName() << "\n";
// 遍历模块中的每个函数
for (auto &F: M) {
// 输出每个函数的名称
errs() << "Function: " << F.getName() << "\n";
}
// 返回 PreservedAnalyses::all(),表示此 Pass 不修改 IR
return PreservedAnalyses::all();
}
};
// 动态插件入口函数,LLVM 在加载插件时会调用此函数
// 函数名固定为 llvmGetPassPluginInfo,返回插件的描述信息
extern "C" PassPluginLibraryInfo llvmGetPassPluginInfo() {
// 返回插件的信息
return {
LLVM_PLUGIN_API_VERSION, // LLVM 插件 API 的版本号
"MyPass", // 插件的名称
LLVM_VERSION_STRING, // 当前 LLVM 的版本号
[](PassBuilder &PB) { // PassBuilder 的回调函数,用于注册 Pass
// 注册解析 Pipeline 的回调函数
PB.registerPipelineParsingCallback(
[](StringRef Name, ModulePassManager &MPM,
ArrayRef<PassBuilder::PipelineElement>) {
// 检查 Pipeline 中的 Pass 名称是否为 "my-module-pass"
if (Name == "my-module-pass") {
// 将 MyModulePass 注册到模块 Pass 管理器中
MPM.addPass(MyModulePass());
return true; // 表示 Pass 已成功注册
}
return false; // 名称不匹配,忽略
});
}};
}
4.3.2 实现 Function / Basic Block Pass
创建 MyFunctionPass.cpp,定义一个函数级别的 Pass,run 遍历函数中的基本块以及基本块中的每条指令,并将其输出。
#include "llvm/IR/PassManager.h" // 包含 LLVM 新 Pass 管理器的头文件
#include "llvm/Passes/PassBuilder.h" // 提供 PassBuilder,用于构建和注册 Pass
#include "llvm/Passes/PassPlugin.h" // 提供 Pass 插件接口的支持
#include "llvm/IR/Module.h" // 定义 LLVM IR 的模块类
#include "llvm/Support/raw_ostream.h" // 提供 LLVM 的输出支持,比如 errs()
using namespace llvm; // 使用 LLVM 的命名空间,简化后续代码编写
// 定义一个函数级别的 Pass
struct MyFunctionPass : public PassInfoMixin<MyFunctionPass> {
// 函数级别的运行入口
// 参数
// - Function &F: 当前正在处理的函数
// - FunctionAnalysisManager &FAM: 函数级别的分析管理器
PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) {
// 输出当前函数的名称
errs() << "Processing Function: " << F.getName() << "\n";
// 遍历函数中的每个基本块(Basic Block)
for (auto &BB : F) {
errs() << "Basic Block:\n";
// 遍历基本块中的每条指令
for (auto &I : BB) {
// 输出当前指令
errs() << I << "\n";
}
}
// 返回 PreservedAnalyses::all(),表明该 Pass 不修改 IR
return PreservedAnalyses::all();
}
};
// 定义插件的入口函数,注册 Pass 到 Pass 管理器
llvm::PassPluginLibraryInfo getPassPluginInfo() {
// 返回插件的元信息
return {
LLVM_PLUGIN_API_VERSION, // LLVM 插件的 API 版本
"MyPass", // 插件名称
LLVM_VERSION_STRING, // 当前 LLVM 的版本号
[](PassBuilder &PB) { // 一个回调,用于将 Pass 注册到 PassBuilder 中
// 注册管道解析回调函数,用于支持命令行参数调用
PB.registerPipelineParsingCallback(
[](StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
// 检查命令行中的 Pass 名称是否匹配 "my-function-pass"
if (Name == "my-function-pass") {
// 如果匹配,将自定义 Pass 添加到函数 Pass 管理器中
FPM.addPass(MyFunctionPass());
return true; // 表明注册成功
}
return false; // 未匹配,跳过此 Pass
});
}};
}
// 必须导出符号 `llvmGetPassPluginInfo`,这是 LLVM 插件的入口点
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
return getPassPluginInfo(); // 调用自定义的插件入口函数
}
4.3.3 编译 Pass
创建 CMake 项目配置文件 CMakeLists.txt,内容如下
cmake_minimum_required(VERSION 4.20.0)
# 设置 LLVM 安装目录的路径,LLVM_DIR 指向 LLVM 的 cmake 配置目录。
#set(LLVM_DIR "D:/Projects/llvm-project/build/lib/cmake/llvm")
# 项目名称
project(pass)
# 查找已安装的 LLVM,要求是通过 CONFIG 模式查找
find_package(LLVM REQUIRED CONFIG)
# 将 LLVM 的 CMake 模块路径添加到 CMake 模块路径中
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
# 包含 LLVM 的 CMake 配置文件,用于导入其编译选项和头文件路径
include(LLVMConfig)
# 设置 C++ 标准为 C++17,并强制启用标准特性
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)
# /MD 使用多线程的动态运行时库(Release 模式常用)。
# /MDd 使用多线程的动态运行时库(带调试符号,Debug 模式)。
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MD")
# 设置 LLVM 编译标志
separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS})
add_definitions(${LLVM_DEFINITIONS_LIST})
# 包含 LLVM 头文件路径
include_directories(${LLVM_INCLUDE_DIRS})
# 链接 LLVM 的库目录,确保链接阶段可以找到需要的库
link_directories(${LLVM_LIBRARY_DIRS})
# 添加自定义 Pass
add_library(MyPass SHARED
MyFunctionPass.cpp
# MyModulePass.cpp
)
# 自动映射并链接需要的 LLVM 组件库(如 core、support 等)
llvm_map_components_to_libnames(LLVM_LIBS core support)
# 将生成的共享库与必要的 LLVM 库链接起来
target_link_libraries(MyPass ${LLVM_LIBS})
# 在 Windows 平台上,显式指定要导出的符号(如 llvmGetPassPluginInfo)
if (WIN32)
# 使用 export.def 文件来控制导出符号
set_target_properties(MyPass PROPERTIES LINK_FLAGS "/DEF:${CMAKE_CURRENT_SOURCE_DIR}/export.def")
endif ()
在 Windows 上,动态链接库(DLL)的符号导出通常需要显式指定。否则,运行时的链接器(linker)无法正确找到并加载这些符号。
创建一个 export.def 文件,用于显式导出 llvmGetPassPluginInfo
LIBRARY MyPass
EXPORTS
llvmGetPassPluginInfo
参数说明:
LIBRARY: DLL 的名称(不需要扩展名),这里是 MyPass。
EXPORTS: 列出需要导出的函数名。
执行下面命令编译 pass
# 创建 build 目录
mkdir build && cd build
# 运行 CMake 命令生成构建文件
cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Debug ..
# 编译项目
cmake --build .
编译完成后,可以看到 MyPass.dll 以及生成在 build 目录
4.3.4 测试
编译插件后,可以通过 opt 命令运行 Pass
其中:
-load-pass-plugin 加载插件。
-passes 指定运行的 Pass 名称。
创建 test.ll LLVM IR 文件,内容如下
define i32 @main() {
ret i32 0
}
使用 opt 工具加载 MyPass.dll 插件,运行自定义的 my-function-pass ,对 test.ll 进行优化处理,并将结果输出到 test_opt.ll。
opt --load-pass-plugin=./MyPass.dll --passes=my-function-pass -S ../test.ll -o ../test_opt.ll
Processing Function: main
Basic Block:
ret i32 0
使用 opt 工具加载 MyPass.dll 插件,运行自定义的 my-module-pass ,对 test.ll 进行优化处理,并将结果输出到 test.bc。
opt --load-pass-plugin=./MyPass.dll --passes=my-module-pass ../test.ll -o ../test.bc
Processing Module: ../test.ll
Function: main
参考:
5 使用 Clion 调试自定义 Pass
编辑 MyPass 运行/调试配置
可执行文件选择 opt 程序
- 添加程序实参,比如
--load-pass-plugin="D:\Projects\MyPass\build\MyPass.dll" --passes=my-function-pass -S "D:\Projects\MyPass\test.ll" -o "D:\Projects\MyPass\test_opt.ll"
- 在 MyFunctionPass 的 run 函数中下断点,运行调试配置
6. 实现函数名加密 Pass
6.1 编写 Pass 源码
创建 MD5FunctionNamePass.cpp,源码如下
#include "llvm/IR/PassManager.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/MD5.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
struct MD5FunctionNamePass : public PassInfoMixin<MD5FunctionNamePass> {
PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
// 获取函数名
std::string originalName = F.getName().str();
// 跳过外部声明(非定义的函数)
if (F.isDeclaration()) {
errs() << "Skipping external function: " << originalName << "\n";
return PreservedAnalyses::all(); // IR 未被修改
}
// 排除标准库函数名
std::set<std::string> standardFunctions = {"printf", "sprintf", "vsprintf"};
if (standardFunctions.count(F.getName().str())) {
errs() << "Skipping standard library function: " << F.getName() << "\n";
return PreservedAnalyses::all();
}
// 排除 comdat 函数
if (F.getComdat()) {
errs() << "Skipping comdat function: " << F.getName() << "\n";
return PreservedAnalyses::all();
}
// 排除 "main" 函数
if (originalName == "main") {
errs() << "Skipping encryption for function: " << originalName << "\n";
return PreservedAnalyses::all();
}
// 打印原始函数名
errs() << "Original Function Name: " << originalName << "\n";
// 计算 MD5 哈希值
MD5 hash;
MD5::MD5Result result;
hash.update(originalName);
hash.final(result);
// 转换 MD5 结果为字符串
SmallString<32> hashString;
MD5::stringifyResult(result, hashString);
errs() << "MD5 Hash: " << hashString << "\n";
// 修改函数名为哈希值
F.setName(hashString);
return PreservedAnalyses::none(); // 函数 IR 已被修改
}
// 当函数带有 optnone 属性时,Pass 默认会被跳过。通过重写 isRequired,可以强制框架认为当前 Pass 对所有函数都是必要的。
static bool isRequired() {
return true;
}
};
// 插件入口
llvm::PassPluginLibraryInfo getPassPluginInfo() {
errs() << "MD5FunctionNamePass Plugin Loaded Successfully.\n";
return {LLVM_PLUGIN_API_VERSION, "MD5FunctionNamePass", LLVM_VERSION_STRING,
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
if (Name == "md5-function-name-pass") {
FPM.addPass(MD5FunctionNamePass());
return true;
}
return false;
});
}};
}
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
return getPassPluginInfo();
}
6.2 CMakeLists.txt
创建 CMakeLists.txt,内容如下
cmake_minimum_required(VERSION 3.20)
project(MD5FunctionNamePass)
# 设置 LLVM 安装目录的路径,LLVM_DIR 指向 LLVM 的 cmake 配置目录。
set(LLVM_DIR "D:/Projects/llvm-project/build/lib/cmake/llvm")
find_package(LLVM REQUIRED CONFIG)
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
include(LLVMConfig)
set(CMAKE_CXX_STANDARD 17)
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})
# 添加自定义 Pass
add_library(MD5FunctionNamePass SHARED
MD5FunctionNamePass.cpp
)
# 链接 LLVM 支持库
llvm_map_components_to_libnames(LLVM_LIBS core support)
target_link_libraries(MD5FunctionNamePass ${LLVM_LIBS})
if (WIN32)
set_target_properties(MD5FunctionNamePass PROPERTIES LINK_FLAGS "/DEF:${CMAKE_CURRENT_SOURCE_DIR}/export.def")
endif ()
6.3 导入项目到CLion
导入项目到 CLion,打开 CMakeLists.txt,作为项目打开
工具链选择 Visual Studio
关于工具链的配置可以参考这篇文章【编译 LLVM 源码,使用 Clion 调试 clang】
6.4 编译
# 进入 build 目录
cd cmake-build-debug-visual-studio
# 编译项目
ninja MD5FunctionNamePass
编译完成,MD5FunctionNamePass.dll 就在在 build 目录下
6.5 测试
新建 hello.c,内容如下
#include <stdio.h>
const char *getHello() {
return "Hello,";
}
const char *getWorld() {
return "World!";
}
// 主函数
int main() {
// 调用两个函数并打印结果
const char *hello = getHello();
const char *world = getWorld();
printf("%s %s\n", hello, world);
return 0;
}
把 hello.c 转换为 hello.ll
clang -S -emit-llvm ../hello.c -o ../hello.ll
使用 md5-function-name-pass 优化 test.ll,导出优化后的 IR 文件 test_opt.ll
opt --load-pass-plugin=./MD5FunctionNamePass.dll --passes=md5-function-name-pass -S ../hello.ll -o ../hello_opt.ll
MD5FunctionNamePass Plugin Loaded Successfully.
Skipping standard library function: sprintf
Skipping standard library function: vsprintf
Skipping comdat function: _snprintf
Skipping comdat function: _vsnprintf
Original Function Name: getHello
MD5 Hash: 9d55bba9469f8ffcfe1202d85490c913
Original Function Name: getWorld
MD5 Hash: f612c236a8d854b3f6fa57efab4376d2
Skipping encryption for function: main
Skipping standard library function: printf
Skipping comdat function: _vsprintf_l
Skipping comdat function: _vsnprintf_l
Skipping comdat function: __local_stdio_printf_options
Skipping comdat function: _vfprintf_l
打开 test_opt.ll 可以看到 getHello 和 getWorld 函数名字都已经替换成 MD5 加密串了
使用 clang 把 test_opt.ll 编译成可执行程序
clang ../hello_opt.ll -o hello_opt.exe
运行 hello_opt.exe,正常输出 Hello, World!
7. clang 使用自定义 Pass
运行以下命令直接加载插件并应用于编译流程
clang -Xclang -load -Xclang ./MD5FunctionNamePass.dll ../hello.c -o hello.exe
或者
clang -Xclang -fpass-plugin=./MD5FunctionNamePass.dll ../hello.c -o hello.exe
8. 定制 clang 自动加载 Pass 插件
定制化 clang,让其在运行时自动加载自定义的 Pass 插件。
8.1. 编写导出接口
编写导出函数,创建 pass 并添加到 FunctionPassManager
extern "C" __declspec(dllexport) void __stdcall clangAddPass(
FunctionPassManager &FPM) {
errs() << "call clangAddPass.\n";
// 将Pass添加到PassManager
FPM.addPass(MD5FunctionNamePass());
}
执行 ninja 命令重新编译 dll ,并把 dll 文件复制到 clang 可执行文件同级目录下。
8.2. 修改 BackendUtil.cpp
BackendUtil.cpp 是代码生成阶段调用 Pass 的地方。
代码路径:llvm-project/clang/lib/CodeGen/BackendUtil.cpp
编写一个函数,用于加载 dll,并调用 dll 中的导出函数 clangAddPass,代码如下
#if defined(_WIN32) || defined(_WIN64) // Windows 平台
#include <windows.h> // Windows 的动态库加载 API
static void registerMD5FunctionNamePassPlugin(FunctionPassManager &FPM) {
errs() << "call registerMD5FunctionNamePassPlugin.\n";
// 动态加载 MD5FunctionNamePass.dll
HMODULE PluginHandle = LoadLibrary(TEXT("MD5FunctionNamePass.dll"));
if (!PluginHandle) {
errs() << "Failed to load plugin: MD5FunctionNamePass.dll\n";
return;
}
// 查找 DLL 导出的函数 clangAddPass
using AddPassFuncType = void(__stdcall *)(FunctionPassManager &);
auto AddPass = (AddPassFuncType)GetProcAddress(PluginHandle, "clangAddPass");
if (!AddPass) {
errs() << "Failed to find exported function: clangAddPass\n";
FreeLibrary(PluginHandle);
return;
}
AddPass(FPM); // 调用动态库中的注册逻辑
llvm::errs() << "Successfully added md5-function-pass.\n";
}
#endif
把上面代码添加到 BackendUtil.cpp 顶部区域
搜索 if (!CodeGenOpts.DisableLLVMPasses) 在该判断代码块后面加上下面代码,动态加载插件并调用注册函数
#if defined(_WIN32) || defined(_WIN64) // 判断是否为 Windows 平台
// 创建一个 FunctionPassManager 对象,用于管理和调度函数级的 Pass
FunctionPassManager FPM;
// 调用动态加载的插件注册函数,将 MD5FunctionNamePass 添加到 FunctionPassManager 中
registerMD5FunctionNamePassPlugin(FPM);
// 将 FunctionPassManager 适配为 ModulePassManager 所需要的格式,并添加到 ModulePassManager 中
// 这样就能将 FunctionPass 管道添加到模块级别的优化管道中
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
#endif
最后,执行 ninja clang 重新编译 clang。
8.3 测试
调用 clang 把 hello.c 编译成 IR 文件
D:\Projects\llvm-project\build>clang -S -emit-llvm hello.c -o hello.ll
call registerMD5FunctionNamePassPlugin.
call clangAddPass.
Successfully added md5-function-pass.
Skipping standard library function: sprintf
Skipping standard library function: vsprintf
Skipping comdat function: _snprintf
Skipping comdat function: _vsnprintf
Original Function Name: getHello
MD5 Hash: 9d55bba9469f8ffcfe1202d85490c913
Original Function Name: getWorld
MD5 Hash: f612c236a8d854b3f6fa57efab4376d2
Skipping encryption for function: main
Skipping standard library function: printf
Skipping comdat function: _vsprintf_l
Skipping comdat function: _vsnprintf_l
Skipping comdat function: __local_stdio_printf_options
Skipping comdat function: _vfprintf_l
可以看到 getHello 和 getWorld 函数名字都已经替换成 MD5 加密串了
源码
完整源码地址:https://github.com/CYRUS-STUDIO/MD5FunctionNamePass
相关文章: