编译器工具链(一)——编译过程

前言

随着芯片行业的不断发展,编译器技术变得比以往任何时候都重要。从大到小的技术公司都需要编译器工程师。随着物联网设备、ML基础设施等越来越多的采用,对编译器工程师的需求在过去几年中急剧增加。
大多数大型科技公司都有编译器工程师在开源或闭源编译器上进行开发。有些编译器工程师致力于编译器优化,有些则致力于优化消耗大量设备资源的重要应用程序。几乎所有的硬件供应商都需要编译工程师来帮助启动处理器。由于RISC-V使处理器芯片组的设计产生变得更加容易,RISC-V生态系统对编译器工程师的需求也相应地增加了。对于工具链专家的需求是不言而喻的,他们可以帮助RISC-V供应商利用他们在编译技术方面的专业知识发展相对于竞争对手的优势。了解编译器工具链的内部是对计算核心技术感兴趣的第一步。

编译器简介

编译器是一种将程序从一种语言转换为另一种语言的系统。一般来说,我们将编译器称为将高级语言(如C、c++)转换为低级语言(如汇编语言或可执行格式)的程序。开源编译器例如gcc、clang通常用来实现这一功能。这些编译器相当复杂,简单来说,它们可以:

  • 支持多种高级语言:C, c++, Fortran, Objective C等。
  • 支持多个架构:ARM, Aarch64, MIPS, RISC-V, WebAssembly, X86-64等。
  • 优化程序,使其运行得更快:通过循环展开、内联、向量化等优化方法。
  • 提供检测以及其他软件功能:例如静态分析、警告等。
  • 提供可供其他源代码自省和转换使用的API。
  • 提供用于性能分析和程序自省的源代码检测功能。

什么是编译器工具链?

你是否也曾好奇编译一个简单的hello world程序需要依赖哪些。即使一个小小的hello world程序也需要编译器使用一堆头文件和库。那些在hello world程序中找不到的函数声明(例如 std::cout)需要通过头文件(例如 iostream)来查找。在链接过程中需要通过库来查找函数定义(例如 ,std::operator<<)。最终可执行文件作为整个编译过程的结果,才能放到机器上运行。

编译过程

当使用g++编译器编译一个c++程序时,编译过程实际上涉及多个步骤,可通过-v选项查看具体步骤。可通过以下hello world程序来了解编译过程:

#include
int main() {
   std::cout << "Hello world\n";
   return 0;
}

通过-v选项查看g++编译器调用的输出。详细的调用输出了大量信息,但其中相关的几行分别是编译器调用、汇编器调用和链接器调用。有人会认为g++ hello.cpp就是编译器调用:这只能算是部分正确,因为g++并不是一个编译器,它是一个编译器驱动程序。编译器驱动程序是一个程序,它调用编译器工具链中的不同工具从而将源代码转换为目标语言。
以下是编译器cc1puls的调用:

$ g++ hello.cpp -v
/usr/lib/gcc/x86_64-linux-gnu/7/cc1plus -quiet -v -imultiarch
x86_64-linux-gnu -D_GNU_SOURCE hello.cpp -quiet -dumpbase hello.cpp
-mtune=generic -march=x86-64 -auxbase hello -version
-fstack-protector-strong -Wformat -Wformat-security -o 
...
GGC heuristics: --param ggc-min-expand=100 --param
ggc-min-heapsize=131072
ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/7"
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory
"/usr/lib/gcc/x86_64-linux-gnu/7/../../../../x86_64-linux-gnu/include"
include "…" search starts here:
include <…> search starts here:
/usr/include/c++/7
/usr/include/x86_64-linux-gnu/c++/7
...

正如我们以上命令中看到的,编译器将hello.cpp编译生成汇编文件/tmp/ccWH0EQc.s。编译过程中,cc1plus需要找到头文件iostream(在/usr/include/c++/7中)。
接下来是汇编器调用。它读取编译器的输出(例如/tmp/ccWH0EQc.s)作为输入,并输出一个“目标文件” /tmp/ccTpqU8Z.o。汇编器不需要依赖:

/usr/bin/x86_64-linux-gnu-as -v --64 -o /tmp/ccTpqU8Z.o /tmp/ccWH0EQc.s

最后是链接器调用。链接器collect2读取汇编器的输出 /tmp/ccTpqU8Z.o (目标文件),并输出可执行文件。链接器需要依赖许多,其中最有趣的是运行时支持文件(例如 crt1. o, crti. o, crtendS. o, crtn. o)和标准库(例如 libc, libgcc, libgcc_s, libm等)。现在链接器需要知道这些文件都在哪里,实际上是编译器驱动程序g++需要知道这些文件在哪里,以便它调用链接器通过合适的路径(查看-L开头的选项)去链接合适的库(查看-l开头的选项)。

/usr/lib/gcc/x86_64-linux-gnu/7/collect2 -plugin
/usr/lib/gcc/x86_64-linux-gnu/7/liblto_plugin.so
-plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
-plugin-opt=-fresolution=/tmp/cc2j00rN.res
-plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc
-plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s
-plugin-opt=-pass-through=-lgcc --sysroot=/ --build-id --eh-frame-hdr
-m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker
/lib64/ld-linux-x86-64.so.2 -pie -z now -z relro
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
-L/usr/lib/gcc/x86_64-linux-gnu/7
-L/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu
-L/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib
-L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu
-L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/7/../../..
/tmp/ccTpqU8Z.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o

简单来说,编译器工具链是一组工具、支持库和头文件,可以将程序从源代码转换为可以在机器上运行的可执行文件。

References:

https://learning.edx.org/course/course-v1:LinuxFoundationX+LFD113x+3T2021/block-v1:LinuxFoundationX+LFD113x+3T2021+type@sequential+block@5118c32dc04a485db2938fcc8ccb69de/block-v1:LinuxFoundationX+LFD113x+3T2021+type@vertical+block@8f97a40b26654b058b5cc5833f934201

若文章中有出错或者不清楚的地方欢迎大家讨论指教!!!

你可能感兴趣的:(编译器工具链,c++,开发语言)