03_ARMv8指令集介绍加载与存储指令
Github地址:
ARMv8指令集介绍
A64指令集只能运⾏在aarch64
所有A64汇编都是32 bits宽的
关注指令的使⽤、有什么limitation
A64能访问的地址数据是64位宽的
A64⽀持全部的⼤写或者⼩写⽅式
ARM官⽅⼤写
应⽤使⽤⼩写
寄存器命名
Wn表⽰32bits宽的寄存器
Xn表⽰64bits宽的寄存器
WZR表⽰32位内容全为0的寄存器
XZR表⽰64位内容全为0的寄存器
...
LDR指令
LDR Xd, [Xn, $offset]
【释义】:将Xn寄存器中存储的地址+offset地址偏移存组成⼀个,把这个地址⾥⾯存储的值放在Xd寄存器中。[]有取地址内存储的数值的含义。
【⽰例】:
S1: 使⽤MOV指令把0x80000加载到X1寄存器:MOV x1, 0x80000(如果是⼀个数,⽽⾮#0x80000, 则是⼀个地址)
S2: 使⽤MOV指令把16数值加载到X3寄存器:MOV x3, 16
S3: 使⽤LDR指令读取X1地址⾥⾯存储的值,存储到X0中: LDR x0,[x1] , 这个不允许->LDR x2,[0x80000]
S4:使⽤LDR指令读取X1 + 8地址⾥⾯存储的值,存储到X2中:LDR x2,[x1, #8]
S5:使⽤LDR指令读取(X1 + X3)地址⾥⾯存储的值,存储到X4中:LDR x4,[x1, x3]
S6: 使⽤LDR指令读取(X1+(X3<<3))地址⾥⾯存储的值,存储到X5中:LDR x5,[x1,x3,lsl #3]
【注意】:
给的数不加任何标志的视为地址
需要给⽴即数的场景⽽⾮地址的值,使⽤#
[]有取地址值的意思
LDR lsl扩展指令,只⽀持1和3
LDR x2,[x1, #8] x1的值不会被更新为0x80008
【变基模式】:
前变基模式 pre-index:先更新偏移地址,后访问地址(注意有叹号!表⽰)
LDR x6, [x1, #8]! : 将x1⾥⾯的地址增加偏移#8并赋给x1,最后将新的x1寄存器内的地址的值给x6寄存器
后变基模式 post-index:先访问内存地址,再更新偏移地址
LDR x6, [x1], #8 : 将x1寄存器内的地址的值赋给x6寄存器,并将x1地址偏移+8。
【伪指令】:
伪指令与指令的最⼤不同在于,伪指令属于编译器处理的范畴,伪指令会被编译展开为多条指令;指令是CPU处理的命令的最⼩单元。
LDR x7,=0x80000 -> 等同于 MOV x7, 0x80000
需要区别LDR x7, 0x800000; 这条指令的意义是,将当前PC寄存器的地址的 + 0x80000的偏移,取出地址内容填充到x7寄存器中。
STR指令
从⼀个寄存器的值吐到内存中,⽀持⽴即数和寄存器操作。把Xd的值,存储到[xn|sp]⾥⾯。
immediate-post-index: STR Xd, [Xn|SP], #<simm>
immediate-pre-index: STR Xd, [Xn|SP, #<simm>]!
【⽰例】:
; Example 1:
MOV x2, 0x400000            ; -> x2 is 0x400000
LDR x6, =0x1234abce          ; -> x6 is 0x1234abce
STR x6, [x2, #8]!            ; -> 把x6的值(0x1234abce),存储到0x400008地址的内存⾥⾯
; What's value of x2? And the value in 0x400000 address?
;Example 2:
MOV x2, 0x500000    ; -> x2 is 0x500000
STR x6, [x2], #8    ; -> 把x6的值(0x1234abce),存储到0x500000⾥⾯,并将x2寄存器变为0x500008
; What's value of x2? And the value in 0x400000 address?
MOV/MOVZ指令
MOV底层原理实际上是MOVZ,MOV 16-bit的⽴即数到寄存器。
MOV xd, #<imm> 16位⽴即数
MOVZ xd, #<imm16>, LSL #<shift> 16位的⽴即数,逻辑左移动 16,32,48位
LDP/STP指令
相⽐于LDR和STR指令(8 bytes),LDP和STP指令⽤于多字节(16 bytes)操作,
【释义】:
LDP :LDP x3, x7, [x0] -> 从x0的值为基地址,加载地址到X3寄存器,存储x0+8到x7寄存器。
STP :STP x1, x2, [x4]-> 以x4的值为基地址,存储x1地址的值到x4,存储x2地址的值到x4 + 8。
【练习】:
练习1:使⽤LDR和STR多字节加载和存储命令实现memset()函数,假设内存地址s是16字节对齐,count也是16字节对齐。例如:memset(0x200000, 0x55, 32)
// memset_a_byte
void *memset_a_byte (void *s, int c) {
char *xs = s;
offset指令是什么意思*xs++ =c;
return s;
}
// 使⽤STR指令,单字节操作
.global my_memset_test:
my_memset_test:
// 保存地址s到x1寄存器,保存c的值到x2寄存器,保存长度到x3寄存器
MOV x1, 0x2000000  // 这个值是需要被修改的肯定需要STP
MOV x2, 0x55        // 这个是个固定的参数
ADD x0, x1, 32
// 确定原⼦操作向地址写值,然后地址增加
wrt:
STR x2, [x1], #8 // 把x2⾥⾯的值存储到x1⾥⾯(0x55 -> 0x200000),接着0x200008加⼀
cmp x1, x0
< wrt
ret
/
/ 使⽤STP指令,双字节操作
.global my_memset_test:
my_memset_test:
// 保存地址s到x1寄存器,保存c的值到x2寄存器,保存长度到x3寄存器
MOV x1, 0x2000000  // 这个值是需要被修改的肯定需要STP
MOV x2, 0x55        // 这个是个固定的参数
ADD x0, x1, 32
// 确定原⼦操作向地址写值,然后地址增加
wrt:
STP x2, x2, [x1], #16 // 把x2⾥⾯的值存储到x1⾥⾯(0x55 -> 0x200000),接着0x200008加⼀
cmp x1, x0
< wrt
ret
练习⼆:同上,使⽤⾮对齐的memset(0x200004, 0x55, 37)
// 需要汇编和C语⾔混合编程实现对于⾮16字节对齐的地址和长度进⾏memset操作
// 汇编实现⼀个16字节的memset
// C语⾔⽤于对⾮对齐部分进⾏C语⾔单字节的处理,⽤汇编实现16字节对齐地址和16字节对齐长度的处理。
// 函数调⽤为 memset(0x200004, 0x55, 37)
.global asm_memset_16_byte_align:
asm_memset_16_byte_align:
ADD x4, x0, x2
wrt:
STP x1, x1, [x0], #16
CMP x0, x4
< wrt
ret
void *memset (void *s, int c, int count)
{
int align = 16;
if (s & (align - 1)) {
// 处理⾮对齐地址
}
// 对齐部分直接调⽤ asm_memset_16_byte_align(s, c, l);
/
/ ⾮对齐部分直接c语⾔指针访问赋值。
}
⼀些需要注意的地⽅
FAQ1:加载⼀个很⼤的数值到通⽤寄存器,例如0xFFFF0000FFFF0000, 使⽤MOV指令,是否正确?
错误,MOV 后⾯的⽴即数为16-bit,应该是使⽤LDR x1,=0000 伪指令来加载⼤数。
FAQ2:加载⼀个寄存器的值,使⽤移位:MOV x1, (1<<0) | (1<<2)|(1<<20)|(1<<40)|(1<<55)
错误,同样是MOV⽴即数16-bit,使⽤LDR x1, = (1<<0)|......|(1<<55).
FAQ3:字符串的LDR指令
string1:
.string "Booting at EL"
LDR x0, string1      // 加载string1字符串的ascii码值到寄存器,最⾼限制在X寄存器⼤⼩也就是 64-bit,如果是W寄存器就是32-bit
LDR x1, =string1    // 加载string1字符串的地址到x1
FAQ3:定义数据LDR指令
my_data:
.word 0x40
LDR x0, my_data    //加载0x40到X0,等同于MOV x0,0x40 前提是不超过16bits
LDR x1, =my_data    //加载存储my_data的地址到x1
⼀种易错的死机状态:树莓派4b上⾯的寄存器都是32bit的,下⾯代码配置26到U_IBRD_REG寄存器,有什么问题?
LDR x1, =U_IBRD_REG
MOV x2, #26
STR x2, [x1]
//正确写法:
LDR w1, =U_IBRD_REG
MOV w2, #26
STR w2, [w1]
错误点在于树莓派4b寄存器访问都是32bit的,现在使⽤X寄存器,为64位的寄存器,应该使⽤W寄存器,32位寄存器访问。GDB-Tips
启动GDB和QEMU链接
> gdb-multiarch --tui benos.elf
gdb> c
gdb> target remote localhost:1234
gdb> b ldr_test // 设定断点
gdb> c
gdb> next //下⼀步
gdb> info register // 查看所有寄存器
gdb> info x1 x2 x3 // 查看x1/x2/x3寄存器
gdb> x 0x80000 // 读取内存0x80000值 32位gdb> x/xg 0x80000 // 读取内存0x80000值64位

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。