代码重定位和位置无关码——运行于nor flash
通过前面的学习,我们知道,把可执行程序从一个位置复制到另一个位置的过程叫做重定位。
现在有两种方式,第一种是只重定位data段到内存(sdram),为什么需要重定位?因为有些flash的写操作,不是简单地内存访问,通常我们使用sdram这个介质作为程序运行的载体。但是只重定位data段这种方式存在弊端。第一,我们的调试工具通常不支持这种分体形式(比如我们的之前的代码在0地址开始存放text和rodata段,而在间隔很远处sdram 0x30000000存放data段,这就是分体的形式)的代码;第二,这种分体方式需要能够直接运行程序的flash比如nor flash才可以工作,但是有些板子根本连nor flash都没有,那么只能通过第二种方式进行开发了。
第二种方式是把整个程序都复制到内存(sdram),这种方式所有数据都是紧挨着的,以后我们都使用这种方式。
现在思考一个问题,关于第二种方式,我们的bin文件是由连接脚本指定了运行地址为sdram(0x30000000)的,但是这个bin文件我们烧写在nor flash,是从0地址开始运行的,那么在nor flash上的代码就需要把整个bin文件拷贝到sdram中去,这就是重定位,但这就要求我们必须做到,在重定位之前的代码必须是位置无关码。(连接脚本指定我们程序的运行地址为0x30000000,为什么我们的在nor flash上的代码从0地址开始运行也能工作?这就是建立在我们这部分代码必须是位置无关码的基础上的,0地址处运行和0x30000000处运行达到同样效果,需要我们保证,在重定位之前,也就是复制操作没有完成之前的代码,必须是位置无关的)。
现在修改我们之前的连接脚本和启动文件:
SECTIONS
{
. = 0x30000000; . = ALIGN();
.text :
{
*(.text)
} . = ALIGN();
.rodata : { *(.rodata) } . = ALIGN();
.data : { *(.data) } . = ALIGN();
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
_end = .;
}
上面是连接脚本,我想此时应该都不需要备注了吧,基本语法。表示0x30000000处是我们的运行地址。
更改启动文件:
/* 重定位text, rodata, data段整个程序 */
mov r1, #
ldr r2, =_start /* 第1条指令运行时的地址 */
ldr r3, =__bss_start /* bss段的起始地址 */
其中标号_start为启动文件的最开始处的标号,完整启动文件如下:
.text
.global _start _start: /* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =
str r1, [r0] /* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0] /* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = :: */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0] /* 设置CPU工作于异步模式 */
mrc p15,,r0,c1,c0,
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,,r0,c1,c0, /* 设置MPLLCON(0x4C000004) = (<<)|(<<)|(<<)
* m = MDIV+ = +=
* p = PDIV+ = + =
* s = SDIV =
* FCLK = *m*Fin/(p*^s) = **/(*^)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(<<)|(<<)|(<<)
str r1, [r0] /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/ /* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* ->[] */
ldr r2, [r1] /* r2=[] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+ /* 先假设是nor启动 */
moveq sp, # /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */ bl sdram_init # /* 重定位data段 */
# ldr r1, =data_load_add /* data段在bin文件中的地址, 加载地址 */
# ldr r2, =data_start /* data段在重定位地址, 运行时的地址 */
# ldr r3, =data_end /* data段结束地址 */
/* 重定位text, rodata, data段整个程序 */
mov r1, #
ldr r2, =_start /* 第1条指令运行时的地址 */
ldr r3, =__bss_start /* bss段的起始地址 */
cpy:
ldr r4, [r1]
str r4, [r2]
add r1, r1, #
add r2, r2, #
cmp r2, r3
bcc cpy /* 清除BSS段 */
ldr r1, =__bss_start
ldr r2, =_end
mov r3, #
clean:
str r3, [r1]
add r1, r1, #
cmp r1, r2
bcc clean bl main halt:
b halt
此时串口输出:
请留意现在的打印字符的速度。
之前我们说了,我们的代码全部重定位到sdram,需要我们在重定位之前的代码是位置无关的!而我们的启动文件最后跳转到main函数使用的是bl指令,bl指令时位置无关的,在调用main之前,我们已经完成了所有代码的重定位,此时程序已经运行在sdram上,但我们使用bl指令调用main函数,所以,此时我们的main函数,其实还是运行在nor flash中的,此时的运行速度,肯定不及sdram快,所以我们再次更改启动文件,
bl main替换成
ldr pc,=main
ldr指令给pc赋值为绝对地址,此时main函数的地址是sdram上的一个地址。
再次编译,查看打印速度。可以发现,现在的打印速度明显快于之前,这个时候,我们的代码才是运行在sdram中的。
现在,我们更改sdram初始化函数为:
void sdram_init2(void)
{
unsigned int arr[] = {
0x22000000, //BWSCON
0x00000700, //BANKCON0
0x00000700, //BANKCON1
0x00000700, //BANKCON2
0x00000700, //BANKCON3
0x00000700, //BANKCON4
0x00000700, //BANKCON5
0x18001, //BANKCON6
0x18001, //BANKCON7
0x8404f5, //REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4
0xb1, //BANKSIZE
0x20, //MRSRB6
0x20, //MRSRB7 };
volatile unsigned int * p = (volatile unsigned int *)0x48000000;
int i; for (i = ; i < ; i++)
{
*p = arr[i];
p++;
} }
定义一个初始化的数组,此时,我们编译运行,发现程序没有打印信息输出了,这是为什么呢?
查看反汇编:
300004e8 <sdram_init2>:
300004e8: e1a0c00d mov ip, sp
300004ec: e92dd800 stmdb sp!, {fp, ip, lr, pc}
300004f0: e24cb004 sub fp, ip, # ; 0x4
300004f4: e24dd03c sub sp, sp, # ; 0x3c
300004f8: e59f3088 ldr r3, [pc, #136] ; 30000588 <.text+0x588>
300004fc: e24be040 sub lr, fp, # ; 0x40
: e1a0c003 mov ip, r3
: e8bc000f ldmia ip!, {r0, r1, r2, r3}
: e8ae000f stmia lr!, {r0, r1, r2, r3}
3000050c: e8bc000f ldmia ip!, {r0, r1, r2, r3}
: e8ae000f stmia lr!, {r0, r1, r2, r3}
: e8bc000f ldmia ip!, {r0, r1, r2, r3}
: e8ae000f stmia lr!, {r0, r1, r2, r3}
3000051c: e59c3000 ldr r3, [ip]
: e58e3000 str r3, [lr]
: e3a03312 mov r3, # ; 0x48000000
: e50b3044 str r3, [fp, #-]
3000052c: e3a03000 mov r3, # ; 0x0
: e50b3048 str r3, [fp, #-]
: e51b3048 ldr r3, [fp, #-]
: e353000c cmp r3, # ; 0xc
3000053c: ca00000f bgt <sdram_init2+0x98>
: e51b1044 ldr r1, [fp, #-]
: e51b3048 ldr r3, [fp, #-]
: e3e02033 mvn r2, # ; 0x33
3000054c: e1a03103 mov r3, r3, lsl #
: e24b000c sub r0, fp, # ; 0xc
: e0833000 add r3, r3, r0
: e0833002 add r3, r3, r2
3000055c: e5933000 ldr r3, [r3]
: e5813000 str r3, [r1]
: e51b3044 ldr r3, [fp, #-]
: e2833004 add r3, r3, # ; 0x4
3000056c: e50b3044 str r3, [fp, #-]
: e51b3048 ldr r3, [fp, #-]
: e2833001 add r3, r3, # ; 0x1
: e50b3048 str r3, [fp, #-]
3000057c: eaffffec b <sdram_init2+0x4c>
: e24bd00c sub sp, fp, # ; 0xc
: e89da800 ldmia sp, {fp, sp, pc}
30000588: 300007b0 strcch r0, [r0], -r0
红色部分出,可以看出,sdram初始化函数的时候,此时在300007b0处要保存这个地址的值给r0-r3这个四个寄存器了,注意,此时我们的sdram还没有初始化完毕呢!这样肯定出问题,现在我们看看在300007b0处到底存放的是什么:
可以看到,在7b0处的值和数组初始化的值一一对应,而且,这是位于rodata段的,这rodata段的数据需要绝对地址访问,那么,我们的这个sdram初始化函数就不是位置无关的,所以,这样的代码不能正常运行。
Summary:
我们以后采取把bin文件全部重定位到sdram的方式,而且,在重定位完成之前,采用位置无关的代码编写程序(这是针对bin文件存储在nor flash上的情况)。有初始值的数组,数组的初始值放在rodata段里面,所以不是位置无关的,rodata段的数据地址已经固定,必须通过绝对地址访问。本来局部变量是存放在栈上的,但是初始值可就不是了,不要以为数组本身是个局部变量,那么数组的初始值也是直接存放在栈上的,存放在栈上的,仅仅是数组名(地址)和开辟对应的空间,具体的局部变量初始值,存放于rodata段,(这个时候编译器会在rodata段去值来初始化局部变量,这个过程会有访问rodata段的操作,不是位置无关的)注意了哟。这个你可能觉得奇怪,那么回到我之前随笔的那个问题:
之前我在main函数中,也是局部变量,定义了上面的变量,此时的字符串“char *q”存放于哪里?当然是存放于rodata段里面,这个例子对于深入学习了C语言的人应该很熟悉,因为我们知道这样初始化了的指针,是不能改变它的值得,只是那个时候我们仅仅是知道,而现在,我们却正在一步步验证我们学习得C语言基础。同样的道理,我们也知道,通常来说,返回一个指针的局部变量会由于内存释放出现问题,但是要是返回上面那个字符串的地址,哪怕是局部变量,也不会出问题,因为它存放在rodata段,地址是绝对地址,固定了的。或许你会问,既然初始化了的局部变量的初始值是存放在rodata段中的,那为什么局部变量 char *q="char *q"可以作为return返回,而char q[]="char *q"就不可以呢?
因为字符串很特别啊,你直接书写一个字符串,这个字符串所参与的操作其实是在操作这个字符串的地址,而这个地址,是rodata段的,属于固定地址,所以我们返回局部指针q,也可以达到目的,因为q的值已经是这个字符串的地址了,而且是一个不变的地址,而局部字符数组char q[];就不同了,q本身是存放在栈里面的,由于是字符数组,是一个萝卜一个坑一一对应于数组的,第一个字符放在数组第一个位置,此时的数组q,是存放在栈中的,栈给予它地址,作为局部变量返回,必然不再安全。而一个例子虽然它也是栈上分配的,可是字符串的地址赋值给了它,返回一个固定不变的地址,就不会有问题。
eg:
可以看到最后两个的地址次才是相同的,字符数组,后面的初始值虽然也是位于rodata,但是字符数组的特殊性,相当于
p[0]='1';p[1]='2';p[2]='3';p[4]='\0';是从rodata处取得值复制到栈地址上,所以这样的局部字符数组不能作为返回值,而指针就不同了,直接是rodata的地址。
只要是有初始值的数组,都不是位置无关的,但是基础局部变量比如 int a=1;这个初始值不是存放在rodata上的。数组有初始值,需要经过一步访问rodata段地操作,rodata是绝对地址,故不是位置无关的。
代码重定位和位置无关码——运行于nor flash的更多相关文章
- s3c2440裸机-代码重定位、清bss的改进和位置无关码
1.代码重定位的改进 用ldr.str代替ldrb, strb加快代码重定位的速度. 前面重定位时,我们使用的是ldrb命令从的Nor Flash读取1字节数据,再用strb命令将1字节数据写到SDR ...
- uboot 与 代码重定位
ref: https://blog.csdn.net/dhauwd/article/details/78566668 https://blog.csdn.net/yueqian_scut/articl ...
- S3C2440—10.代码重定位
文章目录 一.启动方式 1.1 NAND FLASH 启动 1.2 NOR FLASH 启动 二. 段的概念 2.1 重定位数据段 2.2 加载地址的引出 三.链接脚本 3.1 链接脚本的引入 3.2 ...
- Linux从头学06:16张结构图,彻底理解【代码重定位】的底层原理
作 者:道哥,10+年的嵌入式开发老兵. 公众号:[IOT物联网小镇],专注于:C/C++.Linux操作系统.应用程序设计.物联网.单片机和嵌入式开发等领域. 公众号回复[书籍],获取 Linux. ...
- s3c6410_uboot中的代码重定位(nand->sdram)
本文仅探讨s3c6410从nand flash启动u-boot时的代码重定位过程 参考: 1)<USER'S MANUAL-S3C6410X>第二章 MEMORY MAP 第八章 NAND ...
- 汇编指令-位置无关码(BL)与绝对位置码(LDR)(2)
位置无关码,即该段代码无论放在内存的哪个地址,都能正确运行.究其原因,是因为代码里没有使用绝对地址,都是相对地址. 位置相关码,即它的地址与代码处于的位置相关,是绝对地址 BL :带链接分支跳转指令 ...
- s3c2440裸机-代码重定位(2.编程实现代码重定位)
代码重定位(2.编程实现代码重定位) 1.引入链接脚本 我们上一节讲述了为什么要重定位代码,那么怎么去重定位代码呢? 上一节我们发现"arm-linux-ld -Ttext 0 -Tdata ...
- s3c2440裸机-代码重定位(1.重定位的引入,为什么要代码重定位)
1.重定位的引入(为什么要代码重定位) 我们知道s3c2440的cpu从0地址开始取指令执行,当从nor启动时,0地址对应nor,nor可以像内存一样读,但不能像内存一样写.我们能够从nor上取指令执 ...
- U-Boot中关于TEXT_BASE,代码重定位,链接地址相关说明
都知道U-BOOT分为两个阶段,第一阶段是(~/cpu/arm920t/start.S中)在FLASH上运行(一般情况 下),完成对硬件的初始化,包括看门狗,中断缓存等,并且负责把代码搬移到SDRAM ...
随机推荐
- Python学习笔记(四)——编码和字符串
一.编码 1.编码类别: (1)ASCII码:127个字母被编码到计算机里,也就是大小写英文字母.数字和一些符号 (2)GB2312码:中国制定的用于加入中文汉字的编码 (3)Unicode:防止由于 ...
- Tensorflow CNN入门
一.概论 以图像识别来举例,比如我们让计算机如何识别一张猫的图片识别出猫呢? 老式的计算机视觉是如何做的呢? 比如OpenCV: 首先理解很多算法,比如如何检测线条(Edge Detection) 如 ...
- 使用itext直接替换PDF中的文本
直接说问题,itext没有直接提供替换PDF中文本的接口(查看资料得到的结论是PDF不支持这种操作),不过存在解决思路:在需要替换的文本上覆盖新的文本.按照这个思路我们需要解决以下几个问题: itex ...
- 【Android】Android动态加载Jar、APK的实现
本文介绍Android中动态加载Jar.APK的实现.而主要用到的就是DexClassLoader这个类.大家都知道Android和普通的Java虚拟机有差别,它只能加载经过处理的dex文件.而加载这 ...
- 【Android】Android 8种对话框(Dialog)
1.写在前面 Android提供了丰富的Dialog函数,本文介绍最常用的8种对话框的使用方法,包括普通(包含提示消息和按钮).列表.单选.多选.等待.进度条.编辑.自定义等多种形式,将在第2部分介绍 ...
- Git 获取仓库(分布式版本控制系统)
1.在现有目录中初始化仓库 如果你打算使用 Git 来对现有的项目进行管理,你只需要进入该项目目录并输入以下命令. # 初始化仓库 $ git init 该命令将创建一个名为 .git 的子目录,这个 ...
- 启动mysql报错 -- ERROR! The server quit without updating PID file
开发说某个测试环境的mysql,无法重启了,报以下错误提示: # service mysqld restart Shutting down MySQL.. SUCCESS! Starting MySQ ...
- 传智播客c/c++公开课学习笔记--邮箱账户的破解与邮箱安全防控
一.SMTP协议 SMTP(SimpleMail Transfer Protocol)即简单邮件传输协议. SMTP协议属于TCP/IP协议簇,通过SMTP协议所指定的server,就能够把E-mai ...
- [转]Http Message结构学习总结
最近做的东西需要更深入地了解Http协议,故死磕了一下RFC2616-HTTP/1.1协议,主要是了解Http Message结构及每部分含义,在此总结一下,写一个模拟发送HTTP请求的工具,由于时间 ...
- Elasticsearch的基友Logstash(转)
Logstash 是一款强大的数据处理工具,它可以实现数据传输,格式处理,格式化输出,还有强大的插件功能,常用于日志处理. 一.原理 Input可以从文件中.存储中.数据库中抽取数据,Input有两种 ...