您的位置:首页 > 其它

day10 ARM伪指令、ARM混合调用

2017-06-16 17:20 232 查看
回顾:

面试题:谈谈对ARM处理器的认识

1.常见的处理器架构

2.ARM的定义

3.ARM的版本和流水线

4.ARM的7种工作模式

5.ARM的2种工作状态

6.ARM的37个寄存器

7.ARM的7种异常

8.ARM的异常处理流程(核心)

9.ARM指令

  1.移位操作符

  2.影响cpsr的两种情形

  3.CPU核跳转的三种方式

  4.分支跳转指令b/bl

  5.数据处理指令

        数据传送指令mov/mvn

        算数运算指令add/adc/sub/sbc

        位运算指令and/orr/eor/bic

        比较指令cmp/cmn/tst/teq   

  6.存储加载指令:ldr/str

    6.1.明确:数据处理指令(四大类)仅仅在ARM核

              内部玩,操作的对象是ARM寄存器立即数

              数据处理指令不会和外设进行数据交互

              问:汇编中如何实现CPU核和外设数据交互呢?

              答:利用存储加载指令

    6.2.明确:数据加载指令用于实现CPU核和外设的

              数据交互,数据的传输(双向),可以加载

              也可以存储

              问:何为加载,何为存储呢?

              答:

              加载:数据是从外设到CPU核内部寄存器

              存储:数据是从CPU核内部寄存器到外设

              即:

              加载指令为ldr

              存储指令为str

      

     6.3.明确:CPU核访问外设都是以地址的形式访问

               也就是直到了外设的地址,CPU核即可访问

                         

     6.3.加载存储指令ldr,str

     ldr指令作用:将外设中的数据加载到CPU核寄存器中

     str指令作用:将CPU核中的数据存储到外设中

     例如:

         mov r0, #0x48000004 @假设成立,结果:r0=0x48000000

         ldr r1, [r0] @以r0寄存器中的数0x48000000为地址

                       从这个地址中(内存的存储空间)取出

                       4字节数据放到ARM核寄存器r1中

      add r1, r1, #1 @r1=r1+1

      str r1, [r0] @将r1寄存器中的数据存储到以

                    r0寄存器中的数据为地址的外设中

      务必画出以str的存储操作示意图!

    

   7.ARM核栈操作指令

     7.1.明确:栈的本质和功能

         栈本质就是某块内存存储区域

         栈用来保存数据(临时变量,函数参数)

         栈的操作就是压栈和出栈   

              ARM核寄存器sp保存栈指针

               

     7.2.ARM核栈的分类

     减栈:sp向内存地址减小的方向变化

     加栈:sp向内存地址增加的方向变化

     空栈:先压数后调整sp

     满栈:先调整sp后压数

     总结:由以上四种基本栈有得到组合形式:

     满减栈/满加栈/空减栈/空加栈

     只掌握满减栈即可!

      

     7.3.满减栈的操作指令

     老ARM核(ARMV7之前版本),满减栈的操作指令为:stmfd/ldmfd

     新ARM核(ARMV7以后版本),满减栈的操作指令为:push/pop

     注意:ARM核小标号的寄存器数据放到内存的低地址处理!

      

     例如:

     老写法:

     压栈:stmfd sp!, {r4-r7,lr}

                  说明:将ARM寄存器r4,r5,r6,r7,lr的数据保存在栈中

                   

     出栈:ldmfd sp!, {r4-r7,pc}  

                  说明:从栈中将数据恢复到r4,r5,r6,r7,pc寄存器中

      

     新写法:

         压栈:push {r4-r7,lr}

                  说明:将ARM寄存器r4,r5,r6,r7,lr的数据保存在栈中

                   

     出栈: pop  {r4-r7,pc}  

                  说明:从栈中将数据恢复到r4,r5,r6,r7,pc寄存器中                    

     务必举例子画出压栈和出栈的操作示意图!

    

   8.状态寄存器(cpsr/spsr)访问指令mrs/msr

     明确:掌握cpsr的两个重要域

           f域:cpsr的bit[31:24]

           c域:cpsr的bit[8:0]

     例如:

             mrs r0, cpsr @将cpsr的值保存在r0寄存器中

             mrs r1, spsr @将spsr的值保存在r1寄存器中

             

        msr cpsr, r0 @将r0寄存器中的数据写到cpsr

        msr cpsr_f, r0 @将r0寄存器中的数据写到cpsr

                        但仅仅影响f域         

     

    9.ARM伪指令

      9.1.明确:ARM伪指令CPU核是不能直接识别的

                更不能去直接运行的,首先需要汇编器(arm...as)

                进行翻译,最终翻译成CPU核识别的真实指令

       

      9.2.ARM伪指令之adr/adrl  

      adr指令用于地址加载

      加载地址范围:+/-1020bytes

      加载地址的范围是相对于PC

       

      案例:通过反汇编掌握adr指令的操作

      mkdir /opt/arm/day10/1.0 -p

      cd /opt/arm/day10/1.0

      vim adr.s 添加如下内容

      .text

      .code 32

      .global _start

      _start:

            adr r0, Delay @将Delay标签的地址加载到

                                         r0寄存器中,也可以认为是

                                         将Delay标签中第一条指令的

                                         地址加载到r0中

                                       问:adr伪指令到底由汇编器

                                           翻译成了什么模样呢?

          mov r1, #1

          add r1, r1, #15

             

      Delay:

            mov r1, #4

            

      .end

      保存退出

      arm-cortex_a9-linux-gnueabi-as -g -o adr.o adr.s

      arm-cortex_a9-linux-gnueabi-objdump -D adr.o > adr.dis

      vim adr.dis  

          00000000 <_start>: @入口函数_start的地址为0x00000000

            0:   e28f0004        add     r0, pc, #4

            4:   e3a01001        mov     r1, #1

                 8:   e281100f        add     r1, r1, #15

       

             0000000c <Delay>: @Delay标签的地址为0xc

               c:   e3a01004        mov     r1, #4

               说明:

               第一列表示每条指令对应的内存存储地址

               第二列表示每条指令对应的机器码(给CPU核用)

               第三列表示每条机器码对应的汇编代码(给人用)

               分析:

               1.源码中第一条指令为adr,通过汇编器翻译成了add真实指令

               2.adr指令的功能就是将Delay标签的地址加载到r0

               中,运算实现过程:

                 1.当这条指令add r0, pc, #4执行时

                   pc=8,add执行以后,r0=pc+4=8+4=12=0xC

                 2.0xc这个数对应的就是mov r1, #4这条的

                   的地址,并且这条指令的地址和Delay标签的

                   地址一样都是0xC  

                   至此也就验证了adr指令就是用于加载地址

 

                  9.3.ARM伪指令之ldr

                  1.问:ldr到底是真实指令还是伪指令呢?

                  答:关键看ldr使用如何

                  2.ldr作为伪指令的三种使用方式

                  方式一:

                  mov r0, #0x1ff @不合法,编译不通过,立即数超范围

                  ldr r0, =0x1ff @合法,编译通过

                  @将立即数放到r0寄存器中

                  @此时的ldr为伪指令

                  方式二:

                  ldr r0, =testdata @将testdata标签对应的地址

                  加载到r0寄存器中

                  也就是r0保存分配的四字节内存空间的首地址

                  @此时ldr为伪指令

                  ldr r1, [r0] @以r0寄存器中的数据为地址

                  从这个地址中取出4字节的数据

                  放到r1寄存器

                  r1=0x12345678

                  @此时ldr为真实指令

                  ...

                  testdata:@testdata就是分配的4字节内存空间的首地址

                  .word 0x12345678 @分配4字节的内存空间

                  @并且将这块内存空间初始化为

                  0x12345678

                  @.word类似int

 

                  方式三:

                  ldr r0, testdata @将以testdata标签对应的地址

                  直接从这个地址中取出数据给r0

                  r0=0x12345678

                  @此时ldr为伪指令

                  ...

                  testdata:

                  .word 0x1234578

 

              案例:编写汇编代码,然后反汇编,掌握ldr伪指令

              cd /opt/arm/day10/2.0

              vim ldr.s 添加如下内容

              .text

              .code 32

              .global _start

              _start:

                      ldr r0, =testdata

                      ldr r1, [r0]

                      mov r2, #15

                      add r2, r2, #1

               

              testdata:

                      .word 0x12345678

                          

              .end

              保存退出

              arm-cortex_a9-linux-gnueabi-as -g -o ldr.o ldr.s

              arm-cortex_a9-linux-gnueabi-objdump -D ldr.o > ldr.dis

              vim ldr.dis //分析反汇编代码

              结论:

              1.ldr伪指令最终翻译的真实指令还是ldr

              再例如:

                 ldr pc, jmp_table  

                 jmp_table:

                     .word func_addr     

                 结果:让CPU核跑到func_addr地址处继续运行(F->D->E->M->W)

         

         10.ARM伪操作(目前共121个伪操作)

            .text/.data/.bss/.global/.extern/.byte

            /.word/.int/.long/.string/.ascii/.asciz

            /.equ/.skip/.space等

            例如:

            .equ TEST_NUM, #0x20

            等价于:

            #define TEST_NUM  0x20

             

            .ascii  "hello,world\0"

            或者

            .asciz  "hello,world"

            或者

            .string "hello,world" @分配一块存储区域

                                                         并且初始化为“hello,world”

            问:如何获取这块存取区域的首地址呢?

            答:只需加一个标签即可,标签就是这块存储区域的首地址

            str1: @char *str1 = "hello"

                        .asciz "hello"

            str2: @char *str2 = "hfllo"

                        .asciz "hfllo"

                          

         案例:利用汇编实现两个字符串的比较

         回顾C实现的字符串比较

         str1 = "hello";

         str2 = "hfllo"

         int my_strcmp(const char *str1,  

                                     const char *str2)

         {

              while(*str1) {

                      if(*str1 != *str2)

                          return *str1 - *str2;

                          str1++;

                          str2++;

              }

              return *str1 - *str2;

         }    

         

         实施步骤:

         mkdir /opt/arm/day10/3.0

    vim strcmp.s 添加如下内容

    .text  

    .code 32

    .global _start

    _start:

        

        ldr r0, =str1 @r0保存字符串str1的首地址

        ldr r1, =str2 @r1保存字符串str2的首地址

     

    loop:

        ldrb r2, [r0], #1 @取出字符串的单个字符数据

        ldrb r3, [r1], #1

        cmp r2, #0

        beq cmp_end

        cmp r2, r3

        beq loop

        

    cmp_end:

        sub r0, r2, r3

        b   .

            

    @.ascii:分配一块内存空间,存放字符串数据信息

    str1: @相当于给"hello\0"定义一个首地址    

        .ascii "hello\0" @char *str1 = "hello";

    str2: @相当于给"hfllo\0"定义一个首地址

        .ascii "hfllo\0" @char *str2 = "hfllo";

          

    .end

     

    编译利用qemu测试,通过观察r0寄存器的值判断大小  

         

11.ARM的混合调用:C调用汇编/汇编调用C

   11.1.明确:以下存储器设备的访问速度

        ARM核寄存器>Cache缓存->内存>闪存

         

   11.2.明确:函数之间传递参数的方式方法两种:

    1.默认采用ARM核寄存器传递参数(ARM核寄存器速度快)

      注意:ARM核寄存器中能够传参参数的寄存器只有四个:

            r0/r1/r2/r3

      注意:函数的返回值用r0寄存器保存

      例如:

          int add(int a, int b, int c, int d){

                              r0     r1     r2     r3

            return a+b+c+d; //r0=r0+r1+r2+r3

        }

      注意:如果参数的个数超过四个,用栈进行传递,但是栈(内存)的访问速度势必要比ARM核寄存器的访问速度要慢,所以尽量让参数的个数小于4,如果大于4,让访问次数的参数放在最前面!

         int add(int a, int b, int c, int d, int e, int f){

                              r0     r1     r2     r3    栈      栈

            return a+b+c+d+e+f; //r0=r0+r1+r2+r3

         }     

       

      2.在linux内核中,强制在函数前面加关键字asmlinkage要求函数传递参数使用栈,而无需ARM寄存器 。

        asmlinkage int add(int a, int b, int c...);

        给add传递参数强制使用栈!

         

        问:为何不用寄存器呢(速度快)?

        答:由于ARM核寄存器的个数太少了,linux内核尽量让ARM核寄存器在一些效率要求特别高的场合才能使用:

            reigster struct task_struct *current;

            告诉编译器,单独指定一个ARM寄存器来保存current的值,将来访问效率提高!

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: