位置无关编码:汇编源文件被编码成二进制的可执行程序时,编码方式与位置(内存地址)无关。
位置有关编码:汇编源文件被编码成二进制的可执行程序时,编码方式与位置(内存地址)有关。
在设计程序时,会给程序指定一个运行地址。在编译程序时,其实知道被运行时的地址,而且必须给编译器连接器指定这个地址(链接地址),这就是与运行地址有关。但是有的特别的指令可以与运行地址无光,也就是这个指令运行时不管放到哪都能运行。而大部分的代码都是位置有关的。
对于位置有关代码来说:最终执行时的运行地址必须与编译链接时给定的链接地址一致。否则不能运行。
在MakeFile中用-Ttext 0X80100000来指定地址。
链接地址:链接时指定的地址。
运行地址:程序实际运行的地址。
重定位
原因:链接地址和运行地址有时候必须不相同,而且还不能全部使用位置无关码,这时候就要用重定位。
有的时候需要用iRAM,但是因为IRAM的空间又很小,所以只能把代码的部分重定位到iROM上。
1.链接地址:链接时指定的地址,也是我们要指定程序最后的运行位置。很明显,我们利用imxdownload将程序烧录到SD卡中,而最终无论是在IROM还是在DDR中运行,加载地址(存储地址)和运行地址都是不同的。
所以上电后,BootRom会自动对我们的程序进行重定位,而烧录imx文件的头部信息,是包含初始化ddr、地址信息的。就是将程序拷贝到对应的内存位置。
简单一点的链接,就是直接在连接时直接指定连接地址。
arm-buildroot-linux-gnueabihf-ld -Ttext 0X80100000 led.o -o led.elf
这样程序最后的运行地址就是连续的,从汇编文件的start开始,往后一次连续。
2.为了提高程序或者其他目的时,使得部分代码或数据需要放到别的位置,就需要对在拷贝到内存的部分代码或数据再进行重定位。
我们可以通过编写连接脚本来实现更复杂的项目。比如上图,我们需要对data段进行从定位。
0X00900000~0X0091FFFF是i.MX6ULL的IROM(internal ROM),这段内存是CPU的内部内存,所以运行速度要比DDR快得多,所以将部分代码放到这一段来运行会比在DDR上快。
需要做如下操作:
(1)编写.lds连接脚本
SECTIONS {
. = 0x80100000;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
/*对data段进行重定位,将.data 段的运行地址(runtime address)
设定为 0x900000。加载地址由变量 data_load_addr 确定。这样设置后,
在.bin 文件中“ .data”段仍旧存储在“.rodata”段之后。但在程序运行时,
CPU 会从 0x900000 开始的空间内读取“.data 段”的值。*/
data_load_addr = .;
.data 0x900000 : AT(data_load_addr)
{
data_start = . ;
*(.data)
data_end = . ;
}
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
__bss_end = .;
}
(2)搬运代码,因为第一次的重定位后,bootROM会自动帮我们将程序拷贝到要运行的地址上去,但是之后我们在进行重定,CPU只是知道了要在新的地址上去找变量或者指令,所以我们要进行手动搬运。
可以用在汇编里搬,也可以在用C程序搬。
汇编:
bl copy_data /*跳到copy_data并保存下一句到lr*/
copy_data:
/* 重定位data段 */
ldr r1, =data_load_addr /* data段的加载地址 (0x8010....) */
ldr r2, =data_start /* data段重定位地址, 0x900000 */
ldr r3, =data_end /* data段结束地址(重定位后地址 0x90....) */
cpy:
ldr r4, [r1] /* 从r1读到r4 */
str r4, [r2] /* r4存放到r2 */
add r1, r1, #4 /* r1+1 */
add r2, r2, #4 /* r2+1 */
cmp r2, r3 /* r2 r3比较 */
bne cpy /* 如果不等则继续拷贝 */
mov pc, lr /*搬运结束,利用lr回到原来位置的*/
C语言:
上面的搬运是用汇编里实现的,也可以在汇编里直接调用C语言函数,传入的参数,比如是3个参数,传入的参数就是r0,r1,r2。
void copy_data (volatile unsigned int *src, volatile unsigned int *dest, unsigned int len) /* src, dest, len */
{
unsigned int i = 0;
while (i < len)
{
*dest++ = *src++;
i += 4;
}
}
也可以直接调用链接脚本里的变量。
void copy_data (void)
{
/* 从链接脚本中获得参数 data_load_addr, data_start, data_end */
extern int data_load_addr, data_start, data_end;
volatile unsigned int *dest = (volatile unsigned int *)&data_start;
volatile unsigned int *end = (volatile unsigned int *)&data_end;
volatile unsigned int *src = (volatile unsigned int *)&data_load_addr;
/* 重定位数据 */
while (dest < end)
{
*dest++ = *src++;
}
}
(3)编写Makefile
arm-buildroot-linux-gnueabihf-ld -T imx6ull.lds -o init.elf $^