本节书摘来自华章出版社《深入分析GCC 》一书中的第3章,第3.3节,作者 王亚刚 ,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
3.3 GCC源代码编译
在获得了GCC的源代码后,为了生成目标机器上的编译器程序,需要对源代码进行编译,一般步骤包括:
(1)使用conf?igure脚本完成编译配置,生成Makef?ile文件。
(2)使用make工具编译源代码。
(3)使用make工具安装生成的编译程序等。
使用的典型脚本为:
./configure
make
make install
3.3.1 配置
这个过程一般很简单,可以直接在${GCC_SOURCE}目录下使用命令./conf?igure。该脚本会对GCC源代码编译、安装的环境进行检查,并根据conf?igure脚本的参数对编译环境进行配置,从而最终生成Makef?ile文件,作为后续make工具进行编译和安装的依据。
然而,稍微仔细分析后会发现,这又是一个非常令人烦恼的过程,原因是这个配置命令具有较多的选项。可以使用./conf?igure --help查看这些配置选项及其主要的功能说明。
下面对编译配置中的一些主要选项进行简单的说明。
(1)安装目录(Installation directories)选项:
--pref?ix=PREFIX:用来指定目标机器无关代码的安装目录,默认值为/usr/local。
--exec-pref?ix=EPREFIX:用来指定目标机器相关代码的安装目录,一般与--pref?ix选项指定的PREFIX值相同。
(2)程序名称(Program names):
--program-pref?ix=PREFIX:设置安装程序名的前缀为PREFIX。
--program-suff?ix=SUFFIX:设置安装程序名的后缀为SUFFIX。
例如,如果使用如下命令进行配置:[GCC@host1 gcc-4.4.0]$ ./configure --program-prefix=prefix-
那么最终生成的gcc编译程序的名称将变成:/usr/local/bin/prefix-gcc,其中prefix-gcc里面的“prefix-”就是由--program-pref?ix=prefix-选项所指定的。
(3)系统类型(System types):
用来描述生成、运行及目标系统的系统类型,通常由--build、--host及--target三个选项指定,其中:
build=BUILD:指定生成(build)编译器程序的机器和操作系统平台信息。
host=HOST:指定生成的编译器程序所运行的机器和操作系统平台信息,默认值与BUILD相同。
target=TARGET:指定生成的编译器程序生成代码所运行的机器和操作系统平台信息,默认值与HOST相同。
关于这几个选项的指定,通常会有如下的几种组合方式:
1)BUILD、HOST、TARGET三者相同,这一般表示在本机进行GCC源代码的编译,生成的编译器程序GCC也运行在与本机相同的机器和操作系统平台下,并且生成的编译器编译出的目标程序也将运行在同样的系统环境中。
2)HOST与TARGET相同,但HOST与BUILD不同,这是一种典型的生成交叉编译工具的情况,即在BUILD主机环境上编译GCC源代码,生成的编译器程序将运行在HOST主机环境中,并且该编译器程序编译生成的目标文件也将运行在HOST,即TARGET环境中。
3)BUID、HOST及TARGET各不相同,这是一种最复杂的情况,也属于一种生成交叉编译工具的情况,即在BUILD主机环境上编译GCC源代码,生成的编译器程序将运行在HOST主机环境中,并且该编译器程序编译生成的目标文件将运行在另一种TARGET机器环境中。
例3-1 通过Makefile文件查看build、host及target系统的配置值
假设本例运行的主机信息如下:
[GCC@host1 gcc-4.4.0]$ uname -a
Linux host1 2.6.32-71.el6.i686 #1 SMP Fri Nov 12 04:17:17 GMT 2010 i686 i686 i386 GNU/Linux
第一种情况,在gcc-4.4.0源代码目录中直接执行./conf?igure,那么在生成Makef?ile文件中包含了如下信息:
build=i686-pc-linux-gnu
host=i686-pc-linux-gnu
target=i686-pc-linux-gnu
这种情况下,conf?igure脚本会自动根据系统的信息“猜测”出build的值,例如本例中看到的build的值为“i686-pc-linux-gnu”,而host的默认值与build相同,target的默认值与host相同。此时三者的值都是相同的,这就是上述系统类型1)中的组合方式。
第二种情况,假如要在本机上对GCC源代码进行编译,生成的编译器程序也运行在本机上,但编译器需要为arm机器生成运行代码,此时,编译配置可以如下:[GCC@host1 gcc-4.4.0]$ ./configure --target=arm-linux-gnu
或者:
[GCC@host1 gcc-4.4.0]$ ./configure --build=i686-pc-linux-gnu --target=arm-linux-gnu
生成的Makef?ile文件都包含了如下内容:
build=i686-pc-linux-gnu
host=i686-pc-linux-gnu
target=arm-unknown-linux-gnu
此时build与host相同,即用来编译GCC的机器和将来要运行编译器的机器类型是相同的,但是编译器生成的代码却不在host主机系统上运行,而是要运行在一种arm-unknown-linux-gnu机器上,这就是一种典型的交叉编译。
(4)可选编译特性的运行与禁止。
一般使用--enable-FEATURE[=ARG]或者--disable-FEATURE表示设置该FEATURE的值为ARG,或者禁止该特性。
(5)编译时可选的一些包的配置。
一般使用--with-PACKAGE[=ARG]或者--without-PACKAGE来指定包PACKAGE的值或者禁止使用该包。例如使用--with-mpfr =PATH指定mpfr包的目录。
(6)编译时的环境变量。
这些环境变量可能包括编译、汇编、链接等工具以及这些工具运行时的选项值,例如:
./configure CFLAGS="-w -g -O0" --prefix=/opt/paag-gcc --target=paag-linux-gnu --enable-stage1-language=c
其中,CFLAGS="-w -g -O0"就指定了编译标志CLAGS的部分值。表3-1给出了conf?igure脚本中常用环境变量的名称和意义。
3.3.2 编译
执行./conf?igure命令后,就生成了gcc-4.4.0目录下的Makef?ile文件。此时,可以使用make工具来进行整个GCC源代码的编译过程。这个过程的执行异常复杂,为了了解整个编译的详细过程,建议读者将编译的过程重定向到文件中,并结合Makef?ile文件进行仔细研读。例如,可以采用如下命令形式:
[GCC@host1 gcc-4.4.0]$ ./configure CFLAGS="-g -O0" --prefix=/opt/i386 --target= i386-linux-gnu
[GCC@host1 gcc-4.4.0]$ make > make-process 2&>1
GCC源代码编译生成一个在本机运行的编译器时,通常采用一个叫做bootstrapping的技术,即将GCC源代码的编译过程分为多个阶段,通常包括stage1、stage2及stage3三个阶段。这三个阶段的功能分别为:
(1)stage1:使用一个现有的编译器将GCC的源代码编译,生成一个新的编译器new_gcc1;
(2)stage2:使用new_gcc1重新编译GCC的源代码,生成另外一个新的编译器new_gcc2;
(3)stage3:使用new_gcc2重新编译GCC的源代码,生成另外一个新的编译器new_gcc3,并且对new_gcc2和new_gcc3进行比较,如果两者相同,则表示GCC源代码编译成功,否则表示生成的编译器存在bug。
采用上述过程的目的是充分保证编译器的正确性。详细内容可以参考如下文档:
http://stackoverf?low.com/questions/9429491/how-are-gcc-g-bootstrapped
https://gcc.gnu.org/install/build.html
可以通过前面生成的make-process文件中的部分内容验证上述说法。
[GCC@localhost gcc-4.4.0]$ make &> make-process
[GCC@localhost gcc-4.4.0]$ grep stage make-process
[ -f stage_final ] || echo stage3 > stage_final
Configuring stage 1 in host-i686-pc-linux-gnu/intl
Configuring stage 1 in host-i686-pc-linux-gnu/gcc
Configuring stage 1 in host-i686-pc-linux-gnu/libiberty
Configuring stage 1 in host-i686-pc-linux-gnu/zlib
Configuring stage 1 in host-i686-pc-linux-gnu/libcpp
Configuring stage 1 in host-i686-pc-linux-gnu/libdecnumber
Configuring stage 1 in i686-pc-linux-gnu/libgcc
rm -f stage_current
Configuring stage 2 in host-i686-pc-linux-gnu/intl
Configuring stage 2 in host-i686-pc-linux-gnu/gcc
Configuring stage 2 in host-i686-pc-linux-gnu/libiberty
Configuring stage 2 in host-i686-pc-linux-gnu/zlib
Configuring stage 2 in host-i686-pc-linux-gnu/libcpp
Configuring stage 2 in host-i686-pc-linux-gnu/libdecnumber
Configuring stage 2 in i686-pc-linux-gnu/libgcc
rm -f stage_current
Configuring stage 3 in host-i686-pc-linux-gnu/intl
Configuring stage 3 in host-i686-pc-linux-gnu/gcc
Configuring stage 3 in host-i686-pc-linux-gnu/libiberty
Configuring stage 3 in host-i686-pc-linux-gnu/zlib
Configuring stage 3 in host-i686-pc-linux-gnu/libcpp
Configuring stage 3 in host-i686-pc-linux-gnu/libdecnumber
Configuring stage 3 in i686-pc-linux-gnu/libgcc
rm -f stage_current
Comparing stages 2 and 3
以下是各个阶段生成的GCC文件的对比。
使用现有编译器生成的xgcc(即new_gcc1):
[GCC@localhost host-i686-pc-linux-gnu]$ ls stage1-gcc/xgcc -l
-rwxrwxr-x. 1 GCC GCC 499979 Aug 30 15:30 stage1-gcc/xgcc
使用new_gcc1重新编译GCC的源代码,生成另外一个新的编译器xgcc(即new_gcc2):
[GCC@localhost host-i686-pc-linux-gnu]$ ls prev-gcc/xgcc -l
-rwxrwxr-x. 1 GCC GCC 453766 Aug 30 15:44 prev-gcc/xgcc
使用new_gcc2重新编译GCC的源代码,生成另外一个新的编译器xgcc(即new_gcc3):
[GCC@localhost host-i686-pc-linux-gnu]$ ls gcc/xgcc -l
-rwxrwxr-x. 1 GCC GCC 453766 Aug 30 15:49 gcc/xgcc
可以看出,两个阶段生成的编译器gcc/xgcc与prev-gcc/xgcc的大小相同。
另外,还可以使用diff工具对两个编译器目标文件进行对比,从命令的结果可以看出两者是完全相同的,因此,本次GCC源代码的编译是成功的。[GCC@localhost host-i686-pc-linux-gnu]$ diff gcc/xgcc prev-gcc/xgcc
3.3.3 安装
GCC源代码成功编译后,生成的可执行程序及文档等的安装可以使用如下命令进行:make install