1、由于在nor flash上启动,无法使用全局变量,因此希望能使用全局变量,所以需要重定位
2、重定位方法有两种,一种是只把变量重定位到sdram,另一种是把整个程序重定位到sdram
******************************
1、对于第一种,需要做的工作如下:
①在链接脚本中声名,需要将rodata段放到某个地址,但是如果只是这样写一下地址,那么中间的空间将会被占用导致文件过大。
②因此,需要达到这样的目的,既能告诉程序,rodata段在sdram,又能减小 文件的最终大小。需要采用如下格式:
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800) { *(.data) }
.bss : { *(.bss) *(.COMMON) }
}
③上面只是一个声明,程序虽然知道应该从0x30000000读数据,但是我们并没有将数据放到这儿,因此读出来的必然是乱码。所以还需要把数据手动的放到这里。——当然,在这之前,应该先初始化sdram。
/* 重定位data段 */
mov r1, #0x800
ldr r0, [r1]
mov r1, #0x30000000
str r0, [r1]
关键在于,我们怎么知道的全局变量保存到了0x800(上面是先反汇编然后知道的)?想要重定位的地址,是我们知道的,或者说知道重定位开始的地址。
①为了能够自动辨别拷贝的只读数据段的长度,所以在链接脚本中,应该添加一些变量——或者标签,使得我们可以引用这些地址。
因此,上面的链接脚本应该修改,使其更加智能
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800)
{
data_load_addr = LOADADDR(.data);
data_start = . ;
*(.data)
data_end = . ;
}
.bss : { *(.bss) *(.COMMON) }
}
②为了使得能自动初始化bss段,所以还需要修改——即获取开始和结束的运行地址。
bss_start = .;
.bss : { *(.bss) *(.COMMON) }
bss_end = .;
③但是仍然是问题,0x800应该怎么处理?猜测后面肯定要用到上一个段结束的地址。——并且需要对齐。
结果定义一个变量,等于当前地址即可。
链接脚本的解析
SECTIONS {
.text 0 : { *(.text) }
.data 0x30000000 : AT(0x800) //.text是段的名字,0是运行时地址,AT(0x800)是加载地址,
//如果没有此项,则默认和运行时地址相同
//后面的括号中,可以指定存放的内容
{
data_load_addr = LOADADDR(.data);
data_start = . ;
*(.data)
data_end = . ;
}
.bss : { *(.bss) *(.COMMON) }
}
①链接得到elf格式的文件,该文件中含有地址信息——加载地址。
②使用加载器将该文件加载到内存——对于裸板为jtag调试工具,对于应用程序,则加载器也是应用程序。没懂
加载器对elf格式的文件进行解析,读入到内存——读到加载地址上
③如果运行时地址,和加载地址不同,则程序本身需要重定位,将程序重定位到运行地址。
其实,上面三点和之前的两点相同——先声明运行地址,再真正的去将程序搬运到运行地址
④需要注意的是,由于data段已经声明了放到0x30000000,所以其后的bss段也会跟着放到0x3000000后,data段结束的地址。
链接脚本和重定位代码的改进
①拷贝代码的改进,2440外接16位的nor flash和32位的sdram。所以拷贝的时候,如果按照单字节搬运,那么效率不高。
//旧代码使用ldrb和strb,按照1字节进行拷贝
cpy:
ldrb r4, [r1]
strb r4, [r2]
add r1, r1, #1
add r2, r2, #1
cmp r2, r3
bne cpy
//新代码可以使用ldr和str,按照字长4字节进行拷贝
cpy:
ldr r4, [r1]
str r4, [r2]
add r1, r1, #4
add r2, r2, #4
cmp r2, r3
ble cpy
但是发现结果却和预期不同,经过检查是因为bss段清除时,也把数据段给清楚了。
原因是,链接时地址未对齐,所以就会出错。
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 = .;
}
位置无关码
如果是和之前的做法一样,只拷贝数据段,那么就无所谓位置无关不无关,但是如果程序很大,代码段都放不下,那么就需要将整个程序重定位到运行时地址即链接地址,查看uboot中的代码,发现,程序是从0x30000000开始的,那么一上电的时候,为什么就可以从0运行呢?
原因是重定位之前的代码用位置无关码写成
如果拷贝整个程序,就需要从第一条指令运行时的地址开始拷贝,一直到bss段的开始
所以,在汇编文件中,就和地址解耦了,地址只需要在链接文件中指定。
需要注意的是,如果重定位完毕,就不应再使用相对跳转b/bl指令,否则程序将会再次返回,应该使用绝对跳转指令。
分析反汇编时发现,在sdram_init之前,就会使用sdram的地址,所以是使用的偏移。
那么怎么去分析跳转时使用的偏移地址还是绝对地址呢?
我们可以通过代码的编写,来保证是相对跳转,即位置无关码
①使用相对跳转指令b/bl
②不能访问全局变量和静态变量,也不能访问有初始值的数组
如果重定位完毕后,要使用绝对跳转,即ldr pc,=main
用c语言实现重定位和清楚bss段
数据搬运需要知道源,目的,长度
这里就涉及到了汇编如何向c函数传递参数。
①对于arm32,参数由r0到r3共四个通用寄存器来传递,所以需要把上面三个参数依次放入r0到r2三个寄存器,然后在c函数中用形参去接收。
②如果不通过寄存器来进行参数传递,而是调用汇编(链接脚本)中的变量,那么应该如何进行操作?
首先,需要声明变量为外部变量,然后再用一个指针去接收变量的地址。
根据操作,可以认为,汇编中的变量也只是一个普通变量,使用指针获取其地址后,就可以使用*操作符去获取值,也就是说,只要声明为外部变量就可以直接使用。
extern int __code_start, __bss_start;
volatile unsigned int * dest = (volatile unsigned int *)&__code_start;
符号表:
对于c语言的函数:name和addr
对于汇编/链接文件:name和addr