s3c2440代码重定位

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Utotao/article/details/87904948

参考文章:
[1].第013课 S3c2440代码重定位详解
[2].s3c2440学习之路-011代码重定位
[3].arm汇编指令集整理
[4].汇编运行地址,链接地址,加载地址,存储地址 位置无关码、位置有关码

感想:这个部分好多好好看,多看几遍,融汇贯通,彻底理解

1、几个概念

(1)运行地址、加载地址
① 运行地址<—>链接地址:他们两个是等价的,只是两种不同的说法。
运行地址:程序在SRAM、SDRAM中执行时的地址。就是执行这条指令时,PC应该等于这个地址,换句话说,PC等于这个地址时,这条指令应该保存在这个地址内。
② 加载地址<—>存储地址:他们两个是等价的,也是两种不同的说法。
加载地址:程序保存在Nand flash中的地址。

(2) 位置无关码、位置有关码
① 位置无关码:B、BL、MOV都是位置位置无关码。
② 位置有关码:LDR PC,=LABEL等类似的代码都是位置有关码。

(3)程序段的划分
一个程序编译后,会有代码段、数据段、只读数据段、bss段和注释段

  • .text 代码段
  • .data 数据段
  • rodata 只读数据段(const全局变量)
    -bss段 (初始值为0,无初始值的全局变量和静态局部变量)
  • commen 注释

Question:bss段为什么可以减小bin文件的大小?
答:bbs主要记录未初始化和初始化为0的全局(static 修饰的局部)变量的位置,而不会记录其具体数据,因为这部分的数值会默认设置为0。


2、s3c2440启动方式

s3c2440启动方式分为nor启动nand启动

  • nor启动时:0地址即为nor的0地址,片内sram的0地址为0x4000,0000,sdram0地址为0x3000,0000
  • nand启动时:0地址为片内sram,sdram0地址为0x3000,0000,nand启动时会把其中前4k的代码(这部分放引导代码)拷贝到片内sram上(sram只有4k),这个过程由nand flash控制器完成

3、什么是代码重定位

概括来说,所谓的代码重定位指的是将原先烧写在Nor Flash或者Nand Flash上的代码拷贝到SDRAM上运行,避免由于Nor Flash只能读不能写造成的全局变量的写入限制,避免Nand Flash启动时候,Sram空间有限(4K)而程序较大导致的访问出错。

4、为什么需要代码重定位

S3C2440的CPU可以直接给SDRAM发送命令、给Nor Flash发送命令、给4K的片上SDRAM发送命令,但是不能直接给Nand Flsh发送命令

假如把程序烧写到Nand Flsh上,即向Nand Flsh烧入* bin* 文件,CPU是无法从Nand Flsh中取代码执行的。

为什还可以使用NAND启动?

  • 上电后,Nand启动硬件会自动把Nand Flsh前4K复制到SRAM;
    CPU从0地址运行SRAM;
  • 如果我的程序大于4K怎么办?
    前4K的代码需要把整个程序读出来放到SDRAM(即代码重定位)。

如果从Nor Flash启动,会出现什么问题?

将拨动开关拨到Nor Flash启动时,此时CPU认为的 0地址 在Nor Flash上面,片内内存SRAM的基地址就变成了0x40000000(Nand启动时片内内存SRAM的基地址基地址是0),
由于Nor Flash特性:可以像内存一样读,但不能像内存直接写,因此需要把全局变量和静态变量重定位放到SDRAM里。


总结:

  • 当使用nand启动时,因为2440的特性,只能运行前4K的代码(前4K会自动拷贝到内部的sram里),因此想运行1个100K+ 的uboot.bin是不可以能的,所以需要把代码重定位到外部的SDRAM里。
  • 当使用nor启动时,jz2440开发板上nor为2MB,足够放一般的程序。但是nor 可以随意读无法随意写,这意味着全局变量是不可修改的。因为修改全局变量需要把数值重新写到nor 里面,而nor 是不可随意写的,所以写数值会失败。因此也需要把代码重定位到外部的SDRAM里。(局部变量是可以修改的,因为局部变量是放在栈里面,栈一般会设置在2440内部的sram)

5、如何实现重定位

代码重定位依赖分布式链接脚步实现,具体来说,可分为两种链接方式,分别是:分体式链接脚一体式链接脚本

通常我们在这里使用一体式链接脚本,因为:


  1. 分体式链接脚本适合单片机,单片机自带有flash,不需要再将代码复制到内存占用空间。而我们的嵌入式系统内存非常大,没必要节省这点空间,并且有些嵌入式系统没有Nor Flash等可以直接运行代码的Flash,就需要从Nand Flash或者SD卡复制整个代码到内存;
  2. JTAG等调试器一般只支持一体式链接脚本;

代码重定位可分为两步:

  1. 把程序的“代码段(.text)”、“数据段(.data)”、只读数据段(.rodata)全部拷贝到SDRAM上(起始地址为0x3000,0000)
  2. 将PC指针设置到SDRAM上

6、一体式链接脚本

关于分布式链接脚本可以参见韦老师的博客:第013课 S3c2440代码重定位详解

链接脚本的语法:

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
 { contents } 的内容: 
 start.o //内容为start.o文件
 *(.text)所有的代码段文件
 start.o *(.text)文件

本案例中的链接脚本为,保存为xxx.lds:
注意:.text等section和“:”之间要留有一个空格,否make时候会出现错误。

SECTIONS
{
	. = 0x30000000;

	_code_start = .;

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

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

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

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

关于Makefile,只需要将原先链接的那句话修改为:

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

start.s:


.text
.code 32
.global _start

_start:
	bl initconfig

initconfig:
	//关闭看门狗
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]
	//初始化时钟
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000  
	mcr p15,0,r0,c1,c0,0

	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]
	
	//设置启动方式
	mov r1, #0
	ldr r0, [r1] 
	str r1, [r1] 
	ldr r2, [r1] 
	cmp r1, r2   
	ldr sp, =0x40000000+4096
	moveq sp, #4096  
	streq r0, [r1]   
	//初始化SDRAM
	bl sdram_init
	//代码重定位
	bl copy2sram
	//清除bss段
	bl cleanbss

copy2sram:
	mov r0,#0
	ldr r1,= _code_start
	ldr r2,= _bss_start
copy:
	ldr r3,[r0]
	str r3,[r1]
	add r1,r1,#4
	add r0,r0,#4
	cmp r1,r2
	ble copy

cleanbss:
	mov r0,#0
	ldr r1,= _bss_start
	ldr r2,= _end
clean:	
	str r0,[r1]
	add r1,r1,#4
	cmp r1,r2
	ble clean
	
	ldr pc, =main  

halt:
	b halt
.end

注意(要点):

(1)为何要清除 bss段?
bbs主要记录未初始化和初始化为0的全局(static 修饰的局部)变量的位置,而不会记录其具体数据,因为这部分的数值会默认设置为0


(2)程序如何跳转
①:相对跳转
b和bl在汇编语言中是相对跳转的意思
比如对于bl copy2sram
因为这条指令 bl copy2sram是位置无关码,虽然它的运行地址是在SDRAM中的0x30000000,但是它可以在Steppingstone中执行。这条指令的功能是跳转到标号copy2sram处执行,它是一个相对跳转,PC=当前PC的值+偏移量OFFSET
偏移量 = 跳转到地址 - 当前地址 - 8

  • 如果调用C函数,在调用主函数之前应该全部使用bl命令,因为如果使用ldr pc,=XXX,程序就回不来了,无法继续执行start.S余下的代码。
  • 对于main.c,一般是在start.S最后进行调用,可以直接使用ldr pc,=main进行调用,使程序跳转到sdram上运行,实现代码的重定位
  • 对于中断向量表,第一句话 b Reset 应该使用b跳转(不返回),其余全部使用ldr pc,=XXX。第一句话不能使用ldr pc,=XXX,因为这句话会使程序跳转到链接地址处,而此时程序还没有完成代码的重定位,链接地址处并无代码可以执行。不使用bl命令是因为此处不能返回,一旦返回就会执行中断向量表,误触中断

②:绝对跳转

ldr pc,=main

执行这句话以后,程序依据链接地址去需要main所在的运行地址(0x3000,xxxx,在SRAM上),此时main代码已经被拷贝到sdram中,所以得以正确运行
Q:ldr pc,main是否可以正常执行?
A:基本上肯定不可以。ldr pc,=main将直接把main标签处的地址里面的值赋值给pc,而main出的地址所指向的值并不一定指向main函数,所以无法实现跳转。
这里面使用的是指针的思想ldr pc,do_swi 的反汇编是 ldr pc,[pc,#0]:把pc+0地址处的值赋值给pc


(3)重定位后代码的运行过程是怎么样的?

首先执行start.S中的位置无关码(这部分要放在start.S的最前面)

  • 初始化时钟、启动方式等等
  • 初始化SDRAM
  • 拷贝代码到SDRAM上
  • 清除BSS

最后执行绝对跳转指令,跳转到SDRAM上运行代码

  • 执行main

(4)怎么样写位置无关码?

  • 使用相对跳转命令 b或bl;
  • 重定位之前,不可使用绝对地址,不可访问全局变量/静态变量,也不可访问有初始值的数组(因为初始值放在rodata里,使用绝对地址来访问);
    重定位之后,使用ldr pc = xxx,跳转到/runtime地址;

(5)如果代码写成如下形式,是否可以工作?

.text
.code 32
.global _start

_start:
	bl close_watchdog
	bl clock_config
	bl nandornor
	//初始化SDRAM
	bl sdram_init
	//代码重定位
	bl copy2sram
	//清除bss段
	bl cleanbss
	ldr pc, =main
close_watchdog:
	//关闭看门狗
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]
clock_config:
	//初始化时钟
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000  
	mcr p15,0,r0,c1,c0,0

	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

nandornor:
	//设置启动方式
	mov r1, #0
	ldr r0, [r1] 
	str r1, [r1] 
	ldr r2, [r1] 
	cmp r1, r2   
	ldr sp, =0x40000000+4096
	moveq sp, #4096  
	streq r0, [r1]   


copy2sram:
	mov r0,#0
	ldr r1,= _code_start
	ldr r2,= _bss_start
copy:
	ldr r3,[r0]
	str r3,[r1]
	add r1,r1,#4
	add r0,r0,#4
	cmp r1,r2
	ble copy

cleanbss:
	mov r0,#0
	ldr r1,= _bss_start
	ldr r2,= _end
clean:	
	str r0,[r1]
	add r1,r1,#4
	cmp r1,r2
	ble clean
	
halt:
	b halt
.end

答:当然是不可以的
在反汇编中,跳转到某个地址并不是由bl指令所决定,而是由当前pc值决定。反汇编显示这个值只是为了方便读代码。
so,看一下它的反汇编

做出以下修改之后就可以运行了,在每个label的最后添上这么一句话

mov pc,     lr      //返回

为什么第一个start.S中直接写bl,label最后没有添加这句话也可以执行?
因为之前那个是顺序执行,不涉及返回的问题

	bl sdram_init
	//代码重定位
	bl copy2sram
	//清除bss段
	bl cleanbss
	ldr pc, =main

练习:写一个程序:在串口中打印出启动方式信息,代码段起始位置、bss段起始位置和程序最后终止位置,并且实现代码重定位

答案:
main.c:

#include uart.h
char g_char = 'A';

void main()
{
	puts("\n\rhello,relocate!\n\r");
	while(1){
		putch(g_char++);
	}
}

printf_message.c

//打印启动方式
void printf_nand_nor()
{

}
//打印代码起始位置信息
void print_code_start()
{

}
//打印bss起始位置信息
 void print_bss_start()
{

}
 //打印.end起始位置信息
 void print_end_start()
{

}

start.S

.text
.code 32
.global _start

_start:
	bl close_watchdog
	bl clock_config
	bl nandornor
	//初始化SDRAM
	bl sdram_init
	//代码重定位
	bl copy2sram
	//清除bss段
	bl cleanbss
	ldr pc, =main
close_watchdog:
	//关闭看门狗
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]
	mov pc,lr	//返回
	
clock_config:
	//初始化时钟
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000  
	mcr p15,0,r0,c1,c0,0

	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]
	mov pc,lr	//返回
	
nandornor:
	//设置启动方式
	mov r1, #0
	ldr r0, [r1] 
	str r1, [r1] 
	ldr r2, [r1] 
	cmp r1, r2   
	ldr sp, =0x40000000+4096
	moveq sp, #4096  
	streq r0, [r1]   
	mov pc,lr	//返回

copy2sram:
	mov r0,#0
	ldr r1,= _code_start
	ldr r2,= _bss_start
copy:
	ldr r3,[r0]
	str r3,[r1]
	add r1,r1,#4
	add r0,r0,#4
	cmp r1,r2
	ble copy

cleanbss:
	mov r0,#0
	ldr r1,= _bss_start
	ldr r2,= _end
clean:	
	str r0,[r1]
	add r1,r1,#4
	cmp r1,r2
	ble clean
	
halt:
	b halt
.end

此致!

瞎放的无关图片

猜你喜欢

转载自blog.csdn.net/Utotao/article/details/87904948
今日推荐