(嵌入式Linux 6 )代码重定位

目录

段的概念_重定位的引入

2440访问内存有以下几种方式

重定位的概念

段的概念

链接脚本的引入与简单测试

变量的重定位

实现方法

链接脚本的解析

脚本的改进

代码重定位

实现方法

C语言实现


参考链接:https://blog.csdn.net/Sanjay_Wu/article/details/88723448

资料来源:韦东山嵌入式linux

段的概念_重定位的引入

2440访问内存有以下几种方式

1.由内存控制器直接访问SDRAM

2.由内存控制器直接访问NorFlash,此时SRAM的首地址为0x40000000,程序从Nor中运行,以4字节的方式

3.内存控制器直接访问SRAM

4.内存控制器通过NandFlash控制器访问NandFlash,此时SRAM的首地址为0,首先将前4K的程序拷贝到SRAM运行

重定位的概念

1、程序烧写在Nand Flash上的情况

(1)Nand Flash程序的启动流程:当我程序烧写在Nand Flash的时候,CPU是无法从Nand Flash中取代码执行的,开机上电的时候Nand启动硬件会自动把Nand Flash前4K复制到CPU的内部SRAM里面,然后CPU从内部SRAM的0地址开心执行代码。

(2)存在问题:如果当我们的程序大于4K的时候,内部SRAM就不够存放Nand Flash的程序了,因为SRAM的大小只有4K。

(3)解决程序大于4K的方法:在程序设计时,设计程序的前4K代码实现将整个程序读出来放到外部的SDRAM。

2、程序烧写在Nor Flash上的情况

(1)Nor Flash程序的启动流程:Nor Flash的基地址为0,片内RAM地址为0x4000 0000;CPU读出Nor上第1个指令(前4字节),执行;CPU继续读出其它指令执行。

(2)存在问题:Nor Flash可以像内存直接读,但是不能直接写。当程序中含有需要写的全局变量或静态变量时,假如是在Nand Flash可以正常操作,如果是在Nor Flash,修改是无效。

(3)解决写无效的方法:我们把全局变量和静态变量这些数据段或者整个程序重定位放到外部SDRAM上,在SDRAM上修改全局变量和静态变量的值。


 

段的概念

一个程序包含多个段,分别是代码段,数据段(初值不为0的全局变量),rodata段(const全局变量),bss段(初值为0,或者无初值的全局变量),commen段(注释)。其中bss段与commen段不保存在bin中。

 

示例:Nor启动和Nand下修改全局变量的值:

main:

#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"

char g_Char = 'A';
const char g_Char2 = 'B';
int g_A = 0;
int g_B;

int main(void)
{
	uart0_init();

	while (1)
	{
		putchar(g_Char);
		g_Char++;         /* nor启动时, 此代码无效 */
		delay(1000000);
	}

	
	return 0;
}

makefile:

all:
	arm-linux-gcc -c -o led.o led.c
	arm-linux-gcc -c -o uart.o uart.c
	arm-linux-gcc -c -o init.o init.c
	arm-linux-gcc -c -o main.o main.c
	arm-linux-gcc -c -o start.o start.S
	arm-linux-ld -Ttext 0 -Tdata 0x800  start.o led.o uart.o init.o main.o -o sdram.elf
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin
	arm-linux-objdump -D sdram.elf > sdram.dis
clean:
	rm *.bin *.o *.elf *.dis

arm-linux-ld -Ttext 0 -Tdata 0x800  start.o led.o uart.o init.o main.o -o sdram.elf  :将代码段放在0地址,数据段放在0x800地址

生成bin文件:

bin文件大小2049,对应0x801,所以bin文件中,地址0x800只包含了char g_Char = 'A';如图:

可以发现,bss段,comment段是不保存在bin中的(int g_A = 0;int g_B;),而rodata段紧随代码段保存(const char g_Char2 = 'B';),数据段(char g_Char = 'A';)保存在了makefile指定的0x800

运行结果:

当运行nand启动时,程序可以输出ABCDE...  ,当运行Nor启动时,程序输出AAAAAA

原因:

Nor启动时,SRAM的地址是0x40000000,程序直接从nor中运行,nor中0x800存放g_char,Nor可以读,但是不能简单的写,所以g_char++并没有把'A'+1,则输出全是AAA

Nand启动时,程序从Nard中将4K程序拷贝到SRAM中运行,SRAM的地址为0,这时g_char存放在SRAM的0x800中,可以直接修改。

链接脚本的引入与简单测试

如何解决呢?是否可以让数据段放在SDRAM中,代码段放在Nor中呢?答案是否定的,如果这样做了,可以发现,在bin文件中,代码段与数据段之间有巨大的空间浪费,且bin文件达到800M,这样是不行的。所以,这里引入重定位。

1.变量的重定位

将bin文件(包括代码段,数据段)烧写到Nor中,运行时将数据段拷贝到SDRAM中,也就是地址0x3000000中。

2.代码重定位

将bin文件(包含代码段,数据段)烧写到Nor中,运行时,将代码段和数据段都复制到SDRAM中。

 

变量的重定位

实现方法

1.修改makefile,添加脚本:

all:
	arm-linux-gcc -c -o led.o led.c
	arm-linux-gcc -c -o uart.o uart.c
	arm-linux-gcc -c -o init.o init.c
	arm-linux-gcc -c -o main.o main.c
	arm-linux-gcc -c -o start.o start.S
	#arm-linux-ld -Ttext 0 -Tdata 0x800  start.o led.o uart.o init.o main.o -o sdram.elf
	#添加链脚本
	arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf 
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin
	arm-linux-objdump -D sdram.elf > sdram.dis
clean:
	rm *.bin *.o *.elf *.dis

新建脚本

SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) { *(.data) }
   .bss  : { *(.bss) *(.COMMON) }
}

首先代码段text,从0地址开始,然后是rodata段,然后是data段,data段从0x800重定位到0x30000000,再然后是bss段与commen段

main函数开始前,初始化SDRAM,在启动文件Start.S中添加重定位:

bl sdram_init
	
mov r1,#0x800
ldr r0,[r1]  //读取0x800的值到r0
mov r1,#0x30000000  
str r0,[r1]  //将r0的值写入0x30000000

但是这样的重定位并不智能,需要手写地址,过于麻烦,所以可以自动重定位地址:

修改sdram.lds

SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data);  #0x800
      data_start = . ; #0x30000000
      *(.data) 
      data_end = . ;
   }
   .bss  : { *(.bss) *(.COMMON) }
}

data_load_addr表示bin文件中数据加载地址

data_start表示重定位地址

data_end表示段结束地址

修改Start.s

在sdram_init与main中添加:

	/* 重定位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    /*地址+1*/
	add r2, r2, #1
	cmp r2, r3        /*比较r2与r3*/
	bne cpy            /*如果不相等,跳到cpy*/

链接脚本的解析

脚本格式:

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

secname:段名:
start:起始地址:运行时地址runtime addr,重定位地址 relocate addr

AT(ldadr) Load Addr:加载地址 不写时,LoadAddr = runtime addr 如果没有加AT 它的的加载地址就等于链接时的起始地址。

使用脚本时,通过makefile:

arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf

elf格式程序含有地址信息。再将elf格式的文件生成bin文件 bin文件里面不含有地址信息。
arm-linux-objcopy -O binary -- sdram.elf sdram.bin

对于elf格式的程序,

1.链接得到elf文件,含有地址信息(LoadAddr)

2.使用加载器将elf程序读入内存(读到LoadAddr)调试工具。对于APP 加载器也是APP。

3.运行程序

4.如果LoadAddr != RuntimeAddr 程序本身要重定位。

核心:程序运行时,应为与runtimeAddr

而对于裸板程序,

1.blf->bin

2.硬件启动机制

3.如果bin文件所在位置!=runtimeAddr,程序本身实现重定位

SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data);  #0x800
      data_start = . ; #0x30000000
      *(.data) 
      data_end = . ;
   }
   .bss  : { *(.bss) *(.COMMON) }
}

上面代码中,data段loadAddr=0x800,RuntimeAddr=0x30000000,.bss段不在bin文件中,runtimeAdde跟随data,在0x3xxxxxxx。bss段中包含未定义的全局变量或者初始值为0的全局变量,在程序运行时,需要把bss段对应的空间位置清0.如果未清0,bss段的变量会输出乱码,如图:

修改sdram.lds 添加bss代码段的标志

SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data);
      data_start = . ;
      *(.data) 
      data_end = . ;
   }
   
   bss_start = .;
   .bss  : { *(.bss) *(.COMMON) }
   bss_end = .;
}

在启动文件中添加清0代码

	/*清除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

运行结果:

总结:bss段可能包含数百上千个全局变量,如果全部放入bin,会占据很大空间,所以,只需要在程序运行前,将bss段放置在runtimeAddr中,便可省区大量的空间。

脚本的改进

上述脚本中,对数据的复制cpy都是以字节的单位操作,可以改为以字,4字节的单位操作

start.s中:

cpy:
	ldr r4, [r1]
	str r4, [r2]
	add r1, r1, #4
	add r2, r2, #4
	cmp r2, r3
	ble cpy			//ble小于等于

	/*清除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

同时,在脚本文件中进行地址的4字节对齐:

SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data);
	  . = ALIGN(4);
      data_start = . ;
      *(.data) 
      data_end = . ;
   }
   
   . = ALIGN(4);
   bss_start = .;
   .bss  : { *(.bss) *(.COMMON) }
   bss_end = .;
}

代码重定位

实现方法

操作步骤:

1.链接脚本指定RuntimeAdd为SDRAM地址

2.重定位之前的代码为与运行位置无关,为位置无关码

修改链接脚本,代码runtimeAddr为0x30000000

SECTIONS
{
	. = 0x30000000;

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

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

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

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

修改启动文件Start.s中的重定位相关代码:

/* 重定位text data rodata段 */
	mov r1, #0  /*从0地址开始复制代码段 */
	ldr r2, =_start 	 /* 启动地址,地址在Start.s中确认 */
	ldr r3, =__bss_start 	     /* data段结束地址 */

cpy:
	ldr r4, [r1]
	str r4, [r2]
	add r1, r1, #4
	add r2, r2, #4
	cmp r2, r3
	ble cpy			//ble小于等于

	/*清除BSS段*/
	ldr r1,=__bss_start
	ldr r2,=_end
	mov r3,#0
clean:
	str r3,[r1]
	add r1,r1,#4
	cmp r1,r2
	ble clean
	
	//bl main
	ldr pc, =main

将代码Nor中0地址的代码复制到0x30000000,同时在重定位后使用ldr pc, =main  将pc重定位到0x30000000

注:在对应的反汇编里,重定位前,程序跳转使用的是相对跳转

3000005c:	eb000105 	bl	30000478 <sdram_init>

30000478并不是指一定跳转到30000478地址,这是一个相对跳转,程序运行开始于30000000,而sdram_init存放于离程序运行开始478的位置,这个偏移量由系统算得。

而重定位后,需要使用绝对跳转指令将pc指向当前代码需要运行的runtimeAdd:

ldr pc, =main

总结:怎么写位置无关的程序:
使用位置无关码! 不使用绝对地址! 最根本的办法是看反汇编
a. 调用程序时使用B/BL相对跳转指令
b. 重定位之前, 不可使用绝对地址,比如:
   不可访问全局变量/静态变量
   不可访问有初始值的数组(因为初始值放在rodata里,使用绝对地址来访问)
c. 重定位之后, 使用绝对跳转命令跳到Runtime Addr,比如:
    ldr pc, =main

C语言实现

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

在C程序中获得需要复制的代码段地址。

首先在链接脚本中添加地址变量:

SECTIONS
{
	. = 0x30000000;

	__code_start = .;

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

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

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

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

在启动文件中添加C程序函数:

bl sdram_init
	//bl sdram_init2	 /* 用到有初始值的数组, 不是位置无关码 */

	/* 重定位text, rodata, data段整个程序 */
	bl copy2sdram

	/* 清除BSS段 */
	bl clean_bss

	//bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
	ldr pc, =main  /* 绝对跳转, 跳到SDRAM */

在sdram_int.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;

	while (dest < end)
	{
		*dest++ = *src++;
	}
}


void clean_bss(void)
{
	/* 要从lds文件中获得 __bss_start, _end
	 */
	extern int _end, __bss_start;

	volatile unsigned int *start = (volatile unsigned int *)&__bss_start;
	volatile unsigned int *end = (volatile unsigned int *)&_end;


	while (start <= end)
	{
		*start++ = 0;
	}
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要将嵌入Linux代码精简到最少,可以采取以下几个步骤: 1. 选择合适的内核配置:在编译Linux内核时,通过配置文件选择只包含必要功能的最小配置,可以使用make menuconfig或make config等命令进行配置。只选择需要的设备驱动、文件系统、网络协议等,去掉不需要的功能和模块。 2. 精简启动过程:嵌入系统通常可以通过bootloader进行启动,可以通过配置bootloader,如U-Boot,去掉不需要的功能和模块,减少启动时间和内存占用。 3. 优化内存使用:可以通过配置内存分配策略、使用轻量级数据结构等方来优化内存使用。可以使用动态内存分配器(如malloc)的替代方案,如使用静态内存池或固定大小的内存分配器。 4. 移除不必要的库和应用程序:检查并移除不需要的库和应用程序,只保留必要的功能。可以使用静态链接代替动态链接,以减少库的依赖和体积。 5. 优化代码:对代码进行精简和优化,删除冗余代码、无用变量和函数。使用更高效的算法和数据结构,减少资源占用。 6. 压缩文件系统:使用压缩文件系统(如SquashFS)来减小文件系统的大小,节省存储空间。 7. 压缩可执行文件:使用工具(如strip)去除可执行文件中的调试信息和符号表,以减小文件大小。 需要注意的是,在精简代码的过程中,要确保删除的功能和模块不会影响系统的正常运行和所需功能。同时,为了提高可维护性和可扩展性,需要进行充分的测试和验证。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值