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
you're welcome
RSood
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.
| 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
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
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.
Also Tim, the RET 6 actually adds 3*2 (not 6*2), thereby discarding
the 3 parameters.
: 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.
[]
| : 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
Not to mention that using [E]BP usually saves one byte
of instruction length. Optimizing for speed often requires
optimizing for size.
-- Robert
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
Not to mention that "[sp + N]" isn't a valid effective address. Some
folks have been doing 32-bit code too long :)
Best,
Frank
[...]
| 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
Surely. I *said* "sp".
Best,
Frank
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
> 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
[..]
| 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