一个 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
startis the entrypoint of our program (Darwin expectsmainby 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.