参考韦东山和http://www.bubuko.com/infodetail-2504561.html
1、实现一体式代码重定位
把整个程序复制到SDRAM需要哪些技术细节:
- 把程序从Flash复制到运行地址,链接脚本中就要指定运行地址为SDRAM地址;
- 编译链接生成的bin文件,需要在SDRAM地址上运行,但上电后却必须先在0地址运行,这就要求重定位之前的代码与位置无关(是位置无关码);
参考Uboot修改链接脚本:
SECTIONS
{
. = 0x30000000;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
_end = .;
}
修改start.S段:
注意0x30000000的位置就是汇编文件里定义的_start
/* 重定位text, rodata, data段整个程序 */
mov r1, #0
ldr r2, =_start /* 第1条指令运行时的地址 */
ldr r3, =__bss_start /* bss段的起始地址 */
cpy:
ldr r4, [r1]
str r4, [r2]
add r1, r1, #4
add r2, r2, #4
cmp r2, r3
ble cpy
/* 清除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
halt:
b halt
烧写到Nor Flash,启动情况:
运行结果:
代码是正常运行的
g_A = 0x00000000
AaBbCcDdEe
疑惑:为什么可以运行正常呢,main的跳转是位置无关的,所以是跳转到Nor上的,获取的应该是Nor上的data段,Nor上的数据是不可写的,应该是不停写出AaAa才对吧。
代码已经拷贝到SDRAM上了,所以可以正常运行
2、从分析反汇编引入位置无关码的概念
查看反汇编:
对bl 30000490 <sdram_init>
的疑惑,这行代码是在cpy之前执行的,因此我们还没有将代码复制到SDRAM上,而这时候却去SDRAM上执行sdram_init,理论上是执行不了这个函数的。
30000058: 05810000 streq r0, [r1]
3000005c: eb00010b bl 30000490 <sdram_init>
30000060: e3a01000 mov r1, #0 ; 0x0
30000064: e59f204c ldr r2, [pc, #76] ; 300000b8 <halt+0x14>
30000068: e59f304c ldr r3, [pc, #76] ; 300000bc <halt+0x18>
我们把链接脚本的起始地址改为0x320000000,发现机器码与之前起始地址为0x300000000是一样的,都是eb00010b,
32000058: 05810000 streq r0, [r1]
3200005c: eb00010b bl 32000490 <sdram_init>
32000060: e3a01000 mov r1, #0 ; 0x0
32000064: e59f204c ldr r2, [pc, #76] ; 320000b8 <halt+0x14>
32000068: e59f304c ldr r3, [pc, #76] ; 320000bc <halt+0x18>
说明不是真的跳到30000490 的地址,实际是跳到pc+off_set的地址:
从0x30000000执行,当前指令地址为 3000005c,跳到30000490
从0执行,当前指令地址为 5c,则跳到490
总结下,位置无关什么意思呢?
既然这个时候SDRAM还没有代码,那就不去那里找了嘛,我们去别的地方找。别的地方是哪里呢?这个时候代码是运行在SRAM上的,处理器的PC寄存器也是指向这个地方的,有4K代码也是在这个地方的。那我就不用链接指定的地址来寻找想要的代码,我设计一些是与PC寄存器指向的位置有联系的寻址指令来寻址,这样PC寄存器指向哪里,我就在哪里找我想要的代码,而不是去链接指定的地址去找。
位置无关指令就是这样的指令,他不去链接指定位置寻找代码,而是在PC寄存器指向的位置相对它前后32M的范围寻找代码,也就是说只要我想要调用的代码在PC指向位置前后的32M范围内,就能找到(这是硬件完成的,我们知道位置无关指令就是这个意思就行)。当然,我们的SRAM只有4K,所以寻找的范围肯定就只能在这4K里面才行了,超过了也是找不到的。所以要求我们的重定位代码必须在这4K里面,必要的硬件初始化代码,内存初始化代码,nand初始化代码等等,必须在4K以内完成,否则就会出问题。所以位置无关指令的寻址是基于当前PC寄存器位置来寻址的。
因此要写位置无关码的时候,我们都要用位置无关的指令。
位置无关/相关指令:
B BL ADR MOV ADD等,这些指令都是位置无关指令
LDR STR等指令是位置有关指令
怎么写位置无关码?
- 使用相对跳转命令 b或bl;
- 重定位之前,不可使用绝对地址,不可访问全局变量/静态变量,也不可访问有初始值的数组(因为初始值放在rodata里,是使用绝对地址来访问);
- 重定位之后,使用ldr pc = xxx,跳转到/runtime地址;
写位置无关码,其实就是不使用绝对地址,判断有没有使用绝对地址,除了前面的几个规则,最根本的办法看反汇编。
bl main的时候 是跳转到Nor上的地址,是跳不到SDRAM上的main函数
要想让main函数在SDRAM执行,需要修改代码:
//bl main /*bl相对跳转,程序仍在NOR/sram执行*/
ldr pc, =main/*绝对跳转,跳到SDRAM*/
你写的的sdram_init是与位置无关的代码,我们可以写一个与位置有关的——访问有初始值的数组,看看运行结果:
/**************************************************************************
* 设置控制SDRAM的13个寄存器
* 使用位置无关代码
**************************************************************************/
void memsetup(void)
{
unsigned long *p = (unsigned long *)MEM_CTL_BASE;
p[0] = 0x22111110; //BWSCON
p[1] = 0x00000700; //BANKCON0
p[2] = 0x00000700; //BANKCON1
p[3] = 0x00000700; //BANKCON2
p[4] = 0x00000700; //BANKCON3
p[5] = 0x00000700; //BANKCON4
p[6] = 0x00000700; //BANKCON5
p[7] = 0x00018005; //BANKCON6
p[8] = 0x00018005; //BANKCON7
p[9] = 0x008e07a3; //REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4
p[10] = 0x000000b2; //BANKSIZE
p[11] = 0x00000030; //MRSRB6
p[12] = 0x00000030; //MRSRB7
}
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++;
}
}
运行结果:
没有任何输出
因为我们知道,数组的初始值是保存在rodata段(绝对地址),后面才加载到栈上的。因此在Nor/SRAM上运行sdram_init2函数是需要访问SDRAM上的rodata地址,我们还没拷贝过去,是取不到我们要的数据的。