lds链接脚本基础与例子分析
1.基础
(1)段
-
.data
段包含初始值非0的全局变量(不管静态还是非静态) -
.rodata
段包含被const修饰的初始值非0的全局变量 -
.bss
段包含初始值为0或未初始的全局变量(不管有没有const修饰,也不管是静态还是非静态)局部变量保存在栈中
注:有的编译器会将没有初始化的变量保存在COMMON
段,等到链接时再将其放入到bss
段。 -
.text
段保存代码
(2)指定不同段的地址(不用链接脚本)
编译过程
编译: arm-linux-gcc -c -o led_on.o led_on.S #或led_on.c
链接: arm-linux-ld -Ttext 0 led_on.o -o led_on.elf
生成bin: arm-linux-objcopy -o binary -S led_on.elf led_on.bin
生成反汇编: arm-linux-objdump -D led_on.elf > led_on.dis
arm-linux-objcopy
复制一个目标文件的内容到另一个文件中,可以使用不同于源文件的格式来输出目的文件,即可以进行格式转换。常用arm-linux-objcopy
将ELF
格式的可执行文件转换为bin
二进制文件。
arm-linux-objcopy -O binary -S elf_file bin_file
-O:使用指定的格式来输出文件
-S:不从源文件中复制重定位信息和符号信息到目标文件中去
arm-linux-objdump
显示二进制文件信息,常用来查看反汇编代码。
//ELF转为反汇编
arm-linux-objdump -D elf_file>dis_file
//二进制转为反汇编
arm-linux-objdump -D -b binary -m arm bin_file > dis_file
-D:反汇编所有段
-b:指定目标码格式,不是必须的。可通过arm-linux-objdump -i查看支持的目标码格式
-m machine:指定反汇编目标文件时所使用的架构,当待反汇编文件本身没有描述架构信息的时候,这个选项很有用。
arm-linux-ld
-Ttext startaddr #直接指定代码段地址
-Tdata startaddr #直接指定数据段地址
-Tbss startaddr #直接指定bss段地址
例子
arm-linux-gcc -c -o link.o link.s
arm-linux-ld -Ttext 0x00000000 link.o -o link_elf_0x00000000//启动后PC=0x00000000
arm-linux-ld -Ttext 0x30000000 link.o -o link_elf_0x30000000//启动后PC=0x30000000
(3)链接地址和加载地址
- 链接地址是程序实际运行的地址(内存)
- 加载地址指的是程序编译后的存放地址(Flash)
(4)链接脚本格式
- 链接脚本由一系列命令组成,每个命令由一个关键字或一条对符号的赋值语句组成,命令间用分号分开。
- 若文件名或格式名内包含分号,需用单引号引用起来
(5)链接脚本的语法
SECTIONS {
...
secname start ALIGN(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
-
secname
和contents
是必须的,前者用来命名这个段,后者用来确定代码中的什么部分放在这个段中。 -
start
:段重定位地址,也称为VMA
,即运行地址。如果代码中有位置相关的指令,程序在运行时,这个段必须放在这个地址上。 -
ALIGN(align)
:虽然start
指定了运行地址,但是仍可以使用BLOCK(align)来指定对齐的要求一这个对齐的地址才是真正的运行地址。 -
(NOLOAD)
:用来告诉加载器,在运行时不用加载这个段。这个选项只有在有操作系统的情况下才有意义。 -
AT (ldadr)
:指定这个段在编译出来的映象文件中的地址,称为LMA
,即加载地址。若不指定默认加载地址等于运行地址。通过这个选项,可以控制各段分别保存在输出文件中不同的位置。 -
>region :phdr =fill
:没用到,不作介绍。
(6)简单脚本命令
-
ENTRY(SYMBOL)
: 将符号SYMBOL的值设置成入口地址(执行的第一条指令的地址)。- 入口地址设置的方法还有(按优先级高低):
- ld命令行的
-e
选项 - 链接脚本的
ENTRY(SYMBOL)
命令 - 如果定义了
start
符号,使用start
符号值 - 如果存在
.text section
, 使用其第一字节的位置值 - 使用值0
- ld命令行的
- 入口地址设置的方法还有(按优先级高低):
-
INCLUDE filename
:包含其他名为filename的链接脚本。脚本搜索路径由-L选项指定。 -
. = ALIGN(4)
:代码以4字节对齐 -
LOADADDR(.data)
:取data
段的LMA
-
ADDR(.data)
:取data
段的VMA
(7)C语言相关
对C语言符号地址的赋值
在C文件内定义的全局变量可以在链接脚本内被赋值,此处赋值的意思是更改变量的地址。
/* a.c */
#include <stdio.h>
int a = 100;
int main(void)
{
printf( "&a=0x%p ", &a );
return 0;
}
/* a.lds */
a = 3;
/* 不使用链接脚本 */
gcc -Wall -o a-without-lds a.c
&a = 0x8049598
/* 使用链接脚本 */
$ gcc -Wall -o a-with-lds a.c a.lds
&a = 0x3
将变量/函数放入指定段中__attribute__((section("section_name")))
:将作用的函数或数据放入指定名为"section_name"对应的段中。
变量:
const int descriptor[3] __attribute__ ((section ("descr"))) = { 1,2,3 };
long long rw[10] __attribute__ ((section ("RW")));
函数:
void Function_Attributes_section_0(void) __attribute__ ((section ("new_section"))); //声明时指定段
void Function_Attributes_section_0(void)
{
static int aStatic =0;
aStatic++;
}
链接脚本变量
链接脚本中定义的变量可以在C语言中使用extern
关键字声明并使用
2 链接脚本例子
(1)例1:基础链接脚本
SECTIONS {
outputa 0x10000 : //该section的VMA是0x10000
{
all.o //all.o文件的所有section
foo.o (.input1) //foo.o文件的所有(一个文件内可有多个同名section).input1 section
}
outputb : //该section的VMA是当前定位器符号的修调值(对齐后)
{
foo.o (.input2)
foo1.o (.input1)
}
outputc : //将非all.o、foo.o、foo1.o文件的. input1 section和.input2 section放入输出outputc section内
{
*(.input1)
*(.input2)
}
}
(2)例2
编译时使用链接脚本:
arm-linux-ld -Ttimer.lds -o timer_elf head.o init.o interrupt.o main.o
timer.lds
如下
SECTIONS{
. = 0X30000000;
.text : {*(.text)}
.rodata ALIGN(4) :{*(.rodata)}
.data ALIGN(4) :{*(.data)}
.bss ALIGN(4) :{*(.bss) *(COMMON)}
}
①第2行设置运行地址为0x30000000
。
-
.
为定位器符号,不指定默认为0。存放了某个段后,定位器符号会往后移动这个段的大小长度,所以后面rodata
段的地址为0X30000000
+.text
段大小(还要四字节对齐)
②第3行定义了一个名为.text
的段,它的内容为*(.text)
, 表示所有输入文件的代码段。这些代码段被集合在一起,起始运行地址为0x30000000
。
③第4行定义了一个名为.rodata
的段,在输出文件timer_elf
中,它紧挨着.text
段存放。其中的ALIGN (4)
表示起始运行地址为4字节对齐。假设前面.text
段的地址范围0x30000000~0x300003f1,则.rodata
段的地址是4字节对齐后的0x300003f4。
④第5、6行的含义与第4行类似。
(3)例3:输出SECTION的LMA修改
SECTIONS
{
.text 0x1000 :
{
*(.text)
_etext = . ;
}
.mdata 0x2000 : AT ( ADDR (.text) + SIZEOF (.text) )
{
_data = . ;
*(.data);
_edata = . ;
}
.bss 0x3000 :
{
_bstart = . ;
*(.bss) *(COMMON) ;
_bend = . ;
}
}
程序中可定义上面定义的地址:
extern char _etext, _data, _edata, _bstart, _bend;
-
.mdata 0x2000 : AT ( ADDR (.text) + SIZEOF (.text) )
:生成的bin文件中mdata
段紧跟着text
段,但是加载到内存时,mdata
段在0x2000
处。(若LMA不紧接着上一个,生成的bin文件会很大,中间都是空洞,不方便烧写)
再来看一个链接脚本片段:
.data 0x30000000 : AT(0x800) #不指定默认LMA等于VMA
- 对应的bin文件的
data
段会烧写在flash的800处,程序运行时,代码中实现从0x800(加载地址)复制代码到0x30000000(链接地址)