iOS/OSX Crash:崩溃日志报告
iOS/OSX Crash:异常类型
iOS/OSX Crash:捕捉异常
iOS Crash:函数调用栈
ARM64汇编入门知识可以参考:iOS需要了解的ARM64汇编
栈是由于函数运行而临时占用的内存区域,或者说栈是指令执行时存放临时变量的内存空间。一个函数对应一帧,FP指向当前帧的栈底,SP指向栈顶。
相关的寄存器
- x29(fp):Frame pointer,保存栈底地址。
- x30(lr):Link Register,保存子函数结束后需要执行的下一条指令地址。
- sp:Stack Pointer,保存栈顶地址。
- x0~x7(w0~w7):调用子函数时存放参数数据,然后子函数结束后一般把返回值存放在x0。
寄存器大小
当使用x0~x30访问时,它存储64位的值;当使用w0~w30访问时,它存储32位的值。
关于栈的注意点
- 栈是从高地址向低地址生长的:
- 在栈上读写数据时是从低地址向高地址读写的:
- 栈的总体分布:
函数调用时栈的变化
以下面的函数调用为例:
#include "stdio.h"
int add(int a, int b)
{
int c = a + b;
printf("%d", c);
return c;
}
int main(int argc, char * argv[]) {
int a = 4;
int b = 6;
int c = add(a, b);
return 0;
}
得到的汇编如下:
.section __TEXT,__text,regular,pure_instructions
.build_version ios, 14, 2 sdk_version 14, 2
.globl _add ; -- Begin function add
.p2align 2
_add: ; @add
.cfi_startproc
; %bb.0:
sub sp, sp, #48 ; sp上的数据-0x30放在sp上
stp x29, x30, [sp, #32] ; 把x29(fp)和x30(lr)上的数据放在sp+0x20上
add x29, sp, #32 ; 把sp上的数据+0x20放在x29(fp)上
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
stur w0, [x29, #-4] ; 把w0上的数据放在fp-0x4上
stur w1, [x29, #-8] ; 把w1上的数据放在fp-0x8上
ldur w8, [x29, #-4] ; 把fp-0x4上的数据放在w8上
ldur w9, [x29, #-8] ; 把fp-0x8上的数据放在w9上
add w8, w8, w9 ; 把w8和w9上的数据相加放在w8上
stur w8, [x29, #-12] ; 把w8上的数据放在fp-0xc上
ldur w8, [x29, #-12] ; 把fp-0xc上的数据放在w8上
; implicit-def: $x2
mov x2, x8 ; 把x8上的数据放在x2上
adrp x0, l_.str@PAGE
add x0, x0, l_.str@PAGEOFF
mov x10, sp
str x2, [x10]
bl _printf
ldur w8, [x29, #-12] ; 把fp-0xc上的数据放在w8上
mov x0, x8 ; 把x8上的数据放在x0上 把返回值写入x0
ldp x29, x30, [sp, #32] ; 把sp+0x20上的数据放在(x29)fp和x30(lr)上 恢复fp和lr
add sp, sp, #48 ; 把sp上的数据+0x30放在sp上 恢复sp
ret
.cfi_endproc
; -- End function
.globl _main ; -- Begin function main
.p2align 2
_main: ; @main
.cfi_startproc
; %bb.0:
;设置fp和sp,并存储老的fp和lr
sub sp, sp, #48 ; 把sp上的数据-0x30放在sp上
stp x29, x30, [sp, #32] ; 把x29(fp)和x30(lr)上的数据放在sp+0x20上
add x29, sp, #32 ; 把sp上的数据+0x20放在x29(fp)上
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
mov w8, #0 ; 把0放在w8上
stur wzr, [x29, #-4] ; 把wzr(零寄存器)上的数据放在x29(fp)-0x4上
stur w0, [x29, #-8] ; 把w0上的数据放在x29(fp)-0x8上
str x1, [sp, #16] ; 把x1上的数据放在sp+0x10上
mov w9, #4 ; 把4存放在w9上
str w9, [sp, #12] ; 把w9上的数据放在sp+0xc上
mov w9, #6 ; 把6存放在w9上
str w9, [sp, #8] ; 把w9上的数据放在sp+0x8上
ldr w0, [sp, #12] ; 把sp+0xc上的数据放在w0上
ldr w1, [sp, #8] ; 把sp+0x8上的数据放在w1上
str w8, [sp] ; 把w8上的数据放在sp上
bl _add ; 调用add函数
str w0, [sp, #4] ; 把w0上的数据放在sp+0x4上
ldr w0, [sp] ; 4-byte Folded Reload
ldp x29, x30, [sp, #32] ; 16-byte Folded Reload
add sp, sp, #48 ; =48
ret
.cfi_endproc
; -- End function
.section __TEXT,__cstring,cstring_literals
l_.str: ; @.str
.asciz "%d"
.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 64
.subsections_via_symbols
函数调用的过程可分为4个阶段:
上面的代码中,main函数调用了add函数,在调用前,需要把add函数的参数存放到寄存器中(w0和w1)。然后再调用add函数。
调用add函数时,系统会为add函数新建一帧(fp_add~sp_add),用于存储它的内部变量,同时保存main函数的fp和lr寄存器,用于add函数执行完后恢复main函数的栈帧。
add函数执行完成后,会恢复之前保存的fp和lr,系统会回到main函数刚才中断的地方继续往下执行。
通过这种机制,就实现了函数的层层调用,并且每一层都能使用自己的本地变量。
参考
iOS需要了解的ARM64汇编