LLVM外部项目清单介绍

发布时间 2023-06-13 04:06:43作者: 吴建明wujianming
LLVM外部项目清单介绍
  核心LLVM和Clang代码库之外的项目是外部项目,需要单独下载。本章会介绍几种官方LLVM的外部项目,并解释如何编译安装它们。
  将介绍以下项目,包括如何安装它们:
1)LLDB
2)Libc++
3)Compiler-RT
4)DragonEgg
5)LLVM test suite
6)Clang extra tools
2.1 LLDB调试器
2.1.1 LLDB基础知识
LLDB(Low Level Debugger)项目以LLVM基础设施构造一个调试器。LLDB是一个有着REPL 的特性和C++, Python插件的开源高性能调试器,这是Mac OS X上Xcode的默认调试器,支持在桌面和iOS设备和模拟器上调试。
LLDB绑定在Xcode 内部,存在于主窗口底部的控制台中,可以在需要时暂停程序,查看变量的值,执行特定的指令,并按指定的步骤来操作程序的进展。
获取来源
通过以下github链接,可以得到LLDB源:
https://github.com/llvm/llvm-project/tree/main/lldb
或者链接
https://github.com/llvm-mirror/lldb
2.1.2 LLDB控制台
Xcode中内嵌了LLDB控制台,在Xcode中代码的下方,可以看到LLDB控制台。
LLDB控制台平时会输出一些log信息。如果想输入命令调试,必须让程序进入暂停状态。让程序进入暂停状态的方式主要有2种:
1)断点或者watchpoint: 在代码中设置一个断点(watchpoint),当程序运行到断点位置的时候,会进入stop状态。
2)直接暂停,控制台上方有一个暂停按钮,点击即可暂停程序。
1. LLDB语法
在使用LLDB之前,来先看看LLDB的语法,了解语法可以帮助清晰的使用LLDB:
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
现在,对上面的命令解释一下:
1)<command>(命令)和<subcommand>(子命令):LLDB调试命令的名称。命令和子命令按层级结构来排列:一个命令对象为跟随其子命令对象创建一个上下文,子命令又为其子命令创建一个上下文,依此类推。
2)<action>:执行命令的操作
3)<options>:命令选项
4)<arguement>:命令的参数
5)[ ]:表示命令是可选的,可以有也可以没有
举个例子,假设给main方法设置一个断点,使用图2.1所示的命令:

图2.1 设置断点的命令

breakpoint set -n mainbreakpoint set -n main
这个命令对应到上面的语法就是:
1)command: breakpoint 表示断点命令。
2)action: set 表示设置断点。
3)option: -n 表示根据方法name设置断点。
4)arguement: mian 表示方法名为mian。
2. 原始(raw)命令
LLDB支持不带命令选项(options)的原始(raw)命令,原始命令会将命令后面的所有内容当做参数(arguement)传递。不过很多原始命令也可以带命令选项,当使用命令选项的时候,需要在命令选项后面加--区分命令选项和参数。
例如,常用的expression就是raw命令,一般情况下使用expression打印一个内容是这样的:
(lldb) expression count
(int) $2 = 4
当想打印一个对象的时候。需要使用-O命令选项,应该用--将命令选项和参数区分:
(lldb) expression -O -- self
<ViewController: 0x7f9000f17660>
3. 唯一匹配原则
LLDB的命令遵循唯一匹配原则:假如根据前n个字母已经能唯一匹配到某个命令,则只写前n个字母等效于写下完整的命令。
    例如: 前面提到设置断点的命令,可以使用唯一匹配原则简写,下面2条命令等效:
breakpoint set -n main
br s -n main

4. ~/.lldbinit

LLDB有了一个启动时加载的文件~/.lldbinit,每次启动都会加载。所以一些初始化的内容,可以写入~/.lldbinit中,比如给命令定义别名等。但是由于这时候程序还没有真正运行,也有部分操作无法在里面工作,比如设置断点。

5. LLDB命令Expression

expression命令的作用是执行一个表达式,并将表达式返回的结果输出。expression的完整语法是这样的:

expression <cmd-options> -- <expr>
1)<cmd-options>:命令选项,一般情况下使用默认的即可,不需要特别标明。
2)--: 命令选项结束符,表示所有的命令选项已经设置完毕,如果没有命令选项,--可以省略。
3)<expr>: 要执行的表达式。
expression是LLDB里面最重要的命令之一。因为能实现2个功能。
在代码运行过程中,可以通过执行某个表达式来动态改变程序运行的轨迹。假如在运行过程中,突然想把self.view颜色改成红色,看看效果。不必写下代码,重新run,只需暂停程序,用expression改变颜色,再刷新一下界面,就能看到效果。
// 改变颜色
(lldb) expression -- self.view.backgroundColor = [UIColor redColor]
// 刷新界面
(lldb) expression -- (void)[CATransaction flush]
也就是说,可以通过expression来打印内容。
例如想打印self.view:
(lldb) expression -- self.view
(UIView *) $1 = 0x00007fe322c18a10
6. p & print & call
一般情况下,直接用expression还是用得比较少的,更多时候用的是p、print、call。这三个命令其实都是expression --的别名(--表示不再接受命令选项,详情见前面原始(raw)命令)。
1)print: 打印某个内容,可以是变量和表达式。
2)p: 可以看做是print的简写。
3)call: 调用某个方法。
表面上看,可能有不一样的地方,实际都是执行某个表达式(变量也当做表达式),将执行的结果输出到控制台上。所以可以用p调用某个方法,也可以用call打印内容。
    例如,下面代码效果相同:
(lldb) expression -- self.view
(UIView *) $5 = 0x00007fb2a40344a0
(lldb) p self.view
(UIView *) $6 = 0x00007fb2a40344a0
(lldb) print self.view
(UIView *) $7 = 0x00007fb2a40344a0
(lldb) call self.view
(UIView *) $8 = 0x00007fb2a40344a0
(lldb) e self.view
(UIView *) $9 = 0x00007fb2a40344a0
根据唯一匹配原则,如果没有自己添加特殊的命令别名,e也可以表示expression的意思。原始命令默认没有命令选项,所以e也能带给同样的效果。
7. po
众所周知,OC里所有的对象都是用指针表示的,所以一般打印的时候,打印出来的是对象的指针,而不是对象本身。如果想打印对象。需要使用命令选项:-O。为了更方便的使用,LLDB为expression -O --定义了一个别名:po。
(lldb) expression -- self.view
(UIView *) $13 = 0x00007fb2a40344a0
(lldb) expression -O -- self.view
<UIView: 0x7fb2a40344a0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fb2a4018c80>>
(lldb) po self.view
<UIView: 0x7fb2a40344a0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fb2a4018c80>>
8. thread:thread backtrace & bt
有时候想要了解线程堆栈信息,可以使用thread backtrace。
thread backtrace作用是将线程的堆栈打印出来。现在来看看语法。
thread backtrace [-c <count>] [-s <frame-index>] [-e <boolean>]
thread backtrace后面跟的都是命令选项:
-c:设置打印堆栈的帧数(frame)。
-s:设置从哪个帧(frame)开始打印。
-e:是否显示额外的回溯。
实际上,一般不需要使用这些命令选项。
例如,当发生crash的时候,可以使用thread backtrace查看堆栈调用。
(lldb) thread backtrace
* thread #1: tid = 0xdd42, 0x000000010afb380b libobjc.A.dylib`objc_msgSend + 11, queue = ‘com.apple.main-thread‘, stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
frame #0: 0x000000010afb380b libobjc.A.dylib`objc_msgSend + 11
* frame #1: 0x000000010aa9f75e TLLDB`-[ViewController viewDidLoad](self=0x00007fa270e1f440, _cmd="viewDidLoad") + 174 at ViewController.m:23
frame #2: 0x000000010ba67f98 UIKit`-[UIViewController loadViewIfRequired] + 1198
frame #3: 0x000000010ba682e7 UIKit`-[UIViewController view] + 27
frame #4: 0x000000010b93eab0 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 61
 frame #5: 0x000000010b93f199 UIKit`-[UIWindow _setHidden:forced:] + 282
frame #6: 0x000000010b950c2e UIKit`-[UIWindow makeKeyAndVisible] + 42
可以看到crash发生在-[ViewController viewDidLoad]中,只需检查这行代码是不是干了什么非法的操作就可以了。
LLDB还为backtrace专门定义了一个别名:bt,其效果与thread backtrace相同,如果不想写那么长一串字母,直接写下bt即可。
(lldb) bt
thread return
Debug的时候,也许会因为各种原因,不想让代码执行某个方法,或者要直接返回一个想要的值。这时候就该thread return上场了。thread return可以接受一个表达式,调用命令之后直接从当前的frame返回表达式的值。
例如,有一个someMethod方法,默认情况下是返回YES。
如果想要让其返回NO,只需在方法的开始位置加一个断点,当程序中断的时候,输入以下命令即可:
(lldb) thread return NO
效果相当于在断点位置直接调用return NO,不会执行断点后面的代码。
9. c & n & s & finish
一般在调试程序的时候,经常用到图2.2所示这几个个按钮:

 图2.2 LLVM开发调试示例

喜欢用触摸板的人,可能会觉得点击这4个按钮比较费劲。其实LLDB命令也可以完成上面的操作,而且如果不输入命令,直接按Enter键,LLDB会自动执行上次的命令。按一下Enter就能达到想要的效果,顿时感觉很惬意!
现在来看看对应这几个按钮的LLDB命令:
1)c/ continue/ thread continue: 这三个命令效果都等同于上图中第一个按钮的。表示程序继续运行。
2)n/ next/ thread step-over: 这三个命令效果等同于上图第二个按钮。表示单步运行。
3)s/ step/ thread step-in: 这三个命令效果等同于上图第三个按钮。表示进入某个方法。
4)finish/ step-out: 这两个命令效果等同于第四个按钮。表示直接走完当前方法,返回到上层frame。
10. thread其他不常用的命令
thread 相关的还有其他一些不常用的命令,这里就简单介绍一下即可,如果需要了解更多,可以使用命令help thread查阅。
thread jump: 直接让程序跳到某一行。由于ARC下编译器实际插入了不少retain,release命令。跳过一些代码不执行很可能会造成对象内存混乱发生crash。
1)thread list: 列出所有的线程。
2)thread select: 选择某个线程。
3)thread until: 传入一个line的参数,让程序执行到这行的时候暂停。
4)thread info: 输出当前线程的信息。
frame
前面提到过很多次frame(帧)。可能有的朋友对frame这个概念还不太了解。随便打个断点。图2.3,图2.4,图2.5所示表示控制台上打印出来的frame示例。

 图2.3 控制台上打印出来的frame示例

 图2.4 控制台上打印出来的frame示例(二)

 图2.5 控制台上打印出来的frame示例(三)

在控制台上输入命令bt,可以打印出来所有的frame。如果仔细观察,这些frame和左边红框里的堆栈是一致的。平时看到的左边的堆栈就是frame。
11. frame variable
平时Debug的时候,经常做的事就是查看变量的值,通过frame variable命令,可以打印出当前frame的所有变量。
(lldb) frame variable
(ViewController *) self = 0x00007fa158526e60
(SEL) _cmd = "text:"
(BOOL) ret = YES
(int) a = 3
可以看到,将self,_cmd,ret,a等本地变量都打印了出来。
如果要需要打印指定变量,也可以给frame variable传入参数:
(lldb) frame variable self->_string
(NSString *) self->_string = nil
不过frame variable只接受变量作为参数,不接受表达式,也就是说,无法使用frame variable self.string,因为self.string是调用string的getter方法。所以一般打印指定变量,更喜欢用p或者po。
12. 其他不常用命令
一般frame variable打印所有变量用得比较多,frame还有2个不怎么常用的命令:
frame info: 查看当前frame的信息。
(lldb) frame info
frame #0: 0x0000000101bf87d5 TLLDB`-[ViewController text:](self=0x00007fa158526e60, _cmd="text:", ret=YES) + 37 at
frame select: 选择某个frame。
(lldb) frameselect1
frame #1: 0x0000000101bf872e TLLDB`-[ViewController viewDidLoad](self=0x00007fa158526e60, _cmd="viewDidLoad") + 78 at ViewController.m:23
  1.    20
  2.    21      - (void)viewDidLoad {
  3.    22          [super viewDidLoad];
  4. -> 23          [self text:YES];
  5.    24          NSLog(@"1");
  6.    25          NSLog(@"2");
  7.    26          NSLog(@"3");
当选择frame 1的时候,会把frame1的信息和代码打印出来。不过一般都是直接在Xcode左边点击某个frame,这样更方便。
breakpoint
调试过程中,用得最多的可能就是断点了。LLDB中的断点命令也非常强大。
breakpoint set
breakpoint set命令用于设置断点,LLDB提供了很多种设置断点的方式:
使用-n根据方法名设置断点:
例如,想给所有类中的viewWillAppear:设置一个断点:
 (lldb) breakpoint set -n viewWillAppear:
 Breakpoint 13: 33 locations.
使用-f指定文件
例如,只需要给ViewController.m文件中的viewDidLoad设置断点:
 (lldb) breakpoint set -f ViewController.m -n viewDidLoad
Breakpoint 22: where = TLLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:22, address = 0x000000010272a6f4
这里需要注意,如果方法未写在文件中(比如写在category文件中,或者父类文件中),指定文件之后,将无法给这个方法设置断点。
使用-l指定文件某一行设置断点
例如,想给ViewController.m第38行设置断点。
(lldb) breakpoint set -f ViewController.m -l 38
Breakpoint 23: where = TLLDB`-[ViewController text:] + 37 at ViewController.m:38, address = 0x000000010272a7d5
使用-c设置条件断点
例如,text:方法接受一个ret的参数,想让ret == YES的时候程序中断:
(lldb) breakpoint set -n text: -c ret == YES
Breakpoint 7: where = TLLDB`-[ViewController text:] + 30 at ViewController.m:37, address = 0x0000000105ef37ce
  1. 使用-o设置单次断点
  2.  
  3. e.g: 如果刚刚那个断点只想让其中断一次:
  4. (lldb) breakpoint set -n text: -o
  5. ‘breakpoint 3‘: where = TLLDB`-[ViewController text:] + 30 at ViewController.m:37, address = 0x000000010b6f97ce
breakpoint command
有的时候,可能需要给断点添加一些命令,比如每次走到这个断点的时候,都需要打印self对象。只需要给断点添加一个po self命令,就不用每次执行断点,再自己输入po self了。
breakpoint command add
breakpoint command add命令就是给断点添加命令的命令。
例如,假设需要在ViewController的viewDidLoad中查看self.view的值。
首先给-[ViewController viewDidLoad]添加一个断点。
(lldb) breakpoint set -n "-[ViewController viewDidLoad]"
‘breakpoint 3‘: where = TLLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:23, address = 0x00000001055e6004
可以看到添加成功之后,这个breakpoint的id为3,然后给其增加一个命令:po self.view
 (lldb) breakpoint command add -o "po self.view" 3
-o完整写法是--one-liner,表示增加一条命令。3表示对id为3的breakpoint增加命令。
添加完命令之后,每次程序执行到这个断点就可以自动打印出self.view的值了。
如果一下子想增加多条命令,比如,想在viewDidLoad中打印当前frame的所有变量,但是不想让其中断,也就是在打印完成之后,需要继续执行。可以这样做:
(lldb) breakpoint command add 3
Enter your debugger command(s).  Type ‘DONE‘ to end.
  1. > frame variable
  2. > continue
  3. > DONE
输入breakpoint command add 3对断点3增加命令。会要求输入增加哪些命令,输入“DONE”表示结束。这时候就可以输入多条命令了。
breakpoint command list
如果想查看某个断点已有的命令,可以使用breakpoint command list。例如,查看一下刚刚的断点3已有的命令。
2.2 libc++ C++标准库
2.2.1 libc++库概述
libc++是C++标准库的一个新实现,目标是C++11及更高版本。
1. 特点和目标
1)C++11标准定义的正确性。
2)快速执行。
3)内存使用最少。
4)快速编译时间。
5)ABI与gcc的libstdc++的兼容性,用于一些低级功能,如异常对象、RTTI(Run-Time Type Identification)和内存配。
2. 设计和实施
1)广泛的单元测试。
2)内部链接器模型可以转储/读取为文本格式。
3)附加链接功能可以作为“passes”插入。
4)特定于操作系统和特定于CPU的代码。
2.2.2 Ubuntu下安装clang和libc++
1. 需要在Ubuntu上安装clang++
选择版本
选择clang 5.0最终版,那么官网指南中可将trunk改成tags/RELEASE_500/final。
例如:
http://llvm.org/svn/llvm-project/llvm/trunk
可以改成:
http://llvm.org/svn/llvm-project/llvm/tags/RELEASE_500/final
2. clang安装步骤
安装必要的包:
sudo apt install subversion
sudo apt install cmake
建立目录(这里取名为CL):
cd ~
sudo mkdir CL
cd CL
下载llvm:
svn co http://llvm.org/svn/llvm-project/llvm/tags/RELEASE_500/final llvm
下载clang:
cd llvm/tools
svn co http://llvm.org/svn/llvm-project/cfe/tags/RELEASE_500/final clang
cd ../..
下载clang工具(可选):
cd llvm/tools/clang/tools
svn co http://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_500/final extra
cd ../../../..
下载Compiler-RT(可选):
cd llvm/projects
svn co http://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_500/final compiler-rt
cd ../..
下载标准库libcxx(绝对要下载)还有libcxxabi(千万不要遗漏):
cd llvm/projects
svn co http://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_500/final libcxx
svn co http://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_500/final libcxxabi
cd ../..
编译安装:
mkdir build
cd build
注意将默认的Debug模式换成Release模式:
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ../llvm
make
sudo make install
可以用了!
测试一下:
clang++ --help
基于c++11使用libc++编译x.cpp并执行a.out:
clang++ -std=c++11 -stdlib=libc++ x.cpp
.\a.out
验证x.cpp的正确性:
clang x.cpp -fsyntax-only
输出x.cpp未优化的LLVM代码:
clang x.cpp -S -emit-llvm -o -
输出x.cpp经过O3优化的LLVM代码:
clang x.cpp -S -emit-llvm -o - -O3
输出x.cpp的原生机器码:
clang x.cpp -S -O3 -o -
安装完毕之后,可以用clang再编译安装一次:
CC=clang CXX=clang++ cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ../llvm
2.3 compiler-rt runtime运行时库
2.3.1 compiler-rt项目组成
1. builtins内置
一个简单的库,它提供代码生成和其他运行时组件所需的低级目标特定钩子的实现。例如,当为32位目标编译时,将双精度转换为64位无符号整数就是编译为对“__fixunsdfdi”函数的运行时调用。内置库提供了此例程和其他低级例程的优化实现,可以是目标独立的C形式,也可以是高度优化的程序集。
内置程序在支持的目标上提供了对libgcc接口的完全支持,并在汇编中提供了常用函数(如__floatundidf)的高性能手动调优实现,这些实现比libgcc实现快得多。通过添加新目标所需的新例程,引入内置程序来支持新目标应该非常容易。
2. sanitizer runtimes运行时
运行带有sanitizer程序插入的代码所需的运行库。这包括以下方面的运行时:
1)AddressSanitizer
2)ThreadSanitizer
3)UndefinedBehaviorSanitizer
4)MemorySanitizer
5)LeakSanitizer
6)DataFlowSanitizerProfile
3. Profile
用于收集覆盖范围信息的库。
4. BlocksRuntime
苹果“Blocks”运行时接口的独立于目标的实现。
compiler-rt项目中的所有代码都是根据MIT许可证和UIUC许可证(类似BSD的许可证)获得双重许可的。
2.3.2 客户支持
目前compiler-rt主要由Clang和LLVM项目用作运行时编译器支持库的实现。有关将compiler-rt与Clang一起使用的更多信息,请参阅Clang入门页面。
2.3.3 平台支持
众所周知,内置程序可以在以下平台上工作:
1)机器体系结构:i386、X86-64、SPARC64、ARM、PowerPC、PowerPC64。
2)操作系统:DragonFlyBSD、FreeBSD、NetBSD、OpenBSD、Linux、Darwin。
大多数sanitizer程序运行时仅在Linux x86-64上受支持。有关更多详细信息,请参阅Clang文档中特定于工具的页面。
如图2.6,表示xcode开发调试平台。

 图2.6 表示xcode开发调试平台

2.3.4 源码结构
compiler-rt目录结构的简要说明:
为了进行测试,可以构建一个通用库和一个优化库。优化的库是通过将优化的版本叠加到通用库上而形成的。当然,有些体系结构具有额外的功能,因此优化的库可能具有通用版本中找不到的功能。
include/ contains:可以包含在用户程序中的头(例如,用户可以直接从消毒程序运行时调用某些函数)。
lib/contains:库实现。
lib/builtins:内置例程的通用可移植实现。
lib/builtins/(arch):针对所支持的体系结构优化了一些例程的版本。
test/contains:compiler-rt运行时的测试套件。
2.3.5 clang使用方法
通常,需要构建LLVM/Clang才能构建compiler-rt。可以将它与llvm和clang一起构建,也可以单独构建。
要将其构建在一起,只需将compiler-rt添加到要cmake的-DLLVM_ENABLE_RUNTIMES=选项中即可。
要单独构建它,首先单独构建LLVM以获得LLVM配置二进制文件,然后运行:
cd llvm-project
mkdir build-compiler-rt
cd build-compiler-rt
cmake ../compiler-rt -DLLVM_CONFIG_PATH=/path/to/llvm-config
make
sanitizer程序运行时的测试被移植到llvm-lit,并由llvm/Clang/compiler-rt构建树中的make-check-all命令运行。
compiler-rt库通过在LLVM/Clang/compiler-rt或独立compiler-rt构建树中的make-install命令安装到系统中。
如果有问题,请在运行时类别下的话语论坛上提问。对compiler-rt的提交会自动发送到llvm提交邮件列表。
2.4 DragonEgg
2.4.1 DragonEgg-使用LLVM作为GCC后端
DragonEgg是一个gcc插件,它用LLVM项目中的优化器和代码生成器取代了gcc的优化器和编码生成器。它与gcc-4.5或更新版本配合使用,可以针对x86-32/x86-64和ARM处理器系列,并已成功用于Darwin、FreeBSD、KFreeBSD、Linux和OpenBSD平台。它完全支持Ada、C、C++和Fortran。它部分支持Go、Java、Obj-C和Obj-C++。
目标
完全支持所有GCC语言。
当前状态
1)与gcc-4.6配合使用效果最佳。
2)Fortran运行得很好。Ada、C和C++也能很好地工作。Ada在使用gcc-4.7及更高版本时表现不佳。
3)它可以编译合理数量的Obj-C、Obj-C++和Go。
4)它可以编译简单的Java程序,但它们不能正确执行(这是Java前端不支持GCC的LTO的结果)。
5)调试信息不足。
2.4.2 DragonEgg实践
以下是使用gcc-4.5编译一个简单的“hello-world”程序的结果:
$ gcc hello.c -S -O1 -o -
       .file  "hello.c"
       .section  .rodata.str1.1,"aMS",@progbits,1
.LC0:
       .string     "Hello world!"
       .text
.globl main
       .type       main, @function
main:
       subq       $8, %rsp
       movl       $.LC0, %edi
       call  puts
       movl       $0, %eax
       addq      $8, %rsp
       ret
       .size main, .-main
       .ident     "GCC: (GNU) 4.5.0 20090928 (experimental)"
       .section  .note.GNU-stack,"",@progbits
在gcc命令行中添加-fplugin=path/dragonegg.so会导致程序被LLVM优化和编码:
$ gcc hello.c -S -O1 -o - -fplugin=./dragonegg.so
        .file          "hello.c"
# Start of file scope inline assembly
        .ident       "GCC: (GNU) 4.5.0 20090928 (experimental) LLVM: 82450:82981"
# End of file scope inline assembly
 
        .text
        .align       16
        .globl       main
        .type        main,@function
main:
        subq        $8, %rsp
        movl        $.L.str, %edi
        call          puts
        xorl          %eax, %eax
        addq        $8, %rsp
        ret
        .size         main, .-main
        .type        .L.str,@object
        .section    .rodata.str1.1,"aMS",@progbits,1
.L.str:
        .asciz       "Hello world!"
        .size         .L.str, 13
 
        .section    .note.GNU-stack,"",@progbits
添加-fplugin arg dragonegg emit ir或-flto会导致输出LLVM ir(需要请求汇编程序输出-S,而不是对象代码输出-c,因为否则gcc会将LLVM IR传递给系统汇编程序,而系统汇编程序无疑无法对其进行汇编):
$ gcc hello.c -S -O1 -o - -fplugin=./dragonegg.so -fplugin-arg-dragonegg-emit-ir
; ModuleID = 'hello.c'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128"
target triple = "x86_64-unknown-linux-gnu"
 
module asm "\09.ident\09\22GCC: (GNU) 4.5.0 20090928 (experimental) LLVM: 82450:82981\22"
 
@.str = private constant [13 x i8] c"Hello world!\00", align 1 ; <[13 x i8]*> [#uses=1]
 
define i32 @main() nounwind {
entry:
  %0 = tail call i32 @puts(i8* getelementptr inbounds ([13 x i8]* @.str, i64 0, i64 0)) nounwind ; <i32> [#uses=0]
  ret i32 0
}
 
declare i32 @puts(i8* nocapture) nounwind
获取DragonEgg-3.3源代码:
wget http://llvm.org/releases/3.3/dragonegg-3.3.src.tar.gz
打开包装:
tar xzf dragonegg--3.3.src.tar.gz
安装LLVM的3.3版,例如下载并安装适用于现有平台的LLVM-3.3二进制文件(称为clang二进制文件)。
确保安装了gcc-4.5、gcc-4.6、gcc-4.7或gcc-4.8(不需要构建自己的副本);gcc-4.6效果最好。在Debian和Ubuntu上,需要安装相应的插件开发包(gcc-4.5-plugin-dev、gcc-4.6-plugin-dev等)。
执行
       GCC=gcc-4.6 make
(如果使用gcc-4.6;否则用目前现存的gcc版本替换gcc-4.6)在dragonegg--3.3.src目录中应该构建dragonegg.so。如果LLVM二进制文件不在目前路径中,那么可以使用
GCC=gcc-4.6 LLVM_CONFIG=directory_where_llvm_installed/bin/llvm-config make
如果只构建了LLVM而没有安装它,那么仍然可以通过将LLVM_CONFIG设置为指向构建树中LLVM配置的副本来构建dragonegg。
要使用dragonegg.so,请使用gcc-4.6或使用的任何版本的gcc编译一些内容,在命令行中添加-fplugin=path_To_dragonegg/dragoneggso。
获取开发版本
获取DragonEgg开发版本的源代码:
svn http://llvm.org/svn/llvm-project/dragonegg/trunk
获取LLVM开发版本的源代码:
svn http://llvm.org/svn/llvm-project/llvm/trunkllvm
以通常的方式构建LLVM。
安装gcc-4.5、gcc-4.6、gcc-4.7或gcc-4.8(不需要构建自己的副本)。在Debian和Ubuntu上,需要安装相应的插件开发包(gcc-4.5-plugin-dev、gcc-4.6-plugin-dev等)。
执行
       GCC=place_you_installed_gcc/bin/gcc make
然后,在dragonegg目录中构建dragonegg.so。
要使用dragonegg.so,请使用刚安装的gcc版本编译一些组件,在命令行中添加-fplugin=path_to_dragonegg/dragonegg.so。
2.5 构建RISCV LLVM并运行test-suite
2.5.1 RISCV前期准备
RISCV LLVM 源码地址:
llvm/llvm-project
​github.com/llvm/llvm-project
做好工具链的安装:
可以参考
sunshaoce/learning-riscv
​github.com/sunshaoce/learning-riscv/blob/main/2/2.md
包含多个模拟器的安装。
做好环境路径设置的准备,可以提前统一设置为:
export PATH="$HOME/riscv/bin:$PATH"
$ sudo apt update
$ sudo apt install cmake ninja-build
2.5.2开始构建
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
mkdir build && cd build
## 链接过程硬盘空间可能不足,请指定DLLVM_PARALLEL_LINK_JOBS限制并行ld数量。
$ cmake -DLLVM_PARALLEL_LINK_JOBS=3 -DLLVM_TARGETS_TO_BUILD="X86;RISCV" -DLLVM_ENABLE_PROJECTS="clang" -G Ninja ../llvm
$ ninja
$ ninja check ###可能出现failed,暂时不用管,不影响后续工作
2.5.3编译test-suite
参考链接:
test-suite Guide
​llvm.org/docs/TestSuiteGuide.html
1、检测llvm是否构建成功
<path to llvm build>/bin/llvm-lit --version  ###路径可以直接写全路径
2、下载test-suite
git clone https://github.com/llvm/llvm-test-suite.git test-suite
3、构建suite,注意路径
cmake -DCMAKE_C_COMPILER=/llvm/rvv-llvm/build/bin/clang  -C../test-suite/cmake/caches/O3.cmake  ../test-suite
4、make
由于此过程耗时过长(第一次的话至少半天起步),首先要将几个报错提前修改
报错1:
/usr/bin/ld: CMakeFiles/Dither.dir/orderedDitherKernel.c.o: relocation R_X86_64_32S against `.rodata' can not be used when making a PIE object; recompile with -fPIE
/usr/bin/ld: CMakeFiles/Dither.dir/__/utils/glibc_compat_rand.c.o: relocation R_X86_64_32S against `.bss' can not be used when making a PIE object; recompile with -fPIE
collect2: error: ld returned 1 exit status
make[2]: *** [MicroBenchmarks/ImageProcessing/Dither/CMakeFiles/Dither.dir/build.make:146: MicroBenchmarks/ImageProcessing/Dither/Dither] Error 1
make[1]: *** [CMakeFiles/Makefile2:14633: MicroBenchmarks/ImageProcessing/Dither/CMakeFiles/Dither.dir/all] Error 2
make: *** [Makefile:130: all] Error 2
解决办法,在test-suite下修改文件中的
CMAKE_C_FLAGS:STRING = -fPIE
CMAKE_CXX_FLAGS:STRING = -fPIE
tips:由于文件过多,建议使用命令:grep -nir "xxxx"去找这两句,然后修改
报错2:
[ 37%] Building CXX object MicroBenchmarks/XRay/ReturnReference/CMakeFiles/retref-bench.dir/retref-bench.cc.o
/home/removed/release/test-suite/MicroBenchmarks/XRay/ReturnReference/retref-bench.cc:18:10: fatal error:
      'xray/xray_interface.h' file not found
#include "xray/xray_interface.h"
         ^~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
make[2]: *** [MicroBenchmarks/XRay/ReturnReference/CMakeFiles/retref-bench.dir/build.make:63: MicroBenchmarks/XRay/ReturnReference/CMakeFiles/retref-bench.dir/retref-bench.cc.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:19890: MicroBenchmarks/XRay/ReturnReference/CMakeFiles/retref-bench.dir/all] Error 2
make: *** [Makefile:130: all] Error 2
```
解决办法:
MicroBenchmarks/CMakeLists.txt 中注释掉add_subdirectory(XRay)
tip:如果一时半会没找到文件,同样建议使用上面的字符串搜索命令。
应该是在build下的test-suite或者test-suite-build下
2.5.4运行llvm test-suite
注意加上全路径执行
$ llvm-lit -v -j 1 -o results.json .
# Make sure pandas and scipy are installed. Prepend `sudo` if necessary.
$ pip install pandas scipy
# Show a single result file:
$ test-suite/utils/compare.py results.json
交叉编译RISCV的llvm test-suite
1. 在clang_riscv_linux.cmake中配置工具链信息
在test-suite-build下新建riscv-build目录
mkdir riscv-build && cd riscv-build
新建配置文件clang_riscv_linux.cmake
内容如下:
set(CMAKE_SYSTEM_NAME Linux )
set(triple riscv64-unknown-linux-gnu )
set(CMAKE_C_COMPILER /llvm/llvm-project/build/bin/clang CACHE STRING "" FORCE)
set(CMAKE_C_COMPILER_TARGET ${triple} CACHE STRING "" FORCE)
set(CMAKE_CXX_COMPILER /llvm/llvm-project/build/bin/clang++ CACHE STRING "" FORCE)
set(CMAKE_CXX_COMPILER_TARGET ${triple} CACHE STRING "" FORCE)
set(CMAKE_SYSROOT /root/riscv/linux/sysroot )
set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN  /root/riscv/linux/)
set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN  /root/riscv/linux/)
###操作时请注意每一个路径的修改
2. cmake和make
cmake -DCMAKE_TOOLCHAIN_FILE=/llvm/llvm-projects/build/test-suite-build/riscv-build/clang_riscv_linux.cmake  -DCMAKE_C_COMPILER="/llvm/llvm-project/build/bin/clang"  ../
可能会报错:
OMPILER="/llvm/llvm-projects/build/bin/clang"  ../
-- The C compiler identification is unknown
CMake Error at CMakeLists.txt:7 (project):
  The CMAKE_C_COMPILER:
    /llvm/llvm-projects/build/bin/clang
  is not a full path to an existing compiler tool.
解决办法:
根据前面的参考,做好工具链的安装配置,修改好对应的路径。
3. 在模拟器上运行交叉编译的test-suite
-需要安装dtc
apt-get install device-tree-compiler
- ld加上选项-static
在CMakeCache.txt中修改下面的配置项
//Flags used by the linker during all build types.
CMAKE_EXE_LINKER_FLAGS:STRING= -static
apt-get install device-tree-compiler
首先做好spike跟pk的安装,关于这两个模拟器的安装还是参考前面的链接
spike的安装:这里的RISCV$对应一开始设置的环境变量。
这是另外一个模拟器spike,依旧是先下载源代码:
git clone https://github.com/riscv/riscv-isa-sim.git
然后编译newlib版:
RISCV$ cd riscv-isa-sim
riscv-isa-sim$ mkdir build
riscv-isa-sim$ cd build
build$ ../configure --prefix=$RISCV/newlib  #linux版为$RISCV/linux
build$ make # 内存较大可用 -j $(nproc)
build$ make install
PK的安装:
riscv/riscv-pk
​github.com/riscv/riscv-pk
tip:这里要注意
或者,GNU/Linux工具链可以用来构建这个工具链,方法是设置
--host=riscv64-unknown-linux-gnu.
所以在进行构建时,注意后面参数的修改
```shell
root@e7299bcbf9e1:~/llvm/projects/llvm-test-suite-main/riscv-build/SingleSource/Benchmarks/Linpack# spike --isa=RV64gc /root/bin/riscv64-unknown-linux-gnu/bin/pk functionobjects
```
注意pk需要是linux/gnu版本的。
有一些可以成功的运行,有一些测试程序需要用到动态链接库的,就会出错。
试了一下这个程序是可以正确执行的:
SingleSource/Benchmarks/BenchmarkGame/fannkuch
仿真的命令是:
```shell
spike --isa=RV64gc /root/bin/riscv64-unknown-linux-gnu/bin/pk fannkuch > fannkuch.result 2>&1
2.6 介绍Clang extras tools
  LLVM最显著的设计决策是分离前端和后端,分别为单独的LLVM核心库和Clang。LLVM起步于一套以LLVM中间表示(IR)为基础的工具,依赖于一个定制的GCC,用GCC将高级语言翻译为它的特殊IR,存储为bitcode(位码)文件。Bitcode是效仿Java bytecode文件而新造的一个术语。Clang作为由LLVM团队特别设计的第一前端,当它如核心LLVM一般具备高水准的品质、清晰的文档、库组织方式时,它成为LLVM项目的一个重要里程碑。Clang不仅将C和C++转换为LLVM IR,而且能够监督整个编译过程,作为一个灵活的编译器驱动器,努力与GCC兼容共处。
  自此以后,将Clang看作一个前端编译器,而不是一个编译器驱动器,它负责将C和C++程序翻译为LLVM IR。利用Clang可以写出强大的工具,让C++程序员能够自由地探索C++热点技术,例如C++代码重构工具和源代码分析工具。这是Clang程序库激动人心的一面。Clang自带的一些工具或许能让见识其程序库的用途:
Clang Check:它能够作语法检查,实施快速修正以解决常见的问题,还能够输出任意程序的内部Clang抽象语法树(AST,Abstract Syntax Tree)
Clang Format:它是一个工具,也是一个库,LibFormat,它既能整理代码缩进,也能格式化任意的C++代码,使之符合LLVM编码标准、Google的风格规范、Chromium的风格规范、Mozilla的风格规范、和WebKit的风格规范
  代码仓库clang-tools-extra收集了更多建立在Clang之上的应用程序。它们能够读入大型C或C++代码库并执行各种代码重构或分析。下面列举其中一些工具,但不仅限于此:
Clang Modernizer:这是一个代码重构工具,它扫描C++代码并将旧风格的结构转换为符合更现代的风格,这些新风格是由新的标准提议的,例如C++-11标准
Clang Tidy:这是一个剥绒机工具,它检查常见的编程错误,这些错误违背了LLVM或者Google的编码标准。
Modularize:帮助找出适合组成一个模块(module)的C++头文件。
Trace:这是一个跟踪Clang C++预处理器的活动的简单工具
  至于如何运用这些工具,以及如何编译自己的工具,在介绍Clang工具和LibTooling内容时,将详细地介绍它们。
编译和安装Clang附加(extra)工具
  可以获取这个项目的官方源代码,例如3.4版本:http://releases.llvm.org/3.4/clang-tools-extra-3.4.src.tar.gz。另外,也可以查看所有可获取的版本:http://releases.llvm.org/。凭借LLVM编译系统,将这套工具和核心LLVM、Clang源代码一起编译,编译轻而易举。这要求按如下方式将其源代码目录放在Clang源代码树中:
$ wget http://releases.llvm.org/3.4/clang-tools-extra-3.4.src.tar.gz
$ tar xzf clang-tools-extra-3.4.src.tar.gz
$ mv clang-tools-extra-3.4 llvm/tools/clang/tools/extra
  也可以直接从LLVM官方subversion仓库获取源代码:
$ cd llvm/tools/clang/tools
$ svn co http://llvm.org/svn/llvm-project/clang-tools-extra/trunk extra
  如果想获取稳定的3.4版源代码,就用tags/RELEASE_34/final替换trunk。另外,如果喜欢使用GIT版本控制软件,可以用下面的命令下载:
$ cd llvm/tools/clang/tools
$ git clone http://llvm.org/git/clang-tools-extra.git extra
  把源代码放在Clang树中之后,必须用Cmake或者自动工具生成的configure脚本去编译。按如下方式运行clang-modernize工具,可测试是否安装成功:
$ clang-modernize --version
clang-modernizer version 3.4