07 | 函数调用:为什么会发生stack overflow?
07 | 函数调用:为什么会发生stack overflow?
讲述:徐文浩
时长12:08大小11.08M
为什么我们需要程序栈?
如何构造一个 stack overflow?
如何利用函数内联进行性能优化?
总结延伸
推荐阅读
课后思考
赞 100
提建议
精选留言(135)
- CGer_AJ2019-05-10这个很好 讲的很细 这两章我要反复看 并手动实践 感觉作者 把这个课讲的生动形象~这个绝对是最值的课程共 2 条评论80
- kdb_reboot2019-05-10倒数第二图比较好 补充一下寄存器说明 rbp - register base pointer (start of stack) rsp - register stack pointer (current location in stack, growing downwards) 建议将图编号这样评论的时候也能有所指代
作者回复: kdb_reboot,谢谢建议。这个建议不错,我麻烦编辑稍后加上。
共 7 条评论56 - chengzise2019-05-10老师这里需要补冲一下,函数调用call指令时,(PC)指令地址寄存器会自动压栈,即返回地址压栈,函数返回ret指令时会自动弹栈,即返回地址赋值给PC寄存器,把之前。图片有显示压栈,没有文字说明,其他同学可以不太理解。
作者回复: 👍,谢谢 chengzise 同学,谢谢补充。call 在调用的时候会做push eip的操作,而在ret的时候会做pop eip的操作。
共 2 条评论44 - Geek_dark2020-05-14https://manybutfinite.com/post/journey-to-the-stack/ 这篇文章将汇编指令操作栈的步骤一步步画出来了,大家可以看看,浅显易懂。共 5 条评论41
- 杰杰2020-06-23查了一下,个人理解。 call 就相当于 push rip 和 jmp 的结合。rip 是指令地址寄存器,也就是会将返回地址入栈,并跳转到相应函数处执行。而 ret 就相当于 pop rip 和 jmp 的结合。 rbp 和 rsp 用于维护当前栈帧,rbp 指向栈帧的栈底地址,rsp 指向栈顶地址。 push rbp 和 mov rbp, rsp 主要是为了从 main 函数栈帧调整为 add 函数栈帧。 push rbp 是将之前 main 函数栈帧的栈底地址入栈,之后的 pop rbp 就可以将该栈底地址出栈,从而又调整为 main 函数栈帧。 rsp 指向 main 函数栈帧的栈顶地址,也就是当前 add 函数栈帧的栈底地址。mov rbp, rsp 就是把 rbp 从 main 函数栈帧的栈底地址 调整为 add 函数栈帧的栈底地址。 在 push 时 rsp 会减小,在 pop 时 rsp 会增大,从而 rsp 始终指向栈顶位置。展开共 3 条评论29
- 秋天2019-05-27现在有点模糊的是栈只是用来做函数调用,记录跳转地址的?它和寄存器的本质区别吗?这两者能给解释一下吗?谢谢!
作者回复: 秋天同学你好, 我们在这里先要分清楚 抽象概念 和 实际的硬件实现部分。 寄存器 和 内存,是在硬件层面就是放在不同的位置,使用不同的物理硬件来实现的。 而栈是一个抽象概念,实际是存放在内存里面的。栈是用来管理函数调用的“现场”的。确保函数调用完成后,还能回到调用者那里。
27 - 董某人2019-05-15老师,既然push rbp 的作用是把"main 函数栈帧的栈底地址,压到栈顶",那下一句mov rbp,rsp 又是把"栈顶地址复制给栈帧"。 原本rbp 中存储的不就是main 函数栈帧地址,压到栈顶后rsp 中存储的不也是main 函数栈帧地址,mov 这句的作用究竟是什么呢?共 6 条评论26
- JStFs2019-06-20“push rbp 就把之前调用函数,也就是 main 函数的栈帧的栈底地址,压到栈顶。” 一直不明白为什么要把main的栈底压到栈顶?没有图很难理解共 13 条评论21
- Better me2019-05-10push rbp; mov rbp rsp; 老师,想问这两句是如何控制函数调用的
作者回复: 这两个是在维护函数调用的栈帧。 指令地址本身的压栈和出栈是在 call 和 ret 的部分进行的。 你可以认为 call 的同时进行了一次 push rip 把PC寄存器里面的内容压栈了,而在 ret 的时候 pop 把这部分数据出栈写回到PC寄存器里面了。
共 5 条评论19 - 蚂蚁内推+v2019-06-06老师 巨大数组为什么是分配在栈空间的呢?(java里面是分配到堆上的 c预约和java不同吗)
作者回复: 如果是函数作用域内的临时变量,就是分配在栈上的啊。 首先Java运行时候的JVM自己就是一个应用程序,和C编译出来的机器码就不一样。 Java通过New出来的对象是在堆上,但是函数作用域里面的临时变量,以及对应的引用都是放在栈上的。
共 2 条评论17 - Allen2019-05-16int main() { d: 55 push ebp e: 89 e5 mov ebp,esp 10: 83 ec 18 sub esp,0x18 int x = 5; 13: c7 45 f4 05 00 00 00 mov DWORD PTR [ebp-0xc],0x5 int y = 10; 1a: c7 45 f8 0a 00 00 00 mov DWORD PTR [ebp-0x8],0xa 老师,请教下: sub esp,0x18 的目的是干什么? 0x18 是怎么计算的?展开
作者回复: 这个是在维护栈帧,因为后面有两个临时变量需要在调用其他函数之前保留到栈里面。0x18是16进制的24 两个int各需要8 bit,一共16bit,然后ebp本来就要8bit,一共只有24bit,考虑对齐到16bit的整数倍还要额外的8bit一共24bit
共 2 条评论15 - DreamItPossible2019-07-30首先,以函数P调用函数Q为例进行说明: 程序栈里需要保存的信息有: - 函数P调用函数Q完成后的下一个指令的地址,即返回地址; - 如果函数Q的参数个数超过6个,则剩余的参数值需要保存在栈上; - 某些共用的寄存器值; - 为指针类型参数生成的地址信息; - 数组和结构体等复杂数据结构; 其次,需要保存的信息其实可以反过来解答文章开头的问题:为什么需要程序栈? 最后,总结一下:资源有限,即寄存器个数有限,需要结合栈来实现复杂的功能,比如函数调用等展开13
- once2019-08-28老师 call指令已经将pc寄存器里的下一个指令(add函数执行完的跳转地址)压栈了 那 add函数里面的 push rbp压的又是什么栈 还有把main函数从栈底压到栈顶这个是什么意思 没有图看了好几遍也懵懵的 help老师
作者回复: 这两个是在维护函数调用的栈帧。 指令地址本身的压栈和出栈是在 call 和 ret 的部分进行的。 你可以认为 call 的同时进行了一次 push rip 把PC寄存器里面的内容压栈了,而在 ret 的时候 pop 把这部分数据出栈写回到PC寄存器里面了。 这一部分的确是有不少同学表示写得不够清楚,我晚点看单独会在FAQ里面更详细地写一下这个过程。也再修订一下这一讲希望能讲解地更清楚一些。
共 4 条评论12 - 小猪2019-05-22老师,我觉得用goto就可以实现函数调用,起先跳转到函数,运行完,在用goto跳回来就行了
作者回复: 小猪同学你好, 那么被调用的函数运行完之后,怎么知道要跳回到哪一个地址呢?
共 7 条评论9 - Akizuki2019-05-12function_example.c 反汇编结果: int u = add(x, y); 2a: 8b 55 f8 mov edx, DWORD PTR [rbp-0x8] 2d: 8b 45 fc mov eax, DWORD PTR [rbp-0x4] 30: 89 d6 mov esi, edx 32: 89 c7 mov edi, eax 老师,这里为什么没有编译成: mov esi, DWORD PTR [rbp-0x8] mov edi, DWORD PTR [rbp-0x4] 谢谢~展开共 1 条评论9
- 秋天2019-05-27java程序应该不是那种分页的形式,在虚机起动的时候我们根据配置或者是起动参数指定需要的内存大小,应该是预先分配好一大段连续的内存供程序使用,所以在程序运行过程中如果超出啦,预分配大小的内存就会出现内存溢出的错误
作者回复: java虚拟机其实是一个应用层的程序,java虚拟机的内部内存分配其实是在虚拟内存地址层面的分配。的确不涉及到操作系统和硬件层面的分页问题。
8 - Alphalin2019-05-15请问地址34后面的地址怎么直接到39了? 35地址在哪呢
作者回复: 34的整个指令有长度啊,你数一数这条指令对应的机器码需要多少空间呢?
共 2 条评论7 - 曾立涵2020-02-17大家可以结合这篇文章看一下,带图的好理解一些https://blog.csdn.net/fistraiser/article/details/802704736
- tlseek2019-11-03说一下个人理解,函数调用的栈是以栈帧单位,调用一个函数就会出现一个栈帧。但汇编指令的push和pop的操作对象并不是栈帧,而是rsp(栈顶指针)。要记住任何对栈操作的指令都会使rsp移动。 push rbp是指将rbp的内容(前一个函数的栈基地址)压入栈中,rsp向前移一位。 此时rsp指向的当前函数调用的栈基地址,而栈基地址所存放的内容是前一个函数调用的栈基地址。 mov rbp, rsp 是将当前函数调用栈基地址设置到rbp中,便于当前函数后续的指令使用。 pop rbp 指将rbp所指向的内存的内容(前一个函数栈基地址)移出栈(rsp向后移一位),并将此内容存入rbp中。这时rbp就指向前一个函数的栈基了。展开5
- 楼外楼2019-07-15ESP/RSP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。(64位机器变为RSP) EBP/RBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。(64位机器变为RBP)5