News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

"right" the stack frame?

Started by thomas_remkus, January 20, 2007, 11:52:12 PM

Previous topic - Next topic

thomas_remkus

First off, I'm not sure if "right" is correct. I think it's called "balance" but someone would need to confirm for me so I can use the correct terms.

If I do this:

push 1
push 2
push 3
push 4
move eax, [esp - 4]
sub esp, 16


Then this will put into eax the number 3? and then the sub from esp will balance the stack? If this is so, then will this do the same thing:

secondParam TEXTEQU <[esp - 4]>
clearFourParams TEXTEQU <sub esp, 16>
...
push 1
push 2
push 3
push 4
move eax, secondParam
clearFourParams


Liquid

QuoteThen this will put into eax the number 3? and then the sub from esp will balance the stack?

Not at all, you are going the wrong way.

The base of a stack is a higher memory offset then the top of the stack.
When you push something it goes like:
sub esp, 4
mov [esp], thestuff

Pop is the other way around:
mov thestuff, [esp]
add esp, 4

If you do this:
push 1
push 2
push 3
push 4

Then in order to get the 3 back into, for instance, eax you must do:
mov eax, [esp+4]

And you balance the stack with add esp, 16

thomas_remkus

right. i was backwards. it's still attempting to visualize the stack in my head. i understand adding addresses that would increase the esp addresses, but "sub esp, 4" to add a new value is sort of blowing my visual of how the stack works.

does anyone have more of a visual for the stack and how things get push-ed and pop-ed?

stanhebben

It's not that hard. ESP points to the top of the stack. (the last element you pushed on the stack) More elements on the stack means a smaller ESP.

Each time you push an element, ESP gets decremented by 4 (more elements = smaller ESP) and the new value is written to that location.
Each time you pop an element, ESP get incremented by 4. (less elements = larger ESP)

To simulate 4 pops, you perform 4 increments by 4, so add 16 to ESP.

I hope this helps.

thomas_remkus

That *does* help! Thanks. By this, then we would have a stack limit. While this might not effect the binary size, it would effect the size once loaded and used I'm thinking. What happens when I get all the way to the bottom of the stack?

Also, items on the stack are separated by WORDs, or they take up WORDs each time you push. When I push a BYTE or DWORD I guess that this is getting copied to the stack, right? If that's so, then this is legal, right?

mov eax, -1
push eax
mov eax, 2
push eax


And I pushed -1 and 2 and they stay that way.

stanhebben

Windows assigns each program a giant stack. However, this stack is only virtual memory, so it doesn't take up any ram space. As soon as your program uses up a certain amount of stack space, new (ram) memory is assigned to the stack.

But you're right, there *is* a limit. If you push millions of numbers on the stack, it may eventually get full and cause a memory access violation error. This usually only happens in an endless loop or recursion. (a function that calls itself)

For most applications, this limit is no problem. Just don't push megabytes of memory on the stack.

By default, you push dwords on the stack.

You're correct about the piece of code, you indeed push -1 and 2 on the stack. (both are dwords) If you push a 32-bit register (eax, ebx, ecx, edx, esi, edi, ebp, esp) or a constant then the processor will push a dword on the stack (4 bytes).

It is possible to push 16-bit registers on the stack, but this can cause stack misalignment - don't do it. (except if you are running in 16-bit mode, of course)

thomas_remkus

So, you can't push like

push <thomas>

or some variable on the stack. i'm just thinking that i might get in trouble if i push the "addr MyString", then change MySting anf then "call" i might be pointing to nothing. I think if it's allocated array at compilke the contents won't matter, but if i am allocating a dynamic string then i must be passing the DWORD ponter and not the actual string.

Thanks for you time. The stack has been a real issue with me learning MASM and it's getting more clear now.

stanhebben

The stack only contains dwords. It doesn't matter if it's a number or an address, the processor doesn't care, and it's up to you to handle it's value correctly. So, if you allocate some memory, and put it's address on the stack, then free the memory and use it's address later, then that's your fault.

I have a few examples that demonstrate the use of the stack in the attachment.

[attachment deleted by admin]

thomas_remkus

First off, everything compiled and worked like a charm. Secondly, you had to know that your code would raise plenty of questions.

1) Why did you need to "pushad" and "popad" to preserve the registers?

2) What is that odd syntax with the "<hex$(eax)>"?

3) Could you have used "addr" with "push offset Mydword" instead?

4) What is the "%" on the "print args" line in your macro?

5) You used string literals inside the code ("Pop x:"). Does MASM take care of whatever it needs to use this for me? I thought I was going to be creating hundreds of .CONST items.

THANKS! I'm playing with this right now and trying to answer some of my own questions. This "print" is really helpful! I have a C++ class that does some timing things for me with the QueryPerformanceCounter APIs and maybe I can transfer that into this. Need to find the "printf" if there is one. I have been using StdOut and that's helpful too.

stanhebben

1) pushad and popad push and pop all register on/off the stack

so, pushad is something like:
  push eax
  push ebx
  push ecx
  push edx
  push esi
  push edi
  push esp
  push ebp

but, it's much shorter. They are useful for debug code, where you don't want your registers changed when debugging variables.

2) and 4) <hex$(eax)> and %

The hex$(..) is a string formatting macro that can be used with the print macro. See masm32\macros\macros.asm for other ones. I had to surround it with < > to get it compiled correctly. If I don't use both the < > and the %, some of the code gets before the pushad, messing with my registers.

For now, suppose it works. The debug macro in my code accepts a string, <hex$(...)>, <str$(...)>, <ustr$(...)>, etc as first parameter, and zero or more constants (ascii characters). For example,

debug "Something"    ; prints the string Something
debug "Something", 10 ; prints Something, then ascii character 10 (\n, newline)
debug <hex$(eax)>  ; prints the contents of eax, in hex
debug <str$(eax)>  ; prints a signed decimal
debug <ustr$(eax)> ; prints an unsigned decimal
debug <hex$(eax)>, 'h', 10 ; prints eax in hex, then the single character h, then a newline
debug eax ; eax must contain a pointer to a string, that string is printed


You can use this for the print macro too, where you don't have to use the < >.

3) No. addr is only for invoke. In invoke, it replaces the offset.

For example:

.data

MyTitle  db "Example application", 0
MyText db "Hello world!", 0

.code
  push MB_OK
  push offset MyTitle
  push offset MyText
  push 0
  call MessageBox


is the same as

.data

MyTitle  db "Example application", 0
MyText db "Hello world!", 0

.code
  invoke MessageBox, 0, addr MyText, addr MyTitle, MB_OK


5) The "Pop x:" is handled by the print macro. The string "Pop x:" is put - as data - in the data segment. Then, the address of this memory is used.


  debug "Pop 1: "

is the same as:

.const
Pop1 db "Pop 1: ", 0

.code
  debug offset Pop1


The print macro uses the reparg macro, which you can use to save yourself the work of creating const items. For example:


  invoke MessageBox, 0, reparg("Hello world!"), reparg("Example application"), MB_OK



There is a printf, called crt_printf. Many other c functions are available as well, with the prefix crt_ (crt_memcpy, crt_malloc, ...). See masm32\include\msvcrt.inc .

thomas_remkus

THANKS!! There were so many gems in that post for me. I'll have to look, review, and play with much of that myself. Thanks for your time.

pro3carp3

Referring to the posts at the beginning of this thread - Different programming resources with diagrams of memory and the stack may show the lower memory addresses at either the top or bottom of the diagram.  This can be confusing to beginning programmers.  I prefer to visualize memory with the lowest address on the bottom, as it makes the most sense to me.  Higher memory goes up, lower memory goes down.  The stack grows down in memory so the 'top' of the stack, is actually on the bottom.
LGC

zooba

Quote from: Stan Hebben on January 22, 2007, 06:40:59 PM
If you push millions of numbers on the stack, it may eventually get full and cause a memory access violation error.

Actually, the program just disappears. Exceptions are thrown in the same context as the thread, so if the thread is out of stack space it can't be thrown. Windows simply shuts down the program.

The ideal case would be some form of error, but if you're testing a recursive algorithm and your app just disappears, it's probably a stack overflow.

Cheers,

Zooba :U