在 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 参数,学习下链接脚本吧,它能给你的编译带来很专业的体验。