News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Checking assumptions

Started by mywan, July 22, 2012, 05:08:50 PM

Previous topic - Next topic

mywan

I've only been studying assembly a few weeks and need to put a check on some assumptions. Bad thing when such assumptions are based on higher level languages. I wrote the little function, not because the target program needs it but to check states without resorting to a debugger. To view integers as strings:
viewInt proc iNum:DWORD
LOCAL buffer[12]:BYTE
invoke dwtoa,iNum,addr buffer
;MsgBox hWnd,addr buffer,"Number:",MB_OK ; Uncomment to return number string in eax
lea eax, buffer ; Comment out to use the MsgBox
ret
viewInt endp

Can I expect this memory buffer to be released when the function returns?
I would expect this of higher level languages but need to know exactly how it's handled in MASM. Basic math says it must be maintained in some form, since a negative max int requires 96 bits (3*32 bits, including null termination) to hold in ascii form. EAX is merely holding the ADDR. Is the full 12 bytes maintained, upon return, even when it only contains 2 bytes of ascii? Is this buffer maintained even when nothing is returned, such as the MsgBox case? I think I know part of the answer, given the logic here, but need to be certain.

How I should handle various situations very much depends on a good understanding of what's actually occurring. I also need to know how to manually resize or release a buffer if this becomes necessary. I would rather not have to try and learn this after a program is complete and something is leaking.

jj2007

Your buffer was created by subtracting its length from stack. Use OllyDbg to see how exactly it works. So you cannot rely on it after the return from the function.

Check deb macro to see what is possible, and what are typical requirements for such a macro.

Below is a very simple example:

include \masm32\MasmBasic\MasmBasic.inc   ; download
   Init
   mov ecx, 3
   .Repeat
      imul eax, ecx, 20
      dec ecx   ; we need the zero flag
      deb 4, "Loop", ecx, eax
   .Until Zero?   ; and it is still there ;-)
   Inkey
   Exit
end start

Output:
Loop
ecx             2
eax             60

Loop
ecx             1
eax             40

Loop
ecx             0
eax             20

dedndave

viewInt proc iNum:DWORD
LOCAL buffer[12]:BYTE
invoke dwtoa,iNum,addr buffer
;MsgBox hWnd,addr buffer,"Number:",MB_OK ; Uncomment to return number string in eax
lea eax, buffer ; Comment out to use the MsgBox
ret
viewInt endp


as Jochen said - the buffer is created on the stack
the assembler does all the work for you

however - there is a problem with the code if you expect to return the buffer address
lea eax, buffer
ret

it does no good to return the buffer address, as it will be destroyed
that is why the variable is called "local"
it has scope that is local to the routine
if you want to return the address, you could use a global buffer (DB in the .DATA? section)
or - perhaps a better approach is let the caller pass the address of a buffer as another parameter

to better understand the local variable....
the assembler will generate code that looks something like this
viewInt proc iNum:DWORD

        push    ebp
        mov     ebp,esp               ;EBP is now a stable reference for stack addresses
        sub     esp,12                ;reserve space for the buffer

        lea     eax,[ebp-12]          ;EAX is the stack address of the local buffer
invoke  dwtoa,[ebp+8],eax     ;[EBP+8] is the iNum parameter

;other code here

        leave                         ;same as MOV ESP,EBP then POP EBP
ret

viewInt endp

MichaelW

Here is your procedure in the original form, and in a form with no automatic prologue and epilogue code and that shows everthing including the generated code, and with comments that I hope make sense. And in case it's not obvious, after the procedure returns EAX is pointing into the "free" stack and the next thing pushed will overwrite the start of the now invalid buffer.

;==============================================================================
    include \masm32\include\masm32rt.inc
;==============================================================================
    .data
    .code
;==============================================================================

viewInt proc iNum:DWORD
LOCAL buffer[12]:BYTE
invoke dwtoa,iNum,addr buffer
lea eax, buffer
ret
viewInt endp

;==============================================================================

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
ViewInt proc iNum:DWORD
    ; [ESP+0]=return address, [ESP+4]=iNum
    push ebp                        ; preserve EBP
    ; [ESP+0]=preserved EBP,[ESP+4]=return address, [ESP+8]=iNum
    mov ebp, esp                    ; copy ESP into EBP
    ; [EBP+0]=preserved EBP,[EBP+4]=return address, [EBP+8]=iNum
    add esp, -12                    ; reserve 12 bytes of stack for buffer
    lea eax, BYTE PTR ss:[ebp-12]   ; load address of buffer into EAX
    push eax                        ; push address
    push DWORD PTR ss:[ebp+8]       ; push iNum
    call dwtoa
    lea eax, BYTE PTR ss:[ebp-12]   ; load address of buffer into EAX
    mov esp, ebp                    ; restore ESP
    ; [ESP+0]=preserved EBP,[ESP+4]=return address, [ESP+8]=iNum
    pop ebp                         ; recover EBP
    ; Stack now as it was at entry
    ; [ESP+0]=return address, [ESP+4]=iNum
    ret 4                           ; return and remove iNum from stack
ViewInt endp
OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef

;==============================================================================
start:
;==============================================================================

    inkey
    exit
;==============================================================================
END start


Well Microsoft, here's another nice mess you've gotten us into.

mywan

Ok, now I see what was throwing me. Since this function was not meant to be used in the code, only to provide feedback at one particular point in time, then the fact that the stack space was released did not in itself delete the data in that location. It could therefore successfully read this data immediately upon return, even through this data was free to be overwritten by any future action. Exactly what I needed to know, but the successful read on return was throwing my thinking. Didn't really expect that at the time.

Thanks :bgrin: I generally learn by breaking stuff, and didn't quiet understand why it didn't appear to break :icon_eek:

dedndave

QuoteIt could therefore successfully read this data immediately upon return,
even through this data was free to be overwritten by any future action.

that may work - but it isn't really kosher   :P
certainly not what we'd call "good programming practice"

if you want to do that - let the caller declare the local buffer and pass the address to the function

jj2007

Quote from: dedndave on July 22, 2012, 06:55:20 PM
as Jochen said - the buffer is created on the stack
the assembler does all the work for you

however - there is a problem with the code if you expect to return the buffer address
lea eax, buffer
ret

it does no good to return the buffer address, as it will be destroyed

Well, yes and no :biggrin:
include \masm32\include\masm32rt.inc

.code
viewInt proc iNum:DWORD
LOCAL buffer[800]:BYTE
invoke dwtoa, iNum, addr buffer
lea eax, buffer
ret
viewInt endp

start: invoke viewInt, 123
MsgBox 0, eax, "Hi folks:", MB_OK
exit

end start


(but I agree it's awfully bad programming practice; try playing with the buffer size - apparently MessageBox uses about 250 bytes of stack, print a bit less)

mywan

Quote from: dedndave on July 22, 2012, 07:24:06 PM
QuoteIt could therefore successfully read this data immediately upon return,
even through this data was free to be overwritten by any future action.

that may work - but it isn't really kosher   :P
certainly not what we'd call "good programming practice"

if you want to do that - let the caller declare the local buffer and pass the address to the function
Yes, I understand. The function was initially written to simply display a MsgBox from within the function itself. Information only purposes with no real program use outside that function. I then wanted to test other assumptions, being so new to assembly. One being that this local buffer should be cleared on return. I therefore returned an addr to it in eax to read after the function returned. Logic being it shouldn't work if the buffer is cleared. Only it did work and I needed to find out why.

I do most of my learning by testing. Like in science you don't test just the things that are hypothesised to work, you also test what shouldn't work to avoid selection bias.

dedndave

some things, you take on faith   :biggrin:
Marie Curie would tell you that
also - i am sure there were some early Chinese guys that played with things that go boom

if i want to see what you are trying to see, i usually use one of these
        INVOKE  MessageBox,0,uhex$(eax),0,0
        INVOKE  MessageBox,0,ustr$(dwGlobalVar),0,0
        INVOKE  MessageBox,0,str$(dwLocalVar),0,0


of course, in the learning stage, i think it is a good idea to look up the macro code
see what the macro does - see how any called functions work
so you know what goes on in the bacjground

mywan

Marie Curie died from the effects of radiation poisoning :(
Fortunately my computer is taking all the risk  :eusa_dance:
Personally I'm the type of person who never could memorize the multiplication tables, yet I can do calculus in my head. Self taught since the school system seemed incapable of dealing with the subject at the level I needed to learn. Even had an algebra teacher that thought any question concerning "why" was just a way to be a troublemaker, and would punish you accordingly. Failed that class. Not even sure what the word "faith" means.

I'm dissecting some of the code to figure out how to dynamically allocate stack space without excessive waste. Such as when the variability of data size is larger than I want to allocate for smaller bits of resultant data. This will be more relevant when I start writing the routines for reading the config file. The code bits provided, I think, gives me what I need to dissect to do this. Any suggestions or code bits is very much appreciated. I still have a long way to go.

It doesn't matter if I conceptualize it in a completely different way, your input from your own perspective remains just as valuable. Thanks :t

raymond

Quotestart:    invoke viewInt, 123
    MsgBox 0, eax, "Hi folks:", MB_OK
    exit

In case some readers of this thread might be misled, the "MsgBox" in the above code is a macro in JJ's arsenal which calls the MessageBox API. (In standard assembly language, it would be "invoke MessageBox," followed by the four parameters.)

Four parameters would thus be pushed on the stack and destroy part of the LOCAL left behind by the previous call to dwtoa (which required only two parameters). Only garbage would be expected to be displayed by the Message Box in this case.
Whenever you assume something, you risk being wrong half the time.
http://www.ray.masmcode.com

jj2007

Ray,

Thanks for reminders. However, MsgBox has been around long before I joined the forum, it's standard Masm32. Furthermore, the buffer gets trashed not by the four parameters pushed for MessageBox (the API behind MsgBox), but rather somewhere deep in the OS. You need over 50 DWORDS to be on the safe side (my sample above works fine - no garbage). Fascinating - with OllyDbg you could see where it actually happens ;-)

Cheers, JJ