编译过程中的链接地址和实际运行地址
MDK和交叉编译工具编译时都会指定程序的下载的地址(其实就是告诉程序它将在那个地址上开始执行),这有什么意义吗?
其实这么设计有原因的,因为这里涉及到全局变量和全局函数指针的地址问题,假如当你在编译时指定编译器这段程序会在0x0c000000地址上运行,按缺省链接方式(即未使用自己的链接脚本或分散加载文件)全局变量和函数的地址分配就会从0x0c000000基地址上开始,此时如果你把这段程序烧录到0x0c000000地址上运行,变量的访问和绝对跳转指令执行都不会有任何问题,但是如果你将程序下载到0x00000000上运行时,在程序因为还是按链接时指定的地址访问全局变量或运行绝对跳转指令,但是此时因为实际上程序指令访问的地址上并不存在正确的内容从而会导致程序跑飞,因为你编译时链接地址对不上所以程序会到链接指定地址空间去读取这个变量,此时就会读到一个错误的值。对于函数指针也是相同的道理。
ARM架构举例(相对跳转和绝对跳转)
绝对跳转:就是执行了这一条指令之后就会跳转到绝对地址去取指执行。
相对跳转:以当前PC寄存器的地址作为基址偏移一定的偏移地址去取指运行。
将被链接到0xC0000000地址的代码放到0x00000000地址执行,如果它们只使用顺序执行或者相对跳转执行方式就可以正常运行(未使用全局变量),但如果使用了绝对跳转指令,那么程序就跑飞了。前面所说的顺序执行和相对跳转寻址执行的代码也被称之为位置无关代码。
我们参照下面这段伪代码来说明这个情况。
指令编号
指令功能
指令1 :顺序执行
指令2 :顺序执行
指令3 :相对跳转到指令5
指令4 :顺序执行
指令5 :顺序执行
指令6 :绝对跳转到指令8
指令7 :顺序执行
指令8 :顺序执行
在编译、链接的时候,这段程序被告知放在0xC0000000地址空间,编译后烧录到0x00000000结果在存储设备中的存放结果为(每条指令以4字节计算):
指令地址 指令编号 指令功能 下条指令地址 0x00000000 指令1: 顺序执行 当前地址+4 0x00000004 指令2: 顺序执行 当前地址+4 0x00000008 指令3: 相对跳转到指令5 当前地址+8 0x0000000C 指令4: 顺序执行 当前地址+4 0x00000010 指令5: 顺序执行 当前地址+4 0x00000014 指令6: 绝对跳转到指令8 0xC000001C 0x00000018 指令7: 顺序执行 当前地址+4 0x0000001C 指令8: 顺序执行 当前地址+4
程序从0x00000000开始运行直到第五条指令都是不会出错的,但是当执行完指令6后程序就会跑飞了,因为指令6是一条绝对跳转的指令但0xC000001C空间没有代码,这样程序就跑飞了。但当这段程序被放在0xC0000000起始空间时,开始执行指令1,然后采用相对寻址的方法就可以运行到指令6,在指令6执行时执行绝对寻址的方法从0xC0000014正确跳转到指令8所在的0xC000001C位置,这段代码运行正常。(参考博客:http://blog.sina.com.cn/s/blog_908da74601011bg6.html)
如图:
MDK编译后的STM32工程 map文件简单分析
只看map文件中有用的部分:
Code为程序代码部分
RO-data 表示 程序定义的常量const temp;
RW-data 表示 已初始化的全局变量
ZI-data 表示 未初始化的全局变量
Program Size: Code="18248" RO-data=320 RW-data=260 ZI-data=3952
Code, RO-data,RW-data ............flash
RW-data, ZIdata...................RAM (内存)
存储Size:
RO size: Code + RO_data
RW size: RW_data + ZI_data
ROM (minimum)size = Code + RO_data + RW_data (即烧/下载程序到FLASH/ROM时,所占用的最小空间)
Total ROM Size (Code + RO Data + RW Data)这样所写的程序占用的ROM的字节总数,也就是说程序所下载到ROM flash 中的大小。为什么Rom中还要存RW,因为掉电后RAM中所有数据都丢失了,每次上电RAM中的数据是被重新赋值的,每次这些固定的值就是存储在Rom中的,为什么不包含ZI段呢,是因为ZI数据都是0,没必要包含,只要程序运行之前将ZI数据所在的区域一律清零即可。包含进去反而浪费存储空间。
RAM size: RW Data + ZI Data (即程序运行的时,RAM使用的空间)
一个ARM程序包含3部分:RO段,RW段和ZI段
RO是程序中的指令和常量
RW是程序中的已初始化变量
ZI是程序中的零初始化的变量
由以上3点说明可以理解为:
RO就是readonly,
RW就是read/write,
ZI就是zero