-
-
[原创]从Clang到Pass加载与执行的流程
-
发表于: 2024-6-16 17:09 24016
-
文章开始之前,先通过gd动调clang来看一看整个过程是什么样的。
开始调试
我这里调试的是release版本的clang(不想再编译了),显然提示了找不到clang的调试符号。我建议使用debug版的clang来调试,不然就看不到断点处的源码了,这对调试还是有影响的(有些函数下不了断点,不知道是不是这个原因)。
直接把断点下在main函数处
重要的来了!clang在执行过程中,会fork子进程,如果直接进行跟踪会出现如下结果:
因此我们需要通过如下指令在gdb中设置跟踪子进程:
然后启动clang并运行我们的Pass,指令如下:
此时我们还需要对自己的Pass下断点:
继续运行直到命中我们下的Pass断点。
成功断在了预期位置处,此时我们查看函数调用堆栈,指令为bt
。
从上面的函数调用堆栈可以明了的看出Pass从被加载到被执行的整个过程(实际上,它还是缺少了一些函数)。
接下来我们结合源码来仔细分析一下这个流程。
clang
的入口位于clang/tools/driver/driver.cpp
中的main
函数。
其中第25行的BuildCompilation
函数以及第31行的ExecuteCompilation
函数,它们的进一步跟进请参考谁说不能与龙一起跳舞:Clang / LLVM (3) - 知乎 (zhihu.com)。简单来说,一开始的命令行参数并不会满足代码中第6行的要求(即没有-cc1
),从而一开始不会执行ExecuteCC1Tool
函数,但通过一系列操作,最终还是执行了ExecuteCC1Tool
函数。
ExecuteCC1Tool
的具体实现在clang/tools/driver/driver.cpp
中。
在上一小节中,我们知道了第一个参数是-cc1
,因此这里会调用cc1_main
函数。
cc1_main
的具体实现在clang/tools/driver/cc1_main.cpp
中。
这里主要是创建clang
实例,调用ExecuteCompilerInvocation
函数开始编译目标源代码。
ExecuteCompilerInvocation
函数的声明在clang/include/clang/FrontendTool/ExecuteCompilerInvocation.h
中,具体实现在clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
中。
这里主要是创建FrontendAction
对象并执行ExecuteAction
函数。
CompilerInstance
类的声明在clang/include/clang/Frontend/CompilerInstance.h
中,具体实现在clang/lib/Frontend/CompilerInstance.cpp
中。
这里通过BeginSourceFile
函数加载源文件到内存中了,然后调用了FrontendAction
类的Execute
函数进行编译。
FrontendAction
类的声明在clang/include/clang/Frontend/FrontendAction.h
中,具体实现在clang/lib/Frontend/FrontendAction.cpp
中。
该函数进一步调用ASTFrontendAction
类的ExecuteAction
函数。
这个函数在开头的函数调用堆栈图中并没有出现,然而实际上确实调用了(真不明白这个是什么原因),如下图所示:
那么就来看一下这个函数的源码。ASTFrontendAction
类的声明在clang/include/clang/Frontend/FrontendAction.h
中,具体实现在clang/lib/Frontend/FrontendAction.cpp
中。
主要是创建语义分析器,调用 ParseAST
方法,开始解析抽象语法树(AST)。
ParseAST
函数的声明在clang/include/clang/Parse/ParseAST.h
中,具体实现在clang/lib/Parse/ParseAST.cpp
中。
这部分真正是对源码进行语法树构建,并通过HandleTranslationUnit
函数交给AST Consumer
处理。
BackendConsumer
类的声明在clang/include/clang/CodeGen/CodeGenAction.h
中,具体实现在clang/lib/CodeGen/CodeGenAction.cpp
中。
该函数主要是记录了模块中函数的函数名哈希值和声明位置信息,最后调用EmitBackendOutput
函数生成中间代码。
什么是模块?
在LLVM中,"模块(Module)"通常是指一个编译单元或一个源代码文件被编译后生成的中间表示(IR,Intermediate Representation)的集合。在 LLVM 中,每个模块都是一个独立的单元,包含了函数、全局变量、类型定义等信息,可以被独立地优化和编译。
EmitBackendOutput
函数声明在clang/include/clang/CodeGen/BackendUtil.h
,具体实现在clang/lib/CodeGen/BackendUtil.cpp
中。
最终通过BackendConsumer
将AST转换成了IR代码,之后CGOpts.LegacyPassManager
标志选择执行新版本的EmitAssemblyWithLegacyPassManager
或是旧版本的EmitAssembly
。
Clang 的后端消费者(BackendConsumer)是 Clang 的一部分,它负责将 Clang 前端产生的抽象语法树(AST)转换为 LLVM 的中间表示(IR)
很奇怪,这一部分的EmitAssemblyHelper
类的函数无法触发断点,且提示没有加载进来该函数。
该函数同样也在clang/lib/CodeGen/BackendUtil.cpp
中。
这部分代码主要是创建出两个重要的Pass
管理器:PerModulePasses
、PerFunctionPasses
。然后调用CreatePasses
函数创建Pass
并添加到对应的Pass
管理器的执行队列中(详见2.2.2小节)。之后就是调用PerFunctionPasses.run(F)
、PerModulePasses.run(*TheModule)
、CodeGenPasses.run(*TheModule)
来执行Pass
(详见2.3小节,以PerModulePasses.run
函数为例进行讲解)。
CreatePasses
函数同样也在clang/lib/CodeGen/BackendUtil.cpp
中。
最后调用的populateModulePassManager
、populateFunctionPassManager
函数将Pass
添加到对应管理器的执行队列中(详见2.2.3小节,以populateModulePassManager
函数为例进行讲解)。
populateModulePassManager
函数在llvm/lib/Transforms/IPO/PassManagerBuilder.cpp
中。这个函数想必大家都很熟悉,因为在之前的OLLVM
移植、编写自己的Pass
都需要在这里面进行添加。
到这里,可以认为我们的Pass
已经创建好了,不再进行进一步深究。
PerModulePasses.run
在llvm/lib/IR/LegacyPassManager.cpp
中。
最终调用PassManagerImpl::run
函数
PassManagerImpl
类的定义在llvm/include/llvm/IR/LegacyPassManager.h
中,具体实现在llvm/lib/IR/LegacyPassManager.cpp
中。
该函数主要是对模块执行所有被安排执行的Pass
,对应代码中第15~19行,关键函数为runOnModule
。
什么是不可变Pass?
在 LLVM 中,Pass(通常称为优化 Pass 或者分析 Pass)是指一种对 LLVM IR 进行转换或者分析的模块。Passes 可以用于执行各种任务,例如优化代码、收集统计信息、生成调试信息等。Passes 通常根据其行为被分为两类:可变 Pass 和不可变 Pass。
FPPassManager
类的定义在llvm/include/llvm/IR/LegacyPassManagers.h
中,具体实现在llvm/lib/IR/LegacyPassManager.cpp
中。
调用runOnFunction
函数对模块中的函数进行Pass操作。
runOnFunction
函数具体实现在llvm/lib/IR/LegacyPassManager.cpp
中。
最后通过runOnFunction
执行对应的Pass
(代码中第28行),在当前例子中,也就是执行MyPass
的runOnFunction
函数。
以上就是。简而言之,首先clang
会先将我们的目标源码转成AST语法树,然后再通过ASTConsumer
换成IR
代码。之后加载通过CreatePasses
函数创建Pass并加入到执行队列中,后续会调用我们熟知的populateModulePassManager
,这里注册过我们自定义的Pass
。最后就是Pass
执行,主要还是通过对应的Pass
管理器的runOnModule
函数来进行的,它这里面会直接调用我们自定义Pass
的runOnModule
函数。值得一提的是,Pass
的加载与执行的操作都是在EmitAssemblyWithLegacyPassManager
或EmitAssembly
函数中调用和完成的。
参考:
【Linux】GDB保姆级调试指南(什么是GDB?GDB如何使用?)_linux gdb标准输入-CSDN博客
对LLVM Pass进行Debug_vscode llvm pass开发-CSDN博客
谁说不能与龙一起跳舞:Clang / LLVM (3) - 知乎 (zhihu.com)
llvm学习(二十):动态注册Pass的加载过程(上) | LeadroyaL's website
(3 封私信 / 1 条消息) Clang里面真正的前端是什么? - 知乎 (zhihu.com)
https://juejin.cn/post/6844903591115767821
gdb clang
gdb clang
gdb main
gdb main
set
follow-fork-mode child
set
follow-fork-mode child
run
-
mllvm
-
mypass ~
/
Desktop
/
test.cpp
-
o ~
/
Desktop
/
test_debug_clang.ll
run
-
mllvm
-
mypass ~
/
Desktop
/
test.cpp
-
o ~
/
Desktop
/
test_debug_clang.ll
b MyPass::runOnFunction
b MyPass::runOnFunction
int
main(
int
Argc,
const
char
**Argv) {
...
// 从第二个参数开始搜索第一个非空参数(跳过了参数命令行中的clang)
auto
FirstArg = llvm::find_if(llvm::drop_begin(Args), [](
const
char
*A) {
return
A != nullptr; });
//如果FirstArg以"-cc1"开头
if
(FirstArg != Args.end() && StringRef(*FirstArg).startswith(
"-cc1"
)) {
// If -cc1 came from a response file, remove the EOL sentinels.
if
(MarkEOLs) {
auto
newEnd = std::
remove
(Args.begin(), Args.end(), nullptr);
Args.resize(newEnd - Args.begin());
}
return
ExecuteCC1Tool(Args);
//调用ExecuteCC1Tool函数进一步处理
}
// 在Driver::BuildCompilation()中真正的命令行解析之前,处理需要处理的选项
...
//初始化driver
Driver TheDriver(Path, llvm::sys::getDefaultTargetTriple(), Diags);
...
//如果不需要在新的进程中调用cc1工具
if
(!UseNewCC1Process) {
TheDriver.CC1Main = &ExecuteCC1Tool;
//CC1Main指向ExecuteCC1Tool函数
llvm::CrashRecoveryContext::Enable();
}
//调用BuildCompilation构建编译任务,里面会将-cc1加入到Args中
std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(Args));
int
Res = 1;
bool
IsCrash =
false
;
if
(C && !C->containsError()) {
SmallVector<std::pair<
int
,
const
Command *>, 4> FailingCommands;
//执行编译任务,里面会创建多进程,回到main函数开始的地方,执行ExecuteCC1Tool函数
Res = TheDriver.ExecuteCompilation(*C, FailingCommands);
// Force a crash to test the diagnostics.
...
//处理执行编译命令时的失败情况
...
}
...
}
int
main(
int
Argc,
const
char
**Argv) {
...
// 从第二个参数开始搜索第一个非空参数(跳过了参数命令行中的clang)
auto
FirstArg = llvm::find_if(llvm::drop_begin(Args), [](
const
char
*A) {
return
A != nullptr; });
//如果FirstArg以"-cc1"开头
if
(FirstArg != Args.end() && StringRef(*FirstArg).startswith(
"-cc1"
)) {
// If -cc1 came from a response file, remove the EOL sentinels.
if
(MarkEOLs) {
auto
newEnd = std::
remove
(Args.begin(), Args.end(), nullptr);
Args.resize(newEnd - Args.begin());
}
return
ExecuteCC1Tool(Args);
//调用ExecuteCC1Tool函数进一步处理
}
// 在Driver::BuildCompilation()中真正的命令行解析之前,处理需要处理的选项
...
//初始化driver
Driver TheDriver(Path, llvm::sys::getDefaultTargetTriple(), Diags);
...
//如果不需要在新的进程中调用cc1工具
if
(!UseNewCC1Process) {
TheDriver.CC1Main = &ExecuteCC1Tool;
//CC1Main指向ExecuteCC1Tool函数
llvm::CrashRecoveryContext::Enable();
}
//调用BuildCompilation构建编译任务,里面会将-cc1加入到Args中
std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(Args));
int
Res = 1;
bool
IsCrash =
false
;
if
(C && !C->containsError()) {
SmallVector<std::pair<
int
,
const
Command *>, 4> FailingCommands;
//执行编译任务,里面会创建多进程,回到main函数开始的地方,执行ExecuteCC1Tool函数
Res = TheDriver.ExecuteCompilation(*C, FailingCommands);
// Force a crash to test the diagnostics.
...
//处理执行编译命令时的失败情况
...
}
...
}
static
int
ExecuteCC1Tool(SmallVectorImpl<
const
char
*> &ArgV) {
...
StringRef Tool = ArgV[1];
void
*GetExecutablePathVP = (
void
*)(
intptr_t
)GetExecutablePath;
if
(Tool ==
"-cc1"
)
return
cc1_main(makeArrayRef(ArgV).slice(1), ArgV[0], GetExecutablePathVP);
if
(Tool ==
"-cc1as"
)
return
cc1as_main(makeArrayRef(ArgV).slice(2), ArgV[0],
GetExecutablePathVP);
if
(Tool ==
"-cc1gen-reproducer"
)
return
cc1gen_reproducer_main(makeArrayRef(ArgV).slice(2), ArgV[0],
GetExecutablePathVP);
...
}
static
int
ExecuteCC1Tool(SmallVectorImpl<
const
char
*> &ArgV) {
...
StringRef Tool = ArgV[1];
void
*GetExecutablePathVP = (
void
*)(
intptr_t
)GetExecutablePath;
if
(Tool ==
"-cc1"
)
return
cc1_main(makeArrayRef(ArgV).slice(1), ArgV[0], GetExecutablePathVP);
if
(Tool ==
"-cc1as"
)
return
cc1as_main(makeArrayRef(ArgV).slice(2), ArgV[0],
GetExecutablePathVP);
if
(Tool ==
"-cc1gen-reproducer"
)
return
cc1gen_reproducer_main(makeArrayRef(ArgV).slice(2), ArgV[0],
GetExecutablePathVP);
...
}
int
cc1_main(ArrayRef<
const
char
*> Argv,
const
char
*Argv0,
void
*MainAddr) {
...
//创建Clang编译器实例
std::unique_ptr<CompilerInstance> Clang(
new
CompilerInstance());
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(
new
DiagnosticIDs());
//注册了支持对象文件封装的 Clang 模块
...
// 一系列初始化
...
// 执行clang的前端
{
llvm::TimeTraceScope TimeScope(
"ExecuteCompiler"
);
//调用ExecuteCompilerInvocation函数
Success = ExecuteCompilerInvocation(Clang.get());
}
//后续的清理工作
...
}
int
cc1_main(ArrayRef<
const
char
*> Argv,
const
char
*Argv0,
void
*MainAddr) {
...
//创建Clang编译器实例
std::unique_ptr<CompilerInstance> Clang(
new
CompilerInstance());
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(
new
DiagnosticIDs());
//注册了支持对象文件封装的 Clang 模块
...
// 一系列初始化
...
// 执行clang的前端
{
llvm::TimeTraceScope TimeScope(
"ExecuteCompiler"
);
//调用ExecuteCompilerInvocation函数
Success = ExecuteCompilerInvocation(Clang.get());
}
//后续的清理工作
...
}
bool
ExecuteCompilerInvocation(CompilerInstance *Clang) {
// 命令行参数中是否存在-help、-v的参数,是则返回对应的信息
...
// 加载必要插件
Clang->LoadRequestedPlugins();
// 同样还是检查一些参数,例如-mllvm
...
// 调用CreateFrontendAction函数创建前端操作对象
std::unique_ptr<FrontendAction> Act(CreateFrontendAction(*Clang));
if
(!Act)
return
false
;
// 调用ExecuteAction函数,执行前端操作,也就是编译
bool
Success = Clang->ExecuteAction(*Act);
...
return
Success;
}
bool
ExecuteCompilerInvocation(CompilerInstance *Clang) {
// 命令行参数中是否存在-help、-v的参数,是则返回对应的信息
...
// 加载必要插件
Clang->LoadRequestedPlugins();
// 同样还是检查一些参数,例如-mllvm
...
// 调用CreateFrontendAction函数创建前端操作对象
std::unique_ptr<FrontendAction> Act(CreateFrontendAction(*Clang));
if
(!Act)
return
false
;
// 调用ExecuteAction函数,执行前端操作,也就是编译
bool
Success = Clang->ExecuteAction(*Act);
...
return
Success;
}
bool
CompilerInstance::ExecuteAction(FrontendAction &Act) {
//准备工作和选项处理
...
for
(
const
FrontendInputFile &FIF : getFrontendOpts().Inputs) {
if
(hasSourceManager() && !Act.isModelParsingAction())
getSourceManager().clearIDTables();
//BeginSourceFile开始处理源文件
if
(Act.BeginSourceFile(*
this
, FIF)) {
//调用Execute函数进行处理
if
(llvm::Error Err = Act.Execute()) {
consumeError(std::move(Err));
}
//结束
Act.EndSourceFile();
}
}
//错误处理
...
//生成代码输出
...
return
!getDiagnostics().getClient()->getNumErrors();
}
bool
CompilerInstance::ExecuteAction(FrontendAction &Act) {
//准备工作和选项处理
...
for
(
const
FrontendInputFile &FIF : getFrontendOpts().Inputs) {
if
(hasSourceManager() && !Act.isModelParsingAction())
getSourceManager().clearIDTables();
//BeginSourceFile开始处理源文件
if
(Act.BeginSourceFile(*
this
, FIF)) {
//调用Execute函数进行处理
if
(llvm::Error Err = Act.Execute()) {
consumeError(std::move(Err));
}
//结束
Act.EndSourceFile();
}
}
//错误处理
...
//生成代码输出
...
return
!getDiagnostics().getClient()->getNumErrors();
}
llvm::Error FrontendAction::Execute() {
//获取编译器实例的引用
CompilerInstance &CI = getCompilerInstance();
if
(CI.hasFrontendTimer()) {
...
ExecuteAction();
//
}
else
ExecuteAction();
// If we are supposed to rebuild the global module index, do so now unless
// there were any module-build failures.
...
return
llvm::Error::success();
}
llvm::Error FrontendAction::Execute() {
//获取编译器实例的引用
CompilerInstance &CI = getCompilerInstance();
if
(CI.hasFrontendTimer()) {
...
ExecuteAction();
//
}
else
ExecuteAction();
// If we are supposed to rebuild the global module index, do so now unless
// there were any module-build failures.
...
return
llvm::Error::success();
}
void
ASTFrontendAction::ExecuteAction() {
...
//没有语义分析器则创建
if
(!CI.hasSema())
CI.createSema(getTranslationUnitKind(), CompletionConsumer);
//调用ParseAST分析AST语法树
ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats, CI.getFrontendOpts().SkipFunctionBodies);
}
void
ASTFrontendAction::ExecuteAction() {
...
//没有语义分析器则创建
if
(!CI.hasSema())
CI.createSema(getTranslationUnitKind(), CompletionConsumer);
//调用ParseAST分析AST语法树
ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats, CI.getFrontendOpts().SkipFunctionBodies);
}
void
clang::ParseAST(Sema &S,
bool
PrintStats,
bool
SkipFunctionBodies) {
...
//获取AST消费者
ASTConsumer *Consumer = &S.getASTConsumer();
//创建解析器
std::unique_ptr<Parser> ParseOP(
new
Parser(S.getPreprocessor(), S, SkipFunctionBodies));
Parser &P = *ParseOP.get();
...
//设置主源文件,开始处理主源文件中的内容
S.getPreprocessor().EnterMainSourceFile();
//获取外部AST源,外部AST源通常用于提供额外的语义信息或进行增量编译
ExternalASTSource *External = S.getASTContext().getExternalSource();
if
(External)
//通知外部源开始翻译单元的处理
External->StartTranslationUnit(Consumer);
//获取词法分析器
bool
HaveLexer = S.getPreprocessor().getCurrentLexer();
if
(HaveLexer) {
llvm::TimeTraceScope TimeScope(
"Frontend"
);
P.Initialize();
Parser::DeclGroupPtrTy ADecl;
//用于存储解析器解析的顶层声明组
Sema::ModuleImportState ImportState;
//模块导入的状态
//PotentiallyEvaluated用于在语义分析期间设置表达式求值的上下文
EnterExpressionEvaluationContext PotentiallyEvaluated(S, Sema::ExpressionEvaluationContext::PotentiallyEvaluated);
//解析源文件中的顶层声明,并将它们传递给 AST 消费者进行处理
for
(
bool
AtEOF = P.ParseFirstTopLevelDecl(ADecl, ImportState); !AtEOF; AtEOF = P.ParseTopLevelDecl(ADecl, ImportState)) {
if
(ADecl && !Consumer->HandleTopLevelDecl(ADecl.get()))
return
;
}
}
// 处理由#pragma weak生成的顶层声明
for
(Decl *D : S.WeakTopLevelDecls())
Consumer->HandleTopLevelDecl(DeclGroupRef(D));
Consumer->HandleTranslationUnit(S.getASTContext());
//收尾工作
...
}
void
clang::ParseAST(Sema &S,
bool
PrintStats,
bool
SkipFunctionBodies) {
...
//获取AST消费者
ASTConsumer *Consumer = &S.getASTConsumer();
//创建解析器
std::unique_ptr<Parser> ParseOP(
new
Parser(S.getPreprocessor(), S, SkipFunctionBodies));
Parser &P = *ParseOP.get();
...
//设置主源文件,开始处理主源文件中的内容
S.getPreprocessor().EnterMainSourceFile();
//获取外部AST源,外部AST源通常用于提供额外的语义信息或进行增量编译
ExternalASTSource *External = S.getASTContext().getExternalSource();
if
(External)
//通知外部源开始翻译单元的处理
External->StartTranslationUnit(Consumer);
//获取词法分析器
bool
HaveLexer = S.getPreprocessor().getCurrentLexer();
赞赏
- [原创]去除反混淆后程序中的冗余汇编代码 2477
- [原创]从Clang到Pass加载与执行的流程 24017
- OLLVM混淆源码解读 23826