iOS内存-栈

初始化

线程是系统执行的最小单元,每个线程对象初始化时都会分配自己的栈空间储存函数执行过程中传递的参数和产生的变量 (当然上下文切换时,线程对象还会储存每个寄存器的值)

线程栈最关键的两个寄存器就是rbp和rsp(arm对应fp和sp),栈的特点,数据从高地址向低地址填充(push入栈)

rsp: 当前线程栈空间的栈顶,从栈地址到rsp地址的空间都是没使用过的
rbp: 当前线程函数栈使用的空间地址,即rsp到rbp这部分空间是该函数使用的空间(函数参数或者函数中的声明的变量)

以下是我之前写的创建线程的一个demo的一部分

  1. 创建线程栈

    1
    2
    3
    4
    var stack_size: vm_size_t = vm_page_size * 8
    var stack_address: vm_address_t = 0
    let ret1 = vm_allocate(mach_task_self_, &stack_address, stack_size, VM_MEMORY_STACK | VM_FLAGS_ANYWHERE)
    let ret1_1 = vm_protect(mach_task_self_, stack_address, stack_size, 1, VM_PROT_READ | VM_PROT_WRITE)

    vm_allocate创建一块虚拟内存区域,并设置为可读可写

  2. 设置线程对象寄存器变量rsp指向栈顶,因为stack_address是低地址,需要加上栈大小

    1
    2
    3
    4
    5
    // 6 是xnu中_STRUCT_X86_THREAD_STATE64结构的rbp变量偏移
    state_64[6] = UInt64(stack_address+stack_size) - 4*0x8

    // 7 是rsp变量偏移
    state_64[7] = UInt64(stack_address+stack_size) - 7*0x8

Demo

举个小函数调用栗子

1
2
3
4
5
6
func sum(a: Int, b: Int) -> Int {
return a+b
}

let result = sum(a: 1, b: 2)
result += 1

伪指令

1
2
3
4
5
6
7
8
9
10
11
12
13
0x100000 call sum_label
0x100004 add x0 #1

sum_label:
push rbp
mov rsp rbp
sub 0x40 rsp
mov x0 -0x8(%rbp)
mov x1 -0x16(%rbp)
add x0 x1
add 0x40 rsp
pop rbp
ret
  1. 执行到 call sum_label 指令时,会先将当前指令的后面一条指令地址0x100004储存到栈上,在进行跳转

  2. 执行到push rbp 指令,将main函数的栈帧进行入栈保存,此时rsp = rsp - 8

  3. mov rsp rbp, 设置sum函数的栈帧

  4. sub 0x40 rsp,rsp = rsp - 0x40, 给当前sum函数的参数和变量分配空间

  5. mov x0 -0x8(%rbp); mov x1 -0x16(%rbp) , 将参数a, b入栈,保存参数

  6. add x0 x1, a + b并将值保存到a(x0)中, 返回值储存在x0中

  7. add 0x40 rsp, 收回sum函数使用的栈空间

  8. pop rbp,rbp出栈,rbp = main函数的栈帧, rsp = rsp + 8

  9. ret,pc = *(rsp) 0x100004, rsp = rsp + 8(栈溢出攻击点或者泄露指令地址(0x100004),得到ASLR)

Swift语言和栈的关系