深入研究Clang(九) Clang代碼閱讀之打log讀流程2

繼續上一篇,同樣的hello.c,同樣的執行過程,只不過繼續添加了一些log信息,而且對代碼進行了更近一步的挖掘。先看輸入和輸出的log信息(前半部分):

  1. shining@shining-VirtualBox:~/llvm-3.9.0/build/bin$ ./clang hello.c -o hello
  2. clang/tools/driver/driver.cpp/main()_begin/shining_add
  3. clang/lib/Driver/Driver.cpp/BuildCompilation()_begin/shining_add
  4. clang/lib/Driver/ToolChains.cpp/Linux()_begin/shining_add
  5. clang/lib/Driver/Tools.cpp/Clang::ConstructJob()_beforeCC1/shining_add
  6. clang/lib/Driver/Driver.cpp/ExecuteCompilation()_begin/shining_add
  7. clang/lib/Driver/Compilation.cpp/ExecuteJobs()_begin/shining_add
  8. clang/lib/Driver/Compilation.cpp/ExecuteCommand()_begin/shining_add
  9. clang/lib/Driver/Job.cpp/Execute()_begin/shining_add
  10. llvm/lib/Support/Program.cpp/ExecuteAndWait()_begin/shining_add
  11. llvm/lib/Support/Unix/Program.inc/Execute()_begin/shining_add
  12. llvm/lib/Support/Unix/Program.inc/Execute()_begin/shining_add333
  13. llvm/lib/Support/Unix/Program.inc/Execute()_begin/shining_add444
  14. clang/tools/driver/driver.cpp/main()_begin/shining_add
  15. clang/tools/driver/driver.cpp/ExecuteCC1Tool()/shining_add
  16. clang/tools/driver/cc1_main.cpp/cc1_main()/shining_add

-----------------------------------------------------------------------------------------------------

這個時候,程序已經通過了對命令行輸入的第一次初步執行,執行了clang/tools/driver/driver.cpp中的main()函數,並且添加了很多命令行的具體參數,並且新建線程開始第二次執行clang/tools/driver/driver.cpp中的main()函數。在第二次執行main()函數的時候,選擇了和第一次執行完全不同的路徑,這是因為第一遍執行main()函數的調用過程中,已經為命令行添加了-cc1的參數選項。所以,第二次執行main()函數的時候,會選擇執行main()函數的調用ExecuteCC1Tool()的分支,具體代碼如下:

  1. if (FirstArg != argv.end() && StringRef(*FirstArg).startswith("-cc1")) {
  2. // If -cc1 came from a response file, remove the EOL sentinels.
  3. if (MarkEOLs) {
  4. auto newEnd = std::remove(argv.begin(), argv.end(), nullptr);
  5. argv.resize(newEnd - argv.begin());
  6. }
  7. return ExecuteCC1Tool(argv, argv[1] + 4);
  8. }

-------------------------------------------------------------------------------------------------------

現在再回頭對這個過程中的代碼進行分析,第一次執行clang/tools/driver/driver.cpp中的main()函數的時候,下面的代碼是核心部分:

  1. std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(argv));
  2. int Res = 0;
  3. SmallVector<std::pair<int, const Command *>, 4> FailingCommands;
  4. if (C.get())
  5. Res = TheDriver.ExecuteCompilation(*C, FailingCommands);

主要是TheDriver的BuildCompilation和ExecuteCompilation的兩個函數的執行,而且這兩個是一個順序的過程,先build再execute。其中,TheDriver是Driver類的一個具體對象,而Driver類的具體實現是在clang/lib/Driver/Driver.cpp中。也就是說,其實clang的代碼裡面,有兩個driver相關目錄,並各自包含一些文件,以driver直接命名的cpp文件舉例:tools/driver/driver.cpp和lib/Driver/Driver.cpp,前者是驅動編譯器的驅動,而後者是Driver類的具體實現。這兩個具體文件的差別,基本上也代表了兩個目錄的主要用途。

------------------------------------------------------------------------------------------------------

Driver類的BuildCompilation方法,通過調用關係,最終調用了clang/lib/Driver/Tools.cpp里的Clang::ConstructJob()函數,這個函數很重要的一個動作就是為了命令行的參數列表添加了「-cc1」參數,這個動作為第二次執行clang/tools/driver/driver.cpp中的main()函數的路徑起了決定性的作用。

  1. // Invoke ourselves in -cc1 mode.
  2. //
  3. // FIXME: Implement custom jobs for internal actions.
  4. CmdArgs.push_back("-cc1");

------------------------------------------------------------------------------------------------------

Driver類的ExecuteCompilation方法,通過調用關係,最終調用Compilation類的幾個方法,並且在clang/lib/Driver/Job.cpp/Command::Execute()函數的內部,開始調用llvm部分的support代碼llvm/lib/Support/Program.cpp中的llvm::sys::ExecuteAndWait()。

  1. //===----------------------------------------------------------------------===//
  2. //=== WARNING: Implementation here must contain only TRULY operating system
  3. //=== independent code.
  4. //===----------------------------------------------------------------------===//
  5. static bool Execute(ProcessInfo &PI, StringRef Program, const char **args,
  6. const char **env, const StringRef **Redirects,
  7. unsigned memoryLimit, std::string *ErrMsg);
  8. int sys::ExecuteAndWait(StringRef Program, const char **args, const char **envp,
  9. const StringRef **redirects, unsigned secondsToWait,
  10. unsigned memoryLimit, std::string *ErrMsg,
  11. bool *ExecutionFailed) {
  12. ProcessInfo PI;
  13. if (Execute(PI, Program, args, envp, redirects, memoryLimit, ErrMsg)) {
  14. if (ExecutionFailed)
  15. *ExecutionFailed = false;
  16. ProcessInfo Result = Wait(
  17. PI, secondsToWait, /*WaitUntilTerminates=*/secondsToWait == 0, ErrMsg);
  18. return Result.ReturnCode;
  19. }
  20. if (ExecutionFailed)
  21. *ExecutionFailed = true;
  22. return -1;
  23. }

llvm::sys::ExecuteAndWait()調用了Execute()函數,而Execute()函數則會根據不同的操作系統有不同的實現。我使用的環境是Ubuntu16.10,所以Execute()函數的具體實現就在llvm/lib/Support/Unix/Program.inc中。相應的此處也有一個llvm/lib/Support/Windows/目錄,存放Windows操作系統所對應的相關代碼實現。在llvm/lib/Support/Unix/Program.inc中Execute()函數的具體實現過程中有

#ifdef HAVE_POSIX_SPAWN

......

#endif

這樣的宏開關,這個宏開關在我的本地環境是可用的,所以具體執行了其中的

  1. int Err = posix_spawn(&PID, Program.str().c_str(), FileActions,
  2. /*attrp*/nullptr, const_cast<char **>(args),
  3. const_cast<char **>(envp));

根據Execute()函數中的相關注釋:

  1. // If this OS has posix_spawn and there is no memory limit being implied, use
  2. // posix_spawn. It is more efficient than fork/exec.

可以看出,這個實現方式是要比fork/exec的執行效率要高,所以在我的環境下,代碼走的就是這個路徑,而沒有執行下面的

  1. #endif
  2. // Create a child process.
  3. int child = fork();
  4. switch (child) {

這部分代碼。posix_spawn或者fork就是為了建立新的線程,從而重新執行clang/tools/driver/driver.cpp中的main()函數,所以才有了第二次執行clang/tools/driver/driver.cpp中的main()函數的出發點。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

代碼和修改後的代碼,我也見了一個代碼庫,地址:shining1984/clang_code_comment

2017-01-05
推薦閱讀:

過程間和過程內的數據流分析演算法在類似LLVM的IR或HotSpot C1的HIR中,是如何實現的?
聊聊PreStmt&lt;CallExpr&gt;,PreCall,PostCall,PostStmt&lt;CallExpr&gt;之間的關係
LLVM每日談之四 Pass初探

TAG:LLVM | Clang | ChrisLattner |