ARM汇编学习三

2023-11-09

有时,一次性加载(或存储)多个值更有效率。因此,我们需要使用LDM(载入多个值)和STM(存储多个值)。这些指令基于起始地址的不同,有不同的形式。下面是我们将在本节中将会使用的代码。我们将一步一步地完成每一个指令.代码在test5.s中
.data

array_buff:
.word 0x00000000 /* array_buff[0] /
.word 0x00000000 /
array_buff[1] /
.word 0x00000000 /
array_buff[2]. This element has a relative address of array_buff+8 /
.word 0x00000000 /
array_buff[3] /
.word 0x00000000 /
array_buff[4] */

.text
.global _start

_start:
adr r0, words+12 /* address of words[3] -> r0 /
ldr r1, array_buff_bridge /
address of array_buff[0] -> r1 /
ldr r2, array_buff_bridge+4 /
address of array_buff[2] -> r2 /
ldm r0, {r4,r5} /
words[3] -> r4 = 0x03; words[4] -> r5 = 0x04 /
stm r1, {r4,r5} /
r4 -> array_buff[0] = 0x03; r5 -> array_buff[1] = 0x04 /
ldmia r0, {r4-r6} /
words[3] -> r4 = 0x03, words[4] -> r5 = 0x04; words[5] -> r6 = 0x05; /
stmia r1, {r4-r6} /
r4 -> array_buff[0] = 0x03; r5 -> array_buff[1] = 0x04; r6 -> array_buff[2] = 0x05 /
ldmib r0, {r4-r6} /
words[4] -> r4 = 0x04; words[5] -> r5 = 0x05; words[6] -> r6 = 0x06 /
stmib r1, {r4-r6} /
r4 -> array_buff[1] = 0x04; r5 -> array_buff[2] = 0x05; r6 -> array_buff[3] = 0x06 /
ldmda r0, {r4-r6} /
words[3] -> r6 = 0x03; words[2] -> r5 = 0x02; words[1] -> r4 = 0x01 /
ldmdb r0, {r4-r6} /
words[2] -> r6 = 0x02; words[1] -> r5 = 0x01; words[0] -> r4 = 0x00 /
stmda r2, {r4-r6} /
r6 -> array_buff[2] = 0x02; r5 -> array_buff[1] = 0x01; r4 -> array_buff[0] = 0x00 /
stmdb r2, {r4-r5} /
r5 -> array_buff[1] = 0x01; r4 -> array_buff[0] = 0x00; */
bx lr

words:
.word 0x00000000 /* words[0] /
.word 0x00000001 /
words[1] /
.word 0x00000002 /
words[2] /
.word 0x00000003 /
words[3] /
.word 0x00000004 /
words[4] /
.word 0x00000005 /
words[5] /
.word 0x00000006 /
words[6] */

array_buff_bridge:
.word array_buff /* address of array_buff, or in other words - array_buff[0] /
.word array_buff+8 /
address of array_buff[2] */
在这里插入图片描述
.word指向了一个32位共计4字节的数据(内存)区块,这对于理解代码中的偏移很重要。程序中包含了.data区段,在此我们开辟了一个有5个元素的空数组array_buff。.text段包含了由内存操作指令构成的代码,和一个包含了两个标签的只读数据池,其中一个标签规定了一个具有7个元素的数组,另一个标签起到了“桥接”.text区段和.data区段的作用,我们可以用它访问.data区段的array_buffer。

编译链接
在这里插入图片描述
载入gdb调试,在_start下断点
在这里插入图片描述
run,可以看到将要执行的指令
在这里插入图片描述
adr r0, words+12 /* 将word[3]的地址传送给r0*/
我们使用ADR指令(偷懒的办法)将数组第四部分(word[3])的地址传送给R0。我们用R0指向word数组的中间位置,所以我们可以从这开始向前或者向后移动指针
nexti执行
在这里插入图片描述
可以看到此时r0的地址为0x100b8,这是word[3]的地址,说明数组首地址word[0]为0x100b8-0xc(12的16进制)=0x100ac,使用下图命令验证,确实如此
在这里插入图片描述

在上上张图可以看到接下来要执行的两条指令
ldr r1, array_buff_bridge /* address of array_buff[0] -> r1 /
ldr r2, array_buff_bridge+4 /
address of array_buff[2] -> r2 /
执行完上面的两条命令后,R1,R2中分别包含了 array_buff[0]的地址和 array_buff[2]的地址
我们使用nexti 2来执行
在这里插入图片描述
可以看到此时r1为0x200d0,r2为0x200d8,它们分别是array_buff[0]的地址和 array_buff[2]的地址
下一条指令为
ldm r0, {r4,r5} /
words[3] -> r4 = 0x03; words[4] -> r5 = 0x04 /
使用LDM指令,将R0指向的内存中,取两个字的数据出来。由于之前我们让R0指向了word[3],因此 word[3]的值会被存入R4, word[4]的会被存入R5
也就是说,使用一条指令加载了多个数据(2个数据块),并将R4设置为0x00000003 ,R5设置为 0x00000004
使用nexti执行后如图所示
在这里插入图片描述
符合我们的推测
下一条指令是
stm r1, {r4,r5} /
r4 -> array_buff[0] = 0x03; r5 -> array_buff[1] = 0x04 /
使用STM指令将多个值写入内存。STM指令将R4,R5寄存器的值0x3和0x4存入R1指向的内存空间中。由于之前我们让R1指向 array_buff的第一个元素,所以指令执行后 array_buff[0] = 0x00000003 ,array_buff[1] = 0x00000004。如果没有特别说明,LDM和STM指令操作的基本单位是一个字(32位等于4个字节)
使用nexti执行后可以看到符合我们的推测
在这里插入图片描述
LDM和STM有多种不同的使用形式。具体是哪一种使用形式由指令的后缀所决定。这个示例列出了后缀的几种形式:-IA (之后增加), -IB (之前增加), -DA (之后减少), -DB (之前减少)。这么多种类型,他们之间是如何区分的呢?由指令的第一个字段:运算指令规定了访问内存的方式(寄存器作为源地址还是目标地址)。实际上,由于LDM和LDMIA作用相同,所以每次载入完成后,地址指针会自己增加,从而指向下一个被载入的元素。用这种方法可以从某内存地址中获取( 前向 )序列化的数据,并载入寄存器
我们看接下来要执行的两条指令
ldmia r0, {r4-r6} /
words[3] -> r4 = 0x03, words[4] -> r5 = 0x04; words[5] -> r6 = 0x05; /
stmia r1, {r4-r6} /
r4 -> array_buff[0] = 0x03; r5 -> array_buff[1] = 0x04; r6 -> array_buff[2] = 0x05 /
执行完上面的两条指令后,寄存器R4-R6分别包含了 0x3,0x4,0x5 ,内存地址 0x000100D0, 0x000100D4,和 0x000100D8中也包含了0x3,0x4,0x5
nexti 2执行
在这里插入图片描述
可以看到r4-r6确实符合,再看看0x200d0及其后的地址
在这里插入图片描述
同样验证了
接下来的两条指令为
ldmib r0, {r4-r6} /
words[4] -> r4 = 0x04; words[5] -> r5 = 0x05; words[6] -> r6 = 0x06 /
stmib r1, {r4-r6} /
r4 -> array_buff[1] = 0x04; r5 -> array_buff[2] = 0x05; r6 -> array_buff[3] = 0x06 */
LDMIB指令先将源地址增加4字节(一个字),然后开始载入数据。该方法仍能让我们载入一串前向数据,但第一个数据的起始地址相较源地址有4个字节的偏移。这就是为什么我们用LDMIB指令将内存中的第一个元素载入R4后,R4是0x04(word[4]),而不是R0指向的0x3(word[3])
执行之后,寄存器R4-R6存储了0x4,0x5,0x6, 0x100D4, 0x100D8, 和0x100DC也存储了 0x4,0x5,0x6。
在这里插入图片描述
再看看对应的地址
在这里插入图片描述
符合我们的推测
接下来执行的是

ldmda r0, {r4-r6} /* words[3] -> r6 = 0x03; words[2] -> r5 = 0x02; words[1] -> r4 = 0x01 /
使用LDMDA向相反方向执行运算。R0指向word[3],载入操作开始后,我们向后将 words[3], words[2]和words[1]载入R6, R5, R4。注意,连寄存器都是反着被载入的。所以指令将 R6赋值为0x00000003, R5赋值为0x00000002, R4赋值为0x00000001。这里的逻辑是,每次载入操作完成后向后减少内存地址所以是反向赋值。
Nexti后验证
在这里插入图片描述
符合推测
下一条执行的是
ldmdb r0, {r4-r6} /
words[2] -> r6 = 0x02; words[1] -> r5 = 0x01; words[0] -> r4 = 0x00 */
同样的道理,执行后r6位0x2,r5为0x1,r4为0x0
在这里插入图片描述
接着执行的是

stmda r2, {r4-r6} /* r6 -> array_buff[2] = 0x02; r5 -> array_buff[1] = 0x01; r4 -> array_buff[0] = 0x00 */
同样的道理,存储值之后,降低地址,由r6往r4走,执行之后,array_buff[0](0x200d0)为0x0,array_buff[1](0x200d4)为0x1,array_buff[2](0x200d8)为0x2
nexti执行
在这里插入图片描述
这一条执行的是

stmdb r2, {r4-r5} /* r5 -> array_buff[1] = 0x01; r4 -> array_buff[0] = 0x00; */
一样的道理,执行之后,array_buff[1](0x100d4)为0x1,array_buff[0](0x100d0)为0x0
nexti执行后使用下列命令查看
在这里插入图片描述
符合推测

接下来看看push和pop
进程中有使用一块内存区域叫堆栈。堆栈指针(SP)是一个寄存器,在正常情况下,它总是指向栈内存区域中的一个地址。应用程序通常使用堆栈进行临时数据存储。之前提过,ARM使用加载/存储模型进行内存访问,这意味着指令LDR/STR或它们的派生指令(LDM…/STM…)用于内存操作。在x86中,我们使用PUSH和POP从堆栈中加载和存储。在ARM中,我们也可以使用这两个指令:
当我们把数据压入降序分布的栈区(详见第7部分:栈和函数)后会发生:
首先,SP寄存器里的地址值减4
第二步,将信息储存在SP指向的新的地址空间中
当把数据弹出栈时会发生:
首先,当前SP地址所指向的内存里的值内载入到相应寄存器中
然后SP里的值加4
demo代码如下,在test6.s中
.text
.global _start

_start:
mov r0, #3
mov r1, #4
push {r0, r1}
pop {r2, r3}
stmdb sp!, {r0, r1}
ldmia sp!, {r4, r5}
bkpt
在这里插入图片描述
编译,链接后可以查看其汇编代码
在这里插入图片描述
我们发现, LDMIA 和STMDB指令被转换成了 PUSH和POP。这是因为push有同义词stmdb SP!,而pop有同义词 LDMIA sp!
使用gdb调试,在_start下断点,使用nexti 2直接先执行前两条mov指令,然后看看情况
先看看sp
在这里插入图片描述
可以看到下一条push指令应该会把SP减8,并将R0和R1的值压入堆栈

我们使用nexti执行后看看
在这里插入图片描述
可以看到本来sp是0xbeff3a0,现在为0xbeff398,0xbeff398=0xbeff3a0-0x8
并且r0,r1被压栈了
下一条要执行的是pop
在这里插入图片描述
两个值 (0x3和0x4)会被弹出到相应寄存器里(pop {r2, r3}),所以 R2 = 0x3 ,R3 = 0x4。SP加8
执行nexti后查看
在这里插入图片描述
可以看到,此时sp+8,回到了0xbefff3a0,并且r2被赋为0x3,r3被赋为0x4,符合推测,后续的指令的分析也是同样的方法,不再演示。

参考:
azeria-labs 的arm教程
https://azeria-labs.com/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ARM汇编学习三 的相关文章

随机推荐

  • 14 - Spring5 学习笔记 - 整合日志框架、@Nullable 注解

    Spirng5 框架新功能 1 整个 Spring5 框架基于 Java8 运行时兼容 JDK9 许多不建议使用的类和方法在代码库中删除 2 Spring5 框架自带了通用的日志封装 1 Spring5 已经移除了 Log4jConfigL
  • c++ 不插入重复元素但也不排序_十大经典排序算法,看这篇文章就够了

    微信公众号 小超说 如果你觉得对你有帮助 欢迎分享 如果你想系统地学习 建议在电脑端阅读 我想大家学习算法之旅的开端就是各种排序算法吧 的确 排序算法广泛的应用性以及它的简洁基础等性质是初学者的不二之选 那今天我就带着你复习回顾以下各种经典
  • Docker镜像相关操作有哪些?

    什么是Docker Docker是一个开源的应用容器引擎 它让开发者可以打包他们的应用以及依赖包到一个可移植的容器中 然后发布到安装了任何 Linux 发行版本的机器上 Docker基于LXC来实现类似VM的功能 可以在更有限的硬件资源上提
  • 2020年第十一届蓝桥杯省赛javab组寻找2020

    简单的模拟 向右 向下 向右下 package 算法 import java util Scanner public class 寻找20 static int N 100000 4 5 static int M 1000 5 static
  • python实现车牌识别系统

    车牌识别系统 算法参考 http www zengqiang club blog 34 GUI参考 https blog csdn net wzh191920 article details 79589506 基于opencv的模板识别来实
  • 设计模式的应用场景(9)--装饰模式

    装饰模式 定义 装饰模式以对客户端透明的方式扩展对象的功能 是继承方案的一个替代方案 提供比继承更多的灵活性 优点 能够提供比使用继承关系更加灵活的拓展对象的功能 它可以动态增加对象的功能并且可以随意组合这些功能 缺点 使用装饰模式进行设计
  • Hadoop学习之Hadoop完全分布式集群安装

    注 本文的主要目的是为了记录自己的学习过程 也方便与大家做交流 转载请注明来自 http blog csdn net ab198604 article details 8250461 要想深入的学习hadoop数据分析技术 首要的任务是必须
  • 在 Python 中生成随机字符串

    介绍生成随机字符串的几种方法 1 使用random choice 实现 import string import random number of strings 5 length of string 8 for x in range nu
  • Latex的使用

    1 语法规则 是TeX中做为命令的标志 格式 命令名 可选参数 不可省略参数 2 常用的宏包 amsmath 数学符号与公式宏包 amsfonts 数学符号与字体宏包 Ctex 支持中文的排版 gaphicx 插图处理 Xcolor 颜色处
  • Vmwarexiade镜像下载地址

    https msdn itellyou cn 复制链接在迅雷上下载
  • Reactive判断的API,toRef & toRefs,ref其他的API,customRef

    Reactive判断的API isProxy 检查对象是否是由 reactive 或 readonly创建的 proxy isReactive 检查对象是否是由 reactive创建的响应式代理 如果该代理是 readonly 建的 但包裹
  • 基于Arduino Uno的智能小车(可遥控,避障,调速)模块:L298N HC-05 HC-SR04及sg90(180度)舵机

    文章目录 一 先让小车动起来 二 加入对应模块实现对应功能 1 HC SR04及SG90舵机 2 完整程序编写 总结 一 先让小车动起来 1 用到的函数 定义引脚的输入 输出函数 pinMode pin OUT INPUT 通过使用pinM
  • jupyter 设置主题Error:Could not find a version that satisfies the requirement jupyterthemes from version

    1 jupyter设置主题的步骤 命令窗口输入 pip install jupyterthemes 具体主题讲解可参考 https www cnblogs com shanger p 12006161 html 2 遇到的问题 Could
  • QT的Tree View Model示例

    一 介绍 使用MVC架构 Tree View与Tree Widget 相比而言 需要为tree view 设置一个model 使Tree View 能有效降低内存的使用率 下面参考Qt官方提供的demo Simple Tree Model
  • SSM框架controller类正常部分页面跳转404

    一 问题 在做项目的时候 将写好的页面整合到SSM框架过程中 写好controller类 将相关页面调整过后 启动Tomcat 进入系统进行测试页面跳转问题 发现一部分页面跳转成功 一部分页面跳转失败 且跳转失败的页面是同一个目录下的 二
  • blender怎么移动骨骼_Blender

    1 打开blender可以通过 shift a 调出创建菜单 2 通过 rgs 这三个按键 可以分别对模型进行旋转移动缩放 3 shift d 可以实现复制功能 4 使用 z 键可以切换到线框模式 再按一次切换回来 5 tab 按键可以切换
  • 完全小白的pycharm深度学习调试+for循环断点条件设置

    完全小白的pycharm深度学习调试 for循环断点条件设置 写在最前面 基础方法 pycharm断点调试 控制台输入 代码中循环的debug方法 pycharm中图标的介绍 常见的Bug Debug经验 1 检查激活函数的输入值 2 检查
  • Hive往表写入数据的八种方法

    1 使用insert select 语法 insert overwrite table dest table partition dt xxxxxx selectc1 c2from src tablewhere 复制代码 select中的字
  • EC11编码器和单片机通信

    EC11编码器 EC11编码器通常又被称作为旋转编码器 一般主要是用于亮度 温度 频率 音量调节等参数控制 三只脚中的C脚接地 AB脚接上拉电阻后 当左转或右转时 AB脚就有脉冲信号输出 S1和S2脚为按压开关 按下时导通 旋转编码器的引脚
  • ARM汇编学习三

    有时 一次性加载 或存储 多个值更有效率 因此 我们需要使用LDM 载入多个值 和STM 存储多个值 这些指令基于起始地址的不同 有不同的形式 下面是我们将在本节中将会使用的代码 我们将一步一步地完成每一个指令 代码在test5 s中 da