llvm系列文章,本文讲一下Pass的编译和加载。网上ollvm等作品都是整个llvm都丢到 git 里了,非常不优雅,所以本文讲一下如何编译、加载单独的 Pass。
一、准备环境 本文环境为:
MacOS
已按照第一篇安装好clang+llvm,使用7.0
已安装CMake
设置了一个环境变量,叫$LLVM_HOME,指向安装好的目录,文件结构如下:
1 2 3 4 5 6 7 8 9 ➜ /tmp lsa $LLVM_HOME total 0 drwxr-xr-x 7 leadroyal staff 224B 10 9 16:32 . drwxr-xr-x 6 leadroyal staff 192B 10 10 13:28 .. drwxr-xr-x 77 leadroyal staff 2.4K 10 9 16:33 bin drwxr-xr-x 6 leadroyal staff 192B 10 9 16:32 include drwxr-xr-x 173 leadroyal staff 5.4K 10 9 16:33 lib drwxr-xr-x 4 leadroyal staff 128B 10 9 16:32 libexec drwxr-xr-x 7 leadroyal staff 224B 10 9 16:33 share
将来编写代码时,要用到里面的一些头文件、库文件、可执行文件等。
本文的代码在 https://github.com/LeadroyaL/llvm-pass-tutorial 的 master 分支里。
二、编写CMakeLists.txt 1 2 3 4 5 6 7 8 9 10 11 12 13 cmake_minimum_required(VERSION 3.4) if(NOT DEFINED ENV{LLVM_HOME}) message(FATAL_ERROR "$LLVM_HOME is not defined") endif() if(NOT DEFINED ENV{LLVM_DIR}) set(ENV{LLVM_DIR} $ENV{LLVM_HOME}/lib/cmake/llvm) endif() find_package(LLVM REQUIRED CONFIG) add_definitions(${LLVM_DEFINITIONS}) include_directories(${LLVM_INCLUDE_DIRS}) link_directories(${LLVM_LIBRARY_DIRS}) add_subdirectory(skeleton) # Use your pass name here.
前面几句的逻辑就是,首先得设置LLVM_HOME这个环境变量,当然你用别的也行,这个我用起来舒服。
注意set(ENV{LLVM_DIR} $ENV{LLVM_HOME}/lib/cmake/llvm) ,在低版本可能不是这个目录,指向的那个目录里有LLVM-Config.cmake 还是LLVMConfig.cmake 来着,反正找不到的话cmake会报错的,可以搜一下这两个文件在哪。
我对CMake也不熟,前几句就是使用llvm提供的一些环境,最后那句add_subdirectory 是使用子目录的CMake 工程。接下来贴子目录的 CMake 内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 add_library(SkeletonPass MODULE # List your source files here. Skeleton.cpp ) # Use C++11 to compile your pass (i.e., supply -std=c++11). target_compile_features(SkeletonPass PRIVATE cxx_range_for cxx_auto_type) # LLVM is (typically) built with no C++ RTTI. We need to match that; # otherwise, we'll get linker errors about missing RTTI data. set_target_properties(SkeletonPass PROPERTIES COMPILE_FLAGS "-fno-rtti" ) # Get proper shared-library behavior (where symbols are not necessarily # resolved when the shared library is linked) on OS X. if(APPLE) set_target_properties(SkeletonPass PROPERTIES LINK_FLAGS "-undefined dynamic_lookup" ) endif(APPLE)
这个非常简单,就很简单的编译一个叫 libSkeletonPass.so 的共享库出来,源文件是 Skeleton.cpp 。
三、编写代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include "llvm/Pass.h" #include "llvm/IR/Function.h" #include "llvm/Support/raw_ostream.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/Transforms/IPO/PassManagerBuilder.h" using namespace llvm;namespace { struct SkeletonPass : public FunctionPass { static char ID; SkeletonPass () : FunctionPass (ID) {} virtual bool runOnFunction (Function &F) { errs () << "I saw a function called " << F.getName () << "!n" ; return false ; } }; } char SkeletonPass::ID = 0 ;static void registerSkeletonPass (const PassManagerBuilder &, legacy::PassManagerBase &PM) { PM.add (new SkeletonPass ()); } static RegisterStandardPasses RegisterMyPass (PassManagerBuilder::EP_EarlyAsPossible, registerSkeletonPass) ;
稍微解释一下,定义了一个叫 SkeletonPass
,继承了 FunctionPass
。里面有个被重写的方法,runOnFunction
,里面就是真正的代码了,打印一下函数名就 return 掉。
后面有个标准的入口,本文暂时不讲,就当时一个提供给 llvm
的入口标记即可。
四、加载 so 文件 2022年05月07日更新:在 llvm13、llvm14 上,该方案已是即将被官方废弃的方案,临时解决方案是加参数-flegacy-pass-manager
。 编译的方法有很多,我一般用 CLion 上面那个编译按钮,(不是运行按钮),之后在附近的目录里翻一下,就可以翻出来。 在 clang 时候添加一些额外的参数,大概长下面这个样子
1 $LLVM_HOME/bin/clang -Xclang -load -XClang build/skeleton/libSkeletonPass.so /tmp/test.c
核心的是
1 -Xclang -load -XClang build/skeleton/libSkeletonPass.so
执行一下,有下面的结果
1 2 3 4 ➜ /tmp $LLVM_HOME/bin/clang -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include -Xclang -load -Xclang /Users/leadroyal/CLion_code/llvm-pass-tutorial/cmake-build-debug/skeleton/libSkeletonPass.so -w test.c -o test.bin I saw a function called func1! I saw a function called func2! I saw a function called main!
嗯,说明这个Pass被正确加载了。 这里千万不能使用 /usr/bin/clang
,因为 apple-clang 和 开源clang 完全是两个东西,无法加载我们编译出来的 Pass,会出现符号找不到。
1 2 3 4 5 6 ➜ /tmp /usr/bin/clang -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include -Xclang -load -Xclang /Users/leadroyal/CLion_code/llvm-pass-tutorial/cmake-build-debug/skeleton/libSkeletonPass.so -w test.c -o test.bin error: unable to load plugin '/Users/leadroyal/CLion_code/llvm-pass-tutorial/cmake-build-debug/skeleton/libSkeletonPass.so': 'dlopen(/Users/leadroyal/CLion_code/llvm-pass-tutorial/cmake-build-debug/skeleton/libSkeletonPass.so, 9): Symbol not found: __ZN4llvm23EnableABIBreakingChecksE Referenced from: /Users/leadroyal/CLion_code/llvm-pass-tutorial/cmake-build-debug/skeleton/libSkeletonPass.so Expected in: flat namespace in /Users/leadroyal/CLion_code/llvm-pass-tutorial/cmake-build-debug/skeleton/libSkeletonPass.so'
2019年09月08日更新,最近有读者问到这个符号报错如何解决: 一定要用对应版本的Clang去加载对应版本的 Pass,否则可能符号出错,比如下面的。
关于 __ZN4llvm23EnableABIBreakingChecksE
和 __ZN4llvm24DisableABIBreakingChecksE
符号,原因是 Debug 版本和 Release 版本编译出来的符号不一致,与这几个llvm的编译配置有关:LLVM_ENABLE_ABI_BREAKING_CHECKS
LLVM_ENABLE_ASSERTIONS
,因此在使用不配套的 llvm 套件时,会出现该符号找不到的情况 。
对此的解决方案是:一定要使用成套的 llvm组件,不要混用自己编译出来的组件和 Mac 自带的组件,很容易出问题,在环境变量 PATH
上把编译出来的 bin
目录提到前面,就可以解决这个问题。
2019年11月15日更新,符号报错原因和解决方案均可在本系列第十二篇找到,如果是新的 case 请在第十二篇留言或者直接邮箱交流。 五、结论,参考链接 嗯,这样基本的Pass 开发环境就做好了,编译、加载一条龙,非常方便。
主要参考链接是
https://github.com/abenkhadra/llvm-pass-tutorial
http://llvm.org/docs/CMake.html#cmake-out-of-source-pass
本文的源码在
https://github.com/LeadroyaL/llvm-pass-tutorial