文章目录
- 简述
- 寄存器
- 语法
- 系统调用
- 例程
简述
如果不了解x86汇编的话建议先了解下,x86资料多、环境好搞、容易入门
阿尔可是急于求成的人,希望赶快看到成果;
所以本篇文章不会东讲西讲展开讲,只讲让hello world汇编能跑起来的关键点
说句感慨:本科的时候一直没学会汇编,对我来说最大的阻碍就是“通用寄存器”,压根就想不明白,什么通用?我给寄存器丢几个数,CPU怎么知道我放在哪个寄存器了?只是自己把数据左手倒右手的话确实通用,但在现代操作系统下,每个寄存器都有固定的用途,通用个🍑,,,
寄存器
ARM32的寄存器貌似叫r0 r1 r2 …
但是用这个名称在aarch64架构的平台编译不过,
ARM64的寄存器叫x0/w0、x1/w1… 对应rax/rbx
ARM64一共有x0-x30
全部的寄存器可以上网搜,这里说一下关键的
存放系统调用号的寄存器 x8
存放返回值的寄存器 x0
传递参数的寄存器x0-x5
好了,能传参、能执行系统调用、能拿返回值,就好比Minecraft里有了铲子、镐子和斧头
——虽然仍然一无所有,但是,可以开始建造属于自己的一切了
语法
立即数:AT&T汇编中用$标识,ARM汇编用#
地址:AT&T汇编中直接用标签,ARM汇编需要在前面加上=
指令顺序:AT&T汇编是ins src, dst,ARM汇编是inc dst, src,这点和x86汇编比较像
注释:AT&T汇编用#作注释,ARM汇编用//或/**/(我用#作注释也一样能编译运行,不知道汇编器是怎么区分注释和立即数的)
伪指令:因为都是GNU汇编器,所以AT&T汇编和ARM64基本一样
描述比较困难,等下直接看例程
系统调用
汇编里没有printf这么高级的封装函数可以使用,想要使用一些功能就需要系统调用了
上面的unistd文件放的是c库的声明,
下面的unistd文件放的的系统调用号
openEuler20.03和Ubuntu22系统上都是
在其中我们查看write系统调用的调用号和声明
调用号
声明
那么就可以知道,write的系统调用号在aarch64平台上是64,需要三个参数:文件描述符、内容、输出长度
例程
.data
strr: .string "hello world~\n"
len = .-strr
.text
.global _start
// 以您对x86汇编的了解,相信我不注释您也明白↑↑↑这些细枝末节
_start:
// write syscall
mov x8, #64
mov x0, #1 // stdout
ldr x1, =strr
mov x2, len
svc #0 // execute syscall
/*
首先是把write的系统调用号放到x8寄存器
然后参数1(文件描述符)就用标准输出来打印到控制台上
参数2内容,这里填字符串的地址,(ARM不能用mov来访问地址这老生常谈了相信我不说您也知道)
参数3 字符串长度
svc #0执行系统调用
*/
// exit syscall
mov x8, #93
// use x0 as return value
svc #0
/*
但是write系统调用完之后紧接着就exit
直接让write调用完后x0里保存的值作为exit的参数1,不需额外操作
程序执行完后在shell执行echo $?可以看到write系统调用执行的结果
*/
编译
as hello.s -o hello.o
ld hello.o -o hello