登录站点

用户名

密码

ARM汇编学习笔记

已有 2991 次阅读  2009-09-11 15:46   标签ARM  笔记  汇编  学习 
这两天参加了一个编写操作系统的项目,因为要做很多底层的东西,而且这个操作系统是嵌入式的,所以开始学习ARM汇编,发现ARM汇编和一般PC平台上的汇编有很多不同,但主要还是关键字和伪码上的,其编程思想还是相同的。现将一些学习感悟部分列出来,希望能给有问题的人一点帮助。
    1、ARM汇编的格式:
    在ARM汇编里,有些字符是用来标记行号的,这些字符要求顶格写;有些伪码是需要成对出现的,例如ENTRY和END,就需要对齐出现,也就是说他们要么都顶格,要么都空相等的空,否则编译器将报错。常量定义需要顶格书写,不然,编译器同样会报错。
    2、字符串变量的值是一系列的字符,并且使用双引号作为分界符,如果要在字符串中使用双引号,则必须连续使用两个双引号。
    3、在使用LDR时,当格式是LDR r0,=0x022248,则第二个参数表示地址,即0x022248,同样的,当src变量代表一个数组时,需要将r0寄存器指向src则需要这样赋值:LDR r0,=src     当格式是LDR r0,[r2],则第二个参数表示寄存器,我的理解是[]符号表示取内容,r2本身表示一个寄存器地址,取内容候将其存取r0这个寄存器中。
    4、在语句:
       CMP r0,#num
       BHS stop
       书上意思是:如果r0寄存器中的值比num大的话,程序就跳转到stop标记的行。但是,实际测试的时候,我发现如果r0和num相等也能跳转到stop标记的行,也就是说只要r0小于num才不会跳转。
   
     下面就两个具体的例子谈谈ARM汇编(这是我昨天好不容易看懂的,呵呵)。
     第一个是使用跳转表解决分支转移问题的例程,源代码如下(保存的时候请将文件后缀名改为s):  
      AREA JumpTest,CODE,READONLY
      CODE32
 num  EQU  4

 ENTRY
 
start
      MOV  r0, #4
      MOV  r1, #3
      MOV  r2, #2
      MOV  r3, #0
 
      CMP  r0,  #num
      BHS  stop
 
      ADR  r4, JumpTable
 
      CMP  r0, #2
      MOVEQ  r3, #0
      LDREQ  pc, [r4,r3,LSL #2]
 
      CMP  r0, #3
      MOVEQ  r3, #1
      LDREQ  pc, [r4,r3,LSL #2]
 
      CMP  r0, #4
      MOVEQ  r3, #2
      LDREQ  pc, [r4,r3,LSL #2]
 
      CMP  r0, #1
      MOVEQ  r3, #3
      LDREQ  pc, [r4,r3,LSL #2]
 
DEFAULT
      MOVEQ  r0, #0
 
SWITCHEND

stop
      MOV  r0, #0x18
      LDR  r1, =0x20026
      SWI  0x123456
 
JumpTable
      DCD  CASE1
      DCD  CASE2
      DCD  CASE3
      DCD  CASE4
      DCD  DEFAULT
 
CASE1
      ADD  r0, r1, r2
      B  SWITCHEND
 
CASE2
      SUB  r0, r1, r2
      B  SWITCHEND
 
CASE3
      ORR  r0, r1, r2
      B  SWITCHEND
 
CASE4
      AND  r0, r1, r2
      B  SWITCHEND
 END
    程序其实很简单,可见我有多愚笨!还是简要介绍一下这段代码吧。首先用AREA伪代码加上CODE,表明下面引出的将是一个代码段(于此相对的还有数据段DATA),ENTRY 和END成对出现,说明他们之间的代码是程序的主体。start段给寄存器初始化。ADR  r4, JumpTable一句是将相当于数组的JumpTable的地址付给r4这个寄存器。

    stop一段是用来是程序退出的,第一个语句“MOV r0,#0x18”将r0赋值为0x18,这个立即数对应于宏angel_SWIreason_ReportException。表示r1中存放的执行状态。语句“LDR r1,=0x20026”将r1的值设置成ADP_Stopped_ApplicationExit,该宏表示程序正常退出。然后使用SWI,语句“SWI 0x123456”结束程序,将CPU的控制权交回调试器手中。

    在JumpTable表中,DCD类型的数组包含四个字,所以,当实现CASE跳转的时候,需要将给出的索引乘上4,才是真正前进的地址数。

 

    再看一个用汇编实现冒泡排序的例程:

     AREA Sort,CODE,READONLY
 ENTRY
 
start
     MOV r4,#0
     LDR r6,=src
     ADD r6,r6,#len
 
outer
     LDR r1,=src
 
inner
     LDR r2,[r1]
     LDR r3,[r1,#4]
     CMP r2,r3
     STRGT r3,[r1]
     STRGT r2,[r1,#4]
     ADD r1,r1,#4
     CMP r1,r6
     BLT inner
 
     ADD r4,r4,#4
     CMP r4,#len
     SUBLE r6,r6,#4
     BLE outer
 
stop
     MOV r0,#0x18
     LDR r1,=0x20026
     SWI 0x123456
 
     AREA Array,DATA,READWRITE
src DCD 2,4,10,8,14,1,20
len EQU 7*4
     END
     用汇编实现循环需要跳转指令,但是因为ARM系统只有一个CPSR寄存器,所以要实现双重循环还是有些难度。上面这个代码还是有相当大的借鉴意义。程序不难读懂,和C语言的冒泡排序基本思路是完全一样的。

 

 

 


Load CodeWarrior from the Start Menu.
Create a new project (File | New), select ARM Executable Image and give it the name "hello".
Create a new assembler source file (File | New Text File) and paste the following code in it.
            ; Hello world in ARM assembler

            AREA text, CODE
            ; This section is called "text", and contains code

            ENTRY

            ; Print "Hello world"

            ; Get the offset to the string in r4.
            adr       r4, hello           ;; "address in register"

loop        ; "loop" is a label and designates an address
            ; Call putchar to display each character
            ; to illustrate how a loop works

            ldrb      r0, [r4], #1        ; Get next byte and post-index r4
            cmp       r0, #0              ; Stop when we hit a null
            beq       outputstring        ;; "branch if equal" = cond. goto

            bl        putchar            
            b         loop                ;; "branch" =  goto

outputstring
            ; Alternatively, use putstring to write out the
            ; whole string in one go
            adr       r0, hello
            bl        putstring           ;; "branch+link" = subroutine call

finish
            ; Standard exit code: SWI 0x123456, calling routine 0x18
            ; with argument 0x20026
            mov       r0, #0x18
            mov       r1, #0x20000        ; build the "difficult" number...
            add       r1, r1, #0x26       ; ...in two steps
            SWI       0x123456            ;; "software interrupt" = sys call

hello
            DCB       "Hello World\n",0

            END

 

 

 

 

 

 

 


  

 


从下面的一个ARM 汇编小程序要弄懂的以下三个问题:

1).在ARM状态转到THUNB状态和BX的应用

2).汇编的架构

3).SWI指令的使用

    AREA    ADDREG,CODE,READONLY

     ENTRY

MAIN

      ADR  r0,ThunbProg + 1  ;(为什么要加1呢?因为BX指令跳转到指定的地址执行程序   时,   若   (BX{cond}  Rm)Rm的位[0]为1,则跳转时自动将CPSR中的标志T置位即把目标 代码解释为 Thunb代码)

        BX    r0

       CODE16

ThunbProg

       mov r2,#2

      mov r3,#3

     add r2,r2,r3

    ADR r0,ARMProg

    BX  ro

    CODE32

ARMProg

     mov r4,#4

    mov r5,#5

    add  r4,r4,r5

             stop   mov r0,#0x18

              LDR  r1,=0x20026

             SWI   0x123456

             END

SWI--软中断指令:

SWI指令用于产生软中断,从拥护模式变换到管理模式,CPSR保存到管理模式的SPSR中.

 SWI{cond}      immed_24      ;immed_24为软中断号(服务类型)

使用SWI指令时,通常使用以下两种方法进行传递参数,SWI 异常中断处理程序就可以提供相关的服务,这两种方法均是用户软件协定.SWI异常中断处理程序要通过读取引起软中断的SWI指令,以取得24位立即数.

(1) 指令中的24位立即数指定了用户请求的服务类型,参数通过通用寄存器传递.

 mov   r0,#34    ;设置子功能号位34

   SWI   12     ;调用12号软中断

(2) 指令中的24位立即数被忽略,用户请求的服务类型有寄存器RO的值决定,参数通过其他的通用寄存器传递.

 mov  r0,#12         ;调用12号软中断

 mov r1,#34         ;设置子功能号位34

 SWI  0

在SWI异常中断处理程序中,取出SWI立即数的步骤为:首先确定引起软中断的SWI指令是ARM指令还是Thunb指令,这可通过对SPSR访问得到;然后取得该SWI指令的地址,这可通过访问LR寄存器得到;接着读出指令,分解出立即数.如如下程序:

T_bit              EQU                    0X20

SWI_Handler

                STMFD      SP!,{R0-R3,R12,LR}                  ;现场保护

               MRS           R0,SPSR                                   ;读取SPSR

              STMFD       SP!,{R0}                                     :保存SPSR

              TST             R0,#T_bit                       

            LDRNEH        R0,[LR,#-2]                       ;若是Thunb指令,读取指令码(16位)

   BICNE             R0,#0XFF00                     :取得Thunb指令的8位立即数

   LDREQ           R0,[LR,#-4]                      ;若是ARM指令,读取指令码(32位)

   BICEQ            R0,#0XFF000000           ;取得ARM指令的24位立即数

   ....

   LDMFD          SP!,{R0-R3,R12,PC}^    ;SWI异常中断返回

    

 

 


ARM汇编的SWI指令软中断

从下面的一个ARM 汇编小程序要弄懂的以下三个问题:

1).在ARM状态转到THUNB状态和BX的应用

2).汇编的架构

3).SWI指令的使用

    AREA    ADDREG,CODE,READONLY

     ENTRY

MAIN

      ADR  r0,ThunbProg + 1  ;(为什么要加1呢?因为BX指令跳转到指定的地址执行程序   时,   若   (BX{cond}  Rm)Rm的位[0]为1,则跳转时自动将CPSR中的标志T置位即把目标 代码解释为 Thunb代码)

        BX    r0

       CODE16

ThunbProg

       mov r2,#2

      mov r3,#3

     add r2,r2,r3

    ADR r0,ARMProg

    BX  ro

    CODE32

ARMProg

     mov r4,#4

    mov r5,#5

    add  r4,r4,r5

             stop   mov r0,#0x18

              LDR  r1,=0x20026

             SWI   0x123456

             END

SWI--软中断指令:

SWI指令用于产生软中断,从拥护模式变换到管理模式,CPSR保存到管理模式的SPSR中.

 SWI{cond}      immed_24      ;immed_24为软中断号(服务类型)

使用SWI指令时,通常使用以下两种方法进行传递参数,SWI 异常中断处理程序就可以提供相关的服务,这两种方法均是用户软件协定.SWI异常中断处理程序要通过读取引起软中断的SWI指令,以取得24位立即数.

(1) 指令中的24位立即数指定了用户请求的服务类型,参数通过通用寄存器传递.

 mov   r0,#34    ;设置子功能号位34

   SWI   12     ;调用12号软中断

(2) 指令中的24位立即数被忽略,用户请求的服务类型有寄存器RO的值决定,参数通过其他的通用寄存器传递.

 mov  r0,#12         ;调用12号软中断

 mov r1,#34         ;设置子功能号位34

 SWI  0

在SWI异常中断处理程序中,取出SWI立即数的步骤为:首先确定引起软中断的SWI指令是ARM指令还是Thunb指令,这可通过对SPSR访问得到;然后取得该SWI指令的地址,这可通过访问LR寄存器得到;接着读出指令,分解出立即数.如如下程序:

T_bit              EQU                    0X20

SWI_Handler

                STMFD      SP!,{R0-R3,R12,LR}            ;现场保护

               MRS           R0,SPSR                           ;读取SPSR

              STMFD       SP!,{R0}                            :保存SPSR

              TST             R0,#T_bit                       

            LDRNEH        R0,[LR,#-2]              ;若是Thunb指令,读取指令码(16)

   BICNE             R0,#0XFF00                  :取得Thunb指令的8位立即数

   LDREQ           R0,[LR,#-4]                ;若是ARM指令,读取指令码(32位)

   BICEQ            R0,#0XFF000000           ;取得ARM指令的24位立即数

   ....

   LDMFD          SP!,{R0-R3,R12,PC}^    ;SWI异常中断返回

    

Thu Oct 12 2006

 

软件中断SWI的实现
在需要软件中断处调用

__SWI  0xNum           ;Num为SWI中断处理模块的编号,见表SwiFunction


;软件中断
SoftwareInterrupt
        CMP     R0, #12                         ;R0中的SWI编号是否大于最大值

/* 下面这句语句把 (LDRLO地址+ 8 + R0*4) 的地址装载到PC寄存器,举例如果上面的 Num="1",也就是R0 = 1, 假设LDRLO这条指令的地址是0x00008000,那么根据ARM体系的2级流水线 PC寄存器里指向是下两条指令 于是PC = 0x00008008  也就是伪指令DCD     TASK_SW 声明的标号TASK_SW  的地址,注意DCD     TASK_SW 这条指令本身不是ARM能执行的指令,也不会占有地址,这条指令靠汇编器汇编成可执行代码,它的意义就是声明 TASK_SW的地址,  , [PC, R0, LSL #2] 这个寻址方式就是 PC + R0的值左移2位的值( 0x01<<2  => 0x04 ),这样PC的值就是0x0000800C, 即ENTER_CRITICAL的地址于是ARM执行该标号下的任务 */

        LDRLO   PC, [PC, R0, LSL #2]       
        MOVS    PC, LR

SwiFunction
        DCD     TASK_SW                ;0
        DCD     ENTER_CRITICAL         ;1
        DCD     EXIT_CRITICAL            ;2
        DCD     ISRBegin                 ;3
        DCD     ChangeToSYSMode         ;4
        DCD     ChangeToUSRMode         ;5
        DCD     __OSStartHighRdy        ;6
        DCD     TaskIsARM               ;7
        DCD     TaskIsTHUMB             ;8
        DCD     OSISRNeedSwap           ;9
        DCD     GetOSFunctionAddr       ;10
        DCD     GetUsrFunctionAddr      ;11

TASK_SW
        MRS     R3, SPSR                        ;保存任务的CPSR
        MOV     R2, LR                          ;保存任务的PC
       
        MSR     CPSR_c, #(NoInt | SYS32Mode)    ;切换到系统模式
        STMFD   SP!, {R2}                       ;保存PC到堆栈
        STMFD   SP!, {R0-R12, LR}               ;保存R0-R12,LR到堆栈
                                                ;因为R0~R3没有保存有用数据,所以可以这样做
        B       OSIntCtxSw_0                    ;真正进行任务切换

ENTER_CRITICAL
                                                ;OsEnterSum++
        LDR     R1, =OsEnterSum
        LDRB    R2, [R1]
        ADD     R2, R2, #1
        STRB    R2, [R1]
                                                ;关中断
        MRS     R0, SPSR
        ORR     R0, R0, #NoInt
        MSR     SPSR_c, R0
        MOVS    PC, LR

上一篇: verilog设计经验点滴 下一篇: 批量数据加载/存储指令实验

分享 举报