函数调用时的临时对象_iOS Crash:函数调用栈

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位的值。

95e62242d39575447beb45c898470a03.png

关于栈的注意点

  • 栈是从高地址向低地址生长的:

9031c678b50af6acbacf1dee1504b82b.png
  • 在栈上读写数据时是从低地址向高地址读写的:

6a7b645efafcfbb1f27836ac02fdfacb.png
  • 栈的总体分布:

aedad2a90e48a2e507ea585cc63b26f7.png

函数调用时栈的变化

以下面的函数调用为例:

#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个阶段:

3ee8b49b5752d1d5e46de9cc3943b112.png

上面的代码中,main函数调用了add函数,在调用前,需要把add函数的参数存放到寄存器中(w0和w1)。然后再调用add函数。

调用add函数时,系统会为add函数新建一帧(fp_add~sp_add),用于存储它的内部变量,同时保存main函数的fp和lr寄存器,用于add函数执行完后恢复main函数的栈帧。

add函数执行完成后,会恢复之前保存的fp和lr,系统会回到main函数刚才中断的地方继续往下执行。

通过这种机制,就实现了函数的层层调用,并且每一层都能使用自己的本地变量。

参考

iOS需要了解的ARM64汇编

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值