锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

重学计算机(五、静态链接和链接控制)

时间:2023-12-07 06:37:01 传感器lr18xbn08lum

本来上一节链接的时候,我也打算把这个静态链接写进去,但是因为上一节太长,我把这个推迟到了这一节。

5.1 静态库

5.1.1 什么是静态库?

在之前的学习之后,你也应该知道什么是静态库。

事实上,静态库也是一种目标文件ELF我们可以用命令来查看格式文件:

root@ubuntu:~/c_test/05# readelf -h libc.a   File: libc.a(init-first.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: 1568 (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 File: libc.a(libc-start.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:          6544 (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:         15   Section header string table index: 12      ... 

后面省略了很多段,其实静态库很多.o文件一起打包,分散也可以,但管理不方便,如果我的程序需要一个函数,那么我必须去找我用来的.o然后链接文件?

这很麻烦,所以通常提供很多.o文件,然后直接打包,当我们直接调用静态库函数时,链接器会找到相应的实现,这在后面说。

5.1.2 ar命令

讲到静态库,那我们就又介绍一个新命令:ar命令。

Linux ar命令

以上是猜测你教程介绍的命令。在这里,我还强调我们经常使用的两个参数。

-r  将文件插入备存文件中 -t  显示备存文件中包含的文件 -x  在自备存储文件中取出成员文件 

我们现在就用-t来查看libc.a到处都有文件:

root@ubuntu:~/c_test/05# ar -t libc.a | wc -l 1579 

太多了,就不列出来了,不然就说占字数了,所以我做了统计,总共有1579个.o文件。

这里重点介绍一下为什么有这么多。.o事实上,只要我们是一个函数.o组织文件的形式。

为什么会这样?

因为链接器链接是基于.o如果所有函数都在一个基本单位,则文件为基本单位.o文件和链接将合并其他部分,因此链接后的程序将相对较大,因此需要拆分每个函数.o文件,所以不要链接无用的函数。

解压缩静态库:

ar -x libc.a 

文件似乎无法指定,这个命令确实有点缺陷

5.1.3 制作静态库

动态库的制作也比较简单,我用上一节编译.o制作文件。

root@ubuntu:~/c_test/05# ar -r fun2.a fun2.o ar: creating fun2.a root@ubuntu:~/c_test/05# 

是不是也比较简单,可以查看里面的函数:

root@ubuntu:~/c_test/05# ar -t fun2.a
fun2.o
root@ubuntu:~/c_test/05# 

这个就一个函数,所以直接贴上来了,是不是很靠谱。

5.1.4 链接器使用静态库来解析引用

吹了那么多水,终于来到了这一篇的重点内容了。

先来看看怎么使用我们上面制作的静态库。

root@ubuntu:~/c_test/05# gcc -static hello_world.c fun2.a

或者可以这样:

root@ubuntu:~/c_test/05# gcc -static hello_world.c -L. -lfun2

这种就是利用链接器去匹配fun2的库,不过这样的话,静态库的名字前面需要lib,这样链接器才能去匹配。

第一种是直接指定了,所以不需要用名字匹配。

上面我们只是一个静态库,情况是比较简单的,当静态库比较多的时候,并且又相互调用的时候,这种情况就很头疼。这个重点来了:

linux解析外部引用的方式:在符号解析阶段,链接器从左到右按照它们顺序来扫描可重定位文件和存档文件。

扫描的过程中,链接器会维护一个可重定位目标文件的集合E,一个未解析的符号集合U,以及一个已经定义的集合D。

开始时,E,U,D都是空,链接器会一次扫描文件的顺序

  • 如果文件是一个目标文件,加入到集合E中,并更新集合U和集合D
  • 如果文件是一个存档文件,就会尝试用U去匹配存档文件,如果存在文件m,就把m加入到E中,并更新U和D。此时,任何不在集合E中的目标文件,都被丢弃。
  • 直到链接器扫描完成,如果集合U非空,就会报一个符号未找到,如果结合U为空,就输出可执行文件。

就因为这样,所以如果库多了就会出现问题,库的顺序要注意一点:符号的引用要在符号的定义之前。

比如:foo.c调用了libx.a中的函数,然后libx.a又调用了liby.a的库,而liby.a又调用了libx.a的库(这个就是相互调用了),这个怎么办呢?

root@ubuntu:~/c_test/05# gcc foo.c liby.a liba.x

所以需要注意顺序。

5.2 链接过程控制

虽然我们前面前面讲了很多链接过程,并且也讲的很清楚了,但是还有一部分还没讲,就是链接的过程我们是可以控制的,我们之前搞的链接过程,其实都是使用了默认的链接脚本。这一节,我们就来好好分析分析。

5.2.1 链接控制脚本

链接器提供了多种控制整个链接过程的方法。

  1. 使用命令行来给链接器指定参数,比如有-o,-e之类的参数。
  2. 将链接指令存放在目标文件里面,编译器经常会通过这种方法向链接器传递指令。
  3. 使用链接控制脚本。(当年搞嵌入式的时候,就是用链接脚本)

我们之前使用ld的时候,并没有指定链接脚本,其实是默认指定了链接脚本,我们可以用这个命令来查看链接脚本:

root@ubuntu:/usr/lib/ldscripts# ld -verbose

默认的ld链接脚本是存放在/usr/lib/ldscripts/下,具体是哪个我还真不清楚。

root@ubuntu:/usr/lib/ldscripts# find . | xargs grep "i386:x86-64"
grep: .: Is a directory
./elf_x86_64.xd:OUTPUT_ARCH(i386:x86-64)
./elf_x86_64.xu:OUTPUT_ARCH(i386:x86-64)
./elf_x86_64.xsc:OUTPUT_ARCH(i386:x86-64)
./elf_x86_64.xr:OUTPUT_ARCH(i386:x86-64)
./elf_x86_64.xc:OUTPUT_ARCH(i386:x86-64)
./elf_x86_64.xs:OUTPUT_ARCH(i386:x86-64)
./elf_x86_64.xw:OUTPUT_ARCH(i386:x86-64)
./elf_x86_64.xbn:OUTPUT_ARCH(i386:x86-64)
./elf_x86_64.xdc:OUTPUT_ARCH(i386:x86-64)
./elf_x86_64.x:OUTPUT_ARCH(i386:x86-64)
./elf_x86_64.xdw:OUTPUT_ARCH(i386:x86-64)
./elf_x86_64.xsw:OUTPUT_ARCH(i386:x86-64)
./elf_x86_64.xn:OUTPUT_ARCH(i386:x86-64)

匹配了一下,有这么多适合,具体用哪个不是很清楚。

如果需要指定我们自己的链接脚本,可以用一下命令:

root@ubuntu:/usr/lib/ldscripts# ls -T link.script

5.2.2 ld链接脚本语法简介

《程序员的自我修养——链接、装载与库》中,自己实现了一个最小程序,有需要的可以去看看。我这里就偷懒不做了,只是简单分析一下语法就可以了,因为这个ld链接脚本的语法还是有一丢丢用处。

root@ubuntu:/usr/lib/ldscripts# ld -verbose
==================================================
/* Script for -z combreloc: combine and sort reloc sections */
/* Copyright (C) 2014-2015 Free Software Foundation, Inc.
   Copying and distribution of this script, with or without modification,
   are permitted in any medium without royalty provided the copyright
   notice and this notice are preserved.  */
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
	      "elf64-x86-64")		
OUTPUT_ARCH(i386:x86-64)    /* 输出格式 */
ENTRY(_start)				/* 这个重要,指定程序的入口函数 */
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");
/* SEARCH_DIR就是ld链接器查找指定目录查找的库,相当于-Lpath */
SECTIONS	/* 这个就是各个段的定义,看到下面的段名字是不是很熟悉 */
{ 
        
  /* Read-only sections, merged into text segment: */
  /* 在链接脚本中定义某个符号,这个符号是在代码中可以使用的 */
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;  /* 这个就是定义程序的开始地址, SIZEOF_HEADERS 这个可以留到以后讲*/
  .interp         : { 
         *(.interp) }		// *是通配符,表示所有文件的.interp段都符合条件
  .note.gnu.build-id : { 
         *(.note.gnu.build-id) }
  .hash           : { 
         *(.hash) }
  .gnu.hash       : { 
         *(.gnu.hash) }
  .dynsym         : { 
         *(.dynsym) }
  .dynstr         : { 
         *(.dynstr) }
  .gnu.version    : { 
         *(.gnu.version) }
  .gnu.version_d  : { 
         *(.gnu.version_d) }
  .gnu.version_r  : { 
         *(.gnu.version_r) }
  .rela.dyn       :
    { 
        
      *(.rela.init)
      *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
      *(.rela.fini)
      *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
      *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
      *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
      *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
      *(.rela.ctors)
      *(.rela.dtors)
      *(.rela.got)
      *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
      *(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*)
      *(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*)
      *(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*)
      *(.rela.ifunc)
    }
  .rela.plt       :
    { 
        
      *(.rela.plt)
      PROVIDE_HIDDEN (__rela_iplt_start = .);  // 在链接文件中使用,不导出给程序使用
      *(.rela.iplt)
      PROVIDE_HIDDEN (__rela_iplt_end = .);
    }
  .init           :
  { 
        
    KEEP (*(SORT_NONE(.init)))
    //在连接命令行内使用了选项–gc-sections后,连接器可能将某些它认为没用的section过滤掉,此时就有必要强制连接器保留一些特定的 section,可用KEEP()关键字达此目的
  }
  .plt            : { 
         *(.plt) *(.iplt) }
.plt.got        : { 
         *(.plt.got) }
.plt.bnd        : { 
         *(.plt.bnd) }
  .text           :
  { 
        
    *(.text.unlikely .text.*_unlikely .text.unlikely.*)
    *(.text.exit .text.exit.*)
    *(.text.startup .text.startup.*)
    *(.text.hot .text.hot.*)
    *(.text .stub .text.* .gnu.linkonce.t.*)
    /* .gnu.warning sections are handled specially by elf32.em.  */
    *(.gnu.warning)
  }
  .fini           :
  { 
        
    KEEP (*(SORT_NONE(.fini)))
  }
  PROVIDE (__etext = .);
  PROVIDE (_etext = .);
  PROVIDE (etext = .);
  .rodata         : { 
         *(.rodata .rodata.* .gnu.linkonce.r.*) }
  .rodata1        : { 
         *(.rodata1) }
  .eh_frame_hdr : { 
         *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
  .eh_frame       : ONLY_IF_RO { 
         KEEP (*(.eh_frame)) *(.eh_frame.*) }
  .gcc_except_table   : ONLY_IF_RO { 
         *(.gcc_except_table
  .gcc_except_table.*) }
  .gnu_extab   : ONLY_IF_RO { 
         *(.gnu_extab*) }
  /* These sections are generated by the Sun/Oracle C++ compiler.  */
  .exception_ranges   : ONLY_IF_RO { 
         *(.exception_ranges
  .exception_ranges*) }
  /* Adjust the address for the data segment.  We want to adjust up to
     the same address within the page on the next page up.  */
  . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
  /* Exception handling  */
  .eh_frame       : ONLY_IF_RW { 
         KEEP (*(.eh_frame)) *(.eh_frame.*) }
  .gnu_extab      : ONLY_IF_RW { 
         *(.gnu_extab) }
  .gcc_except_table   : ONLY_IF_RW { 
         *(.gcc_except_table .gcc_except_table.*) }
  .exception_ranges   : ONLY_IF_RW { 
         *(.exception_ranges .exception_ranges*) }
  /* Thread Local Storage sections  */
  .tdata	  : { 
         *(.tdata .tdata.* .gnu.linkonce.td.*) }
  .tbss		  : { 
         *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
  .preinit_array     :
  { 
        
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  }
  .init_array     :
  { 
        
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
    KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
    PROVIDE_HIDDEN (__init_array_end = .);
  }
  .fini_array     :
  { 
        
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
    KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
    PROVIDE_HIDDEN (__fini_array_end = .);
  }
  .ctors          :
  { 
        
    /* gcc uses crtbegin.o to find the start of
       the constructors, so we make sure it is
       first.  Because this is a wildcard, it
       doesn't matter if the user does not actually link against crtbegin.o; the linker won't look for a file to match a
       wildcard.  The wildcard also means that it
       doesn't matter which directory crtbegin.o is in. */ KEEP (*crtbegin.o(.ctors)) KEEP (*crtbegin?.o(.ctors)) /* We don't want to include the .ctor section from
       the crtend.o file until after the sorted ctors.
       The .ctor section from the crtend file contains the
       end of ctors marker and it must be last */
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))
  }
  .dtors          :
  { 
        
    KEEP (*crtbegin.o(.dtors))
    KEEP (*crtbegin?.o(.dtors))
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
    KEEP (*(SORT(.dtors.*)))
    KEEP (*(.dtors))
  }
  .jcr            : { 
         KEEP (*(.jcr)) }
  .data.rel.ro : { 
         *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
  .dynamic        : { 
         *(.dynamic) }
  .got            : { 
         *(.got) *(.igot) }
  . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
  .got.plt        : { 
         *(.got.plt)  *(.igot.plt) }
  .data           :
  { 
        
    *(.data .data.* .gnu.linkonce.d.*)
    SORT(CONSTRUCTORS)
  }
  .data1          : { 
         *(.data1) }
  _edata = .; PROVIDE (edata = .);
  . = .;
  __bss_start = .;
  .bss            :
  { 
        
   *(.dynbss)
   *(.bss .bss.* .gnu.linkonce.b.*)
   *(COMMON)
   /* Align here to ensure that the .bss section occupies space up to
      _end.  Align after .bss to ensure correct alignment even if the
      .bss section disappears because there are no input sections.
      FIXME: Why do we need it? When there is no .bss section, we don't
      pad the .data section.  */
   . = ALIGN(. != 0 ? 64 / 8 : 1);
  }
  .lbss   :
  { 
        
    *(.dynlbss)
    *(.lbss .lbss.* .gnu.linkonce.lb.*)
    *(LARGE_COMMON)
  }
  . = ALIGN(64 / 8);
  . = SEGMENT_START("ldata-segment", .);
  .lrodata   ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
  { 
        
    *(.lrodata .lrodata.* .gnu.linkonce.lr.*)
  }
  .ldata   ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
  { 
        
    *(.ldata .ldata.* .gnu.linkonce.l.*)
    . = ALIGN(. != 0 ? 64 / 8 : 1);
  }
  . = ALIGN(64 / 8);
  _end = .; PROVIDE (end = .);
  . = DATA_SEGMENT_END (.);
  /* Stabs debugging sections.  */
  .stab          0 : { 
         *(.stab) }
  .stabstr       0 : { 
         *(.stabstr) }
  .stab.excl     0 : { 
         *(.stab.excl) }
  .stab.exclstr  0 : { 
         *(.stab.exclstr) }
  .stab.index    0 : { 
         *(.stab.index) }
  .stab.indexstr 0 : { 
         *(.stab.indexstr) }
  .comment       0 : { 
         *(.comment) }
  /* DWARF debug sections.
     Symbols in the DWARF debugging sections are relative to the beginning
     of the section so we begin them at 0.  */
  /* DWARF 1 */
  .debug          0 : { 
         *(.debug) }
  .line           0 : { 
         *(.line) }
  /* GNU DWARF 1 extensions */
  .debug_srcinfo  0 : { 
         *(.debug_srcinfo) }
  .debug_sfnames  0 : { 
         *(.debug_sfnames) }
  /* DWARF 1.1 and DWARF 2 */
  .debug_aranges  0 : { 
         *(.debug_aranges) }
  .debug_pubnames 0 : { 
         *(.debug_pubnames) }
  /* DWARF 2 */
  .debug_info     0 : { 
         *(.debug_info .gnu.linkonce.wi.*) }
  .debug_abbrev   0 : { 
         *(.debug_abbrev) }
  .debug_line     0 : { 
         *(.debug_line .debug_line.* .debug_line_end ) }
  .debug_frame    0 : { 
         *(.debug_frame) }
  .debug_str      0 : { 
         *(.debug_str) }
  .debug_loc      0 : { 
         *(.debug_loc) }
  .debug_macinfo  0 : { 
         *(.debug_macinfo) }
  /* SGI/MIPS DWARF 2 extensions */
  .debug_weaknames 0 : { 
         *(.debug_weaknames) }
  .debug_funcnames 0 : { 
         *(.debug_funcnames) }
  .debug_typenames 0 : { 
         *(.debug_typenames) }
  .debug_varnames  0 : { 
         *(.debug_varnames) }
  /* DWARF 3 */
  .debug_pubtypes 0 : { 
         *(.debug_pubtypes) }
  .debug_ranges   0 : { 
         *(.debug_ranges) }
  /* DWARF Extension.  */
  .debug_macro    0 : { 
         *(.debug_macro) }
  .gnu.attributes 0 : { 
         KEEP (*(.gnu.attributes)) }
  /DISCARD/ : { 
         *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
  //该section引用的任何输入section将不会出现在输出文件内,这就是DISCARD的意思吧
}

下面推荐一篇讲解ld链接脚本的链接,讲的很不错。

[[转]Linux下的lds链接脚本详解](https://www.cnblogs.com/li-hao/p/4107964.html)

这一篇,相比上一篇轻松很多,知识点也没那么多,不过下一篇又是一场硬仗,加油。

参考文章:

《程序员的自我修养——链接、装载和库》

《深入理解计算机系统》

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章