历史上的今天
返回首页

历史上的今天

今天是:2025年03月23日(星期日)

正在发生

2020年03月23日 | 第013课 S3c2440代码重定位详解

2020-03-23 来源:eefocus

第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 <__data_start>:

 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] <= 9)

            putchar(arr[i] + '0');

        else if(arr[i] >= 0xA && arr[i] <= 0xF)

            putchar(arr[i] - 0xA + 'A');

    }

}


//打印初始值为0的变量

int g_A = 0;

int g_B;


int main(void)

{

    uart0_init();


    puts("nrg_A = ");

    printHex(g_A);

    puts("nr");


上述代码,没有清理bss段 g_A等于莫名奇妙的值 并不等于0 所以需要清理bss段


修改lds链接文件


SECTIONS {

   .text   0  : { *(.text) }

   .rodata  : { *(.rodata) }

   .data 0x30000000 : AT(0x700) 

   { 

      data_load_addr = LOADADDR(.data);

      data_start = . ;

      *(.data) 

      data_end = . ;

   }


   bss_start = .; //bss开始地址是当前位置

   .bss  : { *(.bss) *(.COMMON) }

   bss_end = .; //bss结束地址也是当前位置

}


修改start.s,清除bss段


/* 清除BSS段 */

ldr r1, =bss_start

ldr r2, =bss_end

mov r3, #0

clean:

    strb r3, [r1]

    add r1, r1, #1

    cmp r1, r2

    bne clean


    bl main

halt:


现在的代码全局变量就是为0,通过几行代码,就可以少几十个甚至上千个全局变量的存储空间。


第004节_拷贝代码和链接脚本的改进

本节进行拷贝代码的改进和链接脚本的改进。 

前面重定位时,需要ldrb命令从的Nor Flash读取1字节数据,再用strb命令将1字节数据写到SDRAM里面。


cpy:

    ldrb r4, [r1] /*首先从flash读出一个字节*/ 

    strb r4, [r2] /*让后把数据写到SDRAM*/

    add r1, r1, #1

    add r2, r2, #1

    cmp r2, r3

    bne cpy


JZ2440上的Nor Flash是16位,SDRAM是32位。 

假设现在需要复制16byte数据, 

采用ldrb命令每次只能加载1byte,因此CPU需要发出16次命令,内存控制器每次收到命令后,访问硬件Nor Flash,因此需要访问硬件16次; 

同理,访问SDRAM时,CPU需要执行strb 16次,内存控制器每次收到命令后,访问硬件SDRAM,也要16次,这样总共访问32次。


现在对其进行改进,使用ldr从Nor Flash中读,ldr命令每次加载4字节数据,因此CPU只需执行4次,但由于Nor Flash是16位的,内存控制器每次收到CPU命令后,需要拆分成两次访问,因此需要访问硬件8次; 

使用str写SDRAM,CPU只需执行4次,内存控制器每次收到命令后,直接硬件访问32位的SDRAM,因此这里只需要4次,这样总共访问只需要12次。 


在整个操作中,花费时间最长的就是硬件访问,改进后代码,减少了硬件访问的次数,极大的提高了效率。

这里写图片描述

根据上面原理修改代码,修改start.S:


cpy:

    ldr r4, [r1]

    str r4, [r2]

    add r1, r1, #4 //r1加4

    add r2, r2, #4 //r2加4

    cmp r2, r3 //如果r2 =< r3继续拷贝

    ble cpy


/* 清除BSS段 */ 

    ldr r1, =bss_start

    ldr r2, =bss_end

    mov r3, #0

clean:

    str r3, [r1]

    add r1, r1, #4

    cmp r1, r2 //如果r1 =< r2则继续拷贝

    ble clean


    bl main


然后编译烧写,发现启动后没有输出字符。修改主程序,尝试以整数格式输出字符,发现输出的数从0开始,应该是全局变量被破坏了。


屏蔽掉start.S里面的清理命令,测试是否是清除bss段是清除了全局变量。


clean:

    //str r3, [r1] //注释掉此句话,str不仅把bss段清除,把全局变量这些也清除了

    add r1, r1, #4

    cmp r1, r2

    ble clean


    bl main


屏蔽后,正常输出,锁定了问题大致位置。查看反汇编文件,原来是没有向4取整。 

修改链接脚本让bss段,使用ALIGN(4)向4取整。


SECTIONS {

   .text   0  : { *(.text) }

   .rodata  : { *(.rodata) }

   .data 0x30000000 : AT(0x700) 

   { 

      data_load_addr = LOADADDR(.data);

      . = ALIGN(4);

      data_start = . ;

      *(.data) 

      data_end = . ;

   }


   . = ALIGN(4);//让当前地址向4对齐

   bss_start = .;

   .bss  : { *(.bss) *(.COMMON) }

   bss_end = .;

}


现在重新编译烧写,测试结果正常。 


再次查看反汇编文件,发现现在bss段以4字节对齐,清理bss段也是正常的。


Disassembly of section .bss:


30000004 :

30000004:   00000000    andeq   r0, r0, r0

推荐阅读

史海拾趣

BLACK&DECKER公司的发展小趣事

BLACK&DECKER公司的历史可以追溯到1910年,由Alonzo G. Decker和Duncan S. Black在美国马里兰州巴尔的摩共同创立。两位创始人的初始投资来自于Black先生卖掉他的二手车所得的600美元,再加上1200美元的贷款。公司起初主要生产牛奶瓶装盖机、棉花采集机、糖果浸包机等工业用设备。然而,真正让BLACK&DECKER崭露头角的,是1916年他们发明的现代手枪钻原型。这一创新产品解决了当时德国电动工具笨重、难以操控的问题,为电动工具行业带来了革命性的变革。

Daito Communication Apparatus Co Ltd公司的发展小趣事

随着微电子学的快速发展,电子设备对元器件的要求也越来越高。Daito公司敏锐地捕捉到了这一市场趋势,决定对保险丝进行进一步的创新。在1995年,他们成功开发出了适用于电路板安装的高精度小型报警保险丝。这一产品的推出,不仅满足了市场对小型化、高精度保险丝的需求,也进一步巩固了Daito公司在电子行业中的领先地位。

随后,Daito公司又针对微电子学的进一步发展,推出了可以连续自动安装的方形微型保险丝。这一产品的推出,大大提高了生产效率,降低了成本,同时也为客户提供了更加便捷的使用体验。近年来,随着面安装技术的兴起,Daito公司又迅速推出了适用于面安装技术的片状保险丝,再次展示了他们在电子行业中的创新实力。

请注意,由于篇幅限制,以上仅为两个故事示例。如果需要更多故事,可以进一步深入研究和探索Daito公司在电子行业中的发展历程和创新实践。

EOS POWER INDIA Pvt公司的发展小趣事

随着市场的不断发展和客户需求的变化,EOS意识到只有不断创新才能保持竞争力。因此,公司加大了对研发的投入,引进了一批高素质的研发人才。经过数年的努力,EOS成功研发出了一系列高性能、高可靠性的电源产品,如交钥匙电源解决方案、定制电源等。这些产品不仅满足了客户的多样化需求,还帮助EOS在市场上获得了更多的份额。

Dow-Key Microwave Corporation公司的发展小趣事

为了保持市场竞争力,Dow-Key Microwave始终将创新作为公司发展的核心动力。公司不断推出新产品、新技术和新服务,以满足客户不断变化的需求。同时,公司还加强对员工的培训和激励,鼓励员工积极参与创新活动。这些措施的实施,使得Dow-Key Microwave在技术创新和产品升级方面取得了显著成效,进一步巩固了公司在行业中的领先地位。

川晶科技公司的发展小趣事

川晶科技注重团队建设和人才培养。公司建立了完善的培训体系和发展机制,为员工提供丰富的培训机会和广阔的发展空间。通过引进高素质的人才和内部培养相结合的方式,川晶科技打造了一支高素质、专业化的团队。团队成员之间团结协作、互相支持,共同推动公司的持续成长和发展。同时,公司还建立了良好的激励机制和企业文化氛围,激发员工的工作积极性和创造力。

AMSCO [Austria micro systems AG]公司的发展小趣事

品质是川晶科技的生命线。公司始终将品质管理贯穿于产品研发、生产、销售等各个环节,严格执行质量管理体系标准。通过引进先进的检测设备和方法,加强产品质量控制和检验,确保每一件产品都符合客户的要求和期望。同时,公司还建立了完善的售后服务体系,为客户提供及时、专业的技术支持和服务。川晶科技凭借卓越的品质和优质的服务赢得了客户的信赖和支持。

问答坊 | AI 解惑

华为公司是如何开展信号完整性和电源完整性分析研究工作的

华为公司是如何开展信号完整性和电源完整性分析研究工作的…

查看全部问答>

在Keil C51 uVision3 中调试w77E58如何设置才能使用片上的1K外部存贮器呀?

伟福V8仿真调试Winbond77E58不能正常使用片上1K外部存贮器的问题: 我用伟福V8/L仿真调试Winbond77E58板子, W77E58有片上1K外部存贮器,我不知是我的编译环境设置不对, 还是伟福不能仿真片上1K外部存贮器?在下面的程序中,将 int xdata i ...…

查看全部问答>

ucosII上开发pdf查看应用程序

想在ucosII上开发一款pdf查看程序(平台君正 Xburst MIPS jz4740),现有Foxit Embedded PDF SDK可惜不免费!!!!!!!!! 总不能从头开发吧,难度太了点吧!!可有免费的库!!!推荐一个!!…

查看全部问答>

CDMA&GPRS的判断与建立问题

各位,我是想写一个程序放到手机里面跑,首先判断有没有连接(GPRS或CDMA),如果没有就建立连接,这里就需要判断是要建立一个GPRS的连接或者是CDMA的连接,各位有什么好建议请说一下,谢谢! 另有人告诉我可以通过读SIM卡的IMSI号判断是移动或者联通,各位 ...…

查看全部问答>

应届毕业生如何进军华为?

应届毕业生如何进军华为? 有没有在华为工作的前辈? 请指点指点,万分感谢!!!!…

查看全部问答>

(高手请进)使用keil如何修改代码段的内容?

使用keil写单片机程序时,在程序运行阶段是不允许修改code段中的内容的,但是,我现在想实现动态加载模块的功能,需要在程序运行的时候修改code段的内容.        请问有什么办法在程序运行期间修改code段的内容? & ...…

查看全部问答>

学单片机怎么做实验啊

学校开了这门课(MCS-51)但学校太差了,这么课居然没实验课,想问一下怎么把书上的程序调一边啊,怎么在电脑上模拟(包括电路设计,程序调试,最终产品模拟),还有实际做中这些器件网上哪里有的买…

查看全部问答>

【求助】74HC165的操作,只能控制一个按键,其他的控制不了,搞不明白

#define PL P2_3//SHIFT/LOAD引脚 #define CLOCK P2_4 #define SETBSC P2_5//OUTPUT QH引脚 read74hc165m() {uchar basic,c; PL=1; nop(); PL=0;//使能端为低电平时8位数据进入寄存器 nop(); PL=1;//高电平数据锁存,同时165数据自动 ...…

查看全部问答>