内容简介:0x01 Xcode调试一个LLVM Pass
之前写过一篇《关于LLVM,这些东西你必须知道!》,先已收录在《iOS成长之路》。
其中讲解了编译器的编译过程,以及通过LLVM可以做哪些有意思的事情,今天继续来跟着官方的文档继续了解Pass。
获取代码:
git clone http://llvm.org/git/llvm.git git clone http://llvm.org/git/clang.git llvm/tools/clang git clone http://llvm.org/git/clang-tools-extra.git llvm/tools/clang/tools/extra git clone http://llvm.org/git/compiler-rt.git llvm/projects/compiler-rt
编译成 Xcode项目
:
mkdir build cd build cmake -G Xcode CMAKE_BUILD_TYPE="Debug" ../llvm open LLVM.xcodeproj
然后运行 clang
,准备工作做好后,继续Pass的学习。
什么是Pass?
LLVM Pass是LLVM系统中非常重要和有趣的一个模块,Pass处理编译过程中代码的转换以及优化工作。
所有的Pass都是 Pass 的子类,不同的Pass实现不同的作用可以继承 ModulePass , CallGraphSCCPass , FunctionPass , LoopPass , RegionPass , BasicBlockPass 等类。
接下来开始介绍如何编写一个Pass,编译,加载以及运行的过程。
编写hello world
先编写一个“Hello” Pass,只是简单的输出程序中每个非外部方法的方法名,不会修改程序本身的功能。官方Demo的源代码存在 lib/Transforms/Hello
文件夹。
配置编译环境
首先来看看环境的配置,如果你想编译一个新的Pass,你可以在 lib/Transforms/
下新建一个文件夹,这里我自己创建的是 PassDemo
,然后配置编译脚本去编译你的源代码。 在新建的目录下创建文件 CMakeLists.txt
:
add_llvm_loadable_module(LLVMPassDemo Hello.cpp PLUGIN_TOOL opt )
然后增加下面一行代码到 lib/Transforms/CMakeLists.txt
:
add_subdirectory(PassDemo)
编译脚本指定了编译源文件 Hello.cpp
去编译生成动态库 $(LEVEL)/lib/LLVMPassDemo.dylib
。该文件可以被 opt
通过 -load
参数动态加载。如果不是Mac平台生成的后缀会不一样,比如 Linux
下是 so
。
配置好编译脚本后,打开 Xcode
项目,按 command + B
编译,然后在项目里面就可以看到刚刚增加的 Target
,打开 Hello.cpp
文件就可以编写代码了。
基础代码
首先编辑 Hello.cpp
文件,导入头文件:
#include "llvm/Pass.h" #include "llvm/IR/Function.h" #include "llvm/Support/raw_ostream.h"
因为需要编一个 Pass
然后操作 Function
, 还可能输出一些东西。
using namespace llvm;
由于导入文件里面的方法是属于 llvm
命名空间的,所以先指定使用的命名空间。
namespace {
编写一个匿名的命名空间, C++
中的匿名命名空间和 C
语言里面的 static
关键词一样,定义在匿名空间中的变量仅仅对当前文件可见。
struct Hello : public FunctionPass{
定义 Hello
类继承 FunctionPass ,后面会讲到不同用处的Pass会继承不同的父类,现在只需要知道,继承自 FunctionPass
可以操作程序中的方法。
static char ID; Hello() : FunctionPass(ID){}
定义可供LLVM标示的Pass ID。
bool runOnFunction(Function &F) override{ errs() << "Hello: "; errs().write_escaped(F.getName()) << "\n"; return false; } }; }
这里定义了一个 runOnFunction ,重载继承自父类的抽象虚函数。在这个函数里面可以做针对于函数的特殊处理,这里只是打印出每个方法的名字。
char Hello::ID = 0;
初始化Pass ID,LLVM使用ID的地址去识别一个Pass,所以初始化的值不重要。
static RegisterPass<Hello> X("hello", "Hello World Pass", false/* Only looks at CFG */, false/* Analysis Pass */);
最后, 注册我们的class Hello, 指定命令行参数为 hello
,名字说明 Hello World Pass
。
整个 Hello.cpp
文件如下:
#include "llvm/Pass.h" #include "llvm/IR/Function.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; namespace { struct Hello : public FunctionPass{ static char ID; Hello() : FunctionPass(ID){} bool runOnFunction(Function &F) override{ errs() << "Hello: "; errs().write_escaped(F.getName()) << "\n"; return false; } }; } char Hello::ID = 0; static RegisterPass<Hello> X("hello", "Hello World Pass", false/* Only looks at CFG */, false/* Analysis Pass */);
选中 Target
LLVMPassDemo
按 command + B
编译,编译完后会生成文件 build/Debug/lib/LLVMPassDemo.dylib
。
使用 opt
加载
上面在代码中通过 RegisterPass
注册了Pass,所以可以通过 opt -load
去加载动态库并指定参数 hello
。
准备源文件:
#include <stdio.h> int add(int x, int y) { return x + y; } int main(){ printf("%d",add(3,4)); return 0; }
编译源文件生成 bitcode
:
path/to/build/Debug/bin/clang -emit-llvm -c test.mm -o test.bc
然后编译启动参数, 选择 opt
target, 点击 Edit Scheme
。
填入启动参数:
command + R
运行。
输出如下:
Hello: _Z3addii Hello: main
当然还可以断点调试啦~
通过 -help
可以看到注册的pass参数:
opt -load lib/LLVMPassDemo.dylib -help
...... Optimizations available: -hello - Hello World Pass ......
PassManager 还提供 --time-passes
参数输出你的Pass和其它Pass执行时间的对比。
opt -load lib/LLVMPassDemo.dylib -hello -time-passes -disable-output test.bc
Hello: _Z3addii Hello: main ===-------------------------------------------------------------------------=== ... Pass execution timing report ... ===-------------------------------------------------------------------------=== Total Execution Time: 0.0003 seconds (0.0003 wall clock) ---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name --- 0.0002 ( 75.3%) 0.0000 ( 27.9%) 0.0002 ( 62.1%) 0.0002 ( 62.7%) Module Verifier 0.0001 ( 24.7%) 0.0001 ( 72.1%) 0.0001 ( 37.9%) 0.0001 ( 37.3%) Hello World Pass 0.0002 (100.0%) 0.0001 (100.0%) 0.0003 (100.0%) 0.0003 (100.0%) Total ===-------------------------------------------------------------------------===
tips:
为了在执行 opt
时能自动检测 LLVMPassDemo
模块有没有被修改,如果修改了,重新编译 LLVMPassDemo
模块,我们把 LLVMPassDemo
模块加到 opt
的依赖里面。
编辑 tools/opt/CMakeLists.txt
:
add_llvm_tool(opt AnalysisWrappers.cpp BreakpointPrinter.cpp GraphPrinters.cpp NewPMDriver.cpp PassPrinters.cpp PrintSCC.cpp opt.cpp DEPENDS intrinsics_gen LLVMPassDemo )
更多Pass类
当你写一个新的Pass的时候,你需要根据需要去选择需要继承的父类,根据不同的作用有 ImmutablePass
、 ModulePass
、 CallGraphSCCPass
、 FunctionPass
、 LoopPass
、 RegionPass
、 BasicBlockPass
、 MachineFunctionPass
。具体可以参考 官方文档 。
调用其它Pass
如果在编写的Pass中需要使用到其它Pass提供的函数功能,需要在:
virtual void getAnalysisUsage(AnalysisUsage &AU) const;
说明,比如像要获取程序中存在循环的信息,可以在该函数里面申请需要依赖的Pass。
以上面的Pass为例,导入头文件 #include "llvm/Analysis/LoopInfo.h"
,在 getAnalysisUsage
中调用:
AU.addRequired<LoopInfoWrapperPass>(); AU.setPreservesAll();
然后就可以通过它提供接口获取存在循环的个数。
LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo(); int loopCounter = 0; for (LoopInfo::iterator i = LI.begin(), e = LI.end(); i != e; ++i) { loopCounter++; } errs() << "loop num:" << loopCounter << "\n";
测试程序:
#include <stdio.h> int add(int x, int y) { for (int i = 0; i < 10; i++) { printf("%d\n",i); } return x + y; } int main(){ printf("%d",add(3,4)); return 0; }
输出如下:
Hello: _Z3addii loop num:1 Hello: main loop num:0 Program ended with exit code: 0
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- iOS常用调试方法:断点调试
- 断点调试和日志调试之间的平衡点:函数计算调试之 Python 篇
- .NET高级调试系列-Windbg调试入门篇
- VisualStudio 通过外部调试方法快速调试库代码
- GDB 调试 Mysql 实战(二)GDB 调试打印
- 使用gdb调试工具上手调试php和swoole源码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Google's PageRank and Beyond
Amy N. Langville、Carl D. Meyer / Princeton University Press / 2006-7-23 / USD 57.50
Why doesn't your home page appear on the first page of search results, even when you query your own name? How do other web pages always appear at the top? What creates these powerful rankings? And how......一起来看看 《Google's PageRank and Beyond》 这本书的介绍吧!