历史上的今天
返回首页

历史上的今天

今天是:2024年09月07日(星期六)

2021年09月07日 | S3C2440裸机------代码重定位

2021-09-07 来源:eefocus

1.段的概念

我们的cpu可以直接把地址通过内存控制器发送到norflash,sram,sdram,但是我们的cpu不能直接到达nandflash,只能发送到nandflash控制器,我们的程序可以放到norflash以及sdram上面,它可以直接运行,但是nandflash的程序是不能直接运行的,但是我们仍然可以把程序放到nandflash上面,因为一上电2440内部的硬件会把nandflash的前4K代码复制到片内的SRAM,这个是由硬件做的,然后cpu从0地址开始运行,此时的0地址对应的是sram。那么如果我的程序超过4K怎么办,如果bin文件在nandflash上面超过4K,那么我们不能只复制前面4K代码,所以显然我们前面4K代码应该把整个程序都复制到SDRAM上面。这就是重定位,重新确定程序的地址。


如果我是用norflash启动,此时的0地址在norflash,此时片内4Ksram的地址是0x4000,0000.norflash可以像内存一样读,但是不能像内存一样的写。如果程序中含有需要更改的全局变量或者静态变量,全局变量是包含在bin文件中,烧在norflash上面(局部变量是在栈中在SRAM上面,可读可写没有问题),直接修改变量无效。所以我们需要把这些全局变量和静态变量重定位放到SDRAM中。


我们修改之前的main.c,然后在里面定义一个全局的变量,可以发现串口一直打印A,并没有递增的打印ABCD。


#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++;         /* nor启动时, 此代码无效 */

delay(1000000);

}

 

return 0;

}


我们的程序主要分为:


代码段(text):代码 


数据段(data):一般的全局变量


只读数据段(rodata) :const全局变量。


bss段:初值为0或者无初始值的全局变量。BSS段不保存在bin文件中。


comment:注释:也不保存在bin文件中。


2.链接脚本的引入与简单测试

通过上一节的测试,我们发现当我们把程序烧写到norflash时,由于norflash是不可写的,因此不能修改g_Char,所以我们需要通过修改makefile,让g_Char放到SDRAM里面,我们的代码仍然放在NORFLASH里面,只是把变量保存到SDRAM里面,由于我们的SDRAM的地址是从0X30000000开始的,因此我们修改makefile为如下的形式。


all:

arm-linux-gcc -c -o led.o led.c

arm-linux-gcc -c -o uart.o uart.c

arm-linux-gcc -c -o init.o init.c

arm-linux-gcc -c -o main.o main.c

arm-linux-gcc -c -o start.o start.S

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

arm-linux-objcopy -O binary -S sdram.elf sdram.bin

arm-linux-objdump -D sdram.elf > sdram.dis

clean:

rm *.bin *.o *.elf *.dis

但是编译完之后,我们发现我们的bin文件达到了800M,这是因为我们把变量放在了0X30000000这里,但是我们显然不能这么改, 针对这个问题有两个解决方案。


2.1方案一

首先我们在bin文件里面,让代码段和全局变量靠在一起,然后把bin文件烧写到norflash上面,然后运行的时候,我们前面的代码需要把全局变量复制到0X30000000的地方, 这就是重定位。


2.2方案二

第二种方法是,我们在链接的时候,让代码段和全局变量中间没有那么大的空闲,我们的代码段也从0x30000000开始,然后烧写到norflash,然后运行的时候,我们前面的代码把代码段和全局变量整个程序复制到0x30000000。


这两种方案的差别在于,第一种方法只是重定位了全局变量,第二种方法是重定位了整个程序。


下面我们先来看方案一:我们通过引入链接脚本,将本来应该位于0X30000000的变量和代码段拼在一起,我们首先修改makefile为下面的形式:


all:

arm-linux-gcc -c -o led.o led.c

arm-linux-gcc -c -o uart.o uart.c

arm-linux-gcc -c -o init.o init.c

arm-linux-gcc -c -o main.o main.c

arm-linux-gcc -c -o start.o start.S

#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

arm-linux-objcopy -O binary -S sdram.elf sdram.bin

arm-linux-objdump -D sdram.elf > sdram.dis

clean:

rm *.bin *.o *.elf *.dis

然后我们来写出这个链接脚本sdram.lds 


SECTIONS {

   .text   0  : { *(.text) }

   .rodata  : { *(.rodata) }

   .data 0x30000000 : AT(0x800) { *(.data) }

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

}


上面的{}里面的*表示所有文件的.text段,所有文件的.rodata段。其中的AT表示at,也就是全局变量运行的时候是在0x30000000,但是在bin文件中我们把它放在0x800这里。


我们把上面的程序烧写到板子上之后显示乱码,这是因为我们的bin文件中,全局变量是保存在0X800的地方,但是我们的main函数中是以0x30000000为地址去访问全局变量的。而我们的代码中并没有把0x30000000地址的地方用全局变量的值进行初始化,也就是我们的代码中缺少重定位相关的代码,我们需要修改代码,将变量从0x800的地方复制到0x30000000的地方,我们修改start.S,在执行main函数之前进行重定位。


.text

.global _start

 

_start:

 

/* 关闭看门狗 */

ldr r0, =0x53000000

ldr r1, =0

str r1, [r0]

 

/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */

/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */

ldr r0, =0x4C000000

ldr r1, =0xFFFFFFFF

str r1, [r0]

 

/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */

ldr r0, =0x4C000014

ldr r1, =0x5

str r1, [r0]

 

/* 设置CPU工作于异步模式 */

mrc p15,0,r0,c1,c0,0

orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA

mcr p15,0,r0,c1,c0,0

 

/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 

*  m = MDIV+8 = 92+8=100

*  p = PDIV+2 = 1+2 = 3

*  s = SDIV = 1

*  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M

*/

ldr r0, =0x4C000004

ldr r1, =(92<<12)|(1<<4)|(1<<0)

str r1, [r0]

 

/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定

* 然后CPU工作于新的频率FCLK

*/

 

/* 设置内存: sp 栈 */

/* 分辨是nor/nand启动

* 写0到0地址, 再读出来

* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动

* 否则就是nor启动

*/

mov r1, #0

ldr r0, [r1] /* 读出原来的值备份 */

str r1, [r1] /* 0->[0] */ 

ldr r2, [r1] /* r2=[0] */

cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */

ldr sp, =0x40000000+4096 /* 先假设是nor启动 */

moveq sp, #4096  /* nand启动 */

streq r0, [r1]   /* 恢复原来的值 */

 

bl sdram_init /*首先要初始化SDRAM,要不然下面的重定位代码不起作用,没法写SDRAM*/

 

/* 重定位data段 */

mov r1, #0x800

ldr r0, [r1]

mov r1, #0x30000000

str r0, [r1]

 

bl main

 

halt:

b halt


上面的重定位data的代码中,我们简单粗暴的从0x800复制了一个字节到0x30000000这里,这种方法并不通用,首先我们程序中有可能有很多个全局变量,另外我们这里的地址0X800和0x30000000都是写死的,这是因为肉眼从链接脚本中观察到的,下面我们写一个通用的程序,首先要在链接脚本中添加一些变量。


SECTIONS {

   .text   0  : { *(.text) }

   .rodata  : { *(.rodata) }

   .data 0x30000000 : AT(0x800) 

   { 

      data_load_addr = LOADADDR(.data);

      data_start = . ;

      *(.data) 

      data_end = . ;

   }

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

}

 

其中data_start后面的.表示当前地址,也就是0x30000000, data_end-data_start就等于数据段的长度,LOADADDR(.data)表示data段在bin文件中的地址,也就是0x800.


然后我们修改start.S如下:


.text

.global _start

 

_start:

 

/* 关闭看门狗 */

ldr r0, =0x53000000

ldr r1, =0

str r1, [r0]

 

/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */

/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */

ldr r0, =0x4C000000

ldr r1, =0xFFFFFFFF

str r1, [r0]

 

/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */

ldr r0, =0x4C000014

ldr r1, =0x5

str r1, [r0]

 

/* 设置CPU工作于异步模式 */

mrc p15,0,r0,c1,c0,0

orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA

mcr p15,0,r0,c1,c0,0

 

/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 

*  m = MDIV+8 = 92+8=100

*  p = PDIV+2 = 1+2 = 3

*  s = SDIV = 1

*  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M

*/

ldr r0, =0x4C000004

ldr r1, =(92<<12)|(1<<4)|(1<<0)

str r1, [r0]

 

/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定

* 然后CPU工作于新的频率FCLK

*/

 

/* 设置内存: sp 栈 */

/* 分辨是nor/nand启动

* 写0到0地址, 再读出来

* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动

* 否则就是nor启动

*/

mov r1, #0

ldr r0, [r1] /* 读出原来的值备份 */

str r1, [r1] /* 0->[0] */ 

ldr r2, [r1] /* r2=[0] */

cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */

ldr sp, =0x40000000+4096 /* 先假设是nor启动 */

moveq sp, #4096  /* nand启动 */

streq r0, [r1]   /* 恢复原来的值 */

 

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]

strb r4, [r2]

add r1, r1, #1

add r2, r2, #1

cmp r2, r3

bne cpy

 

bl main

 

halt:

b halt


3.链接脚本的解析

我们上一节用到了链接脚本,现在我们来解析下前面用到的连接脚本。


首先通过文档 http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html,我们知道链接脚本的格式是这样的:


SECTIONS {

...

secname start BLOCK(align) (NOLOAD) : AT ( ldadr )

  { contents } >region :phdr =fill

...

}


在链接脚本格式中,BLOCK(align)很少用到,NOLOAD也很少用到,region:phdr=fill也很少用到。里面的secname名字我们可以随便写,也可以不叫.text .rodata,叫什么名字都可以,start表示运行时的起始地址runtime address,也叫重定位地址relocate address,AT可写也可以不写,表示加载地址,LOADaddress不写时,就等于runtime address.


然后就是contents的格式,contents格式有下面3种:


1:start.o:你可以 指定start.o,把整个start.o放到这个段里面。


2:*(.text):指定所有.o的text段放到这里面。


3:start.o


*(.text):指定start.o放到前面,然后剩下所有文件的text段放到


elf文件地址问题:


我们的makefile中通过链接脚本将一些.o文件生成了elf格式的文件,elf文件中是含有地址信息的,比如加载地址。


我们可以使用加载器把elf文件加载到内存的加载地址load address,对于裸板来说,JTAG调试器就是加载器,对于应用程序来说,加载器本身也是一个应用程序,

然后运行程序。


如果链接脚本中指定了load address ,然后load address不等于runtime address,那么程序本身还需要重定位代码。


裸板bin文件:


elf格式文件生成bin文件。

硬件机制的启动

如果bin文件所在位置不等于运行地址,那么需要程序本身实现重定位。

下面看一下我们前面用到的链接脚本: 


SECTIONS {

   .text   0  : { *(.text) }

   .rodata  : { *(.rodata) }

   .data 0x30000000 : AT(0x800) 

   { 

      data_load_addr = LOADADDR(.data);

      data_start = . ;

      *(.data) 

      data_end = . ;

   }

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

}

 

第一行没有写加载地址,那么我们的text的加载地址就等于运行地址,也就是0.不要进行重定位。另外这里text是保存的是所有文件的text段,这里的所有文件的顺序就是按照makefile里面写的先后顺序arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf,然后看data段,这里加载地址是0x800,然后运行地址是0x30000000.那么我们程序启动之后需要把数据段从0x800拷贝到0x30000000,这就是重定位。另外,在elf或者bin文件中都不会存bss段,因为bss中是初始值为零的或者没有初始化的全局变量,假如我们有很多初始化为零的全局变脸,那么我们保存这么多零浪费空间。


我们通过编写程序,将初始化为零的全局变量打印出来,发现它并不是零,这是因为我们没有把他清零,我们需要写程序 将BSS段对应的内存清零, 要想清零,需要知道BSS空间的地址是什么,首先要修改链接脚本,


SECTIONS {

   .text   0  : { *(.text) }

   .rodata  : { *(.rodata) }

   .data 0x30000000 : AT(0x800) 

   { 

      data_load_addr = LOADADDR(.data);

      data_start = . ;

      *(.data) 

      data_end = . ;

   }

   

   bss_start = .;

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

   bss_end = .;

}

 

然后修改start.S清除BSS段。


 

.text

.global _start

 

_start:

 

/* 关闭看门狗 */

ldr r0, =0x53000000

ldr r1, =0

str r1, [r0]

 

/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */

/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */

ldr r0, =0x4C000000

ldr r1, =0xFFFFFFFF

str r1, [r0]

 

/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */

ldr r0, =0x4C000014

ldr r1, =0x5

str r1, [r0]

 

/* 设置CPU工作于异步模式 */

mrc p15,0,r0,c1,c0,0

orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA

mcr p15,0,r0,c1,c0,0

 

/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 

*  m = MDIV+8 = 92+8=100

*  p = PDIV+2 = 1+2 = 3

*  s = SDIV = 1

*  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M

*/

ldr r0, =0x4C000004

ldr r1, =(92<<12)|(1<<4)|(1<<0)

str r1, [r0]

 

/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定

* 然后CPU工作于新的频率FCLK

*/

 

/* 设置内存: sp 栈 */

/* 分辨是nor/nand启动

* 写0到0地址, 再读出来

* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动

* 否则就是nor启动

*/

mov r1, #0

ldr r0, [r1] /* 读出原来的值备份 */

推荐阅读

史海拾趣

Electroswitch公司的发展小趣事

面对快速变化的电子行业环境,Electroswitch始终保持着敏锐的洞察力和创新精神。公司不断加大对新技术和新产品的研发力度,推出了一系列具有领先水平的开关产品。同时,公司还积极探索新的业务领域和市场机会,为未来的发展做好充分准备。展望未来,Electroswitch将继续秉承“质量、选择、产品创新和出色的支持”的理念,为客户提供更加优质的产品和服务。

请注意,以上故事框架仅供参考,您可以根据这些框架进一步扩展和丰富故事内容。

Frolyt Condensers & Elements GmbH公司的发展小趣事

在电子元件领域,Frolyt Condensers & Elements GmbH起初是一家专注于电容器研发的小型制造商。2005年,公司研发团队成功开发出一种新型高性能铝电解电容器,该电容器在耐高温、长寿命和低阻抗方面表现出色,迅速吸引了市场的关注。通过持续的技术创新和严格的品质控制,Frolyt的电容器产品在汽车电子、通信设备等多个领域得到广泛应用,公司因此实现了市场份额的快速增长。

锋鸣电子(Fengming)公司的发展小趣事

福斯特(FIRST)公司在光伏封装材料领域取得了显著成就。自2003年公司前身杭州福斯特热熔胶膜有限公司成立以来,团队成功研发出EVA太阳能电池胶膜,正式进军光伏行业。此后,公司不断投入研发,扩大产能,光伏胶膜(EVA/POE胶膜)的设计产能已达10.51亿平米。至2020年,公司光伏胶膜销量达8.65亿平米,占据全球市场份额约55%-60%,大幅领先其他竞争对手。这一成就不仅巩固了福斯特在光伏胶膜市场的领导地位,还推动了整个行业的发展。

Galaxy Semi-Conductor Co Ltd公司的发展小趣事

山西飞虹激光科技有限公司于2010年在中国山西成立,并迅速在北京设立分公司,将北京作为战略重心,拓展全国市场。仅两个月后,飞虹激光的研发&生产中心便设立并投入生产,展现了公司的强大执行力和决心。2012年,飞虹激光积极参与国际展会,如慕尼黑上海光博会,展示了其F3015激光切割机、激光熔覆系统等高端产品,不仅提升了公司知名度,也彰显了其在激光技术领域的专业水平和创新能力。

ABC [ABC Taiwan Electronics Corp]公司的发展小趣事

近年来,随着环保意识的日益增强,ABC Taiwan Electronics Corp积极响应绿色生产的号召。公司引入了环保材料和节能技术,降低了生产过程中的能耗和排放。同时,公司还加强了废弃物的处理和回收利用,实现了资源的循环利用。此外,ABC还积极参与社会公益活动,支持教育事业和环保事业,履行了企业的社会责任。

这五个故事展示了ABC Taiwan Electronics Corp从创业初期的艰难起步到逐步成长为国际知名电子零件供应商的发展历程。通过技术突破、国际化战略、产业升级和绿色生产等方面的努力,ABC不断应对市场的变化和挑战,实现了稳健的发展。

Ava Electronics Corp公司的发展小趣事

随着产品线的不断丰富和技术的不断进步,AVA电子开始将目光投向更广阔的市场。公司积极参加各类行业展会和论坛,与国内外客户建立了广泛的联系。同时,AVA电子还加大了品牌建设的力度,通过广告宣传、品牌推广等方式提高了品牌的知名度和美誉度。这些举措不仅有效提升了公司的市场份额,也为公司的长远发展奠定了坚实的基础。

问答坊 | AI 解惑

数字对讲机

小弟是通信方面的新手 最近要做一个数字对讲机方面的课题 设计一个短信收发的应用层软件   想请教各位高手 有没有做过或者了解这方面的 给我一点资料或者指导   不甚感激…

查看全部问答>

初学者提问 有关arm linux内核 驱动开发

请教各位,要学arm linux内核 驱动开发,要不要对芯片的结构很了解,如寄存器之类了东西。…

查看全部问答>

WINCE死机了

自从我加了.NET 2.0后在WINCE欢迎界面就死机了!去掉就OK了,请问这是怎么回事呀? …

查看全部问答>

分享流水灯程序

#include <REG51.H>#include <INTRINS.H> void delay(void)   //误差 -0.152253987588us{    unsigned char a,b;    for(b=238;b>0;b--)        for(a=250 ...…

查看全部问答>

关于HOHO申请开发板的的经历

HOHO 申请到了下面的开发板 有图和大家分享:(手机拍摄,可能不清楚)   申请表发送目标邮箱 market@prochip.com.cnHOHO就是发到这个邮箱申请的,希望大家也能成功   申请地址为:大家可以去它的官方网站看看去 如果 ...…

查看全部问答>

ST32的一点建议

现在已经有 STM32  USB 转串口建议STM32  USB 转CAN 口可否芯片改进一些, 把USB/CAN共用的RAM分开?…

查看全部问答>

多级放大电路的动态分析

1、多级放大器的级间关系:在多级放大器中,后级电路相当于前级的负载,前级负载是后级放大器的输入电阻;前级相当后级的信号源,后级信号源内阻为前级的输出电阻。2、n级放大器的动态指标a、总电压放大倍数 :可见, n级放大器的总电压放大倍数 ...…

查看全部问答>

竞赛,单片机(处理器)怎么选取??

本帖最后由 paulhyde 于 2014-9-15 04:14 编辑 竞赛,单片机(处理器)怎么选取?是带队老师建议?是芯片厂商推荐?还是自己一个个测试? 当然,2013年全国大学生电子设计竞赛竞赛题目及要求中有以下说明: 竞赛题目包括 ...…

查看全部问答>

建议论坛做个APP

可以谋障碍的看贴看新闻 网页版属实不太方便 吴哈哈哈…

查看全部问答>