计算机系统篇之链接(1):gcc/g++的编译流程
Author: stormQ
Created: Saturday, 21. December 2019 09:48AM
Last Modified: Wednesday, 04. November 2020 07:56PM
本文描述了 gcc/g++ 编译系统的工作流程,并提供了手动地完成各阶段的不同方法,从而达到更好地理解程序翻译过程的效果。
gcc/g++,实质上是一个编译驱动器(Compile Driver),对它的调用意味着调用一系列的程序——预处理器、编译器、汇编器和链接器,从而将源代码转化成可执行目标文件。具体流程可以分为如下四个步骤:
1)预处理
首先调用 C 预处理器(名称为cpp
的可执行目标文件,通常位于/usr/bin/
目录下)将每个源文件(比如:以.c 或 .cc 或. cpp
结尾的文件)扩展源代码——即插入所有用#include
命令指定的文件,并扩展所有用#define
声明指定的宏。对一个源文件进行预处理的输出结果是一个以.i
结尾的中间文件,即编码为 ASCII 码的源代码中间文件。
由源文件生成预处理文件的命令:
# 一个源文件(以.c/.cc/.cpp结尾的文件)对应一个预处理后的源文件(以.i结尾的文件)
$ cpp -std=c++11 main.cpp -o main.i
$ cpp -std=c++11 shm_manager_sim.cpp -o shm_manager_sim.i
$ cpp -std=c++11 subscription_sim.cpp -o subscription_sim.i
$ cpp -std=c++11 topic_manager_sim.cpp -o topic_manager_sim.i
或
$ g++ -E main.cpp -o main.i
$ g++ -E shm_manager_sim.cpp -o shm_manager_sim.i
$ g++ -E subscription_sim.cpp -o subscription_sim.i
$ g++ -E topic_manager_sim.cpp -o topic_manager_sim.i
注意:
main.cpp 中包含了<thread>
头文件(在 C++11 标准中涵盖)。所以,直接调用预处理器cpp
时需要添加-std=c++11
选项,而采用g++ -E
的方式不需要添加该选项。
如果源文件与其包含的用户头文件不在同一个目录中,需要添加-I
选项指定搜索路径,从而成功地生成.i
文件。示例:1)用户头文件都处于同一个目录时,对应的命令:g++ -E main.cpp -o main.i -I <directory>
;2)用户头文件处于不同目录时,对应的命令:g++ -E main.cpp -o main.i -I <directory 1> -I <directory 2> -I <directory n>
。
在某些 gcc/g++ 版本中,预处理器被集成到编译驱动器中,而不是作为独立的程序存在。
2)编译
其次,调用编译器(名称为cc1
——用于编译C程序的可执行目标文件或cc1plus
——用于编译C++程序的可执行目标文件,在本人的机器上两者位于/usr/lib/gcc/x86_64-linux-gnu/6
目录下)将扩展后的源代码(以.i
结尾的文件)编译成汇编代码(以.s
结尾的文件,即编码为 ASCII 码的汇编语言文件)。
由预处理文件生成汇编语言文件的命令:
# 一个预处理后的源文件(以.i结尾的文件)对应一个汇编代码文件(以.s结尾的文件)
# /usr/lib/gcc/x86_64-linux-gnu/6/cc1plus -o main.s main.cpp <other arguments>
$ /usr/lib/gcc/x86_64-linux-gnu/6/cc1plus -o main.s main.cpp -quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE -quiet -dumpbase -mtune=generic -march=x86-64 -auxbase-strip -version -fstack-protector-strong -Wformat -Wformat-security
或
$ g++ -S main.i -o main.s
$ g++ -S shm_manager_sim.i -o shm_manager_sim.s
$ g++ -S subscription_sim.i -o subscription_sim.s
$ g++ -S topic_manager_sim.i -o topic_manager_sim.s
注意:
cc1plus
命令中<other arguments>
部分如何确定?详见下文。
如果需要添加调试信息,只能在执行本步骤时加-g
选项。其他时期:步骤1)、步骤3)、步骤4)时加-g
选项都没有任何作用。添加-g
所生成的汇编代码文件中可以看到与调试相关的 sections,如:.debug_aranges
、.debug_info
、.debug_abbrev
、.debug_line
、.debug_str
、.debug_ranges
等。
区分目标文件是 DEBUG 版本还是 RELEASE 版本的方法之一:readelf -S main | grep debug
。如果是 DEBUG 版本,会有带.debug*
的信息输出;否则,什么也不会输出。
编译器的输入可以是源文件,也可以是预处理后的文件。
对于以 .cpp 结尾的源文件,无论是 gcc 还是 g++,实际调用的编译器都是 cc1plus。
<other arguments>
部分的确定过程:
1)使用-v
选项打印
$ g++ -S main.cpp -o main.s -v
输出结果为:
$ g++ -S main.cpp -o main.s -v
Using built-in specs.
COLLECT_GCC=g++
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 6.5.0-2ubuntu1~16.04' --with-bugurl=file:///usr/share/doc/gcc-6/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --with-as=/usr/bin/x86_64-linux-gnu-as --with-ld=/usr/bin/x86_64-linux-gnu-ld --program-suffix=-6 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-6-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-6-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-6-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 6.5.0 20181026 (Ubuntu 6.5.0-2ubuntu1~16.04)
COLLECT_GCC_OPTIONS='-S' '-o' 'main.s' '-v' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/6/cc1plus -quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE main.cpp -quiet -dumpbase main.cpp -mtune=generic -march=x86-64 -auxbase-strip main.s -version -o main.s -fstack-protector-strong -Wformat -Wformat-security
GNU C++14 (Ubuntu 6.5.0-2ubuntu1~16.04) version 6.5.0 20181026 (x86_64-linux-gnu)
compiled by GNU C version 6.5.0 20181026, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3, isl version 0.15
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/6"
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/6/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/include/c++/6
/usr/include/x86_64-linux-gnu/c++/6
/usr/include/c++/6/backward
/usr/lib/gcc/x86_64-linux-gnu/6/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/6/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
GNU C++14 (Ubuntu 6.5.0-2ubuntu1~16.04) version 6.5.0 20181026 (x86_64-linux-gnu)
compiled by GNU C version 6.5.0 20181026, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3, isl version 0.15
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: f1f1eb1c4b7ccfc7f1bfde5650f1de21
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/6/:/usr/lib/gcc/x86_64-linux-gnu/6/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/6/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/6/:/usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/6/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/6/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-S' '-o' 'main.s' '-v' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
2)完整的编译选项为 cc1plus 后面的内容,即
-quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE main.cpp -quiet -dumpbase main.cpp -mtune=generic -march=x86-64 -auxbase-strip main.s -version -o main.s -fstack-protector-strong -Wformat -Wformat-security
从上面的输出结果中可以看到,-o main.s
、main.cpp
。除这部分之外的就是<other arguments>
部分对应的内容了。
3)汇编
接下来,调用汇编器(名称为as
的可执行目标文件,通常位于/usr/bin/
目录下)将汇编代码(以.s
结尾的文件)转化成可重定位目标代码(以.o
结尾的文件,汇编代码的二进制表示,但还未填入全局值的地址)。
由汇编语言文件生成可重定位目标文件的命令:
# 一个汇编代码文件(以.s结尾的文件)对应一个可重定位目标文件(以.o结尾的文件)
$ as main.s -o main.o
$ as shm_manager_sim.s -o shm_manager_sim.o
$ as subscription_sim.s -o subscription_sim.o
$ as topic_manager_sim.s -o topic_manager_sim.o
或
$ g++ -c main.s -o main.o
$ g++ -c shm_manager_sim.s -o shm_manager_sim.o
$ g++ -c subscription_sim.s -o subscription_sim.o
$ g++ -c topic_manager_sim.s -o topic_manager_sim.o
4)链接
最后,调用链接器(名称为ld
的可执行目标文件,通常位于/usr/bin/
目录下)将(多个)可重定位目标文件以及一些必要的系统目标文件组合起来,并产生最终的可执行目标文件。
由(多个)可重定位目标文件生成可执行文件的命令:
# 将多个可重定位目标文件(以.o结尾的文件)组合起来生成一个可执行目标文件
# ld -o main main.o shm_manager_sim.o subscription_sim.o topic_manager_sim.o <system object files and args>
ld -o main main.o shm_manager_sim.o subscription_sim.o topic_manager_sim.o -plugin /usr/lib/gcc/x86_64-linux-gnu/6/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/6/lto-wrapper -plugin-opt=-fresolution=/tmp/ccCu2RiH.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lpthread -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 -z relro /usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/6/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/6 -L/usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/6/../../../../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/6/../../.. -lstdc++ -lm -lgcc_s -lgcc -lpthread -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/6/crtend.o /usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/crtn.o
或
$ g++ -o main main.o shm_manager_sim.o subscription_sim.o topic_manager_sim.o -pthread
注:
ld
命令中<system object files and args>
部分如何确定?详见下文。
g++ -o main main.o shm_manager_sim.o subscription_sim.o topic_manager_sim.o -pthread
命令将多个可重定位目标文件(main.o、shm_manager_sim.o、subscription_sim.o、topic_manager_sim.o)和系统目标文件(通过-pthread
选项指定)等生成最终的可执行目标文件——main。
<system object files and args>
部分的确定过程:
1)使用-v
选项打印
$ g++ -v -o main_test main.o shm_manager_sim.o subscription_sim.o topic_manager_sim.o -pthread
输出结果为:
$ g++ -v -o main_test main.o shm_manager_sim.o subscription_sim.o topic_manager_sim.o -pthread
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/6/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 6.5.0-2ubuntu1~16.04' --with-bugurl=file:///usr/share/doc/gcc-6/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --with-as=/usr/bin/x86_64-linux-gnu-as --with-ld=/usr/bin/x86_64-linux-gnu-ld --program-suffix=-6 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-6-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-6-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-6-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 6.5.0 20181026 (Ubuntu 6.5.0-2ubuntu1~16.04)
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/6/:/usr/lib/gcc/x86_64-linux-gnu/6/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/6/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/6/:/usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/6/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/6/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-o' 'main_test' '-pthread' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/6/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/6/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/6/lto-wrapper -plugin-opt=-fresolution=/tmp/ccCu2RiH.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lpthread -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 -z relro -o main_test /usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/6/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/6 -L/usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/6/../../../../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/6/../../.. main.o shm_manager_sim.o subscription_sim.o topic_manager_sim.o -lstdc++ -lm -lgcc_s -lgcc -lpthread -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/6/crtend.o /usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/crtn.o
COLLECT_GCC_OPTIONS='-v' '-o' 'main_test' '-pthread' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
2)完整的链接选项为 collect2 后面的内容,即
-plugin /usr/lib/gcc/x86_64-linux-gnu/6/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/6/lto-wrapper -plugin-opt=--plugin /usr/lib/gcc/x86_64-linux-gnu/6/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/6/lto-wrapper -plugin-opt=-fresolution=/tmp/ccCu2RiH.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lpthread -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 -z relro -o main_test /usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/6/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/6 -L/usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/6/../../../../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/6/../../.. main.o shm_manager_sim.o subscription_sim.o topic_manager_sim.o -lstdc++ -lm -lgcc_s -lgcc -lpthread -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/6/crtend.o /usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/crtn.o
从上面的输出结果中可以看到,-o main_test
、main.o shm_manager_sim.o subscription_sim.o topic_manager_sim.o
等。除这部分之外的就是<system object files and args>
部分对应的内容了。
上一篇:计算机系统之目录