本文由 简悦 SimpRead转码, 原文地址 suelan.github.io
RY 的博客
如何在 Xcode 中查看 Assemble 代码
debug -> Product -> Action-> Assemble "main.m"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
生成的文件有 541 行代码,其中包含大量供调试器使用的汇编指令。
汇编指令
.section __TEXT,__text,regular,pure_instructions
.ios_version_min 11, 0 sdk_version 13, 6
.file 1 "/Users/rongyan.zheng/Downloads/AssemblyMain" "AssemblyMain/main.m"
.globl _main ; -- Begin function main
.p2align 2
这些是汇编指令,而不是汇编代码。".section "指令指定了下面的内容将进入哪个部分。
接下来,.globl 指令指定 _main 是一个外部符号。.p2align 2 将文件中的当前位置对齐到指定边界,这里是 2^2,4 字节。
然后,就是我们的 main 集合标签。
_main: ; @main
Lfunc_begin0:
.loc 1 12 0 ; AssemblyMain/main.m:12:0
.cfi_startproc
; %bb.0:
.cfi_startproc指令用于大多数函数的开头。CFI 是调用帧信息的简称。一个 frame 粗略地对应一个函数。当你使用调试器 step in 或 step out 时,你实际上是在步入/步出调用帧。在 C 代码中,函数有自己的调用框架,但其他东西也可以。.cfi_startproc "指令为函数提供了一个进入".eh_frame "的入口,其中包含了展开信息--这就是异常如何展开调用框架栈的。该指令还将为 CFI 发送依赖于体系结构的指令。在输出的更下方,它与相应的.cfi_endproc相匹配,以标记我们的main()函数的结束。- www.objc.io/issues/6-bu…
SP 和堆栈
sub sp, sp, #48 ; =48
它在堆栈上设置了一个调用帧。这里的 sp 指的是栈指针寄存器。在 AArch64 中,堆栈指针必须 128 位对齐;这里是 48 * 8 位。
寄存器
处理器的大部分操作都涉及数据处理。这些数据可以存储在内存中,也可以从内存中访问。但是,从内存中读取数据和将数据存储到内存中会减慢处理器的运行速度,因为这涉及到通过控制总线向 "内存存储单元 "发送数据请求并通过同一通道获取数据的复杂过程。为了加快处理器的运行速度,处理器包含一些 "内部内存存储位置",称为寄存器。
寄存器存储用于处理的数据元素,而无需访问内存。处理器芯片内置的寄存器数量有限。- www.tutorialspoint.com/assembly_pr…
在 ARM 64 中,下图显示了寄存器的作用。
- 前八个寄存器(r0-r7)用于向子程序传递参数值和从函数返回结果值。
- 帧指针寄存器"(FP)应指向最内层帧(属于最近的例程调用)的帧记录。寻址最低的双字应指向 "上一帧记录",寻址最高的双字应包含进入当前函数时在 LR 中传递的值。
堆栈结构
堆栈是内存的一个 "连续区域",可用于存储局部变量,以及在参数寄存器不足时向子程序传递额外参数。堆栈的实现方式是 全降,"堆栈的当前范围 "保存在专用寄存器 "SP "中。-ARM 64 位体系结构的过程调用标准(AArch64)- AArch64 ABI 1.0 版
ARM 环境使用的堆栈在函数调用时是向下增长的,其中包含局部变量和函数参数。堆栈在函数调用时对齐。图 1 显示了子程序调用前和调用过程中的堆栈。
堆栈帧包含以下区域:
- 参数区(parameter area) 存储调用者传递给被调用函数的参数,或者根据每个参数的类型和寄存器的可用性为参数存储空间。该区域位于调用者的堆栈帧中。
- 链接区 包含调用者下一条指令的地址。
- 保存帧指针(可选)包含调用者堆栈帧的基地址。
- 局部存储区 包含子程序的局部变量,以及被调用函数返回前必须恢复的寄存器值。详情请参见寄存器保存。
- 保存寄存器区 包含在调用函数返回前必须恢复的寄存器值。详情请参见寄存器保存。
在这种环境下,堆栈帧的大小并不固定。
另一个堆栈帧布局图来自 ARM 64 位体系结构的过程调用标准(AArch64)- AArch64 ABI 1.0 版
关于堆栈和堆栈指针,请参阅更多内容。
寻址模式
作为初学者,我想知道地址是如何通过方括号内的数字计算出来的。
在这里,我们必须了解一些关于 "寻址模式 "的知识。根据关于 ARMv8 指令集的文档,有几种寻址模式定义了地址的形成方式。
-
基准寄存器 - 最简单的寻址方式是单寄存器。基寄存器是一个 X 寄存器,包含被访问数据的完整或绝对虚拟地址,如图所示:
-
偏移寻址模式 - 可选择将偏移应用于基址,如图所示:
在上图中,X1 包含基地址,
#12是该地址的字节偏移量。这意味着访问地址为X1+12。偏移可以是一个常数,也可以是另一个寄存器。例如,结构体可以使用这种寻址方式。编译器会使用偏移量维护一个指向结构体基部的指针,以选择不同的成员。 -
预索引寻址模式 - 在指令语法中,预索引通过在方括号后添加感叹号
!来表示,如图所示:预索引寻址方式与偏移寻址方式类似,"只是基指针会随着指令的执行而更新"。在上图中,指令执行完毕后,
X1的值为 X1+12。 -
后索引寻址模式 - 使用后索引寻址时,值从基指针中的地址加载,然后更新指针,如图所示:
后索引寻址适用于从堆栈中弹出。该指令从堆栈指针指向的位置加载值,然后将堆栈指针移动到堆栈的下一个完整位置。
下面是我们的 main 函数中的下一条指令:
stp x29, x30, [sp, #32]
stp 将 X29 和 X30 推入堆栈,这意味着这条指令将把 x29 和 x30 的值存储到地址为 sp+32 的内存中。在 ARMv8 中,X29 用于帧指针,X30 用作链接寄存器,可称为 LR。
在堆栈上存储参数
add x29, sp, #32 ; =32
设置 x29 的值为 sp + #32
stur wzr, [x29, #-4]
stur w0, [x29, #-8]
str x1, [sp, #16]
将 wzr 中的值存储到 x29 + #-4,即用 0 写入 x29 + #-4;
零寄存器 ZXR 和 WZR 始终读作 0,忽略写入。
将 w0 中的值存入 x29 + #-8;
将 x1 中的值存储到 sp + #16。
大多数 A64 指令都在寄存器上运行。该架构提供 31 个通用寄存器。每个寄存器可用作 64 位 X 寄存器(X0...X30)或 32 位 W 寄存器(W0...W30)。W0 是 X0 底部的 32 位。
选择 X 还是 W 决定了操作的大小。使用 X 寄存器将导致 64 位计算,而使用 W 寄存器将导致 32 位计算。
参数寄存器
Arm 架构对通用寄存器的使用方式有一些限制。
X0-X7 是参数/结果寄存器。x0-x7 用于向子程序传递参数值和从函数返回结果值。第一个参数传递到 X0,第二个参数传递到 X1。
在我们的例子中,main 函数需要两个参数,因此使用了 w0 和 X。
用于返回的寄存器
返回结果使用哪个寄存器取决于返回结果的类型:
-
如果函数结果的类型 T 为
"void func(T arg)",则返回结果使用的寄存器与传递参数时使用的寄存器相同。例如
- 否则,调用者应为结果预留足够大小和对齐方式的内存块。内存块的地址应作为附加参数传递给函数 X8.
XR|X8.
Ltmp0:
.loc 1 13 16 prologue_end ; AssemblyMain/main.m:13:16
mov x8, #0 ; copy #0 to x8
str x8, [sp, #8] ;
.loc 1 14 22 ; AssemblyMain/main.m:14:22
然后,将 x8 中的值存储到 sp + #8 中。
分支指令和函数调用
让我们看看下一条指令。
bl _objc_autoreleasePoolPush
通常,处理器按程序顺序执行指令。这意味着处理器执行指令的顺序与内存中设置指令的顺序相同。改变这种顺序的一种方法是使用分支指令。分支指令改变了程序流程,用于循环、决策和函数调用。
A64 指令集还有一些条件分支指令。这些指令会根据前面指令的结果改变执行方式。- developer.arm.com/architectur…
无条件分支指令
无条件分支指令有两种:
B表示分支,BR表示带寄存器的分支。
无条件分支指令 B <label> 执行一个直接的、与 PC 有关的分支,指向 .
在我们的例子中,labe是 _objc_autoreleasePoolPush;bl _objc_autoreleasePoolPush 表示我们将跳转到 _objc_autoreleasePoolPush例程。
条件分支指令
条件分支指令 B. 是 B 指令的条件版本。只有当条件为真时,才会执行分支。范围限制为 +/- 1MB。
PC相关地址的标签
标签可以表示 PC 值加上或减去 PC 到标签的偏移量。使用这些标签作为分支指令的目标,或访问嵌入代码段中的小数据项。
adrp x8, _OBJC_SELECTOR_REFERENCES_@PAGE
add x8, x8, _OBJC_SELECTOR_REFERENCES_@PAGEOFF ;@selector(class)
adrp x9, _OBJC_CLASSLIST_REFERENCES_$_@PAGE
add x9, x9, _OBJC_CLASSLIST_REFERENCES_$_@PAGEOFF ; objc_cls_ref_AppDelegate
-
adrp是 PC 相关偏移的 4KB 页面地址我将最终的可执行文件拖入
hopper中,_OBJC_SELECTOR_REFERENCES_@PAGE的地址是#0x100009000.
000000010000621c adrp x8, #0x100009000 ; 0x1000093e0@PAGE
0000000100006220 add x8, x8, #0x3e0 ; 0x1000093e0@PAGEOFF, &@selector(class)
0000000100006224 adrp x9, #0x100009000 ; 0x1000093f0@PAGE
0000000100006228 add x9, x9, #0x3f0 ; 0x1000093f0@PAGEOFF, objc_cls_ref_AppDelegate
000000010000622c ldr x9, [x9] ; objc_cls_ref_AppDelegate,_OBJC_CLASS_$_AppDelegate
0000000100006230 ldr x1, [x8]
ldr x9, [x9] ; load data into x9 from the value in x9.
ldr x1, [x8] ; load data into x1 from the value in x8, which is the address of @selector(class)
str x0, [sp] ; 8-byte Folded Spill
mov x0, x9 ; move the addreess in x9 into x0, which is the address of objc_cls_ref_AppDelegate
bl _objc_msgSend ; call msgSend, [AppDelegate class]
bl _NSStringFromClass ; call NSStringFromClass
返回值和自动释放
mov x29, x29 ; marker for objc_retainAutoreleaseReturnValue
bl _objc_retainAutoreleasedReturnValue
ldr x8, [sp, #8] ; load data into x8 from memory [sp, #8]
str x0, [sp, #8] ; store data from x0 into [sp, #8], which is the result of _NSStringFromClass
mov x0, x8 ; move data from x8 to x0
bl _objc_release
ldr x0, [sp] ; 8-byte Folded Reload
bl _objc_autoreleasePoolPop
str x0, [sp, #8]表示将 x0 中的数据存储到 [sp, #8] 中。如前所述,对于NSString *NSStringFromClass(Class aClass)类型的函数,x0用于存储返回结果。
传递参数
ldur w0, [x29, #-8] ; load data into w0 from [x29, #-8]; which is int argc
ldr x1, [sp, #16] ; load data into x1 from [sp, #16], where argv is stroed
ldr x3, [sp, #8] ; laod data into x3 from [sp, #8], which is the result of the result of _NSStringFromClass
mov x8, #0
mov x2, x8 ; nil
bl _UIApplicationMain ; call _UIApplicationMain
stur w0, [x29, #-4]
add x8, sp, #8 ; =8
mov x0, x8
mov x8, #0
mov x1, x8
bl _objc_storeStrong
ldur w0, [x29, #-4]
ldp x29, x30, [sp, #32] ; 16-byte Folded Reload, reset
add sp, sp, #48 ; =48, reset
ret
ldp x29, x30, [sp, #32] 用于重置 "fp "和链接器注册表
add sp, sp, #48 表示该函数的调用框架已消失。