line #7
上一句,我们得到的只是字符串所在的页的基地址,我们还需要通过偏移地址计算出这个字符串的具体内存地址在哪里。我们通过在上一句查出来的基地址的基础上再增加一个偏移量即得到字符串的内存地址,并且我们用 w0
寄存器来保存它,用于将这个字符串作为 printf
函数的参数传递进去。
line #8
虽然我们在 line #4 里面重置了 w8
寄存器用于接收 printf
函数的返回值,但当我们通过寄存器接收到返回值后,我们还需要栈上的一个内存空间来保存这个返回值,因此在调用这个函数前提前在栈内存上为它准备一个内存地址来存放函数的返回值(即 w8
寄存器里的值)。
这里我们也是通过 MOV
指令,将零寄存器(WZR
)的值(即0)移动到栈内存的32bit内存空间,说人话就是初始化一个 32bit
的内存空间,将这个内存块的数据都清零,准备用来保存 printf
函数的返回值。
line #9
一切准备好了,我们就可以真正使用 BL
指令来调用 printf
函数了,printf
函数的地址是通过 linker
链接到的 libc
内的 printf
函数,一般来说调用指令有多个,例如 B
指令,就是单纯的跳转到另一个地方去执行了,不准备返回了,是一张单程船票,而这里我们使用的 BL
指令在跳转到另一个地方,会先将当前指令的地址保存到 lr
寄存器中,便于跳转到另一个地方之后还有坐标可以传送回来,是一张往返的套票。
line #10
在 printf
函数执行完了以后,它会把函数的返回值(一个32bit的int值)放在 w8
寄存器中,就和电影里面的特务接头一样,我们按照事前约定好的去某个指定的地方(这里是w8
寄存器)里面去拿结果,即可得到最新的情报(即 printf
函数的返回值),并且我们使用 LDR
指令将 w8
寄存器的这个返回值保存到栈内存上。
line #11
这里使用 MOV
指令,将 w8
寄存器的值移动到 w0
寄存器上,即将之前用于传参的 w0
寄存器重置回了 0
了。
line #12
到这里,我们的 main
函数已经通过调用 printf
函数在屏幕上打印出来的 Hello World!
的文字,printf
函数已经返回到了我们的 main
函数,我们也重置了用于传参的寄存器,接下来我们还需要恢复在调用 printf
函数之前备份的寄存器的值。
之前我们将 fp
和 lr
两个寄存器的值,保存在栈内存上,现在我们做一个反操作,将栈内存上保存的值通过 LD
指令还原到寄存器中去。
line #13
咱们的 main
函数已经完成了它的历史使命,成功的打印出了 Hello World!
,它作为一个栈帧也准备退出了,在进入 main
函数一开头的时候,我们在第一句汇编里面,通过 SUB
指令申请了一个 32 Bytes
大小的栈内存空间用来搞事情,现在事情办妥了以后,我们有借有还,把申请的 32 Bytes
栈内存空间通过 ADD
指令给还回去,将栈顶还原到调用 main
函数之前的位置,我们轻轻的来轻轻的走,不带着一byte的内存。
line #14
最后一步,我们使用 RET
指令退出函数,它就是我们的 helloworld
程序里 main
函数的 return
语句。到此我们的程序就写完了。
结语
在写下这14句汇编以后,我们就可以使用 clang
编译器将其编译成可执行的二进制文件:
然后我们可以将它放到任何一台 ARM64
CPU的机器,如大部分的Android机器,或者树莓派等单片机上运行了,我们就可以看见学习一门语言最亲切的打印语句了,这里我们使用的是Android自带的 LLDB
调试器在真机上运行的:
到此你就基本学会了如何用ARM汇编手写一个 helloworld
程序,希望这篇文章真的能带大家走进ARM汇编的世界里一起学习,路漫漫兮。
附录
本文中完整的汇编代码:
本文汇编对应的C源码:
伪指令参考表(节选):
【学习技术大群:769843038】
【网盘免费资料包,需要的自行领取】:嵌入式物联网 22个STM32项目、大赛作品,【华清远见发放资料包】http://makerschool.mikecrm.com/f4wjYBB
【下方分享一些免费教程资料,大家感兴趣的可以看一下】: