主要内容:
- 编译工具链
- Makefile Gcc Clang cmake
- ELF
编译:编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件。
编译工具链
简介
编译工具链是指用于将源代码编译成可执行文件的一组工具集合。它包括编译器、汇编器、链接器等工具,通常还包括调试器、性能分析器等辅助工具。
编译工具链的主要任务是将高级语言编写的源代码转换为目标机器上的机器码,以生成可执行文件或库。在这个过程中,编译器负责将源代码转换为汇编代码,汇编器将汇编代码转换为机器码,链接器将多个目标文件合并到一起,并解析符号依赖关系,最终生成最终的可执行文件或库。
常见的编译工具链包括GNU Compiler Collection (GCC)、LLVM/Clang、Microsoft Visual Studio、Intel C++ Compiler等。这些工具链可以在不同的操作系统和体系结构上使用,并提供了许多优化选项和调试功能,以方便软件开发人员进行程序开发和调试。
Gcc
简介
GCC(GNU Compiler Collection,GNU编译器套装),是一套由 GNU 开发的编程语言编译器。它是一套以 GPL 及 LGPL 许可证所发行的自由软件,也是 GNU计划的关键部分,亦是自由的类Unix及苹果电脑 Mac OS X 操作系统的标准编译器。
GCC 原名为 GNU C 语言编译器,因为它原本只能处理 C语言。GCC 很快地扩展,变得可处理 C++。之后也变得可处理 Fortran、Pascal、Objective-C、Java, 以及 Ada与其他语言。
GCC编译器已经被移植到比其他编译器更多的平台和指令集架构上,并被广泛部署在开发自由和专有软件的工具中。GCC还可用于许多嵌入式系统,包括基于ARM和Power ISA的芯片。
步骤
Gcc 编译的四个步骤:
- 预处理(E)
- 编译(S)
- 汇编(c)
- 链接(无)
暂时无法在飞书文档外展示此内容
Gcc 常用的参数:
在使用GCC编译器时候,必须给出一系列必要的调用参数和文件名称。
GCC最基本的用法是:
Gcc [options] [filenames]
其中options就是编译器所需要的参数(也称为编译选项),filename给出相关的文件名称。
.c 文件直接编译成可执行文件步骤:
- gcc GccTest.c -o GccTest
- ./GccTest 直接可以执行的指令
- (gcc GccTest.c -o GccTest -v )可以看到过程
Makefile
简介
Makefile是一种文本文件,其中包含了编译源代码(如C、C++等)的规则和命令。它是GNU Make工具的输入文件,通过解析Makefile中的规则和依赖关系,自动化执行编译、链接、打包等操作,从而生成目标二进制文件或库文件。
Makefile通常由以下内容组成:
- 变量定义:用于设置编译器、编译选项、目标文件名等参数的变量。
- 规则定义:用于描述源文件和目标文件之间的依赖关系及其对应的编译命令。
- 目标定义:用于指定最终生成的目标文件或库文件名称及其编译规则。
Makefile的规则:
- 依赖规则(Dependency Rules):用于描述每个目标文件所依赖的源文件和其他目标文件,以及如何将它们编译成中间文件。
例如:obj/main.o: src/main.c inc/common.h 表示main.o文件依赖于main.c和common.h文件,并通过gcc编译器将main.c编译成main.o文件。
- 编译规则(Compilation Rules):用于指定源文件的扩展名和编译命令,以及目标文件的扩展名和生成规则。
例如:.c.o: gcc -c $< -o $@ 表示使用gcc编译器将.c文件编译成.o文件,其中$<表示当前规则所依赖的源文件名,$@表示当前规则所生成的目标文件名。
Makefile是一种功能强大的自动化构建工具,它可以根据程序员定义的规则和依赖关系实现高效、灵活、可靠的代码构建过程。
Makefile的简单例子
文件a.c
```C
02 #include <stdio.h>
03
04 int main()
05 {
06 func_b();
07 return 0;
08}
```
文件b.c
```c
2 #include <stdio.h>
3
4 void func_b()
5 {
6 printf("This is B\n");
7 }
```
编译:
```bash
gcc -o test a.c b.c
```
运行:
```bash
./test
```
结果:
```bash
This is B
```
**gcc -o test a.c b.c** 这条命令虽然简单,但是它完成的功能不简单。
我们来看看它做了哪些事情,
我们知道.c程序 ==》 得到可执行程序它们之间要经过四个步骤:
* 1.预处理
* 2.编译
* 3.汇编
* 4.链接
我们经常把前三个步骤统称为编译了。我们具体分析:gcc -o test a.c b.c这条命令
它们要经过下面几个步骤:
* 1)对于**a.c**:执行:预处理 编译 汇编 的过程,**a.c ==>xxx.s ==>xxx.o** 文件。
* 2)对于**b.c**:执行:预处理 编译 汇编 的过程,**b.c ==>yyy.s ==>yyy.o** 文件。
* 3)最后:**xxx.o**和**yyy.o**链接在一起得到一个**test**应用程序。
提示:**gcc -o test a.c b.c -v** :加上一个**‘-v’**选项可以看到它们的处理过程
makefie最基本的语法是规则,规则:
```bash
目标 : 依赖1 依赖2 ...
[TAB]命令
```
当“依赖”比“目标”新,执行它们下面的命令。我们要把上面三个命令写成makefile规则,如下:
```bash
test :a.o b.o //test是目标,它依赖于a.o b.o文件,一旦a.o或者b.o比test新的时候,
就需要执行下面的命令,重新生成test可执行程序。
gcc -o test a.o b.o
a.o : a.c //a.o依赖于a.c,当a.c更加新的话,执行下面的命令来生成a.o
gcc -c -o a.o a.c
b.o : b.c //b.o依赖于b.c,当b.c更加新的话,执行下面的命令,来生成b.o
gcc -c -o b.o b.c
makefile内容:
test :a.o b.o
gcc -o test a.o b.o
a.o : a.c
gcc -c -o a.o a.c
b.o : b.c
gcc -c -o b.o b.c
Makefile语法
通配符
上节程序进行修改代码如下:
```bash
test: a.o b.o
gcc -o test $^
%.o : %.c
gcc -c -o $@ $<
```
%.o:表示所用的.o文件
%.c:表示所有的.c文件
$@:表示目标
$<:表示第1个依赖文件
$^:表示所有依赖文件
gcc 生成.d 作为依赖文件
Makefile常用函数
Clang
简介
Clang 是一个 C++ 编写、基于 LLVM、发布于 LLVM BSD 许可证下的 C/C++/Objective C编译器,其目标(之一)就是超越 GCC。
Low Level Virtual Machine (LLVM) 是一个开源的编译器架构
Clang是LLVM的一个子项目,基于LLVM架构的 C/C++/Objective C编译器前端。
相比于GCC ,Clang具有如下优点:
- 编译速度快:在某些平台上,Clang的编译速度显著的快过GCC(Debug模式下编译OC速度比GGC快3倍)
- 占用内存小:Clang生成的AST所占用的内存是GCC的五分之一左右
- 模块化设计:Clang采用基于库的模块化设计,易于 IDE 集成及其他用途的重用
- 诊断信息可读性强:在编译过程中,Clang 创建并保留了大量详细的元数据 (metadata),有利于调试和错误报告
- 设计清晰简单,容易理解,易于扩展增强
Clang 与LLVM关系
暂时无法在飞书文档外展示此内容
LLVM整体架构,前端用的是clang,广义的LLVM是指整个LLVM架构,一般狭义的LLVM指的是LLVM后端(包含代码优化和目标代码生成)。
源代码(c/c++)经过clang–> 中间代码(经过一系列的优化,优化用的是Pass) --> 机器码
LLVM
编译器LLVM(Low Level Virtual Machine)是一个开源的编译器基础设施。它提供模块化、可重用的编译器和工具链技术,可用于将高级编程语言代码转换为机器码。 编译器LLVM采用三阶段编译架构:前端、中间层和后端。前端负责将源代码解析为语法树,并进行语义分析、类型检查等操作。它支持多种编程语言,如C、C++、Rust、Swift等。中间层使用LLVM中间表示(IR)对源代码进行优化和转换。IR是一种低级、独立于特定语言和硬件的中间表示形式,它具有高度的可移植性和优化潜力。最后,后端将优化后的IR代码转换为特定硬件平台上的机器码。 LLVM编译器以其高度灵活和可扩展的特性而闻名。它提供了丰富的优化技术,如代码传播、循环优化、数据流分析等,以提高生成的机器码的性能和效率。此外,LLVM还支持Just-In-Time(JIT)编译技术,允许代码在运行时动态编译和优化。 值得一提的是,LLVM不仅仅是一个编译器,还是一个通用的编译基础设施。它提供了用于代码生成、静态分析、代码重整等多种工具和库。这使得LLVM成为许多编程语言(如Swift和Rust)的首选编译器。 总之,编译器LLVM是一个强大而灵活的开源编译器基础设施,它在学术界和工业界都得到了广泛的应用。它的模块化和可重用性使得它成为构建高性能编译器和工具链的理想选择。
Cmake
简介
CMake是一种跨平台的开源构建工具,它支持多种编译器、操作系统和目标平台,可以自动生成Makefile、Visual Studio项目文件等常见的构建脚本,并提供了丰富的模块化组件库和插件,方便程序员管理和构建复杂的软件项目。
CMake使用一种名为CMakeLists.txt的声明式语言来描述项目结构和编译规则,通过解析这个文件自动生成相应的构建文件。CMakeLists.txt文件由多个命令构成,用于定义变量、设置编译选项、指定源文件、链接库等操作,其中比较常用的命令包括:
- project:用于定义项目名称、版本号和语言类型。
- add_executable/add_library:用于定义可执行文件或库文件,并指定其对应的源文件列表。
- target_link_libraries:用于指定库文件之间的依赖关系,以及需要链接的第三方库。
- include_directories/link_directories:用于添加头文件搜索路径和库文件搜索路径。
- if/else/endif:用于根据条件判断是否需要编译某些源文件或链接某些库文件。
CMake还支持多种插件和扩展功能,如FindXXX模块用于查找特定的库文件、CTest模块用于测试代码、CPack模块用于打包发布等。
总之,CMake是一种非常灵活、易用、可扩展的构建工具,它为程序员提供了一种便捷的方式来管理和构建复杂的软件项目。
特点
- 开放源代码
- 跨平台
- 能够管理大型项目
- 简化编译构建过程和编译过程
- 高效率
- 可扩展
常用命令:
CmakeLists.txt 文件来描述文件过程。下面是常用命令:
project(xxx) #项目的名称;必须
add_subdirectory(子文件夹名称) #父目录必须,子目录不必
add_library(库文件名称 STATIC 文件) #通常子目录(二选一)
add_executable(可执行文件名称 文件) #通常父目录(二选一)
include_directories(路径) #必须
link_directories(路径) #必须
target_link_libraries(库文件名称/可执行文件名称 链接的库文件名称) #必须
另:
add_executable(demo demo.cpp) # 生成可执行文件
add_library(common STATIC util.cpp) # 生成静态库
add_library(common SHARED util.cpp) # 生成动态库或共享库
详细的命令,仅供后期查询:
# 本CMakeLists.txt的project名称
# 会自动创建两个变量,PROJECT_SOURCE_DIR和PROJECT_NAME
# ${PROJECT_SOURCE_DIR}:本CMakeLists.txt所在的文件夹路径
# ${PROJECT_NAME}:本CMakeLists.txt的project名称
project(xxx)
# 获取路径下所有的.cpp/.c/.cc文件,并赋值给变量中
aux_source_directory(路径 变量)
# 给文件名/路径名或其他字符串起别名,用${变量}获取变量内容
set(变量 文件名/路径/...)
# 添加编译选项
add_definitions(编译选项)
# 打印消息
message(消息)
# 编译子文件夹的CMakeLists.txt
add_subdirectory(子文件夹名称)
# 将.cpp/.c/.cc文件生成.a静态库
# 注意,库文件名称通常为libxxx.so,在这里只要写xxx即可
add_library(库文件名称 STATIC 文件)
# 将.cpp/.c/.cc文件生成可执行文件
add_executable(可执行文件名称 文件)
# 规定.h头文件路径
include_directories(路径)
# 规定.so/.a库文件路径
link_directories(路径)
# 对add_library或add_executable生成的文件进行链接操作
# 注意,库文件名称通常为libxxx.so,在这里只要写xxx即可
target_link_libraries(库文件名称/可执行文件名称 链接的库文件名称)
ELF
现代PC平台流行的可执行文件格式(Executable)主要是Windows下的PE(Portable Executable)和Linux下的ELF(Executable and Linkable Format),它们都是COFF(Common file format)格式的变种。
ELF文件标准里面把系统中采用ELF格式的文件归为下表所列举的4类。
ELF 文件类型 | 说明 | 实例 |
可重定位文件(Relocatable File) | 这类文件包含了代码和数据,可以被用来连接成可执行文件或共享目标文件,静态链接库也可以归为这一类。 | Linux的.o Winows的.obj |
可执行文件(Executable File) | 这类文明包含了可以直接执行的程序,它代表的就是ELF可执行文件,一般都没有扩展名。 | 比如/bin/bash 文件 Windows的.exe文件 |
共享目标文件(shared Object File) | 这种文件包含了代码和数据,可以在以下两种情况下使用。一种是链接器可以使用这种文件跟其他的可重定位文件和共享目标文件链接,产生新的目标文件。第二种是动态链接器可以将几个这种1共享目标文件与可执行文件结合,作为进程映射映像的一部分来运行。 | Linux 下的.so,如/lib/glibc-2.5.so Windows的DLL |
核心转储文件(Core Dump File) | 当进程意外终止时,系统可以将该进程的地址空间内容及终止时的一些其他信息转储到核心转储文件。 | Linux 下的core dump |
简介
ELF(Executable and Linkable Format)是一种可执行文件和可链接目标文件的格式。ELF文件包含了程序代码、数据、符号表、调试信息和重定位信息等多种元素,可以直接被操作系统装载并执行。
ELF文件由多个节(Section)组成,每个节对应一个特定类型的数据,如.text节存储程序代码、.data节存储全局变量、.rodata节存储只读数据等。同一类型的节通常会按照地址排序,并被分组成段(Segment),比如.text和.rodata段就是典型的代码段和数据段。
ELF文件主要有三种类型:
- 可重定向文件,即通过汇编产生的文件,后缀是.o,该文件不能直接运行
- 可执行文件,将多个可重定向文件和共享库文件通过链接产生,可以直接运行
- 共享库,如libc的共享库libc.so,该文件同样不能直接运行,同可重定向文件相比,最大的区别在于该文件不需要经过重定向处理。
ELF文件的格式如下:
链接阶段
ELF header |
Program Header Table(可选) |
session |
... |
session n |
... |
... |
Section Head Table |
执行阶段
ELF header |
Program Header Table(可选) |
session 1 |
session 2 |
... |
Section Head Table |
- ELF header: 描述整个文件的组织,包含ELF文件类型,硬件平台类型,程序执行入口, sections和segments的数量和起始偏移位置,大小等。
- Program Header Table: 描述文件中的各种segments,通常一个segment包含若干个属性(如读写权限等)相同的section,将section合并成segment是为了减少内存空间浪费,方便内存管理,section的大小是任意的,但是segment的大小必须是所在操作系统的内存页(如4KB)大小的整数倍。操作系统加载可执行文件时会把LOAD类型的segment映射至虚拟地址空间。可重定向文件中没有此项,只有可执行文件中才有。
- sections 或者 segments:具体的sections,sections是将汇编代码文件中的各种数据做归类保存,方便对其做内存分配与管理, 如.text section是可执行指令的集合,.data section包含初始化的全局变量,.bss section保存的是未初始化的全局变量和局部静态变量,.dynsym section记录了所有需要重定向处理的符号等。segments是从程序加载和运行的角度来描述elf文件,sections是从链接的角度来描述elf文件,也就是说,在链接阶段,我们可以忽略program header table来处理此文件,在运行阶段可以忽略section header table来处理此程序。
- Section Header Table: 包含了文件各个section的属性信息,比如起始偏移位置,大小等。
ELF文件的优点在于其跨平台、灵活、可扩展的特性,可以方便地在不同架构、不同操作系统之间进行移植和交叉编译,同时也为动态链接等高级特性提供了良好的基础支持。
实例
obj-y += irqchip/
obj-y += bus/
# reset controllers early, since gpu drivers might rely on them to initialize
obj-$(CONFIG_RESET_CONTROLLER) += reset/
obj-$(CONFIG_GENERIC_PHY) += phy/
这段代码是一个Makefile规则,用于编译Linux内核中的驱动程序。
obj-y
表示将目录下的所有文件都添加到目标列表中进行编译。+=
表示将指定目录添加到目标列表的末尾。irqchip/
和bus/
是两个目录名,它们包含了特定类型的驱动程序源代码。obj-$(CONFIG_RESET_CONTROLLER)
和obj-$(CONFIG_GENERIC_PHY)
是条件编译语句。如果配置选项CONFIG_RESET_CONTROLLER
和CONFIG_GENERIC_PHY
被启用,那么相应的目录reset/
和phy/
将会被添加到目标列表中进行编译。总体来说,这段代码的作用是将
irqchip/
、bus/
、reset/
和phy/
目录下的驱动程序源代码添加到编译目标列表中,以便在编译过程中将它们编译为对象文件,并最终链接到可执行内核中。
参考文档:
【CMake】CMakeLists.txt的超傻瓜手把手教程(附实例源码)_Yngz_Miao的博客-CSDN博客