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

前言:

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

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

开发板更多资料可关注华清远见在线实验室(微信号:hqyjlab)领取``

ARM 汇编语言程序设计

GNU ARM 汇编器支持的伪操作

伪操作概述

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

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

数据定义(Data Definition)伪操作

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

 

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

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 汇编中还有一些其他的伪操作,在汇编程序中经常会被使用,包括以下几条。

 

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 寄存器在函数调用过程中的保护规则,如图所示。

 

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。

 

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

 

思考:编程测试如果是 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 寄存器的变化。

 

 

三个数据的和保存在 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 结果。

 

 

猜你喜欢

转载自blog.csdn.net/u014170843/article/details/130687743