day10 ARM伪指令、ARM混合调用

来源:互联网 发布:交易宝行情软件 编辑:程序博客网 时间:2024/06/08 19:54
回顾:
面试题:谈谈对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的值,将来访问效率提高!
 
原创粉丝点击