前言
《LLVM ASAN【源码分析】(一)》中通过已有资料简单分析了下ASAN结构。
《LLVM ASAN【源码分析】(二)》描述了调试环境的准备。
Clang12前使用Legacy Pass Manager启动ASAN,而Clang14开始采用New Pass Manager。
Clang父进程创建子进程完成编译任务。
从Clang父进程Main函数开始
线程刚启动时,记录栈底地址。作为LLVM工具执行一次性初始化。检查标准文件描述符。配置支持目标架构。设置命令行分词器。设置诊断功能。待Clang Driver解析完命令行后,ExecuteCompilation()
Fork
子进程执行具体编译任务。
// clang/tools/driver/driver.cpp
int main(...) {
...
Res = TheDriver.ExecuteCompilation(*C, FailingCommands);
...
}
进入Clang子进程Main函数
子进程带着解析后的命令,完成配置后使用cc1
(clang默认编译器)开始编译。(代码如下)
// clang/tools/driver/driver.cpp
int main(...) {
...
return ExecuteCC1Tool(Args);
...
}
调用cc1编译器
// clang/tools/driver/driver.cpp
static int ExecuteCC1Tool(SmallVectorImpl<const char *> &ArgV) {
...
return cc1_main(makeArrayRef(ArgV).slice(1), ArgV[0], GetExecutablePathVP);
...
}
创建编译器实例,配置调用功能、前端选项和诊断功能,执行前端动作。
// clang/tools/driver/cc1_main.cpp
int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
...
Success = ExecuteCompilerInvocation(Clang.get());
...
}
解析前端选项,加载所需插件,编译器实例创建并执行前端任务。
// clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
bool ExecuteCompilerInvocation(CompilerInstance *Clang) {
...
bool Success = Clang->ExecuteAction(*Act);
...
}
配置前端任务,读取源文件并准备处理。
// clang/lib/Frontend/CompilerInstance.cpp
bool CompilerInstance::ExecuteAction(FrontendAction &Act) {
...
if (llvm::Error Err = Act.Execute()) { ... }
...
}
执行CodeGen动作。
// clang/lib/Frontend/FrontendAction.cpp
llvm::Error FrontendAction::Execute() {
...
ExecuteAction(); //函数为虚函数,当前指向clang::CodeGenAction::ExecuteAction,类继承关系为class CodeGenAction : public ASTFrontendAction : public FrontendAction
...
}
由于当前文件语言不是IR,ASTFrontendAction
执行抽象语法树分析
// clang/lib/CodeGen/CodeGenAction.cpp
void CodeGenAction::ExecuteAction() {
...
this->ASTFrontendAction::ExecuteAction();
...
}
编译器实例创建Sema结构体以分析抽象语法树。
// clang/lib/Frontend/FrontendAction.cpp
void ASTFrontendAction::ExecuteAction() {
...
ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats, CI.getFrontendOpts().SkipFunctionBodies);
}
AST分析中会收集状态,配置语法结构体和分析器结构体。ASTConsumer处理顶级声明的词法分析,使用AST上下文处理转译单元(进入后端)。
// clang/lib/Parse/ParseAST.cpp
void clang::ParseAST(Sema &S, bool PrintStats, bool SkipFunctionBodies) {
...
Consumer->HandleTranslationUnit(S.getASTContext());
...
}
AST分析、IR生成及模块链接后,发射后端输出。
// clang/lib/CodeGen/CodeGenAction.cpp
// clang::BackendConsumer::HandleTranslationUnit
// 继承关系 class BackendConsumer : public ASTConsumer
void HandleTranslationUnit(ASTContext &C) override {
...
EmitBackendOutput(...);
...
}
构建汇编帮助工具实例,根据代码生成选项,使用LegacyPassManager发射二进制。
// clang/lib/CodeGen/BackendUtil.cpp
void clang::EmitBackendOutput(...) {
...
AsmHelper.EmitAssemblyWithNewPassManager(...);
AsmHelper.EmitAssembly(...);
...
}
开始处理Pass
Legacy
配置环境选项,分别构建模块和函数粒度的Pass管理器。
// clang/lib/CodeGen/BackendUtil.cpp
void EmitAssemblyHelper::EmitAssembly(BackendAction Action, std::unique_ptr<raw_pwrite_stream> OS) {
...
CreatePasses(PerModulePasses, PerFunctionPasses);
...
}
PMBuilder开始填充模块和函数粒度的Pass管理器,也就是开始注册各个Pass。最后Pass管理器会调用createAddressSanitizerFunctionPass
/createModuleAddressSanitizerLegacyPassPass
来构建AddressSanitizerLegacyPass
/ModuleAddressSanitizerLegacyPass
类对象,构造函数会进行真正的初始化。
与此同时,由于AddressSanitizerLegacyPass
依赖ASanGlobalsMetadataWrapperPass
(事实上ModuleAddressSanitizerLegacyPass
也依赖,但AddressSanitizerLegacyPass
先初始化),因此会初始化ASanGlobalsMetadataWrapperPass
类,将处理顶级声明时提取的全局变量信息保存到"llvm.asan.globals"
标记的GlobalsMetadata
类中,供ASAN后续针对全局变量插桩Redzone使用。
New
- 对目标机器、选项、PassBuilder、Pass插件及Pass配置注册。
- PassBuilder构造函数依据
shouldPopulateClassToPassNames
(当前false
)记录Pass/Analysis名和类名 - PassBuilder注册几类Pass/Analysis管理器【
Module Analyses Manager
、CGSCC Analyses Manager
、Function Analyses Manager
(登记了Alias Analyses Manager
、Target Library Analysis
)、Loop Analyses Manager
】 - 根据选项(当前
false
)开启BoundsCheckingPass
addSanitizers()
中注册用于添加Pass的Lambda函数- 当前优化级别为
O0
,进入buildO0DefaultPipeline
。
// clang/lib/CodeGen/BackendUtil.cpp
void EmitAssemblyHelper::EmitAssemblyWithNewPassManager(
...
MPM = PB.buildO0DefaultPipeline(Level, IsLTO || IsThinLTO);
...
}
添加最基本的ModulePass,执行注册过的各类Optimizer回调函数(当前均为空),调用前面addSanitizers()
注册的Lambda函数。
// llvm/lib/Passes/PassBuilder.cpp
ModulePassManager PassBuilder::buildO0DefaultPipeline(OptimizationLevel Level, bool LTOPreLink) {
...
for (auto &C : OptimizerLastEPCallbacks)
C(MPM, Level);
...
}
addSanitizers
及其中注册的Lambda函数如下所示。添加ModuleSanitizerCoveragePass
,添加Sanitizer.def
中定义的MemorySanitizer
、Kernel MemorySanitizer
、ThreadSanitizer
、AddressSanitizer
(关注)、Kernel AddressSanitizer
、Hardware-assisted AddressSanitizer
、Kernel Hardware-assisted AddressSanitizer
、DataFlowSanitizer
。
在ASanPass
这个Lambda函数中,它对ASanGlobalsMetadataAnalysis
、ModuleAddressSanitizerPass
、AddressSanitizerPass
(该函数Pass添加时转为模块Pass)三类均以ModulePass的形式进行添加,添加时会调用对应的构造函数。
// clang/lib/CodeGen/BackendUtil.cpp
static void addSanitizers(..., PassBuilder &PB) {
PB.registerOptimizerLastEPCallback([&](ModulePassManager &MPM,
OptimizationLevel Level) {
...
auto ASanPass = [&](SanitizerMask Mask, bool CompileKernel) {
if (LangOpts.Sanitize.has(Mask)) {
...
MPM.addPass(RequireAnalysisPass<ASanGlobalsMetadataAnalysis, Module>());
MPM.addPass(ModuleAddressSanitizerPass(
CompileKernel, Recover, ModuleUseAfterScope, UseOdrIndicator,
DestructorKind));
MPM.addPass(createModuleToFunctionPassAdaptor(AddressSanitizerPass(
CompileKernel, Recover, UseAfterScope, UseAfterReturn)));
}
};
ASanPass(SanitizerKind::Address, false);
ASanPass(SanitizerKind::KernelAddress, true);
...
});
}
ASAN Pass初始化(Legacy)
AddressSanitizerLegacyPass
和ModuleAddressSanitizerLegacyPass
类构造函数内调用初始化函数,根据配置通过PassRegistry
注册自身,包括:
- 将
PassInfo
注册到PassInfoMap
、PassInfoStringMap
- 通知可能存在的监听者
- 将自身加入
ToFree
列表,表示后续需要释放
explicit AddressSanitizerLegacyPass(...) : ... {
initializeAddressSanitizerLegacyPassPass(*PassRegistry::getPassRegistry());
}
返回并准备运行Pass
Legacy
返回EmitAssembly()
,运行Pass
// clang/lib/CodeGen/BackendUtil.cpp
void EmitAssemblyHelper::EmitAssembly(BackendAction Action, std::unique_ptr<raw_pwrite_stream> OS) {
...
PerModulePasses.run(*TheModule);
...
}
最后进入AddressSanitizerLegacyPass
/ModuleAddressSanitizerLegacyPass
的runOnFunction
/runOnModule
。
New
- 返回
EmitAssemblyWithNewPassManager()
。 - 目前版本仍然使用Legacy Pass Manager进行代码生成。
- 所有Pass添加完毕,开始运行它们。
// clang/lib/CodeGen/BackendUtil.cpp
void EmitAssemblyHelper::EmitAssemblyWithNewPassManager( BackendAction Action, std::unique_ptr<raw_pwrite_stream> OS) {
...
MPM.run(*TheModule, MAM);
...
}
后续
Pass运行源码调试放到《LLVM ASAN【源码分析】(四)》中。