ADS映像文件及地址映射分析

1、什么是 arm 的映像文件,
arm 映像文件其实就是可执行文件,包括 bin或 hex两种格式,可以直接烧到 ROM里执行。
在axd调试过程中,我们调试的是 axf文件,其实这也是一种映像文件,它只是在 bin文件中
加了一个文件头和一些调试信息。
映像文件一般 由域组成,域最多由三个输出段组成(RO,RW,ZI),输出段又由输入段组成。所
谓域,指的就是整个bin 映像文件所处在的区域,它又分为加载域和运行域。对于嵌入式系
统而言,程序映象都是存储在 Flash 存储器等一些非易失性器件中的,而在运行时,程序中
的RW段必须重新装载到可读写的RAM中。简单来说,程序的加载时域就是指程序烧入Flash
中的状态,运行时域是指程序执行时的状态。一般来说 flash 里的 整个 bin 文件所在的地址
空间就是加载域
,当然在程序一般都不会放在 flash里执行,一般都会搬到 sdram 里运行工作,
它们在被搬到 sdram 里工作所处的地址空间就是运行域。我们输入的代码,一般有代码部分
和数据部分,这就是所谓的输入段,经过编译后就变成了 bin 文件中 ro 段和 rw 段,还有所
谓的 zi 段,这就是输出段。在 ARM 的集成开发环境中,只读的代码段和常量被称作 RO 段
(ReadOnly);可读写的全局变量和静态变量被称作 RW段(ReadWrite);RW段中要被初始化为
零的变量被称为 ZI段(ZeroInit)。对于加载域中的输出段,一般来说 RO 段后面紧跟着 RW段,
RW 段后面紧跟着 ZI 段。在运行域中这些输出段并不连续,但 RW 和 ZI 一定是连着的。ZI
段和RW段中的数据其实可以是 RW属性。
2、 简单地址映射
对于比较简单的情况,可以在ADS集成开发环境的ARM LINKER选项output中指定ROBase
和RWBase,即在simple 模式下,告知连接器RO和 RW的连接基地址。
这种模式下,ARMLinker会输出以下符号,它们指示了在运行域中各个输出段所处的地址空
间,在使用的时候可以用IMPORT引入:
|Image$$RO$$Base|:表示RO段在运行域中的起始地址
|Image$$RO$$Limit|:表示RO区末地址后面的地址,即 RW数据源的起始地址
|Image$$RW$$Base|:RW 区在 RAM 里的执行区起始地址,也就是编译器选项 RW_Base 指定
的地址
|Image$$ZI$$Base|:ZI区在 RAM里面的起始地址
|Image$$ZI$$Limit|:ZI区在RAM 里面的结束地址后面的一个地址
ROBase 对应的就是| Image$$RO$$Base|,RWBase 对应的是|Image$$RW$$Base|,由于 ZI段
是包含在RW段里的,所以|Image$$RW$$Limit| 就等于|Image$$ZI$$limit| 。
下面给出一个例子,假设RO Base 设为 0x00000000,后面的 RWBase 地址是0x30000000,
然后在 Options选项中有 Imageentry point ,是一个初始程序的入口地址,设为 0x00000000(程
序的入口地址都是从代码段(RO)开始的)。现在要做的就是将 RWsection移到以 0x30000000
开始的地方,并且创造一个 ZIsection。
首先比较 Image$$RO$$Limit 和 Image$$RW$$Base,如果相等,说明 execution view 下 RW
section 的地址和 load view 下 RWsection 的地址相同,这样,不需要移动 RWsection;如果不等,
说明需要移动 RWsection 到它在 execution view 中的地方,把 ROM 里|Image$$RO$$Limt|开
始的RW初始数据拷贝到RAM里面|Image$$RW$$Base|开始的地址,当RAM这边的目标地址
到达|Image$$ZI$$Base|后就表示 RW区的结束和 ZI 区的开始,接下去就对这片 ZI 区进行清零
操作,直到遇到结束地址|Image$$ZI$$Limit|。
ARM映像文件及其地址映射(二)
示例代码如下:
          IMPORT|Image$$RO$$Limit|
          IMPORT|Image$$RW$$Base|
          IMPORT|Image$$ZI$$Base|
          IMPORT|Image$$ZI$$Limit|
          IMPORTmain; 声明C 程序中的Main()函数
AREAStart,CODE,READONLY; 声明代码段 Start
ENTRY; 标识程序入口
CODE32 ; 声明 32位 ARM指令
ResetLDR SP,=0x40003F00
          ; 初始化C 程序的运行环境
          LDR R0,=|Image$$RO$$Limit| ;得到 RW数据源的起始地址
          LDR R1,=|Image$$RW$$Base| ;RW区在RAM 里的执行区起始地址
          LDR R3,=|Image$$ZI$$Base| ;ZI 区在RAM里面的起始地址
          CMP R0,R1 ;检查RWsection的地址在 loadview和 executionview下是否相等
          BEQ LOOP1 ;如果相等就不移动RWsection,直接建立 ZIscetion
LOOP0 ;否则就copy RWsection 到executionview 下指定的地址
          CMP R1,R3
          LDRCC R2,[R0],#4 ;它把从 R0 中的地址开始的 section copy 到 R1 中的地址开始的section
          STRCC R2,[R1],#4
          BCC LOOP0
LOOP1
          LDR R1,=|Image$$ZI$$Limit| ;ZIsection末地址

          MOV R2,#0 ;将 ZIsection 需要的初始化量装入 R2

LOOP2

          CMP R3,R1 ;建立并初始化ZIsection
          STRCC R2,[R3],#4
          BCC LOOP2

          Bmain; 跳转到 C 程序代码Main()函数
END
注:LDRCCR2,[R0],#4 ;将地址为 R0的内存单元数据读取到 R2中,然后 R0=R0+4
CC(小于),EQ(相等)为条件码。
当我们把程序编写好以后,就要进行编译和链接了,在 ADS1.2中选择 MAKE 按钮,会出现
一个 Errors andWarnings 的对话框,在该栏中显示编译和链接的结果,如果没有错误,在文
件的最后应该能看到 Image component sizes,后面紧跟的依次是 Code,ROData ,RWData ,
ZI Data ,Debug 各个项目的字节数,最后会有他们的一个统计数据,后面的字节数是根据
用户不同的程序而来的。
Image componentsizes
Code ROData RWData ZIData Debug
17256 158096 8 184 112580 Object
Totals
1064 299 0 0 796
LibraryTotals
===============================================
Code ROData RWData ZIData Debug
18320 158395 8 184 113376
GrandTotals
===============================================
TotalROSize(Code+ROData) 176715(172.57KB)
TotalRWSize(RWData+ZIData) 192( 0.19KB)
TotalROMSize(Code+ROData+RWData) 176723(172.58KB)
===============================================
Code :显示代码占用了多少字节。
ROData 显示只读数据占用了多少字节。
RWData 显示读写数据占用了多少字节。
ZIData 显示零初始化的数据占用了多少字节。
Debug 显示调试数据占用了多少字节。
ObjectTotals 显示链接到一起以后生成映像的对象占用了多少字节。
LibraryTotals 显示已提取并作为单个对象添加到映像中的库成员占用了多少字节。
GrandTotals 显示映像的真实大小。 GrandTotals=LibraryTotals+ObjectTotals
下面就以上面的数据为例来介绍几个变量的计算:
|Image$$RO$$Base|=Imageentrypoint=0x00000000;表示程序代码存放的起始地址
|Image$$RO$$Limit|=|Image$$RO$$Base|+Total RO Size ( Code+Ro Data )
=0x0+176715+1=0x0002B24C(因为要满足 4的倍数,所以+1)
|Image$$RW$$Base|=0x30000000;由RWBase指定


|Image$$RW$$Limit|=|Image$$RW$$Base|+Total RW Size ( RW Data+ZI Data )
=0x30000000+192=0x300000C0
|Image$$ZI$$Base|=|Image$$RW$$Base|+RWData=0x30000000+8=0x30000008

|Image$$ZI$$Limit|=|Image$$RW$$Limit|


3、复杂地址映射
对于复杂情况,如RO段被分成几部分并映射到存储空间的多个地方时,需要创建一个称为“分
布装载描述文件”的文本文件,通知链接器把程序的某一部分链接在存储器的某个地址空间。
需要指出的是,分布装载描述文件中的定义要按照系统重定向后的存储器分布情况进行。在
引导程序完成初始化的任务后,应该把主程序转移到RAM 中去运行,以加快系统的运行速
度。
如下图,为了解决复杂memory map 的问题需要用到 scatterload 机制。
__main() 和 main()之不同:
当所有的系统初始化工作完成之后,就需要把程序流程转入主应用程序,即呼叫主应用程序。
最简单的一种情况是:
IMPORTmain
Bmain
直接从启动代码跳转到应用程序的主函数入口,当然主函数名字可以由用户随便定义。
在ARMADS 环境中,还另外提供了一套系统级的呼叫机制。
IMPORT_main
B_main
_main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,
最后自动跳转到 main()。所以说,前者(_main)是库函数,后者就是我们自己编写的 main()主
函数;
因此我们用的B_main 其实是执行库函数,然后该库函数再调用我们的main() 函数,因此在单
步调试时会看到先要跑一段程序(其实是库函数),然后再单步到我们自己的 main 函数(这个同
时也说明如果有B_main 则就对应必须有main函数,否则编译出错),如果我们用 Bmain来进
入我们的主函数的话,那在单步调试时就看到直接进入到我们自己的 main 函数了,中间不会看
到其他程序;
那么用B_main和用Bmain 这两这进入我们的 main 函数方式有什么不同呢?
如果采用前者则会由编译器加入一段"段拷贝"程序,即我们说的从加载域到执行域转化程序;
而采用后者就没有这个了,因此如果要进行 "段拷贝"只能自己动手编写程序来实现了,完成段
拷贝后就可以进入我们的主函数了,当然这个主函数不一定是叫做main(),可以起个其他好听
的名字,这个有别于使用 B__main 方式;不管采用哪种方式进入我们的程序,都要有一段"段拷
贝"程序,跑完了段拷贝后才能可以进入我们主程序了!(顺便提一下:startup.s这个文件并没有
所谓的"段拷贝"功能,再看也无益!)(再顺便提一下:上面例子是段拷贝程序)
对含有启动程序来说,"执行地址与加载地址相同"不容易实现:如果执行地址与加载地址相同
哪当然不需要做"段拷贝",但是个人理解编译器还会加入"段拷贝"程序(如果用B__main的话),
只是因为条件不满足而不执行而已;但是对含有启动程序来说,"执行地址与加载地址相同"就
不容易了.因为启动程序是要烧到非易失存储器里,用来在上电执行的,而这个程序必定会有


RW段,如果RW放在非易失存储器,如FLASH,那就不好实现RW功能了,因此要给RW移动到
能够实现 RW 功能的存储器,如 SRAM 等.因此,对含有启动程序来说,"执行地址与加载地址相
同"就不容易实现;程序的入口点在C 库中的__main 处,在该点,库代码执行以下操作:
1. 将非零(只读和读写)运行区域从其载入地址复制到运行地址。
2. 清零 ZI 区域。

3. 跳转到__rt_entry。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值