《LLVM ASAN【源码分析】(一)——简单分析》中通过已有资料简单分析了下ASAN结构。
由于看源码有点懵,进行Clang-ASAN源码调试。
下面使用gdb 调试分析ASAN源码。
gdb 调试分析ASAN源码
Legacy ASAN不再使用,关注使用New PassManager的ASAN。Clang父进程创建子进程完成编译任务。
ASAN初始化时栈信息(子进程视角)
断点下在AddressSanitizerPass::AddressSanitizerPass() ,栈信息如下(可以看到调用过程):
(gdb) set args -g -std=c++11 -fsanitize=address,fuzzer /home/leone/文档/libfuzzer-workshop/lessons/04/first_fuzzer.cc -o /home/leone/文档/libfuzzer-workshop/lessons/04/first_fuzzer
(gdb) set follow-fork-mode child
(gdb) b AddressSanitizerPass::AddressSanitizerPass
(gdb) r
(gdb) info stack
UseAfterReturn=llvm::AsanDetectStackUseAfterReturnMode::Runtime)
at /home/leone/文档/llvm-project/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp:1219
at /home/leone/文档/llvm-project/clang/lib/CodeGen/BackendUtil.cpp:1162
at /home/leone/文档/llvm-project/clang/lib/CodeGen/BackendUtil.cpp:1166
__args
at /home/leone/文档/llvm-project/llvm/lib/Passes/PassBuilder.cpp:1993
Action=clang::Backend_EmitObj, OS=std::unique_ptr<llvm::raw_pwrite_stream> = {...})
at /home/leone/文档/llvm-project/clang/lib/CodeGen/BackendUtil.cpp:1408
M=0x555563e880f0, Action=clang::Backend_EmitObj, OS=std::unique_ptr<llvm::raw_pwrite_stream> = {...})
at /home/leone/文档/llvm-project/clang/lib/CodeGen/BackendUtil.cpp:1658
at /home/leone/文档/llvm-project/clang/lib/CodeGen/CodeGenAction.cpp:334
at /home/leone/文档/llvm-project/clang/lib/Parse/ParseAST.cpp:171
at /home/leone/文档/llvm-project/clang/lib/Frontend/FrontendAction.cpp:1058
at /home/leone/文档/llvm-project/clang/lib/CodeGen/CodeGenAction.cpp:1044
at /home/leone/文档/llvm-project/clang/lib/Frontend/FrontendAction.cpp:951
at /home/leone/文档/llvm-project/clang/lib/Frontend/CompilerInstance.cpp:974
at /home/leone/文档/llvm-project/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp:278
MainAddr=0x55555c3488ea <GetExecutablePath[abi:cxx11](char const*, bool)>)
at /home/leone/文档/llvm-project/clang/tools/driver/cc1_main.cpp:246
从Clang父进程Main函数开始
线程刚启动时,记录栈底地址。作为LLVM工具执行一次性初始化。检查标准文件描述符。配置支持目标架构。设置命令行分词器。设置诊断功能。待Clang Driver解析完命令行后,ExecuteCompilation() Fork 子进程执行具体编译任务。
int main(...) {
...
if (C && !C->containsError()) {
...
Res = TheDriver.ExecuteCompilation(*C, FailingCommands);
...
}
...
}
进入Clang子进程Main函数
子进程带着解析后的具体命令,完成配置后使用cc1 执行具体编译任务。(代码如下)
int main(...) {
...
if (FirstArg != Args.end() && StringRef(*FirstArg).startswith("-cc1")) {
...
return ExecuteCC1Tool(Args);
}
...
}
检查参数包含-cc1 选项(Clang Driver分析命令行后加上的选项),调用cc1_main() 。
static int ExecuteCC1Tool(SmallVectorImpl<const char *> &ArgV) {
...
if (Tool == "-cc1")
return cc1_main(makeArrayRef(ArgV).slice(1), ArgV[0], GetExecutablePathVP);
...
}
创建Clang实例并配置调用功能、前端选项和诊断功能,然后执行前端动作。
int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
...
{
llvm::TimeTraceScope TimeScope("ExecuteCompiler");
Success = ExecuteCompilerInvocation(Clang.get());
}
...
}
解析前端选项,加载所需插件,基于Clang实例创建并执行前端动作。
bool ExecuteCompilerInvocation(CompilerInstance *Clang) {
...
std::unique_ptr<FrontendAction> Act(CreateFrontendAction(*Clang));
if (!Act) return false;
bool Success = Clang->ExecuteAction(*Act);
...
}
配置前端动作,读取源文件并准备处理。
bool CompilerInstance::ExecuteAction(FrontendAction &Act) {
...
for (const FrontendInputFile &FIF : getFrontendOpts().Inputs) {
...
if (Act.BeginSourceFile(*this, FIF)) {
if (llvm::Error Err = Act.Execute()) {
consumeError(std::move(Err));
}
Act.EndSourceFile();
}
}
...
}
执行后端CodeGen动作。
llvm::Error FrontendAction::Execute() {
...
ExecuteAction();
...
}
当前文件所用语言不是IR,让ASTFrontendAction 执行动作
void CodeGenAction::ExecuteAction() {
if (getCurrentFileKind().getLanguage() != Language::LLVM_IR) {
this->ASTFrontendAction::ExecuteAction();
return;
}
...
}
获取编译器实例并创建Sema结构体以分析抽象语法树。
void ASTFrontendAction::ExecuteAction() {
...
ParseAST(CI.getSema(), CI.getFrontendOpts().ShowStats,
CI.getFrontendOpts().SkipFunctionBodies);
}
AST分析中收集状态,配置语法结构体和分析器结构体。ASTConsumer处理顶级声明的词法分析,使用AST上下文处理转译单元(再次进入后端)。
void clang::ParseAST(Sema &S, bool PrintStats, bool SkipFunctionBodies) {
...
Consumer->HandleTranslationUnit(S.getASTContext());
...
}
AST分析、IR生成及模块链接后,发射后端输出。
void HandleTranslationUnit(ASTContext &C) override {
...
EmitBackendOutput(...);
...
}
构建汇编帮助工具实例,根据代码生成选项,使用新的PassManager发射二进制。
void clang::EmitBackendOutput(...) {
...
if (!CGOpts.LegacyPassManager)
AsmHelper.EmitAssemblyWithNewPassManager(Action, std::move(OS));
...
}
根据目标机器配置,各种选项结构体配置,构造PassBuilder,加载Pass插件并使用PassBuilder对其注册回调,注册几个Pass(Alias Analyses Manager、Target Library Analysis、Module Analyses、CGSCC Analyses、Function Analyses、Loop Analyses)
void EmitAssemblyHelper::EmitAssemblyWithNewPassManager(
BackendAction Action, std::unique_ptr<raw_pwrite_stream> OS) {
...
if (!CodeGenOpts.DisableLLVMPasses) {
...
}
...
}
未完待续。。。
|