解释一个ld.script文件

本文详细解释一个 ld.script 文件

OUTPUT_FORMAT("elf32-tradlittlemips")
OUTPUT_ARCH(mips)
ENTRY(_start)
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  . = 0x80100000;
  .text      : 
  {
    _ftext = . ;
    *(.text)
    *(.rodata)
    *(.rodata1)
    *(.reginfo)
    *(.init)
    *(.stub)
    /* .gnu.warning sections are handled specially by elf32.em.  */
    *(.gnu.warning)
  } =0
  _etext = .;
  PROVIDE (etext = .);
  .fini      : { *(.fini)    } =0
  .data    :
  {
    _fdata = . ;
    *(.data)
    CONSTRUCTORS
  }
  .data1   : { *(.data1) }
  .ctors         :
  {
                __CTOR_LIST__ = .;
                LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)
               *(.ctors)
                LONG(0)
                __CTOR_END__ = .;
  }
  .dtors         :
  {
                __DTOR_LIST__ = .;
                LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)
               *(.dtors)
                LONG(0)
                __DTOR_END__ = .;
  }
  _gp = ALIGN(16) + 0x7ff0;
  .got           :
  {
    *(.got.plt) *(.got)
   }
  /* We want the small data sections together, so single-instruction offsets
     can access them all, and initialized data all before uninitialized, so
     we can shorten the on-disk segment size.  */
  .sdata     : { *(.sdata) }
  .lit8 : { *(.lit8) }
  .lit4 : { *(.lit4) }
  _edata  =  .;
  PROVIDE (edata = .);
  __bss_start = .;
  _fbss = .;
  .sbss      : { *(.sbss) *(.scommon) }
  .bss       :
  {
   *(.dynbss)
   *(.bss)
   *(COMMON)
  }
  . = ALIGN(16);
  __bss_end = .;
  _end = .;__end = .; end = .; 
  PROVIDE (end = .);
  /* These are needed for ELF backends which have not yet been
     converted to the new style linker.  */
  .stab 0 : { *(.stab) }
  .stabstr 0 : { *(.stabstr) }
  /* DWARF debug sections.
     Symbols in the .debug DWARF section are relative to the beginning of the
     section so we begin .debug at 0.  It's not clear yet what needs to happen
     for the others.   */
  .debug          0 : { *(.debug) }
  .debug_srcinfo  0 : { *(.debug_srcinfo) }
  .debug_aranges  0 : { *(.debug_aranges) }
  .debug_pubnames 0 : { *(.debug_pubnames) }
  .debug_sfnames  0 : { *(.debug_sfnames) }
  .line           0 : { *(.line) }
  /* These must appear regardless of  .  */
  .gptab.sdata : { *(.gptab.data) *(.gptab.sdata) }
  .gptab.sbss : { *(.gptab.bss) *(.gptab.sbss) }
}

下面逐句解释。

OUTPUT_FORMAT("elf32-tradlittlemips")
OUTPUT_ARCH(mips)

OUTPUT_FORMAT 和 OUTPUT_ARCH 都是 ld 脚本的保留字命令。OUTPUT_FORMAT 说明输出二进制文件的格式。OUTPUT_ARCH 说明输出文件在平台。
ENTRY(_start)

ENTRY 命令的作用是,将后面括号中的符号值设置成入口地址。入口地址(entry point)的定义是这样的──进程执行的第一条用户空间的指令在进程地址空间中的地址。

ld 有多种方法设置进程入口地址,通常它按以下顺序:(编号越前, 优先级越高)
1, ld 命令行的-e选项
2, 连接脚本的 ENTRY(SYMBOL) 命令
3, 如果定义了 start 符号, 使用 start 符号值
4, 如果存在 .text section, 使用 .text section 的第一字节的位置值
5, 使用值 0

SECTIONS
{

然后,接下来是一大段的 SECTIONS,对应的右大括号直到脚本的末尾。
SECTIONS 命令告诉 ld 如何把输入文件的 sections 映射到输出文件的各个 section:即是如何将输入 section 合为输出 section;如何把输出 section 放入程序地址空间 (VMA) 和进程地址空间 (LMA) 。该命令格式如下:
SECTIONS
{
….
}
/* Read-only sections, merged into text segment: */
  . = 0x80100000;

这句把定位器符号置为 0x80100000 (若不指定,则该符号的初始值为 0)。
. 是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置(或某section内的偏移,如果它在SECTIONS命令内的某section描述内),该符号只能在SECTIONS命令内使用。
.text      : 
  {
    _ftext = . ;
    *(.text)
    *(.rodata)
    *(.rodata1)
    *(.reginfo)
    *(.init)
    *(.stub)
    /* .gnu.warning sections are handled specially by elf32.em.  */
    *(.gnu.warning)
  } =0

.text : 表示text段开始。
_ftext
*(.text) 将所有(*符号代表任意输入文件)输入文件的.text section合并成一个.text section, 该section的地址由定位器符号的值指定, 即0x80100000.
*(.rodata)
*(.rodata1)
*(.reginfo)
*(.init)
*(.stub)
*(.gnu.warning)
} =0 表示合并时留下的空隙用 0 填充;
_etext = .;
  PROVIDE (etext = .);

_etext = .; 我们看到,很多变量都定义成等于这个 . 符,实际上这个符号所代表的值是在变化的,随着越往后走,值越增加,根据前面填充的多少自动往后加。
PROVIDE关键字用于定义这类符号:在目标文件内被引用,但没有在任何目标文件内被定义的符号。
这里就定义了一个 etext 符号,当目标文件内引用了 etext 符号,却没有定义它时,etext 符号对应的地址被定义为 .text section 之后的第一个字节的地址。
.fini      : { *(.fini)    } =0

意思与前面一样,但 fini 这名字是哪个段,我还搞不太清楚(???)。
.data    :
  {
    _fdata = . ;
    *(.data)
    CONSTRUCTORS
  }
  .data1   : { *(.data1) }

数据段终于来到了,意思很容易理解的了。
CONSTRUCTORS 是一个保留字命令。与 c++ 内的(全局对象的)构造函数和(全局对像的)析构函数相关。
.ctors         :
  {
                __CTOR_LIST__ = .;
                LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)
               *(.ctors)
                LONG(0)
                __CTOR_END__ = .;
  }
  .dtors         :
  {
                __DTOR_LIST__ = .;
                LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)
               *(.dtors)
                LONG(0)
                __DTOR_END__ = .;
  }

对于支持任意section名的目标文件格式,比如COFF、ELF格式,GNU C++将全局构造和全局析构信息分别放入 .ctors section 和 .dtors section 内
当连接器生成的目标文件格式不支持任意section名字时,比如说ECOFF、XCOFF格式,连接器将通过名字来识别全局构造和全局析构,对于这些文件格式,连接器把与全局构造和全局析构的相关信息放入出现 CONSTRUCTORS 关键字的输出section内。

符号__CTORS_LIST__表示全局构造信息的的开始处,__CTORS_END__表示全局构造信息的结束处。
符号__DTORS_LIST__表示全局构造信息的的开始处,__DTORS_END__表示全局构造信息的结束处。
这两块信息的开始处是一字长的信息,表示该块信息有多少项数据,然后以值为零的一字长数据结束。
一般来说,GNU C++在函数__main内安排全局构造代码的运行,而__main函数被初始化代码(在main函数调用之前执行)调用。

_gp = ALIGN(16) + 0x7ff0;

_gp是一个重要的全局变量,好像是用作全局引用的一个指针。
.got           :
  {
    *(.got.plt) *(.got)
   }
  /* We want the small data sections together, so single-instruction offsets
     can access them all, and initialized data all before uninitialized, so
     we can shorten the on-disk segment size.  */
  .sdata     : { *(.sdata) }
  .lit8 : { *(.lit8) }
  .lit4 : { *(.lit4) }

意义类似。
_edata  =  .;
  PROVIDE (edata = .);

意义与前面的 etext 类似。edata 符号也较为重要。
__bss_start = .;
  _fbss = .;
  .sbss      : { *(.sbss) *(.scommon) }
  .bss       :
  {
   *(.dynbss)
   *(.bss)
   *(COMMON)
  }
  . = ALIGN(16);
  __bss_end = .;
  _end = .;__end = .; end = .; 
  PROVIDE (end = .);

BSS段开始了。
COMMON 这个保留字的意义:
通用符号(common symbol)的输入section:
在许多目标文件格式中,通用符号并没有占用一个section。连接器认为:输入文件的所有通用符号在名为COMMON的section内。
上例中将所有输入文件的所有通用符号放入输出.bss section内。

这里,定义了几个重要的符号
__bss_start = .;
__bss_end = .;
_end = .;
__end = .;
end = .;
在代码中可能会用到的。

/* These are needed for ELF backends which have not yet been
     converted to the new style linker.  */
  .stab 0 : { *(.stab) }
  .stabstr 0 : { *(.stabstr) }
  /* DWARF debug sections.
     Symbols in the .debug DWARF section are relative to the beginning of the
     section so we begin .debug at 0.  It's not clear yet what needs to happen
     for the others.   */
  .debug          0 : { *(.debug) }
  .debug_srcinfo  0 : { *(.debug_srcinfo) }
  .debug_aranges  0 : { *(.debug_aranges) }
  .debug_pubnames 0 : { *(.debug_pubnames) }
  .debug_sfnames  0 : { *(.debug_sfnames) }
  .line           0 : { *(.line) }
  /* These must appear regardless of  .  */
  .gptab.sdata : { *(.gptab.data) *(.gptab.sdata) }
  .gptab.sbss : { *(.gptab.bss) *(.gptab.sbss) }
}

余下的这几个意义也类似,看英文注释应该能明白。

对初步编译出来的一个二进制文件进行 nm 解析,得到如下内容

80100200 D __CTOR_END__
801001f8 D __CTOR_LIST__
80100208 D __DTOR_END__
80100200 D __DTOR_LIST__
80100220 A __bss_end
80100208 A __bss_start
80100220 A __end
80100208 A _edata
80100220 A _end
801001f8 A _etext
80100208 A _fbss
80100200 A _fdata
80100000 T _ftext
80108200 A _gp
80100000 T _start
80100038 t cleanpipe
80100220 A end
80100210 b flag_initialized.1263
80100158 T inbFrCom
801000b4 T initBss
80100060 T initConstructor
80100100 T initMips
801001a4 T outbToCom
80100140 T readComReg
801000f8 T showVersion
800fc000 T stack
80100000 T start
80100120 T writeComReg

可以看到,所有的地址全是从 0x80100000 开始的。

三个起始符号(T表示在text段中)
80100000 T _start
80100000 T start
80100000 T _ftext

几个函数都在代码段内
80100038 t cleanpipe
80100060 T initConstructor
801000b4 T initBss
801000f8 T showVersion
80100100 T initMips
80100120 T writeComReg
80100140 T readComReg
80100158 T inbFrCom
801001a4 T outbToCom

栈底地址果然是在 start 下方 0x4000 处
800fc000 T stack

(A 表示绝对不变)
801001f8 A _etext
80100200 A _fdata
80100208 A _edata
80100208 A _fbss
80100208 A __bss_start
80100220 A __bss_end
80100220 A __end
80100220 A _end
80100220 A end

全局构造和析构变量段(D表示在已初始化过的数据段中)
801001f8 D __CTOR_LIST__
80100200 D __CTOR_END__
80100200 D __DTOR_LIST__
80100208 D __DTOR_END__

还有一个是用 B 标记的,表示在未初始化的数据段中
80100210 b flag_initialized.1263
对应的代码是
static int flag_initialized = 0;
可以看出,这是个局部静态变量。

令其按地址排序

nm -n bamboo

800fc000 T stack
80100000 T _ftext
80100000 T _start
80100000 T start
80100038 t cleanpipe
80100060 T initConstructor
801000b4 T initBss
801000f8 T showVersion
80100100 T initMips
80100120 T writeComReg
80100140 T readComReg
80100158 T inbFrCom
801001a4 T outbToCom
801001f8 D __CTOR_LIST__
801001f8 A _etext
80100200 D __CTOR_END__
80100200 D __DTOR_LIST__
80100200 A _fdata
80100208 D __DTOR_END__
80100208 A __bss_start
80100208 A _edata
80100208 A _fbss
80100210 b flag_initialized.1263
80100220 A __bss_end
80100220 A __end
80100220 A _end
80100220 A end
80108200 A _gp

结合这些数据,去理解前面的 ld.script 的讲解,会有一个清晰的印象。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值