(交叉)编译工具链组成部分分析

发布时间 2023-10-17 05:33:26作者: 吴建明wujianming
(交叉)编译工具链组成部分分析
  GUN 交叉编译工具链中有三个核心组件:Binutils、GCC、C库,如果需要支持 Linux,则还有个 Linux kernel headers。在源代码组织上他们是相互独立的,需要单独进行交叉编译。
Binutils:包括一些二进文件相关的工具。
1.主要工具
主要工具,归纳如下:
(1)ld 链接器。
(3)as 汇编器。
2.调试/分析工具和其他工具
(1)调试/分析工具和其他工具,归纳如下:
addr2line、ar、c++filt、gold、gprof、nm、objcopy、objdump、ranlib、
readelf、size、strings、strip。
(2)需要针对每种 CPU 架构进行配置。
(3)交叉编译非常简单,不需要特殊的依赖项。
3.gcc工具
gcc(GNU Compiler Collection)使用场景,归纳如下:
(1)C、C++、Fortran、Go 等编译器前端。
(2)各种 CPU 架构的编译器后端。
不要被 gcc 这个名字误导,它其实是个 wrapper,会根据输入文件调用一系列其他程序。国外资料中被称为 compiler driver,国内有些资料称为引导器。构建 gcc 比构建 binutils 要复杂的多。
4.Provides
 供应商分类:
(1)编译器本身。例如 cc1 for C、cc1plus for C++。
(2)编译器调用程序。gcc、g++ 不但调用编译器本身,也调用 binutils 中的
汇编器、连接器。
5.引导器分类
引导器分类,归纳如下:
(1)目标库:libgcc(gcc 运行时)、libstdc ++(c ++ 库)、libgfortran
(fortran运行时)。
(2)标准 c++ 库的头文件。
Linux内核头文件:构建需要支持 Linux 系统时必须提供。这些头文件定义了用户空间与内核之间的接口(系统调用、数据结构等)。
(1)为了构建一个 C 库,需要 Linux 内核头文件中系统调用号的定义、各种结构
类型和定义。
(2)在内核中,头文件被分开。
(3)一种头文件是用户空间可见的头文件,存储在 uapi 目录中:include/uapi/、arch/<ARCH>/include/uapi/asm。
(4)另一种头文件是内部的内核头文件。
6.在安装过程中需要使用的工具
在安装过程中需要使用的工具,归纳如下:
(1)安装包括一个清理过程,用于从头文件中删除特定于内核的结构体。
(2)从 Linux 4.8 开始,安装 756 个头文件。
(3)内核到用户空间 ABI 通常是向后兼容的。内核头文件的版本必须等于或者小于目标 Linux 的版本。
7.C库文件
C库文件,归纳如下:
(1)提供 POSIX 标准函数的实现,以及其他几个标准和扩展。
(2)基于 Linux 系统调用。
(3)几个可用的实现。
8.几个可用的C库文件实现
几个可用的C库文件实现,归纳如下:
(1)glibc:The GNU C Library 是 Linux C 库的事实标准,常见的 Linux 发行
版中都使用它。支持众多的架构和操作系统,但是不支持没有 MMU 的平
台,不支持静态链接。早些年由于硬件限制及 glibc 本身太大基本不能直接
用于嵌入式,如今貌似也可以了。
(2)uClibc-ng:以前叫 uClibc,始于 2000 年,支持非常灵活的配置。支持架构很多(包括一些 glibc 不支持的),但是仅支持 Linux 操作系统。支持多种没有 MMU 的架构,如 ARM noMMU、Blackfin 等,支持静态链接。STM32F MCU 没有 MMU,嵌入式 Linux 环境中编译工具链就是使用它。
(3)musl:始于 2011 年,开发非常积极,最近添加了对于 noMMU 的支持。它非常小,尤其是在静态链接时。兼容性好,并且严格遵循 C 标准。
(4)bionic:安卓系统使用。
(5)其他一些特殊用途的:newlib(用于裸机)、dietlibc、klibc。musl 的作者对于Linux 常用的这几个库做了一个对比,Linux 常用的这几个库做了一个
对比,见表1。
表1 Linux 常用的这几个库做了一个对比

扩展比较

musl

uClibc

dietlibc

glibc

Complete .a set

426k

500k

120k

2.0M †

Complete .so set

527k

560k

185k

7.9M †

最小的静态C程序

1.8k

5k

0.2k

662k

静态hello(使用printf)

13k

70k

6k

662k

动态开销 (min. dirty)

20k

40k

40k

48k

静态开销(min. dirty)

8k

12k

8k

28k

静态stdio开销 (min. dirty)

8k

24k

16k

36k

可配置功能集

no

yes

最小

最小

资源枯竭行为

musl

uClibc

dietlibc

glibc

本地线程存储

报告失败

失败

n/a

失败

SIGEV_THREAD计时器

无错误

n/a

n/a

超支失败

pthread_cancel

无错误

失败

n/a

失败

regcomp与regexec

报告失败

失败

报告失败

失败

fnmatch

无错误

unknown

无错误

报告失败

printf类

无错误

无错误

无错误

报告失败

strtol类

无错误

无错误

无错误

无错误

性能比较

musl

uClibc

dietlibc

glibc

小额分配和免费

0.005

0.004

0.013

0.002

大额分配和免费

0.027

0.018

0.023

0.016

分配争用,本地

0.048

0.134

0.393

0.041

分配争用,共享

0.050

0.132

0.394

0.062

零填充(memset)

0.023

0.048

0.055

0.012

字符串长度(strlen)

0.081

0.098

0.161

0.048

字节搜索(strchr)

0.142

0.243

0.198

0.028

子字符串 (strstr)

0.057

1.273

1.030

0.088

线程创建/连接

0.248

0.126

45.761

0.142

互斥锁/解锁

0.042

0.055

0.785

0.046

UTF-8解码缓冲

0.073

0.140

0.257

0.351

UTF-8逐字节解码

0.153

0.395

0.236

0.563

Stdio putc/getc

0.270

0.808

7.791

0.497

Stdio putc/getc解锁

0.200

0.282

0.269

0.144

Regex编译

0.058

0.041

0.014

0.039

Regex搜索(a{25}b)

0.188

0.188

0.967

0.137

Self-exec (静态链接)

234µs

245µs

272µs

457µs

Self-exec (动态链接)

446µs

590µs

675µs

864µs

ABI和版本控制比较

musl

uClibc

dietlibc

glibc

稳定的ABI

yes

no

非正式

yes

LSB兼容ABI

不完整

no

no

yes

向后兼容性

yes

no

非正式

yes

前向兼容性

yes

no

非正式

no

原子升级

yes

no

no

no

符号版本控制

no

no

no

yes

算法比较

musl

uClibc

dietlibc

glibc

子字符串搜索(strstr)

双向

天真的

天真的

双向

正则表达式

dfa

dfa

原路返回

dfa

排序(qsort)

平滑排序

shellsort

天真的 quicksort

向内排序

分配器 (malloc)

musl-native

dlmalloc

diet-native

ptmalloc

功能比较

musl

uClibc

dietlibc

glibc

合格打印

yes

yes

no

yes

精确浮点打印

yes

no

no

yes

C99数学库

yes

部分的

no

yes

C11线程API

yes

no

no

no

C11线程本地存储

yes

yes

no

yes

GCC libstdc++兼容性

yes

yes

no

yes

POSIX线程

yes

yes, on most archs

broken

yes

POSIX过程调度

stub

不正确

no

不正确

POSIX线程优先调度

yes

yes

no

yes

POSIX localedef

no

no

no

yes

宽字符界面

yes

yes

最小

yes

旧式8位代码页

no

yes

最小

slow, via gconv

传统CJK编码

no

no

no

slow, via gconv

UTF-8多字节

native; 100%合格

native; 不合格

危险的不合格

slow, via gconv; 不合格

Iconv字符转换

大多数主要编码

主要UTFs

no

the kitchen sink

Iconv音译扩展

no

no

no

yes

开放墙式TCB阴影

yes

no

no

no

Sun RPC, NIS

no

yes

yes

yes

Zoneinfo (高级时区)

yes

no

yes

yes

Gmon评测

no

no

yes

yes

调试功能

no

no

no

yes

各种Linux扩展

yes

yes

部分的

yes

目标体系结构比较

musl

uClibc

dietlibc

glibc

i386

yes

yes

yes

yes

x86_64

yes

yes

yes

yes

x86_64 x32 ABI (ILP32)

实验

no

no

不合格

ARM

yes

yes

yes

yes

Aarch64 (64-bit ARM)

yes

no

no

yes

MIPS

yes

yes

yes

yes

SuperH

yes

yes

no

yes

Microblaze

yes

部分的

no

yes

PowerPC (32- and 64-bit)

yes

yes

yes

yes

Sparc

no

yes

yes

yes

Alpha

no

yes

yes

yes

S/390 (32-bit)

no

no

yes

yes

S/390x (64-bit)

yes

no

yes

yes

OpenRISC 1000 (or1k)

yes

no

no

非upstream

摩托罗拉680x0 (m68k)

yes

yes

no

yes

MMU-less微控制器

yes, elf/fdpic

yes, bflt

no

no

构建环境比较

musl

uClibc

dietlibc

glibc

旧式代码友好型头文件

部分的

yes

no

yes

轻型头文件

yes

no

yes

no

无需本机工具链即可使用

yes

no

yes

no

尊重C命名空间

yes

LFS64问题

no

LFS64问题

尊重POSIX命名空间

yes

LFS64问题

no

LFS64问题

安全性/硬化性比较

musl

uClibc

dietlibc

glibc

注意角落案例

yes

yes

no

太多malloc

安全UTF-8解码器

yes

yes

no

yes

避免超线性big-O's

yes

有时

no

yes

栈溢出保护功能

yes

yes

no

yes

堆损坏检测

yes

no

no

yes

Misc. c比较

musl

uClibc

dietlibc

glibc

许可证

MIT

LGPL 2.1

GPL 2

LGPL 2.1+ w/例外情况

9.在编译和安装后,输出结果
在编译和安装之后,提供了以下输出结果:
(1)动态链接器 ld.so。
(2)C 库本身 libc.so,及其配套库:libm、librt、libpthread、libutil、libnsl、libresolv、libcrypt。
(3)C 库的头文件:stdio.h、string.h 等。
  GUN 将编译器和 C 库分开放在两个软件包里,好处是比较灵活,方便在工具链中可以选择不同的 C 库。但是,也带来了编译器和 C 库的循环依赖问题:编译 C 库需要 C 编译器,但是 C 编译器又依赖 C 库。理论上编译器是不应该依赖 C 库的,它应该只负责将源代码翻译为汇编代码即可,但实际上并非如此。
  C99 标准定义了两种实现:一种称为 hosted 实现,一种称为 freestanding 实现。其中,hosted 实现支持完整的 C 标准,包括语言标准和库标准,它用于编译在有宿主系统的环境下运行的程序。freestanding 实现仅支持完整的语言标准,对于库标准它只要求支持部分库标准。
构建(交叉)编译工具链分为好多步,而且单是编译 gcc 就要多次。
10.LLVM编译器
  传统编译器的工作原理基本上都是三段式的,可以分为前端(Frontend)、优化器(Optimizer)、后端(Backend)。前端负责解析源代码,检查语法错误,并将其翻译为抽象的语法树(Abstract Syntax Tree)。优化器对这一中间代码进行优化,试图使代码更高效。后端则负责将优化器优化后的中间代码转换为目标机器的代码,这一过程后端会最大化的利用目标机器的特殊指令,以提高代码的性能。
虽然这种三段式的编译器有很多有点,并且被写到了教科书上,但是在实际中,这一结构却从来没有被完美实现过。
回顾 GCC 的历史,虽然它取得了巨大的成功,但开发 GCC 的初衷是提供一款免费的开源的编译器,仅此而已。可后来随着GCC支持了越来越多的语言,GCC 架构的问题也逐渐暴露出来。
   LLVM 作为后起之秀,从开始就是按照前端(Frontend)、优化器(Optimizer)、后端(Backend)的三段式进行设计,整个编译器框架非常符合人们对于编译器的设计,以及非常容易理解和学习。
LLVM 的命名最早源自于底层虚拟机(Low Level Virtual Machine)的首字母缩写,但这个项目并不局限于创建一个虚拟机,开发者因而决定放弃这个缩写的意涵。现在 LLVM 是一个专用名词,表示编译器框架整个项目。
目前,很多平台都开始转投 LLVM了,例如苹果、安卓、ARM等等。