GNU LD 脚本学习笔记

LD脚本(linker script)是什么

GNU ld是链接器,ld实际并不是GCC的一部分,ld属于binutils软件包。但是嵌入式开发时,下载的linaro GCC工具集中是包含 arm-linux-gnueabihf-ld 的。工作中我经常使用ARM的scatter文件,和这个LD脚本差不多,只不过scatter文件的功能要弱不少,这也是为什么ARM6中armclang也是推荐使用 GNU LD脚本的原因,ARM也不想维护自己特有的编译器了,只要专心把clang bytecode翻译成ARM指令的优化做好。

所有的链接过程都是由LD脚本控制的,写这个脚本的语言称为 linker command language,LD脚本的最主要的功能是描述如何将输入文件映射到输出文件以及输出文件的存储布局(memory layout)。在操作系统上开发时一般不会涉及到LD脚本,这是因为如果未使用命令行-T来指定脚本,ld会使用内置的默认脚本,这个脚本可以通过 ld --verbose 来查看,例如 arm-linux-gnueabihf-ld --verbose的输出如下

/* Script for -z combreloc: combine and sort reloc sections */
/* Copyright (C) 2014-2017 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("elf32-littlearm", "elf32-bigarm",
	      "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SEARCH_DIR("=/home/tcwg-buildslave/workspace/tcwg-make-release/builder_arch/amd64/label/tcwg-x86_64-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/lib"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib");
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x00010000)); . = SEGMENT_START("text-segment", 0x00010000) + SIZEOF_HEADERS;
  .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) }
  .rel.dyn        :
    {
      *(.rel.init)
      *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
      *(.rel.fini)
      *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
      *(.rel.data.rel.ro .rel.data.rel.ro.* .rel.gnu.linkonce.d.rel.ro.*)
      *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
      *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
      *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
      *(.rel.ctors)
      *(.rel.dtors)
      *(.rel.got)
      *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
      PROVIDE_HIDDEN (__rel_iplt_start = .);
      *(.rel.iplt)
      PROVIDE_HIDDEN (__rel_iplt_end = .);
    }
  .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.*)
      PROVIDE_HIDDEN (__rela_iplt_start = .);
      *(.rela.iplt)
      PROVIDE_HIDDEN (__rela_iplt_end = .);
    }
  .rel.plt        :
    {
      *(.rel.plt)
    }
  .rela.plt       :
    {
      *(.rela.plt)
    }
  .init           :
  {
    KEEP (*(SORT_NONE(.init)))
  }
  .plt            : { *(.plt) }
  .iplt           : { *(.iplt) }
  .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)
    *(.glue_7t) *(.glue_7) *(.vfp11_veneer) *(.v4_bx)
  }
  .fini           :
  {
    KEEP (*(SORT_NONE(.fini)))
  }
  PROVIDE (__etext = .);
  PROVIDE (_etext = .);
  PROVIDE (etext = .);
  .rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
  .rodata1        : { *(.rodata1) }
  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) }
   PROVIDE_HIDDEN (__exidx_start = .);
  .ARM.exidx   : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) }
   PROVIDE_HIDDEN (__exidx_end = .);
  .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) }
  . = DATA_SEGMENT_RELRO_END (0, .);
  .got            : { *(.got.plt) *(.igot.plt) *(.got) *(.igot) }
  .data           :
  {
    PROVIDE (__data_start = .);
    *(.data .data.* .gnu.linkonce.d.*)
    SORT(CONSTRUCTORS)
  }
  .data1          : { *(.data1) }
  _edata = .; PROVIDE (edata = .);
  . = .;
  __bss_start = .;
  __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 ? 32 / 8 : 1);
  }
  _bss_end__ = . ; __bss_end__ = . ;
  . = ALIGN(32 / 8);
  . = SEGMENT_START("ldata-segment", .);
  . = ALIGN(32 / 8);
  __end__ = . ;
  _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) }
  .debug_addr     0 : { *(.debug_addr) }
  .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
  .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) }
  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}

默认的脚本

大家可以看到,默认的脚本比自己写的还要复杂得多,原来GCC会输出这么多的section!

LD脚本的理解

LD脚本由许多命令组成,下面我们以u-boot中的am335x的u-boot-spl.lds为例来说明下,先贴上这文件内容

 1 MEMORY { .sram : ORIGIN = CONFIG_SPL_TEXT_BASE, 2         LENGTH = CONFIG_SPL_MAX_SIZE }
 3 MEMORY { .sdram : ORIGIN = CONFIG_SPL_BSS_START_ADDR,  4         LENGTH = CONFIG_SPL_BSS_MAX_SIZE }
 5
 6 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
 7 OUTPUT_ARCH(arm)
 8 ENTRY(_start)
 9 SECTIONS
10 {
11     .text      :
12     {
13         __start = .;
14         *(.vectors)
15         arch/arm/cpu/armv7/start.o    (.text*)
16         *(.text*)
17     } >.sram
18
19     . = ALIGN(4);
20     .rodata : { *(SORT_BY_ALIGNMENT(.rodata*)) } >.sram
21
22     . = ALIGN(4);
23     .data : { *(SORT_BY_ALIGNMENT(.data*)) } >.sram
24
25     . = ALIGN(4);
26     .u_boot_list : {
27         KEEP(*(SORT(.u_boot_list*)));
28     } >.sram
29
30     . = ALIGN(4);
31     __image_copy_end = .;
32
33     .end :
34     {
35         *(.__end)
36     }
37
38     _image_binary_end = .;
39
40     .bss :
41     {
42         . = ALIGN(4);
43         __bss_start = .;
44         *(.bss*)
45         . = ALIGN(4);
46         __bss_end = .;
47     } >.sdram
48 }

这个文件涉及如下几个命令

  • OUTPUT_FORMAT:指定输出文件的格式,这里指定了无论命令是否选择了大小端,都输出ARM的小端格式的指令
  • OUTPUT_ARCH:指定输出的架构
  • ENTRY:指定入口地址,注意这里使用的是代码中定义的_start符号,也就是说脚本中可以直接访问符号表中的符号
  • SECTIONS:这是脚本中最重要的命令了,所有的LD脚本都会有这个命令,用来指定如何将输入文件映射到输出文件等等,要看懂SECTIONS的内容需要许多概念,下面来一一说明

object

链接器的目的是把多个输入文件组成一个输出文件,这些文件都叫做object文件(包括最终生成的可执行文件)。object文件有很多内容,但最重要的是它包含一组段(section),输入文件中的段称为输入段(input section),而输出文件中的段称为输出段(output section)。

section

每个section都有自己的名字(如.text/.data/.bss)和大小,大部分section还有自己的数据(.bss就是一种有大小无数据的section)。

  • 有些section是loadable,如.text段,运行时需要把它们的数据加载到内存中去
  • 还有一些section是allocatable,如.bss段,运行时需要在内存中为它们留空间,但是不用加载任何数据到这段内存中去(一般会清零)
  • 除此之外的section,一般只是包含一些调用信息

VMA/LMA

loadable 或 allocatable 的section都有两个地址

  • VMA:Virtual Memory Address,这是运行时section的地址(操作系统上,程序运行的时候的地址一般经过MMU映射过的虚拟地址)
  • LMA:Load Memory Address,这是还未开始运行时,section处在的位置

如果是在操作系统上,可执行程序由外部加载器加载,VMA和LMA一般来说是相同的,但是在嵌入式裸机代码中,LMA可能是在ROM中,程序从ROM中开始运行,初始化代码(VMA==LMA的代码,或与位置无关的代码)负责把其他VMA和LMA不同的代码加载到VMA中。

location counter

SECTIONS命令中,"."是一个特殊的符号,表示当前VMA,"."的初始值是0,通过给"."赋值可以增加它的值,每创建一个新的输出section时,"."也会根据其大小相应地增加。通过直接赋值给"."可能会产生空洞,这些空洞会被填充上(可以指定填充值)。需要注意的是,通过赋值不可以使"."回退,如果ld检测到这种情况,就会报错。

有了上面这些概念,我们再来分析下SECTIONS

  • 输入段的指定方式:file-name(section-name)或 archive-name:file-name(section-name),所有这些名称都可以使用 *和?等通配符
  • 输出段的指定方式:section-name { input-sections },输出段的名称与可执行文件的格式相关
  • 表达式:以分号结尾的表达式,用于直接创建符号或改变"."
  • >指定VMA(运行时)在哪个存储器中,为了方便映射到不同的内存,还可以创建REGION_ALIAS
  • ALIGN(size)返回"."对齐到size字节的值,但是不会改变"."
  • SORT_BY_ALIGNMENT是按对齐大小倒序排列,对齐大的放前面,以减少padding
  • SORT是SORT_BY_NAME简写,按照名称顺序排列
  • KEEP是即使没有代码引用,也保留下来(汇编或其他外部代码会使用这些初始化数据)

这样我们就基本能看懂上面的脚本了,另外我觉得还有几点比较重要:

  • LD脚本直接创建的符号,也会放到符号表中,但是要注意这个符号不同于C代码中的符号。符号表是一个名称到地址的映射,代码中的符号都会被分配到一个存储器的位置,运行时会有值的概念,但LD脚本中创建的符号是不会分配内存的,所以它只有地址,而没有存储位置(值),在C语言中引用时,可以extern为变量,然后使用&获取其地址,或者直接extern为数组,使用数组名即可
  • 当".=exp"在输出段的{}中时,".=exp"就相当于所在的".=输出段的起始VMA+exp"
  • 在输出段的{}以外的地方给符号赋值是有风险的,".=."的方式可以限制ld旋转orphan section的位置,参见这里

要更详细地了解LD脚本,请参见下面的文献。相比arm的scatter文件,gnu ld script更加灵活,我最喜欢的就是可以给各种位置定义符号名,而不是用Image$$RO$$Base之类的magic名称。



参考文献

[1] https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts

[2] https://wiki.osdev.org/Linker_Scripts

原文地址:https://www.cnblogs.com/windtail/p/8398334.html

时间: 2024-11-01 16:47:32

GNU LD 脚本学习笔记的相关文章

GNU工具链学习笔记

GNU工具链学习笔记 1..so为动态链接库,.a为静态连接库.他们在Linux下按照ELF格式存储.ELF有四种文件类型.可重定位文件(Relocatable file,*.o,*.a),包含代码和数据,可用来连接成可执行文件或共享目标文件;可执行文件(Executable File),ELF可执行文件:共享目标文件(Shared Object File,*.so),包含代码和数据:核心转储文件(Core Dump File),进程意外终止时,系统将该进程的地址空间内容和其他信息保存到该文件中

2. 蛤蟆Python脚本学习笔记二基本命令畅玩

2. 蛤蟆Python脚本学习笔记二基本命令畅玩 本篇名言:"成功源于发现细节,没有细节就没有机遇,留心细节意味着创造机遇.一件司空见惯的小事或许就可能是打开机遇宝库的钥匙!" 下班回家,咱先来看下一些常用的基本命令. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/48092873 1.  数字和表达式 看下图1一就能说明很多问题: 加法,整除,浮点除,取模,幂乘方等.是不是很直接也很粗暴. 关于上限,蛤蟆不太清楚

3. 蛤蟆Python脚本学习笔记三字符串

3. 蛤蟆Python脚本学习笔记三字符串 本篇名言:"平静的湖面只有呆板的倒映,奔腾的激流才有美丽的浪花!幸福不是靠别人来布施,而是要自己去赢取!生命的意义在不断挑战自己,战胜自己!" 这个本来放在昨天的,由于昨晚又太晚了,所以就搁在这里了.赶紧看看吧. 字符串两边都用双引号或者单引号包起来.否则就使用转移符号来转移一下. 输入在一起可以直接拼接. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/48112507

1.蛤蟆Python脚本学习笔记一环境搭建

1.蛤蟆Python脚本学习笔记一环境搭建 蛤蟆一直在想在工作的时候能不能有一个牛逼的工具来让自己工作更加轻松和快乐.用过C, C++, C#, JAVA,  SHELL,TCL,汇编,BAT等,感觉这些都是需要的时候能发挥作用,不能和我想象的一样.突然有一天,感觉Python实在不错,那么就和小伙伴们一起乐呵乐呵呗.万事开头难,我们先来搭建环境吧. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/48058315 1. 相关

shell脚本学习笔记系列--1

一.学好shell编程的知识储备 1.相关Linux系统命令应用: 2.Vi/vim 编辑器的熟练使用,相关客户端软件的设置: 3.基础的服务,系统服务ntp,crond,网络服务:nfs,rsync,inotify,sersync,ssh,lanmp等. 补充:清空日志的三种方法: 1)echo  " " > filename.log 2)>filename.log 3)cat  /dev/null > filename.log 注:工作中有的时候不能删除(日志)文

shell脚本学习笔记:通过shell实现linux用户管理和监控

学习shell做的第一个脚本,感谢云知梦李强强老师的shell编程教程 创建shell脚本文件: touch menu.sh touch index.sh touch welcome.sh 赋予脚本文件可执行权限: chmod a+x menu.sh index.sh welcome.sh menu.sh #!/bin/bash #menu.sh function menu(){ title="My Home" name="Randy" time=`date +%Y

shell脚本学习笔记 (sed的高级用法----模式空间和保持空间)

前段时间在学习shell脚本,上次有提到sed的模式空间和保持空间概念,但是一直没有研究好,这两天研究了一下,所以将它发出来,不是很全面,仅仅供大家参考一下. 保持空间sed在正常情况下,将处理的行读入模式空间,脚本中的"sed command(sed命令)"就一条接着一条进行处理,直到脚本执行完毕.然后该行被输出,模式被清空:接着,在重复执行刚才的动作,文件中的新的一行被读入,直到文件处理完毕. 模式空间可以比喻为一个生产线,而保持空间则可以被比喻为仓库,这个比喻希望可以帮助大家理解

Shell 脚本学习笔记十:Shell输入输出重定向

command > file       将输出重定向到 file. command < file       将输入重定向到 file. command >> file     将输出以追加的方式重定向到 file. n > file              将文件描述符为 n 的文件重定向到 file. n >> file             将文件描述符为 n 的文件以追加的方式重定向到 file. n >& m             

shell脚本学习笔记之sed命令用法

sed基本用法 sed:stream editor 行编辑器 sed:模式空间 将匹配的文本内容储存到模式空间中 默认不编辑原文件,仅对模式空间中的数据做处理,而后,处理结束后,架构模式空间的内容显示 sed -n -i:直接修改原文件 -e script -e script:可以同时执行多个脚本 -f file sed -f /scripts  file -r:表示使用扩展正则表达式 sed 'adresscommand' file... -n:静默模式,不再默认显示模式空间中的内容,即只显示