s3c2440之代码重定位

mac2025-04-29  9

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接: 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,0000nand启动时: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、如何实现重定位

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

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


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

代码重定位可分为两步:

把程序的“代码段(.text)”、“数据段(.data)”、只读数据段(.rodata)全部拷贝到SDRAM上(起始地址为0x3000,0000)将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 = .;

}

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&lt;&lt;12)|(1&lt;&lt;4)|(1&lt;&lt;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 1

执行这句话以后,程序依据链接地址去需要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&lt;&lt;12)|(1&lt;&lt;4)|(1&lt;&lt;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 //返回 1

为什么第一个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&lt;&lt;12)|(1&lt;&lt;4)|(1&lt;&lt;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


此致!

</div> <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-e9f16cbbc2.css" rel="stylesheet"> </div>
最新回复(0)