在 mini2440 使用sdram 中和之前一样简单的使用 -T0x30000000 控制链接生成的ELF文件中 .text 段加载位置。在稍复杂的项目中常会涉及哪个对象文件中的哪个section是否出现在最终文件中,如果出现,其加载位置和在elf中的存储位置分别在何处,是否要有对齐限制等。这些如果都以参数传给 ld 会累垮程序猿,于是链接脚本出现了。这里不准备详细描述其规则,仅举一例:网上常有人问《嵌入式Linux应用开发完全手册》第7章的MMU例子编译时出现
ordered `.ARM.exidx' and unordered `.ARM.extab'
类似字样怎么解,提到的解法大致有换用低版本编译器、使用 -nostdlib 参数。
.ARM.exidx 和 .ARM.extab 这两个段是在编译 c++ 时出现的,而且看起来只有 4.1 以上版本的 arm-linux-gcc 编译器才会生成。这可以用 arm-linux-readelf -S 来验证一下。在用配套的 arm-linux-ld 链接时是不允许把 .ARM.exidx 和 .ARM.extab 放在同一个段里的。如果项目文件有head.s 和 sdram.cpp:
.text
.global _start
_start:
bl kill_dog
bl control_mem
bl copy2sdram
ldr pc, =sdram
sdram:
mov sp, #0x34000000
ldr r4, =main
mov lr, pc
bx r4
_end:
b _end
kill_dog:
mov r0, #0x53000000
mov r1, #0
str r1, [r0]
mov pc, lr
control_mem:
mov r0, #0x48000000
ldr r1, =0x22111112
str r1, [r0], #4
mov r1, #0x00000700
str r1, [r0], #4
mov r1, #0x00000700
str r1, [r0], #4
mov r1, #0x00000700
str r1, [r0], #4
mov r1, #0x00000700
str r1, [r0], #4
mov r1, #0x00000700
str r1, [r0], #4
mov r1, #0x00000700
str r1, [r0], #4
ldr r1, =0x00018009
str r1, [r0], #4
ldr r1, =0x00018009
str r1, [r0], #4
ldr r1, =0x008e04eb
str r1, [r0], #4
mov r1, #0x000000b2
str r1, [r0], #4
mov r1, #0x00000030
str r1, [r0], #4
mov r1, #0x00000030
str r1, [r0], #4
mov r1, #0x00000000
str r1, [r0], #4
mov r1, #0x00000000
str r1, [r0], #4
mov pc, lr
copy2sdram:
mov r0, #0x400
mov r1, #0x30000000
mov r2, #0x1000
loop:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r0, r2
bne loop
mov pc, lr
class CNumberedMusicalNotation
{
public:
CNumberedMusicalNotation( void );
~CNumberedMusicalNotation( void );
void dao( void );
void rai( void );
void mi( void );
void fa( void );
void suo( void );
void la( void );
void xi( void );
private:
void latency( void );
unsigned long* data;
};
CNumberedMusicalNotation::CNumberedMusicalNotation()
{
unsigned long* gpbcon = reinterpret_cast<unsigned long*>(0x56000010);
*gpbcon = 0x15400; // enable GPB output
data = reinterpret_cast<unsigned long*>(0x56000014);
*data = ~0;
}
CNumberedMusicalNotation::~CNumberedMusicalNotation()
{
*data = ~0;
latency();
}
void CNumberedMusicalNotation::dao()
{ // led1 - GPB5
*data = ~(1<<5);
latency();
}
void CNumberedMusicalNotation::rai()
{ // led2 - GPB6
*data = ~(1<<6);
latency();
}
void CNumberedMusicalNotation::mi()
{ // led3 - GPB7
*data = ~(1<<7);
latency();
}
void CNumberedMusicalNotation::fa()
{ // led4 - GPB8
*data = ~(1<<8);
latency();
}
void CNumberedMusicalNotation::suo()
{ // led1+3 - GPB5+7
*data = ~(1<<5 | 1<<7);
latency();
}
void CNumberedMusicalNotation::la()
{ // led2+4 - GPB6+8
*data = ~(1<<6 | 1<<8);
latency();
}
void CNumberedMusicalNotation::xi()
{ // led1+4 - GPB5+8
*data = ~(1<<5 | 1<<8);
latency();
}
void CNumberedMusicalNotation::latency()
{
volatile int i;
for ( i = 0; i < 10000; i++ );
}
int __attribute__((__long_call__)) main()
{
CNumberedMusicalNotation n;
n.dao();
n.rai();
n.mi();
n.suo();
n.suo();
n.la();
n.suo();
n.mi();
n.dao();
n.rai();
n.mi();
n.mi();
n.rai();
n.dao();
n.rai();
n.dao();
n.rai();
n.mi();
n.suo();
n.suo();
n.la();
n.suo();
n.mi();
n.dao();
n.rai();
n.mi();
n.mi();
n.rai();
n.rai();
n.dao();
return 0;
}
注意在 head.s 里要求
copy2sdram:
mov r0, #0x400
mov r1, #0x30000000
mov r2, #0x1000
这是对nand flash启动设置的,要求拷贝范围是片内的0x400(1024)到0x1000(4096),目标是0x30000000。这要求1024~4096中包含sdram.cpp 所有代码。而加载位置在 gdb 作 load 时就确定了,因此要求在链接脚本里把存储位置确定好,存储位置到文件头要等于片内加载位置到片ram基址。
Makefile
all: clean sdram.elf
sdram.elf :
arm-linux-gcc -c -O2 -o head.o head.s
arm-linux-g++ -c -O2 -o sdram.o sdram.cpp
arm-linux-ld -Tsdram.lds -o sdram.elf head.o sdram.o
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D -m arm sdram.elf > sdram.dis
clean:
rm -f *.o *.elf *.dis *.bin
链接脚本sdram.lds
ENTRY(_start)
SECTIONS {
. = 0x00000000;
loader : { head.o }
. = 0x30000000;
.ARM.extab ALIGN(4) : AT(1024) { sdram.o(.ARM.extab*) }
.ARM.exidx ALIGN(4) : AT(1024) { sdram.o(.ARM.exidx*) }
runner ALIGN(4) : AT(1128) { sdram.o }
}
用ENTRY确定程序入口,用 . = 确定当前虚拟内存(lma)位置,用 AT 定位存储位置,用ALIGN保证32位对齐。
脚本说明,程序入口为_start 标签处起始加载地址为0,head.o 中所有段将被放入一个名为 loader 的段中,从0处计算相对位置。
之后改变加载地址为0x30000000,把.ARM.extab 和 .ARM.exidx 从 sdram.o 中分离出来(该版本 arm-linux-ld的要求),然后把 sdram.o 中其他段并入一个叫 runner 的段中,这 3 个段将从 0x30000000 计算相对位置。这里为何AT参数分别取 1024 1024 和 1128 是之前用 arm-linux-readelf -S sdram.o 得到的:.ARM.extab 长度为0,.ARM.exidx长度为104,要把sdram.o中代码存到文件 1024 开始出故有上述布局。
可以试着把 sdram.lds 中的 AT 去掉,看看你的 sdram.bin 会有多大。
这里还有一个要注意的,和mini2440 使用sdram中跳到main的方式不同,那时是所有段加载位置在一起,跳转距离小于32M(thumb模式下只有4M),用一句 bl main 就能搞定。现在从bank0 调到 bank6,距离有6*128M,要用 bx 来实现,语句多了几条:
ldr r4, =main
mov lr, pc
bx r4
先用能长距离加载 ldr 把地址读入寄存器,再准备好 lr,最后才是 bx 跳转。
总之,无须换用低版编译器也不用 -nostdlib 参数,学习下链接脚本吧,它能给你的编译带来很专业的体验。