Object file

在Linux系统中主要有三种object file,它们使用Excutable and Linkable Format (ELF):

  • relocatable object file 可重定位的目标文件
  • executable object file 可运行的目标文件
  • shared object file 共享目标文件

链接

链接其实就是由relocatablle object file和shared object file生成executable object file的过程,executable object file可以加载到内存中执行。

考虑一下这个过程,在relocatable object file中的变量类型:

  1. 在本文件中定义的全局变量(函数)
  2. 在本文件中定义的静态变量(函数)
  3. 在本文件中定义的非静态局部变量
  4. 在其他文件中定义的全局变量(函数)

其中前面三种变量都可以在当前文件中找到定义,而如何解析最后一种变量就是链接过程需要做的工作。

ELF sections

ELF包含了几个关键的sections,理解这些sections的作用会对理解链接过程起到很大的作用。

Section 描述
.text 程序代码段
.data 初始化的全局变量
.bss 未初始化的全局变量
.symtab 符号表
.debug 变量调试符号表
.rodata 只读数据,如printf中的format strings,switch的jump tables
... ...

看一个具体的例子,修改于CSAPP中提供的例子:

// main.c
void swap(void);

int buf[2] = {1, 2};

int main(void)
{
    static si = 0;
    ++si;
    char *str = "swap():";

    swap();
    printf("%s success\n", str);
    return 0;
}

// swap.c
extern int buf[];

int *bufp0 = &buf[0];
int *bufp1;

void swap()
{
    static int si;
    int temp;

    bufp1 = &buf[1];
    temp = *bufp0;
    *bufp0 = *bufp1;
    *bufp1 = temp;
}

接下来完成预处理、编译、汇编和链接四个阶段,可以得到一个可执行文件,具体的过程可以参见man gcc,如-E选项会使得gcc在preprocessing stage后停止。

在汇编阶段之后会获得main.o和swap.o,这两个object file是relocatable的,使用readelf来查看它们的符号表:

Machine: X86-64 ubuntu GNU readelf: 2.24.90.20141014

# readelf -a main.o
Symbol table '.symtab' contains 15 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
         1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
         2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
         3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
         4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4
         5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
         6: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 si.2203
         7: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
         8: 0000000000000000     0 SECTION LOCAL  DEFAULT    7
         9: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
        10: 0000000000000000     8 OBJECT  GLOBAL DEFAULT    3 buf
        11: 0000000000000000    16 FUNC    GLOBAL DEFAULT    1 main
        12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND swap
        13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
        14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND __stack_chk_fail
# nm main.o
0000000000000000 D buf
0000000000000000 T main
0000000000000000 b si.2203
                  U __stack_chk_fail
                  U swap
# objdump -s main.o
...
Contents of section .rodata:
0000 25732073 75636365 73730a00           %s success..
...

main.o的符号表中包含了buf,main和swap等五个符号,buf处在初始化的数据段中(.data),main处在代码段中(.text),swap符号没有定义(undefined),本地静态变量si处于未初始化的数据段中(.bss),注意在符号表中它加上了一个后缀,这样如果在其他的object file中定义了相同名字的本地静态变量si,它们之间不会产生冲突。printf中的format string存在了rodata段中。

# readelf -a swap.o
Symbol table '.symtab' contains 13 entries:
        Num:    Value          Size Type    Bind   Vis      Ndx Name
        0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
        1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS swap.c
        2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
        3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
        4: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
        5: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    5 si.2202
        6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7
        7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8
        8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
        9: 0000000000000000     8 OBJECT  GLOBAL DEFAULT    3 bufp0
        10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND buf
        11: 0000000000000008     8 OBJECT  GLOBAL DEFAULT  COM bufp1
        12: 0000000000000000    59 FUNC    GLOBAL DEFAULT    1 swap
# nm swap.o
                 U buf
0000000000000000 D bufp0
0000000000000008 C bufp1
0000000000000000 b si.2202
0000000000000000 T swap

swap.o的符号表中包含了四个符号。bufp1是common symbol,它存在.bss中,注意bufp0和bufp1的区别,前者是已初始化的全局变量,它是一个strong符号,而bufp1是一个weak符号。

strong符号(初始化的全局变量和函数)和weak符号(未初始化的全局变量)是由编译器确定的,在全局符号的解析过程中,当遇到同名的符号时:

  1. 不允许出现多个同名的强符号
  2. 当出现一个强符号和多个弱符号时,选择强符号
  3. 当出现多个弱符号时,任意选择一个弱符号

链接过程实际上是实现了符号的解析和重定位过程。

gcc -o p main.o swap.o

变量存储的位置及变量作用域的实现

  • 全局变量:存储在bss或data,所有文件都可以访问
  • 本地静态变量:存储在bss或data,当前文件可访问
  • 静态分配的局部变量:存储在栈上,只有当前函数可以访问
  • 动态分配的局部变量:存储在堆上,如果没有被释放,而且保存了变量的地址,所有文件都可以访问

静态链接和动态链接

为什么需要静态链接库?

考虑如何为应用程序提供C库的支持:一种是编译器自身能理解所有c库中的符号,执行相应的链接操作;另一种是将所有C库编译成一个relocatable oject file。但这样存在一些明显的缺点,如果要对修改C库的接口,第一种选择就需要改变编译器的实现,第二种选择需要重新编译生成新的relocatable object file。

人的智慧是无穷的,更好的方法就是将一个个的库函数分别编译成一个个的relocatable object file,然后将它们打包成一个archive,称为静态链接库。这样无论是修改还是更新C库的接口带来的影响都比较小。

在实际的编译过程中,编译器纪录下当前未被解析的符号定义,然后如果在静态链接库中找到相应的符号,就将相应的relocatable object file包含进来一起链接。

为什么需要动态链接库?

考虑一下这个画面,小明一个学期写了100个C程序,每个程序中都使用了printf()函数,如果使用静态链接库,同时执行这100个程序,内存中就会有printf.o(包含printf()的object file)的100个拷贝,对于内存这种能省即省的资源,这绝对是一种浪费!

天下大势,总有更牛逼的人出现。然后动态链接库就被设计出来了,shared object file在内存中只保留一个副本,链接器在链接时只是拷贝一些符号信息,实际的链接过程推迟到了运行时。

参考

CSAPP

Comments

Powered by Pelican. Booler's Adventure © 不贰 2014-2016