登录后台

页面导航

本文编写于 488 天前,最后修改于 450 天前,其中某些信息可能已经过时。

加载与存储指令

ARMv8体系结构是基于指令加载与存储的体系结构。所有的数据处理都需要在通用寄存器中完成,而不能直接在内存中完成。因此,需要首先把数据从内存搬移到通用寄存器中,然后才能进行数据处理,最后再把结果写回到内存中。

// 把存储器地址里的数据加载到目标寄存器中
LDR target register <store address>
// 把源寄存器的数据存储到存储器中
STR source register <store address>

基于基地址的寻址模式

基地址模式首先使用寄存器的值来表示一个地址,然后把这个内存地址的内容加载到通用寄存器中。基地址加偏移量模式是指在基地址的基础上再加上偏移量,从而计算内存地址,并且把这个内存地址的值加载到通用寄存器中。偏移量可以是正数,也可以是负数。

  • 基地址模式:

    LDR Xt, [Xn] 以Xn寄存器中的内容作为内存地址,加载此内存地址的内容到Xt寄存器。

    STR Xt, [Xn] 把Xt寄存器中的内容存储到Xn寄存器的内存地址中

  • 基地址加偏移量模式:

    LDR Xt, [Xn, $offset] 把Xn寄存器中的内容加一个偏移量(offset必须是8的倍数),以相加的结果作为内存地址,加载此内存地址的内容到Xt寄存器

    STR Xt, [Xn, $offset] 把Xt寄存器的值存储到以Xn寄存器的值加一个偏移量(offset是8的倍数)表示的内存地址中。

image-20230606222118708

变基模式

主要有如下两种。

  • 前变基(pre-index)模式:先更新偏移量地址,后访问内存地址。
  • 后变基(post-index)模式:先访问内存地址,后更新偏移量地址。

image-20230606222144710

PC相对地址模式

汇编代码里常常会使用标签(label)来标记代码片段。LDR指令还提供一种访问标签的地址模式LDR ,

image-20230606222206396

​ LDR指令会把标签my_data的数据读出来

LDR伪指令

伪指令是对汇编器发出的命令,它在源程序汇编期间由汇编器处理。伪指令可以完成选择处理器、定义程序模式、定义数据、分配存储区、指示程序结束等功能。总之,伪指令可以分解为几条指令的集合。

LDR指令既可以是在大范围内加载地址的伪指令,也可以是普通的内存访问指令。当它的第二个参数前面有“=”时,表示伪指令;否则,表示普通的内存访问指令。

LDR Xt, =<label> //把labe 1标记的地址加载到Xt寄存器

加载与存储指令的变种

不同位宽

LDR 数据加载指令
LDRSW 有符号数据加载指令,单位为字
LDRB 数据加载指令,单位为字节
LDRSB 有符号的加载指令,单位为字节
LDRH 数据加载指令,单位为半字
LDRSH 有符号的数据加载指令,单位为半字
STRB 数据存储指令,单位为字节
STRH 数据存储指令,单位为半字

不可扩展

LDR指令中的基地址加偏移量模式为可扩展模式,即偏移量按照数据大小来扩展并且是正数,取值范围为0〜32 760。A64指令集还支持一种不可扩展模式的加载和存储指令,即偏移量只能按照字节来扩展,还可以是正数或者负数,取值范围为-256〜255,例如LDUR指令。 因此,可扩展模式和不可扩展模式的区别在于是否按照数据大小来进行扩展,扩大寻址范围。

LDUR <Xt>, [<Xn|SP>(, #<simm>)]
STUR <Xt>, [<Xn|SP>{, #<simm>)]

LDUR指令的意思是以Xn/SP寄存器的内容加一个偏移量(simm)作为内存地址,加载此内存地址的内容(8字节数据)到Xt寄存器。

STUR指令是把Xt寄存器的内容存储到Xn/SP寄存器加上simm偏移量的地方。

多字节

A64指令集不再提供LDM和STM指令,而提供LDP和STP指令。LDP和STP指令支持3种寻址模式——基地址偏移模式、前变基模式、后变基模式。

image-20230606222231900

入栈与出栈

栈(stack)是一种后进先出的数据存储结构。栈通常用来保存以下内容。

  • 临时存储的数据,例如局部变量等。
  • 参数。在函数调用过程中,如果传递的参数少于或等于8个,那么使用X0-X7通用寄存器来传递。当参数多于8个时,则需要使用栈来传递。

通常,栈是一种从高地址往低地址扩展(生长)的数据存储结构。栈的起始地址称为栈底,栈从高地址往低地址延伸到某个点,这个点称为栈顶。栈需要一个指针来指向栈最新分配的地址, 即指向栈顶。这个指针是栈指针(StackPointer, SP)。把数据往栈里存储称为入栈,从栈中移除 数据称为出栈。当数据入栈时,SP减小,栈空间扩大;当数据出栈时,SP增大,栈空间缩小。 栈在函数调用过程中起到非常重要的作用,包括存储函数使用的局部变量、传递参数等。
在函数调用过程中,栈是逐步生成的。为单个函数分配的栈空间,即从该函数栈底(高地址) 到栈顶(低地址)这段空间,称为栈帧(stack frame)

.global main
main: 
    /*栈往下扩展16字节*/ 
    stp x29, x30, [sp, #-16]!

    /*把栈继续往下扩展8字节*/ 
    add sp, sp, #-8

    mov x8, #1

    /*x8保存到SP指向的位置上*/
    str x8, [sp]

    /*释放刚才扩展的8字节的栈空间*/ 
    add sp, sp, #8

    /*main函数返回0*/ 
    mov w0, 0

    /*恢复x29和x30寄存器的值,使SP指向原位置*/
    ldp x29, x30, [sp], #16
    ret

首先SP寄存器的值减去16,相当于把栈空间往下扩展16字节,然后把X29和X30寄存器的值压入栈,其中X29寄存器的值保存到SP指向的地址中,X30寄存器的值保存到SP指向的值加8对应的内存地址中。把SP寄存器的值减去8,相当于把栈的空间继续往下扩展8字节。把X8寄存器的值保存到SP指向的地址中。接下来是出栈操作了。使SP指向的值加8,相当于把栈空间缩小,也就是释放了刚才申请的8字节空间的栈,这样把X8寄存器的值弹出栈。使用LDP指令把X29和X30寄存器中的值弹出栈。这是一条后变基模式的
加载指令,加载完成之后会修改SP指向的值,让SP指向的值加上16从而释放栈空

MOV指令

MOV指令常常用于寄存器之间的搬移和立即数搬移。

MOV <Xd|SP>, <Xn|SP> //寄存器之间搬移
MOV <Xd>, #<imm> //立即数搬移

这里能搬移的立即数只有两种:

  • 16位立即数
  • 16位立即数左移16位、32位或者48位后的立即数