arm汇编指令集整理

ARM 199浏览


在学习汇编的时候,我们常常会被要去记忆各种各样的指令集所烦恼,其实在开发过程中,我们需要使用的指令就那么几条,下面是我们在使用中的积累:

一、常用汇编指令

1. b, bl  (相对跳转指令)

2. mov  (数据传输, ldr地址读取伪指令)        

<span style="font-family:SimSun;">mov r1, r2               ;将r2里面的值复制到r1中

mov r1, #4096            ;常数必须用立即数表示</span>

当不知道数是否是"立即数"来表示时,可以使用ldr来赋值。ldr是伪指令, 由编译器会把它扩展成真正的指令。

如果是立即数用mov指令替换,否则 编译时将改常数保存在某个位置,使用内存读取指令把它读出来。

 ldr 本意为大范围的地址读取伪指令

<span style="font-family:SimSun;">       ldr r1, =4096    ; 把常量赋给寄存器
       ldr r2, =label   ; 获取代码的绝对地址
label:
       ...</span>

    movs : 数据传输,并且影响z条件标志位,如果目标寄存器为0,则z条件标志位为1,

               接下来beq就成立,如下:

<span style="font-family:SimSun;">        movs r10, r5                     ; invalid processor (r5=0)?
        beq  error_p                   ; yes, error 'p'
error_p:
        ...
</span>

3.ldr, str, stm, ldm

3.1 ldr, str

         ldr的第二个参数前面是"="时,表示伪指令,否则为内存访问指令        

<span style="font-family:SimSun;">ldr r0, [r1]         //从r1中的存储器地址处读取一个字,然后放入到r0中
str r0, [r1]         //把r0的值写入寄存器r1所指向的地址中。</span>

3.2  stm ldm(多寄存器加载/存储指令)

         ‘!’ : 表示指令执行后寄存器被更新。

                stmia r0!,  {r2-r7}  //将r2-r7的数据存储到r0指向的地址,r0值更新

                ldmia r0!,  {r2-r7}  //加载r0指向的地址上的多字数据,保存到r2-r7中,r0的值更新。

          IA  执行后增加  (increase after), 这时对应的{r2-r7},赋值的顺序是r2~r7

          IB  执行前增加  (increase before)

          DA 执行后减少 (decrease after), 这时对应的{r2-r7}, 赋值的顺序是r7-r2

          DB 执行前减少 (decrease before)

3.3 小结

         ldm ldr 都是往寄存器里面load,ldm是内存往寄存器组,ldr是寄存器往寄存器。

         stm str 都是往内存里面loader,stm是寄存器组往寄存器所指向内存,str是寄存器往寄存器所指向内存。并且stm str 语法位置相反。

         str 源寄存器, [目标寄存器]

         stm目标内存首地址, [源寄存器组]

         ldr 目标寄存器, [源寄存器] or 立即数  //源寄存器里面的值给目标寄存器

         ldm 源内存首地址, [目的寄存器组]   

4. add sub

<span style="font-family:SimSun;">add r1, r2, #1 ; r1=r2+1
sub r1, r2, #1 ; r1=r2-1
</span>

5. msr, mrs

   msr cpsr, r0 ;  赋值r0的值到cpsr

   mrs r0, cpsr ;  赋值cpsr的值到r0

   注:cpsr_cxsf

   c : controlfield mask byte (PSR[7:0])
   x : extension field mask byte (PSR[15:8])
   s : status field mask byte (PSR[23:16)
   f  : flags field mask byte (PSR[31:24]).

   如果加后缀,那么就代表只操作这一个区域,其他的不操作,避免对某些位操作而影响其他位。

   如msr cpsr_c,#0xdf  //只操作control field

6. bic : 逻辑位清除(AND NOT) Rd =Rn & ~N

<span style="font-family:SimSun;">ldr r1, =0b1111   ; mov r1, #0b1111
ldr r2, =0b0101   ; mov r2, #0b0101
bic r0, r1, r2    ; r0 = 0b1010</span>

7. orr : 32bit逻辑或 Rd = Rn | N

<span style="font-family:SimSun;">ldr r1, =0b1111   ; mov r1, #0b1111
ldr r2, =0b0101   ; mov r2, #0b0101
orr r0, r1, r2    ; r0 = 0b1111</span>

8.adr
获得当前运行地址

<p><span style="font-family:SimSun;">//adr读当前运行地址,以uboot为例,如果是从norflash启动, _start为nor flash首地址,为0x0,那么r0 = 0x0</span></p><p><span style="font-family:SimSun;">//如果是通过仿真器dump到sdram中的,那么就是链接地址,0x33F80000</span></p><span style="font-family:SimSun;">adr r0, _start         ; r0 <- current position of code</span>

9. "adr" PK "ldr"

通过下面一段代码分析ldr,adr的区别

<span style="font-family:SimSun;">/*
 * start.s
 */
_start: nop
    nop

    ldr r0, _start
    adr r1, _start
    ldr r2, =_start
    nop
    mov pc, lr

</span><pre name="code" class="cpp"><span style="font-family:SimSun;">/*
 * link.lds
 */
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
	. = 0x0c000080;
	. = ALIGN(4); .text   : { *(.text)}
	. = ALIGN(4); .rodata : { *(.rodata)}
	. = ALIGN(4); .data   : { *(.data) }
	. = ALIGN(4); .bss    : { *(.bss) }
}</span>


<span style="font-family:SimSun;">/*
 * 编译,链接,反汇编
 */
</span>

#arm-linux-gcc -c start.s -o start.o
#arm-linux-ld -Tlink.lds -o start.elf start.o
#arm-linux-objdump -D start.elf

/*
* 输出的反汇编内容如下:
*/
start.elf:     file format elf32-littlearm

Disassembly of section .text:

0c000080 <_start>:
 c000080:    e1a00000     nop            (mov r0,r0)
 c000084:    e1a00000     nop            (mov r0,r0)
 c000088:    e51f0010     ldr    r0, [pc, #-16]  ; c000080 <_start>
 c00008c:    e24f1014     sub    r1, pc, #20     ; 0x14
 c000090:    e59f2004     ldr    r2, [pc, #4]    ; c00009c <_start+0x1c>
 c000094:    e1a00000     nop            (mov r0,r0)
 c000098:    e1a0f00e     mov    pc, lr
 c00009c:    0c000080     .word    0x0c000080
Disassembly of section .ARM.attributes:

00000000 <.ARM.attributes>:
   0:    00001741     andeq    r1, r0, r1, asr #14
   4:    61656100     cmnvs    r5, r0, lsl #2
   8:    01006962     tsteq    r0, r2, ror #18
   c:    0000000d     andeq    r0, r0, sp
  10:    00543405     subseq    r3, r4, r5, lsl #8
  14:    01080206     tsteq    r8, r6, lsl #4


通过上面的反汇编得出如下结论:

ldr     r0, _start       ;
<链接时确定>
从内存地址 _start 的地方把值读入。执行这个后,r0 = 0xe1a00000

adr     r1, _start      ;
<运行时确定>
取得_start的地址到 r0,但是请看反编译的结果,它是与位置无关的, 其实取得的是相对的位置。

                              ; (位置无关码请查验《常用交叉编译工具整理》一篇, arm-linux-ld部分)

                              ; 示例这段代码在 0x0c008000 运行,那么 adr r0, _start 得到 r0 = 0x0c008000;如果在地址 0 运行,就是 0x00000000 了。

ldr     r2, =_start     ;
<链接时确定>
取得标号_start 的绝对地址。

                              ; 这条指令看上去只是一个指令,但是它要占用 2 个 32bit 的空间,一条是指令,另一条是 _start 的数据

                              ; 因为在编译的时候不能确定 _start 的值,而且也不能用 mov 指令来给 r0 赋一个 32bit 的常量,所以需要

                              ;多出一个空间存放 _start 的真正数据,在这里就是 0x0c008014, 因此可以看出,这个是绝对的寻址,

                              ; 不管这段代码在什么地方运行,它的结果都是 r0 = 0x0c008014


二、条件变量

1 CPSR 程序状态寄存器


Q Saturation the result causes an overflow and/or saturation

V oVerflow the result causes a signed overflow

C Carry the result causes an unsigned carry

Z Zero the result is zero, frequently used to indicate equality

N Negative bit 31 of the result is a binary 1

如果成立为1,大写:

2 CMP:比较指令标记根据Rm-Rn的值设置

<span style="font-family:SimSun;">Loop:        mov r0, #4    //比较前 cpsr = nzcvqiFt_USER
             mov r1, #4
             cmp r0, r1    //比较后 cpsr = nZcvqiFt_USER
             bne loop      //如果cpsr中的z位是小z就跳到loop,否则不执行,继续往下。明显是继续往下执行
             mov pc, lr    //所以会执行这句话</span>

3 TEQ:等值测试  标记根据Rm==Rn的值设置

<span style="font-family:SimSun;">Loop:	mov r0, #4	//比较前 cpsr = nzcvqiFt_USER
        mov r1, #4
        teq  r0, r1	//比较后 cpsr = nZcvqiFt_USER
        beq  exit	//如果cpsr中的z位是大Z就跳到exit,否则不执行,继续往下。明显是调到exit
        b loop		//
exit :  mov pc, lr      //所以会执行这句话
</span>


三、伪指令

1 .extern : 定义一个外部符号(变量or函数)

<pre name="code" class="cpp"><span style="font-family:SimSun;">//表示引用的main是一个外部函数</span>

.extern main


2 .text  :表示下面的语句都属于代码段

3 .global : 将本文中某个程序标号定义为全局的,

<pre name="code" class="cpp"><span style="font-family:SimSun;">//比如_start是全局变量</span>

.global _start_start:


4 .equ : 宏定义       

<span style="font-family:SimSun;"> .equ  MEM_CTL_BASE,0x48000000     //定义MEM_CTL_BASE</span>

5 .align : 定义字节对齐

<pre name="code" class="cpp"><span style="font-family:SimSun;">;定义以下为4字节对齐</span>

.align 4
...


6 .long : 就是相当于在当前位置放一个long型(4byte)的值。

<span style="font-family:SimSun;">.long 0x00000030    ;将0x00000030值放于当前位置
.word,.int, .short, .byte类似</span>

7 ^

stmia r0, {sp}^			;保存user mode下的sp 到 r0
stmia r0, {lr}^			;保存user mode下的lr 到 r0
ldmia sp!, {r0-r12, pc}^	;中断返回,^表示将spsr的值copy到cpsr中。
区别:
当执行多寄存器加载操作时(ldmxx)带有”^”, 如果含pc   :表示该指令在执行完毕后,实现程序跳转同时恢复spsr 到 cpsr.
					  如果不含pc :表示内存加载到用户模式下寄存器。
当执行多寄存器存储操作时(stmxx)带有”^”, 表示将user mode下寄存器列表的值存储到内存中,与寄存器列表中有无pc无关。

四、 ATPCS即ARM-THUMB procedure call standard(ARM-Thumb过程调用标准)

1. 存器使用规则

      r15 即 pc;   r14即lr;  r13即 sp

2. 数据栈使用规则

     descending栈 : 递减栈

     ascending栈  : 递增栈

     FULL栈         : 栈指针指向栈顶元素(最后一个入栈的数据)

     EMPTY栈     : 栈指针指向栈顶元素(最后一个入栈的数据)相邻的一个空的数据单元

     即组合成    : FD EDFA EA

      ATPCS规定数据栈为FD类型,数据栈是8字节对齐,

      stmdb:往数据栈中保存内容,先递减sp指针,再保存数据

      ldmia:从数据中恢复数据时,先获得数据,再递增sp指针

3. 参数传递规则

      当参数个数不超过4个时,使用r0~r3这4个寄存器来传递参数;如果超过4个,剩余的参数通过数据栈来传递; 返回结果,通常使用a0~a3来传递

      如CopyCode2SDRAM函数是用C语言实现的, 类型如下:

       int CopyCode2SDRAM(unsigned char *buf, unsigned long start_addr, int size);

       在汇编代码中,使用下面的代码调用它,并判断返回值:

       ldr r0, =0x30000000         // 1. 目标地址=0x30000000,SDRAM的起始地址

       mov r1, #0                           // 2. 源地址 = 0

       mov r2, #16*1024  // 3. 复制长度 = 16K

        bl CopyCode2SDRAM       // 4. 调用C函数CopyCode2SDRAM

        cmp a0, #0                          // 5. 判断函数返回值

        beq XXX