ChatGPT解决这个技术问题 Extra ChatGPT

What is exactly the base pointer and stack pointer? To what do they point?

Using this example coming from wikipedia, in which DrawSquare() calls DrawLine(),

https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/Call_stack_layout.svg/342px-Call_stack_layout.svg.png

(Note that this diagram has high addresses at the bottom and low addresses at the top.)

Could anyone explain me what ebp and esp are in this context?

From what I see, I'd say the stack pointer points always to the top of the stack, and the base pointer to the beginning of the the current function? Or what?

edit: I mean this in the context of windows programs

edit2: And how does eip work, too?

edit3: I have the following code from 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

All of them seem to be dwords, thus taking 4 bytes each. So I can see there is a gap from hInstance to var_4 of 4 bytes. What are they? I assume it is the return address, as can be seen in wikipedia's picture?

(editor's note: removed a long quote from Michael's answer, which doesn't belong in the question, but a followup question was edited in):

This is because the flow of the function call is:

* 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).

One important thing to note is that the stack grows "downwards" in memory. This means that to move the stack pointer upward you decrease its value.
One hint to differentiate what EBP/ESP and EIP are doing: EBP & ESP deal with data, while EIP deals with code.
In your graph, ebp (usually) is the "frame pointer", esp the "stack pointer". This allows to access locals via [ebp-x] and stack parameters via [ebp+x] consistently, independent of the stack pointer (which frequently changes within a function). Adressing could be done through ESP, freeing up EBP for other operations - but that way, debuggers can't tell call stack or values of locals.
@Ben. Not nesacerily. Some compilers put stack frames into the heap. The concept of stack growing down is just that, a concept that makes it easy to understand. The implementation of the stack can be anything (using random chunks of the heap makes hacks that overwrite parts of the stack a lot harder as they are not as deterministic).
in two words: stack pointer allow push/pop operations to work (so push and pop knows where to put/get data). base pointer allows code to independently reference data that have been pushed previously on the stack.

ネロク

esp is as you say it is, the top of the stack.

ebp is usually set to esp at the start of the function. Function parameters and local variables are accessed by adding and subtracting, respectively, a constant offset from ebp. All x86 calling conventions define ebp as being preserved across function calls. ebp itself actually points to the previous frame's base pointer, which enables stack walking in a debugger and viewing other frames local variables to work.

Most function prologs look something like:

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.

Then later in the function you may have code like (presuming both local variables are 4 bytes)

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

FPO or frame pointer omission optimization which you can enable will actually eliminate this and use ebp as another register and access locals directly off of esp, but this makes debugging a bit more difficult since the debugger can no longer directly access the stack frames of earlier function calls.

EDIT:

For your updated question, the missing two entries in the stack are:

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

This is because the flow of the function call is:

Push parameters (hInstance, etc.)

Call function, which pushes return address

Push ebp

Allocate space for locals


Thanks for the explanation! But I am now kinda confused. Let's assume I call a function and I am in the first line of its prolog, still without having executed a single line from it. At that point, what is ebp's value? Does the stack have anything at that point besides the pushed arguments? Thanks!
EBP is not magically changed, so until you've established a new EBP for your function you'll still have the callers value. And besides arguments, the stack will also hold the old EIP (return address)
Nice answer. Though it cannot be complete without mentioning what's in the epilog: "leave" and "ret" instructions.
I think this image will help clarify some things on what the flow is. Also keep in mind the stack grows downwards. ocw.cs.pub.ro/courses/_media/so/laboratoare/call_stack.png
Is it me, or all the minus signs are missing from the code snippet above?
A
Anis LOUNIS aka AnixPasBesoin

ESP is the current stack pointer, which will change any time a word or address is pushed or popped onto/off off the stack. EBP is a more convenient way for the compiler to keep track of a function's parameters and local variables than using the ESP directly.

Generally (and this may vary from compiler to compiler), all of the arguments to a function being called are pushed onto the stack by the calling function (usually in the reverse order that they're declared in the function prototype, but this varies). Then the function is called, which pushes the return address (EIP) onto the stack.

Upon entry to the function, the old EBP value is pushed onto the stack and EBP is set to the value of ESP. Then the ESP is decremented (because the stack grows downward in memory) to allocate space for the function'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's why the EBP is called the Frame Pointer, because it points to the center of the function call frame.

Upon exit, all the function has to do is set ESP to the value of EBP (which deallocates the local variables from the stack, and exposes the entry EBP on the top of the stack), then pop the old EBP value from the stack, and then the function returns (popping the return address into EIP).

Upon returning back to the calling function, it can then increment ESP in order to remove the function arguments it pushed onto the stack just prior to calling the other function. At this point, the stack is back in the same state it was in prior to invoking the called function.


So far the best explanation i read about esp and ebp.
One thing that might be misleading for asm new comers is that the BP in EBP stands for Base Pointer, which is not really the case, since EBP points to the center the stack frame, NOT the bottom. The Frame Pointer feels like a better name for this register.
Some CPUs actually have a register called the "Frame Pointer", such as the Digital Alpha CPU.
I thought the ESP does not change and remains constant throughout the programs lifetime as it simply signifies the start of the stack (top)?
@EpicSpeedy No, ESP points to the last item you pushed onto the stack (or the free space on top of it, can't remember which). Either way, it changes as you push/pop registers, or call functions/return from them.
R
Robert Cartaino

You have it right. The stack pointer points to the top item on the stack and the base pointer points to the "previous" top of the stack before the function was called.

When you call a function, any local variable will be stored on the stack and the stack pointer will be incremented. When you return from the function, all the local variables on the stack go out of scope. You do this by setting the stack pointer back to the base pointer (which was the "previous" top before the function call).

Doing memory allocation this way is very, very fast and efficient.


@Robert: When you say "previous" top of the stack before the function was called, you are ignoring both the parameters, which are pushed onto the stack just before calling the function and the caller EIP. This might confuse readers. Let's just say that in a standard stack frame, EBP points to the same place where ESP pointed just after entering the function.
when you push something to the stack , the stack pointer will be decremented , since the bottom of the stack has the highest address .
w
wigy

EDIT: For a better description, see x86 Disassembly/Functions and Stack Frames in a WikiBook about x86 assembly. I try to add some info you might be interested in using Visual Studio.

Storing the caller EBP as the first local variable is called a standard stack frame, and this may be used for nearly all calling conventions on Windows. Differences exist whether the caller or callee deallocates the passed parameters, and which parameters are passed in registers, but these are orthogonal to the standard stack frame problem.

Speaking about Windows programs, you might probably use Visual Studio to compile your C++ code. Be aware that Microsoft uses an optimization called Frame Pointer Omission, that makes it nearly impossible to do walk the stack without using the dbghlp library and the PDB file for the executable.

This Frame Pointer Omission means that the compiler does not store the old EBP on a standard place and uses the EBP register for something else, therefore you have hard time finding the caller EIP without knowing how much space the local variables need for a given function. Of course Microsoft provides an API that allows you to do stack-walks even in this case, but looking up the symbol table database in PDB files takes too long for some use cases.

To avoid FPO in your compilation units, you need to avoid using /O2 or need to explicitly add /Oy- to the C++ compilation flags in your projects. You probably link against the C or C++ runtime, which uses FPO in the Release configuration, so you will have hard time to do stack walks without the dbghlp.dll.


I don't get how EIP is stored on the stack. Shouldn't it be a register? How can a register be on the stack? Thanks!
The caller EIP is pushed onto the stack by the CALL instruction itself. The RET instruction just fetches the top of the stack and puts it into the EIP. If you have buffer overruns, this fact might be used to jump into user code from a privileged thread.
@devouredelysium The contents (or value) of the EIP register is put on (or copied onto) the stack, not the register itself.
@BarbaraKwarc Thanks for the value-able input. I could not see what the OP was missing from my answer. Indeed, registers stay where they are, only their value is sent to the RAM from the CPU. In the amd64 mode, this gets a bit more complex, but leave that to another question.
What about that amd64? I'm curious.
j
jmucchiello

First of all, the stack pointer points to the bottom of the stack since x86 stacks build from high address values to lower address values. The stack pointer is the point where the next call to push (or call) will place the next value. It's operation is equivalent to the C/C++ statement:

 // 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

The base pointer is top of the current frame. ebp generally points to your return address. ebp+4 points to the first parameter of your function (or the this value of a class method). ebp-4 points to the first local variable of your function, usually the old value of ebp so you can restore the prior frame pointer.


No, ESP does not point to the bottom of the stack. Memory addressing scheme has nothing to do with it. It doesn't matter whether the stack grows to lower or higher addresses. The "top" of the stack is always where the next value will be pushed (put upon the top of the stack), or, on other architectures, where the last pushed value has been put and where it currently lays. Therefore, ESP always points to the top of the stack.
The bottom or base of the stack, on the other hand, is where the first (or oldest) value has been put, and then covered by more recent values. That's where the name "base pointer" for EBP came from: it was supposed to point to the base (or bottom) of the current local stack of a subroutine.
Barbara, in the Intel x86, the stack is UPSIDE DOWN. The top of the stack contains the first item pushed onto the stack and each item after is pushed BELOW the top item. The bottom of the stack is where new items are placed. Programs are placed in memory starting at 1k and grow up to infinity. The stack starts at infinity, realistically max mem minus ROMs, and grows toward 0. ESP points to an address whose value is less than that first address pushed.
W
Wim ten Brink

Long time since I've done Assembly programming, but this link might be useful...

The processor has a collection of registers which are used to store data. Some of these are direct values while others are pointing to an area within RAM. Registers do tend to be used for certain specific actions and every operand in assembly will require a certain amount of data in specific registers.

The stack pointer is mostly used when you're calling other procedures. With modern compilers, a bunch of data will be dumped first on the stack, followed by the return address so the system will know where to return once it's told to return. The stack pointer will point at the next location where new data can be pushed to the stack, where it will stay until it's popped back again.

Base registers or segment registers just point to the address space of a large amount of data. Combined with a second regiser, the Base pointer will divide the memory in huge blocks while the second register will point at an item within this block. Base pointers therefor point to the base of blocks of data.

Do keep in mind that Assembly is very CPU specific. The page I've linked to provides information about different types of CPU's.


Segment registers are seperate on x86 - they're gs, cs, ss, and unless you are writing memory management software you never touch them.
ds is also a segment register and in the days of MS-DOS and 16-bits code, you definitely needed to change these segment registers occasionally, since they could never point to more than 64 KB of RAM. Yet DOS could access memory up to 1 MB because it used 20-bits address pointers. Later we got 32-bits systems, some with 36-bits address registers and now 64-bits registers. So nowadays you won't really need to change these segment registers anymore.
No modern OS uses 386 segments
@Paul: WRONG! WRONG! WRONG! The 16-bits segments are replaced by 32-bits segments. In protected mode, this allows the virtualization of memory, basically allowing the processor to map physical addresses to logical ones. However, within your application, things still seem to be flat, since the OS has virtualized the memory for you. The kernel operates in protected mode, allowing applications to run in a flat memory model. See also en.wikipedia.org/wiki/Protected_mode
@Workshop ALex: That's a technicality. All modern OSes set all segments to [0, FFFFFFFF]. That doesn't really count. And if you would read the linked page, you'll see that all fancy stuff is done with pages, which are much more fine-grained then segments.