写一个ld文件

来源:互联网 发布:战争框架steam网络 编辑:程序博客网 时间:2024/06/05 02:22
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 的讲解,会有一个清晰的印象
0 0
原创粉丝点击