[授权发表]动态符号链接的细节

by falcon [email protected] of TinyLab.org
2008-2-26

最初发表:泰晓科技 – 聚焦嵌入式 Linux,追本溯源,见微知著!
原文链接:动态符号链接的细节
评论说明:为更好地聚合大家的讨论,请到上面原文的评论区回复。


【注】这是开源书籍《C语言编程透视》第四章,如果您喜欢该书,请关注我们的新浪微博@泰晓科技

动态符号链接的细节

前言

Linux支持动态链接库,不仅节省了磁盘、内存空间,而且可以提高程序运行效率。不过引入动态链接库也可能会带来很多问题,例如动态链接库的调试升级更新和潜在的安全威胁1, 2。这里主要讨论符号的动态链接过程,即程序在执行过程中,对其中包含的一些未确定地址的符号进行重定位的过程1, 2

本篇主要参考资料38,前者侧重实践,后者侧重原理,把两者结合起来就方便理解程序的动态链接过程了。另外,动态链接库的创建、使用以及调用动态链接库的部分参考了资料1, 2

下面先来看看几个基本概念,接着就介绍动态链接库的创建、隐式和显示调用,最后介绍符号的动态链接细节。

基本概念

ELF

ELF是Linux支持的一种程序文件格式,本身包含重定位、执行、共享(动态链接库)三种类型(man elf)。

代码:

/* test.c */
#include <stdio.h>    

int global = 0;

int main()
{
        char local = &#39;A&#39;;

        printf("local = %c, global = %dn", local, global);

        return 0;
}

演示:

通过-c生成可重定位文件test.o,这里不会进行链接:

$ gcc -c test.c
$ file test.o
test.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

链接后才可以执行:

$ gcc -o test test.o
$ file test
test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), not stripped

也可链接成动态链接库,不过一般不会把main函数链接成动态链接库,后面再介绍:

$ gcc -fpic -shared -W1,-soname,libtest.so.0 -o libtest.so.0.0 test.o
$ file libtest.so.0.0
libtest.so.0.0: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped

虽然ELF文件本身就支持三种不同的类型,不过它有一个统一的结构。这个结构是:

文件头部(ELF Header)
程序头部表(Program Header Table)
节区1(Section1)
节区2(Section2)
节区3(Section3)
...
节区头部表(Section Header Table)

无论是文件头部、程序头部表、节区头部表,还是节区,它们都对应着C语言里头的一些结构体(elf.h中定义)。文件头部主要描述ELF文件的类型,大小,运行平台,以及和程序头部表和节区头部表相关的信息。节区头部表则用于可重定位文件,以便描述各个节区的信息,这些信息包括节区的名字、类型、大小等。程序头部表则用于描述可执行文件或者动态链接库,以便系统加载和执行它们。而节区主要存放各种特定类型的信息,比如程序的正文区(代码)、数据区(初始化和未初始化的数据)、调试信息、以及用于动态链接的一些节区,比如解释器(.interp)节区将指定程序动态装载/链接器ld-linux.so的位置,而过程链接表(plt)、全局偏移表(got)、重定位表则用于辅助动态链接过程。

符号

对于可执行文件除了编译器引入的一些符号外,主要就是用户自定义的全局变量,函数等,而对于可重定位文件仅仅包含用户自定义的一些符号。

  • 生成可重定位文件

    $ gcc -c test.c
    $ nm test.o
    00000000 B global
    00000000 T main
    U printf

上面包含全局变量、自定义函数以及动态链接库中的函数,但不包含局部变量,而且发现这三个符号的地址都没有确定。

注:nm命令可用来查看ELF文件的符号表信息。

  • 生成可执行文件

    $ gcc -o test test.o
    $ nm test | egrep “main ∣ p r i n t f ∣ g l o b a l | printf|global printfglobal
    080495a0 B global
    08048354 T main
    U printf@@GLIBC_2.0

经链接,global和main的地址都已经确定了,但是printf却还没,因为它是动态链接库glibc中定义函数,需要动态链接,而不是这里的“静态”链接。

重定位:是将符号引用与符号定义进行链接的过程

从上面的演示可以看出,重定位文件test.o中的符号地址都是没有确定的,而经过“静态"链接(gcc默认调用ld进行链接)以后有两个符号地址已经确定了,这样一个确定符号地址的过程实际上就是链接的实质。链接过后,对符号的引用变成了对地址(定义符号时确定该地址)的引用,这样程序运行时就可通过访问内存地址而访问特定的数据。

我们也注意到符号printf在可重定位文件和可执行文件中的地址都没有确定,这意味着该符号是一个外部符号,可能定义在动态链接库中,在程序运行时需要通过动态链接器(ld-linux.so)进行重定位,即动态链接。

通过这个演示可以看出printf确实在glibc中有定义。

$ nm -D /lib/`uname -m`-linux-gnu/libc.so.6 | grep " printf$"
0000000000053840 T printf

除了nm以外,还可以用readelf -s查看.dynsym表或者用objdump -tT查看。

需要提到的是,用nm命令不带-D参数的话,在较新的系统上已经没有办法查看libc.so的符号表了,因为nm默认打印常规符号表(在.symtab.strtab节区中),但是,在打包时为了减少系统大小,这些符号已经被strip掉了,只保留了动态符号(在.dynsym.dynstr中)以便动态链接器在执行程序时寻址这些外部用到的符号。而常规符号除了动态符号以外,还包含有一些静态符号,比如说本地函数,这个信息主要是调试器会用,对于正常部署的系统,一般会用strip工具删除掉。

关于nmreadelf -s的详细比较,可参考

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值