基于stm32mp157 linux开发板ARM裸机开发教程6:ARM 汇编语言程序设计(连载中)
  wmoOZ5zdfjPn 2023年11月02日 35 0

前言:

目前针对ARM Cortex-A7裸机开发文档及视频进行了二次升级持续更新中,使其内容更加丰富,讲解更加细致,全文所使用的开发平台均为华清远见FS-MP1A开发板(STM32MP157开发板)

针对对FS-MP1A开发板,除了Cortex-A7裸机开发篇外,还包括其他多系列教程,包括Cortex-M4开发篇、FreeRTOS篇、Linux基础及应用开发篇、Linux系统移植篇、Linux驱动开发篇、硬件设计篇、人工智能机器视觉篇、Qt应用编程篇、Qt综合项目实战篇等。除此之外计划针对Linux系统移植篇、Linux驱动开发篇均会进行文档及视频的二次升级更新敬请关注!

开发板更多资料可关注华清远见在线实验室领取``

ARM 汇编语言程序设计

GNU ARM 汇编器支持的伪操作

伪操作概述

在 ARM 汇编语言程序中,有一些特殊指令助记符,这些助记符与指令系统的助记符不同,没有相对应的操作码,通常称这些特殊指令助记符为伪操作标识符(directive),它们所完成的操作称为伪操作。伪操作在源程序中的作用是为了完成汇编程序做各种准备工作的,这些伪操作仅在汇编过程中起作用,一旦汇编结束,伪操作的使命就完成。

在 ARM 的汇编程序中,伪操作主要有符号定义伪操作、数据定义伪操作、汇编控制伪操作及其杂项伪操作等。

数据定义(Data Definition)伪操作

数据定义伪操作一般用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。常见的数据定义伪操作有.byte、.short、.long、.quad、.float、.string、.asciz、.ascii 和.rept。数据定义伪操作如下。

基于stm32mp157 linux开发板ARM裸机开发教程6:ARM 汇编语言程序设计(连载中)_伪指令

汇编控制伪操作用于控制汇编程序的执行流程,常用的汇编控制伪操作包括以下几条。

1、 .if、.else、.endif

语法格式

.if、.else、.endif 伪操作能根据条件的成立与否决定是否执行某个指令序列。当.if 后面的逻辑表达式为真,则执行.if 后的指令序列,否则执行.else 后的指令序列。其中,.else 及其后指令序列可以没有,此时,当.if 后面的逻辑表达式为真,则执行指令序列,否则继续执行后面的指令。

提示:

.if、.else、.endif 伪指令可以嵌套使用。

语法格式如下:

示例代码 46-1 使用示例

1 .if logical-expressing

2 …

3 .else

4 …

5 .endif logical-expression:

用于决定指令执行流程的逻辑表达式。

当程序中有一段指令需要在满足一定条件时执行,使用该指令。该操作还有另一种形式。

示例代码 46-2 使用示例

1 .if logical-expression

2 Instruction

3 .elseif logical-expression2

4 Instructions

5 .endif

该形式避免了 if-else 形式的嵌套,使程序结构更加清晰、易读。

2、 .macro、.endm

语法格式

.macro 伪操作可以将一段代码定义为一个整体,称为宏指令,然后就可以在程序中通过宏指令多次调用该段代码。其中,$标号在宏指令被展开时,标号会被替换为用户定义的符号。

宏操作可以使用一个或多个参数,当宏操作被展开时,这些参数被相应的值替换。

宏操作的使用方式和功能与子程序有些相似,子程序可以提供模块化的程序设计、节省存储空间并提高运行速度。但在使用子程序结构时需要保护现场,从而增加了系统的开销,因此,在代码较短且需要传递的参数较多时,可以使用宏操作代替子程序。

包含在.macro 和.endm 之间的指令序列称为宏定义体,在宏定义体的第一行应声明宏的原型(包含宏名、所需的参数),然后就可以在汇编程序中通过宏名来调用该指令序列。在源程序被编译时,汇编器将宏调用展开,用宏定义中的指令序列代替程序中的宏调用,并将实际参数的值传递给宏定义中的形式参数。

提示:

.macro、.endm 伪操作可以嵌套使用。

语法格式如下:

示例代码 46-3 使用示例

1 .macro

2 {$label} macroname {$parameter{,$parameter} … }

3 ;code

4 .endm

参数说明

{$label}:$标号在宏指令被展开时,标号会被替换为用户定义的符号。通常,在一个符号前使用

“$”表示该符号被汇编器编译时,使用相应的值代替该符号。

{macroname}:所定义的宏的名称。

{parameter}:宏指令的参数。当宏指令被展开时将被替换成相应的值,类似于函数中的参数。

示例如下:

示例代码 46-4 使用举例

1 .macro SHIFTLEFT a, b

2 .if \b < 0

3 MOV \a, \a, ASR #-\b

4 .exitm

5 .endif

6 MOV \a, \a, LSL #\b

7 .endm

.exitm 用于从宏定义中跳转出去,只需要在宏定义的代码中插入该指令即可。

在子程序代码比较短,而需要传递的参数比较多的情况下可以使用宏汇编技术。

首先通过.macro 和.endm 伪操作定义宏,包括宏定义体代码。在.macro 伪操作之后的第一行声明宏的原型,其中包含该宏定义的名称及需要的参数。在汇编中可以通过该宏定义的名称来调用它。当源程序被编译时,汇编器将展开每个宏调用,用宏定义体代替源程序中宏定义的名称,并用实际参数值代替宏定义时的形式参数。

3、 .杂项伪操作

ARM 汇编中还有一些其他的伪操作,在汇编程序中经常会被使用,包括以下几条。

基于stm32mp157 linux开发板ARM裸机开发教程6:ARM 汇编语言程序设计(连载中)_寄存器_02

ARM 汇编器支持的伪指令

ARM 汇编器支持 ARM 伪指令,这些伪指令在汇编阶段被翻译成 ARM 或者 Thumb(或 Thumb-2)指令(或指令序列)。ARM 伪指令包含 ADR、ADRL、LDR 等。

ADR 伪指令

语法格式

ADR 伪指令为小范围地址读取伪指令。ADR 伪指令将基于 PC 相对偏移地址或基于寄存器相对偏移地址值读取到寄存器中,当地址值是字节对齐时,取值范围为−255~255,当地址值是字对齐时,取值范围为−1020~1020。当地址值是 16 字节对齐时其取值范围更大。

语法格式如下:

ADR{c}{.W} register,label

{c}:可选的指令执行条件。

{.W}:可选项。指定指令宽度(Thumb-2 指令集支持)。

{register}:目标寄存器。

{label}:基于 PC 或具有寄存器的表达式。

使用说明

ADR 伪指令被汇编器编译成一条指令。汇编器通常使用 ADD 指令或 SUB 指令来实现伪操作的地址装载功能。如果不能用一条指令来实现 ADR 伪指令的功能,汇编器将报告错误。

示例

示例代码 46-5 使用举例

1 adr r1, init_stack

2 ;相当于下面的arm指令:

3 sub/add r1, pc, offset_to_init_stack

4 ...

5 init_stack: ...

LDR 伪指令

语法格式

LDR 伪指令装载一个 32 位的常数和一个地址到寄存器。

语法格式如下:

LDR{cond}{.W} register,=[expr|label-expr]

{c}:可选的指令执行条件。

{.W}指定指令宽度(Thumb-2 指令集支持)。

{register}:目标寄存器。

{expr}:32 位常量表达式。汇编器根据 expr 的取值情况,对 LDR 伪指令做如下处理。

① 当 expr 表示的地址值没有超过 MOV 指令或 MVN 指令的地址取值范围时,汇编器用一对 MOV 和MVN 指令代替 LDR 指令。

② 当 expr 表示的指令地址值超过了 MOV 指令或 MVN 指令的地址范围时,汇编器将常数放入数据缓存池,同时用一条基于 PC 的 LDR 指令读取该常数。

{label-expr}

一个程序相关或声明为外部的表达式。汇编器将 label-expr 表达式的值放入数据缓存池,使用一条程序相关 LDR 指令将该值取出放入寄存器。

当 label-expr 被声明为外部的表达式时,汇编器将在目标文件中插入链接重定位伪操作,由链接器在链接时生成该地址。

使用说明

当要装载的常量超出了 MOV 指令或 MVN 指令的范围时,使用 LDR 指令。

由 LDR 指令装载的地址是绝对地址,即 PC 相关地址。

当要装载的数据不能由 MOV 指令或 MVN 指令直接装载时,该值要先放入数据缓存池,此时 LDR 伪指令处的 PC 值到数据缓存池中目标数据所在地址的偏移量有一定限制。ARM 或 32 位的 Thumb-2 指令中该范围是−4~4KB,Thumb 或 16 位的 Thumb-2 指令中该范围是 0~1KB。将常数 0xff0 读到 r1 中

示例代码 46-6 使用举例

1 ldr r3,=0xff0 ;将常数 0xff0 读到 r1 中

2 ;相当于下面的 ARM 指令:

3 mov r3,#0xff0

将常数 0xfff 读到 R1 中

示例代码 46-7 使用举例

1 ldr r1,=0xfff ;将常数0xfff读到r1中

2 ; 相当于下面的arm指令:

3 ldr r1,[pc,offset_to_litpool]

4 …

5 litpool .word 0xfff

 将 place 标号地址读入 R1 中

 

示例代码 46-8 使用举例

1 ldr r2,=place

2 ;相当于下面的arm指令:

3 ldr r2,[pc,offset_to_litpool]

4 …

5 litpool .word place

ARM 汇编语言的程序结构

汇编语言的程序格式

在 ARM(Thumb)汇编语言程序中可以使用.section 来进行分段,其中每一个段用段名或者文件结尾为结束,这些段使用默认的标志,如 a 为允许段,w 为可写段,x 为执行段。

在一个段中,我们可以定义.text、.data、.bss 子段。由此我们可知道,段可以分为代码段、数据段及其他存储用的段,.text(正文段)包含程序的指令代码;.data(数据段)包含固定的数据,如常量、字符串;.bss(未初始化数据段)包含未初始化的变量、数组等,当程序较长时,可以分割为多个代码段和数据段,多个段在程序编译链接时最终形成一个可执行的映像文件。

示例代码 46-9 使用举例

1 .section .data

2 <initialized data here>

3

4 .section .bss

5 <uninitialized data here>

6

7 .section .text

8 .globl _start

9 _start:

10 <instruction code goes here>

过程调用标准 AAPCS

为了使不同编译器编译的程序之间能够相互调用,必须为子程序间的调用规定一定的规则。AAPCS 就是这样一个标准。所谓 AAPCS,其英文全称为 Procedure Call Standard for the ARM Architecture(AAPCS),即 ARM 体系结构过程调用标准。它是 ABI(Application Binary Interface(ABI)for the ARM Architecture (base standard) [BSABI])标准的一部分。

可以使用“--apcs”选项告诉编译器将源代码编译成符号 AAPCS 调用标准的目标代码。

注意:

使用“--apcs”选项并不影响代码的产生,编译器只是在各段中放置相应的属性,标识用户选定的 AAPCS

属性。

1、 AAPCS 相关的编译/汇编选项

none:指定输入文件不使用 AAPCS 规则。

/interwork:指定输入文件符合 ARM/Thumb 交互标准。

/nointerwork:指定输入文件不能使用 ARM/Thumb 交互。这是编译器默认选项。

/ropi:指定输入文件是位置无关只读文件。

/noropi:指定输入文件是非位置无关只读文件。这是编译器默认选项。

/pic:同/ropi。

/nopic:同/noropi。

/rwpi:指定输入文件是位置无关可读可写文件。

/norwpi:指定输入文件是非位置无关可读可写文件。

/pid:同/rwpi。

/nopid:同/norwpi。

/fpic:指定输入文件编译成位置无关只读代码。代码中地址是 FPIC 地址。

/swstackcheck:编译过程中对输入文件使用堆栈检测。

/noswstackcheck:编译过程中对输入文件不使用堆栈检测。这是编译器默认选项。

/swstna:如果汇编程序对于是否进行数据栈检查无所谓,而与该汇编程序连接的其他程序指定了选项/swst 或选项/noswst,这时该汇编程序使用选项/swstna。

2、 ARM 寄存器使用规则

AAPCS 中定义了 ARM 寄存器使用规则如下:

子程序间通过寄存器 R0、R1、R2、 R3 来传递参数。如果参数多于 4 个,则多出的部分用堆栈传递。被调用的子程序在返回前无须恢复寄存器 R0-R3 的内容。

在子程序中,使用寄存器 R4-R11 来保存局部变量。如果在子程序中使用到了寄存器 R4-R11 中的某些寄存器,子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值;对于子程序中没有用到的寄存器则不必进行这些操作。在 Thumb 程序中,通常只能使用寄存器 R4-R7 来保存局部变量。

寄存器 R12 用做子程序间 scratch 寄存器(用于保存 SP,在函数返回时使用该寄存器出栈),记作 ip。在子程序间的连接代码段中常有这种使用规则。

寄存器 R13 用做数据栈指针,记作 sp。在子程序中寄存器 R13 不能用做其他用途。寄存器 sp 在进入子程序时的值和退出子程序时的值必须相等。

寄存器 R14 称为连接寄存器,记作 lr。它用于保存子程序的返回地址。如果在子程序中保存了返回地址,寄存器 R14 则可以用做其他用途。

寄存器 R15 是程序计数器,记作 pc。它不能用做其他用途。

ARM 寄存器在函数调用过程中的保护规则,如图所示。

基于stm32mp157 linux开发板ARM裸机开发教程6:ARM 汇编语言程序设计(连载中)_伪指令_03

3、 AAPCS 使用举例

编写一个简单的 c 程序,通过反汇编+单步调试,验证学习 AAPCS 规则。可以打开工程 c_AAPCS 工程,其中 main.c 内容如下:

示例代码 46-10 AAPCS 使用举例

1 int add(int a, int b, int c, int d)

2 {

3 int e;

4 e = a+b+c+d;

5 return e;

6 }

7

8 int main()

9 {

10 int a,b,c,d,e;

11 a = 1;

12 b = 2;

13 c = 3;

14 d = 4;

15 e = add(a,b,c,d);

16

17 return 0;

18 }

通过返回汇编窗口,可以看到,在 main 函数中,在调用 add 子函数前,将 R0=1,R1=2,R2=3,R3=4,并将 R0、R1、R2、R3 作为参数传给了子函数 add。

基于stm32mp157 linux开发板ARM裸机开发教程6:ARM 汇编语言程序设计(连载中)_寄存器_04

根据 AAPCS 规则,add 子函数,直接使用 R0,R1,R2,R3,并且不需要保护这几个寄存器。但因为使用到了 R11,根据规则,需要保护,所以对 R11 进行了入栈保护。最终的返回值通过 R0 传输。

基于stm32mp157 linux开发板ARM裸机开发教程6:ARM 汇编语言程序设计(连载中)_寄存器_05

思考:编程测试如果是 5 个参数的情况。

ARM 伪指令实验

实验目的

掌握 ARM 汇编语言的基本使用和一些伪指令的使用;

熟悉 eclipse 开发工具建立汇编工程和仿真;

 实验原理

根据上面阐述 RAM 汇编语言的使用语法和功能,编写汇编程序,实现将存放在两个内存中的数据相加的操作。

实验内容

汇编程序设计如下:

示例代码 46-11 伪指令案例

1 .text

2 .global _start

3 _start:

4

5 .code 32

6

7

8 mov r1, #0

9

10 ldr r2,=myarray

11 loop:

12

13 ldr r3,[r2],#4

14 add r1,r1,r3

15 cmp r3,#0

16 bne loop

17

18 stop:

19 b stop

20

21 myarray:

22 .word 6

23 .word 24

24 .word 12

25 .word 0

26

27 .end

实验步骤

导入工程源码

请参考导入一个已有工程章的导入已有工程章节。

光盘实验源码路径:【资料光盘\华清远见-FS-MP1A 开发资料-2020-11-06\02-程序源码\03-ARM 体系结构与接口技术\Cortex-A7\h_test】


实验现象

单击“ 如下图标”单步,查看 Rn 寄存器的变化。

基于stm32mp157 linux开发板ARM裸机开发教程6:ARM 汇编语言程序设计(连载中)_子程序_06


基于stm32mp157 linux开发板ARM裸机开发教程6:ARM 汇编语言程序设计(连载中)_伪指令_07

三个数据的和保存在 R1 中,最终 R1 的数值为 42。

ARM 内联汇编实验

实验目的

掌握 ARM 汇编语言的基本使用和内联汇编用法;

熟悉 eclipse 开发工具建立汇编工程和仿真;

实验原理

GCC 内联汇编的一般格式:

asm(

代码列表

:输出运算符列表

:输入运算符列表

:被更改资源列表

);

在代码列表中,每个汇编语句都要用" "括起来。

 

示例代码 46-12 内联汇编举例

1 asm(

2 "add %0,%1,%2\n\t"

3 "mov r1,%1\n\t"

4 :"+r"(sum)

5 :"r"(a),"r"(b)

6 :"r0"

7 );

 案例详解

在 C 代码中嵌入汇编需要使用 asm 关键字,用法 asm();

 " " 引号内部包含的部分是指令部分

 : 参数输出部分,函数的返回值

 : 参数输入部分,函数的形参

 : 修饰列表,内联汇编的声明部分,要被更改的资源

 "r" 用寄存器来保存参数

 "i" 是立即数

 "m" 一个有效的内存地址

 "x" 只能做输入

 + 表示参数的可读可写

 不写 表示参数只读

 = 表示只写

 & 只能做输出

 %0 输出列表和输入列表的第 1 个成员

 %1 输出列表和输入列表的第 2 个成员

 %2 输出列表和输入列表的第 3 个成员

根据上面阐述ARM 汇编语言的使用语法和功能,在 C 语言中编写内联汇编代码,实现得到两个参数的最小公倍数。

实验内容

实验程序设计如下:

示例代码 46-13 字节交换

1 unsigned long ByteSwap(unsigned long val)

2 {

3 int ch;

4

5 asm volatile (

6 "eor r3, %1, %1, ror #16\n\t"

7 "bic r3, r3, #0x00ff0000\n\t"

8 "mov %0, %1, ror #8\n\t"

9 "eor %0, %0, r3, lsr #8"

10 : "=r" (ch)

11 : "0"(val)

12 : "r3"

13 );

14 }

15

16 int main(void)

17 {

18 unsigned long test_a = 0x1234,result;

19

20 result = ByteSwap(test_a);

21

22 printf("Result:%d\r\n", result);

23

24 return 0;

25 }

实验步骤

导入工程源码

相关内容请参考导入一个已有工程章节导入已有工程。

光盘实验源码路径:【资料光盘\华清远见-FS-MP1A 开发资料-2020-11-06\02-程序源码\03-ARM 体系结构与接口技术\Cortex-A7\h_inline】

实验结果

点击“ 如下图标”单步,查看 result 结果。

基于stm32mp157 linux开发板ARM裸机开发教程6:ARM 汇编语言程序设计(连载中)_寄存器_08


基于stm32mp157 linux开发板ARM裸机开发教程6:ARM 汇编语言程序设计(连载中)_寄存器_09


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  5SL1O36RFEWc   2023年11月02日   57   0   0 v8寄存器闪存
wmoOZ5zdfjPn