当前位置 博文首页 > cumtchw:S3C2440裸机------代码重定位
目录
1.段的概念
2.链接脚本的引入与简单测试
3.链接脚本的解析
4.拷贝代码和链接脚本的改进
5.代码重定位与位置无关码
6.重定位_清除BSS段的C函数实现
? ? ? ? 我们的cpu可以直接把地址通过内存控制器发送到norflash,sram,sdram,但是我们的cpu不能直接到达nandflash,只能发送到nandflash控制器,我们的程序可以放到norflash以及sdram上面,它可以直接运行,但是nandflash的程序是不能直接运行的,但是我们仍然可以把程序放到nandflash上面,因为一上电2440内部的硬件会把nandflash的前4K代码复制到片内的SRAM,这个是由硬件做的,然后cpu从0地址开始运行,此时的0地址对应的是sram。那么如果我的程序超过4K怎么办,如果bin文件在nandflash上面超过4K,那么我们不能只复制前面4K代码,所以显然我们前面4K代码应该把整个程序都复制到SDRAM上面。这就是重定位,重新确定程序的地址。
? ? ? ? 如果我是用norflash启动,此时的0地址在norflash,此时片内4Ksram的地址是0x4000,0000.norflash可以像内存一样读,但是不能像内存一样的写。如果程序中含有需要更改的全局变量或者静态变量,全局变量是包含在bin文件中,烧在norflash上面(局部变量是在栈中在SRAM上面,可读可写没有问题),直接修改变量无效。所以我们需要把这些全局变量和静态变量重定位放到SDRAM中。
? ? ? ? 我们修改之前的main.c,然后在里面定义一个全局的变量,可以发现串口一直打印A,并没有递增的打印ABCD。
#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"
char g_Char = 'A';
const char g_Char2 = 'B';
int g_A = 0;
int g_B;
int main(void)
{
uart0_init();
while (1)
{
putchar(g_Char);
g_Char++; /* nor启动时, 此代码无效 */
delay(1000000);
}
return 0;
}
我们的程序主要分为:
代码段(text):代码?
数据段(data):一般的全局变量
只读数据段(rodata) :const全局变量。
bss段:初值为0或者无初始值的全局变量。BSS段不保存在bin文件中。
comment:注释:也不保存在bin文件中。
通过上一节的测试,我们发现当我们把程序烧写到norflash时,由于norflash是不可写的,因此不能修改g_Char,所以我们需要通过修改makefile,让g_Char放到SDRAM里面,我们的代码仍然放在NORFLASH里面,只是把变量保存到SDRAM里面,由于我们的SDRAM的地址是从0X30000000开始的,因此我们修改makefile为如下的形式。
all:
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o uart.o uart.c
arm-linux-gcc -c -o init.o init.c
arm-linux-gcc -c -o main.o main.c
arm-linux-gcc -c -o start.o start.S
arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
但是编译完之后,我们发现我们的bin文件达到了800M,这是因为我们把变量放在了0X30000000这里,但是我们显然不能这么改,?针对这个问题有两个解决方案。
2.1方案一
首先我们在bin文件里面,让代码段和全局变量靠在一起,然后把bin文件烧写到norflash上面,然后运行的时候,我们前面的代码需要把全局变量复制到0X30000000的地方, 这就是重定位。
2.2方案二
第二种方法是,我们在链接的时候,让代码段和全局变量中间没有那么大的空闲,我们的代码段也从0x30000000开始,然后烧写到norflash,然后运行的时候,我们前面的代码把代码段和全局变量整个程序复制到0x30000000。
这两种方案的差别在于,第一种方法只是重定位了全局变量,第二种方法是重定位了整个程序。
下面我们先来看方案一:我们通过引入链接脚本,将本来应该位于0X30000000的变量和代码段拼在一起,我们首先修改makefile为下面的形式:
all:
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o uart.o uart.c
arm-linux-gcc -c -o init.o init.c
arm-linux-gcc -c -o main.o main.c
arm-linux-gcc -c -o start.o start.S
#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
然后我们来写出这个链接脚本sdram.lds?
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800) { *(.data) }
.bss : { *(.bss) *(.COMMON) }
}
上面的{}里面的*表示所有文件的.text段,所有文件的.rodata段。其中的AT表示at,也就是全局变量运行的时候是在0x30000000,但是在bin文件中我们把它放在0x800这里。
我们把上面的程序烧写到板子上之后显示乱码,这是因为我们的bin文件中,全局变量是保存在0X800的地方,但是我们的main函数中是以0x30000000为地址去访问全局变量的。而我们的代码中并没有把0x30000000地址的地方用全局变量的值进行初始化,也就是我们的代码中缺少重定位相关的代码,我们需要修改代码,将变量从0x800的地方复制到0x30000000的地方,我们修改start.S,在执行main函数之前进行重定位。
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
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 = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init /*首先要初始化SDRAM,要不然下面的重定位代码不起作用,没法写SDRAM*/
/* 重定位data段 */
mov r1, #0x800
ldr r0, [r1]
mov r1, #0x30000000
str r0, [r1]
bl main
halt:
b halt
上面的重定位data的代码中,我们简单粗暴的从0x800复制了一个字节到0x30000000这里,这种方法并不通用,首先我们程序中有可能有很多个全局变量,另外我们这里的地址0X800和0x30000000都是写死的,这是因为肉眼从链接脚本中观察到的,下面我们写一个通用的程序,首先要在链接脚本中添加一些变量。
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800)
{
data_load_addr = LOADADDR(.data);
data_start = . ;
*(.data)
data_end = . ;
}
.bss : { *(.bss) *(.COMMON) }
}
其中data_start后面的.表示当前地址,也就是0x30000000, data_end-data_start就等于数据段的长度,LOADADDR(.data)表示data段在bin文件中的地址,也就是0x800.
然后我们修改start.S如下:
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
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 = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
/* 重定位data段 */
ldr r1, =data_load_addr /* data段在bin文件中的地址, 加载地址 */
ldr r2, =data_start /* data段在重定位地址, 运行时的地址 */
ldr r3, =data_end /* data段结束地址 */
cpy:
ldrb r4, [r1]
strb r4, [r2]
add r1, r1, #1
add r2, r2, #1
cmp r2, r3
bne cpy
bl main
halt:
b halt
我们上一节用到了链接脚本,现在我们来解析下前面用到的连接脚本。
首先通过文档 http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html,我们知道链接脚本的格式是这样的:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
在链接脚本格式中,BLOCK(align)很少用到,NOLOAD也很少用到,region:phdr=fill也很少用到。里面的secname名字我们可以随便写,也可以不叫.text .rodata,叫什么名字都可以,start表示运行时的起始地址runtime address,也叫重定位地址relocate address,AT可写也可以不写,表示加载地址,LOADaddress不写时,就等于runtime address.
然后就是contents的格式,contents格式有下面3种:
1:start.o:你可以 指定start.o,把整个start.o放到这个段里面。
2:*(.text):指定所有.o的text段放到这里面。
3:start.o
*(.text):指定start.o放到前面,然后剩下所有文件的text段放到
elf文件地址问题:
裸板bin文件:
?下面看一下我们前面用到的链接脚本:?
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800)
{
data_load_addr = LOADADDR(.data);
data_start = . ;
*(.data)
data_end = . ;
}
.bss : { *(.bss) *(.COMMON) }
}
第一行没有写加载地址,那么我们的text的加载地址就等于运行地址,也就是0.不要进行重定位。另外这里text是保存的是所有文件的text段,这里的所有文件的顺序就是按照makefile里面写的先后顺序arm-linux-ld -Ttext 0 -Tdata 0x30000000 ?start.o led.o uart.o init.o main.o -o sdram.elf,然后看data段,这里加载地址是0x800,然后运行地址是0x30000000.那么我们程序启动之后需要把数据段从0x800拷贝到0x30000000,这就是重定位。另外,在elf或者bin文件中都不会存bss段,因为bss中是初始值为零的或者没有初始化的全局变量,假如我们有很多初始化为零的全局变脸,那么我们保存这么多零浪费空间。
我们通过编写程序,将初始化为零的全局变量打印出来,发现它并不是零,这是因为我们没有把他清零,我们需要写程序 将BSS段对应的内存清零, 要想清零,需要知道BSS空间的地址是什么,首先要修改链接脚本,
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800)
{
data_load_addr = LOADADDR(.data);
data_start = . ;
*(.data)
data_end = . ;
}
bss_start = .;
.bss : { *(.bss) *(.COMMON) }
bss_end = .;
}
然后修改start.S清除BSS段。
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
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 = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
/* 重定位data段 */
ldr r1, =data_load_addr /* data段在bin文件中的地址, 加载地址 */
ldr r2, =data_start /* data段在重定位地址, 运行时的地址 */
ldr r3, =data_end /* data段结束地址 */
cpy:
ldrb r4, [r1]
strb r4, [r2]
add r1, r1, #1
add r2, r2, #1
cmp r2, r3
bne cpy
/* 清除BSS段 */
ldr r1, =bss_start
ldr r2, =bss_end
mov r3, #0
clean:
strb r3, [r1]
add r1, r1, #1
cmp r1, r2
bne clean
bl main
halt:
b halt
我们前面将数据从norflash复制到SDRAM的时候,我们用了如下的代码:
/* 重定位data段 */
ldr r1, =data_load_addr /* data段在bin文件中的地址, 加载地址 */
ldr r2, =data_start /* data段在重定位地址, 运行时的地址 */
ldr r3, =data_end /* data段结束地址 */
cpy:
ldrb r4, [r1]
strb r4, [r2]
add r1, r1, #1
add r2, r2, #1
cmp r2, r3
bne cpy
我们首先使用ldrb命令从flash中读取一个字节,然后把这个字节写到SDRAM中,我们的norflash是16位的,SDRAM是32位的,这种读取方式效率比较低。
? ? ? ? 我们前面用的ldrb和strb命令,每次只能操作一个字节,假设我们要拷贝16个字节,那我们要访问16次norflash,访问16次SDRAM,一共32次访问硬件的操作,这样就比较耗时。并且我们的SDRAM是32位的,那么读写SDRAM的时候,一次性操作只能以32位作为最小操作单位,如果CPU想读一个字节,它会把命令发给内存控制器,然后内存控制器从SDRAM中读到4个字节的数据,然后挑出CPU感兴趣的那个字节返回给它,如果CPU想写一个字节,那么cpu把地址和数据发给内存控制器,然后内存控制器把32位的数据发给SDRAM,同时内存控制器还会发出数据屏蔽信息号DQM,屏蔽掉不需要写的其他三个字节,最终只会写一个字节,这种效率太低了。
? ? ? ? 我们使用ldr和str命令进行改进。这两个命令以4个字节为单位进行操作,如果我们要拷贝16个字节,那么我们只需要执行4次ldr和4次str就可以了,但是由于norflash是16位的,我们要访问8次norflash。
我们修改我们的start.S
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
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 = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
/* 重定位data段 */
ldr r1, =data_load_addr /* data段在bin文件中的地址, 加载地址 */
ldr r2, =data_start /* data段在重定位地址, 运行时的地址 */
ldr r3, =data_end /* data段结束地址 */
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, =bss_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
ble clean
bl main
halt:
b halt
修改完之后发现我们的代码不仅把BSS清零了,还把我们的初始化的全局变量也给清零了。我们看一下我们的反汇编:
所以我们要修改我们的链接脚本,让我们的BSS以4字节取整,下面的脚本中同样对data段增加了向4对齐。
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800)
{
data_load_addr = LOADADDR(.data);
. = ALIGN(4);
data_start = . ;
*(.data)
data_end = . ;
}
. = ALIGN(4);
bss_start = .;
.bss : { *(.bss) *(.COMMON) }
bss_end = .;
}
在前面我们讲了重定位的两种方案,方案一是只把变量重定位到SDRAM,下面我们看一下重定位方案二,怎么把整个程序都重定位到SDRAM。
首先来修改链接脚本,链接脚本可以参考uboot,uboot是裸机程序的集大成者,我们这里参考boot_projects/u-boot-1.1.6_jz2440/u-boot-1.1.6/board/smdk2410里面的u-boot.lds文件,然后进行修改,得到我们自己的链接脚本。
SECTIONS
{
. = 0x30000000;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
_end = .;
}
然后修改我们的start.S,以前我们只是重定位了data段,现在我们把所有的段都进行重定位。
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
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 = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
mov r1, #0
ldr r2, =_start /* 第1条指令运行时的地址,也就是最上面_start的地址,也就是0x30000000 */
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 /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
下面我们结合反汇编文件来分析一下程序的启动过程:
我们的bin文件中,一开始是代码段,然后是只读数据段,然后是数据段,它的运行地址是0x30000000,我们的bin文件烧写到norflash上面,上电后,前半部分的代码要把整个程序复制到SDRAM,实现重定位整个程序,我们的start.S实现了copy和clean,下面我们看一下反汇编。
我们再反汇编代码看到了一个bl 30000478<sdram_init>,注意这一条不是跳到0x30000478,我们这时候SDRAM都还没有初始化,这个地址里面都还没有指令,我们不可能跳到这个地址的,我们做个实验,重新修改下链接脚本,将运行地址由0X30000000修改0x32000000,然后发现反汇编文件里面变成了bl 32000478<sdram_init>,但是我们发现bl 30000478<sdram_init>和bl 32000478<sdram_init>这两个对应的机器码都是eb000105,完全一致,如果是跳转命令,那么跳转到不一样的地址机器码是不可能一样的,所以这里并不是跳转命令,而是跳转到当前某个PC值加上offset。这个offset是由连接器帮你算出来的,具体跳转到那里由当前PC值决定,也就是当前指令所在位置决定,假设程序从0x30000000执行,那么从反汇编文件可以看出,当前的bl指令地址是0x3000005c,然后跳转到0x30000478,如果程序从0运行,那么当前的bl指令地址是0x5c,然后跳转到0x04798.如果是从0x32000000开始运行,那么bl指令地址是0x3200005c,然后跳转到0x32000478.所以这里的bl命令并不是跳转到这个地址,具体跳转到那里是取决于当前PC的位置。在反汇编里面写出这个值只是为了方便你去分析代码而已。总结:在反汇编文件里面,B/BL某个值,只是起一个方便查看的作用,并不是真正跳转到这个地址。从前面的分析也可以看到,虽然我们的程序指定运行地址是0x30000000,但是我们把它放到0地址也仍然可以运行,因为跳转的时候使用的是偏移地址,仍然可以跳到正确的代码位置,跟链接地址没有关系。所以以后我们想写出跟链接地址没有关系的代码时,跳转命令必须用B命令或者BL命令,用偏移地址,不能使用绝对地址。
我们继续往下看start.S,我们发现有一个bl main,main函数也是使用相对跳转命令, 然后main函数在0X5C4的地方,然后由于bl是相对跳转,所以这里跳转之后仍然是处于norflash上面,并没有跳到SDRAM上面,前面的代码我们把代码进行了重定位,我们这里应该是跳转到0X300005C4,但是我们使用bl命令时,仍然跳到了0X5C4,所以我们必须使用绝对地址使程序跳转到SDRAM.
下面我们再举一个例子,我们把sdram_init修改为sdram_init2,代码如下:
#include "s3c2440_soc.h"
void sdram_init(void)
{
BWSCON = 0x22000000;
BANKCON6 = 0x18001;
BANKCON7 = 0x18001;
REFRESH = 0x8404f5;
BANKSIZE = 0xb1;
MRSRB6 = 0x20;
MRSRB7 = 0x20;
}
#if 0
/**************************************************************************
* 设置控制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
}
#endif
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++;
}
}
int sdram_test(void)
{
volatile unsigned char *p = (volatile unsigned char *)0x30000000;
int i;
// write sdram
for (i = 0; i < 1000; i++)
p[i] = 0x55;
// read sdram
for (i = 0; i < 1000; i++)
if (p[i] != 0x55)
return -1;
return 0;
}
把程序下载进去之后发现没有任何打印,这是因为sdram_init2不是位置无关的,我们看一下反汇编,找到sdram_init2这个函数的反汇编码,
30000508 <sdram_init2>:
30000508: e1a0c00d mov ip, sp
3000050c: e92dd800 stmdb sp!, {fp, ip, lr, pc}
30000510: e24cb004 sub fp, ip, #4 ; 0x4
30000514: e24dd03c sub sp, sp, #60 ; 0x3c
30000518: e59f3088 ldr r3, [pc, #136] ; 300005a8 <.text+0x5a8>
从地址300005a8取一个值给r3,这个命令依赖于PC当前值,所以虽然用了ldr,
但是仍然是位置无关的,300005a8这里的值是30000708,那么r3=30000708 .
3000051c: e24be040 sub lr, fp, #64 ; 0x40
30000520: e1a0c003 mov ip, r3
ip=r3=30000708
30000524: e8bc000f ldmia ip!, {r0, r1, r2, r3}
去ip=30000708这个地方读内存,把里面的值分别存放到r0,r1,r2,r3.
30000708就是SDRAM里面的数据,这时候SDRAM都还没有初始化,你就去
读数据肯定出错,
30000528: e8ae000f stmia lr!, {r0, r1, r2, r3}
我们看一下30000708是什么东西。
这个数组的初始值存放在只读数据段,也就是放在了30000708这个位置,这些初始值保存在只读数据段里面,我们要使用这些初始值来初始化数组,这些初始值要用绝对地址来访问,所以我们要写位置无关码的话,这种数组也不能使用。
怎么写位置无关码(就是你的程序在任何地址都可以运行,不需要把它放到运行时地址):
使用位置无关码,不使用绝对地址,最根本的办法是看反汇编。