13_代码重定位

13_代码重定位

1、段的概念

程序从nor启动,直接从nor flash中运行;
程序从nand启动,硬件先把前4K内容复制到SRAM中,在前4K程序中完成代码的重定位,即复制代码到SDRAM。
在这里插入图片描述
程序含有:
在这里插入图片描述

2、链接脚本

Nor启动:
在这里插入图片描述
Nand启动:
在这里插入图片描述
重定位:
在这里插入图片描述

2.1、链接脚本编写

SECTIONS {
	.text	0 : { *(.text) }		/* 代码段 */
.rodata   : { *(.rodata) }	/* 只读数据段 */	
	.data 0x30000000 : AT(0x800)	 /* 数据段 */
	{
		data_load_addr = LOADADDR(.data);
		data_start = . ;
		*(.data) 
		data_end = . ;
	}
	.bss	  : { *(.bss) *(.COMMON) }		/* bss段,存放无初值、初值为0、注释代码 */
}

连接脚本的格式:
在这里插入图片描述

2.2、重定位过程

在这里插入图片描述

在start,S中:
	/* 在main函数之前初始化sdram */
	bl sdram_init
	/* 重定位data段 */
	ldr r1, =data_load_addr	/* data段在bin文件中的地址(加载地址) */
	ldr r2, =data_start		/* data段重定位后的地址(程序运行的地址) */
	ldr r3, =data_end		/* data段结束地址 */
cpy:
	ldrb r4, [r1]
	strb r4, [r2]
	add r1, r1, #1
	add r2, r2, #1
	cmp r2, r3
	bne cpy		/* bne:Branch if Not Equal  如果 r2 != r3 则跳转到cpy处*/
	/* 清除bss段 */
	ldr r1, =bss_start
	ldr r2, =bss_end
	mov r3, #0
clean:
	strb r3, [r1]
	add r1, r1, #1
	cmp r1, r2
	bne clean
	/* 跳转到main函数执行程序 */
	bl main

上述使用ldrb strb 一个字节效率低,使用ldr str 四字节效率高。

	/* 在main函数之前初始化sdram */
	bl sdram_init
	/* 重定位data段 */
	ldr r1, =data_load_addr	/* data段在bin文件中的地址(加载地址) */
	ldr r2, =data_start		/* data段重定位后的地址(程序运行的地址) */
	ldr r3, =data_end		/* data段结束地址 */
cpy:
	ldr r4, [r1]
	str r4, [r2]
	add r1, r1, #4
	add r2, r2, #4
	cmp r2, r3
	ble cpy		/* ble:Branch if Less than or Equal如果:r2 <= r3 */
	/* 清除bss段 */
	ldr r1, =bss_start
	ldr r2, =bss_end
	mov r3, #0
clean:
	str r3, [r1]
	add r1, r1, #4
	cmp r1, r2
	ble clean	/* 如果:r1 <= r2 */
/* 跳转到main函数执行程序 */
	bl main

修改了str ldr之后程序在运行清除bss段时可能就把data段也清除了(因为bss段的地址没有4字节对齐)。所以程序在清楚过程中自动4字节对齐之后就把data段业清除了(恰巧data段放在bss段旁边)。
加上四字节对齐即可解决问题:
连接脚本文件sdram.lds中:

	. = ALIGN(4);	/* 加上此句话,向4字节对齐 */
	.bss : 
	{ 
		bss_start = . ;
		*(.bss) *(.COMMON)
		bss_end = . ;
	}

2.3、程序分析

在这里插入图片描述
代码中的bl跳转分析:
在这里插入图片描述
程序使用bl main不会在sdram运行,还是在flash运行;使用ldr pc, main绝对跳转之后程序可在sdram运行
在这里插入图片描述

3、C语言写重定位代码

3.1、汇编写

/* 在main函数之前初始化sdram */
	bl sdram_init
	/* 重定位text,rodata,data段整个程序 */
	mov r1, #0
	ldr r2, =_start			/*第一条指令运行时的地址(程序运行的地址) */
	ldr r3, =__bss_start	/* 结束地址:bss段的起始地址 */
cpy:
	ldr r4, [r1]
	str r4, [r2]
	add r1, r1, #4
	add r2, r2, #4
	cmp r2, r3
	ble cpy		/* 如果:r2 <= r3 */
	/* 清除bss段 */
	ldr r1, =__bss_start
	ldr r2, =_end
	mov r3, #0
clean:
	str r3, [r1]
	add r1, r1, #4
	cmp r1, r2
	ble clean	/* 如果:r1 <= r2 */

3.2、C语言写

/* 在main函数之前初始化sdram */
	bl sdram_init
	/* 重定位text,rodata,data段整个程序 */
	bl copy2sdram
	/* 清除bss段 */
	bl clean_bss

在init.c中:

void copy2sdram(void)
{
	/* 要从lds文件中获得 __code_start,__bss_start
	 * 然后从0地址把数据复制到 __code_start*/
	extern int __code_start,__bss_start;
	volatile unsigned int *dest = (volatile unsigned int *)&__code_start;
	volatile unsigned int *end  = (volatile unsigned int *)&__bss_start;
	volatile unsigned int *src  = (volatile unsigned int *)0;
	unsigned int i = 0;
	while(dest < end)
	{
		*dest++ = *src++;
	}
}
void clean_bss(void)
{
	/* 要从lds文件中获得 __bss_start,_end */
	extern int __bss_start,_end;
	volatile unsigned int *start = (volatile unsigned int *)&__bss_start;
	volatile unsigned int *end  = (volatile unsigned int *)&_end;
	while(start <= end)
	{
		*start++ = 0;
	}
}

3.3、C语言、汇编代码分析

上述汇编程序程序中使用ldr r1等指令把参数传给函数,也可不是用这些命令直接使用lds中的参数。
如: extern int __bss_start,_end;
extern int __code_start,__bss_start;等

使用方法:
C函数怎么使用lds文件中的变量abc?
a. 在C函数中声明改变量为extern类型, 比如:
extern int abc;
b. 使用时, 要取址, 比如:
int *p = &abc; // p的值即为lds文件中abc的值

相关论坛内容:

3.4、C代码如何使用链接脚本中定义的变量

http://www.100ask.org/bbs/forum.php?mod=viewthread&tid=16231&highlight=%C1%B4%BD%D3%BD%C5%B1%BE
即下列内容:
在链接脚本中,经常有这样的代码:

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

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

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

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

其中__bss_start, _end 表示BSS段的起始、终止地址。
我们想对这段空间清零时,
1.在汇编代码中,可以直接引用__bss_start, _end,比如:
ldr r0, =__bss_start
ldr r1, =_end
2. 在C代码中,我们不能直接引用它们,要这样做:
void clean_bss(void)
{
extern int __bss_start, _end;
int *p = &__bss_start;
for (; p < &_end; p++)
*p = 0;
}
问:__bss_start, _end不是表示某个值吗?在C代码中为什么要使用取址符号 & ?
答:
一.
在C代码中,这样的语句:
int foo = 1000;
会导致2件事情发生:

  1. 在代码中,留出4字节的空间,保存数值1000
    2.在C语言的symbole talbe,即符号表中,有一个名为foo的项,它里面存有那4字节空间的地址。
    我们执行 foo = 1时,会先去符号表中找到foo对应的地址,然后把数值1填到那个地址对应的内存;
    我们执行 int *a = &foo时,会直接把符号表中foo的地址,写给a。

二.
在链接脚本中,假设
__bss_start = 1000

__bss_start并不是一个变量,它只是一个值,并不需要在内存中留出一段空间来保存它;
在C语言中,符号表中会有一个名为__bss_start的项,这个项目中的值(地址值)是1000;
注意,这个1000并没有实际存在的内存。

三.
所以:在C语言中,要去使用链接脚本中定义的值时,应该这样做:
extern int __bss_start;
int val = &__bss_start;
使用取址符号&去得到它在符号表中的值。
注意,这个值只是链接脚本中定义的值,并不表示某个变量的地址。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值