【问题标题】:popping from the stack without having pushed从堆栈中弹出而不被推送
【发布时间】:2011-08-24 11:54:16
【问题描述】:

我目前正在学习高级汇编语言,并且一直在研究堆栈的概念。我想我理解得相当好,但是在实践中我有一些问题。

堆栈向下增长,ESP 寄存器始终指向堆栈顶部..低内存中的地址。如果有东西被压入堆栈,那么 ESP 应该被递减。

EBP 用作帧指针,据我了解,应该始终超过 ESP。

但是,使用以下程序:

stdout.put(esp, nl);
stdout.put(ebp, nl);
push(ike);
stdout.put(esp, nl);
stdout.put(ebp, nl);
push(ike);
stdout.put(esp, nl);
stdout.put(ebp, nl);
pop(eax);
pop(eax);
pop(eax);
pop(eax);
stdout.put(esp, nl);
stdout.put(ebp, nl);

似乎并非如此。查看输出:

0018FF6C 0018FF70

0018FF68 0018FF70

0018FF64 0018FF70

0018FF74 0018FF70

EBP 始终相同,第一次推送时 ESP 递减 4 个字节,第二次推送时再递减 4 个字节。

在这之后我很困惑。在我的前 2 次弹出后,ESP 应该回到它开始的地方。如果我没有将任何东西压入堆栈,我怎么能再做两次弹出?我在弹出什么?

从 EAX 中进一步弹出和打印会显示一些数字,然后是 0,然后是更多数字。所以,我肯定会弹出一些东西......但是什么?它属于我的程序内存的哪一部分,为什么没有任何影响?

为什么 EBP 完全没有受到影响?

另外,为什么 ESP 减少 4 个字节,而不是减少 8 个字节?

如果有人能帮助我理解这一点,我将不胜感激。

【问题讨论】:

    标签: assembly stack


    【解决方案1】:

    EBP 不会被推送/弹出指令修改,它是手动设置的,因此除非您自己更改它,否则它将保持不变。

    IKE 的推送导致 4 字节的变化,因此显然您在这里处于 32 位模式。

    EAX 的 4 次弹出(32 位)将导致 16 字节 (10h) 的变化,就像它们一样。

    不确定问题出在哪里。似乎按我的预期工作?

    【讨论】:

    • 我毫不怀疑它按预期工作,只是我不明白。我认为虽然 EBP 是手动设置的......,如果没有这样做,它有时也会在内部使用?我原以为 ESP 会一次递减一个字节,而不是一次递减 32 位?
    • 我的主要问题是为什么我可以继续从堆栈中弹出我没有推送的东西。本质上,如果我从我的程序中删除推送指令,为什么我可以在没有推送的情况下弹出?我在弹出什么?我将堆栈弹出到 EAX 中,但我不确定我从堆栈中取出了什么。
    • 关于 EBP 的进一步问题...我认为它始终是堆栈的一部分,在返回地址之前...
    • EBP 只是一个寄存器。通常用于索引堆栈上的项目(由于标准约定)。如果你真的想要,你可以用它来做其他事情。没什么神奇的。如果您正在推送/弹出 32 位项目,ESP 将更改 32 位。它是一个指针,而不是一个项目计数器。它必须指向内存的正确部分,因此它必须根据操作数的大小而改变。处理器非常乐意弹出,直到它不再弹出为止。 Pop 基本上只是一个 MOV 然后 ADD ESP,操作数大小(以字节为单位)。实模式,它会换行。受保护的最终可能会出现段错误...
    • 所以使用 EBP 作为帧指针只是惯例,并且总是手动完成 - 从来没有在内部完成?我的理解是,如果您使用高级语言进行编码并进行反汇编,您会看到使用了 EBP。我知道处理器很高兴弹出……但是弹出的是什么?来自其他程序的数据?来自操作系统内存空间?
    【解决方案2】:

    除了你 push 和 pop 的东西之外,堆栈还用于保存每个函数的堆栈帧(即局部变量)、返回地址、旧的 ebp(特别是当前 ebp 所在的位置)和函数的参数。因此,您弹出的是函数的堆栈框架。
    如果你反汇编一个程序,你会看到:

    push    param3          ; suppose func takes 3 parameters, they're
    push    param2          ; pushed in reversed order (C-style)
    push    param1
    call    func            ; call pushes also the return address, i.e. the
                            ; address of the instrucion after the call
    
    ...
    
    func:
    push   ebp              ; this is done for preserving the caller's stack frame
    mov    ebp, esp         ; now we set up the beginning of func's stack frame
    sub    esp, smth.       ; and its width, enough to fit all func's variables
    

    此时堆栈将类似于:

            00000058:    arg3
            00000054:    arg2
            00000050:    arg1
            0000004c:    return address
     ebp -> 00000048:    caller's ebp
            ...          ...               -
            00000034:    random stuff       |  func's stack frame, random because they
            00000030:    random stuff       |  are uninitialized
     esp -> 0000002c:    random stuff      - 
    

    最后,您每次只推送和弹出 4 个字节,因为这是您机器的字长,这样您就可以保存整个寄存器。一口气

    【讨论】:

      猜你喜欢
      • 2014-10-02
      • 1970-01-01
      • 1970-01-01
      • 2020-12-19
      • 2016-04-04
      • 1970-01-01
      • 2018-06-12
      • 2014-12-19
      • 1970-01-01
      相关资源
      最近更新 更多