映像文件分析,ADS 中startup.s 文件启动分析,学嵌入式开发ADS 必看
2010-04-17 10:21
声明: 我也是转来的,不是原创,由于别人是网易的日志,不能直接转,所以…… 感谢原
创!让我明白了startup.s 文件中的一些代码。
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 中指定RO Base
和RW Base,即在simple 模式下,告知连接器RO 和RW 的连接基地址。
这种模式下,ARM Linker 会输出以下符号,它们指示了在运行域中各个输出段所处的地址空
间,在使用的时候可以用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 里面的结束地址后面的一个地址
RO Base 对应的就是| Image$$RO$$Base|,RW Base 对应的是|Image$$RW$$Base|,由于ZI 段
是包含在RW 段里的,所以|Image$$RW$$Limit| 就等于|Image$$ZI$$limit| 。
下面给出一个例子,假设RO Base 设为0x00000000,后面的RW Base 地址是0x30000000,
然后在Options 选项中有Image entry point ,是一个初始程序的入口地址,设为0x00000000(程
序的入口地址都是从代码段(RO)开始的)。现在要做的就是将RWsection 移到以0x30000000
开始的地方,并且创造一个ZI section。
首先比较Image$$RO$$Limit 和Image$$RW$$Base,如果相等,说明execution view 下RW
section 的地址和load view 下RW section 的地址相同,这样,不需要移动RW section;如果不等,
说明需要移动RW section 到它在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|
IMPORT main ; 声明C 程序中的Main()函数
AREA Start,CODE,READONLY ; 声明代码段Start
ENTRY ; 标识程序入口
CODE32 ; 声明32 位ARM 指令
Reset LDR 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 的地址在load view 和execution view 下是否相等
BEQ LOOP1 ;如果相等就不移动RWsection,直接建立ZI scetion
LOOP0 ;否则就copy RWsection 到execution view 下指定的地址
CMP R1,R3
LDRCC R2,[R0],#4 ;它把从R0 中的地址开始的section copy 到R1 中的地址开
始的section
STRCC R2,[R1],#4
BCC LOOP0
LOOP1
LDR R1,=|Image$$ZI$$Limit| ;ZI section 末地址
MOV R2,#0 ;将ZI section 需要的初始化量装入R2
LOOP2
CMP R3,R1 ;建立并初始化ZI section
STRCC R2,[R3],#4
BCC LOOP2
B main ; 跳转到C 程序代码Main()函数
END
注:LDRCC R2,[R0],#4 ;将地址为R0 的内存单元数据读取到R2 中,然后R0=R0+4
CC(小于),EQ(相等)为条件码。
当我们把程序编写好以后,就要进行编译和链接了,在ADS1.2 中选择MAKE 按钮,会出现
一个Errors and Warnings 的对话框,在该栏中显示编译和链接的结果,如果没有错误,在文
件的最后应该能看到Image component sizes,后面紧跟的依次是Code,RO Data ,RW Data ,
ZI Data ,Debug 各个项目的字节数,最后会有他们的一个统计数据,后面的字节数是根据
用户不同的程序而来的。
Image component sizes
Code RO Data RWData ZI Data Debug
17256 158096 8 184 112580 Object
Totals
1064 299 0 0 796
Library Totals
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Code RO Data RWData ZI Data Debug
18320 158395 8 184 113376
Grand Totals
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Total RO Size(Code+RO Data) 176715(172.57KB)
Total RWSize(RWData+ZI Data) 192 ( 0.19KB)
Total ROM Size(Code+RO Data+RWData) 176723(172.58KB)
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Code :显示代码占用了多少字节。
RO Data 显示只读数据占用了多少字节。
RW Data 显示读写数据占用了多少字节。
ZI Data 显示零初始化的数据占用了多少字节。
Debug 显示调试数据占用了多少字节。
Object Totals 显示链接到一起以后生成映像的对象占用了多少字节。
Library Totals 显示已提取并作为单个对象添加到映像中的库成员占用了多少字节。
Grand Totals 显示映像的真实大小。Grand Totals=Library Totals+Object Totals
下面就以上面的数据为例来介绍几个变量的计算:
|Image$$RO$$Base|=Image entry point=0x00000000;表示程序代码存放的起始地址
|Image$$RO$$Limit|=|Image$$RO$$Base|+Total RO Size ( Code+Ro Data )
=0x0+176715+1=0x0002B24C(因为要满足4 的倍数,所以+1)
|Image$$RW$$Base|=0x30000000;由RW Base 指定
|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 的问题需要用到scatter load 机制。
__main() 和main()之不同:
当所有的系统初始化工作完成之后,就需要把程序流程转入主应用程序,即呼叫主应用程序。
最简单的一种情况是:
IMPORT main
B main
直接从启动代码跳转到应用程序的主函数入口,当然主函数名字可以由用户随便定义。
在ARM ADS 环境中,还另外提供了一套系统级的呼叫机制。
IMPORT _main
B _main
_main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,
最后自动跳转到main()。所以说,前者(_main)是库函数,后者就是我们自己编写的main()主
函数;
因此我们用的B _main 其实是执行库函数,然后该库函数再调用我们的main() 函数,因此在单
步调试时会看到先要跑一段程序(其实是库函数),然后再单步到我们自己的main 函数(这个同
时也说明如果有B _main 则就对应必须有main 函数,否则编译出错),如果我们用B main 来进
入我们的主函数的话,那在单步调试时就看到直接进入到我们自己的main 函数了,中间不会看
到其他程序;
那么用B _main 和用B main 这两这进入我们的main 函数方式有什么不同呢?
如果采用前者则会由编译器加入一段"段拷贝"程序,即我们说的从加载域到执行域转化程序;
而采用后者就没有这个了,因此如果要进行"段拷贝"只能自己动手编写程序来实现了,完成段
拷贝后就可以进入我们的主函数了,当然这个主函数不一定是叫做main(),可以起个其他好听
的名字,这个有别于使用B __main 方式;不管采用哪种方式进入我们的程序,都要有一段"段拷
贝"程序,跑完了段拷贝后才能可以进入我们主程序了!(顺便提一下:startup.s 这个文件并没有
所谓的"段拷贝"功能,再看也无益!)
对含有启动程序来说,"执行地址与加载地址相同"不容易实现:如果执行地址与加载地址相同
哪当然不需要做"段拷贝",但是个人理解编译器还会加入"段拷贝"程序(如果用B __main 的话),
只是因为条件不满足而不执行而已;但是对含有启动程序来说,"执行地址与加载地址相同"就
不容易了.因为启动程序是要烧到非易失存储器里,用来在上电执行的,而这个程序必定会有
RW 段,如果RW 放在非易失存储器,如FLASH,那就不好实现RW 功能了,因此要给RW 移动到
能够实现RW 功能的存储器,如SRAM 等.因此,对含有启动程序来说,"执行地址与加载地址相
同"就不容易实现;程序的入口点在C 库中的__main 处,在该点,库代码执行以下操作:
1. 将非零(只读和读写)运行区域从其载入地址复制到运行地址。
2. 清零ZI 区域。
3. 跳转到__rt_entry。

ADS中startup.s文件启动分析相关推荐

  1. 如何在VMware中使用CDR文件启动磁盘?(整机菜鸟来这里!)

    喜欢整机的朋友们应该都知道,CDR文件在VMWARE中也可以用于启动磁盘,这是懒人版的ISO文件,它们两个可以互相转换. 你那么有一些整机菜鸡不会在VM里面选择CDR文件,其实很简单,看我下面操作

  2. C51中intrins_h头文件解释分析

    文章目录 摘要 源代码 说明 举个栗子 摘要 这是关于C51中使用循环移位等函数的头文件定义的分析 源代码 /*-------------------------------------------- ...

  3. 关于Java中的iml文件的分析和理解

    什么是iml文件 iml是 项目标识文件,每一个模块都有一个iml文件,存储模块的相关信息,跟eclipse的project文件是一样的功能. idea存放项目的配置信息,包括历史记录,版本控制信息等 ...

  4. 服务器.bat文件启动闪退,直接双击启动tomcat中的startup.bat闪退原因及解决方法

    免安装的tomcat双击startup.bat后,启动窗口一闪而过,而且tomcat服务未启动. 原因是:在启动tomcat是,需要读取环境变量和配置信息,缺少了这些信息,就不能登记环境变量,导致了t ...

  5. s3c2440启动文件详细分析

    启动文件就是引导ARM启动,并进入我们熟悉的C语言程序.它主要完成了ARM最基本的硬件初始化工作.虽然启动文件的内容大同小异(就是设置系统时钟.内存.中断向量表.栈等内容),而且只要有一个现成的启动文 ...

  6. CE5.0 - eboot汇编Startup.s中MMU设置流程详细分析

    CE5.0 - eboot汇编Startup.s中MMU设置流程详细分析   以下为SMDK开发板startup.s部分启动代码.   ;------------------------------- ...

  7. STM32安装Keil5、芯片支持包、startup启动文件(启动过程分析)、建立工程、烧写

    参考:stm32入门之keil5的安装以及第一个工程的建立 作者:SKY丶丿平才 发布时间: 2020-12-06 17:08:30 网址:https://blog.csdn.net/weixin_4 ...

  8. Hhadoop-2.7.0中HDFS写文件源码分析(二):客户端实现(1)

    一.综述 HDFS写文件是整个Hadoop中最为复杂的流程之一,它涉及到HDFS中NameNode.DataNode.DFSClient等众多角色的分工与合作. 首先上一段代码,客户端是如何写文件的: ...

  9. Java中的JavaCore/HeapDump文件及其分析方法

    产生时间 Java程序运行时,有时会产生JavaCore及HeapDump文件,它一般发生于Java程序遇到致命问题的情况下. 有时致命问题发生后,Java应用不会死掉,还能继续运行: 但有时致命问题 ...

最新文章

  1. ​GPT-3好“搭档”:这种方法缓解模型退化,让输出更自然
  2. dim private public static_static方法 (静态方法)
  3. Fedora 17 install VMWare tool
  4. 解释什么是快速排序算法?_解释排序算法
  5. YUV格式学习:NV12和YUV420P格式互换
  6. 浏览器可以用c语言编辑吗,如何在浏览器端运行c/c++语言编写的代码
  7. SQL Server数据库中批量导入数据
  8. java面向对象相关选择题_java面向对象练习题一
  9. 螃蟹效应:表现优秀遭嫉妒,如何应对职场竞争?
  10. 卷积神经网络的现代雏形——LeNet
  11. STM32CubeIDE使用总结(四)——遇到的问题
  12. 【突变检验合集】含Pettitt突变检验等
  13. android 蓝牙BluetoothAdapter的介绍
  14. 正宇丨人生哪有事事如意,生活哪有样样顺心
  15. java pdf 修改内容_生成PDF全攻略之在已有PDF上添加内容的实现方法
  16. vue中搜索功能如何请求数据接口来实现关键字查询
  17. 坑人无数的俩货:半包和粘包
  18. 最新 ICCV | 35个GAN应用主题梳理,最全GAN生成对抗论文汇总
  19. 诺基亚5800XM触控音乐全解析
  20. 3dmax建模模式下的选择--循环选择

热门文章

  1. 非root用户sudo_ssh免密钥
  2. webpack v3 结合 react-router v4 做 dynamic import — 按需加载(懒加载)
  3. MySQL/sqlserver查询in操作 查询结果按in集合顺序显示
  4. SVN trunk branches tags 的用法 - 摘自网络
  5. NA-NP-IE系列实验28:HDLC 和PPP 封装
  6. 深度学习之 FPN (Feature Pyramid Networks)
  7. Python 内置函数之 open (文件操作)
  8. c语言 malloc_C语言快速入门——动态内存分配
  9. Django创建第一个应用
  10. python面向对象实现简易银行管理员页面系统