博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.lds连接脚本文件的分析
阅读量:4051 次
发布时间:2019-05-25

本文共 5664 字,大约阅读时间需要 18 分钟。

对于.lds文件,它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个段的存储位置。虽然现在我还没怎么用它,但感觉还是挺重要的,有必要了解一下。
先看一下
对.lds文件形式的完整描述:

 

SECTIONS {

...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
  {
 contents } >region :phdr =fill
...
}

 

secname和contents是必须的,其他的都是可选的。下面挑几个常用的看看:
1、secname:段名
2、contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)
3、start:本段
连接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start。GNU网站上说start可以用任意一种描述地址的符号来描述。
4、AT(ldadr):定义本段
存储(加载)的地址。
看一个简单的例子:(摘自《2410完全开发》)

SECTIONS {
 
firtst 0x00000000 : {
 head.o init.} 
second 0x30000000 : AT(4096) {
 main.} 
}
    以上,
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
 
既然程序有了两种地址,就涉及到一些跳转指令的区别,这里正好写下来,以后万一忘记了也可查看,以前不少东西没记下来现在忘得差不多了。。。
ARM
汇编中,常有两种跳转方法:
b
跳转指令、
ldr
指令向
PC
赋值。
我自己经过归纳如下:
(1)       
b step1 
b
跳转指令是相对跳转,依赖当前
PC
的值,偏移量是通过该指令本身
bit[23:0]
算出来的,这使得使用
b
指令的程序不依赖于要跳到的代码的位置,只看指令本身。
(2)       
ldr pc, =step1 
:该指令是从内存中的某个位置(
step1
)读出数据并赋给
PC
,同样依赖当前
PC
的值,但是偏移量是那个位置(
step1
)的连接地址(运行时的地址),所以可以用它实现从
Flash
RAM
的程序跳转。
(3)       
此外,有必要回味一下
adr
伪指令,
U-boot
中那段
relocate
代码就是通过
adr
实现当前程序是在
RAM
中还是
flash
中。仍然用我当时的注释:
relocate: 
    adr r0, _start  

    ldr r1, _TEXT_BASE  

    cmp r0, r1 
    下面,结合
u-boot.lds
看看一个正式的连接脚本文件。这个文件的基本功能还能看明白,虽然上面分析了好多,但其中那些
GNU
风格的符号还是着实让我感到迷惑,好菜啊,怪不得连被
3
家公司鄙视,自己鄙视自己。。。
OUTPUT_FORMAT("elf32­littlearm", "elf32­littlearm", "elf32­littlearm")
  ;指定输出可执行文件是elf格式,32位ARM指令,小端
OUTPUT_ARCH(arm)
  ;指定输出可执行文件的平台为ARM
ENTRY(_start)
  ;指定输出可执行文件的起始代码段为_start.
SECTIONS
{

        . = 0x00000000 ; 从0x0位置开始
        . = ALIGN(4) ; 代码以4字节对齐
        .text : ;指定代码段
        {

          cpu/arm920t/start.(.text) ; 代码的第一个代码部分
          *(.text) ;其它代码部分
        }
        . = ALIGN(4) 
        .rodata : {
 *(.rodata) } ;指定只读数据段
        . = ALIGN(4);
        .data : {
 *(.data) } ;指定读/写数据段
        . = ALIGN(4);
        .got : {
 *(.got) } ;指定got段, got段式是uboot自定义的一个段, 非标准段
        __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赋值为当前位置,即结束位置
        . = 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的在线文档:http://sourceware.org/binutils/docs/ld/index.html。其中的第三节Linker Script对链接脚本文件进行了介绍。


现在,我们首先开看一看xloader.lds的代码:


OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")

OUTPUT_ARCH(arm)

ENTRY(XLOADER_ENTRY)

SECTIONS

{

 
 
 
. = 0x00000000;

 
 
 
. = ALIGN(4);

 
 
 
.text 
 
 
:

 
 
 
{

 
 
 
 
 
./obj/init.o 
 
 
(.text)

 
 
 
 
 
*(.text)

 
 
 
}

 

 
 
 
.rodata . :

 
 
 
 
 
 
 
{

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
*(.rodata)

 
 
 
 
 
 
 
}

 

 
 
 
 
 
 
 
. = ALIGN(4);

 

 
 
 
 

 
 
 
.data : { *(.data) }

 
 
 
. = ALIGN(4);

 
 
 
.got : { *(.got) }

 
 
 
 
 

 
 
 
. = ALIGN(4);

 
 
 
__bss_start = .;

 
 
 
.bss : { *(.bss) }

 
 
 
_end = .;

}


下面,我们对这一段代码逐句进行分析。


OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")

在GNU的文档中,是这么定义的:

OUTPUT_FORMAT(default, big, little),在链接的时候,如果使用了-EB的命令行参数,则使用这里的big参数指定的字节序,如果使用了-EL的命令行参数,则使用这里的little参数指定的字节序,如果没有使用任何命令行参数,则使用这里的default参数指定的字节序。

由xloader.lds中的定义可见,不管在链接的时候使用了何种命令行参数,输出的目标文件都是使用elf32-littlearm方式的字节序。


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:

 
 
 
* 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处。整个程序将从这里开始运行。


接下来的部分,是对整个输出目标文件中各个段的存储位置的定义。

在GNU的文档中,是这么定义的:

SECTIONS

 
 
 
 
{

 
 
 
 
 
 
sections-command

 
 
 
 
 
 
sections-command

 
 
 
 
 
 
...

 
 
 
 
}

对于其中的每一个sections-command,其完整的定义如下:

The full description of an output section looks like this:


 
 
 
 
section [address] [(type)] :

 
 
 
 
 
 
[AT(lma)] [ALIGN(section_align)] [SUBALIGN(subsection_align)]

 
 
 
 
 
 
{

 
 
 
 
 
 
 
 
output-section-command

 
 
 
 
 
 
 
 
output-section-command

 
 
 
 
 
 
 
 
...

 
 
 
 
 
 
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]


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的定义:

SECTIONS

{

 
 
 

 
 
 
. = 0x00000000;

 
 
 
. = ALIGN(4); 
 
 

 
 
 

 
 
 
.text 
 
 
:

 
 
 
{

 
 
 
 
 
./obj/init.o 
 
 
(.text)

 
 
 
 
 
*(.text)

 
 
 
}

 

 
 
 

 
 
 
.rodata . :

 
 
 
 
 
 
 
{

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
*(.rodata)

 
 
 
 
 
 
 
}

 

 
 
 
 
 
 
 
. = ALIGN(4); 
 
 

 

 
 
 
 

 
 
 

 
 
 
.data : { *(.data) }

 
 
 
. = ALIGN(4); 
 
 

 
 
 

 
 
 
.got : { *(.got) }

 
 
 
 
 

 
 
 
. = ALIGN(4); 
 
 

 
 
 

 
 
 
__bss_start = .;

 
 
 
.bss : { *(.bss) }

 
 
 
_end = .;

}


从这里,我们能够得到的最关键的信息是:整个程序的入口在标号XLOADER_ENTRY处,并且该标号定义在init.o目标文件中,因为整个最终的链接之后的目标文件中,位于最开头的就是init.o目标文件。

于是,我们可以根据这个线索来继续追踪整个的引导过程了。



参考文章:

对.lds连接脚本文件的分析

http://blog.csdn.net/tony821224/archive/2008/01/18/2051755.aspx

Documentation for binutils 2.18--ld

http://sourceware.org/binutils/docs/ld/index.html

.bss段和.data段的区别

http://www.w3china.org/blog/more.asp?name=FoxWolf&id=29997

什么是bss段

http://blog.csdn.net/bobocheng1231/archive/2008/02/23/2115289.aspx
你可能感兴趣的文章
多线程使用随机函数需要注意的一点
查看>>
getpeername,getsockname
查看>>
关于对象赋值及返回临时对象过程中的构造与析构
查看>>
VS 2005 CRT函数的安全性增强版本
查看>>
Visual Studio 2010:C++0x新特性
查看>>
drwtsn32.exe和adplus.vbs进行dump文件抓取
查看>>
cppcheck c++静态代码检查
查看>>
在C++中使用Lua
查看>>
一些socket的编程经验
查看>>
socket编程中select的使用
查看>>
可以在线C++编译的工具站点
查看>>
关于无人驾驶的过去、现在以及未来,看这篇文章就够了!
查看>>
所谓的进步和提升,就是完成认知升级
查看>>
为什么读了很多书,却学不到什么东西?
查看>>
长文干货:如何轻松应对工作中最棘手的13种场景?
查看>>
如何用好碎片化时间,让思维更有效率?
查看>>
No.174 - LeetCode1305 - 合并两个搜索树
查看>>
No.175 - LeetCode1306
查看>>
No.176 - LeetCode1309
查看>>
No.182 - LeetCode1325 - C指针的魅力
查看>>