S5P4418裸机开发(八):lds链接脚本的使用

前几个程序编译链接时, 会使用ld -Ttext=0x42C00000指定.text段的起始地址;
串口回显的工程为例,查看编译后的文件大小;
这里写图片描述

再看看.elf文件的段表,arm-linux-objdump -h

这里写图片描述

LMA: load address,加载地址;
VMA: virtual address,在RAM中的运行地址;
Loader,装载器的作用:
1。从二进制文件中读出对应的段的信息,比如text,data,bss等段的信息,
将内容拷贝到对应的LMA的地址处。此谓,装载(对应内容)到装载地址(LMA)。
2。如果发现VMA!=LMA, 即 程序运行时候的地址,和刚刚把程序内容拷贝到的地址LMA,两者不一样,
那么就要把对应的内容,此处主要是data,数据段的内容,从刚刚装载到的位置,LMA处,拷贝到VMA处,
这样,程序运行的时候,才能够在执行的时候,找到对应的VMA处的变量,才能找到对应的值,程序才能正常运行。

一般情况下是LMA = VMA,只有小数情况是不相等:
CPU 从ROM,比如常见的NorFlash中读取代码的速度,要远远小于从RAM,比如常见的SDRAM,中读取的速度,所以,才会牵扯到将代码烧写到ROM 里面,然后代码的最开始,将此部分程序reaload,重载,也就是从此处的ROM的地址,即LMA,重新拷贝到SDRAM中去,也就是VMA的地方,然后从那里运行。更详细的点击查看

执行objcopy命令生成.bin文件时,相当于,

  1. 拷贝.elf文件的 0x10000 ~ 0x114db 放到目标文件的 0x0 ~ 0x14db处;
  2. 拷贝.elf文件的 0x114dc ~ 0x11773 放到目标文件的 0x14db ~ 0x1173处;
  3. 拷贝.elf文件的 0x11774 ~ 0x11784 放到目标文件的 0x11774 ~ 0x11784处;

注意第3部分的.data段在.bin文件中起始地址是0x11774,这就造成了.bin文件的0x1174 ~ 0x11774部分浪费了,被0填充;
所以.bin文件的大小就是 0x11784 = 71556 Byte

如何杜绝这种浪费;

同样可以使用 -Tdata 指定 .data 段的起始地址;ld -Ttext=0x42C00000 -Tdata=0x42C01774;
这样,.data段的数据就会紧接在.rodata段的后面;
看看效果;

这里写图片描述

减小了 0x10000 Byte;
再看看段表:

这里写图片描述

但这不是通用的方法,这时就要引入链接脚本 .lds

LDS基本语法

SECTIONS
{
  secname : {contents}
}

secname 表示 输出段的段名,后面必须有一个空格,使得输出段名没有歧义,后面根一个冒号和一对大括号;
大括号里面的contents描述了一套规则和条件,表示符合这种条件的输入段合并到输出段中。

以下脚本将输出文件的text 段定位在0×10000, data 段定位在0×8000000:

SECTIONS
{
. = 0×10000;				/*把定位器符号置为0×10000 (若不指定, 则该符号的初始值为0).*/
.text :     { *(.text) }	/*将所有(*符号代表任意输入文件)输入文件的.text 段合并成一个.text 段, 该段的地址由定位器符号的值指定, 即0×10000.*/
. = 0×8000000;				/*把定位器符号置为0×8000000*/
.data :   { *(.data) }		/*将所有输入文件的.data 段合并成一个.data 段, 该段的地址被置为0×8000000.*/
.bss :     { *(.bss) }		/*将所有输入文件的.bss 段合并成一个.bss 段,该段的地址被置为0×8000000+.data 段的大小.*/
}

连接器每读完一个段描述后, 将定位器符号的值*增加*该段的大小. 注意: 此处没有考虑对齐约束.

下面写出满足咱们工程需求的.lds

SECTIONS
{
	. = 0x42C00000;

	. = ALIGN(4);           /*4字节对齐*/
    .text   :{ *(.text) }

    . = ALIGN(4);
    .rodata :{ *(.rodata) }

    . = ALIGN(4);
    .data   :{ *(.data) }

    . = ALIGN(4);
    .bss    :{ *(.bss) }
}

还可以使用 read -S 查看段表,排版比 objdump 好看;

####C语言访问 .lds 中的变量
比如获取.text段的开始地址和结束地址:

SECTIONS
{
	. = 0x42C00000;

	. = ALIGN(4);           /*4字节对齐*/
    __TEXT_START = .;       /*将当前地址赋值给__TEXT_START*/
    .text   :{ *(.text) }
    __TEXT_END = .;

    . = ALIGN(4);
    .rodata :{ *(.rodata) }

    . = ALIGN(4);
    .data   :{ *(.data) }

    . = ALIGN(4);
    .bss    :{ *(.bss) }
}

在C语言里要先声明外部变量,取值时要加取地址符;

extern __TEXT_START;
extern __TEXT_END;

printf(".text start: %x  end: %x\r\n", &__TEXT_START, &__TEXT_END);	// 输出.text段的开始地址和结束地址;

输出:这里写图片描述

准确的说__TEXT_START, __TEXT_END 并不属于变量,在链接时他们的值就已经确定了,在符号表中记录;加&是为了得到它在符号表中的值;

这里写图片描述

这里写图片描述

下面是输出部分的反汇编代码:

42c000e0:	e30104fc 	movw	r0, #5372	; 0x14fc
42c000e4:	e34402c0 	movt	r0, #17088	; 0x42c0
42c000e8:	e3001000 	movw	r1, #0
42c000ec:	e34412c0 	movt	r1, #17088	; 0x42c0
42c000f0:	e30124f0 	movw	r2, #5360	; 0x14f0
42c000f4:	e34422c0 	movt	r2, #17088	; 0x42c0
42c000f8:	eb0002da 	bl	42c00c68 <printf>

可以看到,传入的是具体的值;
 
 
####关于.bss段
因为.bss段并不储存在目标文件中,又因为裸机环境没有加载器,所以C程序中的未初始化的全局变量和未初始化的静态变量并不会被初始化为0;除非RAM上本身就是0;
用程序来看现象;
在链接脚本中定义bss段的起始和结束地址,输出;
在C程序中定义一个int型全局变量g_a,输出其地址和值;

//.lds
SECTIONS
{
	. = 0x42C00000;

	. = ALIGN(4);           /*4字节对齐*/
    __TEXT_START = .;       /*将当前地址赋值给__TEXT_START*/
    .text   :{ *(.text) }
    __TEXT_END = .;

    . = ALIGN(4);
    .rodata :{ *(.rodata) }

    . = ALIGN(4);
    .data   :{ *(.data) }

    . = ALIGN(4);
    __BSS_START = .;
    .bss    :{ *(.bss) }
    __BSS_END = .;
}
//mian.c
extern __TEXT_START, __TEXT_END;
extern __BSS_START, __BSS_END;

int g_a;

int main(){
    printf(".text start: %x  end: %x\r\n", &__TEXT_START, &__TEXT_END);
    printf(".bss start: %x  end: %x\r\n", &__BSS_START, &__BSS_END);
    printf("&g_a:%x g_a:%x\r\n", &g_a, g_a);
}

在拷贝.bin文件到SD卡时,将SD卡上.bin结束之后的4字节改为0x12345678
这里写图片描述

这里写图片描述

输出结果:
这里写图片描述

所以在裸机环境,C程序的.bss 段需要我们自己来初始化;

//main.c

void bss_init(){
    int *start = &__BSS_START;
    int size = &__BSS_END - &__BSS_START;
    int i = 0;

    for(; i < size; i += 4){
        *(start + i) = 0;
    }
}
//start.s

bl bss_init		; main()之前执行

####工程源码:
码云_5_lds

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值