Load/Store 指令
寻址模式
Base register - w0=[x1]
ldr w0, [x1]
Offset addressing mode - w0=[x1+12]
ldr w0, [x1, 12]
Pre-index addressing mode - x1+=12; w0=[x1]
ldr w0, [x1, 12]!
Post-index addressing mode - w0=[x1]; x1+=12
ldr w0, [x1], 12
更多示例
// load a byte from x1
ldrb w0, [x1]
// load a signed byte from x1
ldrsb w0, [x1]
// store a 32-bit word to address in x1
str w0, [x1]
// load two 32-bit words from stack, then add 8-byte to sp
ldp w0, w1, [sp], 8
// Store two 64-bit regs in [sp-96] and subtract 96-byte from sp.
// ==> [sp]=x1, [sp+8]=x2
stp x1, x2, [sp, -16]!
// LDR伪指令. load 32-bit immediate from literal pool(addr: 0x12345678)
ldr w0, =0x12345678
数据处理指令
Bitfield 操作指令
Bitfield 指令常用于设置/提取寄存器的某个字段.
;BFI(Bit Field Insert)
BFI w0, w0, #9, #6 ;w0[0, 5] = w0[9, 14]
;BFC(Bit Field Clear)
BFC w0, #4, #2 ;w0[4, 5] = 0
;UBFX(Unsigned Bit Field Extract)
UBFX w1, w0, #18, #7 ;w1=w0[18, 24]
与 UBFX 相对的是 SBFX, 若提取后的字段高位为 1, 会进行符号扩展
分支/控制指令
获得地址的相关指令
写汇编中,常常会获取一个变量或者函数(label)的地址,A64 中一共有几种获取地址的指令,
包括adr
, adrp
, adrl
, ldr
等,下面我就对这些指令进行介绍。
ADR
首先是 adr,它的作用是加载 PC 相对地址。PC相对的另一层含义就是物理地址, 而不一定是编译后指令的VMA,仅仅取决于当前的PC在哪,返回的就是偏移后的结果。
我们都知道,指令正常运行不一定PC对应的指令一等等于其在可执行文件中的VMA, 就拿bring-up代码做参考即可。这时候指令能正常执行的原因是:取指动作永远都是简单的PC+1, 仅仅与PC有关而已。
adr 指令的格式是:
adr reg, offset
输入一个 offset,输出 PC+offset 的值,但我们常用的是 offset 填某一个 label,例如函数 或者变量,此时 reg 中存的就是此函数的地址了。例如,
adr x0, a_func # x0 = addr(a_func)
adr 指令中留给 offset 的位数是 21,所以最大的寻址范围为+-1MB。
ADRP
adrp 的指令格式与 adr 相同,不同点是会将计算的结果向下对齐到 4k,但这样直接计算的 地址结果往往是不正确的,因为少了页内的偏移。需要一个额外的 add 指令进行纠正,一般来说 是组合起来用的:
adrp x0, a_func
add x0, x0, :lo12:a_func
显然 adrp 这样做是为了更大的寻址空间,能到+-4GB。
LDR 伪指令
一般通用的ldr指令都是用来加载内存中的值,比如常见的 ldr x0, [x1]
。 或者说容易混淆的加载label处内存的值: ldr x0, label
。
如果 ldr 指令的参数是=<label>
,那就代表伪指令,作用是加载 label 的绝对地址,
注意不是内存的值,而是地址, 拿到的地址是绝对地址,即VMA。
好处是可以获取到一个位于任意 label 的地址,不受寻址空间的约束。
简单说下ldr和ldr伪指令的区别
label_1: // assmue VMA of label_1 = 0x12345678 .word 0xabcdabcd ldr x0, addr // x0 = 0xabcdabcd ldr x0, =addr // x0 = 0x12345678
当目前指令执行地址不等于它们的虚拟地址时,比如当 MMU 未开始时通过 uboot 加载到内存 中直接运行的那段程序,此时用
adr/adrp
这些加载 pc 相对地址的指令就会正确的返回 label 的实际运行地址;而ldr
则是永远返回其编译链接后绑定的虚拟地址。这种情况下 如果寻址范围允许,用adr*
指令更方便。
Hint 指令
yield
指令属于Hint指令的一种,其预留是为了通知处理器当前线程是“不重要的”,处理器可以切换到其他线程去执行。!!注意这个地方的线程指的是硬件线程,而目前所有的ARM处理器都是单硬件线程的,所以yield
在所有ARM处理器上实现为nop
。
有趣特性/常见误区
‘#’ before the immediate value
- A64 assembly language does not require the
#
to introduce constant immediate value. But the assembler can also indentify the#
. - In armv7, there must be a
#
or$
before other than using.syntax unified
. About syntax unified.
Use
.syntax unified
in v7 code, and never use#
on any literal on either v7 or v8. Unified syntax is newer and better, and those#
and$
signs are just more code noise.