ADS简单程序编译分析

一个简单的C语言程序在ADS编译器下变成了数百行的汇编代码,本文对汇编代码进行了分析
摘要由CSDN通过智能技术生成

前言

最近准备将公司的产品进行更新,目前的产品是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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值