Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Problem accessing the stack.

Status
Not open for further replies.

smgarth

Programmer
Mar 12, 2004
20
US
Hello, I have a question with accessing stack variables - I am using NASM. Here is what I did on the calling side:

push msg ; msg is a null-terminated string
call print
add esp, 4

And for the callee:

print:
mov si, [esp-4]
; more code...

For some reason, esp-4 doesn't seem to hold the address of msg?
 
Okay, I read some more from the Intel manuals and see that I was accessing the stack incorrectly (obviously, right?). I made changes to the code but it still doesn't work. Here is basically what I understand now, and what I tried:

1) When a 'call' is invoked, the return address is pushed on the stack, and so this will always be what esp+4 points to (upon entry to the function).

2) Assuming the standard C calling convention, the last parameter pushed on the stack will be the first parameter to the function, and will always be esp+8.

3) lea has to be used when loading an address that cannot be computed at assemble time into a register (not exactly sure why this is).

Are those assumptions correct?

And here is the code I am now trying -

On the calling side:


----------------
push msg
call print
add esp, 4
----------------


And for the callee:


----------------
print:

push ebx ; C convention requires this
lea edx, [esp+8]

printloop:

mov al, byte [edx]
or al, al
jz endprint
mov ah, 0x0e ; BIOS print-char service
mov bx, 0x0007 ; character attribute
int 0x10
inc edx
jmp printloop

endprint:

pop ebx
ret
----------------


Any ideas?


 
(1) what goes onto the stack when you make a "call" depends on memory model. I am a tasm-assembler mostly in 16-bit world, so that colours my answer. Near calls push just the offset of the thing that's called ("near" means that the thing is in the same code segment so cs need not be changed, so there is no need to save the cs on the stack). Therefore near calls in 16-bit world push a single word onto the stack. Far calls push the segment, then the offset, so two words are pushed onto the stack.
(2) A call first decreases sp, and then puts the value at sp. Therefore after the call has been made, sp points to the return address.
(3) lea puts the offset of the thing you are aiming at into the destination register, while mov puts the thing. If you'd said mov edx, [something] you'd have ended up with the value of something in edx, not the location of something.
(4) You're moving a value into al and then setting al to zero at the start of your loop. That doesn't seem logical...
(5) unless you have to, to interface with C, you're not under any obligation in assembler to follow any particular calling convention. Provided an assembler procedure looks right to the outside world, it doesn't matter how it accesses pushed variables.

Good luck!
 
>> lea puts the offset of the thing you are aiming at into the destination register, while mov puts the thing.

Right, but since I pushed an address, then that 'thing' would be an address, not the contents at that address (ie: the first element of the string) - right?

>> Therefore near calls in 16-bit world push a single word onto the stack.

OK, thanks. Right now I am targeting the 32-bit environment, but if I ever do 16-bit I will keep that in mind!

>> You're moving a value into al and then setting al to zero at the start of your loop

When you OR a value with itself, isn't the result going to be the same as the value itself? My intent there was that if al contained 0, then al OR al would set the zero-flag. But I suppose I could have used 'cmp' to be more clear.

>> unless you have to, to interface with C, you're not under any obligation in assembler to follow any particular calling convention

I know. It's just that I am trying to develop a simple operating system and, since I am a C/C++ programmer, I will be interfacing with the assembly.

 
I guess the real problem is just that I do not know how to access data from the stack properly. I simplified the problem a bit in order to narrow down the possibilities, but each and every time the output is erroneous. Here is a simpler program I tried:

(assembler: nasm, 32 bit environment)

entry:

push msg
call print
add esp, 4
ret

print:

lea edx, [esp+8]
push edx
call printf
add esp, 4
ret


I even changed 'lea' to 'mov', but that didn't work either. Can someone show me a working example of using the stack to pass strings?
 
smgarth,

I'm assuming that you will run this under windows (32bit), am I right ?

If so, first of all, you are mixing 16bit & 32bit. Calling interrupt is a violation in windows, especially BIOS interrupt. Maybe (just maybe, I'm not sure) you can still run DOS Interrupt function (INT 21h) under windows (Win9X only) but its just for some function. To get a full access to interrupt you must create a service, but that is another story.

So even if you make the right code, you will still getting error/crash!
[tt] [/tt]
Assuming the standard C calling convention, the last parameter pushed on the stack will be the first parameter to the function, and will always be esp+8

That is right. But it will always be esp+4 not +8. Why you can get +8 ? Because at your 2nd post, you pushed another value (EBX) right at the beginning of the routine. And that is another +4.

Right, but since I pushed an address, then that 'thing' would be an address, not the contents at that address (ie: the first element of the string) - right?

Right. So you can just use MOV reg, [esp+4].

Hope that helps a little

-- AirCon --
 
Thank you very much, it's a lot clearer now, and everything is working properly!

>> Calling interrupt is a violation in windows, especially BIOS interrupt

I know. I'm just dabbling with a small OS, hence the BIOS code.

>> Right. So you can just use MOV reg, [esp+4].

Oddly enough, lea didn't even work! I wonder why not?

Anyway, many thanks, I think I'm getting the hang of stacks now, thanks to you guys. :)
 
Glad you can worked it out.
I know. I'm just dabbling with a small OS, hence the BIOS code
So, I made a wrong assumption :-D


Oddly enough, lea didn't even work! I wonder why not?
Well.. this is a bit difficult to explain, but I'm trying.
As Lionell had explained, lea puts the offset/address (pointer) of the "thing", while mov puts the "thing". Now look into your code.

Before the call to the routine, it pushed the offset of msg into the stack. By simply use a mov it already gets the offset, because the "thing" in the stack is an "offset".

LEA will get an offset from the "thing". While this "thing" in the stack is an "offset", it will end up getting an offset from an offset. It's like getting a pointer inside pointer. So it is not because lea doesn't work, but because you slightly misunderstood how it works.

I'm not sure if that makes any sense but I hope it does :)


-- AirCon --
 
Ok, I get it. Basically by using lea in that context I was creating a malformed pointer using the first four chars of msg - hence the error! Or in C:

char * ptr = someBuffer;
char * badptr = (char*)((int)(*ptr));

Thanks for all the help, by the way. :)

Cheers.

- Sebastian
 
so sorry about the or thingy, thingy. Of course, I should read properly. I just read it as xor in my head because one sees that so often.

For the perfectionist you might consider "test" when you're trying to set flags. The reason is a bit petty but worth knowing.

Or can change the destination register. Even if it doesn't, the processor has to assume it might have done. Therefore the destination register is not available for use in subsequent instructions until the processor can be sure the value has been calculated (remember, the modern processor is executing several instructions at once in different pipelines). Therefore
or eax, eax
add ebx, eax
would run slower than it should because the add instruction will have to wait until the or is finished.
"test" does not alter any values (except the flags) so it doesn't cause delays.

Glad it's working for you now. I'd assumed 16-bit because you were using interrupts.
 
I haven't gotten it to work, yet, actually. It's really got me stumped. First of all, everyone is telling me you can't use 32bit code in real mode (which is what I'm in, entering the bootstrap process). So I modified all of my code accordingly, but nasm won't let me do:

mov dx, [sp+2]; 'error: invalid effective address'

It *will* let me do:

mov edx, [esp+2]

...but there I go using 32bit code! I don't know if that's why it won't work, but unusual nonetheless. Once I get to pmode I know the 32bit code will work (and besides, at that point I can actually use C, anyway - yippee!), so maybe I'll just keep using the stackless code I was using before till I get there. :)

Still, I'd love to know why it doesn't work. Here's the rundown of the environment I have setup:

org 0
cs=0x7c0; seems fine
ds=0x7c0; seems fine
ss=0x7c0; not sure
sp=0x200; should be fine, the program is exactly 256 bytes long, so setting sp here should give me a 256-byte stack.

Does that look OK?
 
may i join u guys :)?
looks u managed the stack problem finally!
the thing i want to comment is using 32 bit code in real mode. THIS IS POSSIBLE starting on 386+ CPUs.

little example to make things clearer:
suppose CPU is in real mode, 16-bit registers are native (but 32-bit accesible!). say, u assemble

mov ax,01
db 00
db 00

with 16-bit target assembler will be compile this as
B8 01 00 00 00, and in real mode this does mean
mov ax, 01
dw 00 ;btw, this is add [bx][si],al
(B8 is "mov ax" code here)
but u can write and compile
mov eax, 01
for the same 16-bit target
and this will yield
66 B8 01 00 00 00
(thats why i added these two zeros above). mind the prefix 66 meaning "change native register model" for CPU.
so, the point is that 32-bit operations generally are quite accessible in 16-bit mode (not pm instructions sure) but are 1 byte longer. personally i quite use 32-bit instructions in DOS com files.

now the 2nd part of the story. say, u r in 32-bit mode. here
B8 01 00 00 00
means
mov eax,01
on the contrary, the above
66 B8 01 00 00 00
means now
mov ax,01
add [eax],al

end of the story.. hope i was not too boring :)
just wanted be helpful
regards, oleksii



 
Yes, you're right, but with a couple of provisos. Firstly, you will run into difficulties with the top half of esp etc., because in 16-bit mode from 386 onwards you are not allowed to address things with 32 bits (sensibly). You must make sure that nothing you do could possibly be construed by the processor as an attempt to address something beyond the dos limit.
Secondly, your compiler must know you're in 16-bit world. Otherwise it won't write the 32-bit size override prefix. Good compilers like tasm will allow you all the right things and sort out the prefixes for you (specify .386).
Third proviso, size prefix costs you a cycle, I believe. If you're in speed-sensitive areas of code, it's worth thinking whether you need it.
But basically I agree, Oleksii, it's a useful tool to know about.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top