GNU ARM 汇编基础笔记
Posted Naisu Xu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GNU ARM 汇编基础笔记相关的知识,希望对你有一定的参考价值。
文章目录
目的
汇编的核心就是使用各种指令来编码完成需求,而指令这个东西其实就是最底层二进制的机器码的基础上做了一层语义化的替换,比如用 ADD 代表数字逻辑上的加减,用 MOV 代表数据传递等等,方便程序员进行阅读和编写。不过对于现在的各种高级语言而言汇编已经不怎么方便了。现在还在用汇编的主要是底层开发中一些特定需求的实现必须用到汇编。对于嵌入式开发而言了解汇编还是有一定需求的。
汇编主要是用各种指令来完成功能的编写,不同的处理器而言其架构和使用的指令集不同,不同的编译器下具体的语法差异也蛮大,但基础的语法思路和常用的指令基本上大家差异都不大。这篇文章将以 GNU ARM 汇编 为基础进行介绍。
模拟器 VisUAL
VisUAL has been developed as a cross-platform tool to make learning ARM Assembly language easier. In addition to emulating a subset of the ARM UAL instruction set, it provides visualisations of key concepts unique to assembly language programming and therefore helps make programming ARM assembly more accessible.
VisUAL是一个跨平台的可视化ARM汇编模拟器,可以方便的学习和演示使用ARM汇编指令。官方主页如下:
https://salmanarif.bitbucket.io/visual/index.html
VisUAL中用户可以访问的主要是 R0 ~ R13
、 SP
、 LR
、 PC
这几个寄存器,以及内存地址 0x00010000 ~ 0xFFFFFFFC
这些区域。对于ARM来说 SP
是堆栈指针、 LR
用来存放返回地址、 PC
存放正在取的指令。
VisUAL模拟支持了部分常用的指令指令列表可以在下面找到:
https://salmanarif.bitbucket.io/visual/supported_instructions.html
VisUAL中可以使用 Ctrl + Space
显示当前行指令的详细语法说明:
常用指令
每种架构下其指令可能有几十条到上百条,这里只介绍些常用的指令。
内存访问指令
指令 | 说明 |
---|---|
ADR | 取相对于PC的地址到寄存器 |
LDR | 取数据到寄存器 |
LDM | 批量取数据到寄存器 |
STR | 将寄存器上的数据保存到内存 |
STM | 批量到寄存器数据内存 |
PUSH | 数据入栈 |
POP | 数据出栈 |
通用数据处理指令
指令 | 功能 |
---|---|
ADD | 加法 |
SUB | 减法 |
AND | 按位与 |
ORR | 按位或 |
EOR | 按位异或 |
BIC | 按位清零 |
LSL | 逻辑左移 |
LSR | 逻辑右移 |
CMP | 比较 |
CMN | 源数据取反后比较 |
TST | 测试 |
MOV | 移动数据 |
MVN | 源数据取反后移动 |
分支和控制指令
指令 | 功能 |
---|---|
B | 跳转 |
BL | 跳转并将返回地址保存到LR寄存器中 |
其它指令
指令 | 功能 |
---|---|
DCD | 数据定义 |
END | 程序结束 |
GNU汇编语法
VisUAL只能用来演示指令,下面的一些内容在VisUAL中可能无法正常使用。
伪指令
在汇编代码中经常可以见到以 .
开头的字段,这些字段都是伪指令。伪指令只是一些标识信息。下面是一部分常见的伪指令:
伪指令 | 描述 |
---|---|
.arm | 声明接下来的程序使用ARM指令集 |
.thumb | 声明接下来的程序使用Thumb指令集 |
.arch | 声明目标架构 |
.global | 全局声明标志 |
.local | 局部声明标志 |
.weak | 弱声明相当于C语言中的__weak |
.section | 段标记 |
.ascii | 定义一串字符串 |
.asciz .string | 定义一串字符串,自动补充结束符 数据将在连续的空间上 |
.byte | 声明一个字节变量 多个变量可以一次性声明,使用逗号分隔 变量将在连续的空间上 |
.hword | 声明一个2字节变量 |
.word | 声明一个4字节变量 |
.fill | 重复填充数据 |
.set .equ | 给符号(变量)复制 语法:.set/.equ symbol, expression |
.type | 设置一个符号的属性值 语法:.type name , description description取值如下: %function 表示该符号用来表示一个函数名 %object表示该符号用来表示一个数据对象 |
.balign | 数据对齐指令 |
.align | 数据对齐指令 |
.macro .endm | 宏定义 |
.if .else .endif | 条件编译 |
分段
使用.section伪指令可以对程序进行分段,比如下面这样:
.section .bss @未初始化数据段
.section .data @已初始化数据段
.section .text @可执行代码段(程序段)
当然上面的 .bss .data .text 这些都是汇编中已经定义的段,使用的时候可以省略前面的 .section 。
标号
在汇编代码中以 :
结尾的字段是标号,标号有点类似于C语言中的函数名,可以使用标号来跳转调用等。
_start
标号是一个特殊的标号,是程序的入口点,相当于C语言中的main函数。
注释
在汇编中使用 @
符号表示注释,其后面的内容为注释内容。有点编译器中你也可以使用 /* 注释内容 */
的方式进行注释
与C语言混合使用
从这里开始的演示均在树莓派4B中进行,理论上只要CPU是ARM架构的电脑上装个主流的Linux发行版就可以进行测试。
汇编程序中调用C程序
准备下面程序保存为 main.c 文件:
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
printf("Hello world!\\n");
exit(0);
}
准备下面程序保存为 start.s 文件:
.text
.global _start @ 使_start标号全局可见
_start: @ _start标号是程序入口
bl main @ 跳转到main函数
汇编程序中调用C程序直接使用函数名就行,如果函数的传入参数不大于四个则依次把参数放入R0~R3寄存器即可(如果参数是double或是long类型的话一个参数会占用连续的两个寄存器):
如果函数的传入参数大于四个,那么多出来的那些参数需要放入堆栈中,可以编写C程序然后使用gcc -S编译成汇编文件查看具体的处理方式。
函数如果有返回值,返回值会放入R0寄存器(如果是double或是long类型的会放入R0和R1)。
C程序中调用汇编程序
准备下面程序保存为 fun.s 文件:
.text
.global fun @ 使fun这个符号(函数名)外部可见
fun:
add r4, r0, r1 @ 将传入的两个参数相加
mov r0, r4 @ 装载返回值
mov pc, lr @ 函数返回
准备下面程序保存为 main.c 文件:
#include <stdio.h>
extern int fun(int a, int b); // 声明fun函数,该函数功能在上面汇编代码中实现
void main(void)
{
printf("%d\\n", fun(200, 33));
}
汇编中的符号就相当于C语言的函数名,把这个符号使用 .global
声明为全局的就可以被其它文件使用了。
C程序中内嵌汇编代码
使用 __asm
指令可以在C程序中直接内嵌汇编代码,当然这样使用的汇编代码部分有很多功能限制。
总结
相比于很多高级语言来说,汇编的语法其实很简单,更多内容可以参考下面连接:
《GNU ARM Assembler Quick Reference》
链接: https://pan.baidu.com/s/1VS7HgKtDxH0OMmkiGPZBEA 提取码: grw3
《ARM and Thumb-2 Instruction Set Quick Reference Card》
链接: https://pan.baidu.com/s/1PcSYCf7–mlEtOlqGoRnUg 提取码: ksg3
以上是关于GNU ARM 汇编基础笔记的主要内容,如果未能解决你的问题,请参考以下文章