代码重定位和位置无关码——运行于nor flash
2017-12-17 20:22
127 查看
通过前面的学习,我们知道,把可执行程序从一个位置复制到另一个位置的过程叫做重定位。
现在有两种方式,第一种是只重定位data段到内存(sdram),为什么需要重定位?因为有些flash的写操作,不是简单地内存访问,通常我们使用sdram这个介质作为程序运行的载体。但是只重定位data段这种方式存在弊端。第一,我们的调试工具通常不支持这种分体形式(比如我们的之前的代码在0地址开始存放text和rodata段,而在间隔很远处sdram 0x30000000存放data段,这就是分体的形式)的代码;第二,这种分体方式需要能够直接运行程序的flash比如nor flash才可以工作,但是有些板子根本连nor flash都没有,那么只能通过第二种方式进行开发了。
View Code
此时串口输出:
请留意现在的打印字符的速度。
之前我们说了,我们的代码全部重定位到sdram,需要我们在重定位之前的代码是位置无关的!而我们的启动文件最后跳转到main函数使用的是bl指令,bl指令时位置无关的,在调用main之前,我们已经完成了所有代码的重定位,此时程序已经运行在sdram上,但我们使用bl指令调用main函数,所以,此时我们的main函数,其实还是运行在nor flash中的,此时的运行速度,肯定不及sdram快,所以我们再次更改启动文件,
bl main替换成
ldr pc,=main
ldr指令给pc赋值为绝对地址,此时main函数的地址是sdram上的一个地址。
再次编译,查看打印速度。可以发现,现在的打印速度明显快于之前,这个时候,我们的代码才是运行在sdram中的。
现在,我们更改sdram初始化函数为:
定义一个初始化的数组,此时,我们编译运行,发现程序没有打印信息输出了,这是为什么呢?
查看反汇编:
红色部分出,可以看出,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是绝对地址,故不是位置无关的。
现在有两种方式,第一种是只重定位data段到内存(sdram),为什么需要重定位?因为有些flash的写操作,不是简单地内存访问,通常我们使用sdram这个介质作为程序运行的载体。但是只重定位data段这种方式存在弊端。第一,我们的调试工具通常不支持这种分体形式(比如我们的之前的代码在0地址开始存放text和rodata段,而在间隔很远处sdram 0x30000000存放data段,这就是分体的形式)的代码;第二,这种分体方式需要能够直接运行程序的flash比如nor flash才可以工作,但是有些板子根本连nor flash都没有,那么只能通过第二种方式进行开发了。
1 .text 2 .global _start 3 4 _start: 5 6 /* 关闭看门狗 */ 7 ldr r0, =0x53000000 8 ldr r1, =0 9 str r1, [r0] 10 11 /* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */ 12 /* LOCKTIME(0x4C000000) = 0xFFFFFFFF */ 13 ldr r0, =0x4C000000 14 ldr r1, =0xFFFFFFFF 15 str r1, [r0] 16 17 /* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */ 18 ldr r0, =0x4C000014 19 ldr r1, =0x5 20 str r1, [r0] 21 22 /* 设置CPU工作于异步模式 */ 23 mrc p15,0,r0,c1,c0,0 24 orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA 25 mcr p15,0,r0,c1,c0,0 26 27 /* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 28 * m = MDIV+8 = 92+8=100 29 * p = PDIV+2 = 1+2 = 3 30 * s = SDIV = 1 31 * FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M 32 */ 33 ldr r0, =0x4C000004 34 ldr r1, =(92<<12)|(1<<4)|(1<<0) 35 str r1, [r0] 36 37 /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定 38 * 然后CPU工作于新的频率FCLK 39 */ 40 41 42 43 /* 设置内存: sp 栈 */ 44 /* 分辨是nor/nand启动 45 * 写0到0地址, 再读出来 46 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动 47 * 否则就是nor启动 48 */ 49 mov r1, #0 50 ldr r0, [r1] /* 读出原来的值备份 */ 51 str r1, [r1] /* 0->[0] */ 52 ldr r2, [r1] /* r2=[0] */ 53 cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */ 54 ldr sp, =0x40000000+4096 /* 先假设是nor启动 */ 55 moveq sp, #4096 /* nand启动 */ 56 streq r0, [r1] /* 恢复原来的值 */ 57 58 bl sdram_init 59 60 # /* 重定位data段 */ 61 # ldr r1, =data_load_add /* data段在bin文件中的地址, 加载地址 */ 62 # ldr r2, =data_start /* data段在重定位地址, 运行时的地址 */ 63 # ldr r3, =data_end /* data段结束地址 */ 64 /* 重定位text, rodata, data段整个程序 */ 65 mov r1, #0 66 ldr r2, =_start /* 第1条指令运行时的地址 */ 67 ldr r3, =__bss_start /* bss段的起始地址 */ 68 cpy: 69 ldr r4, [r1] 70 str r4, [r2] 71 add r1, r1, #4 72 add r2, r2, #4 73 cmp r2, r3 74 bcc cpy 75 76 77 /* 清除BSS段 */ 78 ldr r1, =__bss_start 79 ldr r2, =_end 80 mov r3, #0 81 clean: 82 str r3, [r1] 83 add r1, r1, #4 84 cmp r1, r2 85 bcc clean 86 87 bl main 88 89 halt: 90 b halt 91
View Code
此时串口输出:
请留意现在的打印字符的速度。
之前我们说了,我们的代码全部重定位到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 = 0; i < 13; 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, #4 ; 0x4 300004f4: e24dd03c sub sp, sp, #60 ; 0x3c 300004f8: e59f3088 ldr r3, [pc, #136] ; 30000588 <.text+0x588> 300004fc: e24be040 sub lr, fp, #64 ; 0x40 30000500: e1a0c003 mov ip, r3 30000504: e8bc000f ldmia ip!, {r0, r1, r2, r3} 30000508: e8ae000f stmia lr!, {r0, r1, r2, r3} 3000050c: e8bc000f ldmia ip!, {r0, r1, r2, r3} 30000510: e8ae000f stmia lr!, {r0, r1, r2, r3} 30000514: e8bc000f ldmia ip!, {r0, r1, r2, r3} 30000518: e8ae000f stmia lr!, {r0, r1, r2, r3} 3000051c: e59c3000 ldr r3, [ip] 30000520: e58e3000 str r3, [lr] 30000524: e3a03312 mov r3, #1207959552 ; 0x48000000 30000528: e50b3044 str r3, [fp, #-68] 3000052c: e3a03000 mov r3, #0 ; 0x0 30000530: e50b3048 str r3, [fp, #-72] 30000534: e51b3048 ldr r3, [fp, #-72] 30000538: e353000c cmp r3, #12 ; 0xc 3000053c: ca00000f bgt 30000580 <sdram_init2+0x98> 30000540: e51b1044 ldr r1, [fp, #-68] 30000544: e51b3048 ldr r3, [fp, #-72] 30000548: e3e02033 mvn r2, #51 ; 0x33 3000054c: e1a03103 mov r3, r3, lsl #2 30000550: e24b000c sub r0, fp, #12 ; 0xc 30000554: e0833000 add r3, r3, r0 30000558: e0833002 add r3, r3, r2 3000055c: e5933000 ldr r3, [r3] 30000560: e5813000 str r3, [r1] 30000564: e51b3044 ldr r3, [fp, #-68] 30000568: e2833004 add r3, r3, #4 ; 0x4 3000056c: e50b3044 str r3, [fp, #-68] 30000570: e51b3048 ldr r3, [fp, #-72] 30000574: e2833001 add r3, r3, #1 ; 0x1 30000578: e50b3048 str r3, [fp, #-72] 3000057c: eaffffec b 30000534 <sdram_init2+0x4c> 30000580: e24bd00c sub sp, fp, #12 ; 0xc 30000584: 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是绝对地址,故不是位置无关的。
相关文章推荐
- 反汇编代码里的地址 链接地址 运行地址 存储地址 位置无关码 位置有关码
- android 混淆代码后 app 运行报错时, 如何精准定位报错位置
- 重定位过程中的位置无关代码和位置相关的代码
- 1.1 关于ARM中重定位:位置有关码和位置无关码及运行地址和链接地址
- 虚拟内存地址VMA、装载内存地址LMA和位置无关代码PIC
- 位置无关代码(PIC)的思考
- 深入理解-位置无关代码
- 计算机科学基础知识(四): 动态库和位置无关代码
- Android百度地图之位置定位和附近查找代码简单实现 (上)
- FastMM 定位内存泄露的代码位置
- Arm汇编 位置无关代码 adr 指令
- Android百度地图定位后获取周边位置的实现代码
- 位置无关(PIC)代码原理剖析
- 删除特定位置前面的字符串c++代码实例及运行结果
- Arm汇编 位置无关代码 adr 指令
- FastMM 定位内存泄露的代码位置
- b MAIN 和 ldr pc,=MAIN 的区别(谈到代码位置无关性)
- 共享库中的位置无关代码(PIC)
- arm蛋疼汇编part 10--与位置有关代码 与位置无关代码
- 使用atos和错误堆栈内存地址定位崩溃代码位置