mini2440 链接脚本

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

你可能感兴趣的:(LDS,.ARM.exidx,.ARM.extab,bx)