ARM64汇编打印hello

不用链接C库的裸程序,向标准输出打印hello并调用exit退出:

.section .rodata

hellostr:
.ascii "hello\n"


.section .text
.global _start

_start:
        mov x0, #1
        ldr x1, =hellostr
        mov x2, #6
        mov x8, #64  //syscall write
        svc #0


        mov x0, #0
        mov x8, #93  //syscall exit
        svc #0
all:
        as main.s -o main.o
        ld main.o -o a.out
        rm main.o

链接C库,调用printf打印hello并退出:

.section .rodata

hellostr:
.ascii "hello\n"


.section .text
.global _start

_start:
        ldr x0, =hellostr
        bl printf

        mov x0, #0
        bl exit
all:
        as main.s -o main.o
        ld -dynamic-linker /lib/ld-linux-aarch64.so.1 -lc  main.o -o a.out
        rm main.o
$ ldd a.out
        linux-vdso.so.1 (0x0000007fa9beb000)
        libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000007fa9a33000)
        /lib/ld-linux-aarch64.so.1 (0x0000007fa9bbb000)

ldd 可以看到依赖了C库, vdso及ld解释器(loader)。

关于vdso, 为了提高时间相关的系统调用效率,内核会把时间及时刷新到一块内存上,把这段内存再映射到用户空间,获取这这些数据相关的代码就是在vdso中实现的,映射到用户空间的内存数据就是vvar。找一个运行中的程序,内存映射如下,vvar即是存储了时间的只读数据段,vdso即是访问它的代码段:

55a483a000-55a485b000 rw-p 00000000 00:00 0                              [heap]
7fa8154000-7fa873d000 r--p 00000000 b3:02 660                            /usr/lib/locale/locale-archive
7fa873d000-7fa889a000 r-xp 00000000 b3:02 7015                           /usr/lib/aarch64-linux-gnu/libc-2.31.so
7fa889a000-7fa88aa000 ---p 0015d000 b3:02 7015                           /usr/lib/aarch64-linux-gnu/libc-2.31.so
7fa88aa000-7fa88ad000 r--p 0015d000 b3:02 7015                           /usr/lib/aarch64-linux-gnu/libc-2.31.so
7fa88ad000-7fa88b0000 rw-p 00160000 b3:02 7015                           /usr/lib/aarch64-linux-gnu/libc-2.31.so
7fa88b0000-7fa88b3000 rw-p 00000000 00:00 0
7fa88c5000-7fa88e7000 r-xp 00000000 b3:02 6839                           /usr/lib/aarch64-linux-gnu/ld-2.31.so
7fa88f1000-7fa88f3000 rw-p 00000000 00:00 0
7fa88f3000-7fa88f5000 r--p 00000000 00:00 0                              [vvar]
7fa88f5000-7fa88f6000 r-xp 00000000 00:00 0                              [vdso]
7fa88f6000-7fa88f7000 r--p 00021000 b3:02 6839                           /usr/lib/aarch64-linux-gnu/ld-2.31.so
7fa88f7000-7fa88f9000 rw-p 00022000 b3:02 6839                           /usr/lib/aarch64-linux-gnu/ld-2.31.so
7fe3763000-7fe3784000 rw-p 00000000 00:00 0                              [stack]

ld-linux-aarch64.so 是一个加载器,在命令行上运行程序时,bash终端执行fork, exec打开a.out,a.out需要解释器,内核就将ld-linux-aarch64.so映射进来,接下来ld-linux-aarch64.so 将a.out依赖的动态库加载到进程地址空间(代码段,数据段等映射进来),准备好相关工作后,调用a.out入口点_start 。

用strace 查看a.out执行相关的系统调用,没有ld-linux-aarch64.so, 是因为execve执行时,内核把它加载了。

$ strace ./a.out
execve("./a.out", ["./a.out"], 0x7fd3127fb0 /* 24 vars */) = 0
brk(NULL)                               = 0x33b33000
faccessat(AT_FDCWD, "/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=70321, ...}) = 0
mmap(NULL, 70321, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa50e4000
close(3)                                = 0
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0`C\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1458480, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa5122000
mmap(NULL, 1531032, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa4f6e000
mprotect(0x7fa50cb000, 65536, PROT_NONE) = 0
mmap(0x7fa50db000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15d000) = 0x7fa50db000
mmap(0x7fa50e1000, 11416, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa50e1000
close(3)                                = 0
mprotect(0x7fa50db000, 12288, PROT_READ) = 0
mprotect(0x410000, 4096, PROT_READ)     = 0
mprotect(0x7fa5127000, 4096, PROT_READ) = 0
munmap(0x7fa50e4000, 70321)             = 0
write(1, "hello\n", 6hello
)                  = 6
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
brk(NULL)                               = 0x33b33000
brk(0x33b54000)                         = 0x33b54000
write(1, "hello\n", 6hello
)                  = 6
exit_group(0)                           = ?
+++ exited with 0 +++

### 设置ARM64汇编语言开发环境 #### 选择合适的操作系统 为了方便地编写和测试ARM64架构下的汇编代码,建议使用Linux发行版作为工作平台。Ubuntu是一个不错的选择,因为它拥有庞大的社区支持以及丰富的软件包资源。 #### 安装必要的工具链 对于ARM64架构而言,GNU工具链是最常用的解决方案之一。可以通过以下命令安装所需的组件: ```bash sudo apt-get update sudo apt-get install build-essential gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu qemu-user-static debootstrap schroot ``` 上述命令将会安装一系列重要的工具[^1]: - `gcc-aarch64-linux-gnu`:用于交叉编译C/C++程序到ARM64目标平台上。 - `binutils-aarch64-linux-gnu`:提供了一系列链接器(ld),汇编器(as)以及其他辅助工具。 - `qemu-user-static` 和 `debootstrap` 配合使用可以创建并启动一个完整的ARM64根文件系统镜像,在其中可以直接执行原生应用程序而无需实际硬件设备的支持;同时利用schroot管理这些容器化的环境以便更安全高效地进行调试与实验操作。 #### 编写简单的ARM64汇编代码 当一切准备就绪之后,便可以在本地编辑器里撰写一段基础的ARM64汇编代码了。下面给出的是一个非常简单的小例子——打印字符串至控制台: ```assembly .global _start .section .data msg: .asciz "Hello, world!\n" .text _start: mov x0, #1 // 文件描述符(stdout) ldr x1, =msg // 将消息指针加载入寄存器x1 ldr x2, =(13) // 消息长度 mov x8, #64 // syscall write number (for Linux AArch64) svc 0 // 执行syscall mov x0, #0 // 返回状态码 mov x8, #93 // syscall exit number (for Linux AArch64) svc 0 // 终止进程 ``` 保存此段代码为hello.s,并通过如下方式对其进行编译连接得到可执行文件: ```bash aarch64-linux-gnu-as hello.s -o hello.o aarch64-linux-gnu-ld hello.o -o hello ``` 最后借助于QEMU模拟器来运行该二进制文件查看效果: ```bash qemu-aarch64 ./hello ``` 如果一切正常,则应该能够在终端上看到预期输出:“Hello, world!” #### 使用在线IDE和服务 除了搭建本地环境外,还有许多在线服务允许用户直接在网页浏览器内部完成整个流程,比如Repl.it或Wandbox等网站都提供了良好的体验和支持多种编程语言的能力,其中包括对ARM体系结构的支持选项。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值