编译器 LLVM Clang原理与实战 制作自己的编译器 source-to-source 源代码转换 编译遍 compile pass 代码插桩

编译器 LLVM Clang原理与实战

在这里插入图片描述
参考1

clang

LLVM CMU 教案

深入剖析-iOS-编译-Clang—LLVM

LLVM_proj

LLVM编程索引

llvm源码浏览带跳转

llvm-clang-samples

llvm-tutor A collection of LLVM passes (with tests and build scripts)

简介

LLVM的命名最早来源于底层语言虚拟机(Low Level Virtual Machine)的缩写。它是一个用于建立编译器的基础框架,以C++编写。创建此工程的目的是对于任意的编程语言,利用该基础框架,构建一个包括编译时、链接时、执行时等的语言执行器。目前官方的LLVM只支持处理C/C++,Objective-C三种语言,当然也有一些非官方的扩展,使其支持ActionScript、Ada、D语言、Fortran、GLSL、Haskell、Java bytecode、Objective-C、Python、Ruby、Rust、Scala以及C#

llvm的主要作用是它可以作为多种语言的后端,它可以提供可编程语言无关的优化和针对很多种CPU的代码生成功能。此外llvm目前已经不仅仅是个编程框架,它目前还包含了很多的子项目,比如最具盛名的clang(LLVM编译器的C家族语言编译器前端).

LLVM的核心思想是将各种语言解析成LLVM的中间表示语言(LLVM IR),然后LLVM通过各种pass在IR上进行优化,最后通过各种代码生成后端生成目标机器上的机器代码,这样就可以最大程度重用前端解析、中端优化以及后端代码生成。

其他更详细的介绍与历史,知乎上一大堆。

话不多说,进入正题。

编译

github源码

从上述路径下载源代码,下面是在linux上的安装方法。

编译命令:

编译依赖:

brew install gcc
brew install cmake

进入到 源码目录 cmake生成make脚本

mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=“clang;libcxx;libcxxabi;clang-tools-extra” -DBUILD_SHARED_LIBS=ON -DLLVM_TARGETS_TO_BUILD=“AArch64;X86” -DCMAKE_INSTALL_PREFIX=/usr/local -G “Unix Makefiles” …/llvm

编译

make j8

安装

make install

其中:

-DLLVM_ENABLE_PROJECTS 指定了安装的附加库 DCMAKE_INSTALL_PREFIX指定了工具安装到计算机的目录

前端clang分析

Clang是LLVM编译器工具集的前端部分,也就是涵盖词法分析、语法语义分析的部分。而LLVM是Apple在Mac OS上用于替代GCC工具集的编译器软件集合。Clang支持类C语言的语言,例如C、C++、Objective C。Clang的与众不同在于其模块化的设计,使其不仅实现编译器前端部分,并且包装成库的形式提供给上层应用。使用Clang可以做诸如语法高亮、语法检查、编程规范检查方面的工作,当然也可以作为你自己的编译器前端。

前端主要完成目标目标的解析生成LLVM中间表示语言,其步骤为:
字符串流 -> 词法分析(Lexical analysis,生成TOKEN流)-> 语法分析(Syntactic analysis,生成初级抽象语法树AST)、语义分析(Semantic analysis,生成合规的AST)最后输出与平台无关的IR(LLVM IR generator)。

源码目录分析

clang/lib/Lex 词法分析(Lexical analysis,生成TOKEN流)
clang/lib/Parse 语法分析(Syntactic analysis,生成初级抽象语法树AST)
clang/lib/Sema 语义分析(Semantic analysis,生成合规的AST)
clang/lib/AST AST抽象语法树
clang/lib/StaticAnalyzer 程序静态分析
clang/lib/CodeGen LLVM IR代码生成
clang/lib/Rewrite 语言重写,AST 反向转换回源语言
其他

Clang 执行方式分析

Clang是LLVM的C/C++前端,从原理上他会产生用于后端的IR指令。但实际上Clang会有两种执行方式: 我们可以使用”-###”观察Clang的执行过程。

1. 以Driver的方式执行: 生成目标代码

会自动调用相关的前后端程序,并生成可执行文件,这也是之所以Clang虽然只是前端,却可以直接产生目标代码的原因.
在驱动模式下,clang实质只是一个调度管理程序.

2. 作为编译器-cc1前端方式运行:最后仅生成LLVM IR 

Clang执行初期是作为driver执行的,因此,程序的入口是:tools/driver/driver.cpp;

代码位置

如果不是-cc1,则进行相关命令解释,生成相容的命令行

通过Driver建立与GCC相容的编译过程,并由TheDriver.ExecuteCompilation执行该相容的命令行

注意因为clang两种工作模式下,驱动模式实际是在补足参数后再通过-cc1的方式执行;

在Driver方式下,只是为clang补齐相关执行的各参数,如类库的名字,然后是通过“系统”执行clang -cc1命令,而并没有在“内部”继续clang的其余的操作;此时,clang会等待相关的执行操作完成后执行下一个命令(如ld)

clang驱动方式过程:

  1. Parse:Option Parsing 编译参数解析
    在这个阶段完成后,命令行将被分解为具有适当参数定义好的选项。

  2. Pipeline:编译动作构造(Compilation Action Construction) 编译流水线构造

    子进程作业树需要确认构造编译序列。这包含确认输入文件及其类型、对他们进行什么样的工作(预处理、编译、汇编、链接等)并为每个人物构造动作实例链表。这样的结构是一个或更多的顶层动作列表,每个通常对应一个单一的输出(例如,对象或链接的可执行文件)多数的动作(Actions)对应一个实际的任务,但是这里有两个特殊的任务(Actions),第一个是InputActions,他只是简单将输入参数匹配到另一个Actions的输入。第二个是BindArchAction,从概念上给所有使用输入的动作替换架构Clang驱动可以使用命令“-ccc-print-phases”转印这一阶段的结果。

  3. Action 执行行动 步骤 阶段 过程

每次的 option 都会完整的走一遍从预处理,静态分析,backend 再到汇编的过程。

下面列下一些编译器的前端 Action,大家可以一个个用着玩。

InitOnlyAction - 只做前端初始化,编译器 option 是    -init-only
PreprocessOnlyAction - 只做预处理,不输出,编译器的 option 是 -Eonly
PrintPreprocessedAction - 做预处理,子选项还包括-P、-C、-dM、-dD 具体可以查看PreprocessorOutputOptions 这个类,编译器 option 是 -E
RewriteIncludesAction - 预处理
DumpTokensAction - 打印token,option 是 -dump-tokens
DumpRawTokensAction - 输出原始tokens,包括空格符,option 是 -dump-raw-tokens
RewriteMacrosAction - 处理并扩展宏定义,对应的 option 是 -rewrite-macros
HTMLPrintAction - 生成高亮的代码网页,对应的 option 是 - emit-html
DeclContextPrintAction - 打印声明,option 对应的是 -print-decl-contexts
ASTDeclListAction - 打印 AST 节点,option 是 -ast-list
ASTDumpAction - 打印 AST 详细信息,对应 option 是 -ast-dump
ASTViewAction - 生成 AST dot 文件,能够通过 Graphviz 来查看图形语法树。 option 是 -ast-view
AnalysisAction - 运行静态分析引擎,option 是 -analyze
EmitLLVMAction - 生成可读的 IR 中间语言文件,对应的 option 是      -emit-llvm
EmitBCAction - 生成 IR Bitcode 文件,option 是                   -emit-llvm-bc
MigrateSourceAction - 代码迁移,option 是 -migrate
  1. Bind 绑定:

Tool & Filename Selection 工具、文件绑定

Bind 主要是与工具链 ToolChain 交互
根据创建的那些 Action,在 Action 执行时 Bind 来提供使用哪些工具,比如生成汇编时是使用内嵌的还是 GNU 的,还是其它的呢,这个就是由 Bind 来决定的,具体使用的工具有各个架构,平台,系统的 ToolChain 来决定

驱动与工具链互动去执行工具绑定(The driver interacts with a ToolChain to perform the Tool bindings)。每个工具链包含特定架构、平台和操作系统等编译需要的所有信息;一个单一的工具在编译期间需要提取很多工具链,为了与不同体系架构的工具进行交互。

这一阶段不会直接计算,但是驱动器可以使用”-ccc-print-bindings”参数打印这一结果,会显示绑定到编译序列的工具链,工具,输入和输出。

  1. Translate 翻译:

Tool Specific Argument Translation 工具参数翻译

当工具被选择来执行特定的动作,工具必须为之后运行的编译过程构造具体的命令。主要的工作是翻译gcc格式的命令到子进程所期望的格式。

  1. Execute 工具执行

其执行过程大致如下:Driver::ExecuteCompilation -> Compilation::ExecuteJobs -> Compilation::ExecuteCommand-> Command::Execute -> llvm::sys::ExecuteAndWait;此时执行的ExecuteAndWait为Support/Program.cpp中的程序,其调用相关操作系统,执行其系统相关的执行程序,并等待执行过程完成。

clang cc1 前端运行

真正有意义的前端操作

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

如果是 -cc1 的话会调用 ExecuteCC1Tool 这个函数,先看看这个函数

static int ExecuteCC1Tool(ArrayRef<const char *> argv) {
  llvm::cl::ResetAllOptionOccurrences();
  StringRef Tool = argv[1];
  void *GetExecutablePathVP = (void *)(intptr_t) GetExecutablePath;
  if (Tool == "-cc1")
    return cc1_main(argv.slice(2), argv[0], GetExecutablePathVP);
  if (Tool == "-cc1as")
    return cc1as_main(argv.slice(2), argv[0], GetExecutablePathVP);
  if (Tool == "-cc1gen-reproducer")
    return cc1gen_reproducer_main(argv.slice(2), argv[0], GetExecutablePathVP);
  // Reject unknown tools.
  return 1;
}

最终的执行会执行 cc1-main 、cc1as_main 、cc1gen_reproducer_main。这三个函数分别在 driver.cpp 同级目录里的 cc1_main.cpp 、cc1as_main.cpp 、cc1gen_reproducer_main.cpp中。

依照关于Driver的示意图,clang将藉由Action完成具体的操作,在clang中所有action定义在include/clang/Drivers名字域:clang::driver下,其Action即其派生的Actions定义如下:

这一阶段完成,编译过程被分为一组需要执行并产生中间或最终输出(某些情况下,类似-fsyntax-only,不会有“真实”的最终输出)的Action。阶段是我们熟知的编译步骤,类似:预处理、编译、汇编、链接等等。

所有相关Action的定义在FrontendOptions.h(clang/include/clang/Frontend/FrontendOptions.h )中;

代码

在clang中允许通过FrontendAction编写自己的Action,使用FrontendPluginRegistry(clang/frontend/FrontendRegistry.h)注册自己的Action:其核心是通过继承clang::FrontendAction来实现,详细示例参考:clang/examples/AnnotateFunctions/AnnotateFunctions.cpp,该示例通过继承PluginASTAction,并使用FrontendPluginRegistry::Add将其注册

前端基础知识介绍

最初的C/C++源码经过:词法分析(Lexical analysis)、语法分析(Syntactic analysis)、语义分析(Semantic analysis)最后输出与平台无关的IR(LLVM IR generator)

从词法分析开始——将C语言 源码分解成token流,每个token可表示标识符、字面量、运算符等;
token流会传递给语法分析器,语法分析器会在语言的CFG(Context Free Grammar,上下文无关文法)的指导下将token流组织成AST(抽 象语法树);接下来会进行语义分析,检查语义正确性,然后生成IR。
libclang clang-c/Index.h c接口调用clang 示例

1. 词法分析(Lexical analysis) clang/lib/Lex/

token

词法分析器读入组成源程序的字节流,并将他们组成有意义的词素(Lexeme)序列。对于每个词素,词法分析器产生词单元(token)作为输出,并生成相关符号表。词法库包含了几个紧密相连的类,他们涉及到词法和C源码预处理。

相关诊断: clang/include/clang/Basic/DiagnosticLexKinds.td

词法单元(Token)的定义:TokenKinds.def(clang/include/clang/Basic/)

Clang的保留字定义在 TokenKinds.def,如常见的if或for关键字

说明TokenKinds.def中定义了许多诸如TOK和KEYWORD等宏,实际相关宏只有在外部引用程序引用TokenKinds.def前定义才会真正有意义;而且引用位置不同,其意义可能不同

Preprocessor类 ----- 返回下一个Token

Preprocessor是词法分析中的一个主要接口,可以看到,词法分析时在预处理过程中初始化的即Preprocessor.

在词法分析中,预处理程序被初始调用的主要程序是CompilerInstance::createPreprocessor(lib/Frontend/CompilerInstance.cpp)

之后是 FrontEnd Action -> /clang/lib/Lex/Preprocessor.cpp

其核心程序是Preprocessor::Lex,该程序返回下一个Token

Token类 clang/include/clang/Lex/Token.h

Token类用于表述电仪的词法单元。Token被用于词法/预处理单元,但并不会在这些库以外存在(如,Token不会存在于AST中).
Token.h

2. 语法分析(Syntactic analysis) libclangParse libclangAST

语法分析主要是解析词法分析产生的词法单元(token)并生成抽象语法树(ATS)。如同自然语言一样,语法分析并不检查语法的上下文的合理性(CFG上下文无关文法),其仅仅是从语法角度确认是否正确。

语法分析的核心数据结构:Decl、Stmt、Type对应于声明、指令、类型。所有的被Clang描述的C/C++类都是继承于这三个类

源码 clang/lib/Parse/ 和clang/lib/AST/

诊断信息 DiagnosticParseKinds.td

语法分析器是使用递归下降(recursive-descent)的语法分析。

clang的Parser是由clang::ParseAST执行的。

libclangAST:

    提供了类用于:表示C AST、C类型、内建函数和一些用于分析和操作AST的功能(visitors、漂亮的打印输出等)
    源码定义:lib/AST
    重要的AST节点:Type、Decl、DeclContext、Stmt

Type类和他的派生类 clang/lib/AST/Type.cpp

Type类及其派生类是AST中非常重要的一部分。通过ASTContext访问Type(clang/ast/ASTContext.h),在需要时他隐式的唯一的创建他们。Type有一些不明显的特征:
1)他们不捕获类似const或volatile类型修饰符(See QualType);
2)他们隐含的捕获typedef信息。一旦创建,type将是不可变的。

声明,Decl类 clang/AST/DeclBase.h

表示一个声明(declaration)或定义(definition)。例如:变量、typedef、函数、结构等

声明上下文,DeclContext类 clang/AST/DeclBase.h

程序中每个声明都存在于某一个声明上下文中,类似翻译单元、名字空间、类或函数。Clang中的声明上下文是由类DeclContext类进行描述:各种AST节点声明上下文均派生于此(TranslationUnitDecl、NamespaceDecl、RecordDecl、FunctionDecl等)

DeclContext类对于每个声明上下文提供了一些公用功能:

  1. 与源码为中心和语义为中心的声明视图

    DeclContext提供了两种声明视图:源码为中心视图准确表示源程序代码,包括多个实体的声明(参见再宣告和重载)。而语义为中心的视图表示了程序语义。这两个视图在AST构造时与语义分析保持同步。

  2. 在上下文中保存声明

每个声明上下文中都包含若干的声明。例如:C++类(被表示为RecordDecl)包含了一些成员函数、域、嵌套类型等等。所有这些声明都保存在DeclContext中:即可以由容器迭代操作获得

这个机制提供了基于源码视图的声明上下文视图。

  1. 在上下文中查找声明

由DeclarationName类型指定的声明在声明上下文中查找声明 lookup()

该机制提供了基于语义为中心的声明上下文视图

  1. 声明所有者

DeclContext包含了所有在其中声明的声明上下文,并负责管理他们并以及序列化(反)他们所有声明都保存在声明上下文中,并可以查询每个保存在其中的声明信息。关于声明上下文可以查看词法和语义分析一节.

  1. 再宣告和重载

在翻译单元中,公共的实体可能会被声明多次。

表达式”f”在以源码为中心的和以语义为中心的上下文中的视图有所不同,在以源码为中心的声明上下文中,再宣告与他在源码中声明的位置有关。在语义为中心的视图中,会使用最近的视图替换最初的声明。而基于DeclContext::look操作将返回基于语义视图的上下文。

  1. 词法和语义上下文

对于每个声明可能存在两个不同的声明上下文:词法上下文,对应于源码视图的声明上下文。语义上下文对应于语法视图的。Decl::getLexicalDeclContext(clang/AST/DeclBase.h)返回词法声明上下文。而Decl::getDeclContext返回基于语义上下文,返回的两个值都是指向DeclContext的指针。

  1. 透明声明上下文(TransparentDeclaration Contexts)

现于枚举类型,Red是Color中的成员,但是,我们在Color外引用Red时并不需要限定名:Color;

  1. 多重定义的声明上下文(Multiply-DefinedDeclaration Contexts)

可以由DeclContext::isTransparentContext确认是否是透明声明。

Stmt 指令 类 clang/AST/Stmt.h for do goto return …

QualType类 查询类

QualType被设计为一个微不足道的、微小的通过传值用于高效查询的类。QualType的思想是将类型修饰符(const、volatile、restrict以及语言扩展所需的修饰符)与他们自己的类型分开保存。QualType概念上是一对“Type *”和他们的类型修饰符。类型限定符只是占用指针的低位。

声明名字(Declarationnames)

DeclarationName(clang/AST/DeclarationName.h)用来描述clang中的声明名字。声明在C族语言中有一些不同的形式。多数的声明被命名为简单的标识,例如:f(int x)中的声明f和x。在C++中,声明可以构造类的构造函数、类的析构函数、重载操作符合转换函数。

CFG类 控制流程图 contro flow graph

CFG是用于描述单个指令(Stmt *)的源码级控制流程图?。典型的CFG实例为构造函数体(典型的是一个CompoundStmt实例),但是也可以表示任何Stmt派生类的控制流。控制流图通常对给定函数执行流-或路径-敏感的分析特别有用。

3.语义分析(Semantic Analysis)

libclangSema

4. 中间代码生成(IR Generator) libclangCodeGen

libclangCodeGen

5. 其他库介绍

libclangAnaylysis:用于进行静态分析用的

libclangRewrite:编辑文本缓冲区(代码重写转换非常重要,如重构)

libclangBasic:诊断、源码定位、源码缓冲区抽象化、输入源文件的文件缓冲区

Clang诊断子系统是一个编译器与人交互的重要部分。诊断是当代码不正确或可疑时产生警告和错误。在clang中,每个诊断产生(最少)一个唯一标识ID、一个相关的英文、SourceLocation源码位置“放置一个^”和一个严重性(例如:WARNING或ERROR)。他可以选择包含一些参数给争端(如使用%0填充字串)以及相关源码区域。

Clang diagnostics Diagnostic::Level enum [ NOTE WARNING EXTENSION EXTWARN ERROR ]

SourceLocation:表示源代码的位置。See:clang/Basic/SourceLocation.h。SourceLocation因为被嵌入到许多AST中,因此,该类必须足够小。

SourceLocation:通常是和SourceManager一同使用,用于对一个位置信息的两条信息进行编码。见:clang/Basic/SourceManager.h

SourceRange:是SourceLocation.h中类,表示源码的范围:【first,last】。First和last都是SourceLocation

clang实战

实战 利用Clang制作自己的编译器 source-to-source 源代码转换


[ASTmatcher ast匹配器 匹配规则](http://clang.llvm.org/docs/LibASTMatchersReference.html)

void foo(int* a, int *b) {
  if (a[0] > 1)
  {
    b[0] = 2;
  }
}
void bar(float x, float y); // just a declaration 

自动添加 添加注释

// Begin function foo returning void
void foo(int* a, int *b) {
  if (a[0] > 1) // the 'if' part
  {
    b[0] = 2;
  }
}
// End function foo
void bar(float x, float y); // just a declaration 

实现代码


#include <sstream>
#include <string>

#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"      // 递归 AST遍历
#include "clang/Frontend/ASTConsumers.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Rewrite/Core/Rewriter.h"        // 重写 c代码
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/raw_ostream.h"

using namespace clang;
using namespace clang::driver;
using namespace clang::tooling;

static llvm::cl::OptionCategory ToolingSampleCategory("Tooling Sample");

// By implementing RecursiveASTVisitor, we can specify which AST nodes
// we're interested in by overriding relevant methods.  通过 RecursiveASTVisitor 可以遍历特点的节点 并添加重写内容
class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor> {
  // RecursiveASTVisitor类负责实际对源码的改写 
public:
  MyASTVisitor(Rewriter &R) : TheRewriter(R) {}
 
  // 在RecursiveASTVisitor中重写VisitStmt函数与VisitFunctionDecl函数实现源码中目标语素的检测以及改写动作 
  // 改写好的源码送入Rewriter类中,进行写入源代码文件的动作 
  
  // 语句statements Stmt  遍历          expression表达式
  bool VisitStmt(Stmt *s) {
    // Only care about If statements.
    if (isa<IfStmt>(s)) {    // 是 if 语句
      IfStmt *IfStatement = cast<IfStmt>(s);
      Stmt *Then = IfStatement->getThen();

      TheRewriter.InsertText(Then->getBeginLoc(), "// the 'if' part\n", true,
                             true);
                  // 在if语句 后面添加注释   getLocStart() 旧接口 ---> getBeginLoc()  clang/AST/Stmt.h中

      Stmt *Else = IfStatement->getElse();
      if (Else)
        TheRewriter.InsertText(Else->getBeginLoc(), "// the 'else' part\n",
                               true, true);
                // 在else语句后面添加注释
    }

    return true;
  }

  // 函数定义(function definitions) 遍历
  bool VisitFunctionDecl(FunctionDecl *f) {
    // Only function definitions (with bodies), not declarations.
    if (f->hasBody()) {       // 有函数体 函数定义
      
      // 函数体是一个语句集和 statements set
      Stmt *FuncBody = f->getBody();

      // Type name as string
      QualType QT = f->getReturnType();       // 函数返回值类型
      std::string TypeStr = QT.getAsString(); // 对于的类型字符串

      // Function name
      DeclarationName DeclName = f->getNameInfo().getName(); // 函数名
      std::string FuncName = DeclName.getAsString();

      // param 
      int param_num =  f->getNumParams();    // clang/AST/Decl.h中
      std::string func_param;
      //for (FunctionDecl::param_iterator fit = f->param_begin(); fit != f->param_end(); fit++)
      for (int i = 0; i < param_num; i ++)
      {
              ParmVarDecl *ptemp = f->getParamDecl(i);
              func_param += " | ";

              //func_param += fit->getOriginalType().getAsString();   
              
              func_param += ptemp->getOriginalType().getAsString(); 

      }

      // Add comment before  生成函数头注释
      std::stringstream SSBefore;
      SSBefore << "// Begin function " << FuncName << ", returning " << TypeStr << ", param num: " << param_num << ", type: " << func_param
               << "\n"; 
      
      // 获取函数开头位置
      SourceLocation ST = f->getSourceRange().getBegin();
      // 插入注释
      TheRewriter.InsertText(ST, SSBefore.str(), true, true);

      // And after 添加函数尾注释
      std::stringstream SSAfter;
      SSAfter << "\n// End function " << FuncName;
      ST = FuncBody->getEndLoc().getLocWithOffset(1); // 函数体结束后的后面一个位置   getLocEnd() 旧接口 ---> getEndLoc()
      //  插入注释
      TheRewriter.InsertText(ST, SSAfter.str(), true, true);
    }

    return true;
  }

private:
  // 代码重写类对象实例
  Rewriter &TheRewriter;
};

// ASTConsumer负责读取Clang解析出来的AST树 并调用 MyASTVisitor 进行 匹配与改写
// Implementation of the ASTConsumer interface for reading an AST produced
// by the Clang parser.
class MyASTConsumer : public ASTConsumer {
public:
  MyASTConsumer(Rewriter &R) : Visitor(R) {}

  // Override the method that gets called for each parsed top-level
  // declaration.   遍历声明
  // 在ASTConsumer中重写HandleTopLevelDecl函数用以检测源码中的函数声明语句
  bool HandleTopLevelDecl(DeclGroupRef DR) override {
    for (DeclGroupRef::iterator b = DR.begin(), e = DR.end(); b != e; ++b) {
      // Traverse the declaration using our AST visitor.
      
      // 逐个遍历 调用 MyASTVisitor 对源码进行匹配与改写
      Visitor.TraverseDecl(*b);
      
      (*b)->dump();
    }
    return true;
  }
private:
  MyASTVisitor Visitor;   // 上面定义的 遍历AST类实例
};

// 前端 动作执行 类

// For each source file provided to the tool, a new FrontendAction is created.
class MyFrontendAction : public ASTFrontendAction {
public:
  MyFrontendAction() {}
  void EndSourceFileAction() override {
    // 源码管理器
    SourceManager &SM = TheRewriter.getSourceMgr();
    llvm::errs() << "** EndSourceFileAction for: "
                 << SM.getFileEntryForID(SM.getMainFileID())->getName() << "\n";  // 打印源码文件名

    // Now emit the rewritten buffer.
    TheRewriter.getEditBuffer(SM.getMainFileID()).write(llvm::outs());
  }

  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                 StringRef file) override {
    llvm::errs() << "** Creating AST consumer for: " << file << "\n";
    TheRewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts());
    return std::make_unique<MyASTConsumer>(TheRewriter);
  }

private:
  Rewriter TheRewriter;
};

int main(int argc, const char **argv) {
  CommonOptionsParser op(argc, argv, ToolingSampleCategory);
  ClangTool Tool(op.getCompilations(), op.getSourcePathList());

  // ClangTool::run accepts a FrontendActionFactory, which is then used to
  // create new objects implementing the FrontendAction interface. Here we use
  // the helper newFrontendActionFactory to create a default factory that will
  // return a new MyFrontendAction object every time.
  // To further customize this, we could create our own factory class.
  return Tool.run(newFrontendActionFactory<MyFrontendAction>().get());
}

老版本

// 编写你感兴趣的语法树节点访问接口,例如该例子中提供了函数调用语句和goto语句的节点访问接口
class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor> {
public:
    bool VisitCallExpr(CallExpr *expr);      //  遍历函数调用表达式

    bool VisitGotoStmt(GotoStmt *stmt);   // 遍历goto语句 Clang中使用类Stmt来代表statement语句
    // Clang构造出来的语法树,其节点类型就是Stmt。针对不同类型的语句,Clang有对应的Stmt子类,例如GotoStmt。Clang中的表达式也被视为语句,Clang使用Expr类来表示表达式,而Expr本身就派生于Stmt。
// 每个语法树节点都会有一个子节点列表,在Clang中一般可以使用如下语句遍历一个节点的子节点:
/*
for (Stmt::child_iterator it = stmt->child_begin(); it != stmt->child_end(); ++it) {
    Stmt *child = *it;
}
// 但遗憾的是,无法从一个语法树节点获取其父节点。
*/
    ...
// TraverseXXXStmt
在自己实现的Visitor中(例如MyASTVisitor),除了可以提供VisitXXXStmt系列接口去访问某类型的语法树节点外,还可以提供TraverseXXXStmt系列接口。Traverse系列的接口包装对应的Visit接口,即他们的关系大致为:
/*
bool TraverseGotoStmt(GotoStmt *s) {
    VisitGotoStmt(s);
    return true;
}
*/
// 例如对于GotoStmt节点而言,Clang会先调用TraverseGotoStmt,在TraverseGotoStmt的实现中才会调用VisitGotoStmt。利用Traverse和Visit之间的调用关系,我们可以解决一些因为不能访问某节点父节点而出现的问题。例如,我们需要限制逗号表达式的使用,在任何地方一旦检测到逗号表达式的出现,都给予警告,除非这个逗号表达式出现在for语句中
/*
a = (a = 1, b = 2);                    // 违反规范,非法 
for (a = 1, b = 2; a < 2; ++a)   // 合法
*/
// 逗号表达式对应的访问接口为VisitBinComma,所以我们只需要提供该接口的实现即可
    bool VisitBinComma(BinaryOperator *stmt) { 
    // BinaryOperator用于表示二目运算表达式,例如a + b,逗号表达式也是二目表达式
        /* 报告错误 */
        return true;
    }
// 但在循环中出现的逗号表达式也会调用到VisitBinComma。为了有效区分该逗号表达式是否出现在for语句中,我们可以期望获取该逗号表达式的父节点,并检查该父节点是否为for语句。但Clang并没有提供这样的能力,我想很大一部分原因在于臆测语法树(抽象语法树)节点的组织结构(父节点、兄弟节点)本身就不是一个确定的事。
// 这里的解决办法是通过提供TraverseForStmt,以在进入for语句前得到一个标识:

    // 这个函数的实现可以参考RecursiveASTVisitor的默认实现,我们唯一要做的就是在for语句的头那设定一个标志m_inForLine
    bool TraverseForStmt(ForStmt *s) {
        if (!WalkUpFromForStmt(s))
            return false;
        m_inForLine = true;
        for (Stmt::child_range range = s->children(); range; ++range) {
            if (*range == s->getBody())
                m_inForLine = false;
                // 注:严格来说,我们必须检查逗号表达式是出现在for语句的头中,而不包括for语句循环体
            TraverseStmt(*range);
        }
        return true;
    }



};

class MyASTConsumer : public ASTConsumer {
public:
    virtual bool HandleTopLevelDecl(DeclGroupRef DR) {
        for (DeclGroupRef::iterator b = DR.begin(), e = DR.end(); b != e; ++b) {
            Visitor.TraverseDecl(*b);
        }
        return true;
    } 
    
private:
    MyASTVisitor Visitor;
};

int main(int argc, char **argv) {
    CompilerInstance inst;
    Rewriter writer;
    inst.createFileManager();
    inst.createSourceManager(inst.getFileManager());
    inst.createPreprocessor();
    inst.createASTContext();
    writer.setSourceMgr(inst.getSourceManager(), inst.getLangOpts());
    ... // 其他初始化CompilerInstance的代码
  
    const FileEntry *fileIn = fileMgr.getFile(argv[1]);
    sourceMgr.createMainFileID(fileIn);
    inst.getDiagnosticClient().BeginSourceFile(inst.getLangOpts(), &inst.getPreprocessor());
    MyASTConsumer consumer(writer);
    ParseAST(inst.getPreprocessor(), &consumer, inst.getASTContext());
    inst.getDiagnosticClient().EndSourceFile();
    return 0;
}
// ParseAST为Clang开始分析代码的主入口,其中提供了一个ASTConsumer。每次分析到一个顶层定义时(Top level decl)就会回调MyASTConsumer::HandleTopLevelDecl,该函数的实现里调用MyASTVisitor开始递归访问该节点。这里的decl实际上包含定义。

类型信息

对于表达式(Expr)而言,都有一个类型信息。Clang直接用于表示类型的类是QualType,实际上这个类只是一个接口包装。这些类型信息可以用于很多类型相关的编程规范检查。例如不允许定义超过2级的指针(例如int ***p):

bool MyASTVisitor::VisitVarDecl(VarDecl *decl) { // 当发现变量定义时该接口被调用
    QualType t = decl->getType(); // 取得该变量的类型
    int pdepth = 0;
    // check pointer level
    for ( ; t->isPointerType(); t = t->getPointeeType()) { // 如果是指针类型就获取其指向类型(PointeeType)
        ++pdepth; // 指针类型计数
    }
    if (pdepth >= 3)
        /* 报告错误 */
}

可以直接调用Expr::getType接口,用于获取指定表达式最终的类型,基于此我们可以检查复杂表达式中的类型转换:

float f = 2.0f;
double d = 1.0;
f = d * f; /* 检查此表达式 */

对以上表达式的检查有很多方法,你可以实现MyASTVisitor::VisitBinaryOperator(只要是二目运算符都会调用),或者MyASTVisitor::VisitBinAssign(赋值运算=调用)。无论哪种方式,我们都可以提供一个递归检查两个表达式类型是否相同的接口:

bool HasDiffType(BinaryOperator *stmt) {
    Expr *lhs = stmt->getLHS()->IgnoreImpCasts(); // 忽略隐式转换
    Expr *rhs = stmt->getRHS()->IgnoreImpCasts();
    if (lhs->getType() == rhs->getType())) {
        if (isa<BinaryOperator>(lhs) && HasDiffType(cast<BinaryOperator>(lhs)))
            return true;
        if (isa<BinaryOperator>(rhs) && HasDiffType(cast<BinaryOperator>(rhs)))
            return true;
        return false;
    }
    return true;
}
// (注:此函数只是简单实现,未考虑类型修饰符之类的问题)
// 该函数获得二目运算表达式的两个子表达式,然后递归检测这两个表达式的类型是否相同。

死循环
Expr类提供了更多方便的类型相关的接口,例如判定该表达式是否为常数,是否是布尔表达式,甚至在某些情况下可以直接计算得到值。例如我们可以检查明显的死循环:

while (1) { }

可以使用:

ASTContext &context = inst.GetASTContext();
bool result;
// 假设stmt为WhileStmt
if (stmt->getCond()->EvaluateAsBooleanCondition(result, context)) {
    if (result) 
        /* 死循环 */

符号表 全局变量 类型

符号表这个概念比较广义,这里我仅指的是用于保存类型和变量信息的表。Clang中没有显示的符号表数据结构,但每一个定义都有一个DeclContext,DeclContext用于描述一个定义的上下文环境。有一个特殊的DeclContext被称为translation unit decl,其实也就是全局环境。利用这个translation unit decl,我们可以获取一些全局符号,例如全局变量、全局类型:


// 获取全局作用域里指定名字的符号列表
DeclContext::lookup_result GetGlobalDecl(const std::string &name) {
    ASTContext &context = CompilerInst::getSingleton().GetASTContext();
    DeclContext *tcxt = context.getTranslationUnitDecl();
    IdentifierInfo &id = context.Idents.get(name);
    return tcxt->lookup(DeclarationName(&id));
}

// 可以根据GetGlobalDecl的返回结果,检查该列表里是否有特定的定义,例如函数定义、类型定义等
bool HasSpecDecl(DeclContext::lookup_result ret, Decl::Kind kind) {
    for (size_t i = 0; i < ret.size(); ++i) {
        NamedDecl *decl = ret[i];
        if (decl->getKind() == kind) {
            return true;
        }
    }
    return false;
}

有了以上两个函数,我们要检测全局作用域里是否有名为”var”的变量定义,就可以:

HasSpecDecl(GetGlobalDecl("var"), Decl::Var);

每一个Decl都有对应的DeclContext,要检查相同作用域是否包含相同名字的符号,其处理方式和全局的方式有点不一样:

// 检查在ctx中是否有与decl同名的符号定义
bool HasSymbolInContext(const NamedDecl *decl, const DeclContext *ctx) {
    for (DeclContext::decl_iterator it = ctx->decls_begin(); it != ctx->decls_end(); ++it) {
        Decl *d = *it;
        if (d != decl && isa<NamedDecl>(d) && 
            cast<NamedDecl>(d)->getNameAsString() == decl->getNameAsString())
            return true;
    }
    return false;
}

bool HasSymbolInContext(const NamedDecl *decl) {
    return HasSymbolInContext(decl, decl->getDeclContext());
}

宏处理

宏的处理属于预处理阶段,并不涵盖在语法分析阶段,所以通过Clang的语法树相关接口是无法处理的。跟宏相关的接口,都是通过Clang的Preprocessor相关接口。Clang为此提供了相应的处理机制,上层需要往Preprocessor对象中添加回调对象,例如:

class MyPPCallback : public PPCallbacks {
// 通过实现PPCallbacks中对应的接口,就可以获得处理宏的通知。
public:
    // 处理#include
    virtual void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
        StringRef FileName, bool IsAngled, CharSourceRange FilenameRange,
        const FileEntry *File, StringRef SearchPath, StringRef RelativePath, const Module *Imported) {
    }

    // 处理#define
    virtual void MacroDefined(const Token &MacroNameTok, const MacroInfo *MI) {
    }

    virtual void MacroUndefined(const Token &MacroNameTok, const MacroInfo *MI) {
    } 
}

inst.getPreprocessor().addPPCallbacks(new MyPPCallback());

Clang使用MacroInfo去表示一个宏。MacroInfo将宏体以一堆token来保存,例如我们要检测宏体中使用##和#的情况,则只能遍历这些tokens:

// 分别记录#和##在宏体中使用的数量
int hash = 0, hashhash = 0;
for (MacroInfo::tokens_iterator it = MI->tokens_begin(); it != MI->tokens_end(); ++it) {
    const Token &token = *it;
    hash += (token.getKind() == tok::hash ? 1 : 0);
    hashhash += (token.getKind() == tok::hashhash ? 1 : 0);
}

手工解析

在我们所支持的编程规范中,有些规范是难以支持的,因此我使用了一些蹩脚的方式来实现。

在针对函数的参数定义方面,我们支持的规范要求不能定义参数为空的函数,如果该函数没有参数,则必须以void显示标识:

int func(); /* 非法 */
int func(void); /* 合法 */

对于Clang而言,函数定义(或声明)使用的是FunctionDecl,而Clang记录的信息仅包括该函数是否有参数,参数个数是多少,并不记录当其参数个数为0时是否使用void来声明(记录下来没多大意义)。解决这个问题的办法,可以通过SourceLocation获取到对应源代码中的文本内容,然后对此文本内容做手工分析即可。

(注:SourceLocation是Clang中用于表示源代码位置的类,包括行号和列号,所有Stmt都会包含此信息)

通过SourceLocation获取对应源码的内容:

std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
bool invalidTemp = false;
llvm::StringRef file = SM.getBufferData(locInfo.first, &invalidTemp);
if (invalidTemp)
    return false;
// tokenBegin即为loc对应源码内容的起始点
const char *tokenBegin = file.data() + locInfo.second;

要手工分析这些内容实际上还是有点繁杂,为此我们可以直接使用Clang中词法分析相关的组件来完成这件事:

Lexer *lexer = new Lexer(SM.getLocForStartOfFile(locInfo.first), opts, file.begin(), tokenBegin, file.end());
Token tok;
lexer->Lex(tok); // 取得第一个tok,反复调用可以获取一段token流

Diagnostic 诊断 警告wing 错误error

Clang中用Diagnostic来进行编译错误的提示。每一个编译错误(警告、建议等)都会有一段文字描述,这些文字描述为了支持多国语言,使用了一种ID的表示方法。总之,对于一个特定的编译错误提示而言,其diagnostic ID是固定的。

在我们的规范中,有些规范检测的代码在Clang中会直接编译出错,例如函数调用传递的参数个数不等于函数定义时的形参个数。当Clang编译出错时,其语法树实际上是不完善的。解决此问题的最简单办法,就是通过diagnostic实现。也就是说,我是通过将我们的特定规范映射到特定的diagnostic,当发生这个特定的编译错误时,就可以认定该规范实际上被检测到。对于简单的情况而言,这样的手段还算奏效。

// `TextDiagnosticPrinter`可以将错误信息打印在控制台上,为了调试方便我从它派生而来
class MyDiagnosticConsumer : public TextDiagnosticPrinter {
public:
    // 当一个错误发生时,会调用此函数,我会在这个函数里通过Info.getID()取得Diagnostic ID,然后对应地取出规范ID
    virtual void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
        const Diagnostic &Info) {
        TextDiagnosticPrinter::HandleDiagnostic(DiagLevel, Info);
        // 例如检查三字母词(trigraph)的使用
        if (Info.getID() == 816)
            /* 报告使用了三字母词 */
    }
};

// 初始化时需传入自己定义的diagnostic
inst.createDiagnostics(0, NULL, new MyDiagnosticConsumer(&inst.getDiagnosticOpts()));

中后端 LLVM 介绍

LLVM源码目录介绍

llvm-project/llvm/ 为LLVM源码,llvm-project/clang为c类语言编译器前端,本文主要介绍这两个工程,其他为相关的工具和应用。

LLVM 源码工程目录介绍

llvm/examples - 使用 LLVM IR 和 JIT 的例子。
llvm/include - 导出的头文件。
llvm/lib - 主要源文件都在这里。
llvm/project - 创建自己基于 LLVM 的项目的目录。
llvm/test - 基于 LLVM 的回归测试,健全检察。
llvm/suite - 正确性,性能和基准测试套件。
llvm/tools - 基于 lib 构建的可以执行文件,用户通过这些程序进行交互,-help 可以查看各个工具详细使用。
llvm/utils - LLVM 源代码的实用工具,比如,查找 LLC 和 LLI 生成代码差异工具, Vim 或 Emacs 的语法高亮工具等。
llvm/unittests - 基于gtest测试框架对llvm进行的单元测试

lib 目录介绍

llvm/lib/IR/ - 中端表示,这些文件实现了很多核心的类(class),比如:Instruction指令类,BasicBlock基本块类,函数Func类。
llvm/lib/AsmParser/ - 汇编语言解析器。
llvm/lib/Bitcode/ - 读取和写入字节码
llvm/lib/Analysis/ - 各种对程序的分析,比如 Call Graphs,Induction Variables,Natural Loop Identification ,对其别名分析、相关性分析、常量折叠(Constant folding)、循环信息、内存依赖分析、指令化简等
llvm/lib/Passes/ 各种pass优化
llvm/lib/Transforms/ - IR-to-IR 程序的变换,IR的转换程序,比如:Aggressive Dead Code Elimination(死代码去除), Sparse Conditional Constant Propagation, Inlining, Loop Invariant Code Motion, Dead Global Elimination(主动死代码消除,稀疏有条件常量传播,内联,循环不变码变化,死全局消除…)。
llvm/lib/Target/ - 对像 X86 这样机器的描述,由通用目标及抽象提供对目标机的访问。这些通用的抽象提供了libLLVMCodeGen中通用的后端算法和目标相关逻辑间的通讯途径。包含了已经支持的各平台libLLVMxxxxCodeGen(xxxx表示具体支持的平台,如X86等):具体平台相关的代码生成、转换和分析Pass,其是相关平台的后端。
llvm/lib/CodeGen/ - 主要是代码生成,指令选择器,指令调度和寄存器分配(Instruction Selector, Instruction Scheduling, and Register Allocation),目标无关的代码生成、低级LLVM IR(机器相关)分析和转换
llvm/lib/ExecutionEngine/ - 在解释执行和JIT编译场景能够直接在运行时执行字节码的库。
其他
llvm/lib/Support LLVM的支持库:错误处理、整形和浮点处理、命令行解析、调试、文件支持、字符串操作等;

LLVM 编码参考 开发者手册

API介绍

cast类型相关

源码

1. isa<>: isa<具体类型>(变量名) 判断该变量是否是某个具体类型

isa<>操作符的工作原理与Java “instanceof”操作符完全相同。它返回true或false,这取决于引用或指针是否指向指定类的实例。这对于各种类型的约束检查非常有用

2.cast<>: 已知类型的强制转换

cast<>操作符是一个“已检查的强制转换”操作。它将指针或引用从基类转换为派生类,如果不是正确类型的实例,则会导致断言失败。当你有一些信息使你相信某样东西是正确的类型时,应该使用这种方法。isa<> 和 cast<> 模板的一个例子是:

static bool isLoopInvariant(const Value *V, const Loop *L) {
  if (isa<Constant>(V) || isa<Argument>(V) || isa<GlobalValue>(V))  // V是常量/参数/全局变量?
    return true;

  // Otherwise, it must be an instruction...  否则V是指令
  return !L->contains(cast<Instruction>(V)->getParent());  // 将基类Value类型的V转换cast成派生类类型的 Instruction
}

3.dyn_cast<>:未知类型的强制转换

dyn_cast<>操作符是一个“检查转换”操作。它检查操作数是否属于指定的类型,如果是,则返回指向它的指针(该操作符不与引用一起工作)。如果操作数类型不正确,则返回空指针。因此,这与c++中的dynamic_cast<>操作符非常相似,应该在相同的环境中使用。通常,dyn_cast<>操作符用于if语句或其他类似的流控制语句中:

if (auto *AI = dyn_cast<AllocationInst>(Val)) { // 如果Val可以转换成 AllocationInst类型则if语句成立,否则返回NULL,if语句不成立
  // ...
}

这种形式的if语句有效地将对 isa<> 的调用和对 cast<> 的调用组合到一个语句中,这非常方便。
注意,dyn_cast<>操作符可以被滥用,就像c++的dynamic_cast<>或Java的instanceof操作符一样。特别是,不应该使用大的if/then/else块来检查类的许多不同变体。如果您发现自己想这样做,那么使用 InstVisitor 类直接分派指令类型会更清晰、更有效。

4.cast_or_null<>

cast_or_null<>操作符的工作原理与 cast<>操作符类似,只是它允许一个空指针作为参数(然后将其传播)。这有时很有用,允许您将多个null检查合并到一个检查中。

5.dyn_cast_or_null<>

dyn_cast_or_null<>操作符的工作原理与 dyn_cast<> 操作符类似,只是它允许一个空指针作为参数(然后将其传播)。这有时很有用,允许您将多个null检查合并到一个检查中。

传递字符串(StringRef和Twine类)

虽然LLVM通常不做太多字符串操作,但是我们有几个重要的APIs接受字符串。两个重要的例子是 Value 类(它有指令、函数等的名称)和 StringMap 类(在 LLVM 和 Clang 中广泛使用)。

这些是泛型类,它们需要能够接受可能包含空字符的字符串。因此,它们不能简单地接受const char *,而接受const std::string&要求客户机执行堆分配,这通常是不必要的。代替的是,许多LLVM APIs使用StringRef或const twine&来有效地传递字符串。

1.StringRef类 llvm字符串引用类

StringRef数据类型表示对常量字符串(一个字符数组和一个长度)的引用,并支持std::string上可用的公共操作,但不需要堆分配。
它可以使用一个C风格的以null结尾的字符串、一个std::string隐式地被造,也可以使用一个字符指针和长度显式地构造。例如,StringRef find函数声明为:

iterator find(StringRef Key);

client可以用以下任意一种方式调用这个函数:Map是 StringMap 类对象

Map.find("foo");                 // Lookup "foo"    C风格的以null结尾的字符串构造 StringRef 
Map.find(std::string("bar"));    // Lookup "bar"    C风格的以null结尾的字符串构造 std::string 再构造成 StringRef 
Map.find(StringRef("\0baz", 4)); // Lookup "\0baz"  一个字符指针 和 长度   显式地构造 StringRef 

类似地,需要返回string的APIs可能会返回一个StringRef实例,该实例可以直接使用,也可以使用str成员函数将其转换为std::string。有关更多信息,请查看 llvm/ADT/StringRef.h

您应该很少直接使用StringRef类,因为它包含指向外部内存的指针,所以存储该类的实例通常是不安全的(除非您知道不会释放外部存储)。StringRef在 LLVM 中足够小和普遍,因此它应该总是通过值传递。

2. Twine 字符串连接

Twine类是 APIs 接受连接字符串的有效方法。例如,一个常见的LLVM范型是根据带有后缀的另一条指令的名称来命名一条指令。

Twine类实际上是一个轻量级的rope,它指向临时(分配给栈的)对象。Twine可以隐式地构造为加运算符应用于字符串的结果(即,一个C字符串,一个std::string,或者一个StringRef)。Twine会延迟字符串的实际连接,直到实际需要它时,才会有效地将其直接呈现到字符数组中。这避免了在构造字符串连接的临时结果时涉及的不必要的堆分配。有关更多信息,请查看llvm/ADT/Twine.h和这里。
与StringRef一样,Twine对象指向外部内存,并且几乎不应该直接存储或提及。它们仅用于在定义一个应该能够有效接受连接字符串的函数时使用。

格式化字符串(formatv函数)

虽然LLVM不一定要做很多字符串操作和解析,但它确实做了很多字符串格式化。从诊断消息,到llvm工具输出(如llvm-readobj),再到打印详细的分解清单和LLDB运行时日志,字符串格式化的需求无处不在。

formatv在本质上类似于printf,但是使用了另一种语法,这种语法大量借鉴了Python和c#。与printf不同,它推断要在编译时格式化的类型,因此不需要%d之类的格式说明符。这减少了构造可移植格式字符串的脑力开销,特别是对于size_t或指针类型等特定于平台的类型。与printf和Python不同的是,如果LLVM不知道如何格式化类型,它还不能编译。这两个属性确保函数比传统的格式化方法(如printf函数族)更安全,使用起来也更简单。

简单格式化

formatv调用涉及一个由0个或多个替换序列组成的格式字符串,然后是替换值的一个可变长度列表。一个替换序列是一个形式为{N[[,align]:style]}的字符串。 {列表中的第一个元素,可选对齐:风格}

N表示替换值列表中参数的基于0的索引。注意,这意味着可以以任何顺序多次引用相同的参数,可能使用不同的样式和/或对齐选项。
align是一个可选字符串,指定要将值格式化为的字段的宽度,以及字段内值的对齐方式。它被指定为一个可选的对齐样式,后跟一个正整数字段宽度。对齐样式可以是字符-(左对齐)、=(中对齐)或+(右对齐)中的一个。默认值是右对齐的。

style是一个可选字符串,由控制值格式的特定类型组成。例如,要将浮点值格式化为百分比,可以使用样式选项P。

std::string S;
// 基本类型的简单格式化和隐式字符串转换。
S = formatv("{0} ({1:P})", 7, 0.35);  // S == "7 (35.00%)"  // P 是格式 percent 百分比格式  N十进制  X十六进制 E科学计数法

// 无序引用和多引用
outs() << formatv("{0} {2} {1} {0}", 1, "test", 3); // prints "1 3 test 1"

// 左、右、中对齐  align
S = formatv("{0,7}",  'a');  // S == "      a";
S = formatv("{0,-7}", 'a');  // S == "a      "; // -号左对齐
S = formatv("{0,=7}", 'a');  // S == "   a   ";
S = formatv("{0,+7}", 'a');  // S == "      a";

// 自定义样式  style
S = formatv("{0:N} - {0:x} - {1:E}", 12345, 123908342); // S == "12,345 - 0x3039 - 1.24E8"

// Adapters
S = formatv("{0}", fmt_align(42, AlignStyle::Center, 7));  // S == "  42   "  对齐格式fmt_align函数
S = formatv("{0}", fmt_repeat("hi", 3)); // S == "hihihi"                     重复函数 fmt_repeat
S = formatv("{0}", fmt_pad("hi", 2, 6)); // S == "  hi      "                 填充函数 fmt_pad  

// Ranges 范围填充
std::vector<int> V = {8, 9, 10};
S = formatv("{0}", make_range(V.begin(), V.end())); // S == "8, 9, 10"
S = formatv("{0:$[+]}", make_range(V.begin(), V.end())); // S == "8+9+10"
S = formatv("{0:$[ + ]@[x]}", make_range(V.begin(), V.end())); // S == "0x8 + 0x9 + 0xA"

错误处理

正确的错误处理帮助我们识别代码中的错误,并帮助最终用户理解他们的工具使用中的错误。错误可以分为两大类:编程错误和可恢复性错误,它们具有不同的处理和报告策略

编程错误 断言assert llvm_unaccessible

  1. 断言用于表示不变条件,并且应该包含描述不变条件的消息:
assert(isPhysReg(R) && "All virt regs should have been allocated already.");

llvm_unaccessible 函数可用于记录控制流的区域,如果程序不变量保持:

enum { Foo, Bar, Baz } X = foo();

switch (X) {
  case Foo: /* Handle Foo */; break;
  case Bar: /* Handle Bar */; break;
  default:
    llvm_unreachable("X should be Foo or Bar here");
}

可恢复性错误

可恢复错误表示程序环境中的错误,例如资源故障(丢失的文件、丢失的网络连接等)或格式错误的输入。应该检测这些错误,并将其传达给程序的某个级别,以便对其进行适当处理。处理错误可能与向用户报告问题一样简单,也可能涉及尝试恢复。

可恢复错误使用LLVM的错误模式建模。这个方案使用函数返回值表示错误,类似于经典的C整数错误代码,或者c++的std::error_code。然而,Error类实际上是用户定义错误类型的轻量级包装器,允许附加任意信息来描述错误。这类似于c++异常允许抛出用户定义类型的方式。
成功值是通过调用Error:: Success()创建的

传递函数和其他可调用对象

为任务选择正确的数据结构

LLVM /ADT/目录中有大量的数据结构,我们通常使用STL数据结构。本节描述在选择时应该考虑的权衡。
第一步是选择您自己的冒险:您想要顺序容器、类似于集合的容器还是类似于映射的容器?在选择容器时,最重要的是计划如何访问容器的算法属性。基于此,你应该使用:

如果需要基于另一个值高效地查找值,则使用类似于映射的容器。类映射容器还支持有效的包含查询(无论键是否在映射中)。类映射容器通常不支持有效的反向映射(值到键)。如果需要,可以使用两个映射。一些类似于映射的容器还支持按顺序高效地遍历键。类映射容器是最昂贵的一种,只有在需要这些功能之一时才使用它们。
如果您需要将一堆东西放入一个容器中,这个容器可以自动消除重复。一些类似集合的容器支持按排序顺序对元素进行有效的迭代。类集合容器比顺序容器更昂贵。
顺序容器提供了最有效的方法来添加元素,并跟踪它们添加到集合中的顺序。它们允许复制并支持有效的迭代,但不支持基于键的高效查找。
字符串容器是用于字符或字节数组的专用顺序容器或引用结构。

位容器提供了一种有效的方法来存储和执行数字id集上的set操作,同时自动消除重复。要存储的每个标识符,位容器最多需要1位。
一旦确定了容器的适当类别,您就可以通过明智地选择类别中的成员来微调内存使用、常量因素和缓存访问行为。请注意,常量因素和缓存行为可能很重要。例如,如果您有一个向量,它通常只包含几个元素(但是可以包含许多元素),那么使用SmallVector比使用vector要好得多。这样做可以避免(相对)昂贵的malloc/free调用,这大大降低了向容器添加元素的成本。

顺序容器Sequential Containers (std::vector, std::list, etc)

根据您的需要,可以使用各种顺序容器。在本节中选择第一个可以做您想做的事情。

1.数据引用lvm/ADT/ArrayRef.h
llvm::ArrayRef类是接口中使用的首选类,该接口接受内存中元素的顺序列表并从其中读取数据。通过使用ArrayRef,可以向API传递一个固定大小的数组、一个std::vector、一个llvm::SmallVector以及内存中任何相邻的内容。

2.Fixed Size Arrays固定大小数组
固定大小的数组非常简单和快速。如果您确切地知道您有多少个元素,或者您对您有多少个元素有一个(低)上限,那么它们是很好的。

3.Heap Allocated Arrays堆分配数组
堆分配数组(new[] + delete[])也很简单。如果元素的数量是可变的,如果您知道在分配数组之前需要多少元素,如果数组通常很大(如果不是,请考虑一个小向量),那么它们是很有用的。堆分配数组的成本是new/delete(又名malloc/free)的成本。还请注意,如果使用构造函数分配类型的数组,则将为数组中的每个元素运行构造函数和析构函数(重新调整大小的向量只构造实际使用的元素)。

4.llvm/ADT/SmallVector.h简化vecot 小向量
是一个看起来和闻起来都像vector的简单类:它支持高效的迭代,以内存顺序排列元素(这样您就可以在元素之间执行指针算术),支持高效的push_back/pop_back操作,支持对其元素的高效随机访问,等等。

SmallVector的主要优点是它为对象本身中的一些元素(N)分配了空间。因此,如果小向量动态地小于N,则不执行malloc。如果malloc/free调用比处理元素的代码昂贵得多,那么这将是一个巨大的胜利。

这是有利于向量”通常小”(如前辈的数量/继任者的一块通常小于8)。另一方面,这使得SmallVector本身的尺寸大,所以你不想分配很多(这样做会浪费很多空间)。因此,在堆栈上使用小向量是最有用的。

SmallVector还为alloca提供了一个很好的可移植性和高效的替代品。

SmallString是SmallVector的子类,它添加了一些方便的api,比如+=,它接受StringRef的api。SmallString避免在预分配的空间足够容纳其数据时分配内存,并且在需要时回调一般堆分配。因为它拥有自己的数据,所以使用它非常安全,并且支持字符串的完全变异。

和SmallVector一样,SmallString的最大缺点是它们的sizeof。虽然它们针对小字符串进行了优化,但它们本身并不特别小。这意味着它们对于堆栈上的临时刮擦缓冲区非常有效,但通常不应该放到堆中:很少看到SmallString作为频繁分配的堆数据结构的成员或按值返回。

Set-Like Containers 集合类容器 (std::set, SmallSet, SetVector, etc)*

Map-Like Containers (std::map, DenseMap, etc) 字典映射类容器*

Bit storage containers 位存储容器(BitVector, SparseBitVector)*

遍历程序

A. module类简介

Module类表示LLVM程序中的顶层结构。
一个LLVM Module实际上要么是原始程序的一个翻译单元,要么是链接器合并的几个翻译单元的一个组合。Module 模块包含 Functions列表 + GlobalVariables全局变量列 + SymbolTable符号表

1.一个Functions列表

Module::FunctionListType &getFunctionList()
返回Function列表。当您需要更新列表或执行没有转发方法的复杂操作时,这是必需的。

2.一个GlobalVariables全局变量列表

Module::global_iterator —— 全局变量列表iterator的类型定义
Module::const_global_iterator —— const_iterator的类型定义。
global_begin(), global_end(), global_size(), global_empty()
Module::GlobalListType &getGlobalList()
返回GlobalVariables列表。当您需要更新列表或执行没有转发方法的复杂操作时,这是必需的。

3.一个SymbolTable符号表。此外,它还包含一些有用的成员函数,这些函数试图简化常见操作。

SymbolTable *getSymbolTable()
返回对这个Module的SymbolTable的一个引用。

Function *getFunction(StringRef Name) const
在Module SymbolTable中查找指定的函数。如果不存在,返回null。

FunctionCallee getOrInsertFunction(const std::string &Name, const FunctionType *T)
在Module SymbolTable中查找指定的函数。如果它不存在,则为函数添加一个外部声明并返回它。注意,已经存在的函数签名可能与请求的签名不匹配。因此,为了支持将结果直接传递给EmitCall的常见用法,
返回类型是{FunctionType *T, Constant *FunctionPtr}的一个结构体,而不是具有潜在的意外签名的简单Function*。

std::string getTypeName(const Type *Ty)
如果指定Type的SymbolTable中至少有一个条目,则返回它。否则返回空字符串。

bool addTypeName(const std::string &Name, const Type *Ty)
在将Name映射到Ty的SymbolTable中插入一个条目。如果已经有该名称的条目,则返回true,并且不修改SymbolTable。


a. 函数类 FunctionType

Function类表示LLVM中的一个过程。它实际上是LLVM层次结构中比较复杂的类之一,因为它必须跟踪大量数据。Function类跟踪基本块列表BBtable、形式参数列表param list和符号表SymbolTable。

基本块basic block table列表是函数对象中最常用的部分。该列表强制函数中块的隐式排序,这指示代码将如何由后端布局。此外,第一个基本块是函数的隐式入口节点。在LLVM中显式地分支到这个初始块是不合法的。不存在隐式的退出节点,实际上一个函数可能有多个退出节点。

如果BasicBlock列表是空的,这表明函数实际上是一个函数声明:函数的实际主体还没有被链接进来。

除了基本块列表之外,函数类还跟踪函数接收到的形式参数列表。这个容器管理参数节点的生存期,就像BasicBlock列表管理BasicBlock一样。

SymbolTable是一个很少使用的LLVM特性,只在必须按名称查找值时才使用。除此之外,符号表还用于内部,以确保函数体中指令、基本块或参数的名称之间没有冲突。

注意,函数是一个全局值,因此也是一个常量。函数的值是它的地址(链接后),它保证是常量。

DerivedTypes 的子类,用于function类型。
bool isVarArg() cons:                  如果它是一个vararg函数,则返回true。
const Type * getReturnType() const:    返回函数的返回类型。
const Type * getParamType (unsigned i):返回第i个参数的类型。  参数名字在哪里??
const unsigned getNumParams() const:   返回形式参数的数量。
FunctionType *getFunctionType():       返回函数类型

Function(const FunctionType *Ty, LinkageTypes Linkage, const std::string &N = "", Module* Parent = 0)
//构造函数,用于在需要创建新函数来添加程序时使用。构造函数必须指定要创建的函数的类型以及函数应该具有哪种类型的链接。FunctionType参数指定函数的形式参数和返回值。同一个FunctionType值可用于创建多个函数。父参数指定定义函数的模块。如果提供了这个参数,函数将自动插入该模块的函数列表中。

bool isDeclaration ()
//返回函数是否定义了主体。如果函数是“外部的”,那么它就没有主体,因此必须通过与在不同翻译单元中定义的函数链接来解决。

Function::iterator —— 基本块列表迭代器的类型定义
Function::const_iterator —— const_iterator的类型定义。
begin(), end(), size(), empty()
这些转发方法使访问函数对象的BasicBlock列表的内容变得很容易。

Function::BasicBlockListType &getBasicBlockList()
//返回BasicBlock列表。当您需要更新列表或执行没有转发方法的复杂操作时,这是必需的。

Function::arg_iterator —— 参数列表iterator的类型定义
Function::const_arg_iterator —— const_iterator的类型定义。
arg_begin(), arg_end(), arg_size(), arg_empty()
//这些转发方法使访问函数对象的参数列表的内容变得很容易。

Function::ArgumentListType &getArgumentList()
//返回参数列表。当您需要更新列表或执行没有转发方法的复杂操作时,这是必需的。

BasicBlock &getEntryBlock()
//返回函数的入口BasicBlock。因为函数的入口块总是第一个块,所以它返回Function的第一个块。

Type *getReturnType()
FunctionType *getFunctionType()
//它遍历Function的Type并返回函数的返回类型,或实际函数的FunctionType。

SymbolTable *getSymbolTable()
//返回指向此函数的SymbolTable的指针。


b. BasicBlock类

该类表示代码的单个入口和单个出口部分,编译器社区通常将其称为基本块。

BasicBlock类维护一个Instructions指令列表,这些指令构成了块的主体。与语言定义匹配,此指令列表的最后一个元素始终是一个终止符指令。

除了跟踪组成块的指令列表外,BasicBlock类还跟踪它所嵌入的Function(调用的外部函数 函数调用指令)。

注意,BasicBlocks本身是Values,因为它们由branches之类的指令引用,所以可以放在switch表中。BasicBlocks有类型label。

BasicBlock类的重要Public成员


BasicBlock(const std::string &Name = "", Function *Parent = 0)

//BasicBlock构造函数用于创建用于插入函数的新基本块。构造函数可选地接受新块的一个名称和将其插入其中的一个Function。
//如果指定了Parent参数,则在指定Function的末尾自动插入新的BasicBlock;如果没有指定,则必须手动将BasicBlock插入Function。

BasicBlock::iterator —— 指令列表iterator的类型定义
BasicBlock::const_iterator —— const_iterator的类型定义。
用于访问指令列表的begin(), end(), front(), back(), size(), empty() STL样式函数。

这些方法和typedefs是转发函数,它们具有与相同名称的标准库方法相同的语义。这些方法以易于操作的方式公开基本块的底层指令列表。要获得完整的容器操作(包括更新列表的操作),必须使用getInstList()方法。

BasicBlock::InstListType &getInstList()
此方法用于访问实际包含指令的底层容器。当BasicBlock类中没有要执行的操作的转发函数时,必须使用此方法。因为没有用于“更新”操作的转发函数,所以如果想更新BasicBlock的内容,就需要使用这个函数。

Function *getParent()
返回一个指针,它指向这个块所嵌套的Function,或者返回一个空指针(如果它是无家可归的)。

Instruction *getTerminator()
返回一个指向出现在BasicBlock末尾的终止符指令的指针。如果没有终止符指令,或者如果块中的最后一条指令不是终止符,则返回一个空指针。

c. Argument类 函数参数类

这个Value的子类为函数的传入形式参数定义接口。一个函数维护其一个形式参数的列表。一个参数有一个指向父Function的指针。

d. Value类 包含了我被哪些指令(LLVM User)使用

Value类是LLVM源库中最重要的类。它表示一个类型化值,可以(除其他外)用作一条指令的操作数。有许多不同类型的Values,比如常量、参数。甚至指令和函数也是Values。

一个特定的Value可以在程序的LLVM表示中多次使用。例如,一个函数的一个传入参数(用Argument类的一个实例表示)被引用该参数的函数中的每条指令“使用”。为了跟踪这种关系,Value类保存了使用它的所有Users的一个列表(User类是LLVM图中所有可以引用Values的节点的基类)。这个use列表是LLVM在程序中表示def-use信息的方式,并且可以通过use_*方法访问,如下所示。

因为LLVM是一个类型化表示,所以每个LLVM Value都是类型化的,并且这种Type可以通过 getType() 方法获得。此外,所有LLVM values都可以被命名。Value的“name”是可在LLVM代码中打印的一个符号字符串:
%foo = add i32 1, 2

这个指令的名称是“foo”。注意,任何值的名称都可能丢失(一个空字符串),所以名称应该只用于调试(使源代码更容易阅读,调试打印输出),不应该用于跟踪值或在它们之间映射。为此,使用指向这个Value本身的一个std::map代替。

LLVM的一个重要方面是,SSA变量(静态单赋值 三地址)和生成它的操作之间没有区别。因此,任何对指令生成的值的引用(例如,作为传入参数可用的值)都表示为指向表示该值的类实例的直接指针。虽然这可能需要一些时间来适应,但它简化了表示,使操作更容易。

Value::use_iterator       —— use-list上的iterator的类型定义   遍历使用者的迭代器
Value::const_use_iterator —— use-list上的const_iterator的类型定义
unsigned use_size()       —— 返回这个value的users数量。
bool use_empty()          —— 如果没有users,返回true。
use_iterator use_begin()  —— 获取指向use-list的开始的一个迭代器。
use_iterator use_end()    —— 获取指向use-list的结尾的一个迭代器。
User *use_back()          —— 返回列表中的最后一个元素。

这些方法是访问LLVM中的def-use信息的接口。与LLVM中的所有其他iterators一样,命名约定遵循STL定义的约定。

Type *getType() const 这个方法返回Value的Type。
bool hasName() const
std::string getName() const
void setName(const std::string &Name)

void replaceAllUsesWith(Value *V)

此方法遍历一个Value的use列表,它更改当前value的所有Users以引用“V”。例如,如果您检测到一条指令总是产生一个常量值(例如通过常量折叠),您可以像这样用常量替换该指令的所有用法:
Inst->replaceAllUsesWith(ConstVal);


e. User类 包含了我使用了哪些操作数(LLVM Value)

是所有可能 引用Values的LLVM节点 的公共基类。它公开了一个“操作数”列表,这些“操作数”是User引用的所有Values。User类本身是Value的子类。

User的操作数直接指向它引用的LLVM Value。因为LLVM使用静态单赋值(SSA)表单,所以只能引用一个定义,从而允许这种直接连接。这个连接在LLVM中提供use-def信息。

User类以两种方式公开操作数列表:通过一个索引访问接口和一个基于iterator的接口。

Value *getOperand(unsigned i);  // 通过一个索引访问接口
unsigned getNumOperands();
//一个基于iterator的接口访问
User::op_iterator       —— 操作数列表上的iterator的类型定义
op_iterator op_begin()  —— 获取指向操作数列表的开始的一个迭代器。
op_iterator op_end()    —— 获取指向操作数列表的末尾的一个迭代器。
这些方法一起组成了一个User操作数的基于iterator的接口。

f. Instruction 指令类
Instruction类是所有LLVM指令的公共基类。它只提供了几个方法,但是是一个非常常用的类。

Instruction类本身跟踪的主要数据是操作码(指令类型)和嵌入Instruction的 父BasicBlock。

为了表示一个特定类型的指令,使用了众多Instruction子类中的一个。

因为Instruction类是User类的子类,所以可以像访问其他Users一样访问它的操作数
使用 getOperand() / getNumOperands() 和

op_begin() / op_end()方法)。

Instruction类的一个重要文件是llvm/Instruction.def文件。

这个文件包含一些关于LLVM中各种不同类型指令的元数据。

它描述了用作操作码的enum值(例如,Instruction::Add和Instruction::ICmp),以及实现该指令的具体Instruction子类(例如,BinaryOperator和CmpInst)。不幸的是,这个文件中宏的使用混淆了doxygen,所以这些enum值没有正确地显示在doxygen输出中。

g. Instruction类的重要子类

BinaryOperator:这个子类表示所有两个操作数指令,它们的操作数必须是相同的类型,比较指令除外。

CastInst:这个子类是12个casting指令的父类。它提供了对cast指令的通用操作。

CmpInst:这个子类表示两个比较指令,ICmpInst(整数操作数)和FCmpInst(浮点操作数)。

h. Instruction类的重要Public成员


BasicBlock *getParent():返回嵌入该 Instruction 的BasicBlock,返回该指令所属的 基本块。
bool mayWriteToMemory():如果指令(即call、free、invoke或store)写入内存,则返回true。
unsigned getOpcode()   :返回该 Instruction 的操作码。
Instruction *clone() const
返回指定指令的另一个实例,该实例在所有方面与原始指令相同,只是该指令没有parent(即没有嵌入到BasicBlock中),而且没有名称。

i. Constant类和子类

Constant表示不同类型常量的基类。它由 ConstantInt、ConstantArray 等构成子类,用于表示各种类型的Constants。GlobalValue也是一个子类,它表示全局变量或函数的地址。

j. Constant类的重要子类

ConstantInt                  :Constant的子类表示任意宽度的一个整数常量。
const APInt& getValue() const:返回这个常量的底层值,一个APInt值。

int64_t getSExtValue() const :通过符号扩展将底层APInt值转换为int64_t。如果APInt的值(而不是位宽)太大,无法容纳int64_t,则会生成一个断言。由于这个原因,不鼓励使用这种方法。

uint64_t getZExtValue() const:通过zero扩展将底层APInt值转换为uint64_t。如果APInt的值(而不是位宽)太大,无法放入uint64_t中,则会生成一个断言。由于这个原因,不鼓励使用这种方法。

static ConstantInt* get(const APInt& Val):返回代表Val提供的值的ConstantInt对象。该类型被暗示为与Val的位宽相对应的整数类型。

static ConstantInt* get(const Type *Ty, uint64_t Val):返回代表Val为整数类型Ty提供的值的ConstantInt对象。
ConstantFP:这个类表示一个浮点常量。
double getValue() const:返回这个常量的基础值。
ConstantArray          :这表示一个常量数组。
const std::vector<Use> &getValues() const:返回组成这个数组的一个组件常量向量。
ConstantStruct         :这表示一个常量Struct。
const std::vector<Use> &getValues() const:返回组成这个struct的一个组件常量向量。
GlobalValue:它表示一个全局变量或函数。在这两种情况下,值都是一个常量固定地址(链接之后)

k. GlobalValue类 全局变量

GlobalValue(GlobalVariables 或 Functions)是所有函数体中唯一可见的LLVM values。因为它们在全局范围内是可见的,所以它们还受制于与其他在不同翻译单元中定义的全局变量的链接。为了控制链接过程,GlobalValues知道它们的 linkage 规则。具体地说,GlobalValues知道它们是否具有internal或external linkage,这是由LinkageTypes枚举定义的。

如果一个GlobalValue有internal linkage(相当于C语言中的static链接),那么它对于当前翻译单元之外的代码是不可见的,并且不参与链接。如果它有external linkage,那么它对外部代码是可见的,并且确实参与了链接。除了linkage信息,GlobalValues还跟踪它们当前属于哪个Module。

因为GlobalValues是内存对象,所以它们总是由它们的地址来引用。因此,一个全局的Type始终是指向其内容的一个指针。在使用GetElementPtrInst指令时,一定要记住这一点,因为必须首先取消对该指针的引用。例如,如果您有一个GlobalVariable (GlobalValue的子类),它是一个24 int的数组,类型为[24xi32],那么GlobalVariable是指向该数组的指针。虽然这个数组的第一个元素的地址和GlobalVariable的值是相同的,但是它们有不同的类型。全局变量的类型是[24xi32]。第一个元素的类型是i32。

遍历一个Function中的BasicBlock 遍历一个函数中的基本块

有一个你想要以某种方式转换的函数实例是很常见的;特别是,您希望操作它的基本块。为了实现这一点,您需要遍历构成该Function的所有BasicBlocks。下面是打印一个BasicBlock的名称和它包含的Instructions数的例子:

Function &Func = ...
for (BasicBlock &BB : Func)
  // 如果有BasicBlock,则打印它的名称,然后打印它包含的Instructions数
  errs() << "Basic block (name=" << BB.getName() << ") has "
             << BB.size() << " instructions.\n";

遍历一个BasicBlock中的Instruction 遍历一个基本块中的指令

就像在函数中处理基本块一样,很容易遍历组成基本块的各个指令。这是一个代码片段,打印出在一个基本块的每个指令:

BasicBlock& BB = ...
// errs() << BB << "\n";  // 调用基本块本身上的打印例程
for (Instruction &I : BB)
   // 由于操作符<<(ostream&,…)为Instruction&重载,所以下一条指令可用
   errs() << I << "\n";

然而,这并不是打印BasicBlock内容的最佳方式!由于ostream操作符实际上重载了您所关心的所有内容,所以您可以调用基本块本身上的打印例程:errs() << BB << “\n”;。

直接遍历一个Function中的Instruction

如果您发现您通常遍历函数的基本块,然后遍历基本块的指令,那么应该使用InstIterator。您需要include llvm/IR/InstIterator.h(doxygen),然后在代码中显式实例化InstIterator。下面是一个小例子,展示了如何将函数中的所有指令转储到标准错误流:

#include "llvm/IR/InstIterator.h"
// F是指向函数实例的指针 Function &F = ...
for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I)  // 直接遍历函数中的所有指令
  errs() << *I << "\n";

很容易,不是吗?您还可以使用InstIterator来用工作列表的初始内容填充工作列表。例如,如果你想初始化一个工作列表来包含函数F中的所有指令,你需要做的就是:

std::set<Instruction*> worklist; // 指令集合(顺序不变)
// 或者更好的是:SmallPtrSet<Instruction*, 64> worklist;
for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I)
  worklist.insert(&*I); // STL set工作列表现在将包含F指向的函数中的所有指令。

综合应用 编写函数pass 记录目标函数在函数中被调用的次数

记住,因为我们在编写FunctionPass,我们的FunctionPass派生类只需要重载 runOnFunction 方法

Function* targetFunc = ...; // 目标函数指针
class OurFunctionPass : public FunctionPass { // 我们自己的函数pass 继承于 父类 FunctionPass
  public:
    OurFunctionPass(): callCounter(0) { } // 类构造函数 初始化 计数变量 callCounter 为 0
    virtual runOnFunction(Function& F) {  // 重载 runOnFunction 方法, 赋予新的灵魂 
      for (BasicBlock &B : F) {           // 遍历该函数的每一个基本块
        for (Instruction &I: B) {         // 遍历该基本块的每一个指令
          if (auto *CallInst = dyn_cast<CallInst>(&I)) { // 如果该指令是一个 调用指令 则强制转换 为 调用指令
            // 我们知道我们已经遇到了一个调用指令,所以我们需要确定它是否是m_func指向的函数的调用。
            if (CallInst->getCalledFunction() == targetFunc)
              // 该调用指令指向的函数 是 目标函数 则 将调用计数变量+1
              ++callCounter;
          }
        }
      }
    }
  private:
    unsigned callCounter;
};


遍历 def-use(哪些用户(指令)使用了我) 和 use-def(我使用了哪些东西(值,value)) 链

通常,我们可能有一个Value类的实例,我们希望确定哪些Users使用这个值。具有特定Value的所有Users的列表称为def-use链。例如,我们有一个 Function* F 指向一个特定的函数 foo。找到所有使用 foo 的指令就像遍历 F 的def-use链一样简单:

Function *F = ...;
for (User *U : F->users()) { // 遍历具有特定值F的所有Users的列表 
  if (Instruction *Inst = dyn_cast<Instruction>(U)) { // 如果该使用者是 一条指令 则强制转换为指令
    errs() << "F is used in instruction:\n";
    errs() << *Inst << "\n";
  }

或者,通常有一个User类的实例 ,并且需要知道它使用什么Values。一个User使用的所有Values的列表称为use-def链。类Instruction的实例是常见的User,所以我们可能需要遍历特定Instruction使用的所有values(即特定Instruction的操作数):

Instruction *pi = ...;
for (Use &U : pi->operands()) { // 遍历该条指令 所使用的东西(值)
  Value *v = U.get();
  // ...
}

遍历块的前置和后继

使用“llvm/IR/CFG.h”中定义的例程,遍历块的前置和后继是非常容易的。只需使用这样的代码来遍历所有BB的前置:

#include "llvm/IR/CFG.h"   // 控制流程图
BasicBlock *BB = ...;
for (BasicBlock *Pred : predecessors(BB)) { // predecessors(B) 基本块B的前继基本块
  // ...                                    // successors(B)   基本块B的后继基本块
}

改变IR

LLVM基础架构中有一些基本的转换操作值得了解。在执行转换时,操作基本块的内容是相当常见的。本节描述了一些常用的方法,并给出了示例代码。

创建和插入新 Instructions

实例化 Instructions
创建Instructions非常简单:只需调用该类指令的构造函数来实例化并提供必要的参数。例如,AllocaInst 只需要提供一个(const-ptr-to) Type。因此:

auto *ai = new AllocaInst(Type::Int32Ty);

这将在运行时创建一个 AllocaInst 实例,该实例表示当前堆栈帧中一个整数的分配。每个 Instruction 子类都可能有不同的默认参数,这些参数会改变这个指令的语义,所以请参考 Instruction子类的doxygen文档,以获得您感兴趣的要实例化的子类的内容。

命名值

如果可以的话,命名指令的值是非常有用的,因为这有助于调试您的转换。如果您最终查看生成的LLVM机器码,那么您肯定希望有与指令结果关联的逻辑名称!通过为Instruction构造函数的Name(缺省)参数提供一个值,您可以将逻辑名称与运行时指令执行的结果关联起来。例如,假设我正在编写一个转换,它动态地为堆栈上的一个整数分配空间,这个整数将被其他代码用作某种索引。为此,我将AllocaInst放在某个 Function 的第一个 BasicBlock 的第一个 point 上,并打算在同一个 Function 中使用它。我可能会做:

auto *pa = new AllocaInst(Type::Int32Ty, 0, "indexLoc");

其中indexLoc现在是指令执行值的逻辑名称,它是指向运行时堆栈上整数的指针。

插入 Instructions

从本质上讲,有三种方法可以将一条 Instruction 插入到构成一个 BasicBlock 的现有指令序列中:
插入到显式指令列表中
给定一个BasicBlock* pb,该 BasicBlock 中的一个Instruction* pi,以及我们希望在 *pi 之前插入的一条新创建的instruction,我们执行以下操作:

BasicBlock *pb = ...;
Instruction *pi = ...;
auto *newInst = new Instruction(...);
pb->getInstList().insert(pi, newInst); // Inserts newInst before pi in 

删除 Instructions
从构成一个 BasicBlock 的现有指令序列中删除一条指令非常简单:只需调用该指令的eraseFromParent()方法。例如:

Instruction *I = .. ;
I->eraseFromParent();

这将从其包含的基本块中断开指令的链接并删除它。如果只是想从包含基本块的指令中断开链接,而不是删除它,可以使用removeFromParent()方法。

线程和LLVM

本节描述LLVM APIs与多线程的交互,包括客户端应用程序的交互和JIT中的交互,以及托管应用程序中的交互。
注意,LLVM对多线程的支持仍然相对较年轻。在2.5版之前,支持线程托管应用程序的执行,但不支持线程客户机访问APIs。虽然现在支持这个用例,但是客户端必须遵守下面指定的指导原则,以确保在多线程模式下正确操作。
注意,在类unix平台上,为了支持线程操作,LLVM需要GCC的原子内部特性。如果需要在没有合适的现代系统编译器的平台上使用支持多线程的LLVM,可以考虑在单线程模式下编译LLVM和LLVM-GCC,并使用生成的编译器构建支持多线程的LLVM副本。

使用llvm_shutdown()结束执行

使用LLVM api之后,应该调用llvm_shutdown()来释放用于内部结构的内存。

使用ManagedStatic延迟初始化

ManagedStatic是LLVM中的一个实用程序类,用于实现静态资源的静态初始化,比如全局类型表。在单线程环境中,它实现了一个简单的延迟初始化方案。然而,在编译支持多线程的LLVM时,它使用双重检查锁定来实现线程安全的延迟初始化。

使用LLVMContext实现隔离

LLVMContext是LLVM API中的一个不透明类,客户端可以使用它在同一个地址空间内并发地操作多个独立的LLVM实例。例如,在假设的编译服务器中,单个翻译单元的编译在概念上独立于所有其他单元,并且希望能够在独立的服务器线程上同时编译传入的翻译单元。幸运的是,LLVMContext只支持这种场景!

从概念上讲,LLVMContext提供了隔离。LLVM内存IR中的每个LLVM实体(模块、值、类型、常量等)都属于一个LLVMContext。不同上下文中的实体不能相互交互:不同上下文中的模块不能链接在一起,不同上下文中的函数不能添加到模块中,等等。这意味着在多个线程上同时编译是安全的,只要没有两个线程对同一上下文中的实体进行操作。

实际上,除了类型创建/查找API之外,API中很少有地方需要LLVMContext的显式规范。因为每种类型都带有对其所属上下文的引用,所以大多数其他实体可以通过查看自己的类型来确定它们属于哪个上下文。如果您正在向LLVM IR添加新实体,请尝试维护此接口设计。

线程和JIT

LLVM的“eager”JIT编译器在线程程序中使用是安全的。多个线程可以并发地调用ExecutionEngine::getPointerToFunction()或ExecutionEngine::runFunction(),多个线程可以并发地运行JIT输出的代码。用户仍然必须确保只有一个线程访问给定LLVMContext中的IR,而另一个线程可能正在修改它。一种方法是在访问JIT外部的IR时始终保持JIT锁(JIT通过添加CallbackVHs来修改IR)。另一种方法是只从LLVMContext的线程调用getPointerToFunction()。
当JIT被配置为延迟编译(使用ExecutionEngine:: disablelazycompile (false))时,当前在延迟jated函数之后更新调用站点时存在竞争条件。如果您确保每次只有一个线程可以调用任何特定的延迟存根,并且JIT锁保护任何IR访问,那么仍然可以在线程程序中使用延迟JIT,但是我们建议只在线程程序中使用即时JIT。

LLVM 实战

1. 打印LLVM ir 中的函数签名

参考

函数签名

C语言中的函数签名由以下几部分组成:

返回类型 函数名 (参数个数及参数类型)

// 本程序 输入 llvm IR文件 输出 IR中的函数签名
// 输入的IR文件 可以由clang编译得到
// 例如 clang -emit-llvm -c test.c -o test.bc // test.c为测试程序
// 本程序编译命令
// clang++ $(llvm-config --cxxflags --ldflags --libs) main.cpp -o main
// 运行程序
// ./main test.bc

// 引入相关LLVM头文件
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/Module.h>
#include <llvm/IRReader/IRReader.h>
#include <llvm/Support/SourceMgr.h>
#include <llvm/Support/CommandLine.h>

using namespace llvm;

// LLVM上下文全局变量
static ManagedStatic<LLVMContext> GlobalContext;

// 命令行位置参数全局变量, 这个参数的含义是需要处理的LLVM IR字节码的文件名
static cl::opt<std::string> InputFilename(cl::Positional, cl::desc("<filename>.bc"), cl::Required);

int main(int argc, char **argv) {
    // 诊断实例
    SMDiagnostic Err;
    // 格式化命令行参数,
    cl::ParseCommandLineOptions(argc, argv);
    // 读取并格式化LLVM IR字节码文件, 返回LLVM Module(Module是LLVM IR的顶级容器)
    std::unique_ptr<Module> M = parseIRFile(InputFilename, Err, *GlobalContext);
    // 错误处理
    if (!M) {
        Err.print(argv[0], errs());
        return 1;
    }
    // 遍历Module中的每一个Function
    for (Function &F:*M) { // c++ 语法 范围for F是 IR模块中的每一个函数的引用
        // 过滤掉那些以llvm.开头的无关函数
        if (!F.isIntrinsic()) {
            // 打印函数返回类型
            outs() << *(F.getReturnType());
            // 打印函数名
            outs() << ' ' << F.getName() << '('; // 函数名有可能和c文件里的不同(加了一些属性描述)
            // 遍历函数的每一个参数
            for (Function::arg_iterator it = F.arg_begin(), ie = F.arg_end(); it != ie; it++) {
                // 打印参数类型
                outs() << *(it->getType());
                if (it != ie - 1) {
                    outs() << ", ";
                }
            }
            outs() << ")\n";
        }
    }
}

编译遍pass编写示例
2 编写pass 记录函数中每种操作op的数量

#define DEBUG_TYPE "opCounter"
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
#include <map>
using namespace llvm;
namespace {
struct CountOp : public FunctionPass {
std::map<std::string, int> opCounter;
static char ID;
CountOp() : FunctionPass(ID) {}
virtual bool runOnFunction(Function &F) {
errs() << "Function " << F.getName() << '\n';
// for (Module::iterator F = M.begin(), E = M.end(); F != E; ++F);        // 模块迭代器 得到函数列表
for (Function::iterator bb = F.begin(), e = F.end(); bb != e; ++bb) {     // 函数迭代器 得到 基本快BB 列表
for (BasicBlock::iterator i = bb->begin(), e = bb->end(); i != e; ++i) {  // 基本快迭代器 得到指令列表
// for (User::op_iterator O = I.op_begin(), E = I.op_end(); O != E; ++O); // 指令是使用者User 迭代它 可以得到 操作op列表
if(opCounter.find(i->getOpcodeName()) == opCounter.end()) {
opCounter[i->getOpcodeName()] = 1; // 第一次遇到这个op,赋值为1
} else {
opCounter[i->getOpcodeName()] += 1; // 之后没遇到一次,+1
}
}
}

// 打印
std::map <std::string, int>::iterator i = opCounter.begin();
std::map <std::string, int>::iterator e = opCounter.end();
while (i != e) {
errs() << i->first << ": " << i->second << "\n";
i++;
}
errs() << "\n";
opCounter.clear();
return false;
}
};
}
char CountOp::ID = 0;
static RegisterPass<CountOp> X("opCounter", "Counts opcodes per functions");

3. 记录循环内的bb数量
namespace {
struct BBinLoops : public Func@onPass {
staAc char ID;
BBinLoops() : FuncAonPass(ID) {}

// An LLVM pass must declare which other passes it requires to execute properly.
void getAnalysisUsage(AnalysisUsage &AU) const {
    AU.addRequired<LoopInfo>();
    AU.setPreservesAll ();
}

void countBlocksInLoop(Loop *L, unsigned nesAng) {
unsigned numBlocks = 0;
Loop::block_iterator bb;
for(bb = L‐>block_begin(); bb != L‐>block_end();++bb)
numBlocks++;
errs() << "Loop level " << nesAng << " has " << numBlocks << " blocks\n";
vector<Loop*> subLoops = L‐>getSubLoops();
Loop::iterator j, f;
for (j = subLoops.begin(), f = subLoops.end(); j != f; ++j)
    countBlocksInLoop(*j, nesAng + 1);
}

virtual bool runOnFuncAon(FuncAon &F) {
LoopInfo &LI = getAnalysis<LoopInfo>();
errs() << "FuncAon " << F.getName() + "\n";
for (LoopInfo::iterator i = LI.begin(), e = LI.end(); i != e; ++i)
    countBlocksInLoop(*i, 0);
return(false);
}
};
}
char BBinLoops::ID = 0;
staAc RegisterPass<BBinLoops> X("bbloop",
"Count the number of BBs inside each loop");

4.各种分支类型计数
bool BranchCounter::runOnFunction(Function &F) {
for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I) {
    if (BranchInst* BI = dyn_cast<BranchInst>(&*I)) {
        // Count this branch in the total.
        TotalBranches++;
        // Count unconditional branches.
        if (!BI->isConditional())
          UnconditionalBranches++;
        // Count the other types os branches
        else if (ICmpInst* CI = dyn_cast<ICmpInst>(BI->getCondition())) {
          bool const_op0 = dyn_cast<ConstantInt>(CI->getOperand(0)) != 0;
          bool const_op1 = dyn_cast<ConstantInt>(CI->getOperand(1)) != 0;
          // Both operands are constants.
          if (const_op0 && const_op1)
            ConstantAndConstantBranches++;
          // Both operands are variables.
          else if (!const_op0 && !const_op1)
            VarAndVarBranches++;
          // A variable and a constant operands.
          else
            ConstantAndVarBranches++;
        // Count other types of branches.
        } 
        else
          OtherBranches++;
        }
    }
    return false;
}

5. 控制流程图中的 分支会和节点 统计
struct Count_Phis : public FunctionPass
{
  static char ID;
  Count_Phis(): FunctionPass(ID) {}
  virtual bool runOnFunction(Function &F) {
  errs() << "FuncFon " << F.getName() << '\n';
  for(inst_iterator I=inst_begin(F), E=inst_end(F); I !=E; ++I)
  // 直接使用 inst_iterator迭代 函数中的 指令
  // 也可以使用 Function::iterator 迭代函数中的BB,再使用BasicBlock::iterator迭代BB中的指令
  {
    if(isa<PHINode>(*I))
    // if(PHINode *PN = dyn_cast<PHINode>(&*I)) // 动态判断类型
    {
       errs() << *I << "\n";
       errs() <<" has" <<  cast<PHINode>(*I).getNumIncomingValues() <<" arguments.\n";
       for(int arg=0; arg<numArgs; arg++)
       {
          errs() << " Argument"<< arg << ": \n";
          errs() << PN‐>getIncomingBlock(arg)>getName()<<": "<< *(PN‐>getIncomingValue(arg))<< "\n";
       }
    }
      
  }
  return false;
}

6. 优化分支会和节点的 常量分支
virtual bool runOnFunction(Function &F)
{
bool cutInstrucFon = false;
errs() << "FuncFon " << F.getName() << '\n';
SmallVector<PHINode*,16> Worklist;
for(Function::iterator B = F.begin(), EB = F.end(); B != EB; ++B)
{
  for (BasicBlock::iterator I = B‐>begin(), EI = B‐>end(); I != EI; ++I)
  {
    if (PHINode *PN = dyn_cast<PHINode>(I))
    {
      if (PN‐>hasConstantValue()) // 常量
      {
        Worklist.push_back(PN); // 记录优化点
        cutInstrucFon = true;
      }
    }
    else
    {
      break;
    }
  }
}

// 优化处理
while (!Worklist.empty())
{
  PHINode* PN = Worklist.pop_back_val();
  PN‐>replaceAllUsesWith(PN‐>getIncomingValue(0));
  PN‐>eraseFromParent();
}
return cutInstrucFon;
}

7. 处理函数参数的pass
namespace {
    struct Add_No_Alias : public ModulePass {
        staFc char ID;
        Add_No_Alias() : ModulePass(ID) {}
        virtual bool runOnModule(Module &M) {
            for (Module::iterator F = M.begin(), E = M.end(); F != E; ++F) {
                if (!F‐>isDeclaration()) {
                    FuncFon::arg_iterator Arg = F‐>arg_begin(), ArgEnd = F‐>arg_end(); // 遍历函数参数
                    // for (inst_iterator I = inst_begin(&*F), E = inst_end(&*F); I != E; ++I) // 遍历指令
                    while (Arg != ArgEnd) {
                        if (Arg‐>getType()>isPointerTy()) {
                            // 处理指针参数
                            AjrBuilder noalias(Ajribute::get(Arg‐>getContext(), Ajribute::NoAlias));
                            int argNo = Arg‐>getArgNo() + 1;
                            Arg‐>addAjr(AjributeSet::get(Arg‐>getContext(), argNo, noalias));
                        }
                        ++Arg;
                    }
                }
            }
            return true;
        }
    };
}

// 注册pass
char Add_No_Alias::ID = 0;
staFc RegisterPass<Add_No_Alias> X
("addnoalias", "Add no alias to funcFon ajributes");
8. 利用深度遍历来剔除死代码 的pass Dead Blocks Elimination

参考

#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/ADT/DepthFirstIterator.h"  // LLVM的数据结构  深度优先遍历
#include "llvm/IR/CFG.h" // 控制流程图
#include <set>           // STL  SET集合

using namespace llvm;

namespace {
struct DeadBlock : public FunctionPass   // 死代码块去除pass 继承 FunctionPass
                                         // 这个pass将会在每个函数上运行一次
{
    static char ID;

    DeadBlock() : FunctionPass(ID) {}
    
    virtual bool runOnFunction(llvm::Function& F) override {
        // pass的入口
        bool changed = false;  // 是否改变了函数
         // 用来指示这个pass是否改变了目标function,如果改变了,则要标记为true。这个值将是我们的返回值

        // visitedSet 用于存放已经被访问过的BaseBlock
        // 从函数的root block开始,遍历这个root block可能会达到的block,被遍历到的block将会存放到这个 visitedSet 中
        // unreachableSet 则在最后用于存放无法被访问到的block
        // 在得到visitedSet之后,我们可以将其和这个函数中所有block做比较,如果有不在visitedSet中的block,就将其添加到unreachableSet
        
        std::set<BasicBlock*> visitedSet;
        std::set<BasicBlock*> unreachableSet;

        // 从EntryBlock开始深度优先遍历整个函数内可以访问的BaseBlock
        // 将已被访问过的BaseBlock存放在visitedSet中
        for (auto i = df_ext_begin<BasicBlock*,std::set<BasicBlock*>>(&F.getEntryBlock(), visitedSet),
            e = df_ext_end<BasicBlock*,std::set<BasicBlock*>>(&F.getEntryBlock(), visitedSet);
            i != e; i++);
        // 我们无需自己手动实现深度优先遍历,
        // 只需调用DepthFirstIterator.h里的df_ext_begin和df_ext_end两个模板,
        // 就能轻松遍历整个函数,并将访问过的block添加到visitedSet之中。
        
        // 遍历函数内所有BaseBlock,将不在 vistitedSet 中的BaseBlock添加到unreachableSet中
        for (BasicBlock & BB : F) {
            if (visitedSet.find(&BB) == visitedSet.end()) { // 在 vistitedSet中未找到
                unreachableSet.insert(&BB);
            }
        }

        // 标记目标函数是否会被修改
        if (!unreachableSet.empty()) {
            changed = true;
        }

        // 遍历unreachableSet,通知其successor移除多余的phi node
        for (BasicBlock* BB : unreachableSet) {
            for (auto i = succ_begin(BB); i != succ_end(BB); i++) {
                i->removePredecessor(BB); 
                // removePredecessor()函数会通知该block有predecessor已被移除,
                // 随后这个block会检查自己是否有会受到影响的phi node并自动做出修改。
            }
            BB->eraseFromParent();  // 删除掉不想要的block
        }
    
        return changed;
    };
};
}

// LLVM会利用pass的地址来为这个id赋值,所以初始值并不重要
char DeadBlock::ID = 0;
// 注册pass,这个pass可能会改变CFG,所以将第三个参数设为true
static RegisterPass<DeadBlock> X("deadblock", "Dead blocks elimination pass", true, false);

9. 代码插桩pass

#include <set>                           // stl 集合容器
#include <string>
#include "llvm/Pass.h"
#include "llvm/IR/Constants.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Value.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

cl::list<std::string> FunctionList(
        "instrument",                           // 函数内容 指令
        cl::desc("Functions to instrument"),    //
        cl::value_desc("function name"),
        cl::OneOrMore);

namespace {
    class InstrumentFunctions : public ModulePass { // 继承 ModulePass
        public:

            // name of instrumentation functions
            const char *LOG_FUNCTION_STR = "log_function_call";  // 日志打印函数调用
            const char *INIT_FUNCTION_STR = "init";              // 初始化函数
            /* const char *LOG_VARIABLE_STR = "log_variable_change"; */

            static char ID;

            std::set<std::string> funcsToInst; // 以函数名 记录函数是否已修改

            InstrumentFunctions() : ModulePass(ID) {
                for (unsigned i = 0; i != FunctionList.size(); ++i) {
                    funcsToInst.insert(FunctionList[i]);
                }
            }

            bool runOnModule(Module &M) override {
                // 声明日志函数
                declare_log_functions(M); // 后面定义
                    
                for (Module::iterator mi = M.begin(); mi != M.end(); ++mi) {
                    // 遍历模块内部的每一个函数
                    Function &f = *mi;
                    std::string fname = f.getName();            // 函数名
                    /* errs().write_escaped(fname) << "\n"; */
                    if (fname == "main") {
                        initializeLogger(M, f);                 // 在main 主函数 加入自己的初始化日志函数
                    }
                    if (funcsToInst.count(fname) != 0) {
                        instrumentFunction(M, f);               // 修改其他函数
                    }
                }
                return true;
            }
            
            // 在主函数 加入自己的代码
            void initializeLogger(Module &M, Function &f) {
                // main 函数入口 代码块
                BasicBlock &entryBlock = f.getEntryBlock();
                
                // 自己定义的 函数
                Function *initFunction = M.getFunction(INIT_FUNCTION_STR); // 初始化函数
                
                // 在主函数 第一个block代码块后 加入自己写的函数
                CallInst::Create(initFunction, "", entryBlock.getFirstNonPHI());
            }
            
            // 修改其他函数
            void instrumentFunction(Module &M, Function &f) {
                // 函数内部第一个代码块的 第一个指令
                BasicBlock &entryBlock = f.getEntryBlock();
                Instruction *firstInstr = entryBlock.getFirstNonPHI();

                IRBuilder<> builder(firstInstr);
                // 该函数的指针变量
                Value *strPointer = builder.CreateGlobalStringPtr(f.getName());
                
                // 自己定义的 日志函数
                Function *logFunction = M.getFunction(LOG_FUNCTION_STR);

                std::vector<Value *> args;
                args.push_back(strPointer); // 生成日志函数 的参数列表
                
                // 创建自己的日志函数 并传入  本函数的函数指针
                CallInst::Create(logFunction, args, "", entryBlock.getFirstNonPHI());
            }

            void declare_log_functions(Module &m) {
                // 代码上下文
                LLVMContext &C = m.getContext();
                    
                // void type
                Type *voidTy = Type::getVoidTy(C);

                // 64 bit integer
                Type *IntTy64 = Type::getInt64Ty(C);
                
                // 字符串类型
                Type *StringType = Type::getInt8PtrTy(C);

                bool isVarArg = false;

                /* std::vector<Type*> variable_change_params; */
                /* variable_change_params.push_back(StringType); */
                /* variable_change_params.push_back(IntTy64); */
                /* FunctionType *variable_change_type = FunctionType::get(
                 * voidTy, variable_change_params, isVarArg); */
                
                // 函数参数类型
                std::vector<Type*> functionCallParams;
                functionCallParams.push_back(StringType);
                
                // 函数调用类型   (函数指针, 函数参数列表,)
                FunctionType *functionCallType = FunctionType::get(
                        voidTy, functionCallParams, isVarArg
                        );
                
                // 初始化函数类型
                FunctionType *initFunctionType = FunctionType::get(
                        IntTy64, isVarArg
                        );

                // 在模块内部插入 自己定义的函数 insert functions to module
                m.getOrInsertFunction(LOG_FUNCTION_STR, functionCallType);   // 日志函数(函数参数类型)
                m.getOrInsertFunction(INIT_FUNCTION_STR, initFunctionType);  // 初始化函数

                /* m.getOrInsertFunction(LOG_VARIABLE_STR, variable_change_type); */
            }
    }; // end of struct
}  // end of anonymous namespace

char InstrumentFunctions::ID = 0;

// 注册该pass
static RegisterPass<InstrumentFunctions> X("instrument_function_calls",
        "Instrument Function Calls Pass",
        false /* Modifies CFG */,
        false /* Non Analysis Pass */);

logger.c

/* compile with
   cc -std=c11 -pthread main.c
   
   自己的日志函数,可以插入其他代码中,用来对函数 插桩
   
   */
#include <stdlib.h>
#include <sys/time.h>
#include "zlog.h"

zlog_category_t *variable_values_cat;   // 记录变量 

zlog_category_t *function_calls_cat;    // 记录函数调用

int initialized = 0;

// 初始化函数   用来插入 main函数中
int init() {
    int rc = zlog_init("zlog.conf");  // 初始化 zlog  日志
    if (rc) {
        printf("init failed\n");
        return -1;
    }

    variable_values_cat = zlog_get_category("variable_values_cat");
    if (!variable_values_cat) {
        printf("get cat fail\n");
        zlog_fini();
        return -2;
    }

    function_calls_cat = zlog_get_category("function_calls_cat");
    if (!function_calls_cat) {
        printf("get cat fail\n");
        zlog_fini();
        return -2;
    }
    initialized = 1;
    return 0;
}

// 记录变量 变化
void log_variable_change(const char* variable, int value) {
    initialized || init();  // 先确保已经初始化完成

    zlog_info(variable_values_cat, "%s %d", variable, value); // 变量和值
}

// 记录函数调用  
void log_function_call(const char* function) {
    initialized || init();

    zlog_info(function_calls_cat, "%s", function);
}


在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EwenWanW

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值