• CGer_AJ
    2019-05-10
    这个很好 讲的很细 这两章我要反复看 并手动实践 感觉作者 把这个课讲的生动形象~这个绝对是最值的课程
    
     28
  • kdb_reboot
    2019-05-10
    倒数第二图比较好
    补充一下寄存器说明
    rbp - register base pointer (start of stack)
    rsp - register stack pointer (current location in stack, growing downwards)
    建议将图编号这样评论的时候也能有所指代

    作者回复: kdb_reboot,谢谢建议。这个建议不错,我麻烦编辑稍后加上。

     1
     25
  • 董某人
    2019-05-15
    老师,既然push rbp 的作用是把"main 函数栈帧的栈底地址,压到栈顶",那下一句mov rbp,rsp 又是把"栈顶地址复制给栈帧"。
    原本rbp 中存储的不就是main 函数栈帧地址,压到栈顶后rsp 中存储的不也是main 函数栈帧地址,mov 这句的作用究竟是什么呢?
     3
     12
  • JStFs
    2019-06-20
    “push rbp 就把之前调用函数,也就是 main 函数的栈帧的栈底地址,压到栈顶。”

    一直不明白为什么要把main的栈底压到栈顶?没有图很难理解
     3
     8
  • chengzise
    2019-05-10
    老师这里需要补冲一下,函数调用call指令时,(PC)指令地址寄存器会自动压栈,即返回地址压栈,函数返回ret指令时会自动弹栈,即返回地址赋值给PC寄存器,把之前。图片有显示压栈,没有文字说明,其他同学可以不太理解。

    作者回复: 👍,谢谢 chengzise 同学,谢谢补充。call 在调用的时候会做push eip的操作,而在ret的时候会做pop eip的操作。

    
     7
  • Better me
    2019-05-10
    push rbp;
    mov rbp rsp;
    老师,想问这两句是如何控制函数调用的

    作者回复: 这两个是在维护函数调用的栈帧。

    指令地址本身的压栈和出栈是在 call 和 ret 的部分进行的。
    你可以认为 call 的同时进行了一次 push rip 把PC寄存器里面的内容压栈了,而在 ret 的时候 pop 把这部分数据出栈写回到PC寄存器里面了。

    
     6
  • Akizuki
    2019-05-12
    function_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
     5
  • Allen
    2019-05-16
    int 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

    
     4
  • DreamItPossible
    2019-07-30
    首先,以函数P调用函数Q为例进行说明:
    程序栈里需要保存的信息有:
    - 函数P调用函数Q完成后的下一个指令的地址,即返回地址;
    - 如果函数Q的参数个数超过6个,则剩余的参数值需要保存在栈上;
    - 某些共用的寄存器值;
    - 为指针类型参数生成的地址信息;
    - 数组和结构体等复杂数据结构;
    其次,需要保存的信息其实可以反过来解答文章开头的问题:为什么需要程序栈?
    最后,总结一下:资源有限,即寄存器个数有限,需要结合栈来实现复杂的功能,比如函数调用等
    展开
    
     3
  • 小美
    2019-06-06
    老师 巨大数组为什么是分配在栈空间的呢?(java里面是分配到堆上的 c预约和java不同吗)

    作者回复: 如果是函数作用域内的临时变量,就是分配在栈上的啊。

    首先Java运行时候的JVM自己就是一个应用程序,和C编译出来的机器码就不一样。

    Java通过New出来的对象是在堆上,但是函数作用域里面的临时变量,以及对应的引用都是放在栈上的。

    
     3
  • Linuxer
    2019-05-10
    0x0000000000400508 <+0>: push %rbp
    0x0000000000400509 <+1>: mov %rsp,%rbp
    0x000000000040050c <+4>: sub $0x18,%rsp
    0x0000000000400510 <+8>: movl $0x1,-0x4(%rbp)
    0x0000000000400517 <+15>: movl $0x2,-0x8(%rbp)
    0x000000000040051e <+22>: mov -0x8(%rbp),%esi
    0x0000000000400521 <+25>: mov -0x4(%rbp),%eax
    0x0000000000400524 <+28>: movl $0x7,(%rsp)
    => 0x000000000040052b <+35>: mov $0x6,%r9d
     0x0000000000400531 <+41>: mov $0x5,%r8d
     0x0000000000400537 <+47>: mov $0x4,%ecx
     0x000000000040053c <+52>: mov $0x3,%edx
     0x0000000000400541 <+57>: mov %eax,%edi
     0x0000000000400543 <+59>: callq 0x4004cd <add>
     0x0000000000400548 <+64>: mov %eax,-0xc(%rbp)
     0x000000000040054b <+67>: mov $0x0,%eax
     0x0000000000400550 <+72>: leaveq
     0x0000000000400551 <+73>: retq
    (gdb) i r
    rcx 0x400560 4195680
    rdx 0x7fffffffe4e8 140737488348392
    rbp 0x7fffffffe3f0 0x7fffffffe3f0
    rsp 0x7fffffffe3d8 0x7fffffffe3d8
    r12 0x4003e0 4195296
    r13 0x7fffffffe4d0 140737488348368
    r14 0x0 0
    r15 0x0 0
    rip 0x40052b 0x40052b <main+35>
    eflags 0x216 [ PF AF IF ]
    cs 0x33 51
    ss 0x2b 43
    ds 0x0 0
    es 0x0 0
    fs 0x0 0
    gs 0x0 0
    (gdb) x/24x $rbp
    0x7fffffffe3f0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
    0x7fffffffe3f8: 0xd5 0x03 0xa3 0xf7 0xff 0x7f 0x00 0x00
    0x7fffffffe400: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
    (gdb) x/24x ($rbp-24)
    0x7fffffffe3d8: 0x07 0x00 0x00 0x00 0x00 0x00 0x00 0x00
    0x7fffffffe3e0: 0xd0 0xe4 0xff 0xff 0xff 0x7f 0x00 0x00
    0x7fffffffe3e8: 0x02 0x00 0x00 0x00 0x01 0x00 0x00 0x00
    从gdb的结果来看,保存了局部变量 调用函数的参数,但是这里不理解的是我的main方法里面只定义了三个局部变量,为什么要分配24字节呢? 加上传参的4字节应该16字节也够了
    展开

    作者回复: 6个还是8个以内的调用参数会放在寄存器内而不是stack frame里面。
    X64 下 stack 需要按 16 bytes 做 alignment 可能是导致你需要的空间变成 24 bytes 的原因。这里的 24 bytes 加上你的 8 bytes 的 rbp 正好是 32 bytes 能是16的倍数。
    可以看看这个 stackoverflow 的问题 https://stackoverflow.com/questions/40580914/why-more-space-on-the-stack-frame-is-reserved-than-is-needed-in-x86

    
     3
  • Spring
    2019-05-10
    call之后,原函数的bp就会赋值为sp,因此只要把bp压栈就好了,call之后再把之前压栈的bp出栈赋值给sp就好了。
    函数返回后会把返回值放到ax寄存器,如果有多个返回值的话就将返回值的内存地址放到ax中。
    因此call之后恢复回原函数还要保存bp和返回值。

    作者回复: 还需要考虑函数的调用参数传递哦。

    
     3
  • @我
    2019-05-10
    还要保护现场,保护现场的变量值也是存到系统栈里面的?多线程切换的话,是不是也要存到自己线程对应的栈里面?

    作者回复: 是存到寄存器或者栈里面的,如果寄存器里面存不下,就会放到栈帧里面去。

    多线程情况下,每个线程有自己的栈。但是线程切换是context switch,这个和函数调用并不相同。context switch通常要解决的是把当前现场中的其他信息保留下来,比如寄存器里面的内容等等,而不是做一次压栈。

    
     3
  • zlw
    2019-05-10
    ret接着出栈后的栈顶继续执行,是rbp还是rsp?rsp始终指向栈顶怎么做到的?

    作者回复: 其实是在call的时候会对PC寄存器进行压栈,做了一个push eip 的操作,而在ret的时候做了pop eip的操作。

    
     3
  • Captain perison
    2019-05-10
    老师,栈是按照线程进行区分的吗?那个线程都有各自对应的栈吗?

    作者回复: 每个线程都有一个自己的栈。

    
     3
  • once
    2019-08-28
    老师 call指令已经将pc寄存器里的下一个指令(add函数执行完的跳转地址)压栈了 那 add函数里面的 push rbp压的又是什么栈 还有把main函数从栈底压到栈顶这个是什么意思 没有图看了好几遍也懵懵的 help老师

    作者回复: 这两个是在维护函数调用的栈帧。

    指令地址本身的压栈和出栈是在 call 和 ret 的部分进行的。
    你可以认为 call 的同时进行了一次 push rip 把PC寄存器里面的内容压栈了,而在 ret 的时候 pop 把这部分数据出栈写回到PC寄存器里面了。

    这一部分的确是有不少同学表示写得不够清楚,我晚点看单独会在FAQ里面更详细地写一下这个过程。也再修订一下这一讲希望能讲解地更清楚一些。

    
     2
  • 秋天
    2019-05-27
    java程序应该不是那种分页的形式,在虚机起动的时候我们根据配置或者是起动参数指定需要的内存大小,应该是预先分配好一大段连续的内存供程序使用,所以在程序运行过程中如果超出啦,预分配大小的内存就会出现内存溢出的错误

    作者回复: java虚拟机其实是一个应用层的程序,java虚拟机的内部内存分配其实是在虚拟内存地址层面的分配。的确不涉及到操作系统和硬件层面的分页问题。

    
     2
  • 秋天
    2019-05-27
    现在有点模糊的是栈只是用来做函数调用,记录跳转地址的?它和寄存器的本质区别吗?这两者能给解释一下吗?谢谢!

    作者回复: 秋天同学你好,

    我们在这里先要分清楚 抽象概念 和 实际的硬件实现部分。

    寄存器 和 内存,是在硬件层面就是放在不同的位置,使用不同的物理硬件来实现的。

    而栈是一个抽象概念,实际是存放在内存里面的。栈是用来管理函数调用的“现场”的。确保函数调用完成后,还能回到调用者那里。

    
     2
  • 小猪
    2019-05-22
    老师,我觉得用goto就可以实现函数调用,起先跳转到函数,运行完,在用goto跳回来就行了

    作者回复: 小猪同学你好,

    那么被调用的函数运行完之后,怎么知道要跳回到哪一个地址呢?

     5
     2
  • 愤怒的虾干
    2019-05-21
    我记得在中断处理时,需要保护现场,将cs、ip寄存器压栈,保存Flags寄存器,清除中断标识,然后跳转到中断处理命令;推广开来方法调用也需要将cs、ip寄存器压栈,保存调用前Flags寄存器状态,清除Flags寄存器的标识。老师,是这样吗?

    作者回复: 愤怒的虾干同学你好,

    中断不是函数调用啊。不过中断的确会对cs,ip进行压栈,以及保存flags寄存器。但是函数调用虽然对于ip会进行压栈,但是并不需要取清理条件码寄存器,也就是flags寄存器的状态,也不需要对于cs进行压栈。

    
     2
我们在线,来聊聊吧