News:

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

Manually creating local stack variables

Started by tekhead009, July 29, 2005, 06:33:22 PM

Previous topic - Next topic

tekhead009

I'm using local stack variables in a few procedures. I wan't to create the local vars manually as opposed to using the LOCAL directive. I'd also like to use easily readable names for these local stack variables, rather then [EBP - X] to access their data. Is using an EQU statement customary for doing this?

Finally, at the end of my procedure if I use ret -X where X is the number of bytes my local vars are taking up. It works, but it doesn't seem right to me. I thought I had to provide a positive number - the number of bytes the procedures arguments use.



MyProc
MyLocalVar   equ [EBP - 4]

push EBP
mov EBP,ESP
add EBP, -4     ;Make room for MyLocalVar

mov hLocalVar,EAX ;Stick data into MyLocalVar [EBP - 4]

mov ECX,MyLocalVar ;Use MyLocalVar for whatever purpose

pop EBP

ret -4 ;**** Cleanup stack **** This works, but is it right?

MyProc endp

P1

Stack can be very fragil to the mistakes of the programmer.

I would suggest you allocation some memory from the OS for this kind of work.  Plus it give you more protection from certain kinds of errors, without crashing the OS.  Create a structure and then assume into the allocated memory for easy naming access.

When programming the stack, a BSOD is not welcomed, but a memory error is a little easier on you.

Regards,  P1  :8) 

Randall Hyde

Quote from: tekhead009 on July 29, 2005, 06:33:22 PM
I'm using local stack variables in a few procedures. I wan't to create the local vars manually as opposed to using the LOCAL directive. I'd also like to use easily readable names for these local stack variables, rather then [EBP - X] to access their data. Is using an EQU statement customary for doing this?
Yes, the EQU or TEXTEQU directive is the customary way to do this if you're not using LOCAL.
Though I'm not sure way you want to do it manually.

Quote
Finally, at the end of my procedure if I use ret -X where X is the number of bytes my local vars are taking up. It works, but it doesn't seem right to me. I thought I had to provide a positive number - the number of bytes the procedures arguments use.

No. Ret n is used to clean up parameters, not local variables. Use LEAVE or mov esp, ebp/pop ebp to clean up the locals. You might check out the chapters on procedures in the 16-bit edition of Art of Assembly for all the gory details. (it's 16-bits, but the same principles apply to 32-bit code).
Cheers,
Randy Hyde

tekhead009

QuoteStack can be very fragil to the mistakes of the programmer.

I've learned that several times over now.

I'll probably do as you suggested and allocate memory for most applications, but I'm working on a recursive proc. Wouldn't allocating/deallocating memory be slower than just using the stack? Of course, it would be easier though. My next post will probably be about how to allocate more stack memory since I'll probably run out.



QuoteNo. Ret n is used to clean up parameters, not local variables. Use LEAVE or mov esp, ebp/pop ebp to clean up the locals.

I just learned that by traceing the procedure with OllyDbg, you managed to sneak in your post before I got to it. I'm now using LEAVE, then RET 4.  I'm interested in going back with Olly and seeing why exactly ret -4 worked for me without crashing.

QuoteThough I'm not sure way you want to do it manually.

I just wanted to learn it, I do like to use the higher level directives, but I also like to know how to do things manually. That and I've been thinking about learning how to program microcontrollers recently, which wouldn't give me the luxuries that MASM 8.x does.

I'll also add that the 16-bit edition of AoA was one of my first sources for Assembly. I'll admit that I only got though chapter 3 before purchasing a book on 32-bit programming, but I didn't even need to need skim the the sections covered from your book. I've still the notes stashed away on my bookshelf too.

Thanks for the help!

Ratch

#4
tekhead009:
Hi tek,
     So you want to create stack variables by the mucho macho manual method, eh?  As a confirmed programming maschochist, I can show you how I do it.  Look at the code snippet of the subroutine below.  The key to keeping everything straight is to set up a STRUC which maps out how the stack will look after you decrement the ESP register to make some local storage space.  So the structure below defines the parameters that were passed to the subroutine, the return address and the local variables.  Then all you need to do is keep your head screwed on correctly and compensate for every PUSH and POP you do that changes the value the of ESP.  Notice how the structure helps you determine how much local storage you need by simply subtracting DBCC1 from DBCC2.  Similiarly for the RET instruction.  Now comes the fun part.  You need to compensate the ESP for each PUSH and POP when you are referencing any variable within the structure.   Notice how INVOKEing TextOut requires a +1*DWORD, 2*DWORD, etc for every previous PUSH.  Get one of those compensations wrong, and you could jump into forever never land.  The advantage of this method is that you can forget about the PROC directive and sometimes PUSH parameters long before they are needed, but happen to be available.  Also it gives you complete control of the stack.  The code snippet below was from my MASM translation of the C version of DEVCAPS2 from Charles Petzold"s book on windows programming.  If you are interested in the whole program, let me know.  Anyway, that is the way I program.  Ratch


.CODE
DoBitCodedCaps:
DBCC STRUC 
DBCC1        =  $
szBuffer BYTE 80 DUP (?)
iDevCaps DWORD ?
EBXSAVE DWORD  ?
EDISAVE DWORD  ?
DBCC2         = $
return  DWORD  ?
DBCC3         = $
hdc     DWORD  ?
hdcinfo DWORD  ?
cxChar  DWORD  ?
cyChar  DWORD  ?
iType   DWORD  ?
DBCC4         = $
DBCC ENDS

SUB ESP,(DBCC2-DBCC1)                 ;make local variable space
MOV [ESP.DBCC.EBXSAVE],EBX            ;save EBX
MOV [ESP.DBCC.EDISAVE],EDI            ;save EDI

MOV EDI,[ESP.DBCC.iType]              ;EDI=iType

INVOKE GetDeviceCaps,[ESP.DBCC.hdcinfo+1*DWORD],[bitinfoDEF+4*EDI]
MOV [ESP.DBCC.iDevCaps],EAX

INVOKE TextOut,[ESP.DBCC.hdc+4*DWORD],[ESP.DBCC.cxChar+3*DWORD],\
                [ESP.DBCC.cyChar+2*DWORD],[bitinfoTEXT+4*EDI],\
                [bitinfotitlesSIZE+4*EDI]

XOR EBX,EBX
.WHILE EBX < [iTypeSIZE+4*EDI]
   MOV ECX,[iTypeDEF+4*EDI]
   MOV EAX,[iTypeTEXT+4*EDI]
   MOV ECX,[ECX+4*EBX]
   MOV EDX,[EAX+4*EBX]
   AND ECX,[ESP.DBCC.iDevCaps]

    PUSH @ L_NO                       ;***for wsprintf
   .IF ECX
     MOV [ESP],@ L_YES                ;***for wsprintf
   .ENDIF

   LEA ECX,[ESP.DBCC.szBuffer+1*DWORD] ;for wsprintf
   INVOKIT wsprintf,ECX,BYTER('%-55s %3s',0),EDX
   ADD ESP,4*DWORD                    ;balance stack

   PUSH EAX                           ;***for TextOut
   LEA ECX,[ESP.DBCC.szBuffer+1*DWORD] ;for TextOut
   LEA EAX,[EBX+3]
   MUL [ESP.DBCC.cyChar+1*DWORD]
   INVOKIT TextOut,[ESP.DBCC.hdc+4*DWORD],[ESP.DBCC.cxChar+3*DWORD],EAX,ECX
INC EBX                              ;loop index
.ENDW

MOV EBX,[ESP.DBCC.EBXSAVE]           ;restore EBX
MOV EDI,[ESP.DBCC.EDISAVE]           ;restore EDI
ADD ESP,(DBCC2-DBCC1)                ;release local variable space
RET (DBCC4-DBCC3)

tekhead009

I like the idea of creating a structure to keep track of all the local vaiables created. Much better idea than trying to add all of them together as I was doing before.

I'm not sure, but because I'm using EBP as a reference to my local vars and parameters, I don't need to compensate for every push/pop within my procedure.

Ratch

Hi tek,
Quote
I'm not sure, but because I'm using EBP as a reference to my local vars and parameters, I don't need to compensate for every push/pop within my procedure.

     Right you are.  You are using the stack frame method, so EBP "freezes" that portion of the stack you are referencing.  But consider the overhead of setting up/releasing the stackframe.  And don't forget that it takes away a precious register in an already register starved CPU.  I prefer "stack frameless" programming, because it is faster and gives me another register and more flexibility.  By flexibility, I mean  being able to PUSH parameters when they are easily available, but before they are really needed.  This requires knowing what is happening to the stack at all times. The STRUC method is the best way I know of to keep the stack organization straight.  Ratch

Randall Hyde

Quote from: tekhead009 on July 29, 2005, 08:06:57 PM
I just learned that by traceing the procedure with OllyDbg, you managed to sneak in your post before I got to it. I'm now using LEAVE, then RET 4.  I'm interested in going back with Olly and seeing why exactly ret -4 worked for me without crashing.

Thanks for the help!

Probably you got lucky, and the calling code quickly moved EBP into ESP and returned itself, thus undoing the mess that was made of the stack. I don't believe the value following RET is sign extended, so you were probably subtracting 65532 from the stack pointer. I could be wrong about the sign extension, though.
Cheers,
Randy Hyde