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 的讲解,会有一个清晰的印象