日常学习

link compile

February 19, 2021

为什么c语言中的 header file *.h 一定需要 防止重复被include 呢? 而为什么一定需要 header file的存在呢?

C 语言 到可执行文件的过程

elf 文件的类型:

type 说明 实例
可重定位文件(Relocatable File) 可以被可执行文件或 共享目标文件 链接 Linux: .o
可执行文件(Executable File) 可执行程序, 一般没有 扩展名 Linux: /bin/bash Windows .exe
共享目标文件(Shared Object File ) 1) 可被 其他 可重定位文件 、共享文件 连接成 目标文件 2) 动态链接器 将其与可执行文件结合,映射为 进程的一部分 Linux: .so, Windows: DLL
核心转储文件(Core Dump File) 进程意外终止时候, 系统可以 将进程的地址空间内容 等信息 转储到 该文件中 Linux: core dump
vagrant@precise64:/vagrant_data/link_test$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x91de252bc2c3703aa5c324e5176b05e6b36a5bfa, not stripped

vagrant@precise64:/vagrant_data/link_test$ file main.o
main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

深入 .o 文件


int main() {
  int a = 10;
  name(a);
}

vagrant@precise64:/vagrant_data/link_test$ objdump -h main.o

main.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000020  0000000000000000  0000000000000000  00000040  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000060  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000060  2**2
                  ALLOC
  3 .comment      0000002b  0000000000000000  0000000000000000  00000060  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  0000000000000000  0000000000000000  0000008b  2**0
                  CONTENTS, READONLY
  5 .eh_frame     00000038  0000000000000000  0000000000000000  00000090  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
vagrant@precise64:/vagrant_data/link_test$ readelf -h main.o
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          296 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         12
  Section header string table index: 9

//  注意 在 文件中添加 了 全局变量之后 .data 文件 的size 变大了。
int b = 10;
int main() {
  int a = 10;
  name(a);
}


vagrant@precise64:/vagrant_data/link_test$ objdump -h main.o

main.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000020  0000000000000000  0000000000000000  00000040  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000004  0000000000000000  0000000000000000  00000060  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000064  2**2
                  ALLOC
  3 .comment      0000002b  0000000000000000  0000000000000000  00000064  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  0000000000000000  0000000000000000  0000008f  2**0
                  CONTENTS, READONLY
  5 .eh_frame     00000038  0000000000000000  0000000000000000  00000090  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

vagrant@precise64:/vagrant_data/link_test$ size main.o
   text	   data	    bss	    dec	    hex	filename
     88	      4	      0	     92	     5c	main.o

objdump -s -d main.o 其中 -s 将各个段 都打印出来, -d 则将 代码段反汇编

vagrant@precise64:/vagrant_data/link_test$ objdump -s -d main.o

main.o:     file format elf64-x86-64

Contents of section .text:
 0000 554889e5 4883ec10 c745fc0a 0000008b  UH..H....E......
 0010 45fc89c7 b8000000 00e80000 0000c9c3  E...............
Contents of section .data:
 0000 0a000000                             ....
Contents of section .comment:
 0000 00474343 3a202855 62756e74 752f4c69  .GCC: (Ubuntu/Li
 0010 6e61726f 20342e36 2e332d31 7562756e  naro 4.6.3-1ubun
 0020 74753529 20342e36 2e3300             tu5) 4.6.3.
Contents of section .eh_frame:
 0000 14000000 00000000 017a5200 01781001  .........zR..x..
 0010 1b0c0708 90010000 1c000000 1c000000  ................
 0020 00000000 20000000 00410e10 8602430d  .... ....A....C.
 0030 065b0c07 08000000                    .[......

Disassembly of section .text:

0000000000000000 <main>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	48 83 ec 10          	sub    $0x10,%rsp
   8:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)
   f:	8b 45 fc             	mov    -0x4(%rbp),%eax
  12:	89 c7                	mov    %eax,%edi
  14:	b8 00 00 00 00       	mov    $0x0,%eax
  19:	e8 00 00 00 00       	callq  1e <main+0x1e>
  1e:	c9                   	leaveq
  1f:	c3                   	retq
  vagrant@precise64:/vagrant_data/link_test$ readelf -S main.o
There are 12 section headers, starting at offset 0x128:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000020  0000000000000000  AX       0     0     4
  [ 2] .rela.text        RELA             0000000000000000  00000548
       0000000000000018  0000000000000018          10     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000060
       0000000000000004  0000000000000000  WA       0     0     4
  [ 4] .bss              NOBITS           0000000000000000  00000064
       0000000000000000  0000000000000000  WA       0     0     4
  [ 5] .comment          PROGBITS         0000000000000000  00000064
       000000000000002b  0000000000000001  MS       0     0     1
  [ 6] .note.GNU-stack   PROGBITS         0000000000000000  0000008f
       0000000000000000  0000000000000000           0     0     1
  [ 7] .eh_frame         PROGBITS         0000000000000000  00000090
       0000000000000038  0000000000000000   A       0     0     8
  [ 8] .rela.eh_frame    RELA             0000000000000000  00000560
       0000000000000018  0000000000000018          10     7     8
  [ 9] .shstrtab         STRTAB           0000000000000000  000000c8
       0000000000000059  0000000000000000           0     0     1
  [10] .symtab           SYMTAB           0000000000000000  00000428
       0000000000000108  0000000000000018          11     8     8
  [11] .strtab           STRTAB           0000000000000000  00000530
       0000000000000014  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
vagrant@precise64:/vagrant_data/link_test$ readelf -s main.o

Symbol table '.symtab' contains 11 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    6
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 b
     9: 0000000000000000    32 FUNC    GLOBAL DEFAULT    1 main
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND name  


vagrant@precise64:/vagrant_data/link_test$ objdump -t main.o

main.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 main.c
0000000000000000 l    d  .text	0000000000000000 .text
0000000000000000 l    d  .data	0000000000000000 .data
0000000000000000 l    d  .bss	0000000000000000 .bss
0000000000000000 l    d  .note.GNU-stack	0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame	0000000000000000 .eh_frame
0000000000000000 l    d  .comment	0000000000000000 .comment
0000000000000000 g     O .data	0000000000000004 b
0000000000000000 g     F .text	0000000000000020 main
0000000000000000         *UND*	0000000000000000 name
#+end_src

#### 相似段合并:

```shell
vagrant@precise64:/vagrant_data/link_test$ objdump -h main.o

main.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000020  0000000000000000  0000000000000000  00000040  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000004  0000000000000000  0000000000000000  00000060  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000064  2**2
                  ALLOC
  3 .comment      0000002b  0000000000000000  0000000000000000  00000064  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  0000000000000000  0000000000000000  0000008f  2**0
                  CONTENTS, READONLY
  5 .eh_frame     00000038  0000000000000000  0000000000000000  00000090  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
vagrant@precise64:/vagrant_data/link_test$ objdump -h name.o

name.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000000f  0000000000000000  0000000000000000  00000040  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000050  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000050  2**2
                  ALLOC
  3 .comment      0000002b  0000000000000000  0000000000000000  00000050  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  0000000000000000  0000000000000000  0000007b  2**0
                  CONTENTS, READONLY
  5 .eh_frame     00000038  0000000000000000  0000000000000000  00000080  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

ld main.o  name.o -e main -o ab                  
vagrant@precise64:/vagrant_data/link_test$ objdump -h ab

ab:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000002f  00000000004000e8  00000000004000e8  000000e8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .eh_frame     00000058  0000000000400118  0000000000400118  00000118  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .data         00000004  0000000000600170  0000000000600170  00000170  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .comment      0000002a  0000000000000000  0000000000000000  00000174  2**0
                  CONTENTS, READONLY

重定位表: 用来提供 给链接器 关于重定位 的相关信息, 比如那些需要被调整,怎样调整? 重定位表 即用来专门提供这些信息

   vagrant@precise64:/vagrant_data/link_test$ objdump -r main.o

main.o:     file format elf64-x86-64

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
000000000000001a R_X86_64_PC32     name-0x0000000000000004


RELOCATION RECORDS FOR [.eh_frame]:
OFFSET           TYPE              VALUE
0000000000000020 R_X86_64_PC32     .text



vagrant@precise64:/vagrant_data/link_test$ objdump -d main.o

main.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	48 83 ec 10          	sub    $0x10,%rsp
   8:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)
   f:	8b 45 fc             	mov    -0x4(%rbp),%eax
  12:	89 c7                	mov    %eax,%edi
  14:	b8 00 00 00 00       	mov    $0x0,%eax
  19:	e8 00 00 00 00       	callq  1e <main+0x1e>
  1e:	c9                   	leaveq
  1f:	c3                   	retq

因为  main中 存在对 外部函数(name.c 中name ) 的调用,所以 call name 命令 的地址 需要被 ld 调整修改,即是 .rel.text 段中需要记录的信息. 在  重定位表 中, offset 代表 改需要被重定位的符号,在段中的偏移,
main.o 中的 rel.text name中的offset 为1a, 即是 main.o 中的代码段 callq 的地址部分。
common 块

静态链接: 一种语言的开发环境 往往带有语言库,这些苦 就是对操作系统的api 封装,其实一个静态库可以简单的堪称一组目标文件的集合,很多目标文件经过压缩打包后 形成的一个文件, 比如linux 下 /usr/lib/libc.a

链接脚本控制: 即是 控制ld 参数的方式:

动态连接: 页映射,

装载的方式: 程序执行时所需要的指令和数据 都必须在内存中 才能够正常运行。将程序的指令和数据 装载到内存中 就称为 装载。覆盖装入(overlay) 和页面映射 (Paging) 是典型的两种装载方法。

页映射:

进程的建立:

进程虚拟空间分布:

堆栈:在进程的虚拟空间中,同样是以 VMA 存在的, 很多情况下,一个进程的堆和栈 都有一个对应的VMA, Linux可以通过/proc 来查看进程的 虚拟空间分布
vagrant@precise64:/vagrant_data/link_test$ sleep 100 &
[1] 3834
vagrant@precise64:/vagrant_data/link_test$ cat /proc/3834/maps
00400000-00406000 r-xp 00000000 fc:00 2359461                            /bin/sleep
00605000-00606000 r--p 00005000 fc:00 2359461                            /bin/sleep
00606000-00607000 rw-p 00006000 fc:00 2359461                            /bin/sleep
01ee5000-01f06000 rw-p 00000000 00:00 0                                  [heap]
7fba0d791000-7fba0da96000 r--p 00000000 fc:00 3413573                    /usr/lib/locale/locale-archive
7fba0da96000-7fba0dc4d000 r-xp 00000000 fc:00 2752737                    /lib/x86_64-linux-gnu/libc-2.15.so
7fba0dc4d000-7fba0de4c000 ---p 001b7000 fc:00 2752737                    /lib/x86_64-linux-gnu/libc-2.15.so
7fba0de4c000-7fba0de50000 r--p 001b6000 fc:00 2752737                    /lib/x86_64-linux-gnu/libc-2.15.so
7fba0de50000-7fba0de52000 rw-p 001ba000 fc:00 2752737                    /lib/x86_64-linux-gnu/libc-2.15.so
7fba0de52000-7fba0de57000 rw-p 00000000 00:00 0
7fba0de57000-7fba0de79000 r-xp 00000000 fc:00 2756455                    /lib/x86_64-linux-gnu/ld-2.15.so
7fba0e06c000-7fba0e06f000 rw-p 00000000 00:00 0
7fba0e077000-7fba0e079000 rw-p 00000000 00:00 0
7fba0e079000-7fba0e07a000 r--p 00022000 fc:00 2756455                    /lib/x86_64-linux-gnu/ld-2.15.so
7fba0e07a000-7fba0e07c000 rw-p 00023000 fc:00 2756455                    /lib/x86_64-linux-gnu/ld-2.15.so
7fff892a4000-7fff892c5000 rw-p 00000000 00:00 0                          [stack]
7fff89388000-7fff89389000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]       
对于 输出结构 的描述:

第一列为 VMA 的地址范围, 第二列 为 权限, 第三列 为偏移,标识VMA对应的Segment 在映射文件中的偏移, 
第四列为 映射文件 所在设备的主、次设备号 (可以为0 即没有映射文件 比如stack heap), 第五列: 映射文件的节点号, 最后为 映射文件路径

进程栈初始化: 进程运行存在一些环境变量以及 运行参数, 常见的做法 是 操作系统 在进程启动时 将这些基本信息 提前保存到 进程的虚拟空间栈中

动态链接:

动态链接 导致的问题: 因为动态链接 将在程序编译阶段的工作推迟到了 运行时间段,导致 程序每次被装载时 都要进行 重新进行连接。导致一些程序性能上的损失,性能损失 大约在5%以下。这些损失 换来 程序在空间上的 节省和程序构建升级上的便利性,是相当值得的。

地址无关代码:

gcc -fPIC -shared -o name.so name.c

    1. 同4中一样, 使用 GOT 来保存 函数地址
  指令跳转、调用 数据访问    
模块内部 相对跳转和调用 相对地址访问    
模块外部 间接跳转和调用(GOT) 间接访问(GOT)    

-fpic 与 -fPIC 参数的区别: -fPIC 产生的代码要更大, 更跨平台, 而-fpic 产生的代码更小, 也更快,但是存在一些限制,更与硬件相关

由上可见: 动态链接 会使程序变慢的 原因有: 1) 动态装载,重定位计算, 2) 共享代码段中 通过GOT来访问数据,调用函数

延迟绑定(Procedure Linkage Table): 共享模块加载时候,程序需要浪费不少时间来 用于解决模块之间的函数应用的符号查找 以及重定位。 而在实际中 大量的函数很少被使用到, 所以 ELF采用了一种叫做延迟绑定的做法, 基本的思想就是 当函数第一次被用到时候 才进行绑定(符号查找,重定位等)

动态链接 文件启动 顺序: 与静态链接情况基本一致, 首先操作系统 读取可执行文件 的头部,检查合法性, 并从 header中读取各个Segment 映射到进程虚拟空间的对应文职。 这时候差异开始显现, 静态链接情况下, 操作系统将 控制权直接交给 可执行文件的入口地址,然后程序开始执行。 动态链接情况下: 操作系统并不能将控制权交给可执行文件, 因为其 依赖的 很多共享对象,还没有加载起来,所以操作系统 先启动一个动态链接器,由动态链接器 来进行一系列的初始化工作,并进行动态链接工作,当所有 链接工作完成以后, 动态联机器将 控制权 交给可执行文件 的入口地址,程序开始正式执行

  1. 动态链接器的路径配置: .interp 段 中 为 ld 的具体路径
  2. 动态符号表: 为了完成链接, 需要 所依赖的符号和相关文件的信息, 在静态链接中 有 段为 .symtab (Symbol Table) 动态链接中 则为 Dynmic Symbol Table .dynsym 其中只保存 动态链接相关的符号。很多动态链接库中同时存在.dynsym 以及 .symtab 两个表, .symtab 中 保存了所有的符号。其中的符号 字符串 则保存在 .dynstr (Dynamic String Table) 其中往往还有 符号哈希表 用于加快符号查找速度

readelf -sD lib.so

动态链接 需要在装载时候 进行重定位, 对于 使用 PIC 模式编译的(地址无关代码) 则只需要对GOT进行 重定位, 非PIC编译的,则在装载时候 重定位(即 代码段 也被 修改,而无法与其他程序共享)
重定位结构: 动态链接中 重定位表 为.rel.dyn, .rel.plt 对应于静态链接中的 .rel.text, .rel.data 分别对应 .got (数据段), 以及 .got.plt (代码段,函数调用地址的修正)

root@precise64:/vagrant_data/link_test# readelf -r main.so

Relocation section ‘.rela.dyn’ at offset 0x420 contains 4 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000201010 000000000008 R_X86_64_RELATIVE 0000000000201010
000000200fd0 000300000006 R_X86_64_GLOB_DAT 0000000000000000 gmon_start + 0
000000200fd8 000400000006 R_X86_64_GLOB_DAT 0000000000000000 _Jv_RegisterClasses + 0
000000200fe0 000500000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize + 0

Relocation section ‘.rela.plt’ at offset 0x480 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000201000 000200000007 R_X86_64_JUMP_SLO 0000000000000000 name + 0
000000201008 000500000007 R_X86_64_JUMP_SLO 0000000000000000 __cxa_finalize + 0

动态链接器自举: 因为动态链接器 也是一个共享库,而动态链接器是操作系统的程序入口, 所以动态链接器如何递归的解决自己的依赖关系,完成启动,是一个 非常有意思的事情。

如何 避免全局符号介入问题: 对于 导出 符号(全局符号) 是不可能解决的,然而 内部函数调用, 可以使用 static 关键字 来避免其成为 全局符号 而被 其他模块覆盖,则在内部 程序直接使用 内部 相对地址调用,也加快了 程序的调用速度。 即 不适用 .got.plt 来 进行 间接跳转。
共享库的 组织方式: