ld文件

OUTPUT_FORMAT("elf32-tradlittlemips")
OUTPUT_ARCH(mips)
ENTRY(_start)
SECTIONS
{
  . = 0x80100000;
  .text:
  {
      _ftext = . ;
      *(.text)
      *(.rodata)
      *(.rodata1)
      *(.reginfo)
      *(.init)
      *(.stub)   
      *(.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)
  }
 
  .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 = .);
 
  .stab 0 : { *(.stab) }
  .stabstr 0 : { *(.stabstr) }
 
  .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) }
 
  .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
{
….
}
 
  . = 0x80100000;
 
 
这句把定位器符号置为0x80100000 (若不指定,则该符号的初始值为 0)。
.是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置(或某section内的偏移,如果它在SECTIONS命令内的某section描述内),该符号只能在SECTIONS命令内使用。
 
.text      :
  {
    _ftext = . ;
    *(.text)
    *(.rodata)
    *(.rodata1)
    *(.reginfo)
    *(.init)
    *(.stub)
    *(.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)
   }
 
  .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 = .;
在代码中可能会用到的。
 
  .stab 0 : { *(.stab) }
  .stabstr 0 : { *(.stabstr) }
 
  .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) }
 
  .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 的讲解,会有一个清晰的印象

你可能感兴趣的:(编译)