前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >riscv gcc工具链是如何被编译的

riscv gcc工具链是如何被编译的

作者头像
bigmagic
发布2022-01-10 08:10:09
1.8K0
发布2022-01-10 08:10:09
举报
文章被收录于专栏:嵌入式iot

riscv gcc工具链是如何被编译的

  • 概述
  • 编译器编译原理
    • 历史背景
    • gcc工具链是如何工作的?
    • 工具链中有哪些组件?
  • 工具链的构建顺序
  • riscv gcc编译器的目录结构
  • riscv gcc编译器的构建
  • 编译最小支持RVB和RVV的riscv gcc

概述

gcc工具链是一个复杂而又巧妙的工程,随着riscv上层软件的逐渐完善,工具链和底层系统软件的开发也显得尤为重要。深入理解gcc的原理,能够更好的对计算机体系结构有一个完整的了解。

但是由于gcc的源码过于复杂,其诞生的年代也十分久远,入手gcc也相当棘手。而riscv是一个新的体系架构,在该架构上去理解gcc的开发和编译过程,不会有许多的历史包袱,这也是后面文章中主要进行分析的架构。

编译器编译原理

历史背景

GNU C编译器的早期作者是GNU项目的创始人Richard Stallman。

GNU项目最早开始于1984年,其目的是为了创建一个完全自由免费的类Unix的操作系统。由于每一个类Unix的操作系统都需要一个C编译器作为支持,但是当时并没有免费使用的编译器。所以GNU项目不得不从头开始进行编译器的开发工作。在1987年,第一个GCC正式版本诞生,这是一个免费发布并且可移植的编译器,此后GCC成为开发免费软件最重要的工具之一。

后来随着编译语言的增多,包括Fortran,ada,Java与Objective-C也被支持。

gcc工具链是如何工作的?

gcc工具链并不是一个单独的程序,而是一系列程序的合集,这些工具以一种串联的方式进行排列。

其中就包括预处理,编译,汇编,链接等过程。这种特性的特点就是上一个步骤的输出结果总是下一个过程的输入,最后生成了特定架构所需的可执行的文件。按照这种方式组合,形成了"工具链",当为不同的架构生成机器代码时,称为交叉编译工具链。

工具链中有哪些组件?

下图展示了riscv gcc编译完成后的组件。当然,最新发挥作用的是编译器gcc本身,将C文件转换成汇编代码。

汇编代码则由汇编器进行链接,生成特定的机器代码。

下面通过一个表格简单的描述一下

工具

功能

addr2line

可以将指令的地址转换成文件名,函数名和源代码行数的工具

ar

库管理器,创建静态库

as

汇编器,主要处理汇编代码

objcopy

将文件转换成另外一种格式,比如.bin转换成.elf,或者将.elf转换成.bin

objdump

反汇编

readelf

显示elf相关的信息

size

列出可执行文件的每一部分的尺寸,代码段,数据段等大小信息

通过上述一些列的工具,可以将C语言转换成可以执行的代码程序,但是现在还缺少在目标机器上运行程序时的C库,C库提供了一个标准的抽象层,可以执行基本的任务,包括内存分配、终端输出、文件访问等等。对于不同的系统,也有着不同的C库,比如针对Linux桌面环境,有glibc或者eglibc或者uClibc等等。对于嵌入式Linux,可以选择eglibc或者uClibc,对于没有任何操作系统或者RTOS来说,可以使用newlib,甚至可以不使用。还有一些小众的C库,针对特定的需要进行设计,比如针对ramdisk优化的klibc等等。

工具链的构建顺序

这些工具构建需要一定的顺序,这是一件有趣的事情。

  • 编译出的编译器需要C库
  • 编译出C库需要编译器

这就带来一个问题,A依赖B,B也依赖A。这就是典型的先有鸡,后有蛋的问题。

编译器首先会构建一个不需要C库就能构建出来的精简编译器,这部分我们称为引导程序、初始编译器或者核心编译器。

  • 最后的编译器需要C库
  • 编译出C库需要编译器
  • 编译器需要C库的头文件和引导程序

现在的问题变成了编译C库需要的头文件和引导文件。由于C库提供了"C runtime",也被称为CRT,但是这部分的编译还是需要依赖编译器。

  • 最后的编译器需要C库
  • 编译出C库需要编译器
  • 编译器需要C库的头文件和引导程序
  • 编译C库的引导程序

这样问题可能就变得简单一些了,我们只需要构建一个简单的编译器,他不需要C库头文件但是需要启动文件,该编译器同时也是C库的引导程序。我们称为这个简易编译器为pass1。最后完整的编译器为pass2。下面则变得清晰了起来:

  • 最后的编译器需要C库
  • 编译出C库需要编译器
  • 编译器需要C库的头文件和引导程序
  • 编译C库的引导程序
  • 我们需要一个简易的编译器

这样下来,我们就可以得到一个编译器所需要的C库了,然后完整的编译出最终的编译器。

上述只是一个基本的流程概述,实际上过程比这个更加的复杂。

下面来看riscv gcc编译后生成的文件夹

带有build前缀的都是编译器编译时的中间产物。可以看到build-gcc-newlib-stage1build-gcc-newlib-stage2

实际上gcc在编译过程中编译了三次:

  • 编译额外的C编译器(stage1)
  • 用stage1的编译器重新编译GCC编译器(stage2)
  • 用stage2的编译器再次编译GCC编译器(stage3)

stage2和stage3是为了更好的检查GCC编译的准确性,同时,也可以采用不同的优化等级对最后生成的gcc工具链进行优化。

其实stage2和stage3产生的应该是一样的结果,可以采用make compare来进行校验,由于这种严密的编译机制,可以使得最后的gcc生成的产物变得准确无误。

riscv gcc编译器的目录结构

在了解如何编译之前,首先看一下riscv gcc仓库有哪些东西。

代码语言:javascript
复制
https://github.com/riscv-collab/riscv-gnu-toolchain
  • qemu

工具链仓库的qemu左右是为了测试使用,结合riscv gcc的dejagnu测试框架,测试功能

代码语言:javascript
复制
make report-linux SIM=qemu # Run with qemu
  • riscv-binutils

该目录用于binutils生成,比如ar,ld等二进制工具,工具集合,也是非常重要的一个仓库。

  • riscv-dejagnu

dejagnu测试框架是测试gcc和binutils重要的工具,是保证gcc和binutils功能正常的非常重要的测试框架。

  • riscv-gcc

gcc主要的程序

  • riscv-gdb

通过外设接口,可以通过gdb调试

  • riscv-glibc

支持编译的程序在Linux运行的glibc库

  • riscv-newlib

支持编译的程序在rtos或者baremetal上运行的的C库

riscv gcc编译器的构建

当前公认的riscv gcc主线在

代码语言:javascript
复制
https://github.com/riscv-collab/riscv-gnu-toolchain

该master分支是稳定的可以使用的riscv 版本。但是现在做riscv扩展指令集分析,这里选择

代码语言:javascript
复制
https://github.com/riscv-collab/riscv-gnu-toolchain/tree/basic-rvv

该分支实现了也就是riscv b扩展,v扩展的最小支持。该分支作为入手gcc分析是最好的选择。

下载代码

代码语言:javascript
复制
git clone https://github.com/riscv-collab/riscv-gnu-toolchain.git
cd riscv-gnu-toolchain
git checkout basic-rvv
git submodule init && git submodule update

在官方的readme.md中介绍了如何编译的流程。

大概介绍一下:

该编译器支持两种libc库,支持rtos和barematel的newlib库和支持Linux的glibc。

默认使用make时,链接的是newlib库,使用make linux时,链接的是glibc。

同时由于riscv有着非常多的arch组合,可以编译单独的arch,比如

代码语言:javascript
复制
./configure --prefix=/opt/riscv --with-arch=rv32gc --with-abi=ilp32d

那么只编译arch是rv32gc,abi为ilp32d的组合。

如果同时支持rv32和rv64的组合配置,可以选择--enable-multilib

代码语言:javascript
复制
./configure --prefix=/opt/riscv --enable-multilib

不同的组合有着不同的需求,如果只是针对不同的芯片编译出的编译器,那么只选择一个配置即可,若需要发布更多组合的arch支持编译器,可以选择multilib。

编译最小支持RVB和RVV的riscv gcc

可以选择下面的配置

代码语言:javascript
复制
./configure --prefix=$RISCV/learn/  --with-arch=rv64gcv_zba_zbb_zbc_zbs --with-abi=lp64d --with-cmodel=medany --with-multilib-generator="rv64gcv_zba_zbb_zbc_zbs-lp64d--"
make -j $(nproc)

这里的RVV可以直接采用gcv即可,而RVB则被拆分成各种子扩展。

代码语言:javascript
复制
Zba - address generation
Zbb - basic bit manipulation
Zbc - carryless multiplication
Zbs - single-bit instructions

最后生成的riscv gcc工具链可以做测试。

结合编译参数,开启O2优化。

代码语言:javascript
复制
'-march=rv64gcv_zba_zbb_zbc_zbs' ,'-mabi=lp64d'

可以生成带有RVB扩展的格式的代码。

不难看出,当写出下面的函数时

代码语言:javascript
复制
long sh1add_test(long a, long b)
{
  return a + (b << 1);
}

而开启rvb扩展时的反汇编代码:

代码语言:javascript
复制
long sh1add_test(long a, long b)
{
    800000a2:   1141                    addi    sp,sp,-16
    800000a4:   e422                    sd      s0,8(sp)
    800000a6:   0800                    addi    s0,sp,16
  return a + (b << 1);
}
    800000a8:   6422                    ld      s0,8(sp)
    800000aa:   20a5a533                sh1add  a0,a1,a0
    800000ae:   0141                    addi    sp,sp,16
    800000b0:   8082                    ret

不开启rvb扩展时的反汇编代码如下:

代码语言:javascript
复制
long sh1add_test(long a, long b)
{
    800000a2: 1141                 addi sp,sp,-16
    800000a4: e422                 sd s0,8(sp)
    800000a6: 0800                 addi s0,sp,16
  return a + (b << 1);
}
    800000a8: 6422                 ld s0,8(sp)
  return a + (b << 1);
    800000aa: 0586                 slli a1,a1,0x1
}
    800000ac: 952e                 add a0,a0,a1
    800000ae: 0141                 addi sp,sp,16
    800000b0: 8082                 ret

由此可见开启rvb扩展优化后,代码的code size和性能均会提高。

那么这个优化在gcc中是如何实现的,后面文章中会慢慢的提及。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式IoT 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • riscv gcc工具链是如何被编译的
    • 概述
      • 编译器编译原理
        • 历史背景
        • gcc工具链是如何工作的?
        • 工具链中有哪些组件?
      • 工具链的构建顺序
        • riscv gcc编译器的目录结构
          • riscv gcc编译器的构建
            • 编译最小支持RVB和RVV的riscv gcc
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档