一个 macOS 汇编的分析(M1 芯片)
1. 代码
源文件 main.c
代码如下
1 |
|
swap 函数实现了语句 v[k] <--> v[k+1]
,将两个元素进行交换,注意,这里使用了内联汇编。
执行以后的 main.s
文件代码如下
1 | .section __TEXT,__text,regular,pure_instructions |
2. 分析
1 | .section __TEXT,__text,regular,pure_instructions |
首先,定义了一个节(section)属于__Text 段的 _text 节,属于常规(regular)类型,属性为纯指令(pure_instructions)。
随后指定了构建系统版本(macOS 11.0)和 SDK 版本(11.3)。
然后开始定义 swap 函数。
1 | .globl _swap ; -- Begin function swap |
首先前三行申明了函数名称和指令对齐方式,其中.p2align
伪指令指定指令以 2^x 字节对齐,由于 arm 指令集定长为 32 位,即 4 字节,所以对齐方式为 4 字节对齐。.cfi_startproc
和.cfi_endproc
定义了一段代码的开始与结束,在这之间写汇编指令。
这里要说明的是,arm64 要求栈的对齐方式为16字节,由于 swap 的这两个参数均为 64 位,即 8 字节,所以只需要开一个 16 字节的栈就可以了。和 x86 类似,栈也是从高地址向低地址生长,且参数由右向左入栈,所以 x0 存放的是 v 数组的基地址,x1 存放的是 k 的值,由于 long long int
数据类型一个占 8 字节,所以需要将 k * 8 才能找到 &v[k]
。然后通过一系列 load/store
交换内存这两个元素的值,最后清空栈,也就是将 sp 加 16,然后通过 ret 指令设置 pc 程序计数寄存器返回到调用者。
然后开始定义 main 函数。在操作系统看来,main
函数和其他函数并没有什么不同,由于它是用户编写程序的入口,调用者是操作系统,执行完毕或出现异常之后要返回到操作系统,所以 main 函数第一步需要做的是定义对栈,然后将操作系统在执行 main 函数的 fp 信息和 sp 信息存储下来。
1 | sub sp, sp, #80 ; =80 |
首先是建立堆栈,这里的对栈也是 16 字节对齐。这里建立了一个五个元素的对栈。其中,第一个元素有 16 字节,可以存放 fp 和 lr 的值。然后栈顶指向第四个元素的下面第一个字节表示现在堆栈为空。.cfi_def_cfa
其中 cfi
指的是 call from info,是 DWARF 2.0 规定的堆栈信息。cfa
指的是 canonical frame address
规范堆栈框,这是定义了该程序所有函数的基地址,其基地址是原先 x29 寄存器加 16,即我们开的80字节堆栈的栈底。然后又定义了 fp 和 sp 相对于 cfa 的偏移。整个对栈此时的情况如下。
相对地址(以执行程序前 sp 地址为相对零地址) | 存放内容 | 字节 |
---|---|---|
0 | ||
+16 | ||
+32 | ||
+48 | ||
+64 | FP | 低 8 字节 |
LR | 高 8 字节 | |
+80 | CFA |
然后通过暂存寄存器 x8 去检查堆栈是否发生错误,指令如下。
1 | adrp x8, ___stack_chk_guard@GOTPAGE |
首先,先从编译器堆栈保护函数处获取页号将基地址对应到 x8 上,然后加载页偏移处 x8 的值,这也是一个地址,最后通过这个地址找到该值并且将其送入到 x8 中。接着,这几步看不大懂。
1 | stur x8, [x29, #-8] |
然后在堆栈中存储程序中的数据
1 | adrp x8, l___const.main.num@PAGE |
前两句赋给 x8 寄存器 &num,然后将从 sp + 32 开始的 4 个 long long int
大小的地方存 num 数组。注意,这里的存储是小端存储。
之后就是执行 printf
函数了。
3. 从 .s 编译到可执行程序
.s –> .o
1 | as -o <filename>.o <filename>.s |
.o –> executive file (binary file)
1 | ld -o <filename> <filename>.o |
- -lSystem: tell the linker to link executable with
libSystem.dylib
- -syslibroot: it’s mandatory to tell the linker where to find
libSystem.dylib
xcrun -sdk -macosx --show-sdk-path
: dynamically use the currently active version of Xcode- -e start: tell the linker
start
is the entrypoint of our program (Darwin expectsmain
by default) - -arch arm64: tell the linker our architecture is arm64, which is optional if you use apple silicon mac
About this Post
This post is written by Chen Li, licensed under CC BY-NC 4.0.