首页 文章

什么是基本指针和堆栈指针?他们指出了什么?

提问于
浏览
196

使用来自维基百科的this example,其中DrawSquare()调用DrawLine(),

alt text

(请注意,此图表底部有高地址,顶部有低地址 . )

谁能解释一下 ebpesp 在这种情况下是什么?

从我看到的,我会说堆栈指针总是指向堆栈的顶部,而指针指向当前函数的开头?或者是什么?


编辑:我的意思是在Windows程序的上下文中

edit2: And how does eip work, too?

edit3: 我有来自MSVC的以下代码:

var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
hInstance= dword ptr  8
hPrevInstance= dword ptr  0Ch
lpCmdLine= dword ptr  10h
nShowCmd= dword ptr  14h

所有这些似乎都是dwords,因此每个都占用4个字节 . 所以我可以看到从hInstance到4个字节的var_4之间存在差距 . 这些是什么?我假设它是返回地址,可以在维基百科的图片中看到?


(编者注:删除了迈克尔答案的长引用,该答案不属于问题,但后续问题已编辑):

这是因为函数调用的流程是:

* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals

My question (last, i hope!) now is, what is exactly what happens from the instant I pop the arguments of the function i want to call up to the end of the prolog? I want to know how the ebp, esp evolve during those moments(I already understood how the prolog works, I just want to know what is happening after i pushed the arguments on the stack and before the prolog).

8 回答

  • 1

    esp 就像你说的那样,是堆栈的顶部 .

    ebp 通常在函数开头设置为 esp . 通过分别从 ebp 加上和减去一个常量偏移来访问函数参数和局部变量 . 所有x86调用约定都将 ebp 定义为跨函数调用保留 . ebp 本身实际指向前一帧的基指针,它使堆栈在调试器中行走并查看其他帧局部变量 .

    大多数功能序言看起来像:

    push ebp      ; Preserve current frame pointer
    mov ebp, esp  ; Create new frame pointer pointing to current stack top
    sub esp, 20   ; allocate 20 bytes worth of locals on stack.
    

    然后在函数中你可能有代码(假设两个局部变量都是4个字节)

    mov [ebp-4], eax    ; Store eax in first local
    mov ebx, [ebp - 8]  ; Load ebx from second local
    

    您可以启用的FPO或帧指针省略优化实际上会消除这种情况并使用 ebp 作为另一个寄存器并直接从 esp 访问本地,但这使调试变得更加困难,因为调试器无法再直接访问早期函数的堆栈帧调用 .

    编辑:

    对于您更新的问题,堆栈中缺少的两个条目是:

    var_C = dword ptr -0Ch
    var_8 = dword ptr -8
    var_4 = dword ptr -4
    *savedFramePointer = dword ptr 0*
    *return address = dword ptr 4*
    hInstance = dword ptr  8h
    PrevInstance = dword ptr  0C
    hlpCmdLine = dword ptr  10h
    nShowCmd = dword ptr  14h
    

    这是因为函数调用的流程是:

    • 推送参数( hInstance 等)

    • 调用函数,用于推送返回地址

    • ebp

    • 为当地人分配空间

  • 6

    ESP是当前的堆栈指针,无论何时将字或地址推入或弹出堆栈,它都会发生变化 . 与直接使用ESP相比,EBP是编译器跟踪函数参数和局部变量的更方便的方法 .

    通常(并且这可能因编译器而异),被调用函数的所有参数都被压入堆栈(通常按照它们在函数原型中声明的相反顺序,但这会有所不同) . 然后调用该函数,将返回地址(EIP)压入堆栈 .

    进入函数后,旧的EBP值被压入堆栈,EBP被设置为ESP的值 . 然后ESP递减(因为堆栈在内存中向下增长)为函数's local variables and temporaries. From that point on, during the execution of the function, the arguments to the function are located on the stack at positive offsets from EBP (because they were pushed prior to the function call), and the local variables are located at negative offsets from EBP (because they were allocated on the stack after the function entry). That'分配空间,因为它指向function call frame的中心 .

    退出时,所有函数必须做的是将ESP设置为EBP的值(从堆栈中释放局部变量,并在堆栈顶部公开条目EBP),然后从堆栈中弹出旧的EBP值,然后函数返回(将返回地址弹出到EIP中) .

  • 68

    你没事 . 堆栈指针指向堆栈上的顶部项目,并且在调用函数之前,基本指针指向堆栈的"previous"顶部 .

    调用函数时,任何局部变量都将存储在堆栈中,堆栈指针将递增 . 从函数返回时,堆栈上的所有局部变量都超出范围 . 您可以通过将堆栈指针设置回基指针(函数调用之前的“上一个”顶部)来完成此操作 .

    以这种方式进行内存分配是非常非常快速和高效的 .

  • -3

    EDIT: 有关更好的描述,请参阅WikiBook中有关x86程序集的x86 Disassembly/Functions and Stack Frames . 我尝试使用Visual Studio添加您可能感兴趣的一些信息 .

    将调用者EBP存储为第一个局部变量称为标准堆栈帧,这可用于Windows上的几乎所有调用约定 . 无论调用者还是被调用者都释放传递的参数,以及哪些参数在寄存器中传递,但这些参数与标准堆栈帧问题正交,则存在差异 .

    谈到Windows程序,您可能使用Visual Studio来编译C代码 . 请注意,Microsoft使用称为帧指针省略的优化,这使得几乎不可能在不使用dbghlp库和PDB文件执行可执行文件的情况下遍历堆栈 .

    此帧指针省略表示编译器不存储旧的EBP位于标准位置并使用EBP寄存器进行其他操作,因此您很难找到调用者EIP,而不知道局部变量对给定函数需要多少空间 . 当然,Microsoft提供的API允许您在这种情况下进行堆栈遍历,但在PDB文件中查找符号表数据库对于某些用例来说需要太长时间 .

    要避免编译单元中的FPO,您需要避免使用/ O2或者需要在项目中明确地添加/ Oy-到C编译标志 . 您可能链接到C或C运行时,它在Release配置中使用FPO,因此您将很难在没有dbghlp.dll的情况下进行堆栈遍历 .

  • 5

    首先,堆栈指针指向堆栈的底部,因为x86堆栈从高地址值构建到较低的地址值 . 堆栈指针是下一次推送(或调用)调用将放置下一个值的点 . 它的操作等同于C / C语句:

    // push eax
     --*esp = eax
     // pop eax
     eax = *esp++;
    
     // a function call, in this case, the caller must clean up the function parameters
     move eax,some value
     push eax
     call some address  // this pushes the next value of the instruction pointer onto the
                        // stack and changes the instruction pointer to "some address"
     add esp,4 // remove eax from the stack
    
     // a function
     push ebp // save the old stack frame
     move ebp, esp
     ... // do stuff
     pop ebp  // restore the old stack frame
     ret
    

    基指针位于当前帧的顶部 . ebp通常指向您的退货地址 . ebp 4指向函数的第一个参数(或类方法的this值) . ebp-4指向函数的第一个局部变量,通常是ebp的旧值,因此可以恢复先前的帧指针 .

  • -4

    自从我完成汇编编程以来,很长一段时间,但this link可能会有用......

    处理器具有一组寄存器,用于存储数据 . 其中一些是直接值,而另一些则指向RAM中的一个区域 . 寄存器确实倾向于用于某些特定的操作,并且汇编中的每个操作数都需要特定寄存器中的一定数量的数据 .

    当您调用其他过程时,主要使用堆栈指针 . 使用现代编译器,一堆数据将首先被堆积在堆栈中,然后是返回地址,因此系统将在知道返回后知道返回的位置 . 堆栈指针将指向下一个可以将新数据推送到堆栈的位置,它将保持不变,直到它再次弹回 .

    基址寄存器或段寄存器只指向大量数据的地址空间 . 结合第二个注册器,Base指针将把内存分成大块,而第二个寄存器将指向该块中的一个项目 . 因此,基指针指向数据块的基础 .

    请记住,Assembly是非常特定于CPU的 . 我链接的页面提供了有关不同类型CPU的信息 .

  • 204

    编辑是的,这大多是错的 . 它描述了一些完全不同的东西,如果有人有兴趣:)

    是的,堆栈指针指向堆栈的顶部(无论是第一个空堆栈位置还是最后一个我不确定的堆栈位置) . 基指针指向正在执行的指令的内存位置 . 这是在操作码级别上 - 您可以在计算机上获得的最基本的指令 . 每个操作码及其参数都存储在存储器位置 . 一个C或C或C#行可以转换为一个操作码,或者两个或更多的序列,这取决于它的复杂程度 . 它们按顺序写入程序存储器并执行 . 在正常情况下,基指针递增一条指令 . 对于程序控制(GOTO,IF等),它可以多次递增或仅用下一个存储器地址替换 .

    在这种情况下,函数存储在某个地址的程序存储器中 . 当调用该函数时,某些信息被推送到堆栈上,让程序发现它已经回到调用函数的位置以及函数的参数,然后程序存储器中函数的地址被推入到基指针 . 在下一个时钟周期,计算机开始执行该存储器地址的指令 . 然后在某些时候它会在调用该函数的指令之后返回到存储器位置并从那里继续 .

  • 13

    esp代表“扩展堆栈指针”..... ebp代表“Something Base Pointer”....并且eip代表“Something Instruction Pointer”......堆栈指针指向堆栈段的偏移地址 . Base Pointer指向额外段的偏移地址 . 指令指针指向代码段的偏移地址 . 现在,关于段......它们是处理器存储区的64KB小部分......这个过程称为存储器分段 . 我希望这篇文章很有帮助 .

相关问题