软件的编译过程由一系列的步骤完成,每一个步骤都有一个对应的工具。这些工具紧密地工作在一起,前一个工具的输出是后一个工具的输入,像一根链条一样,我们称这些工具为工具链。

Linux系统上,通常只需要使用gcc就可以完成整个编译过程。但不要被gcc的名字误导,事实上,gcc并不是一个编译器,而是一个驱动程序。在整个的编译过程中,gcc就像一个包工头一样,编译过程中的每一个环节由具体的工具负责。比如编译过程由cc1负责,汇编过程由as负责,链接过程由ld负责。

我们以通过gcc -v参数输出构建过程的详细信息。

可以看到,对于一个C程序来说,从源码构建出可执行程序经过了三个阶段:

编译:gcc调用cc1进行预编译和编译,产生的汇编代码保存在/tmp下的文件ccYBInzt.s(随机)
汇编:gcc调用as进行汇编,汇编过程产生的目标文件保存在/tmp/ccj54pkM.o
链接:gcc调用collect2安排好静态对像,然后collect2调用ld链接ccj54pkM.o以及启动文件(crt1.0,crti.0,crtbegin.o,crtend.o,crtn.o)和libc、libgcc等库生成最终的可执行文件。

编译程序会对源文件进行以预处理然后进行词法分析、语法分析、语义分析后生成中间代码并对中间代码进行优化,最终生成相应的汇编代码。

汇编程序将汇编代码翻译成机器指令,生成ELF格式的目标文件,还要在目标文件中创建辅助链接时需要的信息,包括符号表、重定位表等。

链接程序将一个或多个的目标文件和库(动态和静态库)链接为一个单独的文件(可执行文件或者动态库或者静态库),并为生成的文件处理好符号重定位。

链接静态库时有一点要注意,链接时并不是将整个静态库中的所有的文件都合并到目标文件中,而是仅链接库中使用的目标文件。当然,这个库中的目标文件中可能有个别的函数用不到。静态链接在文件生成的最后会处理好符号的重定位问题。而动态链接无法在文件生成时重定位符号,只能是生成一份重定位表,帮助加载器在文件加载运行时重定位符号。

构建工具链

我们构建软件时通常会在宿主系统中使用工具链构建目标程序。而这个过程当中我们会使用到宿主系统的链接器,它会在宿主的系统中寻找依赖的动态库。这势必会导致目标程序依赖宿主系统的某些库。这有时会出现问题,就是程序在构建的时候可能会顺利通过,但是拿到目标系统中运行时可能会出现未定义符号的错误。

现实中我们自己手工构建工具链的机会并不多,很多时候我们是使用系统中别人已经构建好的。但是,工具链中包含的组件可以说是除了操作系统内核以外的最底层的系统软件了。无论是对理解操作系统,还是对开发程序来说,都有着重要的意义。即使永远不需要自己手工构建工具链,但是了解工具链的构建过程,也可以帮助我们更高效灵活地运用已有的工具链。

工具链的组成:

GUN将构建软件过程中的工具分别包含在3个软件包中:Binutils、GCC、Glibc。

Binutils:包括二进文件相关的工具,比如生成目标文件的汇编器as,链接器ld以及若干处理二进制文件的工具(像objdump、strip等)。也不是所有工具都是处理二进制的,像处理文本的预编译程序cpp也在这个包里。
GCC:包括各种编译器程序,像C编译器、C++编译器、Fortran编译器、Ada编译器等。这里面还包括了C++的启动文件。
Glibc:包括C库。还包括动态链接器和C的启动文件等。事实上,C库有很多的实现,比如适用于Linux桌面系统的Glibc、EGlibc、uClibc等,对于没有操作系统的系统(freestanding enviroment),可以选择newlib、dietlibc或者根本就不用C库。

除了上面三个软件包以外,工具链中还需要包括内核头文件。软件一般很少去直接调用Linux的内核接口,一般都是使用更易用的C库。C库中很大一部分函数就是对内核服务的封装。我们可以把内核头文件看作是内核与C库之间的协议。

构建工具链的过程

我们使用构建交叉编译工具链的方式展示一下这个过程。

GUN将编译器和C库分开放在两个软件包里,好处是比较灵活,方便在工具链中可以选择不同的C库,比如Glibc、uClibc等。但是,也带来了编译器和C库的循环依赖问题:编译C库需要C编译器,但是C编译器又依赖C库。理论上编译器是不应该依赖C库的,它应该只负责将源代码翻译为汇编代码即可,但实际上并非如此,原因如下:

C编译器需要根据C库的某此特性来决定自己支持哪些特性,因此C编译器要依赖C库的头文件。
C++的库和编译器需要C库支持,比如异常处理和栈回溯部分。
GCC不仅包含编译器,还包含一些库,这些库通常依赖C库
C编译器本身也会使用C库的一些函数。
Glibc基本是自包含的,可能有小部分地方使用了GCC的代码。

这个循环依赖肯定会产生鸡蛋问题。GCC依赖C库,C库又是用GCC编译出来的。GUN是怎么解决这个问题的呢。

其实,C99标准定义了两种实现:一种称为hosted实现,一种称为freestanding实现。

其中hosted实现支持完整的C标准,包括语言标准和库标准,它用于编译在有宿主系统的环境下运行的程序。

freestanding实现仅支持完整的语言标准,对于库标准它只要求支持部分库标准。

C99标准要求hosted实现要支持freestanding实现,通常这是通过向编译器传递参数来控制编译器采用哪种方式进行编译。

hosted实现包含编译器(比如GCC)和C库(比如Glibc)。而freestanding实现只包编译器,最多再加上一个简单的库,比如典型的newlib,但即使没有newlib的支持,GCC自己也能自给自足。

而Binutils是编译C库和GCC时都要用到的,因为这个软件包里有链接器。所以索性Binutils几乎没有任何依赖。

综上,我们可以这样编译一个交叉编译工具链:

  • 1、使用宿主系统中的编译工具链构建一个交叉Binutils,包括汇编器as、链接器ld等。
  • 2、使用宿主系统中的编译工具链构建一个临时的交叉编译器(仅支持freestanding)。
  • 3、安装目标系统的内核头文件。
  • 4、使用临时的交叉编译器构建目标系统的C库。
  • 5、使用临时的交叉编译器构建完整的交叉编译器(支持hosted和freestanding)。

解释一下,正常的我们现在机器上的GCC可以在我们机器上运行,编译生成我们自己机器上运行的程序(也就是输入和输出是同一个体系)。但不能编译生成其它cpu或者系统上运行的程序。

我们需要一种GCC,这种GCC可以在我们机器上运行,编译生成一种不能在我们机器上运行,但是可以在其它机器系统上运行的程序(输入和输出不是同一个体系)。对于我们机器上现在的GCC来说,这种新的GCC也是一种程序。

现在我们要先用我们机器上的GCC来编译GCC源码生成一个新的GCC程序,这个新的GCC就是我们需要的。用它能编译生成其它机器上运行的程序(一对一,只能在本机运行,用于编译生成特定目标系统运行的程序)。这种新生成的GCC程序我们就叫它交叉编译工具。

但是GCC是一种特殊的程序,你想生成有交叉编译能力的GCC,你得先需要编译生成目标系统的C库,而想要生成目标C库,你就要用交叉编译能力的GCC。形成了循环依赖。

所以我们先用我们机器上的GCC来编译生成一个不依赖目标C库的交叉编译GCC(freestanding实现),再用这个GCC先编译构建目标系统的C库。当然,我们完全可以使用这个freestanding的GCC来当作工具直接编译生成目标系统运行的程序,但是由于这个GCC是freestanding的,它编译出来的程序也是需要不能有任何依赖的,同时他不支持很多高级特性。所以我们用它来工作很不合适。

目标C库生成以后我们就可以重新编译一个功能齐全,行为正常的standing交叉编译GCC了。这个最终的GCC交叉编译器支持所有的特性。

其中对于GCC,第一次编译一个只支持C的gcc,原因是要编译出一个支持交叉的C++,必须有一个编译好的用于目标体系平台的glibc,而不是只有glibc的头文件就可以的,好在编译glibc有C支持就够了,所以编译glibc也成了第一次的gcc(freestanding)唯一的理由和作用。工具链中gcc的第一次和第二次编译都是由宿主系统的gcc和binutils来完成的。到目前为止只有在编译glibc的时候用到了交叉版本的binutils,其它部分的链接都是由主系统的binutils来完成的。

接下我们通过过程呈现一下具体的技术细节

我们下载GCC、Binutils、GLibc的源码。GUN的源码网站:http://ftp.gnu.org/gnu/

具体下载的文件名和版本号:gcc-9.2.0.tar.xz、glibc-2.31.tar.xz、binutils-2.34.tar.xz

linux内核我们使用linux-5.4.19

开始前先说几个参数

系统类型参数:用来描述生成、运行及目标系统的系统类型,通常由--build、--host及--target三个选项指定,其中:

build=BUILD:指定用来编译这个源代码的编译器是在什么机器和操作系统平台。也就是在什么平台编译我。
host=HOST:指定这个源代码编译出来的程序在什么平台上运行。也就是在什么平台运行我。
·target=TARGET:指定构建出来的程序将来用于编译别的源代码的话编译出来的程序运行在什么平台,默认值与HOST相同。也就是将来我构建的程序是在什么平台运行。

关于这几个选项的指定,通常会有如下的几种组合方式:

1)BUILD、HOST、TARGET三者相同,这一般表示在本机进行源代码的编译,生成的程序也运行在与本机相同的机器和操作系统平台下,并且生成的构建工具将来编译出的目标程序也将运行在同样的系统环境中。
2)BUILD与HOST相同,但HOST与TARGET不同,这是一种生成交叉编译工具的情况,即在HOST主机环境上编译GCC源代码,生成的编译器程序也运行在HOST主机环境中,但是该编译器程序编译生成的目标文件确是运行在目标系统上的,即TARGET环境中。
3)HOST与TARGET相同,但HOST与BUILD不同,这也是一种典型的生成交叉编译工具的情况,即在BUILD主机环境上编译GCC源代码,生成的编译器程序将运行在HOST主机环境中,并且该编译器程序编译生成的目标文件也将运行在HOST,即TARGET环境中。
4)BUID、HOST及TARGET各不相同,这是一种最复杂的情况,也属于一种生成交叉编译工具的情况,即在BUILD主机环境上编译GCC源代码,生成的编译器程序将运行在HOST主机环境中,并且该编译器程序编译生成的目标文件将运行在另一种TARGET机器环境中。

还有一种情况就不说了,那个意义不大。原理都是一样的。

我们采用第二种参数配置方式构建一个交叉编译工具。

先交待一个我这台宿主机的情况:

主机系统:Linux kali 5.4.0-kali4-amd64 #1 SMP Debian 5.4.19-1kali1 (2020-02-17) x86_64 GNU/Linux

GCC版本:gcc (Debian 9.2.1-29) 9.2.1 20200220

awk版本:GNU Awk 5.0.1, API: 2.0 (GNU MPFR 4.0.2, GNU MP 6.1.2)

make版本:GNU Make 4.2.1

HOST值:x86_64-pc-linux-gnu (可以通过命令echo $MACHTYPE查看)

文件目录结构:

目录说明

src目录中存放我们的源码,其中我们新建了一个目录free-standing-gcc,这个用来存放我们编译出来的freestanding交叉编译器。

cross-tools目录用于存放我们的交叉编译工具链

new-system-rootfs目录用于存放编译好的目标机器上的文件。相当于目标系统的根文件系统

第一步构建二进制工具Binutils

先安装一个工具Texinfo,它是GNU项目的官方文档格式。由Richard Stallman和Bob Chassell于多年前发明的,大致基于Brian Reid的Scribe和当时的其他格式化语言。安装这个工具,不然编译过程会报一些文档错误。使用如下命令安装。

sudo apt install texinfo

然后我们在binutils源码文件夹binutils-2.34中新建一个目录tmp-build,在这个目录中单独构建binutils。命令如下

kali@kali:~/src/binutils-2.34/tmp-build$ ../configure --prefix="/home/kali/cross-tools" --target="arm-hwlee-linux-gnueabi" --host="x86_64-pc-linux-gnu" --build="x86_64-pc-linux-gnu" --with-sysroot="/home/kali/new-system-rootfs"

make

make install

这样就完成了对binutils的构建。完成后cross-tools的目录内容如下:

我们看到binutils除了安装二进制工具外,还安装了链接脚本。lib/ldscripts

第二步构建freestanding交叉编译器

gcc提供了一个编译选项--with-newlib,这是一个让人困惑的C库参数,因为newlib本身就是一套C库的实现,所以这个容易让人误解为工具链中使用的C库是newlib,而不是其它的C库。事实上,这个在构建交叉编译器时有着特殊的意义。

软件发布前会使用configure.ac(通过autoconf)来生成configure文件。configure文件用来在配置阶段做各种测试检测,并将结果替换到http://Makefile.in中,生成最终的Makefile。

在GCC源码的configure.ac中有这样一段

能够看到,在同时指明--with-headers和--with-libs的情况下,默认就会是--without-newlib。否则看一下--with-newlib有没有,有的话就设置with_newlib=yes。也就是说这三个选项都没设置的话才把with_newlib设成no。

在gcc/configure文件中:由with_newlib会影响到inhibit_libc这个参数的值。

然后inhibit_libc这个参数会接着影响libgcc/crtstuff.c文件里的内容:

最终我们看到,如果没有定义inhibit_libc,则libgcc库中可能会包含link.h,而这恰恰是glibc提供的头文件。

通过上面的脚本可以看到,只要我们指定--with_newlib和--with-sysroot就可以解除依赖。

建立GCC需要几个支持库,一些是必需的,另一些是可选的。在某些情况下,较新的支持版本可能会起作用,但使用要求的确切版本会更安全。

GNU多精度库(GMP)版本4.3.2(或更高版本),这是GCC必须的库。如果在GCC源码的子目录中有GMP源,它将与GCC一起构建。或者如果已经安装了GMP,但它不在您的库搜索路径中,则必须使用 --with-gmp配置选项指明GMP库的路径。也可以看看--with-gmp-lib 和 --with-gmp-include。只有download_prerequisites安装的GMP版本才支持树内构建。下载地址https://gmplib.org

MPFR库版本3.1.0(或更高版本)

建立GCC很有必要。可以从https://www.mpfr.org下载 。如果在名为GCC的源的子目录中找到MPFR源分发mpfr,它将与GCC一起构建。或者,如果MPFR已安装但不在默认库搜索路径中,则可以 --with-mpfr应该使用配置选项。也可以看看 --with-mpfr-lib 和 --with-mpfr-include。仅安装download_prerequisites的MPFR版本才支持树内构建。

MPC库版本1.0.1(或更高版本)

建立GCC很有必要。可以从http://www.multiprecision.org/mpc/下载 。如果在名为GCC的源的子目录中找到MPC源分发多媒体广告,它将与GCC一起构建。或者,如果已经安装了MPC,但不在默认的库搜索路径中,则 --with-mpc应该使用配置选项。也可以看看 --with-mpc-lib 和 --with-mpc-include。仅安装download_prerequisites安装的MPC版本才支持树内构建。

isl库0.15或更高版本。

使用Graphite循环优化来构建GCC很有必要。可以从https://gcc.gnu.org/pub/gcc/infrastructure/下载。如果在名为GCC的源的子目录中找到isl源分发岛,它将与GCC一起构建。或者,--with-isl 如果默认库搜索路径中未安装isl,则应使用configure选项。

zstd库。

必须使用用于LTO字节码的zstd压缩来构建GCC。在您的默认库补丁程序搜索中搜索库。或者,--with-zstd 应该使用配置选项。

源码的configure说明:https://gcc.gnu.org/install/configure.html

我们把mpc、mpfr、gmp三个的源码放到gcc源码目录里。

然后我们把上一步编译好的binutils的路径放到PATH里,放在宿主机的PATH之前,以保证我们的交叉编译工具被使用。

export PATH="/home/kali/cross-tools/bin":$PATH

../configure --prefix="/home/kali/src/free-standing-gcc" --target="arm-hwlee-linux-gnueabi" --host="x86_64-pc-linux-gnu" --build="x86_64-pc-linux-gnu" --with-sysroot="/home/kali/new-system-rootfs" --with-newlib --enable-languages=c --disable-shared --disable-threads --disable-libatomic --disable-decimal-fload --disable-libquadmath --disable-libmudflap --disable-libgomp --disable-nls --disable-libssp

未完,要上班了,以后补上!

gcc mips64编译后无法运行在octeon上运行_编译工具链相关推荐

  1. gcc mips64编译后无法运行在octeon上运行_V 语言运行hello world程序

    安装V语言可执行文件(编译器) 1,最简单的方法是去官网下载编译好的安装包. 由截图可以看出v的编译器只有Linux 和 Mac 版本,还不支持window系统.关于支持window系统,github ...

  2. 深入理解:cmd下java命令启动JVM运行class文件时,可以自动识别不同编码编译后的class文件并加之运行

    cmd下java命令启动JVM运行class文件时,可以自动识别不同编码编译后的class文件并加之运行 总结如下: 一:程序源代码以GBK格式保存时: 二:程序以源代码UTF-8无BOM格式保存时: ...

  3. Linux环境下开发板Tiny4412应用,实现交叉编译及minicom的调配,将代码编译后下载到开发板并运行

    一.实验目的 1.熟悉Linux环境,学习使用命令行操控计算机系统,学会基础的ubuntu机器操作. 2.初步学习使用开发板Tiny4412,查看实验说明以及开发板说明书,学习基本使用步骤. 3.安装 ...

  4. Driver not loaded Driver not loaded(QT打包后在别人的电脑上运行出现这个错误)解决方法

    Driver not loaded Driver not loaded(QT打包后在别人的电脑上运行出现这个错误)解决方法 出现这个错误,导致的原因有很多,所以不妨先试试我找的这种解决方法,我也是试过 ...

  5. 修改了xml要不要重新起服务器,关于设置:Eclipse每次运行项目时都会修改server.xml(运行-在服务器上运行)...

    我在Eclipse Juno上使用Tomcat 7. 我使用工作区元数据作为服务器位置(请在下面查看我的tomcat配置). 另外,我在Eclipse中有一个Server项目[请参见下图],其中包含单 ...

  6. win10应用开发——如何判断应用是在手机上运行还是电脑上运行

    原文:win10应用开发--如何判断应用是在手机上运行还是电脑上运行 在进行uwp应用开发的时候, 有时我们需要知道自己的应用是在手机端运行还是在桌面端运行,那么通过以下的api就可以进行判断: Wi ...

  7. QT5.15.2源码编译后在君正MIPS架构运行播放实时视频流

    问题背景: 公司新项目需要使用君正T40 soc,平台是 MIPS 架构,但是君正在此平台未开发出图形界面工具,项目需要人机交互,于是需要使用QT实现相关需求. 问题描述: 下载QT5.15.2源码( ...

  8. 树莓派编译 android,RPI3: 在树莓派3上运行ANDROID 8.1系统

    Android 官方并没有提供可运行在树莓派3上的Android系统,而只提供了AndroidThings系统,并且还只是preview的版本.想在树莓派上运行Android 8.1系统,就需要自己动 ...

  9. python程序文件的扩展名称-Python源代码程序编译后的文件扩展名为( )。_学小易找答案...

    [单选题]9.药物与血浆蛋白结合后( ). [填空题]根据ISO标准,当刀具中心轨迹在程序轨迹前进方向左边时称为左刀补,用( )指令表示! [判断题]进口的农产品必须按照国家规定的农产品质量安全标准进 ...

最新文章

  1. Framework启动过程浅析
  2. 设计模式(三) | 为别人做嫁衣---代理模式
  3. Vista上远程管理Hyper-V服务器
  4. hdu 4280 最大流sap
  5. rancher添加私有仓库_使用Rancher和私有仓库快速搭建Kubernetes集群
  6. 对.Net 垃圾回收的C#编程相关方面(Finalize 和Dispose(bool disposing)和 Dispose())的一些理解体会...
  7. python随机抽奖程序代码_详解用python写一个抽奖程序
  8. 护卫神 mysql 升级_护卫神php套件 php版本升级方法
  9. python+opencv人脸识别(用耶鲁大学的Yale人脸库训练cnn)3
  10. MovieLens电影推荐系统
  11. ios safari 描述文件 跳转到_iOS系统在线下载安装ipa文件,以及跳转描述文件信任证书的实现...
  12. 双引号后面要加句号吗_引号里应不应该用句号,双引号后的句号在前还是在后?...
  13. 商城静态页面(仿小米官网)
  14. MySQL备份报错mysqldump: Got error: 1045: Access denied for user ‘root‘@‘localhost‘ (using password: YES)
  15. 实现手机端的触屏滑动效果
  16. windows mysql提示:1045 access denied for user 'root'@'localhost' using password ye
  17. Hive on Hbase
  18. Qt串口通信实时曲线上位机源代码
  19. 北京圣思园JAVA培训教学视频汇总
  20. Unity学习 — 23种设计模式

热门文章

  1. python重要吗-毫无基础,商英专业,Python真的有用吗?
  2. 计算机速录学什么,学速录的要求有哪些
  3. linux创建进程读共享写复制,Linux下进程的创建、执行和终止
  4. mysql物理读和逻辑读,SQL Server中STATISTICS IO物理读和逻辑读的误区
  5. JVM的YGC,这次被它搞惨了!
  6. Python 2.7.18 发布,Python 2 时代结束
  7. Jenkins Pipeline插件十大最佳实践!
  8. C语言32位数加一精简,一个简单的32位多任务操作系统的实现(1)
  9. php 自动登录脚本_php利用cookie实现自动登录的方法
  10. TENSORFLOW PROCESS FINISHED WITH EXIT CODE -1073741819 (0XC0000005)