嵌入式系统Linux内核开发实战指南(ARM平台)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第3章 ARM指令及其寻址方式

3.1 ARM处理器的程序状态寄存器(PSR)

ARM中的当前程序状态寄存器(Current Program Status Register,CPSR)可以在任何处理器模式下被访问,而在5种异常中断(exception)模式中又各自拥有一个独立的备份程序状态寄存器(Saved Program Status Register,SPSR)用来保存CPSR中的内容。当特定的异常中断发生时,SPSR_xxx用来保存CPSR,当异常中断程序退出时,可以用SPSR_xxx中保存的值来恢复CPSR。

由于用户(user)模式和系统(system)模式不是异常模式,所以它们没有SPSR,当在用户模式或系统模式下访问SPSR时,将会产生不可预知的结果。

当前程序状态寄存器共32位,当前只实现了16位。该寄存器的各位定义说明如下:

表3-1 ARM中的程序状态寄存器(PSR)

3.2 ARM指令的条件码

3.3 ARM指令介绍

3.3.1 跳转指令

跳转指令是程序跳转到-32MB~+32MB范围内地址处执行。

b{<cond>} <target_address>

程序跳转到<target_address>处执行。

bl{<cond>} <target_address>

程序跳转到<target_address>处执行,将当前pc的值保存到lr中。

blx <target_address>

程序跳转到<target_address>处执行,将程序状态从ARM态切换到Thumb态,将当前pc的值保存到lr中。

blx{<cond>} <rm>

程序跳转到rm值所指的地址处执行,目标地址处的指令可以是ARM指令也可以是Thumb指令,将当前pc的值保存到lr中。

bx{<cond>} <rm>

程序跳转到rm值所指的地址处执行,目标地址处的指令可以是ARM指令(rm[bit0=0]),也可以是Thumb指令(rm[bit0=1])。

3.3.2 数据处理指令

mov{<cond>}{s} <rd>, <shifter_operand>

将<shifter_operand>表示的数据传递给rd。

mvn{<cond>}{s} <rd>, <shifter_operand>

将<shifter_operand>表示的数据的反码传递给rd。

add{<cond>}{s} <rd>, <rn>, <shifter_operand>

将<shifter_operand>表示的数据与<rn>的值相加,并把结果保存到<rd>中。

adc{<cond>}{s} <rd>, <rn>, <shifter_operand>

将<shifter_operand>表示的数据与<rn>的值相加,再加上CPSR中C标志的值,并把结果保存到<rd>中。

sub{<cond>}{s} <rd>, <rn>, <shifter_operand>

将<rn>的值减去<shifter_operand>表示的数据,并把结果保存到<rd>中。

sbc{<cond>}{s} <rd>, <rn>, <shifter_operand>

将<rn>的值减去<shifter_operand>表示的数据,再减去CPSR中C标志的值,并把结果保存到<rd>中。

rsb{<cond>}{s} <rd>, <rn>, <shifter_operand>

将<shifter_operand>表示的数据减去<rn>的值,并把结果保存到<rd>中。

将<shifter_operand>表示的数据减去<rn>的值,再减去CPSR中C标志的值,并把结果保存到<rd>中。

and{<cond>}{s} <rd>, <rn>, <shifter_operand>

将<shifter_operand>表示的数据与<rn>的值按位做逻辑与操作,并把结果保存到<rd>中。

orr{<cond>}{s} <rd>, <rn>, <shifter_operand>

将<shifter_operand>表示的数据与<rn>的值按位做逻辑或操作,并把结果保存到<rd>中。

eor{<cond>}{s} <rd>, <rn>, <shifter_operand>

将<shifter_operand>表示的数据与<rn>的值按位做逻辑异或操作,并把结果保存到<rd>中。

bic{<cond>}{s} <rd>, <rn>, <shifter_operand>

将<shifter_operand>表示的数据与<rn>的值的反码按位做逻辑与操作,并把结果保存到<rd>中。

cmp{<cond>} <rn>, <shifter_operand>

将<rn>的值减去<shifter_operand>表示的数据,根据操作结果更新CPSR中相应的条件标志位。

cmn{<cond>} <rn>, <shifter_operand>

将<rn>的值加上<shifter_operand>表示的数据,根据操作结果更新CPSR中相应的条件标志位。

tst{<cond>} <rn>, <shifter_operand>

将<shifter_operand>表示的数据与<rn>的值按位做逻辑与操作,根据操作结果更新CPSR中相应的条件标志位。

teq{<cond>} <rn>, <shifter_operand>

将<shifter_operand>表示的数据与<rn>的值按位做逻辑异或操作,根据操作结果更新CPSR中相应的条件标志位。

3.3.3 乘法指令

考虑到操作结果,指令中所有操作数都放在寄存器中。

mul{<cond>}{s} <rd>, <rm>, <rs>

将两个32位(有/无)符号操作数相乘,将结果保存到32位寄存器<rd>中。

mla{<cond>}{s} <rd>, <rm>, <rs>, <rn>

将两个32位(有/无)符号操作数相乘,再将乘积加上第3个操作数,将结果保存到32位寄存器<rd>中。

smull{<cond>}{s} <rdLo>, <rdHi>, <rm>, <rs>

将两个32位有符号操作数相乘,将结果的高32位保存到寄存器<rdHi>中,将结果的低32位保存到寄存器<rdLo>中。

smlal{<cond>}{s} <rdLo>, <rdHi>, <rm>, <rs>

将两个32位有符号操作数的64位乘积结果与<rdHi>和<rdLo>中原来的64位数相加,将最后加法结果的高32位保存到寄存器<rdHi>中,低32位保存到寄存器<rdLo>中。

umull{<cond>}{s} <rdLo>, <rdHi>, <rm>, <rs>

将两个32位无符号操作数相乘,将结果的高32位保存到寄存器<rdHi>中,将结果的低32位保存到寄存器<rdLo>中。

umlal{<cond>}{s} <rdLo>, <rdHi>, <rm>, <rs>

将两个32位无符号操作数的64位乘积结果与<rdHi>和<rdLo>中原来的64位数相加,将最后加法结果的高32位保存到寄存器<rdHi>中,低32位保存到寄存器<rdLo>中。

3.3.4 杂类算术指令

clz{<cond>} <rd>, <rm>

计算寄存器<rm>的值的最高端0的个数,如果bit31为1,那么指令返回0,如果<rm>=0,则指令返回32。

3.3.5 状态寄存器访问指令

mrs{<cond>} <rd>, cpsr

将当前状态寄存器的内容传送到<rd>中。

mrs{<cond>} <rd>, spsr

将处理器当前工作模式的保存状态寄存器的内容传送到<rd>中。

msr{<cond>} cpsr_<fields>, #<immediate>

将立即数传给当前状态寄存器。

msr{<cond>} cpsr_<fields>, <rm>

将<rm>的值传给当前状态寄存器。

msr{<cond>} spsr_<fields>, #<immediate>

将立即数传给处理器当前工作模式的保存状态寄存器。

msr{<cond>} spsr_<fields>, <rm>

将<rm>的值传给处理器当前工作模式的保存状态寄存器。

说明:

<fields>设置状态寄存器中需要操作的位。状态寄存器的32位可以分为4个8位的域:bits[31:24]为条件标志位域,用f表示;bits[23:16]为状态标志位域,用s表示;bits[15:8]为扩展位域,用x表示;bits[7:0]为控制标志位域,用c表示。4部分状态寄存器对应的表示方法为:cpsr_f、cpsr_s、cpsr_x、cpsr_c及spsr_f、spsr_s、spsr_x、spsr_c。

3.3.6 Load/Store内存访问指令

ldr{<cond>} <rd>, <addressing_mode>

将<addressing_mode>寻址模式表示的内存地址处的32位字读取到<rd>中,如果地址不是字对齐的,那么从内存中读出的数据要进行循环右移操作,移位的位数为地址的bits[1:0]表示的值的8倍,这样对于little-endian的内存模式,指令要读取的字节数据存放在<rd>的低8位;对于big-endian的内存模式,指令要读取的字节数据存放在<rd>的bits[31:24]。

ldr{<cond>}b <rd>, <addressing_mode>

从内存中将一个8位字节数读取到<rd>中,并将<rd>的高24位清0。

ldr{<cond>}bt <rd>, <post_indexed_addressing_mode>

从内存中将一个8位字节数读取到<rd>中,并将<rd>的高24位清0;当在特权级处理器模式下使用本指令时,内存系统将该操作当作一般用户模式下的内存访问操作。

ldr{<cond>}h <rd>, <addressing_mode>

从内存中将一个16位数据读取到<rd>中,并将<rd>的高16位清0。如果内存地址不是半字对齐的,将产生不可预知的结果。

ldr{<cond>}sb <rd>, <addressing_mode>

从内存中将一个8位有符号字节数读取到<rd>中,并将<rd>的高24位设置成该字节数据符号位的值。

ldr{<cond>}sh <rd>, <addressing_mode>

从内存中将一个16位有符号数据读取到<rd>中,并将<rd>的高16位设置成该半字数据的符号位的值。如果内存地址不是半字对齐的,将产生不可预知的结果。

ldr{<cond>}t <rd>, <post_indexed_addressing_mode>

从内存中将一个32位字读取到<rd>中,如果地址不是字对齐的,那么从内存中读出的数据要进行循环右移操作,移位的位数为地址的bits[1:0]表示的值的8倍,这样对于little-endian的内存模式,指令要读取的字节数据存放在<rd>的低8位;对于big-endian的内存模式,指令要读取的字节数据存放在<rd>的bits[31:24]。当在特权级处理器模式下使用本指令时,内存系统将该操作当作一般用户模式下的内存访问操作。

str{<cond>} <rd>, <addressing_mode>

将<rd>中的32位字写入到<addressing_mode>寻址模式表示的内存地址处。

str{<cond>}b <rd>, <addressing_mode>

将<rd>中的低8位字节数据写入到<addressing_mode>寻址模式表示的内存地址处。

str{<cond>}h <rd>, <addressing_mode>

将<rd>中的低16位半字数据写入到<addressing_mode>寻址模式表示的内存地址处。如果内存地址不是半字对齐的,将产生不可预知的结果。

str{<cond>}t <rd>, <addressing_mode>

将<rd>中的32位字写入到<addressing_mode>寻址模式表示的内存地址处。当在特权级处理器模式下使用本指令时,内存系统将该操作当作一般用户模式下的内存访问操作。

将<rd>中的低8位字节数据写入到<addressing_mode>寻址模式表示的内存地址处。当在特权级处理器模式下使用本指令时,内存系统将该操作当作一般用户模式下的内存访问操作。

str{<cond>}bt <rd>, <addressing_mode>

3.3.7 批量Load/Store内存访问指令

ldm{<cond>}<addressing_mode> <rn>!, <registers>

将数据从连续的内存单元中读取32位字到寄存器列表中。

ldm{<cond>}<addressing_mode> <rn>!, <registers_without_pc>^

将数据从连续的内存单元中读取32位字到寄存器列表中,但r15/pc寄存器不在寄存器列表中,此时指示指令中所有的寄存器为用户模式下的寄存器。

ldm{<cond>}<addressing_mode> <rn>!, <registers_and_pc>^

将数据从连续的内存单元中读取32位字到寄存器列表中,且r15/pc寄存器必须在寄存器列表中,同时将当前处理器模式对应的SPSR寄存器的内容复制到CPSR寄存器中。

stm{<cond>}<addressing_mode> <rn>!, <registers>

将寄存器列表中的32位字数据写入连续的内存单元中。

stm{<cond>}<addressing_mode> <rn>!, <registers>^

将寄存器列表中的32位字数据写入连续的内存单元中,并指示指令中所有的寄存器为用户模式下的寄存器。

3.3.8 LDREX和STREX指令

■ 独占加载和存储寄存器

指令语法格式如下所示:

        LDREX{cond} Rt, [Rn {, #offset}]
        STREX{cond} Rd, Rt, [Rn {, #offset}]
        LDREXB{cond} Rt, [Rn]
        STREXB{cond} Rd, Rt, [Rn]
        LDREXH{cond} Rt, [Rn]
        STREXH{cond} Rd, Rt, [Rn]
        LDREXD{cond} Rt, Rt2, [Rn]
        STREXD{cond} Rd, Rt, Rt2, [Rn]

说明:

con可选的条件代码。

Rd存放返回状态的目标寄存器。

Rt要加载或存储的寄存器。

Rt2进行双字加载或存储时要用到的第二个寄存器。

Rn内存地址所基于的寄存器。

offset要应用到Rn中的值的可选偏移量。offset只可用于Thumb-2指令中。如果省略offset,则认为偏移量为0。

■ LDREX(独占装载指令)

LDREX可从内存中加载数据。

如果物理地址有共享TLB属性,那么LDREX会将该物理地址标记为由当前处理器独占访问,并且会清除该处理器对其他任何物理地址的独占访问标签。否则,会标记为“执行处理器已经标记了一个物理地址,但访问尚未完毕”。

■ STREX(独占存储指令)

STREX可在一定条件下向内存中存储数据。条件具体如下:

● 如果物理地址没有共享TLB属性,执行处理器有一个已标记物理地址,但尚未访问完毕,那么将会进行存储,清除该标记,并向Rd中返回值0;

● 如果物理地址没有共享TLB属性,且执行处理器也没有已标记但尚未访问完毕的物理地址,那么将不会进行存储,而会向Rd返回值1;

● 如果物理地址有共享TLB属性,且已被标记为由执行处理器独占访问,则将进行存储,清除标签,并向Rd返回值0;

● 如果物理地址有共享TLB属性,但却没有标记为由执行处理器独占访问,则不会进行存储,而会向Rd中返回值1。

■ 限制

offset不可用于ARM指令中;offset的值可为0~1020范围内的任意一个4的倍数。

r15不可用于Rd、Rt、Rt2或Rn。

对于STREX,Rd一定不能与Rt、Rt2或Rn为同一寄存器。

对于LDREX,Rt和Rt2不可为同一寄存器。

在ARM指令中,Rt必须是一个编号为偶数的寄存器,且不能为r14;同时Rt2必须为R(d+1)。

■ 用法

利用LDREX和STREX可在多处理器和共享内存系统间实现进程间通信。

出于性能方面的考虑,请将相应LDREX指令和STREX指令间的指令数控制到最少。

■ 注意

STREX指令中所用的地址必须要与近期执行次数最多的LDREX指令所用的地址相同。如果使用不同的地址,则STREX指令的执行结果将不可预知。

■ 适用体系结构

ARM LDREX和STREX可用于ARMv6及更高版本中。

ARM LDREXB、LDREXH、LDREXD、STREXB、STREXD和STREXH可用于ARMv6K及更高版本中。

所有这些32位Thumb指令均可用于ARMv6T2和ARMv7,但LDREXD和STREXD不可用于ARMv7-M架构。

这些指令均无16位版本。

■ 示例

        MOV r1, #0x1
        try
        LDREX r0, [LockAddr]
        CMP r0, #0
        STREXEQ r0, r1, [LockAddr]
        CMPEQ r0, #0
        BNE try
        ...

3.3.9 信号量操作指令

swp{<cond>} <rd>, <rm>, [<rn>]

将<rn>的值所指的内存地址处的字节读取到<rd>中,同时将<rm>的值写入到<rn>的值所指的内存地址中,当<rd>和<rm>是同一寄存器时,指令交换该寄存器<rd>和内存单元的内容。

swp{<cond>}b <rd>, <rm>, [<rn>]

将<rn>的值所指的内存地址处的字节数据读取到<rd>的低8位中,<rd>的高24位清0,同时将<rm>的低8位写入到<rn>的值所指的内存地址中,当<rd>和<rm>是同一寄存器时,指令交换该寄存器<rd>的低8位和内存单元的内容。

3.3.10 异常中断产生指令

swi{<cond>} <immed_24>

产生软中断。ARM通过swi中断机制来实现在用户模式下对操作系统中特权模式程序的调用。<immed_24>又叫软中断号,被操作系统用来判断用户程序请求的服务类型。

bkpt <immed_16>

产生软件断点中断。软件调试程序可以使用该中断,当系统使用硬件调试器时可以忽略该中断。<immed_16>被调试软件用来保存额外的断点信息。

3.3.11 ARM协处理器指令

cdp{<cond>} <coproc>, <opcode_1>, <crd>, <crn>, <crm>, <opcode_2>

ARM处理器通知ARM协处理器执行特定的操作,该操作由协处理器完成,如果协处理器不能成功执行该操作,将产生未定义的指令异常中断。

说明:

<coproc> 协处理器的编号。

<opcode_1> 协处理器将执行的操作的操作码。

<crd> 作为目标寄存器的协处理器寄存器。

<crn> 存放第1个操作数的协处理器寄存器。

<crm> 存放第1个操作数的协处理器寄存器。

<opcode_2> 协处理器将执行的操作的操作码。

示例

        cdp p5, 2, c12, c10, c3, 4 ;初始化协处理器p5,操作码1为2,操作码2为4,目标
    寄存器为c12,源操作数寄存器为c10和c3

ldc{<cond>}{L} <coproc>, <crd>, <addressing_mode>

从一系列连续的内存单元中将数据读取到协处理器的寄存器中,如果协处理器不能成功执行该操作,将产生未定义的指令异常中断。

说明:

{L} 指示指令为长读取操作,比如用于双精度的数据传送。

示例

        ldc p6, cr4, [r2, #4]  ;读取内存单元[r2+4]的字数据并写入到协处理器p6的cr4的
    寄存器中
        stc{<cond>}{L}  <coproc>, <crd>, <addressing_mode>

将协处理器的寄存器中的数据写入到一系列连续的内存单元中,如果协处理器不能成功执行该操作,将产生未定义的指令异常中断。

示例

        stc p6, cr6, [r2, #4]! ;将协处理器p6的cr6的寄存器中的字数据写入到内存单元
    [r2+4]中,然后r2=r2+4
        mcr{<cond>} <coproc>, <opcode_1>, <rd>, <crn>, <crm>, {<opcode_2>}

将ARM处理器寄存器中的数据写到协处理器的寄存器中,如果协处理器不能成功执行该操作,将产生未定义的指令异常中断。

示例

        mcr p14, 3, r7, c7, c11, 6 ;将r7中的数据写到协处理器p14的寄存器c7和c11中,操作码1为3,操作码2为6
        mrc{<cond>} <coproc>, <opcode_1>, <rd>, <crn>, <crm>, {<opcode_2>}

将协处理器的寄存器中的数据写到ARM处理器的寄存器中,如果协处理器不能成功执行该操作,将产生未定义的指令异常中断。

示例

        mrc p15, 2, r5, c0, c2, 4      ;将协处理器p15的寄存器中的数据写到r5中,操作
    码1为2,操作码2为4

3.4 ARM指令寻址方式

3.4.1 数据处理指令的操作数的寻址方式

数据处理指令的一般编码格式如下所示:

数据处理指令的一般语法格式如下所示:

<opcode>{<cond>}{S} <Rd>,<Rn>,<shifter_operand>

数据操作指令的操作数的寻址方式是针对第2个操作数shifter_operand的,粗分为3种寻址方式,具体细分为11种寻址方式。

数据处理指令的(第2个)操作数的寻址方式粗分为以下3种方式:

■ 立即数方式

立即数是32位的,但它只能占用指令格式中12位的<shifter_operand>,所以在汇编代码时是要将32位的立即数编码成12位的<shifter_operand>。立即数编码有固定的规则,每个立即数由一个8位的常数循环右移偶数位得到,右移的位数由一个4位的二进制数的两倍表示,所以立即数寻址方式的指令编码格式如下:

其中4位的roate_imm是二进制移位数的一半,8位的immed_8是常数,两者刚好组成了指令中的第2个操作数<shifter_operand>。如果立即数记作<immediate>,那么:

<immediate> = immed_8循环右移(2*rotate_imm)

千万不要错误直观地认为:

<immediate> = immed_8 |(rotate_imm<<8)

由以上立即数的编码规则可以判断立即数的合法性,举例说明:

0xFF、0x104、0xFF0、0xFF00等都是合法的立即数;而0x101、0x102、0xFF1等则是非法的立即数,因为无法根据上面的编码规则对它们进行编码。

另外同一个合法立即数可能有若干种编码方法,比如0x3F可以有以下两种编码方法:

由于这种立即数的构造方法中包含了循环移位操作,而循环移位操作会影响CPSR的条件标志位C,因此同一个合法立即数采用不同的编码方式将使某些指令的执行产生不同的结果,这是不允许的。ARM汇编编译器遵守下面的规则:

immed_8=0x3F,rotate_imm=0或者immed_8=0xFC,rotate_imm=0xF

当立即数数值在0~0xFF范围时,令immed_8=<immediate>,rotate_imm=0;

在其他情况下,汇编编译器选择使rotate_imm数值最小的编码方式。

心得:如何快速判断一个32位立即数是否合法?只要看该立即数能否通过右移偶数位把该立即数的所有非0位都移到最低8位中,如果能,那么这个立即数肯定能通过上面的编码规则进行编码,是合法立即数;否则该立即数一定是非法立即数。

■ 寄存器方式

第2个操作数即是Rm寄存器的值。

■ 寄存器移位方式

移位的方式有5种,它们的助记符和含义说明如下:

ASR 算术右移

LSL 逻辑左移

LSR 逻辑右移

ROR 循环右移

RRX扩展的循环右移

数据处理指令的(第2个)操作数的寻址方式具体细分为以下11种方式:

① #<immediate>

指令编码格式:

示例:

        mov r0, #0xfc0

② <Rm>

指令编码格式:

示例:

        mov r3,r2

注意:当R15/pc用作第1个源操作数Rn或者第2个操作数Rm时,操作数即为当前指令地址加常数8,见前面“ARM流水线操作”说明。

③ <Rm>,LSL #<shift_imm>

指令编码格式:

示例:

        mov r0,r0,LSL #n   ;将r0的值左移n位,右边空位用0补充,循环器进位标志的值等于
    最后从寄存器r0中移出位的值,如果没有移位,那么循环器进位标志的值等于CPSR中C标志位的值

注意:当R15/pc用作第1个源操作数Rn或者第2个操作数Rm时,操作数即为当前指令地址加常数8。

④ <Rm>,LSL <Rs>

指令编码格式:

注意:当R15/pc用作Rn、Rm、Rd或Rs时,会产生不可预知的结果。

⑤ <Rm>,LSR #<shift_imm>

指令编码格式:

示例:

        mov r0,r0,LSR #n   ;将r0的值右移n位,左边空位用0补充,循环器进位标志的值等于
    最后从寄存器r0中移出位的值,如果n等于0,那么右移32位

注意:当R15/pc用作第1个源操作数Rn或者第2个操作数Rm时,操作数即为当前指令地址加常数8。

⑥ <Rm>,LSR <Rs>

指令编码格式:

注意:当R15/pc用作Rn、Rm、Rd或Rs时,会产生不可预知的结果。

⑦ <Rm>,ASR #<shift_imm>

指令编码格式:

示例:

        mov r0,r0,ASR #n   ;将r0的值右移n位,左边空位用R0[31]位的值补充,循环器进位标志的值等于最后从寄存器r0中移出位的值,如果n等于0,则没有移位,那么循环器进位标志的值等于CPSR中C标志位的值
    R0[31]

注意:当R15/pc用作第1个源操作数Rn或者第2个操作数Rm时,操作数即为当前指令地址加常数8。

⑧ <Rm>,ASR <Rs>

指令编码格式:

注意:当R15/pcP用作Rn、Rm、Rd或Rs时,会产生不可预知的结果。

⑨ <Rm>,ROR #<shift_imm>

指令编码格式:

示例:

      mov r0,r0,ROR #n   ;将r0的值右移n位,移出的位右移入寄存器的左边空位,循环器进位
      标志的值等于最后从r0寄存器右边移出位的值;如果n等于0,那么操作与下面的RRX移位操作相同

注意:当R15/pc用作第1个源操作数Rn或者第2个操作数Rm时,操作数即为当前指令地址加常数8。

⑩ <Rm>,ROR <Rs>

指令编码格式:

注意:当R15/pc用作第1个源操作数Rn或者第2个操作数Rm时,操作数即为当前指令地址加常数8。

⑪ <Rm>,RRX

指令编码格式:

示例:

        mov r0,r0,RRX  ;将r0的值右移1位,将CPSR的C标志填充左边移出的位,而CPSR的C
    标志位用从右边移出位的值替代

注意:当R15/pc用作第1个源操作数Rn或者第2个操作数Rm时,操作数即为当前指令地址加常数8。

3.4.2 字及无符号字节的Load/Store指令的寻址方式

字及无符号字节Load/Store指令的一般编码格式如下所示:

字及无符号字节Load/Store指令的一般语法格式如下所示:

ldr|str{<cond>}{B} {T}<Rd>, <addressing_mode>

各种类型的Load/Store指令的寻址方式由两部分组成:一部分为一个基址寄存器,另一部分为一个地址偏移量。基址寄存器可以是任一通用寄存器,地址偏移量则有以下3种格式:

● 立即数

● 寄存器

● 寄存器加一个移位常数

同样,寻址方式的地址计算方法有如下3种:

● 不更新基址寄存器方法

● 事先更新基址寄存器方法

● 事后更新基址寄存器方法

字及无符号字节Load/Store指令的操作数的寻址方式是针对第2个操作数Addressing_mode的,具体细分为以下9种寻址方式。

① [<Rn>, #+/-<offset_12>]

指令编码格式:

示例:

        ldr r0, [r1,-#4]   ;将内存单元r1-4中的字读取到r0寄存器中

注意:当R15/pc用作Rn时,内存基地址为当前指令地址加8字节偏移量。

② [<Rn>, +/-<Rm>]

指令编码格式:

示例:

        ldr r0, [r1,-r2]   ;将内存单元r1-r2中的字读取到r0寄存器中

注意:当R15/pc用作Rn时,内存基地址为当前指令地址加8字节偏移量;当R15/pc用作索引寄存器Rm时,会产生不可预知的结果。

③ [<Rn>, +/-<Rm>, <shift>#<shift_imm>]

指令编码格式:

示例:

        ldr r0, [r1, r2, LSL #2]       ;将内存单元(r1+r2*4)中的字读取到r0中

注意:当R15/pc用作Rn时,内存基地址为当前指令地址加8字节偏移量;当R15/pc用作索引寄存器Rm时,会产生不可预知的结果。

④ [<Rn>, #+/-<offset_12>]!

指令编码格式:

示例:

        ldr r0, [r1,#4]!   ;将内存单元(r1+4)中的字读取到r0中,同时r1=r1+4

注意:当R15/pc用作Rn时,会产生不可预知的结果。

⑤ [<Rn>, +/-<Rm>]!

指令编码格式:

示例:

        ldr r0, [r1, r2]!  ;将内存单元(r1+r2)中的字读取到r0中,同时r1=r1+r2

注意:当R15/pc用作Rn或Rm时,会产生不可预知的结果;当Rn和Rm是同一个寄存器时,会产生不可预知的结果。

⑥ [<Rn>, +/-<Rm>, <shift>#<shift_imm>]!

指令编码格式:

示例:

        ldr r0, [r1, r2, LSL #2]!      ;将内存单元(r1+r2*4)中的字读取到r0中,同时
    r1=r1+r2*4

注意:当R15/pc用作Rn或Rm时,会产生不可预知的结果;当Rn和Rm是同一寄存器时,会产生不可预知的结果。

⑦ [<Rn>], #+/-<offset_12>

指令编码格式:

示例:

        ldr r0, [r1], #4   ;将内存单元r1中的字读取到r0中,然后r1=r1+4,这叫事后更新
    方法

注意:当R15/pc用作Rn或Rm时,会产生不可预知的结果。

      ⑧ [<Rn>],   +/-<Rm>

指令编码格式:

示例:

        ldr r0, [r1], r2   ;将内存单元r1中的字读取到r0中,然后r1=r1+r2,这叫事后更新
    方法

注意:当R15/pc用作Rn或Rm时,会产生不可预知的结果;当Rn和Rm是同一寄存器时,会产生不可预知的结果。

      ⑨ [<Rn>],   +/-<Rm>,    <shift>#<shift_imm>

指令编码格式:示例:

        ldr r0, [r1], r2, LSL #2       ;将内存单元r1中的字读取到r0中,然后
    r1=r1+r2*4,这叫事后更新方法

注意:当R15/pc用作Rn或Rm时,会产生不可预知的结果;当Rn和Rm是同一寄存器时,会产生不可预知的结果。

3.4.3 杂类Load/Store指令的寻址方式

杂类Load/Store指令包括半字、带符号字节、双字的Load/Store指令。

杂类Load/Store指令的一般编码格式如下所示:

杂类Load/Store指令的一般语法格式如下所示:

ldr|str{<cond>}H|SH|SB|D <Rd>, <addressing_mode>;addressing_mode为bit[11:0];

杂类Load/Store指令操作数的寻址方式是针对第2个操作数Address_mode的,具体细分为以下6种寻址方式:

① [<Rn>, #+/-<offset_8>];offset_8=(offsetH<<4)|offsetL

指令编码格式:示例:

        ldrsb   r0, [r1, #3]   ;将内存单元(r1+3)中的有符号字节读取到r0中,r0中高24
    位为字节的符号位

注意:当R15/pc用作Rn时,内存基地址为当前指令地址加8字节偏移量。

② [<Rn>, +/-<Rm>]

指令编码格式:

示例:

        strh    r0, [r1, r2]   ;将r0中的低16位数据保存到内存单元(r1+r2)中

注意:当R15/pc用作Rn时,内存基地址为当前指令地址加8字节偏移量;当R15/pc用作索引寄存器Rm时,会产生不可预知的结果。

③ [<Rn>, #+/-<offset_8>]!;offset_8=(offsetH<<4)|offsetL

指令编码格式:

示例:

        ldrsh   r0, [r1, #2]!  ;将内存单元(r1+2)中的有符号半字读取到r0中,r0中高16
    位为半字的符号位,同时r1=r1+2

注意:当R15/pc用作Rn时,会产生不可预知的结果。

④ [<Rn>, +/-<Rm>]!

指令编码格式:

示例:

        ldrh    r0, [r1, r2]!  ;将存单元(r1+r2)中的半字数据读取到r0中,r0中的高16
    位设置为0,同时r1=r1+r2

注意:当R15/pc用作Rn或Rm时,会产生不可预知的结果;当Rn和Rm是同一寄存器时,会产生不可预知的结果。

⑤ [<Rn>], #+/-<offset_8>;offset_8=(offsetH<<4)|offsetL

指令编码格式:示例:

        strh    r0, [r1], #2   ;将r0中低16位数据保存到内存单元(r1)中,然后r1=r1+2

注意:当R15/pc用作Rn或Rm时,会产生不可预知的结果。

⑥ [<Rn>], +/-<Rm>

指令编码格式:

示例:

        strh    r0, [r1], r2   ;将r0中低16位数据保存到内存单元(r1)中,然后r1=r1+r2

注意:当R15/pc用作Rn或Rm时,会产生不可预知的结果;当Rn和Rm是同一寄存器时,会产生不可预知的结果。

3.4.4 批量Load/Store指令的寻址方式

批量Load/Store指令的一般编码格式如下所示:

批量Load/Store指令的一般语法格式如下所示:

ldm|stm{<cond>}<addressing_mode> <Rn>{!}, <registers>{^};addressing_mode为bit[11:0];

一条批量Load/Store指令可以实现在一组寄存器和一块连续的内存单元之间传输数据,指令中的寄存器组和内存单元的对应关系有固定的规则,即:低编号的寄存器对应于内存单元中低地址单元,高编号的寄存器对应于内存中高地址单元,基址寄存器<Rn>中存放地址块的起始地址值,可能是最高地址值,也可能是最低地址值。

指令中的<addressing_mode>表示地址的变化方式,共有如下4种方式:

① IA-Increment After 事后递增方式

指令编码格式如下所示:

② IB-Increment Before 事先递增方式

指令编码格式如下所示:

③ DA-Decrement After 事后递减方式

指令编码格式如下所示:

④ DB-Decrement Before 事先递减方式

指令编码格式如下所示:

除了可以在一组寄存器和一块连续的内存单元之间批量传输数据外,还可以在数据栈中批量传输数据。数据栈的栈指针通常可以指向不同的位置,栈指针指向栈顶元素(即最后一个入栈的数据元素)的叫Full栈。栈指针指向栈顶元素相邻的一个可用数据单元的称为Empty栈。

另外数据栈的增长方向也可以不同,数据栈向内存地址减小的方向增长叫Descending栈;数据栈向内存地址增加的方向增长的叫Ascending栈。

综合以上两种特点,总共有下面4种数据栈:

● FD Full Descending

● ED Empty Descending

● FA Full Ascending

● EA Empty Ascending

不同数据栈对应的批量Load/Store指令的寻址方式如表3-2所示。

表3-2 Load/Store寻址方式

3.4.5 协处理器Load/Store指令的寻址方式

杂类Load/Store指令的一般编码格式如下所示:

协处理器Load/Store指令的一般语法格式如下所示:

mcr|mrc{<cond>}{L} <coproc>,<Rd>,<addressing_mode>

其中<addressing_mode>表示地址变化方式,有以下4种格式:

① 偏移量[<Rn>, #+/-<offset_8>*4]

指令编码格式如下所示:

注意:当R15/pc作为Rn时,其值为当前指令的地址加8。

② 事前更新[<Rn>, #+/-<offset_8>*4]!

指令编码格式如下所示:

注意:当R15/pc作为Rn时,会产生不可预知的结果。

③ 事后更新[<Rn>], #+/-<offset_8>*4

指令编码格式如下所示:

注意:当R15/pc作为Rn时,会产生不可预知的结果。

④ 非索引[<Rn>], <option>;<option>未被ARM使用,可用作协处理器来扩展指令。

指令编码格式如下所示:

注意:当R15/pc作为Rn时,其值为当前指令的地址加8。

3.4.6 ARM指令的寻址方式总结

所谓寻址方式就是处理器根据指令中给出的地址信息来寻找物理地址的方式,总结上面的详细描述,可以把ARM指令的寻址方式归纳成以下7种:

3.5 ARM汇编伪操作(Directive)

ARM汇编语言源程序的语句由指令、伪操作和宏指令组成,伪操作叫Derective,宏指令叫pseudo-instruction,宏指令也是通过伪操作来定义的。

伪操作是由汇编器在对源程序汇编期间进行处理的;宏则是一段独立的程序代码,在程序中通过宏指令调用宏,当程序被汇编时,汇编器对每个宏调用进行展开,用宏定义体取代源程序中的宏指令。

ARM伪操作主要包括以下几类:

1)符号定义(Symbol definition)伪操作;

2)数据定义(Data definition)伪操作;

3)汇编控制(Assembly control)伪操作;

4)栈中数据帧描述(Frame description)伪操作;

5)信息报告(Reporting)伪操作;

6)其他(Miscellaneous)伪操作。

3.5.1 符号定义伪操作

3.5.2 数据定义伪操作

续表

3.5.4 栈中数据帧描述伪操作

主要用于调试,此处不做介绍。

3.5.5 信息报告伪操作

表3-2 opt伪操作选项编码

ttl

在列表文件每一页的开头插入一个标题,它将作用于其后的每一页直到遇到新的ttl。

语法格式

        ttl title

subt

在列表文件每一页的开头插入一个子标题,它将作用于其后的每一页直到遇到新的subt。

语法格式

        subt    title

3.5.6 其他伪操作

续表

3.6 ARM汇编伪指令

ARM伪指令不是真正的ARM指令或Thumb指令,伪指令在汇编编译器对源程序进行汇编处理时被替换成对应的ARM或Thumb指令序列。

3.7 Thumb指令介绍

Thumb指令集是将ARM指令集的一个子集重新编码形成的一个指令集。ARM指令长度为32位而Thumb指令长度为16位,所以使用Thumb指令集可以得到密度更高的代码,这对于需要严格控制产品成本的设计是非常有意义的。

与ARM指令集相比,Thumb指令集有以下局限:

1)完成相同的操作,通常需要更多的Thumb指令,所以在对系统运行时间要求苛刻的应用场合ARM指令集更为合适;

2)Thumb指令集没有包含进行异常处理的一些指令,因此在异常处理低级处理时,还是需要ARM指令,这就使得Thumb指令必须与ARM指令配合使用。

Thumb指令集有两个版本:版本1用于ARMv4的T变种;而版本2用于ARMv5及以上的T变种。

Thumb指令集的版本2相对于版本1有以下特点:

1)通过增加指令和对已有指令的修改,提高ARM指令和Thumb指令混合使用时的效率;

2)增加了软件断点指令;

3)更严格定义了Thumb乘法指令对条件标志位的影响。