内容简介: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源码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
ASP.NET 2.0入门经典
哈特 / 清华大学出版社 / 2006-8 / 78.00元
该书是Wrox红皮书中的畅销品种, 从初版、1.0版、1.1版到目前的2.0版,已经3次升级,不仅内容更加完善、实用,还展现了.NET 2.0的最新技术和ASP.NET 2.0最新编程知识,是各种初学者学习ASP.NET的优秀教程,也是Web开发人员了解ASP.NET 2.0新技术的优秀参考书。该书与《ASP.NET 2.0高级编程(第4版)》及其早期版本,曾影响到无数中国Web程序员。一起来看看 《ASP.NET 2.0入门经典》 这本书的介绍吧!