Here is some example code I am trying to understand.
My questions deal with the sum procedure. I don't see why we do
push BP
mov BP, SP
What is the point of doing this?
1: TITLE Parameter passing via the stack PROCEX3.ASM
2: COMMENT |
3: Objective: To show parameter passing via the stack
4: Input: Requests two integers from the user.
5: | Output: Outputs the sum of the input integers via register AX.
6: .MODEL SMALL
7: .STACK 100H
8: .DATA
9: prompt_msg1 DB ’input first number: ’,0
10: prompt_msg2 DB ’input second number: ’,0
11: sum_msg DB ’The sum is ’,0
12:
13: .CODE
14: INCLUDE io.mac
15:
16: main PROC
17: .STARTUP
18: PutStr prompt_msg1 ; request first number
19: GetInt CX ; CX := first number
20: nwln
21: PutStr prompt_msg2 ; get second number
22: GetInt DX ; DX := second number
23: nwln
24: push CX ; put first number on stack
25: push DX ; put second number on stack
26: call sum ; returns sum in AX
27: PutStr sum_msg ; display sum
28: PutInt AX
29: nwln
30: done:
31: .EXIT
32: main ENDP
35: ;Procedure sum receives two integers via the stack.
36: ; The sum of the two integers is returned in AX.
37: ;----------------------------------------------------------
38: sum PROC
39: push BP ; we use BP, so save it
40: mov BP,SP
41: mov AX, ; sum := 1st number
42: add AX, ; sum := sum + 2nd number
43: pop BP ; restore BP
44: ret 4 ; return and clear parameters
45: sum ENDP
46: END main
My questions deal with the sum procedure. I don't see why we do
push BP
mov BP, SP
What is the point of doing this?
no point really since no local variables are declared, you could just use sp to access the 2 parameters if you want
Why do we need to use SP to access the parameters, that is why is SP needed at all?
This is to create a Stack Frame for the procedure.
If you describe the proc's input params better, the assembler will generate that for you.
I am not sure this will work on MASM, since I think MASM generally likes dword-aligned parameters, but try anyway.
Try this:
If that seems to assemble and work as expected, disassemble your EXE and have a look at what the assembler actually generated :)
And as a hint to help you disassemble it, make the first line of code inside the procedure this:
Rebuild your executable, and execute it inside a debugger (eg OllyDBG).
You should now find yourself staring at the INT 3 instruction, and you will see that the assember has generated some junk just before and after your procedure's contents.
The extra junk is making space on the Stack for your input parameters, and cleaning up when ready to RET.
The junk is also known as a "PROLOGUE and EPILOGUE".
You can do all that junk yourself, or you can be lazy and let the assembler generate it for you.
But if you do it yourself, do not mention the parameters in the procedure's definition.
If you describe the proc's input params better, the assembler will generate that for you.
I am not sure this will work on MASM, since I think MASM generally likes dword-aligned parameters, but try anyway.
Try this:
sum PROC val1:word, val2:word
mov AX,val1 ; sum := 1st number
add AX,val2 ; sum := sum + 2nd number
ret
sum ENDP
If that seems to assemble and work as expected, disassemble your EXE and have a look at what the assembler actually generated :)
And as a hint to help you disassemble it, make the first line of code inside the procedure this:
int 3 ;BREAKPOINT for debugger
Rebuild your executable, and execute it inside a debugger (eg OllyDBG).
You should now find yourself staring at the INT 3 instruction, and you will see that the assember has generated some junk just before and after your procedure's contents.
The extra junk is making space on the Stack for your input parameters, and cleaning up when ready to RET.
The junk is also known as a "PROLOGUE and EPILOGUE".
You can do all that junk yourself, or you can be lazy and let the assembler generate it for you.
But if you do it yourself, do not mention the parameters in the procedure's definition.
Why do we need to use SP to access the parameters, that is why is SP needed at all?
Because you push 2 parameters onto the stack before the call and sp points to the top of the stack.. the computer organization book explains this in the same chapter the code is from..
Why do we need to use SP to access the parameters, that is why is SP needed at all?
Because you push 2 parameters onto the stack before the call and sp points to the top of the stack.. the computer organization book explains this in the same chapter the code is from..
Do we always need to work with SP when dealing with the stack?
Whoa!!!
Youse guys have been doing 32-bit code too long! "" is not a valid effective address in 16-bit code! bx is your "base register", si and di are your "index registers", bp is your "base pointer"(?). (normal calling convention specifies that we should preserve these registers across a call - so we have to "push bp"!) An effective address (in 16-bit code) consists of an optional offset, plus an optional base register, plus an optional index register. Period. (you could use "" in 16-bit code... the upper word of esp is "probably" clear... you want code that "probably" works?)
It is worth mentioning that bx, si, and di default to ds:??, bp defaults to ss:bp (these defaults can be overridden... "", for example... "es:" for Masm, I guess). All x86 addresses involve a segment and an offset - even in 32-bit mode! The "flat memory model" (all segment descriptors start from offset zero) allows us to forget about this fact - thank goodness!!! (you're gonna like 32-bit code, when you "graduate" to it!)
So... we:
push dx
push cx
call sum
I hope you know that "call" uses the stack to store the return address. So our stack looks like
???
parameter 2
parameter 1
return address <- ss:sp
Now we hit "sum:", and "push bp". Stack looks like:
???
parameter 2
parameter 1
return address
caller's bp <- ss:sp
Then we do "mov bp, sp", so bp is pointed at "caller's bp". It isn't used in this routine, but commonly something is subtracted from bp at this point to make space on the stack for "local variables", aka "stack variables". C call's 'em "automatic" variables, because the memory (on the stack) is automatically freed when the function exits. But we don't need local variables, we only need to access our parameters...
???
parameter 2 <-
parameter 1 <-
return address <-
caller's bp <-
(the "ss" is default, and you probably shouldn't write it that way - Masm will "optimize it away" - I think - but Nasm will emit the "segment override" byte - which we don't need!)
Does that clear up why we do it that way?
(all of this cruft should be in your book!!!)
Best,
Frank
Youse guys have been doing 32-bit code too long! "" is not a valid effective address in 16-bit code! bx is your "base register", si and di are your "index registers", bp is your "base pointer"(?). (normal calling convention specifies that we should preserve these registers across a call - so we have to "push bp"!) An effective address (in 16-bit code) consists of an optional offset, plus an optional base register, plus an optional index register. Period. (you could use "" in 16-bit code... the upper word of esp is "probably" clear... you want code that "probably" works?)
It is worth mentioning that bx, si, and di default to ds:??, bp defaults to ss:bp (these defaults can be overridden... "", for example... "es:" for Masm, I guess). All x86 addresses involve a segment and an offset - even in 32-bit mode! The "flat memory model" (all segment descriptors start from offset zero) allows us to forget about this fact - thank goodness!!! (you're gonna like 32-bit code, when you "graduate" to it!)
So... we:
push dx
push cx
call sum
I hope you know that "call" uses the stack to store the return address. So our stack looks like
???
parameter 2
parameter 1
return address <- ss:sp
Now we hit "sum:", and "push bp". Stack looks like:
???
parameter 2
parameter 1
return address
caller's bp <- ss:sp
Then we do "mov bp, sp", so bp is pointed at "caller's bp". It isn't used in this routine, but commonly something is subtracted from bp at this point to make space on the stack for "local variables", aka "stack variables". C call's 'em "automatic" variables, because the memory (on the stack) is automatically freed when the function exits. But we don't need local variables, we only need to access our parameters...
???
parameter 2 <-
parameter 1 <-
return address <-
caller's bp <-
(the "ss" is default, and you probably shouldn't write it that way - Masm will "optimize it away" - I think - but Nasm will emit the "segment override" byte - which we don't need!)
Does that clear up why we do it that way?
(all of this cruft should be in your book!!!)
Best,
Frank
(all of this cruft should be in your book!!!)
There's plenty that needs to be in there!
If you wish to assist, you should have edit privileges in the Book.
Sorry guys these are my class notes, no book was required for this course so I am unable to refer to the text for questions that would likely be answered in there. Thank you for the help though, the stack is a clear to me now.