快速搞懂 ”ld“ ——链接器

    链接器的功能:将一个可执行程序所需的目标文件和库最终整合在一起。
    一个程序包含三个段:.text 、.data 和 .bss 段。
    而各目标文件和库都包含这三段,所以,ld 链接器的功能就是将各个目标文件和库的三段合并在一起。
    当然,链接器所完成的链接工作并非只是简单地将各个目标文件和库的三段内存堆砌在一起,而是还要完成 “重定位” 的工作。

1、重定位:
         链接而生成的可执行程序虽然放在文件中,但当程序运行时需要加载到内存中。而上述的三段烦恼光在内存的什么位置是由可执行程序文件内的头部信息指定的。
         一旦一个程序被加载到内存中,就意味着不论是函数还是变量,他们都会在内存中占据一定的空间,而这关系着内存地址。
         从处理器的角度来说,当我们在 C 程序中写下一行调用函数的代码,就意味着在调用此函数时,需要跳转到相应的内存地址(如 main :0x03H),那么,处理器如何知道跳转到 0x03H 的内存地址中去呢?这就是链接器的工作。
         当一个源文件被编译成目标文件时,此时目标文件中的各段并没有具体的地址,只是在目标文件中记录了程序中的符号和各符号在段中的相对地址。当链接器将所有的目标文件整合成一个可执行程序文件时,各目标文件中的各段将会真正的获得在内存中的具体地址。从而实现真正的函数调用和变量引用功能,这一动作就是重定位。
         那么,链接器如何知道每一个目标文件中的各个段应当放在哪一地址处?这就需要链接脚本来指定。

2、链接脚本:  
    
    
    
    
  1. GNU ld version 2.17.50.0.6-5.el5 20061020 Supported emulations: elf_i386 i386linux using internal linker script: 
  2. ==================================================
  3. * Script for -z combreloc: combine and sort reloc sections */
  4. OUTPUT_FORMAT("elf32-i386", "elf32-i386",
  5. "elf32-i386")
  6. OUTPUT_ARCH(i386)
  7. ENTRY(_start)
  8. SEARCH_DIR("/usr/i386-redhat-linux/lib"); SEARCH_DIR("/usr/local/lib"); SEARCH_DIR("/lib"); SEARCH_DIR("/usr/lib");
  9. SECTIONS
  10. {
  11. /* Read-only sections, merged into text segment: */
  12.     PROVIDE (__executable_start = 0x08048000); . = 0x08048000 + SIZEOF_HEADERS;
  13.     .interp : { *(.interp) }
  14.      .hash : { *(.hash) }
  15.      .gnu.hash : { *(.gnu.hash) }
  16.      .dynsym : { *(.dynsym) }
  17.      .dynstr : { *(.dynstr) }
  18.      .gnu.version : { *(.gnu.version) }
  19.      .gnu.version_d : { *(.gnu.version_d) }
  20.      .gnu.version_r : { *(.gnu.version_r) }
  21.      .rel.dyn :
  22.      {
  23.          *(.rel.init)
  24.          *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
  25.          *(.rel.fini)
  26.          *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
  27.          *(.rel.data.rel.ro* .rel.gnu.linkonce.d.rel.ro.*)
  28.          *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
  29.          *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
  30.          *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
  31.          *(.rel.ctors)
  32.          *(.rel.dtors)
  33.          *(.rel.got)
  34.          *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
  35.      }
  36.      .rela.dyn :
  37.      {
  38.          *(.rela.init)
  39.          *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
  40.          *(.rela.fini)
  41.          *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
  42.          *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
  43.          *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
  44.          *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
  45.          *(.rela.ctors)
  46.          *(.rela.dtors)
  47.          *(.rela.got)
  48.          *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
  49.      }
  50.      .rel.plt : { *(.rel.plt) }
  51.      .rela.plt : { *(.rela.plt) }
  52.      .init :
  53.      {
  54.           KEEP (*(.init))
  55.      } =0x90909090
  56.      .plt : { *(.plt) }
  57.      .text :
  58.      {
  59.          *(.text .stub .text.* .gnu.linkonce.t.*)
  60.          KEEP (*(.text.*personality*))
  61.          /* .gnu.warning sections are handled specially by elf32.em. */
  62.          *(.gnu.warning)
  63.      } =0x90909090
  64.      .fini :
  65.      {
  66.           KEEP (*(.fini))
  67.      } =0x90909090
  68.      PROVIDE (__etext = .);
  69.      PROVIDE (_etext = .);
  70.      PROVIDE (etext = .);
  71.      .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
  72.      .rodata1 : { *(.rodata1) }
  73.      .eh_frame_hdr : { *(.eh_frame_hdr) }
  74.      .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) }
  75.      .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) }
  76.      /* Adjust the address for the data segment. We want to adjust up to
  77.     the same address within the page on the next page up. */
  78.      . = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1    )); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
  79.      /* Exception handling */
  80.     .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) }
  81.      .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
  82.      /* Thread Local Storage sections */
  83.      .tdata : { *(.tdata .tdata.* .gnu.linkonce.td.*) }
  84.      .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
  85.      .preinit_array :
  86.      {
  87.          PROVIDE_HIDDEN (__preinit_array_start = .);
  88.          KEEP (*(.preinit_array))
  89.          PROVIDE_HIDDEN (__preinit_array_end = .);
  90.      }
  91.      .init_array :
  92.      {
  93.          PROVIDE_HIDDEN (__init_array_start = .);
  94.          KEEP (*(SORT(.init_array.*)))
  95.          KEEP (*(.init_array))
  96.          PROVIDE_HIDDEN (__init_array_end = .);
  97.      }
  98.      .fini_array :
  99.      {
  100.          PROVIDE_HIDDEN (__fini_array_start = .);
  101.          KEEP (*(.fini_array))
  102.          KEEP (*(SORT(.fini_array.*)))
  103.          PROVIDE_HIDDEN (__fini_array_end = .);
  104.      }
  105.      .ctors :
  106.      {
  107.           /* gcc uses crtbegin.o to find the start of
  108.          the constructors, so we make sure it is
  109.          first. Because this is a wildcard, it
  110.         doesn't matter if the user does not
  111.          actually link against crtbegin.o; the
  112.          linker won't look for a file to match a
  113.          wildcard. The wildcard also means that it
  114.         doesn't matter which directory crtbegin.o
  115.          is in. */
  116.          KEEP (*crtbegin.o(.ctors))
  117.          KEEP (*crtbegin?.o(.ctors))
  118.           /* We don't want to include the .ctor section from
  119.         the crtend.o file until after the sorted ctors.
  120.          The .ctor section from the crtend file contains the
  121.          end of ctors marker and it must be last */
  122.          KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
  123.          KEEP (*(SORT(.ctors.*)))
  124.          KEEP (*(.ctors))
  125.     }
  126.      .dtors :
  127.      {
  128.          KEEP (*crtbegin.o(.dtors))
  129.          KEEP (*crtbegin?.o(.dtors))
  130.          KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
  131.           KEEP (*(SORT(.dtors.*)))
  132.          KEEP (*(.dtors))
  133.    }
  134.      .jcr : { KEEP (*(.jcr)) }
  135.      .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro* .gnu.link     once.d.rel.ro.*) }
  136.      .dynamic : { *(.dynamic) }
  137.      .got : { *(.got) }
  138.      . = DATA_SEGMENT_RELRO_END (12, .);
  139.      .got.plt : { *(.got.plt) }
  140.      .data :
  141.      {
  142.          *(.data .data.* .gnu.linkonce.d.*)
  143.          KEEP (*(.gnu.linkonce.d.*personality*))
  144.          SORT(CONSTRUCTORS)
  145.      }
  146.      .data1 : { *(.data1) }
  147.     _edata = .; PROVIDE (edata = .);
  148.    _bss_start = .;
  149.    .bss :    
  150.     {
  151.          *(dynbss)
  152.          *(.bss .bss.* .gnu.linkonce.b.*)
  153.          *(COMMON)
  154.          /* Align here to ensure that the .bss section occupies space up to
  155.          _end. Align after .bss to ensure correct alignment even if the
  156.          .bss section disappears because there are no input sections.
  157.          FIXME: Why do we need it? When there is no .bss section, we don't
  158.          pad the .data section. */
  159.          . = ALIGN(. != 0 ? 32 / 8 : 1);
  160.      }
  161.      . = ALIGN(32 / 8);
  162.      . = ALIGN(32 / 8);
  163.      _end = .; PROVIDE (end = .);
  164.      . = DATA_SEGMENT_END (.);
  165.      /* Stabs debugging sections. */
  166.      .stab 0 : { *(.stab) }
  167.      .stabstr 0 : { *(.stabstr) }
  168.      .stab.excl 0 : { *(.stab.excl) }
  169.      .stab.exclstr 0 : { *(.stab.exclstr) }
  170.      .stab.index 0 : { *(.stab.index) }
  171.      .stab.indexstr 0 : { *(.stab.indexstr) }
  172.      .comment 0 : { *(.comment) }
  173.      /* DWARF debug sections.
  174.     Symbols in the DWARF debugging sections are relative to the beginning
  175.     of the section so we begin them at 0. */
  176.      /* DWARF 1 */
  177.      .debug 0 : { *(.debug) }
  178.      .line 0 : { *(.line) }
  179.      /* GNU DWARF 1 extensions */
  180.      .debug_srcinfo 0 : { *(.debug_srcinfo) }
  181.      .debug_sfnames 0 : { *(.debug_sfnames) }
  182.      /* DWARF 1.1 and DWARF 2 */
  183.      .debug_aranges 0 : { *(.debug_aranges) }
  184.      .debug_pubnames 0 : { *(.debug_pubnames) }
  185.      /* DWARF 2 */
  186.      .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
  187.      .debug_abbrev 0 : { *(.debug_abbrev) }
  188.      .debug_line 0 : { *(.debug_line) }
  189.      .debug_frame 0 : { *(.debug_frame) }
  190.      .debug_str 0 : { *(.debug_str) }
  191.      .debug_loc 0 : { *(.debug_loc) }
  192.     .debug_macinfo 0 : { *(.debug_macinfo) }
  193.      /* SGI/MIPS DWARF 2 extensions */
  194.      .debug_weaknames 0 : { *(.debug_weaknames) }
  195.      .debug_funcnames 0 : { *(.debug_funcnames) }
  196.      .debug_typenames 0 : { *(.debug_typenames) }
  197.      .debug_varnames 0 : { *(.debug_varnames) }
  198.      /* DWARF 3 */
  199.      .debug_pubtypes 0 : { *(.debug_pubtypes) }
  200.      .debug_ranges 0 : { *(.debug_ranges) }
  201.      /DISCARD/ : { *(.note.GNU-stack) }
  202. }

          链接脚本的功能:告诉链接器,如何将各个不同的目标文件(包括库)中的段合并在一起,并最终生成一个可执行程序。从链接脚本的角度来看,一方面它需要描述输出,即最终生成一个可执行程序的段;另一方面又要描述输入,即来自各个目标文件的段。
     2.1、段
         一个可执行程序最终会分成多个段的,当引导加载器加载一个可执行程序时,会根据文件的头信息将各个段位于文件中的内容复制到内存中,至于复制到内存中的哪一个具体地址是通过链接脚本指定的。
         以下是一个很简单的链接脚本:
    
    
    
    
  1. SECTIONS
  2. {
  3. . = 0x1000000;
  4. .text :
  5. {
  6. *(.text)
  7. }
  8. .data 0x8000000;
  9. {
  10. *(.data)
  11. }
  12. .bss :
  13. {
  14. *(.bss)
  15. }
  16. }
         通过使用和操控位置指针就能实现对各个段在地址空间的安排,位置指针的值其实代表的就是处理器的地址空间。
         对以上程序作如下分析:
  1. 刚进入 SECTIONS ,位置指针为 0;
  2. 第三行是一个改变位置指针的语句,其中 "  .  " 就是位置指针,将其设置成 0x1000000 之后,后面的 .text 段将从内存地址 0x1000000 处开始存放。
  3. 第四 ~ 七行是第一个输出段描述语句块,它表示生成的可执行程序中将有一个 .text 段。由于 .text 段所处的位置指针值为 0x1000000 ,因此当出现被加载时位于可执行程序中的 .text 段的内容将被拷贝到 0x1000000 内存地址开始处。输出到可执行程序的段中包含什么?在第六行的输入段来指定,它表示,所有目标文件中 .text 段中。
  4. 第八 ~ 十一行定义了可执行程序的 .data 段。注意,这里采用的指定位置指针的方法不同上。第八行指明 .data 段在程序运行时所存放的开始地址是 0x8000000 。第十行的输入段描述指定所有库和目标文件中的 .data 段。
  5. 第十二 ~ 十五行定义了可执行程序的 .bss 段,注意,在此链接脚本中,并没有指定 .bss 段的位置指针,因此,它的位置完全取决于程序在链接时位置指针的值,而位置指针的值取决于它之前 .data 输出段的大小。
  6. 当链接器生成可执行程序时,会将每一个段在内存中的开始地址及段大小这些信息放在可执行程序文件的头部,而这些信息是为程序加载器加载程序所准备的。

    2.2、符号
         链接脚本中除了指定段和段在内存中的地址外,还可以定义符号。
    
    
    
    
  1. SECTIONS
  2. {
  3. . = 0x1000000;
  4. .text :
  5. {
  6. *(.text)
  7. }
  8. .data 0x8000000;
  9. {
  10. *(.data)
  11. }
  12. .bss :
  13. {
  14. __bss_start__ = .;
  15. *(.bss)
  16. __bss_end__ = .;
  17. }
  18. __end__ = .;
  19. }
         在第 14 、16 和 18 行分别定义了三个符号。__end__符号代表了程序各段所占用内存空间的结束地址,或者说它代表了堆的起始地址。这些符号的值都是使用位置指针加以指定的。符号一旦在链接脚本中定义后,就可以在程序中使用它们。
    
    
    
    
  1. #include
  2. extern char _bss_start__[];
  3. extern char _bss_end__[];
  4. extern char _end__[];
  5. int main()
  6. {
  7. printf("__bss_start__ = %p\n", _bss_start__);
  8. printf("__bss_end__ = %p\n", _bss_end__);
  9. printf("__end__ = %p\n", _end__);
  10. return 0;
  11. }
         注意:
  1. 所声明的变量与链接脚本中定义的符号相差一个下划线前缀。

         运行结果:
$ gcc -Wl,-Map=main.map main.c -o main.exe
$ ./main.exe
__bss_start__ = 0x404000
__bss_end__ = 0x404130
__end__ = 0x406000

    2.3、存储区域
         在默认情况下,ld 认为整个可执行程序都是放入同一个存储空间的。如果一个嵌入式系统中存在多块不同的存储空间,就得使用 MEMORY 命令进行存储区域定义。
    
    
    
    
  1. MEMORY
  2. {
  3. RAM0 (WX) : ORIGIN = 0x40000000, LENGTH = 256K
  4. RAM1 (WX) : ORIGIN = 0, LENGTH = 2M
  5. }
  6. SECTIONS
  7. {
  8. .text :
  9. {
  10. . += 0x10000;
  11. *(.text)
  12. } > RAM1
  13. .data :
  14. {
  15. *(.data)
  16. } > RAM1
  17. .bss :
  18. {
  19. *(.bss)
  20. } > RAM0
  21. }
         RAM0 和 RAM1 都被定义成可以读写和执行的存储区域。在 SECTIONS 命令中,将 .bss 段放入了 RAM0 存储区域中,而将 .text 和 .data 段放入了 RAM1 存储区域中。
         使用存储区域时,如果链接器碰到存放段大于存储区域的容量时就会发出警报,我们可以利用链接器这一特性,通过定义多个(连续的)存储区域的形式监视各段是否超出规定的大小。

    2.4、常用命令
         2.4.1 ALIGN 和 BLOCK 命令
              ALIGN 格式:
  • ALIGN (_align)
  • ALIGN (_exp, _align)
              第一个 ALIGN 命令将返回位置指针之后的第一个满足边界对齐字节数 _align 的地址值,
              第二个 ALIGN 命令返回 _exp 表达式之后的、满足边界对齐字节数 _align 的地址值。
         BLOCK 命令等同于第一个 ALIGN 命令,它的存在是为了兼容老的语法

         2.4.2、BYTE、SHORT、LONG 和 QUAD 命令
              格式:
  • BYTE ( _value)
  • SHORT ( _value)
  • LONG ( _value)
  • QUAD ( _value)
              这四个命令依次表示在输出的可执行程序文件中放置所在存储空间为 1、2、4 和 8 字节的值,值由参数 _value 指定。
     
     
     
     
  1. SECTIONS
  2. {
  3. .text :
  4. {
  5. *(.text)
  6. }
  7. LONG(1)
  8. .data :
  9. {
  10. *(.data)
  11. }
  12. }
    
         2.4.3、ENTRY 命令
              格式:
  • ENTRY (_symbol)
              这个命令用于指定可执行程序的入口点。入口点是指程序被加载到内存后,运行的第一条指令所在的内存地址

         2.4.4、MEMORY 命令
              格式:
  •        
           
           
           
    1. MEMORY
    2. {
    3. name [(_attr)] : ORIGIN = _origin, LENGTH = _len
    4. ...
    5. }
  •         
            
            
            
    1. MEMORY
    2. {
    3. name [(_attr)] : org = _origin, l = _len
    4. ...
    5. }

         2.4.5、PROVIDE 命令
              格式:
  • PROVIDE (_symbol = _expression)
              在某些情况下,我们希望脚本中定义的符号只有当它被引用时才出现,这就可以通过 PROVIDE 命令来实现。
     
     
     
     
  1. SECTIONS
  2. {
  3. . = 0x1000000;
  4. .text :
  5. {
  6. *(.text)
  7. }
  8. .data 0x8000000;
  9. {
  10. *(.data)
  11. }
  12. .bss :
  13. {
  14. PROVIDE (__bss_start__ = .);
  15. PROVIDE (_bss_start__ = .);
  16. *(.bss)
  17. PROVIDE (__bss_end__ = .);
  18. PROVIDE (_bss_end__ = .);
  19. }
  20. PROVIDE (__end__ = .);
  21. PROVIDE (_end__ = .);
  22. }
         
          2.4.6、SECTIONS 命令
              格式:
                   SECTIONS
                   {
                             sections-command
                              sections-command
                             ...
                    }
                      参考文献:《专业嵌入式软件开发》李云·著                                                             2016年7月11日,星期一

你可能感兴趣的:(Linux,C)