Chinaunix首页 | 论坛 | 博客
  • 博客访问: 224754
  • 博文数量: 39
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 584
  • 用 户 组: 普通用户
  • 注册时间: 2015-04-18 20:24
个人简介

学习总结,分享快乐。 望指正错误, 共同提高!!!

文章分类

全部博文(39)

分类: 嵌入式

2015-05-01 11:19:52


0> 学习思路:先看下标准现场保护与恢复,然后通过不实现现场保护与恢复发生的问题,理解5行神奇代码。


1>【标准现场保护与恢复】 示例:

点击(此处)折叠或打开

  1. test.c

  2. #include <stdio.h>

  3. void fun(int a)
  4.  {
  5.         printf("***********************%d*****************",a);
  6. }

  7. int main(void)
  8.  {
  9.         int i = 1993;

  10.         fun(i);

  11.         return 0;
  12. }

arm-linux-gcc -g ap.c -mapcs             // -g:加了调试            -mapcs:完全安照APCS规编译

arm-linux-objdump -dS a.out >a.S     //带C语言反汇编

vim a.S :

    mov ip, sp

    push {fp, ip, lr, pc}

    sub fp, ip, #4

    ...

    sub sp, fp, #12

    ldm sp, {fp, sp, pc}

man函数和fun函数里都有这5行代码,他们就是传说中的保护现场,恢复现场。

                    下边就看看这5行的神奇。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

2>【问题1】源码:test.c

点击(此处)折叠或打开

  1. #include <stdio.h>

  2. __asm__(

  3.         "fool:\n\t"
  4.                 "adr r0, str\n\t"             //取地址 等价于:"ldr r0, = str\n\t"
  5.                 "ldr r1, data\n\t"            //取值

  6.                 "bl printf\n\t"             //问题就出在这。。。。。此处修改了lr。

  7.                 "mov pc, lr\n\t"             //函数返回      //最终死循环在此。

  8.           "str:\n\t"
  9.                     ".string \"This year is %d.\\n\"\n\t"     
  10.           "data:\n\t"
  11.                     ".word 2015\n\t"

  12. );

  13. int main(void)
  14. {

  15.         __asm__ __volatile__(

  16.                     "mov lr, pc\n\t"                     //保存print地址到lr。
  17.                     "ldr pc, =fool\n\t"             //伪指令

  18.         );

  19.         printf("Come back to %s!\n", __func__);

  20.         return 0;
  21. }

编译:arm-linux-gcc test.c

运行:./a.out:

[root@FriendlyARM /mnt]# ./a.out

This year is 2015.

_

死循环了,程序回不来了,分析...
man函数【25】行:lr中保存了子程序【fool】的返回地址,fool中 "bl printf\n\t"  覆盖了lr中的值(此时的lr中的值是【11】行地址)
                            当fool中的printf执行完,将lr赋给pc,
                            执行    "mov pc, lr\n\t"  形成死循环。 
指令注解: 
                  1》 "ldr r1, data\n\t"
                        数据加载指令:将内存中的数据加载到寄存器。此指令是标号寻址。
                                          pc是隐含的基址寄存器,偏移量是语句标号所在地址和pc(正在执行的指令)之间差值。                     
                 2》"ldr pc, =fool\n\t"
                        加载32位立即数或一个地址到指定寄存器。
                        
                  

解决办法:


点击(此处)折叠或打开

  1. #include <stdio.h>

  2.     __asm__(

  3.             "fool:\n\t"
  4.                     "adr r0, str\n\t"                 //取地址 等价于:"ldr r0, = str\n\t"
  5.                     "ldr r1, data\n\t"             //取值

  6.                      "push {lr}\n\t"

  7.                      "bl printf\n\t"
  8.      
  9.                     "pop {pc}\n\t"   

  10.               "str:\n\t"
  11.                         ".string \"This year is %d.\\n\"\n\t"
  12.               "data:\n\t"
  13.                         ".word 2015\n\t"

  14.     );

  15.     int main(void)
  16.     {

  17.             __asm__ __volatile__(

  18.                         "mov lr, pc\n\t"                 //保存print地址到lr。
  19.                         "ldr pc, =fool\n\t"                //伪指令

  20.             );

  21.             printf("Come back to %s!\n", __func__);

  22.             return 0;
  23.     }
分析:
"push {lr}\n\t"  指令相等于:"sub sp, sp, #4\n\t"         //因为栈是满递减的,所以先偏移4个字节。
                                                       "str   lr, [sp]\n\t"                //将lr的值保存到

"pop {pc}\n\t" 指令相等于:"ldr lr, [sp]\n\t"               //从栈中取出lr原值,保存到lr寄存器。
                                                       "add sp, sp, #4\n\t"      //sp+4移回原处。
                                                      “mov pc, lr\n\t"              //返回
问题解决

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


3> 【问题2】细想【问题1】你会发现有非常严重的问题。
                 栈 :   在程序中用的是非常频繁的,因为定义的局部变量就存放在栈中,所以要经常push,pop,sp的值就会一直改变。
                  这样当子程序返回时,你找lr的值,以及恢复到原来sp的值是非常木乱的 。


解决:思路:
                "mov ip, sp\n\t"      //将sp的值先保存到ip。
                "mov sp, ip\n\t"       //将ip的值赋给sp,还原了。

还有问题:APCS规则中ip是会改变的, 所以也得存入栈中,"push {ip,lr}\n\t" .

这样问题还是没解决:当子程序退出时你还得通过,【sp,#4×num】移动sp找到ip,lr的值,才能返回。
继续解决:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
好吧 , 借鉴标准:

点击(此处)折叠或打开

  1. #include <stdio.h>
  2.  
  3.    void fun(int a)
  4.    {
  5.            int q = 0x111111;
  6.            int w = 0x222222;
  7.            int c = 0x333333;
  8.            printf("***********************%x*****************", a);
  9.   }
  10.   
  11.  int main(void)
  12.  {
  13.          int i = 0x444444;
  14.  
  15.          fun(i);
  16.  
  17.          return 0;
  18.  }


点击(此处)折叠或打开

  1.  000083b4 :
  2.  #include
  3. void fun(int a)
  4.   {
  5.       83b4: e1a0c00d                 mov ip, sp
  6.       83b8: e92dd800                 push {fp, ip, lr, pc}
  7.       83bc: e24cb004                 sub fp, ip, #4

  8.       83c0: e24dd018                             sub sp, sp, #24
  9.       83c4: e50b0020                            str r0, [fp, #-32]

  10.           int q = 0x111111;
  11.      83c8: e59f3028                 ldr r3, [pc, #40] ; 83f8 <fun+0x44>
  12.      83cc: e50b3010                 str r3, [fp, #-16]

  13.           int w = 0x222222;
  14.      83d0: e59f3024                 ldr r3, [pc, #36] ; 83fc <fun+0x48>
  15.      83d4: e50b3014                 str r3, [fp, #-20]

  16.          int c = 0x333333;
  17.      83d8: e59f3020                  ldr r3, [pc, #32] ; 8400 <fun+0x4c>
  18.      83dc: e50b3018                  str r3, [fp, #-24]
  19.          printf("***********************%x*****************", a);
  20.      83e0: e59f301c                 ldr r3, [pc, #28] ; 8404 <fun+0x50>
  21.      83e4: e1a00003                 mov r0, r3
  22.      83e8: e51b1020                 ldr r1, [fp, #-32]
  23.      83ec: ebffffc2                     bl 82fc <_init+0x44>
  24.  }
  25.      83f0: e24bd00c                sub sp, fp, #12
  26.      83f4: e89da800                 ldm sp, {fp, sp, pc}

  27.      83f8: 00111111                    .word 0x00111111
  28.      83fc: 00222222                    .word 0x00222222
  29.      8400: 00333333                     .word 0x00333333
  30.      8404: 000084b4                     .word 0x000084b4
  31.  
  32.  00008408 <main>:
  33.  

  34.  int main(void)
  35.  {
  36.      8408: e1a0c00d                 mov ip, sp
  37.      840c: e92dd800                 push {fp, ip, lr, pc}
  38.      8410: e24cb004                 sub fp, ip, #4

  39.      8414: e24dd008                 sub sp, sp, #8

  40.          int i = 0x444444;
  41.      8418: e59f3018                ldr r3, [pc, #24] ; 8438 <main+0x30>
  42.      841c: e50b3010                 str r3, [fp, #-16]

  43.         fun(i);
  44.      8420: e51b0010                 ldr r0, [fp, #-16]
  45.      8424: ebffffe2                     bl 83b4 <fun>
  46.  
  47.          return 0;
  48.      8428: e3a03000                 mov r3, #0
  49.  }
  50.     842c: e1a00003                     mov r0, r3

  51.      8430: e24bd00c                 sub sp, fp, #12
  52.      8434: e89da800                  ldm sp, {fp, sp, pc}

  53.      8438: 00444444                 .word 0x00444444
分析:
                mov ip, sp      //ip寄存器保存sp寄存器的值。
               push {fp, ip, lr, pc}            //可以不考虑, pc用作调试。
                sub fp, ip, #4

分析fun函数   3条指令后栈中的造型:


高地址              →

。。。


sp指向位置    Sp    

。。。



Pc

←     fp(ip-4)


lr



ip


sp指向位置      Sp     →

fp

<======sp恢复现场时



<------------------------fp-16



<---------------fp-20

低地址              →


    <--------fp-24                        









<----------------[sp-24]





得到结果是:fp是帧指针寄存器, sub sp, sp, #24:指令先分配内存单元,以后的操作都以fp为基准。


恢复现场:
                   

                    sub    sp,    fp,   #12         //:sp指向  【sp恢复现场时】
                      ldm sp, {fp, sp, pc}        //相等于: ldmia sp, {fp, sp, pc} 
                            这样就是 fp <- fp, sp <- ip, pc <- lr     // fp, sp的值都恢复,  函数也返回了。
                    不要写成 ldmia sp!, {fp, sp, pc} 
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
总结:
            ip:保存sp原值,用于恢复sp。
            fp: 保存整个子程序栈的起始地址,对栈的操作,以它为坐标。瞧它的名字帧指针寄存器, 帧,恰当!!!
            sp:分配栈。这儿请注意,一定要分配足够,并且sp要更新,不然, 再调一个子程序时就会把主调函数的栈内容覆盖。 
这样,再也不用计算sp该偏移多少才能找到ip, lr的值。


注意:
        1》除了r15,其余都为通用寄存器,没什么特别的,只是APCS,一种规则,你完全可以用,r5等代替fp。
        有疑问r13,也是通用的吗?必须是,只是APCS规定。
        2》上面说的某个寄存器保存什么值,但你要明白,他们值都保存在栈当中。



















阅读(2664) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~