段概念的引入

关于段的脚本语法编写可以参考:

 

参考资料:
Using LD, the GNU linker

 

关于重定位可以参考我之前的一篇随笔:

ld,连接器

简单通俗一点来说,就是把可执行代码放在内存的另一个地方去。

比如,我的bin文件烧写在nor flash上,在前2k存放代码段,紧接着存放.data段,此时我想把烧写在nor上的代码中.data段的数据赋值到到sdram0x30000000的位置进行操作,这个过程就叫做重定位,重定位之后,我们读写.data段的数据就在sdram中进行。

(1)位置无关编码:PIC,可执行程序运行时与代码在内存中的地址无关,代码中没有使用绝对地址,而是使用的相对地址。(例如:B、BL、MOV等指令)
(2)位置有关编码:可执行程序运行时与代码在内存中的地址有关系。(例如:LDR PC, =MAIN等指令)

1、运行地址<--->链接地址:他们两个是等价的,只是两种不同的说法。

2、加载地址<--->存储地址:他们两个是等价的,也是两种不同的说法。

运行地址:也叫链接地址,是程序定位的绝对地址,即在编译连接时确定的地址。如果程序中有位置相关指令,程序在运行时,程序必须在运行地址上

加载地址:程序放置的位置。

 运行地址和加载地址的值有时相等,有时却不相等,所以这给初学者带来很大的困扰。这也涉及到位置有关和无关码。

说了那么多概念文字类的东西,肯定很空洞没有真实体会感,so,

Talk is cheap. Show me the code

现在,我们开始编写连接脚本,对于熟悉理解段和运行或者加载地址很有帮助。

在上一个实验中,我们把数据段连接在0x800的地址处。而现在,我们要实现重定位,简单地在Makefile中指定连接地址已经不能满足要求了,需要编写连接脚本。在之前的nor启动代码,我们对变量执行++操作不起作用,所以,现在我们采取把nor flash上的data段重定位到sdram上,这样就可实现在nor启动的时候,全局变量执行写操作也可以得到正确的响应。

连接脚本如下:

 

SECTIONS
{ 
    .text 0: {*(.text)}
    .rodata : {*(.rodata)}
    .data 0x30000000 :AT(0x800)
    {
        data_load_add = LOADADDR(.data);    
        data_start = . ;
        *(.data)
        data_end = . ;
    }
    .bss :{*(.bss) *(COMMEN)}
}

 

注意一点,=号左右空出一个空格可以让你正确执行上面的脚本,第一次实验的时候,我没有再=号后面加上一个空格,导致连接器无法识别我的段内符号。

由于连接脚本的符号支持各种字符,你不使用空格分离,ld不知道如何解释,所以,请你在书写脚本的时候注意空格分割你的符号。

再者,细心的你可能会问,那脚本中什么时候末尾加分号,什么时候又不加呢?

 

译:

您可以创建全局符号,并使用任何C赋值运算符将值(地址)分配给全局符号:

symbol = expression ;
symbol &= expression ;
symbol += expression ;
symbol -= expression ;
symbol *= expression ;
symbol /= expression ;

ld 表达式中,有两点区别于其他运算符。

  • 赋值只能在表达式的根部使用; `A = B + 3;” 是允许的,但是 `A + B = 3;” 是一个错误。
  • 您必须;在赋值语句的末尾放置一个尾随分号(“; ”)。

可能会出现赋值语句:

  • 作为ld脚本自身的命令; 
  • 作为SECTIONS命令内的独立陈述; 
  • 作为SECTIONS命令中部分定义内容的一部分 

通用符号(common symbol)的输入section:
在许多目标文件格式中,通用符号并没有占用一个section。连接器认为:输入文件的所有通用符号在名为COMMON的section内。
例子,
.bss { *(.bss) *(COMMON) }
这个例子中将所有输入文件的所有通用符号放入输出.bss section内。可以看到COMMOM section的使用方法跟其他section的使用方法是一样的。
有些目标文件格式把通用符号分成几类。例如,在MIPS elf目标文件格式中,把通用符号分成standard common symbols(标准通用符号)和small common symbols(微通用符号,不知道这么译对不对?),此时连接器认为所有standard common symbols在COMMON section内,而small common symbols在.scommon section内。
在一些以前的连接脚本内可以看见[COMMON],相当于*(COMMON),不建议继续使用这种陈旧的方式。

通配符在连接脚本中是支持的:

In any place where you may use a specific file or section name, you may also use a wildcard pattern. The linker handles wildcards much as the Unix shell does. A `*' character matches any number of characters. A `?' character matches any single character. The sequence `[chars]' will match a single instance of any of the chars; the `-' character may be used to specify a range of characters, as in `[a-z]' to match any lower case letter. A `\' character may be used to quote the following character.

在启动文件中需要增加sdram初始化和重定位代码:

    bl sdram_init    

    /* 重定位data段 */
    ldr r1, =data_load_add  /* data段在bin文件中的地址, 加载地址 */
    ldr r2, =data_start      /* data段在重定位地址, 运行时的地址 */
    ldr r3, =data_end          /* data段结束地址 */

cpy:
    ldrb r4, [r1]
    strb r4, [r2]
    add r1, r1, #1
    add r2, r2, #1
    cmp r2, r3
    bne cpy
    bl main

上面重定位汇编,是把加载地址也就是存储地址的代码拷贝到运行地址也是连接地址(也是重定位地址)sdram上。

main函数:

#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"

char g_Char = 'A';
const char g_Char2 = 'B';
int g_A = 0;
int g_B;

void delay(volatile int d)
{
    while (d--);
}

int main(void)
{
    uart0_init();

    while (1)
    {
        putchar(g_Char);
        g_Char++;         /*没有重定位时, nor启动, 此代码无效 */
        delay(1000000);
    }

    
    return 0;
}

Makefile:

.PHONY:clean
objcts :=start.o main.o init.o uart.o 
lds:=sdram.lds
sdram.bin:$(objcts)
    arm-linux-ld  -T $(lds) $^ -o sdram.elf
    arm-linux-objcopy -O binary -S sdram.elf $@
    arm-linux-objdump -S -D sdram.elf > sdram.dis
%.o:%.c
    arm-linux-gcc -o $@ -c -g $<
%.o:%.S
    arm-linux-gcc -o $@ -c -g $<
clean:
    -rm *.bin *.o *.elf *.dis

注意上面红色部分,已经变成连接脚本了而不是单纯的简单指定。

这个程序,烧写在nand和nor上都可以正常运行了。

当然,这需要连接脚本的基础,所以还是建议先看看那篇gnu ld的文章。

 

posted @ 2017-11-26 12:25  Crystal_Guang  阅读(416)  评论(0编辑  收藏  举报