lds分析
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
OUTPUT_FORMAT("elf32­littlearm","elf32­littlearm", "elf32­littlearm")
;指定输出可执行文件是elf格式,32位ARM指令,小端
OUTPUT_ARCH(arm)
;指定输出可执行文件的平台为ARM
ENTRY(_start)
_start = 0;当此段在RAM中执行时_start = _TEXT_BASE(在board/smdk2410/config.mk中指定的值为0x33F80000,
即u-boot在把代码拷贝到RAM中去执行的代码段的开始) */
ldr r1, _TEXT_BASE /* 测试判断是从Flash启动,还是RAM */
现在,我们首先开看一看 xloader.lds 的代码:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(XLOADER_ENTRY) SECTIONS {
. = 0x00000000; . = ALIGN(4); .text : {
;指定输出可执行文件的起始代码段为_start.
SECTIONS
{
. = 0x00000000 . = ALIGN(4) .text :
; 从0x0位置开始 ; 代码以4字节对齐 ;指定代码段
{
cpu/arm920t/start.o (.text) ; 代码的第一个码部分 *(.text) ;其它代码部分
/* nand.lds */
SECTIONS {
firtst 0x00000000 : { head.o init.o }
second 0x30000000 : AT(4096) { main.o }
}
以上,head.o 放在 0x00000000 地址开始处,init.o 放在 head.o 后面,他们的运行 地址也是 0x00000000,即连接和存储地址相同(没有 AT 指定);main.o 放在 4096 (0x1000,是 AT 指定的,存储地址)开始处,但是它的运行地址在 0x30000000,运行 之前需要从 0x1000(加载处)复制到 0x30000000(运行处),此过程也就用到了读取 Nand flash。 这就是存储地址和连接(运行)地址的不同,称为加载时域和运行时域,可以在.lds 连接 脚本文件中分别指定。 编写好的.lds 文件,在用 arm-linux-ld 连接命令时带-Tfilename 来调用执行,如 arm-linux-ld –Tnand.lds x.o y.o –o xy.o。 也用-Ttext 参数直接指定连接地址,如 arm-linux-ld –Ttext 0x30000000 x.o y.o –o xy.o。
(3)此外,学习一下 adr 伪指令,U-boot 中那段 relocate 代码就是通过 adr 实现当 前程序是在 RAM 中还是 flash 中。注释如下:
relocate:
/* 把U-Boot重新定位到RAM */
adr r0, _start /* r0是代码的当前位置 */
/* adr伪指令,汇编器自动通过当前PC的值算出 如果执行到_start时PC的值,放到r0中:当此段在flash中执行时r0 =
对.lds 连接脚本文件的分析
对于.lds 文件,它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个段 的存储位置。 先看一下 GNU 官方网站上对.lds 文件形式的完整描述:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
既然程序有了两种地址,就涉及到一些跳转指令的区别,这里正好写下来,以后万一忘记 了也可查看. ARM 汇编中,常有两种跳转方法: b 跳转指令、ldr 指令向 PC 赋值。 经过归纳如下: (1)b step1 :b 跳转指令是相对跳转,依赖当前 PC 的值,偏移量是通过该指令本身的
bit[23:0]算出来的,这使得使用 b 指令的程序不依赖于要跳到的代码的位置,只看指令 本身。 (2)ldr pc, =step1 :该指令是从内存中的某个位置(step1)读出数据并赋给 PC, 同样依赖当前 PC 的值,但是偏移量是那个位置(step1)的连接地址(运行时的地址), 所以可以用它实现从 Flash 到 RAM 的程序跳转。
接下来的部分,是对整个输出目标文件中各个段的存储位置的定义。 在 GNU 的文档中,是这么定义的:
SECTIONS { sections-command
sections-command ... }
对于其中的每一个 sections-command,其完整的定义如下: The full description of an output section looks like this:
__u_boot_cmd_start = . ;把__u_boot_cmd_start赋值为当前位置, 即起始位置
.u_boot_cmd : { *(.u_boot_cmd) } ;指定u_boot_cmd段, uboot把所有的uboot命令放在该段.
__u_boot_cmd_end = .;把__u_boot_cmd_end赋值为当前位置,即结束位置
Most output sections do not use most of the optional section attributes. The whitespace around section is required, so that the section name is unambiguous. The colon and the curly braces are also required. The line breaks and other white space are optional. 下面来看看 xloader.lds 中 SECTIONS 的定义:
* the `-e' entry command-line option; * the ENTRY(symbol) command in a linker script; * the value of the symbol start, if defined; * the address of the first byte of the `.text' section, if present; * The address 0. 也 就 是 说 , ENTRY(XLOADER_ENTRY) 定 义 了 整 个 程 序 的 入 口 处 , 也 就 是 在 标 号 XLOADER_ENTRY 处。整个程序将从这里开始运行。
section [address] [(type)] : [AT(lma)] [ALIGN(section_align)] [SUBALIGN(subsection_align)] {
output-section-command output-section-command ... } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
. = ALIGN(4);
__bss_start = .; 把__bss_start赋值为当前位置,即bss段的开始位置 .bss : { *(.bss) }; 指定bss段 _end = .; 把_end赋值为当前位置,即bss段的结束位置 }
本文中的所有代码版本都是基于 ST 的 SpearPlus 开发板的。 xloader 是在系统上电之后,执行完 ROM 中的 frimware 后最先开始执行的用户程序, 它的体积很小,执行的功能也很简单,主要是对系统时钟以及外部 SDRAM 进行初始化,初 始化完成之后就检查 Flash 中的 uboot image 是否准备好,如果准备好了就将 Flash 中的 uboot image 根据 image header 中指定的 load address 加载到外部 SDRAM 中,然后就跳转到 uboot 执行代码。 这里,我试图从头开始,在源代码级别上来分析整个系统的引导过程。 像 Xloader 或者 uboot 之类的程序,并不像我们平常写的应用程序那样,程序的入口函 数直接找 main 函数就行。对于这种系统程序,在最开始看代码,尤其是要找到最开始执行 的代码的位置的时候,最好的一个方法就是找到整个工程的.lds 文件,也就是链接脚本文 件(linker loader script)。它定义了整个工程在编译之后的链接过程,以及各个输 入目标文件中的各个段在输出目标文件中的分布。详细的关于 lds 文件的介绍可以参考 gnu 的在线文档:/binutils/docs/ld/index.html。其 中的第三节 Linker Script 对链接脚本文件进行了介绍。
{ contents } >region :phdr =fill
...
}
secname 和 contents 是必须的,其他的都是可选的。 下面是几个常用的: 1、secname:段名 2、contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某 段(代码段、数据段等) 3、start:本段连接(运行)的地址,如果没有使用 AT(ldadr),本段存储的地址也是 start。GNU 网站上说 start 可以用任意一种描述地址的符号来描述。 4、AT(ldadr):定义本段存储(加载)的地址。 看一个简单的例子:(摘自《2410 完全开发》)
/* 此句执行的结果r1始终是0x33FF80000,因为此值是又编译器指定的(ads中设置,或-D设置编译器参数) */
cmp r0, r1 /* 比较r0和r1,调试的时候不要执行重定位 */
下面,结合u-boot.lds看看一个正式的连接脚本文件。这个文件的基本功能还能看明白, 虽然上面分析了好多,但其中那些GNU风格的符号还是比较难理解:
下面,我们对这一段代码逐句进行分析。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
在 GNU 的文档中,是这么定义的: OUTPUT_FORMAT(default, big, little),在链接的时候,如果使用了-EB 的命令 行参数,则使用这里的 big 参数指定的字节序,如果使用了-EL 的命令行参数,则使用这 里的 little 参数指定的字节序,如果没有使用任何命令行参数,则使用这里的 default 参数指定的字节序。 由 xloader.lds 中的定义可见,不管在链接的时候使用了何种命令行参数,输出的目标 文件都是使用 elf32-littlearm 方式的字节序。
./obj/init.o (.text) *(.text) }
.rodata . : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4); .got : { *(.got) }
. = ALIGN(4); __bss_start = .; .bss : { *(.bss) } _end = .; }
OUTPUT_ARCH(arm)
在 GNU 的文档中,是这么定义的: OUTPUT_ARCH(bfdarch),也就是指定了目标的体系结构,在这里,SpearPlus 内部使 用的处理器核是 arm926ejs 的,因此体系结构也就是 arm。
ENTRY(XLOADER_ENTRY)
在 GNU 的文档中,是这么定义的: ENTRY(symbol) There are several ways to set the entry point. The linker will set the entry point by trying each of the following methods in order, and stopping when one of them succeeds:
}
. = ALIGN(4)
.rodata : { *(.rodata) }
;指定只读数据段
. = ALIGN(4);
.data : { *(.data) }
;指定读/写数据段
. = ALIGN(4);
.got : { *(.got) }
;指定got段, got段式是uboot自定义的一个段, 非标准段
;指定输出可执行文件是elf格式,32位ARM指令,小端
OUTPUT_ARCH(arm)
;指定输出可执行文件的平台为ARM
ENTRY(_start)
_start = 0;当此段在RAM中执行时_start = _TEXT_BASE(在board/smdk2410/config.mk中指定的值为0x33F80000,
即u-boot在把代码拷贝到RAM中去执行的代码段的开始) */
ldr r1, _TEXT_BASE /* 测试判断是从Flash启动,还是RAM */
现在,我们首先开看一看 xloader.lds 的代码:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(XLOADER_ENTRY) SECTIONS {
. = 0x00000000; . = ALIGN(4); .text : {
;指定输出可执行文件的起始代码段为_start.
SECTIONS
{
. = 0x00000000 . = ALIGN(4) .text :
; 从0x0位置开始 ; 代码以4字节对齐 ;指定代码段
{
cpu/arm920t/start.o (.text) ; 代码的第一个码部分 *(.text) ;其它代码部分
/* nand.lds */
SECTIONS {
firtst 0x00000000 : { head.o init.o }
second 0x30000000 : AT(4096) { main.o }
}
以上,head.o 放在 0x00000000 地址开始处,init.o 放在 head.o 后面,他们的运行 地址也是 0x00000000,即连接和存储地址相同(没有 AT 指定);main.o 放在 4096 (0x1000,是 AT 指定的,存储地址)开始处,但是它的运行地址在 0x30000000,运行 之前需要从 0x1000(加载处)复制到 0x30000000(运行处),此过程也就用到了读取 Nand flash。 这就是存储地址和连接(运行)地址的不同,称为加载时域和运行时域,可以在.lds 连接 脚本文件中分别指定。 编写好的.lds 文件,在用 arm-linux-ld 连接命令时带-Tfilename 来调用执行,如 arm-linux-ld –Tnand.lds x.o y.o –o xy.o。 也用-Ttext 参数直接指定连接地址,如 arm-linux-ld –Ttext 0x30000000 x.o y.o –o xy.o。
(3)此外,学习一下 adr 伪指令,U-boot 中那段 relocate 代码就是通过 adr 实现当 前程序是在 RAM 中还是 flash 中。注释如下:
relocate:
/* 把U-Boot重新定位到RAM */
adr r0, _start /* r0是代码的当前位置 */
/* adr伪指令,汇编器自动通过当前PC的值算出 如果执行到_start时PC的值,放到r0中:当此段在flash中执行时r0 =
对.lds 连接脚本文件的分析
对于.lds 文件,它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个段 的存储位置。 先看一下 GNU 官方网站上对.lds 文件形式的完整描述:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
既然程序有了两种地址,就涉及到一些跳转指令的区别,这里正好写下来,以后万一忘记 了也可查看. ARM 汇编中,常有两种跳转方法: b 跳转指令、ldr 指令向 PC 赋值。 经过归纳如下: (1)b step1 :b 跳转指令是相对跳转,依赖当前 PC 的值,偏移量是通过该指令本身的
bit[23:0]算出来的,这使得使用 b 指令的程序不依赖于要跳到的代码的位置,只看指令 本身。 (2)ldr pc, =step1 :该指令是从内存中的某个位置(step1)读出数据并赋给 PC, 同样依赖当前 PC 的值,但是偏移量是那个位置(step1)的连接地址(运行时的地址), 所以可以用它实现从 Flash 到 RAM 的程序跳转。
接下来的部分,是对整个输出目标文件中各个段的存储位置的定义。 在 GNU 的文档中,是这么定义的:
SECTIONS { sections-command
sections-command ... }
对于其中的每一个 sections-command,其完整的定义如下: The full description of an output section looks like this:
__u_boot_cmd_start = . ;把__u_boot_cmd_start赋值为当前位置, 即起始位置
.u_boot_cmd : { *(.u_boot_cmd) } ;指定u_boot_cmd段, uboot把所有的uboot命令放在该段.
__u_boot_cmd_end = .;把__u_boot_cmd_end赋值为当前位置,即结束位置
Most output sections do not use most of the optional section attributes. The whitespace around section is required, so that the section name is unambiguous. The colon and the curly braces are also required. The line breaks and other white space are optional. 下面来看看 xloader.lds 中 SECTIONS 的定义:
* the `-e' entry command-line option; * the ENTRY(symbol) command in a linker script; * the value of the symbol start, if defined; * the address of the first byte of the `.text' section, if present; * The address 0. 也 就 是 说 , ENTRY(XLOADER_ENTRY) 定 义 了 整 个 程 序 的 入 口 处 , 也 就 是 在 标 号 XLOADER_ENTRY 处。整个程序将从这里开始运行。
section [address] [(type)] : [AT(lma)] [ALIGN(section_align)] [SUBALIGN(subsection_align)] {
output-section-command output-section-command ... } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
. = ALIGN(4);
__bss_start = .; 把__bss_start赋值为当前位置,即bss段的开始位置 .bss : { *(.bss) }; 指定bss段 _end = .; 把_end赋值为当前位置,即bss段的结束位置 }
本文中的所有代码版本都是基于 ST 的 SpearPlus 开发板的。 xloader 是在系统上电之后,执行完 ROM 中的 frimware 后最先开始执行的用户程序, 它的体积很小,执行的功能也很简单,主要是对系统时钟以及外部 SDRAM 进行初始化,初 始化完成之后就检查 Flash 中的 uboot image 是否准备好,如果准备好了就将 Flash 中的 uboot image 根据 image header 中指定的 load address 加载到外部 SDRAM 中,然后就跳转到 uboot 执行代码。 这里,我试图从头开始,在源代码级别上来分析整个系统的引导过程。 像 Xloader 或者 uboot 之类的程序,并不像我们平常写的应用程序那样,程序的入口函 数直接找 main 函数就行。对于这种系统程序,在最开始看代码,尤其是要找到最开始执行 的代码的位置的时候,最好的一个方法就是找到整个工程的.lds 文件,也就是链接脚本文 件(linker loader script)。它定义了整个工程在编译之后的链接过程,以及各个输 入目标文件中的各个段在输出目标文件中的分布。详细的关于 lds 文件的介绍可以参考 gnu 的在线文档:/binutils/docs/ld/index.html。其 中的第三节 Linker Script 对链接脚本文件进行了介绍。
{ contents } >region :phdr =fill
...
}
secname 和 contents 是必须的,其他的都是可选的。 下面是几个常用的: 1、secname:段名 2、contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某 段(代码段、数据段等) 3、start:本段连接(运行)的地址,如果没有使用 AT(ldadr),本段存储的地址也是 start。GNU 网站上说 start 可以用任意一种描述地址的符号来描述。 4、AT(ldadr):定义本段存储(加载)的地址。 看一个简单的例子:(摘自《2410 完全开发》)
/* 此句执行的结果r1始终是0x33FF80000,因为此值是又编译器指定的(ads中设置,或-D设置编译器参数) */
cmp r0, r1 /* 比较r0和r1,调试的时候不要执行重定位 */
下面,结合u-boot.lds看看一个正式的连接脚本文件。这个文件的基本功能还能看明白, 虽然上面分析了好多,但其中那些GNU风格的符号还是比较难理解:
下面,我们对这一段代码逐句进行分析。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
在 GNU 的文档中,是这么定义的: OUTPUT_FORMAT(default, big, little),在链接的时候,如果使用了-EB 的命令 行参数,则使用这里的 big 参数指定的字节序,如果使用了-EL 的命令行参数,则使用这 里的 little 参数指定的字节序,如果没有使用任何命令行参数,则使用这里的 default 参数指定的字节序。 由 xloader.lds 中的定义可见,不管在链接的时候使用了何种命令行参数,输出的目标 文件都是使用 elf32-littlearm 方式的字节序。
./obj/init.o (.text) *(.text) }
.rodata . : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4); .got : { *(.got) }
. = ALIGN(4); __bss_start = .; .bss : { *(.bss) } _end = .; }
OUTPUT_ARCH(arm)
在 GNU 的文档中,是这么定义的: OUTPUT_ARCH(bfdarch),也就是指定了目标的体系结构,在这里,SpearPlus 内部使 用的处理器核是 arm926ejs 的,因此体系结构也就是 arm。
ENTRY(XLOADER_ENTRY)
在 GNU 的文档中,是这么定义的: ENTRY(symbol) There are several ways to set the entry point. The linker will set the entry point by trying each of the following methods in order, and stopping when one of them succeeds:
}
. = ALIGN(4)
.rodata : { *(.rodata) }
;指定只读数据段
. = ALIGN(4);
.data : { *(.data) }
;指定读/写数据段
. = ALIGN(4);
.got : { *(.got) }
;指定got段, got段式是uboot自定义的一个段, 非标准段