天天看点

clang&llvm简介

文章目录

    • 摘要
    • LLVM&Clang内容介绍
      • LLVM概述
      • Clang概述
      • LLVM&Clang
          • 传统的架构
          • LLVM架构
        • Clang和LLVM的关系
    • Clang和LLVM的简单使用
      • Clang和LLVM的安装
      • 源程序
      • 命令行查看编译的过程
      • 查看preprocessor(预处理)的结果
      • 词法分析
      • 语法树-AST
      • LLVM IR
    • 附录

摘要

翻译了LLVM和Clang官网首页部分信息,以初步了解这两个工具。

陈述了LLVM和Clang两者之间的联系(关系)。

使用实例简单演示了Clang(和LLVM)的使用。

最后在附录中给出了Clang相关工具的官网链接、两篇推荐阅读的blog,两篇关于Clang的中文论文。

LLVM&Clang内容介绍

LLVM概述

llvm官网地址 | llvm 文档翻译 | LLVM – wiki

下面内容来自LLVM官网首页

LLVM(Low Level Virtual Machine)项目是一个模块化、可重用的编译器和工具链技术的集合。尽管它的名字叫LLVM,但它与传统的虚拟机几乎没有什么关系。“LLVM”这个名字本身并不是一个缩写;它是项目的全称。

LLVM最初是伊利诺伊大学的一个研究项目,其目标是提供一种现代的、基于SSA的编译策略,能够支持任意编程语言的静态和动态编译。从那时起,LLVM已经成长为一个由许多子项目组成的伞形项目,其中许多子项目被广泛用于各种商业和开源项目的生产中,也被广泛用于学术研究。LLVM项目中的代码是在“带有LLVM例外的Apache 2.0许可证”下获得许可的。

主要子项目有:

  1. LLVM Core libraries:提供了一个现代的独立于源代码和目标的优化器。这些库(被构建出来)用于优化LLVM intermediate representation (“LLVM IR”)。
  2. Clang:Clang是一个“LLVM native”C/C++/Objective-C编译器,它的目标是提供惊人的快速编译,极其有用的错误和警告消息。Clang Static Analyzer和Clang -tidy都是可以自动发现代码bug的工具,它们是可以使用Clang前端作为解析C/C++代码的库来构建的工具。
  3. LLDB:LLDB项目建立在LLVM和Clang提供的库之上,提供了一个很棒的本地调试器。
  4. libc++和libc++ ABI:提供了一个符合标准的、高性能的c++标准库实现,包括对c++11和c++14的完全支持。
  5. compiler-rt:为目标平台提供其硬件不支持的低级功能的优化实现。它还为动态测试工具(如 AddressSanitizer, ThreadSanitizer, MemorySanitizer, and DataFlowSanitizer)提供了运行时库的实现。
  6. MLIR:MLIR是一种用来构建可重用和可扩展编译基础设施的新方法。MLIR旨在解决软件碎片化,改进异构硬件的编译,显著减少构建特定领域编译器的成本,并帮助连接现有的编译器。(??)
  7. OpenMP:用于Clang中的OpenMP实现
  8. polly:使用polyhedral模型,implements a suite of cache-locality optimizations as well as auto-parallelism and vectorization
  9. libclc:libclc项目的目标是实现OpenCL标准库。
  10. klee:klee项目实现了一个“符号虚拟机”,它使用一个定理证明程序来尝试评估程序中所有的动态路径,从而找到bug并证明函数的属性。klee的一个主要特性是,它可以在检测到bug时生成一个测试用例。
  11. LLD:LLD项目是一个新的链接器(linker)。 这是系统链接程序的直接替代,并且运行速度更快。

除了LLVM的官方子项目外,还有许多其他项目将LLVM的组件用于各种任务。 通过这些外部项目,您可以使用LLVM编译Ruby,Python,Haskell,Rust,D,PHP,Pure,Lua和许多其他语言。 LLVM的主要优势是它的多功能性,灵活性和可重用性,这就是为什么它被用于各种各样的任务:从轻量级的JIT编译嵌入式语言(如Lua)到为大型计算机超级编译Fortran代码。

和其他东西一样,LLVM有一个广泛而友好的社区,里面的人都对构建优秀的底层工具感兴趣。如果你对参与其中感兴趣,首先最好浏览LLVM博客,并注册LLVM开发者邮件列表。关于如何发送补丁的信息,获取提交访问,版权和许可主题,请参阅LLVM开发者策略。

侧边栏: llvm文档、LLVM Command Guide

Clang概述

clang 官网

下面内容来自Clang官网首页

Clang: a C language family frontend for LLVM

Clang项目为LLVM项目提供了C语言家族(C,C ++,Objective C / C ++,OpenCL,CUDA和RenderScript)的前端和工具基础结构。

特点和目标:

  1. 终端用户特点
    • 快速编译和低内存使用
    • 生成的诊断(错误和警告消息)尽可能有用
    • GCC的能力
  2. 实用工具
    • 基于模块化库的架构
    • 支持各种客户端(重构,静态分析,代码生成等)
    • 允许与IDE紧密集成
    • 使用在‘apache 2’许可证
  3. 内部设计与实现
    • 一个真实的、产品级的编译器
    • 一个简单易学的代码库
    • 适用于C,Objective C,C ++和Objective C ++的单个统一解析器
    • 符合C / C++ / ObjC及其变体

当然,这只是对Clang的目标和特性的粗略概述。要真正了解它的意义,请参阅 Features 部分,该部分将对每一项功能进行细分,并对其进行更详细的解释。

新前端的开发源于对编译器的需求,这种编译器允许更好的诊断、与 ide 更好的集成、与商业产品兼容的许可证以及易于开发和维护的灵活编译器。所有这些都是开始工作的动机,一个新的前端,可以满足这些需求。(因为GCC做不到这些)

侧边栏:Clang Static Analyzer 、Choosing the Right Interface for Your Application

Clang provides infrastructure to write tools that need syntactic and semantic information about a program. This document will give a short introduction of the different ways to write clang tools, and their pros and cons.

LLVM&Clang

本节来源:Clang/LLVM 从入门到实践,我不知道其内容正确与否。

传统的架构
clang&llvm简介

Frontend:前端

主要负责:词法分析、语法分析、语义分析、生成中间代码

Optimizer:优化器

主要负责:中间代码的优化

Backend:后端

主要职责:生成机器码

LLVM架构
clang&llvm简介

相比传统架构的优势:

  • 不同的前端后端使用统一的中间代码LLVM Intermediate Representation (LLVM IR)
  • 如果需要支持一种新的编程语言,那么只需要实现一个新的前端
  • 如果需要支持一种新的硬件设备,那么只需要实现一个新的后端
  • 优化阶段是一个通用的阶段,它针对的是统一的LLVM IR,不论是支持新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做修改
  • 相比之下,GCC的前端和后端没分得太开,前端后端耦合在了一起。所以GCC为了支持一门新的语言,或者为了支持一个新的目标平台,就变得特别困难
  • LLVM现在被作为实现各种静态和运行时编译语言的通用基础结构(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等)

Clang和LLVM的关系

clang&llvm简介

LLVM整体架构,前端用的是Clang,广义的LLVM是指整个LLVM架构,一般狭义的LLVM指的是LLVM后端(包含代码优化和目标代码生成)。

源代码(c/c++)经过clang–> 中间代码(经过一系列的优化,优化用的是Pass) –> 机器码。

Clang和LLVM的简单使用

参考:Clang/LLVM 从入门到实践

Clang和LLVM的安装

# 我的系统ubuntu20.04
# 安装clang的时候,会默认顺带安装llvm
$ sudo apt install clang
$ clang --version
clang version 10.0.0-4ubuntu1 
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

# gcc对应gdb调试;clang对应lldb调试
$ sudo apt isntall lldb
           

略微麻烦点的是,

sudo apt install clang

之后,安装的clang和llvm相关命令,后面大都跟着版本数字。比如

llc-10

llvm-as-10

所以这里我们使用update-alternatives进行版本控制,避免后面版本数字的影响。

# 先查看之前有无进行版本控制【无候选项说明之前没有进行配置】
update-alternatives --display llc

# 添加命令链接
# 我个人习惯使用版本号和优先级相同的数字;这样默认总是最新的版本。
linux-source-5.4.0 sudo update-alternatives --install /usr/bin/llc llc /usr/bin/llc-10 10
           

源程序

// 代码保存为main.c
#include <stdio.h>

int main(void){
    printf("hello world\n");
    return 0;
}
           

命令行查看编译的过程

$ clang -ccc-print-phases main.c
            +- 0: input, "main.c", c
         +- 1: preprocessor, {0}, cpp-output
      +- 2: compiler, {1}, ir
   +- 3: backend, {2}, assembler
+- 4: assembler, {3}, object
5: linker, {4}, image
           

过程分析:

0.找到main.c文件

1.预处理器,处理include、import、宏定义

2.编译器编译,编译成ir中间代码

3.后端,生成汇编代码

4.汇编代码生成目标代码

5.链接其他库生成image

查看preprocessor(预处理)的结果

$ llvm clang -E main.c
# 1 "main.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 341 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "main.c" 2
# 1 "/usr/include/stdio.h" 1 3 4
# 27 "/usr/include/stdio.h" 3 4
...
# 2 "main.c" 2

int main(void){
    printf("hello world\n");
    return 0;
}
           

词法分析

$ clang -E -Xclang -dump-tokens main.c
...
int 'int'        [StartOfLine]  Loc=<main.c:3:1>
identifier 'main'        [LeadingSpace] Loc=<main.c:3:5>
l_paren '('             Loc=<main.c:3:9>
void 'void'             Loc=<main.c:3:10>
r_paren ')'             Loc=<main.c:3:14>
l_brace '{'             Loc=<main.c:3:15>
identifier 'printf'      [StartOfLine] [LeadingSpace]   Loc=<main.c:4:5>
l_paren '('             Loc=<main.c:4:11>
string_literal '"hello world\n"'                Loc=<main.c:4:12>
r_paren ')'             Loc=<main.c:4:27>
semi ';'                Loc=<main.c:4:28>
return 'return'  [StartOfLine] [LeadingSpace]   Loc=<main.c:5:5>
numeric_constant '0'     [LeadingSpace] Loc=<main.c:5:12>
semi ';'                Loc=<main.c:5:13>
r_brace '}'      [StartOfLine]  Loc=<main.c:6:1>
eof ''          Loc=<main.c:6:2>
           

可以看出,词法分析的时候,将上面的代码拆分一个个token,后面数字表示某一行的第几个字符

语法树-AST

$ clang -fsyntax-only -Xclang -ast-dump main.c
...
`-FunctionDecl 0x16cb620 <main.c:3:1, line:6:1> line:3:5 main 'int (void)'
  `-CompoundStmt 0x16cb828 <col:15, line:6:1>
    |-CallExpr 0x16cb7a0 <line:4:5, col:27> 'int'
    | |-ImplicitCastExpr 0x16cb788 <col:5> 'int (*)(const char *, ...)' <FunctionToPointerDecay>
    | | `-DeclRefExpr 0x16cb6c0 <col:5> 'int (const char *, ...)' Function 0x16bb150 'printf' 'int (const char *, ...)'
    | `-ImplicitCastExpr 0x16cb7e0 <col:12> 'const char *' <NoOp>
    |   `-ImplicitCastExpr 0x16cb7c8 <col:12> 'char *' <ArrayToPointerDecay>
    |     `-StringLiteral 0x16cb718 <col:12> 'char [13]' lvalue "hello world\n"
    `-ReturnStmt 0x16cb818 <line:5:5, col:12>
      `-IntegerLiteral 0x16cb7f8 <col:12> 'int' 0
           

更多见:Clang AST简介

LLVM IR

LLVM IR有3种表示形式(本质是等价的):

  • text:便于阅读的文本格式,类似于汇编语言,拓展名.ll, $ clang -S -emit-llvm main.c
  • memory:内存格式
  • bitcode:二进制格式,拓展名.bc, $ clang -c -emit-llvm main.c

我们以text形式编译查看:

$ clang -S -emit-llvm main.c 

; ModuleID = 'main.c'
source_filename = "main.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

@.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00", align 1

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
  %1 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  %2 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str, i64 0, i64 0))
  ret i32 0
}

declare dso_local i32 @printf(i8*, ...) #1

attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"clang version 10.0.0-4ubuntu1 "}
           

更多见:LLVM IR入门指南

附录

  1. 官网链接: llvm 官网、clang 官网、clang命令行参数、GCC 中文手册(内容陈旧)、gcc 官网手册
  2. 推荐阅读:Clang/LLVM 从入门到实践、LLVM IR入门指南
  3. 龚丹, 苏小红, 王甜甜. Clang 编译平台优势分析[J]. 收藏, 2017, 3.
    Clang的编译速度是GCC的2.5倍以上,clang编译时占用空间为源文件的1.3倍,而GCC的产出物字节数则为源文件的10倍以上。
    或许,可以舍弃gcc,使用clang替代。
  4. 曹原野, 丁丽萍. 基于 Clang 编译前端的 Android 源代码静态分析技术 ①[J]. 2017.
    摘 要: Android 手机在全球占有很大的市场份额, 基于 Android 衍生的第三方系统也为数不少. 针对 Android 系统 重大安全问题频发的现状, 提出一种使用 Clang 编译前端对 Android 源码进行静态分析的方法. 该方法从已公布的 CVE 漏洞中提取规则和模型, 通过改进的 Clang 编译前端, 对 Android 源码进行静态分析, 从而检测出有潜在安全 风险的代码片段. 在对 Android 源码进行污点分析时, 调用新加入的 stp 约束求解器, 通过符号执行, 对敏感数据进行污点标记, 并对敏感函数、敏感操作、敏感规则进行污点分析, 如果存在潜在的安全隐患, 则进行报告. 经过实验 分析, 该方法可以找出 Android 源代码中存在的同类型有安全风险的代码片段, 可以检出 libstagefright 模块 5 个高 危 CVE 漏洞
    • 人工分析少量的CVE漏洞,以提取特征:数据溢出再强制转换,直接让污点数据参与运算(没有消毒)
    • 通过 Clang 的源码, 可以发现, 如果将 Clang 的静态分析直接应用到 Android 系统源码之上, 基本没有效果. 一个原因是 Clang 自带的 range 约束求解器的求 解能力过于薄弱, 只能处理简单约束. 另一个原因是 Clang 作为一个通用编译前端, 更注重的是通用性, 所以特定问题的检测能力不强. 需要对 Clang 的符号执 行能力和检测能力进行增强. (LLVM 预编译工具链中 Clang 版本为3.3) —> 使用stp 约束求解器,从攻击面引入的危险数据加上污点标记。
    • 对于未打补丁的源码可以找出高危漏洞。
    • 时间耗费较长、内存耗费较大;该工具存在内存泄露问题;符号执行存在一定偏差;溢出判断容易造成漏报;
  5. 必然存在关于clang和llvm的优质英文论文。但是英文读起来没有中文舒服快速。。