美文网首页
汇编基础(四)函数&返回值

汇编基础(四)函数&返回值

作者: struggle3g | 来源:发表于2018-04-23 00:00 被阅读78次

bl和ret指令

bl标号
  • 将下一条指令的地址放入lr(x30)寄存器
  • 转到标号处执行指令

注意:当我们遇到bl指令的时候,是跳转指令,跳转之后我们是怎么回来的?怎么回到bl下面的指令?遇到bl后,CPU会将下一个指令的内存地址存到lr寄存器当中。
验证:

  • 随便写一个函数,在main中调用,run


  • lldb输入跳转指令进入函数内部,查看lr寄存器是否是上图bl下一条指令的内存地址


ret
  • 默认使用lr(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址!
ARM64平台的特色指令,它面向硬件做了优化处理apple文档写的

练习题:

.text
.global _Test1,_Test2
_Test1:
    mov x0,#0xaaaa
    mov x0,#0xcccc
    bl _Test2
    mov x0,#0xdddd
    ret

_Test2:
    mov x0,#0xbbbb
    mov x0,#0xffff
    ret

.text
.global _Test1,_Test2

_Test1:
    mov x0,#0xaaaa
    str x30,[sp,#-0x10]!
    bl _Test2
    mov x0,#0xcccc
    ldr x30,[sp],#0x10
    ret

_Test2:
    mov x0,#0xbbbb
    ret
答案
  • 一里面的习题会产生死循环,根据bl跳转之前会将下一条指令的地址付给lr寄存器,这样函数在调用完成 ret以后会跳到lr指向的指令。 出现了死循环
  • 二里面 str 首先开辟一个栈空间,来将ret跳转的指令地址存到栈里面,当bl执行完成以后,在从栈空间读出来存到lr寄存器当中,来跳过死循环。
  • x30就是lr寄存器

我们平时写的函数转换成汇编会变成什么?

int sum(int a, int b){
    return a + b;
}
int main (){
    sum(10,20);
    return 0;
}

汇编中

->  0x1049ba8ac <+0>:  sub    sp, sp, #0x10             ; =0x10 
    0x1049ba8b0 <+4>:  str    w0, [sp, #0xc]
    0x1049ba8b4 <+8>:  str    w1, [sp, #0x8]
    0x1049ba8b8 <+12>: ldr    w0, [sp, #0xc]
    0x1049ba8bc <+16>: ldr    w1, [sp, #0x8]
    0x1049ba8c0 <+20>: add    w0, w0, w1
    0x1049ba8c4 <+24>: add    sp, sp, #0x10             ; =0x10 
    0x1049ba8c8 <+28>: ret  

解读

  • sub sp, sp, #0x10 首先开辟栈空间向下移动16个字节
  • 后面的4句话将w0,w1的值存到栈空间,又从栈空间读到w0,w1,废话
  • add w0, w0, w1 计算两个值的和,最后返回的值是w0
  • add sp, sp, #0x10 因为函数使用完成,需要将该函数占用的栈空间清除,也就是栈平衡

练习题,自己写一个sum函数

int sumA(int a, int b);

int main(int argc, char * argv[]) {

    int b = sumA(10, 20);
    printf("%d",b);
    return 0;
  
}
  • 返回x0
.text
.global _sumA

_sumA:
    add x0,x0,x1
    ret
  • 返回x1
.text
.global _sumA

_sumA:
    add x1,x0,x1
    ret

验证
返回x0寄存器是正确的值,返回x1寄存器的值是10
那么:

通常情况下函数的返回值通常都放在x0 里面!!ARM64下,函数的参数是存放在X0到X7(W0到W7)这8个寄存器里面的.如果超过8个参数,就会入栈.

代码验证:

int map(int a, int b,int c, int d, int e,int f,int g, int h, int i,int j){
    
    return a+b+c+d+e+f+g+h+j+i+j;
}
int main(int argc, char * argv[]) {
    map(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    return 0;
}

汇编代码

0x1005c289c <+0>: sub sp, sp, #0x30 ; =0x30
0x1005c28a0 <+4>: stp x29, x30, [sp, #0x20]
0x1005c28a4 <+8>: add x29, sp, #0x20 ; =0x20
0x1005c28a8 <+12>: orr w8, wzr, #0x1
0x1005c28ac <+16>: orr w9, wzr, #0x2
0x1005c28b0 <+20>: orr w2, wzr, #0x3
0x1005c28b4 <+24>: orr w3, wzr, #0x4
0x1005c28b8 <+28>: mov w4, #0x5
0x1005c28bc <+32>: orr w5, wzr, #0x6
0x1005c28c0 <+36>: orr w6, wzr, #0x7
0x1005c28c4 <+40>: orr w7, wzr, #0x8
0x1005c28c8 <+44>: mov w10, #0x9
0x1005c28cc <+48>: mov w11, #0xa
0x1005c28d0 <+52>: stur wzr, [x29, #-0x4]
0x1005c28d4 <+56>: stur w0, [x29, #-0x8]
0x1005c28d8 <+60>: str x1, [sp, #0x10]
0x1005c28dc <+64>: mov x0, x8
0x1005c28e0 <+68>: mov x1, x9
0x1005c28e4 <+72>: str w10, [sp]
0x1005c28e8 <+76>: str w11, [sp, #0x4]
0x1005c28ec <+80>: bl 0x1005c280c ; map at main.m:21 bl之前是当前的准备工作,开辟栈空间,设置局部变量
0x1005c28f0 <+84>: mov w8, #0x0
0x1005c28f4 <+88>: str w0, [sp, #0xc]
0x1005c28f8 <+92>: mov x0, x8
0x1005c28fc <+96>: ldp x29, x30, [sp, #0x20]
0x1005c2900 <+100>: add sp, sp, #0x30 ; =0x30
0x1005c2904 <+104>: ret

代码分析:

  • sub sp, sp, #0x30 开辟空间,sp向下移动48个字节
  • stp x29, x30, [sp, #0x20] 向sp向上偏移32个字节,开始存储x29,x30. sp + 0x20 = x29 sp + 0x20 + 1 = x30
  • add x29, sp, #0x20 x29 = sp + 0x20
  • orr w8, wzr, #0x1 w8 = wzr | #0x1
  • orr w9, wzr, #0x2
  • orr w2, wzr, #0x3
  • orr w3, wzr, #0x4
  • mov w4, #0x5 w4 = #0x5
  • orr w5, wzr, #0x6
  • orr w6, wzr, #0x7
  • orr w7, wzr, #0x8
  • mov w10, #0x9
  • mov w11, #0xa 这以上不用解释可以类推 这几个赋值就是没有x0 x1,股么的x0,x1有用
  • stur wzr, [x29, #-0x4] 0x00000000 写入到内存 x29 - 0x4
  • stur w0, [x29, #-0x8] 正好8个字节放入到栈内存
  • str x1, [sp, #0x10] 将x1放入到栈内存
  • mov x0, x8 x0 = x8
  • mov x1, x9 x1 = x9
  • str w10, [sp] 将w10 放入到占内存中 sp
  • str w11, [sp, #0x4] 将w11 放入栈内存中 sp,+#0x4
    然后跳转到map函数里面
0x1005c280c <+0>:   sub    sp, sp, #0x30             ; =0x30 
    0x1005c2810 <+4>:   ldr    w8, [sp, #0x34]
    0x1005c2814 <+8>:   ldr    w9, [sp, #0x30]
    0x1005c2818 <+12>:  str    w0, [sp, #0x2c]
    0x1005c281c <+16>:  str    w1, [sp, #0x28]
    0x1005c2820 <+20>:  str    w2, [sp, #0x24]
    0x1005c2824 <+24>:  str    w3, [sp, #0x20]
    0x1005c2828 <+28>:  str    w4, [sp, #0x1c]
    0x1005c282c <+32>:  str    w5, [sp, #0x18]
    0x1005c2830 <+36>:  str    w6, [sp, #0x14]
    0x1005c2834 <+40>:  str    w7, [sp, #0x10]
    0x1005c2838 <+44>:  str    w9, [sp, #0xc]
    0x1005c283c <+48>:  str    w8, [sp, #0x8]
    0x1005c2840 <+52>:  ldr    w8, [sp, #0x2c]
    0x1005c2844 <+56>:  ldr    w9, [sp, #0x28]
    0x1005c2848 <+60>:  add    w8, w8, w9
    0x1005c284c <+64>:  ldr    w9, [sp, #0x24]
    0x1005c2850 <+68>:  add    w8, w8, w9
    0x1005c2854 <+72>:  ldr    w9, [sp, #0x20]
    0x1005c2858 <+76>:  add    w8, w8, w9
    0x1005c285c <+80>:  ldr    w9, [sp, #0x1c]
    0x1005c2860 <+84>:  add    w8, w8, w9
    0x1005c2864 <+88>:  ldr    w9, [sp, #0x18]
    0x1005c2868 <+92>:  add    w8, w8, w9
    0x1005c286c <+96>:  ldr    w9, [sp, #0x14]
    0x1005c2870 <+100>: add    w8, w8, w9
    0x1005c2874 <+104>: ldr    w9, [sp, #0x10]
    0x1005c2878 <+108>: add    w8, w8, w9
    0x1005c287c <+112>: ldr    w9, [sp, #0x8]
    0x1005c2880 <+116>: add    w8, w8, w9
    0x1005c2884 <+120>: ldr    w9, [sp, #0xc]
    0x1005c2888 <+124>: add    w8, w8, w9
    0x1005c288c <+128>: ldr    w9, [sp, #0x8]
    0x1005c2890 <+132>: add    w0, w8, w9
    0x1005c2894 <+136>: add    sp, sp, #0x30             ; =0x30 
    0x1005c2898 <+140>: ret    

这代码我就不分析,可以根据上面的分析进行类推,

  • 稍微解释下,参数分析,一般函数参数,如果不超过8个直接可以用寄存器搞定,如果超过8个需要存到栈空间
  • 到函数里面,汇编分析,如果如果函数参数超过8个,多余的会存到栈内存中,再将寄存器中的参数按照从低到高的顺序存储到内存当中跟上面多余的参数的内存是连续的,那么首先将栈内存当中按照从低到高的顺序去读取两个值,然后add相加给x8,最后得到的值在赋值给x0 返回

练习:

int sum(int a, int b){
    int c = 6;
    int d = 0; 
    return  a + b + c;
}

int main(int argc, char * argv[]) {
    sum(10,20);
    return 0;
}

汇编分配的栈空间

  sub  sp,sp,#0x10
int sum(int a, int b){
    int c = 6;
    int d = 0; 
    int f = 1;  
    return  a + b + c;
}

int main(int argc, char * argv[]) {
    sum(10,20);
    return 0;
}

汇编分配的栈空间

  sub  sp,sp,#0x20

结论
这个应该可以理解哦 ,因为ARM64是16位每次开辟空间都是16为基准,只能是16的倍数, int = 4字节,4* 4正好 4*5 = 20 需要再开辟一个16位的空间 也就是 sp + 0x20

练习

int sum(int a, int b){
    int c = 6;
    printf("%d",c);
    return  a + b + c;
}

int main(int argc, char * argv[]) {
    sum(10,20);
    return 0;
}

汇编分配的栈空间

  sub  sp,sp,#0x30

为什么会多呐? 因为嵌套函数必须保护寄存器x30,lr 回家的路

练习

int sum(int a, int b){
    
    return  a + b ;
}


int funcA(int a, int b){
    
    int d = sum(a, b);
    int e = sum(a, b);
    return e;
    
}
  0x102e62880 <+0>:  sub    sp, sp, #0x20             ; =0x20 
    0x102e62884 <+4>:  stp    x29, x30, [sp, #0x10]
    0x102e62888 <+8>:  add    x29, sp, #0x10            ; =0x10 
    0x102e6288c <+12>: stur   w0, [x29, #-0x4]
    0x102e62890 <+16>: str    w1, [sp, #0x8]
    0x102e62894 <+20>: ldur   w0, [x29, #-0x4]
    0x102e62898 <+24>: ldr    w1, [sp, #0x8]
    0x102e6289c <+28>: bl     0x102e62860               ; sum at main.m:14
    0x102e628a0 <+32>: str    w0, [sp, #0x4]
    0x102e628a4 <+36>: ldur   w0, [x29, #-0x4]
    0x102e628a8 <+40>: ldr    w1, [sp, #0x8]
    0x102e628ac <+44>: bl     0x102e62860               ; sum at main.m:14
    0x102e628b0 <+48>: str    w0, [sp]
    0x102e628b4 <+52>: ldr    w0, [sp]
    0x102e628b8 <+56>: ldp    x29, x30, [sp, #0x10]
    0x102e628bc <+60>: add    sp, sp, #0x20             ; =0x20 
    0x102e628c0 <+64>: ret    

分析

 - 开辟栈空间
- 保护回家的路
- 保存栈底
- 保存w0
- 保存w1
- 读取 w0
- 读取w1
- sum跳转
- 保存w0的值
- 读取a值到x0
- 读取b值到x1
- sum跳转
- 保存w0的的值搭配 栈
- 读取栈内存的值到w0
- 恢复x30的值 准备回家
- 平衡栈空间
- 回家

总结

bl指令: 跳转 将下一条执行的指令放入lr(x30)寄存器
ret指令:返回lr寄存器所保存的地址,执行代码
pc 寄存器指向马上要执行的代码地址
sp 指向了栈
函数调用会开辟一个栈空间,

  • 函数的局部变量、参数、寄存器的保护
  • 这个栈空间属于这个函数自己

函数中的参数放在哪里:
ARM64每一个寄存器都是64位的

  • x0 ~ x7(1.个数有关系;2.数据类型也有关系)
    • 放入栈
    • 或者如果放不下放入浮点、向量寄存器

代码只有一份,内存不只一份
函数嵌套调用:
| - A(开辟)-->B(开辟)--->A(开辟)
| - A<-->A 死的溢出 递归

扩展 小知识

[self viewDidLoad];
OC方法的调用 本质上是不是调用msgSend()
msgSend(self,@selector(viewDidLoad));
x0 x1 寄存器!!  

相关文章

  • 汇编基础(四)函数&返回值

    bl和ret指令 bl标号 将下一条指令的地址放入lr(x30)寄存器 转到标号处执行指令 注意:当我们遇到bl指...

  • <安全攻防之汇编基础>

    &关于汇编基础请点击 <汇编一> <汇编二> <汇编三> <汇编四> <汇编五> <汇编六> <汇编七> <汇编八...

  • iOS逆向:函数本质(下)

    本文主要是讲解函数的参数、返回值、局部变量在汇编中是如何存储,以及CPSR标志寄存器 函数的参数和返回值 arm6...

  • 学习 之 TypeScript -- 基础知识 【2】

    学习 之 TypeScript -- 基础知识 1、函数: 函数定义 1.1、无返回值 1.2、返回值类型 1....

  • AE表达式名词解释

    函数: - wiggle(freq, amp, octaves = 1, amp_mult = .5, t = t...

  • Python函数笔记

    定义函数 基础语法 如果没有写return语句,函数执行之后会返回None 函数的返回值 可以有多个返回值,但是多...

  • c语言逆向分析之函数1

    从汇编的角度去理解函数会让你更加的深刻。。。。。。 看这篇文章需要一定的c语言基础和汇编基础(能基本看懂简单的汇编...

  • C语言2-参数的传递与返回值

    C语言2-参数的传递与返回值 plus函数对应反汇编代码和意义 调用函数调用: 下面是call内部 堆栈没有变化 ...

  • 详解Python装饰器

    基础 注意:函数名、函数加括号(函数的返回值)可以被当做参数传递,也可以被当做返回值return 内部原理 把wr...

  • Python vi函数

    Python的 vi中 函数分为四种: 无参数,无返回值的函数 无参数,有返回值的函数 有参数,无返回值的函数 有...

网友评论

      本文标题:汇编基础(四)函数&返回值

      本文链接:https://www.haomeiwen.com/subject/qdnhlftx.html