Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

what does PUSH BP, MOV BP, SP do? (stack)

3,644 views
Skip to first unread message

Tomas Selnekovic

unread,
Dec 10, 2004, 8:13:06 PM12/10/04
to
consider this simple source code :

sum proc
push bp
mov bp, sp

xor ax, ax
mov al, [bp+4]
add al, [bp+6]
add al, [bp+8]

pop bp
ret 6
endp


...
...
xor ax, ax
mov al, 1
push ax
mov al, 2
push ax
mov al, 3
push ax

call sum
...
...

I don't really understand what's actually going on there in the stack.
I know that the result of this operation will be 6 in the AX, but ...

1. could you please tell me why need to store the base pointer BP?

push bp
mov bp, sp

2. what does "ret 6" do?

3. mov al, [bp+4] ;why +4? why not just +2?
4. where is the SP before we "call sum"?

thanks for any hints.

ts

rsood

unread,
Dec 10, 2004, 10:12:23 PM12/10/04
to
I can't answer all your questions but I can say that it is bp+4 because
you pushed 8 bytes onto the stack, the first six being the parameters
passed by pushing ax three times, the last two being from the push bp
right before you move the stack pointer into bp. Since bp was the last
thing pushed, it is at bp+2, the first (or last) parameter is at bp+4
and the 2nd is at bp+6 and the third at bp+8.

you're welcome
RSood

Nick Nelson

unread,
Dec 10, 2004, 11:23:59 PM12/10/04
to

Copying SP to BP and then using BP inside a routine rather than SP is
a convention for accessing pushed parameters. The reason you don't
use SP is that if you happen to save something on the stack before
getting the parameters you will have to account for that in the
offsets used for retrieving stack data. If there's one path through
the code where you save something and one where you don't, when you
subsequently get the parameters you'll have to have some way of
knowing which path you took and offsetting appropriately. It's much
safer to have a register that is guaranteed to represent the stack
pointer at entry to the routine.

In the sample you included you could delete the PUSH BP and subsequent
POP BP because nothing else is pushed on the stack, but that would be
dangerous for later modifications to the code. (Bad programming
practice!)

RET 6 means get the return address from the top of the stack and then
pop 6 bytes from the stack (which discards the 3 parameters pushed by
the caller). So 8 is added to the stack pointer (SP) by the RET 6
instruction.

BP+2 is used because the contents at the stack pointer always points
to the current "last thing pushed" slot. The stack is loaded
downward, which means that when a PUSH occurs the data is put where
SS:[SP] points and then SP is decremented by 2 (word pushes, as done
here). So adding 2 to the stack pointer gets you the return address
(always the last thing pushed in a call), +4 gets you the 3rd thing
pushed (in this example), +6 gives the 2nd thing, and +8 gives the 1st
thing.

wolfgang kern

unread,
Dec 11, 2004, 12:43:35 AM12/11/04
to

Tomas Selnekovic asked:

| consider this simple source code :
|
| sum proc
| push bp
| mov bp, sp
|
| xor ax, ax
| mov al, [bp+4]
| add al, [bp+6]
| add al, [bp+8]
|
| pop bp
| ret 6
| endp

[...]

| I don't really understand what's actually going on there in the stack.
| I know that the result of this operation will be 6 in the AX, but ...

| 1. could you please tell me why need to store the base pointer BP?

This is just the typical HLL-schools "procedure standard",
absolutely a useless detour for your simple example.
Instead of calling a procedure you can add the three byte values
without using the stack at all.

But for more complex and frequent called functions this may make
sense, even I prefer parameter-passing by registers or by pointer.

So BP becomes a copy of SP during the procedure, this makes
live easier (for the lazy thinkers) to address a previous pushed
parameter even when the stack will change, either by (ab)used for
a local frame or any push or call within the procedure.

| push bp
| mov bp, sp

| 2. what does "ret 6" do?

it 'kills' the three pushed parameters off the stack,
can be seen as "POP IP" followed by "ADD SP 6".

| 3. mov al, [bp+4] ;why +4? why not just +2?

there is a push BP in front of the mov from [bp+n]

| 4. where is the SP before we "call sum"?

example SP:

7ffa whatsoever came here before
7ff8 before push arg1
7ff6 before push arg2
7ff4 before push arg3
7ff2 before call [7ff0] holds the return address after
7ff0 before push BP [7fee] holds BP-value after
7fee
bp=sp so bp+4 means arg3
...any action
pop BP from [7fee] SP is 7ff0 after
"ret 6" does:
ret (POP IP) from [7ff0] SP is 7ff2 after
add SP,6 SP is now 7ff8 = as started with.

Note: +286 stack works different to 8086 (just off by two)

Hope this helps,

__
wolfgang

Tomas Selnekovic

unread,
Dec 11, 2004, 3:05:25 PM12/11/04
to
thank you for your answers, now it's clear.

tomas

Dirk Wolfgang Glomp

unread,
Dec 11, 2004, 12:56:07 PM12/11/04
to
Tomas Selnekovic schrieb:

> consider this simple source code :
>
> sum proc
> push bp

push 16Bit to stack

> mov bp, sp

stackpointer to bp

> xor ax, ax
> mov al, [bp+4]

load 8Bit from stack

> push bp
> mov bp, sp
>
> 2. what does "ret 6" do?

remove 6 Byte from stack(stackpointer)

> 3. mov al, [bp+4] ;why +4? why not just +2?

Why "al" its only a 8-Bit register?

Dirk

rsood

unread,
Dec 11, 2004, 8:29:22 PM12/11/04
to
Now I'm confused. There is a push bp and also a return address. They
can't both be on the stack at bp+2. So which one is on the stack and
where is the other one?

Tim Roberts

unread,
Dec 11, 2004, 11:55:36 PM12/11/04
to
"rsood" <spam...@crayne.org> wrote:

If you will forgive me for being verbose, let's step through the code and
look at the values of the variables. It can be VERY useful to do this with
pencil and paper as you're learning about stack handling. I'll put lower
addresses at the top.

So, when we start executing your code, the stack pointer points at
something meaningful to the calling routine, and bp has something unknown:

sp --> xxxx

xor ax, ax
mov al, 1
push ax

Now we have:

sp --> 0001
xxxx

mov al, 2
push ax
mov al, 3
push ax

Now we have:

sp --> 0003
sp+2 0002
sp+4 0001
sp+6 xxxx

call sum

Now we have:

sp --> return address
sp+2 0003
sp+4 0002
sp+6 0001
sp+8 xxxx

sum proc
push bp
mov bp, sp

Now we have:

bp = sp --> old bp
bp+2 sp+2 return address
bp+4 sp+4 0003
bp+6 sp+6 0002
bp+8 sp+8 0001
bp+10 sp+10 xxxx

xor ax, ax
mov al, [bp+4]
add al, [bp+6]
add al, [bp+8]

Now you can see why this works. The parameters actually start at bp+4.
The result is stored in ax.

pop bp
ret 6
endp

We restore the old value of bp. The "ret 6" instruction pops the return
address into ip, and then adds 6x2 to the stack pointer, automatically
popping the 3 parameters. Everything is back to where it started.
--
- Tim Roberts, ti...@probo.com
Providenza & Boekelheide, Inc.

rsood

unread,
Dec 12, 2004, 7:11:03 PM12/12/04
to
Okay, makes sense.
Thanks

Nick Nelson

unread,
Dec 12, 2004, 11:35:54 PM12/12/04
to

The confusion may lie in the BP versus SP usage. When the MOV BP.SP
instruction is executed, BP points to the top of the stack as loaded
by the caller (the parameters) and the CPU (the return address). From
this point on it doesn't matter what is pushed on the stack as far as
BP is concerned. Pushing and popping the stack doesn't affect BP,
only SP. This is the whole point of using BP rather than SP for
accessing parameters in a routine.

Also Tim, the RET 6 actually adds 3*2 (not 6*2), thereby discarding
the 3 parameters.

Wendy E. McCaughrin

unread,
Dec 12, 2004, 11:35:57 PM12/12/04
to
wolfgang kern <now...@nevernet.at> wrote:

: Tomas Selnekovic asked:

: | consider this simple source code :
: |
: | sum proc
: | push bp
: | mov bp, sp

: |
: | .....
:
: This is just the typical HLL-schools "procedure standard",


: absolutely a useless detour for your simple example.
: Instead of calling a procedure you can add the three byte values
: without using the stack at all.

: But for more complex and frequent called functions this may make
: sense, even I prefer parameter-passing by registers or by pointer.

: So BP becomes a copy of SP during the procedure, this makes
: live easier (for the lazy thinkers) to address a previous pushed
: parameter even when the stack will change, either by (ab)used for
: a local frame or any push or call within the procedure.

Wrong. This is _not_ a "useless detour" adn especially for complex
functions definitely makes sense, particularly when debuggers depend
upon this standard for stack-frames to do back-traces. It is a standard
for disciplined thinkers who program defensively, not lazy ones who don't.

wolfgang kern

unread,
Dec 13, 2004, 4:02:48 PM12/13/04
to

Wendy E. McCaughrin replied:

[]


| : This is just the typical HLL-schools "procedure standard",
| : absolutely a useless detour for your simple example.
| : Instead of calling a procedure you can add the three byte values
| : without using the stack at all.
|
| : But for more complex and frequent called functions this may make
| : sense, even I prefer parameter-passing by registers or by pointer.
|
| : So BP becomes a copy of SP during the procedure, this makes
| : live easier (for the lazy thinkers) to address a previous pushed
| : parameter even when the stack will change, either by (ab)used for
| : a local frame or any push or call within the procedure.

| Wrong. This is _not_ a "useless detour" and especially for complex


| functions definitely makes sense, particularly when debuggers depend
| upon this standard for stack-frames to do back-traces. It is a standard
| for disciplined thinkers who program defensively, not lazy ones who don't.

I wont start any discussion about our different opinions on this matter,
we already had this dispute several times.

Fine if you feel good with 'the standard', but you wont be able to
change my mind, as I found my way in using hardware strict logical
regardless of the 'oh so common' rules.

__
wolfgang kern (Author of KESYS)
How fast and short could windoze be
if everyone would think like me.
http://web.utanet.at/schw1285/KESYS/index.htm

Robert Redelmeier

unread,
Dec 13, 2004, 4:35:34 PM12/13/04
to
Wendy E. McCaughrin <spam...@crayne.org> wrote:
> Wrong. This is _not_ a "useless detour" adn especially for
> complex functions definitely makes sense, particularly when
> debuggers depend upon this standard for stack-frames to do
> back-traces. It is a standard for disciplined thinkers who
> program defensively, not lazy ones who don't.


Not to mention that using [E]BP usually saves one byte
of instruction length. Optimizing for speed often requires
optimizing for size.

-- Robert

wolfgang kern

unread,
Dec 14, 2004, 2:25:00 PM12/14/04
to

Robert Redelmeier wrote:

Right, and by avoiding stack-pollution with parameter copies at all,
we save many L1-cache lines, which then are available for code.

But as I already said, I wont restart the programming philosophy
discussion about parameter passing again. I just like to mention
that there are other possibilities beside the windoze/C-+ -methods,
which appear to be faster and shorter by far.

So everyone may chose what fits best.

__
wolfgang

Frank Kotler

unread,
Dec 16, 2004, 3:14:28 AM12/16/04
to

Not to mention that "[sp + N]" isn't a valid effective address. Some
folks have been doing 32-bit code too long :)

Best,
Frank

wolfgang kern

unread,
Dec 16, 2004, 9:51:49 PM12/16/04
to

Frank Kotler wrote:

[...]


| Not to mention that "[sp + N]" isn't a valid effective address.
| Some folks have been doing 32-bit code too long :)

Absolutely right Frank,

mov eax,[esp]

doesn't have a 16-bit equivalent, except 'all' the 'few' forms like:

--use16:
67 8b 04 24 mov ax,[esp]
67 8b 44 24 nn mov ax,[esp+/-nn]
67 8b 84 24 nnnnnnnn mov ax [esp+nnnnnnnn]
(esp = ZxSP in all cases above)

;esp cannot be an index, but it may be a base even in 16-bit mode.

You never tried? ;)
__
wolfgang

Frank Kotler

unread,
Dec 16, 2004, 11:36:02 PM12/16/04
to

Surely. I *said* "sp".

Best,
Frank

wolfgang kern

unread,
Dec 17, 2004, 12:58:40 PM12/17/04
to

Frank Kotler wrote:
[..]

| > --use16:
| > 67 8b 04 24 mov ax,[esp]
| > 67 8b 44 24 nn mov ax,[esp+/-nn]
| > 67 8b 84 24 nnnnnnnn mov ax [esp+nnnnnnnn]
| > (esp = ZxSP in all cases above)
| >
| > ;esp cannot be an index, but it may be a base even in 16-bit mode.
| >
| > You never tried? ;)
|
| Surely. I *said* "sp".

Ok I see now, as I never coded for VM86, I wasn't aware of:

VM86 and big-real wont guaranty to have a zero in the high word of esp.

But true-RM and PM16 should always have a zero there, so here
'mov ax,[esp]' can be assumed to perform as 'mov ax,[sp]'.

__
wolfgang

Frank Kotler

unread,
Dec 17, 2004, 6:51:11 PM12/17/04
to
wolfgang kern wrote:

> Ok I see now, as I never coded for VM86, I wasn't aware of:
>
> VM86 and big-real wont guaranty to have a zero in the high word of esp.

I don't know about VM86... In big-real ("relimited real mode" would be
the most descriptive name, IMO), you might get a non-zero value into the
high word of esp... We're still in *real* mode, so only sp would be used
in normal stack operations... I suppose "[esp]" *would* honor the high
word, so it might be something to worry about... that's one I *haven't*
tried...

> But true-RM and PM16 should always have a zero there, so here
> 'mov ax,[esp]' can be assumed to perform as 'mov ax,[sp]'.

Agreed... but "mov ax, [sp]" isn't valid, despite the fact that it would
work like "mov ax, [esp]" if it were. I know *you* know the difference,
but several people have implied that it would be an alternative to the
subject line.

Best,
Frank

wolfgang kern

unread,
Dec 18, 2004, 4:05:37 AM12/18/04
to

Frank Kotler wrote:

[..]


| I don't know about VM86... In big-real ("relimited real mode" would be
| the most descriptive name, IMO), you might get a non-zero value into the
| high word of esp... We're still in *real* mode, so only sp would be used
| in normal stack operations... I suppose "[esp]" *would* honor the high
| word, so it might be something to worry about... that's one I *haven't*
| tried...

As usual, I first tried it the hard way...,
and exceptions 0Dh (RM:seg-limit/PM16: access violation) are raised.

| > But true-RM and PM16 should always have a zero there, so here
| > 'mov ax,[esp]' can be assumed to perform as 'mov ax,[sp]'.
|
| Agreed... but "mov ax, [sp]" isn't valid, despite the fact that it would
| work like "mov ax, [esp]" if it were. I know *you* know the difference,
| but several people have implied that it would be an alternative to the
| subject line.

Right, sometimes it needs more than one voice to avoid confusion. ;)

__
wolfgang

0 new messages