ARM汇编语言 - 简介 [一]

2023-05-16

origin: https://zhuanlan.zhihu.com/p/82490125
 

ARM汇编语言 - 简介 [一]

兰新宇

兰新宇

talk is cheap

说明:本系列文章将主要以ARMv7和ARMv8架构为例,介绍ARM汇编语言的一些基础知识。关于ARM汇编语言的学习,这里我要推荐一本书和一个网站,其中书是由宋岩翻译的《Cortex-M3权威指南》,其文笔风趣幽默,引人入胜,网站则是azeria-labs。当然,ARM官方的Architecture Reference Manual更是重要的参考。

说起与系统结构相关的汇编语言,自然要先介绍该体系结构的寄存器组成。ARMv7相较于同为32位的x86,寄存器的数量要多一些,名称和配置也不尽相同,但两者还是有一个基本的对照关系:

ARMv7-A在设计之初,就有和之前系列的处理器(比如以ARM9系列为代表的ARMv5)兼容的七种处理器模式,后来在向ARMv8过渡的过程中,又增加了"MON"和"HYP"。

为了减少模式切换时的寄存器保存和恢复,同名寄存器在多种模式下各有一份,称为bank register。某些模式会有自己专有的寄存器,比如FIQ就比IRQ多一些寄存器(R8到R12),这样FIQ在进入和退出中断的时候,所需要做的寄存器保存和恢复就可以减少,这也是它比IRQ更"Fast"的原因。

自从ARMv8出现以后,ARM的寄存器就全面进入了64位时代,通用寄存器的数量从13个(R0-R12)变成了30个(X0-X29) ,其名称中的"R"也被"X"所取代了,但为了保持和32位系统的兼容性,每个ARMv8/ARM64通用寄存器都可被当做2个32位寄存器来使用,这样的32位寄存器用"Wn"来表示。

ARMv8支持两种执行状态(execution state),分别是AArch64和AArch32,在AArch64状态下执行的是A64指令集,在AArch32状态下执行的是与ARMv7前向兼容的A32/T32指令集。

A64指令集看起来和前代的指令集差别不大,但其具有更高的编码效率,别看它叫A64就以为它的指令长度是64位的,依然是32位,也就是4个字节。通常一条指令不会占据太多字节,而为了方便流水线的操作,ARM中指令的字节数通常是保持一致的(最多就是T32/Thumb-2这种2字节和4字节混合的指令),都设计成8个字节话的确挺浪费代码空间。

ARMv8中的A32/T32指令集也不是和ARMv7中A32/T32一模一样的,它做了一些改进和增强,如果你使用了这些强化的特性,当然可以获得更好的性能,但是就不能和ARMv7完全兼容了。如果你希望同样的一套汇编代码在ARMv7和ARMv8中都能直接运行,就不能使用这部分额外的特性。

ARM虽说是RISC架构的,但RISC和CISC并不是泾渭分明的,双方都在互相学习,取长补短。现在ARM支持的指令也是越来越多,本系列文章将仅介绍其中的一部分指令。

【数据传送指令

基础的LDR/STR

在x86架构中,不管是寄存器之间,还是寄存器和内存之间,都可以使用MOV指令,并且直接操作内存单元上的数据是被允许的。

在ARM架构中,寄存器间传送数据的指令依然是MOV,比如"MOV Ra Rb" 就是把Rb里存放的数据传送给Ra,但内存单元上的数据不允许被直接操作,而是必须先放到寄存器中,为此就有了把内存的内容传送到寄存器的指令LDR(Load),以及把寄存器的内容传送回内存的指令STR(Store)。

 

传送的时候,内存单元的地址存放在一个寄存器中(比如R1),用[R1]表示,"[]"在这里就对应C语言里的"*",表示取地址里的内容。假设R1里存放的是0x200,内存中地址0x200处的内容是0x5,那么"ldr r0, [r1]"就是将0x5放入r0中。

通用寄存器的数量一共就那么多,直接用寄存器的值来获取内存地址的数量实在太有限了,更多的时候,是通过寄存器的值(基址)加上一个偏移/索引(offset/index)来指向内存对应的单元,索引的大小可以由立即数提供,也可以由寄存器存储的值提供:

STR  R0,[R1, #12]  // R0 --> [R1+12]
LDR  R4,[R5, R6]  // R4 <-- [R5+R6]

如果索引对基址的更改发生在数据传输之前,则称为"预索引"(pre-index),传输前后寄存器R1的值都不会改变。

如果索引对基址的更改发生在数据传输之后(注意下图"[]"位置的改变),则称为"后索引"(post-index),传输后寄存器R1的内容将变为加上其原来的值加上索引后的值。"后索引"其实算是一种二合一的指令,比如"str r0, [r1], #12"就等同于"str r0, [r1]"加上"r1 = r1+12"。

好像缺了点什么,没有既更改R1的值为R1+12,同时把*(R1+12)的值送入R0的?不急,这将在本文的后半段给出答案。

LDR和STR后面可以接一些后缀,比如"B", "H"和"W"分别表示从给定的内存地址取1个字节,2个字节和4个字节。

如果一次传送的不是8个字节,那么64位的寄存器是填不满的,为了保持负数的数值不变,这剩余的字节可能就需要进行符号位扩展(Signed),由后缀"S"表示,其配合"W"使用表示只进行低32位空余字节的扩展,配合"X"则表示进行整个64位的符号位扩展:

LDM/STM与三个问题

一个字节一个字节的传送那是“蚂蚁搬家”,如果要复制大批量的数据,效率实在不高,为此ARMv7还提供了用于批量传输的LDMSTM指令,"M"在这里代表Multiple。STM是把多个寄存器的值传送到内存相邻的位置,LDM反之。多个寄存器在ARM汇编语言中用"{}"圈起来,表示待传送的寄存器列表。

比如"STM R0, {R4,R5}" 就表示将R4的值传送到R0指向的内存单元,R5的值传送到下一个内存单元。批量传输其实是存在一个方向问题的,为了区分下一个内存单元是在上一个单元的后面还是前面(地址更大还是更小),需要加上后缀"I"和"D"来分别表示"Increase"和"Decrease"。

还有一个问题,要将R5的值传送到下一个内存单元,需要首先获得这“下一个”内存单元的地址,这就涉及到地址的增减。假设R0的值是0,如果先增加"0"这个值(在32位系统中,一次增加的值是4),再传送R4,那么就是[0x4]=R4, [0x8]=R5;如果是传送完R4后再增加"0"这个值,那么就是[0x0]=R4, [0x4]=R5。所以还需要加上后缀"A"和"B"来分别表示"After"(传送后增加)和"Before"(传送前增加)。

因此,LDM/STM家族一共有"IA", "IB", "DA"和"DB"四个变种(variant),"LDM"和"STM"什么后缀都不接也可以直接使用,但它其实包含一个隐式规则,即默认为"IA",也就是说"LDM"和"STM"其实分别等同于"LDMIA"和"STMIA"。

在函数调用中,进入子函数的时候要用"PUSH"指令,把存储在CPU寄存器中的局部变量/上下文保存到内存的栈中,退出子函数的时候要用"POP"指令,将栈中保存的内容恢复到对应的寄存器中,因为栈通常是自顶向下生长的,所以"PUSH"和"POP"其实可以分别用"STMDB"和"LDMIA"来替代。

STMDB  SP!, {R0-R3,  LR}  <-->  PUSH  {R0-R3, LR}     
LDMIA  SP!, {R0-R3,  PC}  <-->  POP  {R0-R3, PC}

这里出现了一个"!"符号,那就是我们要解决的第三个问题:在增加/减少"SP"表示的这个数值(比如前面假设的"0")的时候,"SP"本身存储的内容是否跟着一起变化?加上"!"就表示在传送过程中"SP"会自增/自减,传送完成后"SP"的值已经不再是传送前的那个值了,不加"!"就是在传送前后保持"SP"的内容不变。"SP"作为stack pointer,在入栈和出栈的时候自然是要移动的,所以这里用了"!"。

"!"是表示寄存器自增/自减的,所以它并不局限于配合LDM/STM使用,如果它用在STR指令中,比如"str r0, [r1, #12]!",就相当于"str r0 [r1, #12]"加上"r1 = r1+12",这也解决了本文前半段介绍LDR/STR指令时留下的那个问题。

新一代的LDP/STP

在ARMv8中,LDM/STM被新一代的指令LDP(Load Pair)和STP(Store Pair)所取代了,LDM/STM对寄存器列表里包含的寄存器数量并没有什么限制,而LDP/STP要求和内存之间传送数据的寄存器不超过2个。因为"PUSH"和"POP"完全可以用LDM/STM表示,所以他俩也被一并干掉了。两代指令的对应关系大概是这样的:

 

小结一下,本文主要介绍了ARMv7和ARMv8的数据传送指令,并在其中穿插了ARM汇编语言中"[]", "{}", "!"符号的含义和用法。下文将介绍移位、序转和位操作等数据处理指令。

 

原创文章,转载请注明出处。

 

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

ARM汇编语言 - 简介 [一] 的相关文章

  • Android 上原生的自修改代码

    我正在尝试在 Android 上制作一些自修改本机代码并在模拟器中运行它 我的示例基于 android ndk 中的 Hello JNI 示例 它看起来像这样 define NOPE LENGTH 4 typedef void FUNC v
  • 线程安全的向量和字符串容器?

    我之前发过一个问题 在嵌入式 Linux 平台上使用 std string 时出现段错误 https stackoverflow com questions 2412667 seg fault when using stdstring on
  • 使用 GCC 编译器为代码的特定部分保留寄存器

    是否可以为 C 代码的特定部分保留寄存器 ffixed reg 选项或声明全局寄存器变量不是我正在寻找的答案 我想保留特定范围 比如说特定函数 的寄存器值 使用局部寄存器变量是不可能的 因为它不能保证在整个范围内保留寄存器的值 我正在寻找类
  • ARM 9处理器的opencv交叉编译

    我需要为 ARM 9 处理器交叉编译 opencv 我有处理器的工具链 但不知道如何交叉编译 请告诉我为arm板交叉编译的过程 谢谢大家 看这个参考 http www airs com ian configure configure 5 h
  • ARM 的启动过程是怎样的?

    我们知道 对于X86架构 按下电源按钮后 机器开始执行0xFFFFFFF0处的代码 然后开始执行BIOS中的代码以进行硬件初始化 BIOS 执行后 它使用引导加载程序将操作系统映像加载到内存中 最后 操作系统代码开始运行 对于ARM架构 使
  • 如何使用 gcc 编译代码和 ARM Cortex A8 目标进行调用图分析?

    我对这个已经咬牙切齿了 我需要在 ARM 板上进行分析并需要查看调用图 我尝试使用 OProfile Kernel perf 和 Google 性能工具 一切正常 但不输出任何调用图信息 这使我得出结论 我没有正确编译代码 我在编译 C 代
  • DSP 库 - RFFT - 奇怪的结果

    最近我一直在尝试在我的STM32F4 Discovery评估板上进行FFT计算 然后将其发送到PC 我已经调查了我的问题 我认为我对制造商提供的 FFT 函数做错了 我正在使用 CMSIS DSP 库 现在我一直在用代码生成样本 如果工作正
  • 将 GCC 内联汇编与采用立即值的指令结合使用

    问题 我正在为 ARM Cortex M3 处理器开发定制操作系统 为了与我的内核交互 用户线程必须生成 SuperVisor Call SVC 指令 以前称为 SWI 用于软件中断 该指令在ARM ARM中的定义是 这意味着该指令需要即时
  • GCC ARM 汇编预处理器宏

    我正在尝试使用汇编 ARM 宏进行定点乘法 define MULT a b asm volatile SMULL r2 r3 0 1 n t ADD r2 r2 0x8000 n t ADC r3 r3 0 n t MOV 0 r2 ASR
  • 读取和打印手臂组件中的字符串

    我正在使用 ARMSim 刚刚开始学习汇编 所以如果我看起来一无所知 请原谅我 但我正在尝试从输入文件中读取字符串 然后将其打印到输出屏幕 到目前为止我有 equ SWI Open 0x66 open a file equ SWI Clos
  • 有没有办法在 Xcode 4 中为 ARM 而不是 Thumb 进行编译?

    如果有很多浮点运算正在进行 Apple 建议针对 ARM 进行编译 而不是针对拇指进行编译 我的整个应用程序几乎是一个大型浮点运算 iOS 应用程序开发工作流程指南中是这样说的 iOS 设备支持两种指令集 ARM 和 Thumb Xcode
  • 使用 Android NDK 使用 -fsigned-char 进行构建安全吗?

    为了与其他平台保持一致 我需要使用signed char在我正在处理的一些本机代码中 但默认情况下在Android NDK上char类型是unsigned 我尝试明确使用signed char类型 但它生成太多警告differ in sig
  • 了解 U-Boot 内存占用

    我不明白加载 U Boot 时 RAM 中发生了什么 我正在开发 Xilinx Zynq ZC702 评估套件 并尝试使用 U Boot 在其上加载 Linux 内核 于是我使用Xilinx工具Vivado和SDK生成了一个BOOT bin
  • saber sd 如何在没有 SPL 的情况下直接从 uboot 启动

    sabre sd 基于 imx 6 最大内部 RAM 约为 150Kb 然而 uboot 足够大 可以容纳在这个空间中 在这个场景中事情是如何进行的 https community freescale com docs DOC 95015
  • Qemu flash 启动不起作用

    我有一本相当旧的 2009 年出版 嵌入式 ARM Linux 书 其中使用u boot and qemu 的用法qemu与u boot书中对二进制的解释如下 qemu system arm M connex pflash u boot b
  • 如何模拟ARM处理器运行环境并加载Linux内核模块?

    我尝试加载我的vmlinux into gdb并使用 ARM 内核模拟器 但我不明白为什么我会得到Undefined target command sim 这是外壳输出 arm eabi gdb vmlinux GNU gdb GDB 7
  • 交叉编译armv5,但它创建v7二进制文件

    我设法为arm926ej s创建了一个目标文件我在 qemu 上使用 Debian Arm arm linux gnueabi gcc 4 4 static O c mcpu arm926ej s hello c o hello root
  • 英特尔的最后分支记录功能是英特尔处理器独有的吗?

    最后分支记录是指存储与最近执行的分支相关的源地址和目标地址的寄存器对 MSR 的集合 它们受英特尔酷睿 2 英特尔至强和英特尔凌动处理器系列的支持 http css csail mit edu 6 858 2012 readings ia3
  • ARM Cortex-M3 启动代码

    我试图了解 STM32 微控制器的 Keil realview v4 附带的初始化代码是如何工作的 具体来说 我试图了解堆栈是如何初始化的 In the 文档 http infocenter arm com help index jsp t
  • 让 TensorFlow 在 ARM Mac 上使用 GPU

    我已经安装了TensorFlow在 M1 上 ARM Mac 根据这些说明 https github com apple tensorflow macos issues 153 一切正常 然而 模型训练正在进行CPU 如何将培训切换到GPU

随机推荐

  • C++设计模式22:模板方法模式

    C 23种设计模式系列文章目录 创建型模式 第1式 工厂方法模式 第2式 抽象工厂模式 第3式 单例模式 第4式 建造者模式 第5式 原型模式 结构型模式 第6式 适配器模式 第7式 桥接模式 第8式 组合模式 第9式 装饰器模式
  • C++ 设计模式23:访问者模式

    C 23种设计模式系列文章目录 创建型模式 第1式 工厂方法模式 第2式 抽象工厂模式 第3式 单例模式 第4式 建造者模式 第5式 原型模式 结构型模式 第6式 适配器模式 第7式 桥接模式 第8式 组合模式 第9式 装饰器模式
  • C++设计模式17:中介者模式

    C 23种设计模式系列文章目录 创建型模式 第1式 工厂方法模式 第2式 抽象工厂模式 第3式 单例模式 第4式 建造者模式 第5式 原型模式 结构型模式 第6式 适配器模式 第7式 桥接模式 第8式 组合模式 第9式 装饰器模式
  • C++设计模式14:命令模式

    C 23种设计模式系列文章目录 创建型模式 第1式 工厂方法模式 第2式 抽象工厂模式 第3式 单例模式 第4式 建造者模式 第5式 原型模式 结构型模式 第6式 适配器模式 第7式 桥接模式 第8式 组合模式 第9式 装饰器模式
  • GPS 和 RTK 定位

    refers xff1a https blog csdn net u012241570 article details 80802675 GPS定位的基本原理 测量出已知位置的卫星到地面GPS接收器之间的距离 xff0c 然后接收器通过与至
  • 关于GD32的CMakeLists以及gcc部分编译选项的解释

    set CMAKE SYSTEM NAME Generic cmake最低版本 cmake minimum required VERSION 3 0 0 工程名称 语言 project TEST PRJ NAME LANGUAGES C C
  • ulimit -s 指定栈上的内存上限

    转自 xff1a http blog chinaunix net uid 24439730 id 144094 html ulimit s 指定栈上的内存上限 xff0c 单位为KB xff0c 如 xff1a root 64 wdqf1w
  • C++头文件重复包含问题

    为了避免同一个文件被include多次 有两种方式 1 span class token macro property span class token directive keyword ifdef span SOMEFILE H spa
  • TCP(select函数模型)

    客户端代码 include lt stdio h gt include lt sys types h gt include lt sys socket h gt include lt arpa inet h gt include lt st
  • i2c那些坑

    origin http bbs ntpcb com simple t126695 html I2C 的那些坑 一般情况下 xff0c i2c 设备焊接没什么问题 xff0c 按照设备手册一步步来 xff0c 基本上就顺风顺水能够用起来 如果
  • stm32f103系列引脚定义-功能图

    器件功能和配置 STM32F103xx增强型 STM32F103xx增强型模块框架图 STM32F103xx增强型VFQFPN36管脚图 STM32F103xx增强型LQFP100管脚图 STM32F103xx增强型LQFP64管脚图 ST
  • 用数百行代码实现60亿设备互联:微软重金收购的ThreadX硬在何处

    origin https www sohu com a 315222502 485057 2019年4月 xff0c 微软收购了ThreadX的母公司Express Logic 公司 而ThreadX有几亿个设备在运行 1 物联网操作系统简
  • Cache和DMA一致性

    cache读必须要buffer是cacheline对齐的 DMA应该多多少少知道点吧 DMA Direct Memory Access 是指在外接可以不用CPU干预 xff0c 直接把数据传输到内存的技术 这个过程中可以把CPU解放出来 x
  • stm32几种低功耗模式的实现和差别

    origin https blog csdn net jian3214 article details 99818603 01 前言 按功耗由高到低排列 xff0c STM32具有运行 睡眠 停止和待机四种工作模式 上电复位后 STM32
  • threadx也开源了

    前一段时间ucos开源了 xff0c 今天微软收购的threadx也开源了 xff0c 行业剧变呀 xff01 xff01 xff01 2020 5 26
  • armv8-M 32bit处理器

    https www eet china com mp a14579 html https developer arm com ip products processors cortex m 最早的Cortex M0属于Armv6 M架构 x
  • 作为ARM Cortex-M家族的继承者 Cortex-M23与M33有哪五大特色?

    http news eeworld com cn xfdz article 2017011259937 html 集微网消息 xff0c ARM处理器在嵌入式设备领域的应用非常广泛 基于ARM Cortex处理器的片上系统 xff08 So
  • emmc5.1, ufs2.0, ufs3.0

    总的来说 xff0c UFS3 0的综合性能 xff0c 特别是持续读写速度有着秒杀UFS2 1前辈的表现 xff0c 只是在随机读写和SQLite性能上 xff0c 却依旧和双通道的UFS2 1持平 xff0c 有些小遗憾 最后 xff0
  • 串行Norflash是如何实现XIP的?

    先说问题 xff1a 要想程序在串行的Norflash中运行 xff0c 到底需要做哪些工作 xff1f Norflash和Nandflash想必大家都知道 恕本人才学疏浅 xff0c 最近突然发现Norflash可以并行连接实现XIP x
  • ARM汇编语言 - 简介 [一]

    origin https zhuanlan zhihu com p 82490125 ARM汇编语言 简介 一 兰新宇 talk is cheap 说明 xff1a 本系列文章将主要以ARMv7和ARMv8架构为例 xff0c 介绍ARM汇