上篇随笔写了一个连接脚本,如果你不想看那个复杂的ld连接脚本文章,看我上一篇和这篇随笔就可以做到编写目前我们够用的连接脚本了,本章的知识点全部来自那篇文章。
先看连接脚本的格式:
其中有一些东西我们是不会用到的:
第一个,段名,这个我们可以随便取,但是一般按照.data .text等这样的名字,也可以命名成first,second等这样,这两种命名是连接脚本中最常见的。
start:加载地址
。 start可以表示为任何表达式。
BLOCK(align)
您可以在该部分开始之前包含BLOCK()
指定位置计数器.
,以便该部分从指定的对齐位置开始。 align是一个表达式。
这个在我们的连接脚本中不会使用。
(NOLOAD)
该 “(NOLOAD)”指令将标记一个部分在运行时不被加载。链接器将正常处理该部分,但会将其标记为程序加载器不会将其加载到内存中。例如,在下面的脚本示例中,该ROM
部分位于内存位置‘0‘在程序运行时不需要加载。该ROM
部分的内容将照常显示在链接器输出文件中。
SECTIONS { ROM 0(NOLOAD):{...} ... }这个在我们的连接脚本中不会使用。AT ( ldadr )
The expression ldadr that follows theAT
keyword specifies the load address of the section. The default (if you do not use theAT
keyword) is to make the load address the same as the relocation address. This feature is designed to make it easy to build a ROM image. For example, thisSECTIONS
definition creates two output sections: one called `.text‘, which starts at0x1000
, and one called `.mdata‘, which is loaded at the end of the `.text‘ section even though its relocation address is0x2000
. The symbol_data
is defined with the value0x2000
:
SECTIONS { .text 0x1000 : { *(.text) _etext = . ; } .mdata 0x2000 : AT ( ADDR(.text) + SIZEOF ( .text ) ) { _data = . ; *(.data); _edata = . ; } .bss 0x3000 : { _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;} }
The run-time initialization code (for C programs, usually crt0
) for use with a ROM generated this way has to include something like the following, to copy the initialized data from the ROM image to its runtime address:
char *src = _etext; char *dst = _data; /* ROM has data at end of text; copy it. */ while (dst < _edata) { *dst++ = *src++; } /* Zero bss */ for (dst = _bstart; dst< _bend; dst++) *dst = 0;
AT就是指定加载地址。>region
Assignthis section to a previously defined region of memory.(这个我们也不会用到)
这个我们也不用到。
所以我们需要掌握的也就那么一点。然后说明一点,上面我们连接脚本中使用了一个LOADADDR函数:LOADADDR(section)
Return the absolute load address of the named section. 其实就是返回这个section的加载地址。SIZEOF(section)
如果该部分已被分配,则返回指定部分的字节大小
当没有指定AT时,运行地址和加载地址是一样的,而且,在指定了一个运行地址之后,后面再继续指定段,此时没有再指定运行地址时,该段的运行地址时紧接着前一个指定的eg:
.rodata段没有指定运行地址,证明它是紧接着上面的.text段排放的。
上面的图片说明了我们需要用代码实现重定位,在运行地址和加载地址不相同的时候:
这里运行地址是0x30000000,加载地址是0x800,不相等,此刻就需要我们编写重定位代码,这也就是上一篇随笔中的汇编copy的原因。
bss段是存放初始值为0或者没有初始化的全局变量的,它不存放在elf文件也不存放在bin文件,原因很简单,要是有10万个为0的值,难道我们要开辟那么大的空间去存放这些数吗?所以,bss段的清零也需要我们自己做,bss段连接在.data段后面的,这也就是bss段的变量正真存放的位置,这也让我们之后读写bss段的数据也可以有效。有人问,既然bss段不存放在bin文件中,为什么我们还要清零它呢?
第一,bss段决定了它的初始值是0,;
第二,不存放在bin文件中,但它存放在内存中,这里是我们的sdram,不然为什么我们可以对bss段的数据进行算术操作而不出问题呢;
第三,各个变量或者函数是有特定存放段的,这是连接器工作的基础。
下一篇随笔书写bss段清零操作,并且改进我们的重定位代码。