前言
最近准备将公司的产品进行更新,目前的产品是ucos操作系统混合了产品程序一起烧写到LPC2148的512k FLASH内部,这样做有个弊端,就是一旦产品程序出了错误,无法让客户自行更新程序,因此希望改成操作系统与产品程序分离,在必要的时候执行将tf卡内部的程序烧写道FLASH内部进行产品程序更新。操作系统程序一旦固化入FLASH则尽量不去修改,目前没有设计更新操作系统的方法。
如此一来,512K FLASH的底层空间由操作系统占用,程序上电从0地址运行,此时为操作系统端,此后程序将运行到能够切换到用户状态加载程序处。操作系统把需要的程序接口地址写入一个约定地址(比如读取TF卡),并没有使用swi(软件中断),因为这种形式修改起来方便,都是现成的程序。如果将来再次升级系统可以改变为swi调用系统函数。
系统按照与用户程序约定的地址调用用户程序,用户调用系统程序时也使用约定的地址寻找程序地址,因此首先需要了解ADS是如何编译程序的,因此进行了下面的分析:
C代码
首先,用ADS创建一个工程,输入以下代码:
void fun1()
{
char *p;
int i;
p = (char *)0x40004000;
for(i = 0; i < 16; i++)
{
*p = i;
p++;
}
}
int main()
{
fun1();
return 1;
}
在编译器设置里,将Linker-ARM linker下的link type设置为simple,RO Base设置为0x29c00,这是程序将来将要烧写到的位置。RW Base设置为0x40003000这是将来保存变量的区域,此后在Linker-ARM fromELF功能里选择生成Text Information,设置好文件名,将会产生出文本文档形式的ELF文件详细说明,其中我们需要关注的是将要烧写到ROM中的代码段,附录里是摘抄出来的完整的编译后的汇编代码。
汇编程序分析
下面是对程序的分析:
首先,因为设置了RO Base为0x29c00,因此在执行此程序的时候,内存地址应为0x29c00,且PC(指令计数器)也应当指向此处。
__main
$a
!!!
0x00029c00: e28f8090 .... ADD r8,pc,#0x90 ; #0x29c98
0x00029c04: e898000f .... LDMIA r8,{r0-r3}
0x00029c08: e0800008 .... ADD r0,r0,r8
0x00029c0c: e0811008 .... ADD r1,r1,r8
0x00029c10: e0822008 . .. ADD r2,r2,r8
0x00029c14: e0833008 .0.. ADD r3,r3,r8
0x00029c18: e240b001 ..@. SUB r11,r0,#1
0x00029c1c: e242c001 ..B. SUB r12,r2,#1
可以看到第一行即程序的起始地址就是这个数,将来烧写的时候,烧写器会根据烧写地址放入对应位置(LPC2148的前512kb字节为Flash,属于非易失存储器,因此程序保存在这个范围内)。第一条语句将pc加上0x90放入r8,此时pc指向的是下一个取命令地址,也可能是因为一次取16字节命令,所以pc的值为0x29c08,则r8此后为0x29c98。下一条语句的意思是从r8开始,取连续的4个地址的值,放入r0-r3。
_region_table
$d
0x00029c98: 0000032c ,... DCD 812
0x00029c9c: 00000350 P... DCD 848
0x00029ca0: 00000350 P... DCD 848
0x00029ca4: 00000368 h... DCD 872
因此r0=812, r1=848, r2=848, r3=872。
后面的几个语句为r0=r0+r8…r3=r3+r8,r11=r0-1,r12=r2-1
通过计算,r0=0x29fc4, r1=0x29fe8, r2=0x29fe8, r3=0x2a000,可以看出来,这应该还是程序段内部的地址,他们对应的是下面的代码:
Region$$Table$$Base
$d
0x00029fc4: 00000000 .... DCD 0
0x00029fc8: 00000000 .... DCD 0
0x00029fcc: 00000000 .... DCD 0
0x00029fd0: 0002a000 .... DCD 172032
0x00029fd4: 40003000 .0.@ DCD 1073754112
0x00029fd8: 00000000 .... DCD 0
0x00029fdc: 0002a000 .... DCD 172032
0x00029fe0: 40003000 .0.@ DCD 1073754112
0x00029fe4: 00000000 .... DCD 0
Region$$Table$$Limit
ZISection$$Table$$Base
0x00029fe8: 0002a000 .... DCD 172032
0x00029fec: 00000000 .... DCD 0
0x00029ff0: 40003000 .0.@ DCD 1073754112
0x00029ff4: 00000000 .... DCD 0
0x00029ff8: 40003000 .0.@ DCD 1073754112
0x00029ffc: 00000060 `... DCD 96
ZISection$$Table$$Limit
可以看出分别对应了一个叫做Region表的起始和结束地址,以及一个叫做ZISection表的起始和结束地址。这大概对应了程序中的全局数据段以及函数变量段,他们的区别就是一个要全程保留且要初始化,分配在堆里面,另一个在函数内才会被分配空间,即在堆栈内分配空间,一旦函数释放了数据就没有义务被保存,初始化的时候需要清零。从名字可以看出来ZISection应当对应了Zero Initial Section,所以是函数变量的空间。他们都有几个特殊数字,分别对应了编译器指定的RO Base地址0x40003000,以及代码段的结尾0x2a000,功能后面再分析。
后面的代码为:
_move_region
0x00029c20: e1500001 ..P. CMP r0,r1
0x00029c24: 0a00000e .... BEQ _zero_region ; 0x29c64
0x00029c28: e8b00070 p... LDMIA r0!,{r4-r6}
0x00029c2c: e1540005 ..T. CMP r4,r5
0x00029c30: 0afffffa .... BEQ _move_region ; 0x29c20
0x00029c34: e3140001 .... TST r4,#1
0x00029c38: 1084400b .@.. ADDNE r4,r4,r11
0x00029c3c: e3150001 .... TST r5,#1
0x00029c40: 1085500b .P.. ADDNE r5,r5,r11
0x00029c44: e3150002 .... TST r5,#2
0x00029c48: 10855009 .P.. ADDNE r5,r5,r9
0x00029c4c: e3c55003 .P.. BIC r5,r5,#3
此段的名称可以看出来是移动region表,前二行为比较r0,r1,若相等说明没有region表,则跳到_zero_region执行。
LDMIA r0!,{r4-r6}
此句多了一个’!’符号,表示执行后将最终地址放入r0。参考region表,可以知道r4-r6均为0,r0=0x00029fd0。
0x00029c2c: e1540005 ..T. CMP r4,r5
0x00029c30: 0afffffa .... BEQ _move_region ;
若r4,r5相等则跳回_move_region执行,此时r0的值已经改变,则再次运行到此的时候,r4=172032(0x2a000), r5=1073754112(0x40004000), r6=0, r0=0x00029fdc,此后由于r4!=r5,程序向下进行。
0x00029c34: e3140001 .... TST r4,#1
0x00029c38: 1084400b .@.. ADDNE r4,r4,r11
0x00029c3c: e3150001 .... TST r5,#1
0x00029c40: 1085500b .P.. ADDNE r5,r5,r11
0x00029c44: e3150002 .... TST r5,#2
0x00029c48: 10855009 .P.. ADDNE r5,r5,r9
0x00029c4c: e3c55003 .P.. BIC r5,r5,#3
TST的意思是,r4与后面的立即数位与,并根据结果设置标志位,由于r4没有设置最低位,因此结果为0,后面的ADDNE将不会执行。
BIC是清位操作,等同于等同于 R5 &=~(0x03)
在此程序中实际上此段未进行什么实质处理。
_move_loop
0x00029c50: e2566004 .`V. SUBS r6,r6,#4
0x00029c54: 24947004 .p.$ LDRCS r7,[r4],#4
0x00029c58: 24857004 .p.$ STRCS r7,[r5],#4
0x00029c5c: 8afffffb .... BHI _move_loop ; 0x29c50
0x00029c60: eaffffee .... B _move_region ; 0x29c20
第一句SUBS r6,r6,#4
等同于r6=r6-4,并根据结果设置标志位。减法是将操作数求补后相加,这里由于执行前r6=0因此将不设置标志位。LDRCS r7,[r4],#4
找不到对应的指令说明,但根据字面意思可以得到,如果标志位被置位,则执行r7=[r4](即r4指向地址的值),r4=r4+4,这里应当不会执行。同理下面一句r7,[r5],#4
也不执行。BHI _move_loop
如果进位位被置位则跳转到_move_loop此处也不执行。
此后依然跳转回_move_region执行,将会按照前面解释的过程再执行一次,此次运行到跳转回_move_region的位置时,r0=0x00029fe8,与r1相等了,程序将跳出_move_region转到_zero_region执行。可以看出,此段程序根据region中段的数量可以进行多次拷贝,本程序共拷贝了2次。
_zero_region
0x00029c64: e1520003 ..R. CMP r2,r3
0x00029c68: 0b000018 .... BLEQ __rt_entry ; 0x29cd0
0x00029c6c: e3a07000 .p.. MOV r7,#0
0x00029c70: e8b20030 0... LDMIA r2!,{r4,r5}
0x00029c74: e3140001 .... TST r4,#1
0x00029c78: 1084400c .@.. ADDNE r4,r4,r12
0x00029c7c: e3140002 .... TST r4,#2
0x00029c80: 10844009 .@.. ADDNE r4,r4,r9
0x00029c84: e3c44003 .@.. BIC r4,r4,#3
_zero_loop
0x00029c88: e2555004 .PU. SUBS r5,r5,#4
0x00029c8c: 24847004 .p.$ STRCS r7,[r4],#4
0x00029c90: 8afffffc .... BHI _zero_loop ; 0x29c88
0x00029c94: eafffff2 .... B _zero_region ; 0x29c64
与region一样,首先比较r2,r3,若相等就跳转到__rt_entry。BL是需要返回值得跳转,此语句默认设置R14作为返回值地址(在程序中不会显式的看到此操作)。实际上此段程序将会循环三次,直到第三次,r5=96,将会连续写24个0到0x40003000开头的位置。此后r2=r3=0x2a000,程序跳转到__rt_entry。
__rt_entry
$a
.text
0x00029cd0: eb000046 F... BL __rt_stackheap_init ; 0x29df0
0x00029cd4: eb0000ad .... BL $Ven$AT$L$$__rt_lib_init ; 0x29f90
0x00029cd8: e59fc01c .... LDR r12,0x29cfc
BL为要求返回的跳转,将会把PC的值保存在r14中,作为以后返回此处的地址。可见先调用了__rt_stackheap_init,位于地址0x29df0。此后调用了$Ven$AT$L$$__rt_lib_init,位于地址0x29f90,最后把0x29cfc赋值到r12。下面分析两个函数的功能:
1.函数__rt_stackheap_init
__rt_stackheap_init
$a
0x00029df0: e1a0500e .P.. MOV r5,r14
0x00029df4: eb000047 G... BL __user_libspace ; 0x29f18
0x00029df8: e1a0e005 .... MOV r14,r5
首先,把r14保存起来,作为以后返回的地址,此后调用__user_libspace,
__user_libspace
$a
0x00029f18: e59f0000 .... LDR r0,0x29f20
0x00029f1c: e12fff1e ../. BX r14
$d
0x00029f20: 40003000 .0.@ DCD 1073754112
此函数功能很简单,就是把0x40003000放入r0后返回。下面回到__rt_stackheap_init,恢复此前保存的r14值,r5可以用作他处了。
0x00029dfc: e1a04000 .@.. MOV r4,r0
0x00029e00: e1a0100d .... MOV r1,r13
0x00029e04: e1a0300a .0.. MOV r3,r10
r4=0x40003000,r1=r13,r3=r10其中arm通常使用r13作为sp(堆栈)地址寄存器,所以r1表示的是当前堆栈的底部,r10的作用暂时未知,r3被赋值后未见何特殊操作。
0x00029e08: e3c00007 .... BIC r0,r0,#7
0x00029e0c: e280d060 `... ADD r13,r0,#0x60
0x00029e10: e92d4010 .@-. STMFD r