在kernel开发以及debug过程中,难免接触到汇编命令,这里仅记录一些常用的,基于arm64的汇编命令。
1. A64常用寄存器
x0-x30 64bit 通用寄存器,有只用低 32bit的w0-w30
FP(x29) 64bit 栈底指针
LR(x30) 64bit 通常称x30为程序链接寄存器,保存跳转返回信息地址
XZR 64bit Zero寄存器,写入此寄存器的数据被忽略,读出的数据全为0
WZR 32bit Zero寄存器的32bit形式
ELR_ELx 64bit 异常链接寄存器,保存异常进入ELx的异常地址(x={1,2,3})
SP_ELx 64bit 栈指针, 保存进入ELx的栈地址(x={0,1,2,3})
PC 64bit 当前程序运行的指针
SPSR_ELx 32bit 保存的处理状态寄存器(x={1,2,3}),用于存放程序运行中一些状态标识。
状态寄存器又分为 The Current Program Status Register (CPSR) 和The Saved Program Status Registers (SPSRs)。 一般都是使用CPSR,当发生异常时, CPSR会存入SPSR。当异常恢复,再拷贝回CPSR。
这里的ELx (x={0,1,2,3}) 是异常等级(exception level),每个程序能跑到不同级别,最低的是EL0最高的是EL3,这里不具体展开说明。
x0-x7: 用于子程序调用时的参数传递,x0还用于返回值传递。
2. A64指令集--加载与存储指令
常见的内存加载指令是LDR指令,存储指令是STR指令。
LDR 目标寄存器, <存储器地址> // 把存储器地址中的数据加载到目标寄存器中
STR 源寄存器, <存储器地址> // 把源寄存器的数据存储到存储器中
在A64指令集中,加载和存储指令有多种寻址模式
1.基地址模式
基地址模式首先使用寄存器的值来表示一个地址,然后把这个内存地址的内容加载到通用寄存器中。基地址加偏移量模式是指在这个基地址的基础上再加上偏移量,从而计算内存地址,并且把这个内存地址的值加载到通用寄存器中。偏移量可以是正数,也可以是负数。
LDR Xt, [Xn] // 以Xn寄存器中的内容作为内存地址,加载此内存地址的内容到Xt寄存器中。
STR Xt, [Xn] // 把Xt寄存器中的内容存储到Xn寄存器上的内存地址中
LDR Xt, [Xn, #offset] // 把Xn寄存器中内容加一个偏移量(offset必须是8的倍数),以相加的结果作为内存地址,加载此内存地址的内容到Xt寄存器
STR Xt, [Xn, #offfset] // 把Xt寄存器的值存储到以Xn寄存器的值加一个偏移量表示的内存地址中
2.变基模式
变基模式主要有如下两种。
前变基模式:先更新偏移量地址,后访问内存地址
后变基模式:先访问内存地址,后更新偏移量地址
前变基模式的指令格式如下:
首先,更新Xn/SP寄存器的值为Xn/SP寄存器的值加simm。然后,以新的Xn/SP寄存器的值为内存地址,加载该内存地址的值到Xt寄存器,
simm:表示偏移量,带符号的立即数,取值范围为-256~255.
LDR <Xt>, [<Xn|SP>, #simm]!
后变基模式的指令格式如下:
首先以Xn/SP寄存器的值为内存地址,加载该内存地址的值到Xt寄存器,然后更新Xn寄存器的值为Xn/SP寄存器的值加simm.
LDR <Xt>, [<Xn|SP>], #<simm>
3.PC相对地址模式
汇编代码里常常会使用标签(label)来标记代码片段。LDR指令还提供一种访问标签的地址模式,指令格式如下。
LDR <Xt>, <label>
这条指令读取label所在内存地址的内容到Xt寄存器中。但是这个label必须在当前PC地址前后1MB的范围内,若超出这个范围,汇编器会报错。
4.LDR伪指令
伪指令是对汇编器发出的命令,它在源程序汇编期间由汇编器处理。伪指令可以完成选择处理器、定义程序模式、定义数据、分配存储区、指示程序结束等功能。总之,伪指令可以分解成几条指令的集合。
LDR指令既可以是在大范围内加载地址的伪指令,也可以是普通的内存访问指令。当它的第二个参数前面有“=”时,表示伪指令;否则,表示普通的内存访问指令。注意,GNU汇编器没有对应的STR伪指令。
LDR伪指令的格式如下:
LDR Xt, =<label> // 把label标记的地址加载到Xt寄存器
一个简单总结:
// 前变基模式。先更新X1寄存器的值为X1寄存器的值加8,然后以新的值为内存地址,加载该内存地址的值到X0寄存器
LDR X0, [X1, #8]!
方括号([])表示从该内存地址中读取或者存储数据,而指令中的感叹号(!)表示是否更新存放内存地址的寄存器,即写回和更新寄存器。
3. A64指令集--加载与存储指令的变种
1.不同位宽的加载与存储指令
指令 | 说明 |
---|---|
LDR | 数据加载指令 |
LDRSW | 有符号的数据加载指令,单位为字 |
LDRB | 数据加载指令,单位为字节 |
LDRSB | 有符号的加载指令,单位为字节 |
LDRH | 数据加载指令,单位为半字 |
LDRSH | 有符号的数据加载指令,单位为半字 |
STRB | 数据存储指令,单位为字节 |
STRH | 数据存储指令,单位为半字 |
2.不可扩展的加载和存储指令--LDUR
3.多字节内存加载和存储指令--LDP和STP
4.独占内存访问指令--LDXR,STXR,LDXP,STXP
5.隐含加载-获取/存储-释放内存屏障原语--LDAR,STLR
4. 入栈与出栈
在函数调用过程中,如果传递的参数少于或等于8个,那么使用X0-X7通过寄存器来传递。当参数多于8个时,则需要使用栈来传递.
A32指令集提供了PUSH和POP指令来实现入栈和出栈操作,不过,A64指令集已经去掉了PUSH和POP指令。在A64指令集上,入栈和出栈操作可以使用加载和存储指令来实现
5. MOV指令
MOV指令常常用于寄存器之间的搬移和立即数搬移。
用于寄存器之间搬移的MOV指令格式如下。
MOV <Xd|SP>, <Xn|SP>
用于立即数搬移的MOV指令格式如下:
MOV <Xd>, #<imm>
这里能搬移的立即数只有两种:
- 16位立即数
- 16位立即数左移16位、32位或者48位后的立即数
因此用于立即数搬移的MOV指令等同于如下的MOVZ指令
MOVZ <Xd>, #<imm16>, LSL #<shift>
MOV指令还能搬移一些用于位图的立即数,此时它等同于ORR指令。
ORR <Xd|SP>, XZR, #<imm>
6. A64指令集--算术与移位指令
6.1 条件操作码
A64指令集沿用了A32指令集中的条件操作,在PSTATE寄存器中有4个条件标志诶,即N、Z、C、V。
条件标志位 | 描述 |
---|---|
N | 负数标志(上一次运算结果为负值) |
Z | 零结果标志(上一次运算结果为零) |
C | 进位标志(上一次运算结果发生了无符号数溢出) |
V | 溢出标志(上一次运算结果发生了有符号数溢出) |
6.2 加法与减法指令
普通的加法指令有下面几种用法。
- 使用立即数的加法
格式如下:
- 使用寄存器的加法
- 使用移位操作的加法
常用汇编命令
MOV x1,x0
; 将寄存器x0的值传送到寄存器x1
ADD x0,x1,x2
; 寄存器x1和x2的值相加后传送到x0
SUB x0,x1,x2
; 寄存器x1和x2的值相减后传送到x0
AND x0,x0,#0xF
; x0的值与0xF相位与后的值传送到x0
ORR x0,x0,#9
; x0的值与9相位或后的值传送到x0
EOR x0,x0,#0xF
; x0的值与0xF相异或后的值传送到x0
LDR x5,[x6,#0x8]
; x6寄存器加0x8的和值传送到x5
STR x0, [SP, #0x8]
; x0寄存器的数据传送到SP+0x8地址值指向的存储空间
STP x29, x30, [sp, #0x10]
; 入栈指令
LDP x29, x30, [sp, #0x10]
; 出栈指令
CBZ x19, 0x10
; 比较,如果结果为零(Zero)就转移(只能跳到后面的指令)
CBNZ x19, 0x10
; 比较,如果结果非零(Non Zero)就转移(只能跳到后面的指令)
B/BL
; 绝对跳转,返回地址保存到LR(x30)
b ffff000008283b80
bl ffff000008dc566c
其中 MOV 指令只能用于寄存器之间传值,寄存器和内存之间传值通过 LDR 和 STR.
A64指令又一个重要特点就不是所有指令都是带有条件的,就是说汇编中部分指令(跳转指令为主)需要根据状态寄存器中的一些状态来控制分支的执行。例如:
`b.cc ffff000008283dac <show_slab_objects+0x94>
ARM手册里面各个状态指令和代表的含义:
ARM手册里面各个状态指令和代表的含义
3、举例说明
通常出问题的时候是需要将ko或者整个内核反汇编出来的,下面以ko为例,如何反汇编。这里的-S 如果有符号信息,会把代码与汇编放一起,如果没有符号信息,就只有反汇编内容。
aarch64-linux-gnu-objdump -S reg_hal.ko > test.txt
以一段非常简单的代码为例
int example_test(int a, u32 *b)
{
if(a > 0)
*b = 1;
else
*b = 16;
return 0;
}
对应的反汇编如下,我们一句一句翻译一下,大概就能看明白了。
000000000003a150 <example_test>:
3a150: a9be7bfd stp x29, x30, [sp,#-32]! //把x29 x30 的值存到sp-32的地址后,sp=sp-32
3a154: 910003fd mov x29, sp //把 sp的值放在x29中
3a158: a90153f3 stp x19, x20, [sp,#16] //把x19 x20 的值存到sp+16的地址中后sp=sp+16
3a15c: 2a0003f3 mov w19, w0 //把w0 的值存到w19 中
3a160: aa1e03e0 mov x0, x30 //把x30 的值放在x0
3a164: aa0103f4 mov x20, x1 //把x1的值放在x20
3a168: 94000000 bl 0 <_mcount> //跳转到地址0,这里还不太清楚,我理解是需要加载ko后,跳转到某些符号对应的地址上
3a16c: 6b1f027f cmp w19, wzr //比较w19 和 0
3a170: 540000ed b.le 3a18c <example_test+0x3c> //如果小于 就跳转到<example_test+0x3c>这个地址
3a174: 52800020 mov w0, #0x1 // 写w0 为1
3a178: b9000280 str w0, [x20] //w0的值写到x20内
3a17c: 52800000 mov w0, #0x0 // w0 清零
3a180: a94153f3 ldp x19, x20, [sp,#16] // sp+16 地址的值分别放回x19 x20
3a184: a8c27bfd ldp x29, x30, [sp],#32 //sp地址取的值分别放回x29 x30后sp=sp+32
3a188: d65f03c0 ret //返回
3a18c: 52800200 mov w0, #0x10 //w0 的值写成0x10
3a190: b9000280 str w0, [x20] //w0 的值存到 x20
3a194: 52800000 mov w0, #0x0 // w0 清零
3a198: a94153f3 ldp x19, x20, [sp,#16] // sp+16 地址的值分别放回x19 x20
3a19c: a8c27bfd ldp x29, x30, [sp],#32 //sp地址取的值分别放回x29 x30后sp=sp+32
3a1a0: d65f03c0 ret //返回
3a1a4: d503201f nop //空操作
参考文献:
评论 (0)