天天看點

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的優質英文論文。但是英文讀起來沒有中文舒服快速。。