您当前的位置: 首页 > 

韦东山

暂无认证

  • 0浏览

    0关注

    506博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

第013课 S3c2440代码重定位详解

韦东山 发布时间:2018-02-28 10:20:05 ,浏览量:0

原文地址: http://wiki.100ask.org

第001节段的概念重定位的引入

S3C2440的CPU可以直接给SDRAM发送命令、给Nor Flash发送命令、给4K的片上SDRAM发送命令,但是不能直接给Nand Flsh发送命令

假如把程序烧写到Nand Flsh上,即向Nand Flsh烧入* bin* 文件,CPU是无法从Nand Flsh中取代码执行的。

为什还可以使用NAND启动?

  • 上电后,Nand启动硬件会自动把Nand Flsh前4K复制到SRAM;
  • CPU从0地址运行SRAM;

如果我的程序大于4K怎么办? 前4K的代码需要把整个程序读出来放到SDRAM(即代码重定位)。

如果从Nor Flash启动,会出现什么问题?

将拨动开关拨到Nor Flash启动时,此时CPU认为的 0地址 在Nor Flash上面,片内内存SRAM的基地址就变成了0x40000000(Nand启动时片内内存SRAM的基地址基地址是0), 由于Nor Flash特性:可以像内存一样读,但不能像内存直接写,因此需要把全局变量和静态变量重定位 放到SDRAM里。 这里写图片描述

例如执行如下几条汇编指令

 MOV R0, #0
 LDR R1, [R0] @读有效
 STR R1, [R0] @写无效

当程序中含有需要写的全局变量或静态变量时,假如是在Nand Flash可以正常操作,如果是在Nor Flash,修改无效。因此我们需要把全局变量和静态变量重定位 放到SDRAM


#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;

int main(void)
{
    uart0_init();

    while (1)
    {
        putchar(g_Char); /*让g_Char输出*/
        g_Char++;         /* nor启动时, 此代码无效 */
        delay(1000000);
    }


    return 0;
}

编译运行查看是否有效果

查看sdram.dis文件 发现data数据段放在了0x00008474这个地址导致 程序太大

在makefile中加入这么一句话

 arm-linux-ld -Ttext 0 ** -Tdata 0x700 ** start.o led.o uart.o init.o main.o -o sdram.elf

16进制的700就是十进制的2048 这时我们的bin文件就变为2049

烧写程序:

烧写在NORFlash 和 烧写在NANDFlash观察这两种的效果。

设置成NANDFlash启动没有问题 显示ABCDE…

设置成NORFlash启动显示AAA…

对于NOR启动时g_Char++; /* nor启动时, 此代码无效 */

Disassembly of section .data:
00000700 :
 700:   Address 0x700 is out of bounds.  //数据段
Disassembly of section .rodata:
                            //放在只读数据段内
00000474 :         //const char g_Char2 = 'B';
 474:   Address 0x474 is out of bounds.

Disassembly of section .bss:    //bss段

00000804 :             //int g_A = 0;

 804:   00000000    andeq   r0, r0, r0

00000808 :             //int g_B;
 808:   00000000    andeq   r0, r0, r0
Disassembly of section .comment:

一个程序里面有

  • .text 代码段
  • .data 数据段
  • rodata 只读数据段(const全局变量)
  • bss段 (初始值为0,无初始值的全局变量)
  • commen 注释

其中bss段和commen 注释不保存在bin文件中。

第002节_链接脚本的引入与简单测试

前面程序运行,发现从Nand Flash启动和从Nor Flash启动表现是不一样的。

设置成Nand Flash启动没有问题 显示ABCDE…

设置成NOor Flash启动则显示AAA…

这是什么原因呢?

  • 假如现在是Nor启动: 这里写图片描述

Nor Flash就被认为是0地址,g_Char被放在0x700后面。CPU上电后从0地址开始执行,它能读取Nor Flash上的代码,打印出A,当进行g_Char++的时候,写操作操作无效,下次读取的数据仍然是A。

  • 假如现在是Nor启动: 这里写图片描述 上电后,Nand Flash前4K代码就被自动的复制到SRAM里面,SRAM是CPU认为的0地址。CPU上电后从0地址开始执行,它读取SRAM上的代码,并g_Char++修改变量,下次读取的数据就依次增加了。

为了解决Nor Flash里面的变量不能写的问题,我们把变量所在的数据段放在SDRAM里面,看行不行。 修改Makefile 指定数据段为0x30000000 -Tdata 0x30000000:

 arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf

这样的话编译出来的bin文件 从0地址 到 0x30000000地址 文件大小有700多MB,代码段和数据段直接有间隔,称之为黑洞 这里写图片描述

解决黑洞有两个办法:

  • 第一个方法
    • 把数据段的g_Char和代码段靠在一起;
    • 烧写在Nor Flash上面;
    • 运行时把g_char(全局变量)复制到SDRAM,即0x3000000位置(重定位);
  • 第二个方法
    • 让文件直接从0x30000000开始,全局变量在0x3……;
    • 烧写Nor Flash上 0地址处;
    • 运行会把整个代码段数据段(整个程序)从0地址复制到SDRAM的0x30000000(重定位);

这两个方法的区别是前者只重定位了数据段,后者重定位了数据段和代码段。

参考文档 [http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html Using LD, the GNU linker]

第一种办法如何实现 修改Makefile的代码段地址,使用链接脚本sdram.lds指定。

 #arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf
 arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf

链接脚本的语法:

SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
  { contents } >region :phdr =fill
...
}

我们需要依次排列 代码段、只读数据段、数据段、.bss段、.common。

其中数据段放在0x700,但运行时在0x3000000:

SECTIONS {
   .text   0  : { *(.text) }//所有文件的.text
   .rodata  : { *(.rodata) } //只读数据段
   .data 0x30000000 : AT(0x700) { *(.data) } //放在0x700,但运行时在0x3000000
   .bss  : { *(.bss) *(.COMMON) }//所有文件的bss段,所有文件的.COMMON段
}

重新编译后烧写bin文件,发现启动后显示乱码。原因是我们从0x30000000处获取g_Char,但在这之前,并没有在0x30000000处准备好数据。因此需要重定位数据段,将0x700的数据移动到0x30000000处,在start.S加入:

 bl sdram_init

 /* 重定位data段 */
 mov r1, #0x700 
 ldr r0, [r1]
 mov r1, #0x30000000
 str r0, [r1]

 bl main

上面的这种方法,只能复制0x700处的一位数据,不太通用,下面写一个更加通用的复制方法:

链接脚本修改如下:

SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x700) 
   { 
      data_load_addr = LOADADDR(.data);
      data_start = . ;//等于当前位置
      *(.data)  //等于数据段的大小
      data_end = . ;//等于当前位置
   }
   .bss  : { *(.bss) *(.COMMON) }
}

修改start.S

    bl sdram_init   

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

cpy:
    ldrb r4, [r1] //从r1读到r4
    strb r4, [r2] //r4存放到r2
    add r1, r1, #1 //r1+1
    add r2, r2, #1 //r2+1
    cmp r2, r3 //r2 r3比较
    bne cpy //如果不等则继续拷贝

    bl main
第003节_链接脚本的解析

链接脚本的语法

SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
  { contents } >region :phdr =fill
...
}

解释:

 secname  :段名
 start  :起始地址:运行时的地址(runtime addr);重定位地址(relocate addr)
 AT ( ldadr ) :可有可无(load addr:加载地址) 不写时LoadAddr = runtime addr
 { contents } 的内容: 
 start.o //内容为start.o文件
 *(.text)所有的代码段文件
 start.o *(.text)文件

elf文件格式

1 链接得到elf文件,含有地址信息(load addr)

2 使用加载器

:: 2.1 对于裸板是JTAG调试工具

:: 2.2 对于APP,加载器也是APP 把elf文件解析读入内存的加载地址

3 运行程序

4 如果loadaddr != runtimeaddr程序本身要重定位

核心程序运行时应该位于 runtimeaddr(reloate addr)或者链接地址

bin文件

1 elf生成bin文件 

2 硬件机制启动

3 如果bin文件所在位置 不等于runtimeaddr ,程序本身实现重定位

bin文件/elf文件都不保存bss段 这些都是初始值为0 或者没有初始化的全局变量

程序运行时把bss段对应的空间清零

做个实验,把全局变量g_A以16进制打印出来

/* 0xABCDEF12 */
void printHex(unsigned int val)
{
    int i;
    unsigned char arr[8];

    /* 先取出每一位的值 */
    for (i = 0; i < 8; i++)
    {
        arr[i] = val & 0xf;
        val >>= 4;   /* arr[0] = 2, arr[1] = 1, arr[2] = 0xF */
    }

    /* 打印 */
    puts("0x");
    for (i = 7; i >=0; i--)
    {
        if (arr[i] >= 0 && arr[i] = 0xA && arr[i]             
关注
打赏
1658827356
查看更多评论
0.0390s