<返回更多

详解 gcc 编译、链接原理—揭开应用程序运行背后的奥秘

2020-11-16    
加入收藏

如何使用 gcc 构建 c/c++ 项目,大家都很熟悉了,甚至对链接器、静态库、共享库等概念,大家也略知一二。然而,对于 ld 链接器、linux 操作系统(OS)及应用程序(exec)之间的详细交互流程,估计就有点懵了。接下来,我将从单个源文件编译、编译期链接、程序运行期这三个阶段入手,揭开应用程序运行背后的奥秘。

单个源文件编译

单个 c/cpp 文件可以被 gcc 编译成目标文件(.o 文件),这部分就不过多赘述,大家应该都很熟悉了。二进制目标文件中的 section 有很多,详细内容可以打开汇编代码详细研究下,下图列出了其中比较常见的段。

详解 gcc 编译、链接原理—揭开应用程序运行背后的奥秘

这里的目标文件包括 .o 文件及后面提到的库文件

符号表的作用是什么?

  1. 记录该目标文件中定义的全局变量及函数;
  2. 记录该目标文件中引用的全局变量及函数;
详解 gcc 编译、链接原理—揭开应用程序运行背后的奥秘

Func 是源文件中引用的外部符号,a 是源文件中定义的全局变量

.rela.* 的作用是什么?

全称 relocation(重定位),记录编译器在编译时不确定的符号地址——针对引用的外部符号。

dynamic 段中保存了可执行文件依赖哪些动态库。

GOT 段记录了需要引用的外部符号的地址。

编译期链接

多个 .o 文件可以通过链接器(ld)被打包在一起,组合成库文件。

库文件又分为静态库(.a 文件)和共享库(.so 文件)。

什么是 ld 呢?它本身也是可执行文件,属于 GNU 的一部分,将一堆目标文件通过符号表链接成最终的目标文件、库文件和可执行文件。

.a 文件如何生成?

ld 直接将涉及的所有目标文件打包进静态库文件。

.so 文件如何生成?

在链接生成共享库文件的过程中,并不拷贝目标文件中涉及的代码段,只记录它需要引用的外部符号位置(在哪些目标文件中)。

所有的目标文件、库文件和可执行文件都有统一的格式,即 ELF,Executable and Linking Format(可执行链接格式)。

详解 gcc 编译、链接原理—揭开应用程序运行背后的奥秘

libstdc++.so 是标准库文件

上图中,多个 .o 文件链接在一起形成 .a 文件,多个 .o 和 .so 文件也可以链接形成 .so 文件,可执行文件也可以由 .a 文件、.so、.o 文件链接而成。

程序运行期

如果可执行文件没有使用共享库,那么该程序就可以独立运行,因为它内部所有的符号都有对应的二进制机器码。这种情况比较简单,我们这里主要讨论下面这种程序运行方式。

如果可执行文件要使用共享库,那么该程序就不能独立运行,它在运行时需要使用共享库的代码,且对应的两种使用方式,分别是运行时动态链接运行时动态加载

可执行文件的组成

详解 gcc 编译、链接原理—揭开应用程序运行背后的奥秘

 

ld-linux.so:不是一个可执行程序,只是一个 shell 脚本。作为解释器,写在 elf 文件(可执行文件)中,ld-linux.so 先于 main 函数工作,用于查找主程序所依赖的共享库,实际上可以直接执行 ld-linux.so. 还有另外一种比较常见的是 ld.so,它是个符号链接,指向 ld-linux.so.(通过命令 ln -s ld.so ld-linux.so 创建)。

详解 gcc 编译、链接原理—揭开应用程序运行背后的奥秘

 


详解 gcc 编译、链接原理—揭开应用程序运行背后的奥秘

 

为什么这里使用解释器呢?

解释器的特点是动态特性和可移植性,比如在解释器执行时可以动态改变变量的类型、对程序进行修改以及在程序中插入良好的调试诊断信息等。而将解释器移植到不同的系统上,则程序不用改动就可以在移植了解释器的系统上运行。

同时解释器也有很大的缺点,比如执行效率低,占用空间大,因为不仅要给用户程序分配空间,解释器本身也占用了宝贵的系统资源。

动态链接和动态加载的区别

动态加载和动态链接都是在程序运行时发生,并将所需代码拷贝到内存这点很重要!

关键区别是:动态链接的流程是 OS 直接把共享库的代码拷贝到内存,而动态加载由人工指定(代码中的 dlopen() 接口)。

动态链接需要 OS 的特殊支持,通过动态链接方式拷贝到内存的库代码可以在各个进程之间共享。而对动态加载而言,可以在各自进程中打开共享库代码。

其他概念

ldconfig:这是个可执行程序,隶属于 GNU,作用是在默认搜寻目录(/lib和/usr/lib)以及共享库配置文件 /etc/ld.so.conf 内所列的目录下,搜索出共享库文件(lib*.so*),进而创建出 ld-linux.so 所需要的链接和缓存文件。缓存文件默认为 /etc/ld.so.cache,此文件保存已排好序的共享库名字列表。更新缓存使新添加的库生效,当然系统启动时会自动运行 ldconfig。

详解 gcc 编译、链接原理—揭开应用程序运行背后的奥秘

 

ldd:这是 Linux 内核中自带的脚本,可以用来查看可执行文件链接了哪些共享库

详解 gcc 编译、链接原理—揭开应用程序运行背后的奥秘

 

strip <可执行文件名> 去除符号表,可以给可执行文件瘦身

使用 objdump、readelf、nm 等命令可以查询目标文件的详细内容。

gcc -print-search-dirs 可以查看 gcc 在编译、链接过程中的共享库搜索路径。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>