为了更加清晰的了解stack frame 是什么,下面写了测试的代码并附上debug信息 。可能比较冗长,耐心查看必有收获!
- (void)viewDidLoad {
[super viewDidLoad];
[self launchUCBrowser];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)launchUCBrowser
{
[self clickSearchBar];
}
- (void)clickSearchBar
{
[self typing];
}
- (void)typing
{
NSLog(@"Hello world");
}
首先查看一下当前程序加载到内存的起始地址:
(lldb) image list
[ 0] B8DF937E-E66C-3633-AEE8-87CFE8EA18DE 0x00000001000d8000 /Users/vedon/Library/Developer/Xcode/DerivedData/StackFrame-blkbdwapfezplhfqiynvukzdvxgc/Build/Products/Debug-iphoneos/StackFrame.app/StackFrame
分别在每个函数执行的位置打上断点,并把当前的寄存器的值打印出来。以下去除了部分数据的寄存器数据:
(lldb) register read
General Purpose Registers:
x0 = 0x0000000109d0aab0
x1 = 0x000000018b331732 "viewDidLoad"
x2 = 0x0000000000000001
fp = 0x000000016fd25f70
lr = 0x00000001000de710 StackFrame`-[ViewController viewDidLoad] + 68 at ViewController.m:17
sp = 0x000000016fd25f50
pc = 0x00000001000de718 StackFrame`-[ViewController viewDidLoad] + 76 at ViewController.m:18
(lldb) register read
General Purpose Registers:
x0 = 0x0000000109d0aab0
x1 = 0x00000001000dec90 "launchUCBrowser"
x2 = 0x0000000000000001
fp = 0x000000016fd25f40
lr = 0x00000001000de728 StackFrame`-[ViewController viewDidLoad] + 92 at ViewController.m:19
sp = 0x000000016fd25f30
pc = 0x00000001000de7a0 StackFrame`-[ViewController launchUCBrowser] + 28 at ViewController.m:31
(lldb) register read
General Purpose Registers:
x0 = 0x0000000109d0aab0
x1 = 0x00000001000decb8 "clickSearchBar"
x2 = 0x0000000000000001
fp = 0x000000016fd25f20
lr = 0x00000001000de7ac StackFrame`-[ViewController launchUCBrowser] + 40 at ViewController.m:32
sp = 0x000000016fd25f10
pc = 0x00000001000de7d4 StackFrame`-[ViewController clickSearchBar] + 28 at ViewController.m:36
(lldb) register read
General Purpose Registers:
x0 = 0x0000000109d0aab0
x1 = 0x00000001000decc7 "typing"
x2 = 0x0000000000000001
fp = 0x000000016fd25f00
lr = 0x00000001000de7e0 StackFrame`-[ViewController clickSearchBar] + 40 at ViewController.m:37
sp = 0x000000016fd25ef0
pc = 0x00000001000de800 StackFrame`-[ViewController typing] + 20 at ViewController.m:41
可以看到:
- x0 的地址都一样,代表的self
- x1 就是函数的selector
- lr 保存了方法调用完后,返回的地址。
- pc 就是当前执行的指令地址。
fp 与 sp 是一个frame 的界限。如下表:
function name | fp | sp | desc |
---|---|---|---|
viewDidLoad | 0x000000016fd25f70 | 0x000000016fd25f50 | 此fp 和sp 构成了上caller的frame |
launchUCBrowser | 0x000000016fd25f40 | 0x000000016fd25f30 | 此fp 和sp 构成了上viewDidLoad的frame |
clickSearchBar | 0x000000016fd25f20 | 0x000000016fd25f10 | 此fp 和sp 构成了上launchUCBrowser的frame |
typing | 0x000000016fd25f00 | 0x000000016fd25ef0 | 此fp 和sp 构成了上clickSearchBar的frame |
下面是demo 在hopper 内容:
; ================ B E G I N N I N G O F P R O C E D U R E ================
-[ViewController viewDidLoad]:
00000001000066cc sub sp, sp, #0x30 ; Objective C Implementation defined at 0x1000080f8 (instance method), DATA XREF=0x1000080f8
// sp = sp - 0x30
00000001000066d0 stp x29, x30, [sp, #0x20]
//将x29(fp) ,x30(lr) 存入 sp + 0x20。sp = sp + 0x20 (指针下移),注意这里是上一次方法调用存进来的,用于方法调用完之后恢复上一次调用。
00000001000066d4 add x29, sp, #0x20
//设置当前函数的fp = sp + 0x20
00000001000066d8 mov x8, sp
//将sp 的值保存到x8
00000001000066dc adrp x9, #0x100008000
//把当前pc的值后12bit 设为0,并把0x100008000 << 12,然后把两者相加存入x9.
00000001000066e0 add x9, x9, #0xcd0 ; @selector(viewDidLoad)
//x9 = x9 + 0xcd0 定位到viewDidLoad
00000001000066e4 adrp x10, #0x100008000
//同上
00000001000066e8 add x10, x10, #0xd08 ; 0x100008d08
00000001000066ec stur x0, [x29, #-0x8]
//把x0 存入 fp - 0x8
00000001000066f0 str x1, [sp, #0x10]
// 把x1 存入 sp + 0x10
00000001000066f4 ldur x0, [x29, #-0x8]
//把存在 fp - 0x8 的内容存入x0 ,这里可以看到和上面的操作是相对的,刚被存到sp + 0x10 ,又马上拿出来。如果编译器允许基本的编译优化,这些多余的指令会被删除。
00000001000066f8 str x0, sp
//把 x0 存入 sp
00000001000066fc ldr x10, x10
0000000100006700 str x10, [sp, #0x8]
// 把x10 存入 sp + 0x8
0000000100006704 ldr x1, x9
// 把x9 存入x1 ,x9 就是viewDidload 的地址
0000000100006708 mov x0, x8
// 把 x8 存入 x0
000000010000670c bl imp___stubs__objc_msgSendSuper2
// 执行 [super viewDidLoad]
0000000100006710 adrp x8, #0x100008000
// 把当前pc的值后12bit 设为0,并把0x100008000 << 12,然后把两者相加存入x8.
0000000100006714 add x8, x8, #0xcd8 ; @selector(launchUCBrowser)
// x8 = x8 + 0xcd8 ,定位到launchUCBrowser
0000000100006718 ldur x9, [x29, #-0x8]
// 把x29 - 0x8 存入x9
000000010000671c ldr x1, x8
//把x8 存入 x1
0000000100006720 mov x0, x9
// 把 x9 存入x0
0000000100006724 bl imp___stubs__objc_msgSend
0000000100006728 ldp x29, x30, [sp, #0x20]
//从sp + 0x20 取数据,存入 fp 和sp . (ldp/stp 从栈取/存数据)
000000010000672c add sp, sp, #0x30
//sp = sp + 0x30
0000000100006730 ret
-[ViewController launchUCBrowser]:
0000000100006784 sub sp, sp, #0x20 ; Objective C Implementation defined at 0x100008128 (instance method), DATA XREF=0x100008128
// sp = sp - 0x20
0000000100006788 stp x29, x30, [sp, #0x10]
// 把 fp 和 sp 存入 sp + 0x10 ,并且 sp = sp + 0x10
000000010000678c add x29, sp, #0x10
// fp = sp + 0x10
0000000100006790 adrp x8, #0x100008000
//把当前pc的值后12bit 设为0,并把0x100008000 << 12,然后把两者相加存入x8
0000000100006794 add x8, x8, #0xce8 ; @selector(clickSearchBar)
//x8 = x8 + 0xcd8 ,定位到launchUCBrowser
0000000100006798 str x0, [sp, #0x8]
// 把x0 存入 sp + 0x8
000000010000679c str x1, sp
// 把 x1 存入 sp
00000001000067a0 ldr x0, [sp, #0x8]
// 把 sp + 0x8 存入 x0
00000001000067a4 ldr x1, x8
// 把 x8 存入 x1
00000001000067a8 bl imp___stubs__objc_msgSend
00000001000067ac ldp x29, x30, [sp, #0x10]
//从sp + 0x20 取数据,存入 fp 和sp . (ldp/stp 从栈取/存数据)
00000001000067b0 add sp, sp, #0x20
00000001000067b4 ret
; endp
; ================ B E G I N N I N G O F P R O C E D U R E ================
-[ViewController clickSearchBar]:
00000001000067b8 sub sp, sp, #0x20 ; Objective C Implementation defined at 0x100008140 (instance method), DATA XREF=0x100008140
00000001000067bc stp x29, x30, [sp, #0x10]
00000001000067c0 add x29, sp, #0x10
00000001000067c4 adrp x8, #0x100008000
00000001000067c8 add x8, x8, #0xcf0 ; @selector(typing)
00000001000067cc str x0, [sp, #0x8]
00000001000067d0 str x1, sp
00000001000067d4 ldr x0, [sp, #0x8]
00000001000067d8 ldr x1, x8
00000001000067dc bl imp___stubs__objc_msgSend
00000001000067e0 ldp x29, x30, [sp, #0x10]
00000001000067e4 add sp, sp, #0x20
00000001000067e8 ret
; endp
; ================ B E G I N N I N G O F P R O C E D U R E ================
-[ViewController typing]:
00000001000067ec sub sp, sp, #0x20 ; Objective C Implementation defined at 0x100008158 (instance method), DATA XREF=0x100008158
00000001000067f0 stp x29, x30, [sp, #0x10]
00000001000067f4 add x29, sp, #0x10
00000001000067f8 str x0, [sp, #0x8]
00000001000067fc str x1, sp
0000000100006800 adrp x0, #0x100008000 ; argument #1 for method imp___stubs__NSLog
0000000100006804 add x0, x0, #0x60 ; @"Hello world"
0000000100006808 bl imp___stubs__NSLog
000000010000680c ldp x29, x30, [sp, #0x10]
0000000100006810 add sp, sp, #0x20
0000000100006814 ret
不知道大家有没有发现,
function name | assembly | range |
---|---|---|
viewDidLoad | x29, x30, [sp, #0x20] | 0x000000016fd25f70 - 0x000000016fd25f50 = 0x20 |
launchUCBrowser | x29, x30, [sp, #0x10] | 0x000000016fd25f40 - 0x000000016fd25f30 = 0x10 |
压栈的范围是有调用函数的frame 决定的。通过fp 和sp ,就可以得出所有函数的调用顺序。
To be continue