原文地址:http://thinkingeek.com/arm-assembler-raspberry-pi/
阅读建议:尽量阅读原文
一般来讲,学习高级语言要比学习汇编有用的多。但学习一些汇编也是有好处的。本系列文章的目的并不是要去精通汇编语言编程,而是作为一个入门教程,希望大家通过学习可以了解一些底层的细节。
ARM介绍
下面只是介绍一些常用的细节,并不全面。
ARM是32位结构并遵循一个简单的设计原则:flexibility(灵活)。这对于集成商(芯片厂商)来讲是非常方便的(在设计硬件的时候有非常大的自由),但对于系统开发者特别是需要考虑硬件多样性的开发者来讲就不那么友好了。教程中的代码等都在 树莓派 硬件平台验证过。
虽然有些部分可以在其他ARM平台运行,但有些只能在 树莓派 上运行,教程中并未进行区分。参考:ARM官网
书写汇编程序
汇编语言只是基于机器码实现的非常简单的语法规则。
机器码(二进制码)是只机器可执行的序列。是指令的集合,指令是被编码成二进制形式(具体的编码规则可参考ARM教程)。当然直接使用二进制指令编写代码是非常费力的。
因此我们使用ARM汇编语言编写程序。由于计算机无法直接执行汇编程序,因此我们使用 汇编器 将其转换成可直接执行的机器码序列。汇编器又称为 GNU汇编器,原因是其属于GNU项目的一部分。
第一个汇编程序
下面我们开始写第一个汇源程序,非常简单,只是返回一个错误码。
/* -- first.s */
/* This is a comment */
.global main /* 'main' is our entry point and must be global */
.func main /* 'main' is a function */
main: /* This is main */
mov r0, #2 /* Put a 2 inside the register r0 */
bx lr /* Return from main */
Create a file called first.s
and write the contents shown above. Save it.
打开一个文本编辑器如vim,新建一个汇编源文件first.s (汇编文件一般都以.s结尾)。输入上面代码,保存退出。
执行下面命令:
$ as -o first.o first.s
命令执行后会产生first.o。现在链接成可执行程序:
1$ gcc -o first first.o
如果一切顺利我们会得到一个可执行程序first。拷贝到目标平台执行:
./first
没有任何输出,如下命令获取错误码:
$ ./first ; echo
$ 2
错误码2就是上面代码赋值的。将下面编译脚本保存一下,后续就不需要每次手动编译了。
# Makefile
all: first
first: first.o
gcc -o $@ $+
first.o : first.s
as -o $@ $<
clean:
rm -vf first *.o
代码解释
上面代码实现了一个c的main函数,只做了“返回2”的动作。并且使用c的runtime帮我们实现了应用程序的初始化和退出的工作。后续的示例代码都使用这种方式。
下面一行一行解释。
1 /* -- first.s */
2 /* This is a comment */
这些是注释,注释是包括在/**/中的内容。汇编器会忽略这些内容,但一般不要内嵌。
3 .global main /* 'main' is our entry point and must be global */
这是一个GNU汇编器指令。汇编器指令用于控制汇编行为。以 "." 开头,后面跟指令的名字以及一些参数。上述代码表示main是一个全局变量,只有这样c runtime才能调用到main函数。如果不声明为全局的,c runtime将不能调用进而链接过程会失败
4 .func main /* 'main' is a function */
这又是另一个汇编器指令,将main声明为函数。由于汇编程序一般包含指令(代码)和数据,因此我们需要显示的将main声明为函数,即代码。
6 main: /* This is main */
汇编代码如果不是汇编器指令一般都是如下形式:label:instruction
空白行会被忽略。A line with onlylabel:, applies that label to the next line (you can have more than one label referring to the same thing this way).
instruction表示汇源源代码,上面的代码只定义了main label没有代码。
7 mov r0, #2 /* Put a 2 inside the register r0 */
前面的空白字符会被忽略,但缩进格式表示下面代码属于main函数。上面代码是mov指令,表示设置寄存器r0的值为2。下一章会涉及到更多的寄存器,还会涉及到立即数等,在arm指令中,目标参数一般都在左边。
8 bx lr /* Return from main */
bx指令表示“branch-跳转 和 exchange-变换”,这里先不关心变换。跳转指令会改变指令的执行流程。arm处理器指令的执行方式是顺序的,一条接着一条,因此执行完mov指令后,会执行bx。跳转指令用于显示的修改执行序列,上面的指令的含义是跳转到lr寄存器指示的位置继续执行。暂时我们先不关心lr的内容,只需知道bx使得程序跳出了main函数,并结束了程序的执行。
简单介绍下错误码(error code),main函数执行的结果就是这个应用的错误码,当程序执行完毕,错误码需要保存在r0寄存器中。