News:

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

Question about stack balancing

Started by dicky96, September 05, 2006, 12:24:06 PM

Previous topic - Next topic

dicky96

At least I think it is a stack problem causing my program to crash

Here is what is causing the problem, stripped down to it's basic form


DlgProc
....
....
....
      invoke ParseString,addr RecvBuffer,addr HexBuffer,addr ParseBuffer

DlgProc endp

ParseString proc uses ebx ebp esi edi BinData:DWORD, AsciiData:DWORD, TextBuffer:DWORD

;   mov esi,BinData
;   mov edi,AsciiData
;   mov edx,TextBuffer
;   xor ebp,ebp
;       ......
;       ......
;       ......
        call ParseZLoop
        ret

ParseZLoop
;      .....
;      .....
;      .....
        ret

ParseString endp

As soon as I call ParseZLoop my program just dissappears, no windows error message, it's just gone from the Task Manager as well as the screen.  I trierd REM out the lines that initialise esi,edi,ebp,edx and it is still crashing I also simplified ParseZLoop to a single ret opcode.

Now if I try this

ParseECM proc  BinData:DWORD, AsciiData:DWORD, TextBuffer:DWORD

;   mov esi,BinData
;   mov edi,AsciiData
;   mov edx,TextBuffer
;   xor ebp,ebp
        call ParseZLoop
        ret

ParseZLoop
        ret

ParseString endp

Then it does not crash, just returns to the main dialog without doing anything useful, as expected.  So i figure it is a stack problem. But obviously I have the proc USES syntax as I'm gonna use those registers and need to preserve them

But I thought that proc USES just pushes the registers and pops them when the procedure ends so surely here we have

on entry to ParseString

push ebx
push ebp
push esi
push edi

call ParseZLoop -> push program counter
ret                  <- pop program counter

ret
pop edi
pop esi
pop ebp
pop ebx

So what's the problem causing the crash??  OK I'm not sure which order the USES statement pushes/pops the registers but the above seems about right to my mind.

TIA for helping

dicky

Phoenix

Try to avoid using ebp:

Quote%EBP - Base Pointer
    This 32-bit register is used to reference all the function parameters and local variables in the current stack frame. Unlike the %esp register, the base pointer is manipulated only explicitly. This is sometimes called the "Frame Pointer".

dicky96

Thanks Phoenix
I can accept the advice and not use ebp and I'll bet that will fix things

.... but I still don't understand the actual reason why my program bombs as I thought the stack pointer was ESP and I don't see where I was unbalancing the stack

tenkey

You have ParseZLoop inside the ParseString PROC.

ParseZLoop is not recognized as a PROC, but the RET in it is interpreted as a return from ParseString. With EBP unchanged, it will simply return directly from ParseString.

In a PROC, EBP is used to reset the stack on a RET. If you don't change EBP, the stack is reset to where it was on PROC entry. Thus you will not get expected results if ParseZLoop is called from somewhere in the middle of ParseString. Instead of returning to ParseString, it will abort ParseString.

Move the ParseZLoop subroutine outside the ParseString PROC.
A programming language is low level when its programs require attention to the irrelevant.
Alan Perlis, Epigram #8


hutch--

dicky,

All that is happening with your register usage is you are assuming a stack frame with the PROC layout you are using then modifying one of the registers required to maintain a stack frame. If you use a normal MASM "PROC / ENDP" then you do not modify either EBP or ESP as they are pointers to the base and stack addresses respectively.

When you write very small procedures that get called at a very fast rate, you can write a procedure that does not have a stack frame which does two things, reduces the call overhead and make EBP available within your procedure but such procedures are a bit more restricted in what you can do with them.

With a normal procedure its a good coding habit to start using LOCAL variables first then once you have the proc up, running and reliable you can see if you can make it faster by replacing local variables with registers but if you start the other way around, you usually run out of registers before you get the procedure working.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

dicky96

Thanks again Hutch - u must get fed up of digging me out of the dirt on a regular basis  :lol

I think I need (ok, I WANT) to know more about stack frames and the use of this base address register

Is there some good tutorial on this topic

Also how to write a procedure that does not use a astack frame would be interesting

In reality this ParseZLoop is just a subroutine that gets called often from different parts of my procedure, so I just stuck it in a call --- ret so I don't have to writre irt several times

But of course you knew that.....

dicky

Petroizki

Quote from: dicky96 on September 06, 2006, 08:54:41 AMAlso how to write a procedure that does not use a astack frame would be interesting

The easiest way (IMO) is to use my pmacros (http://www.masm32.com/board/index.php?topic=1063.0) :wink

Ratch

dicky96,

Quote
Also how to write a procedure that does not use a astack frame would be interesting

     Because you asked for it, I have a license to show you.  This method is not for everyone, especially beginners.  If it is too heavy for you, just forget it and learn subroutines the conventional way.  Ask if you have any questions.  Ratch

http://www.masm32.com/board/index.php?topic=380.15   reply #15
http://www.masm32.com/board/index.php?topic=4650.0
http://www.masm32.com/board/index.php?topic=3938.0   reply #10
http://www.masm32.com/board/index.php?topic=2371.0

dicky96

#9
Hi guys

Well, I'm still having Stacks of problems LOL

I've come back to this part of my code, and re-wrote it without using ebp, but I'm still having problems.  I

have a procedure that concatenates both asciiz strings and non-termianted data strings that have a len 

parameter to make one big string.

I still couldn't understand why using EBP was screwing up my software, as I have another procedure in this

same program that has the Proc USES ebp.... syntax and that one works.  The only difference I can see is taht

procedure does not call subroutines within itself and this one does.  So here is the results of my

experimenting(or messing about!)

Here is example 1/  This has the Proc Uses ....  syntax

ParseMess proc uses ebx esi edi BinData:DWORD, AsciiData:DWORD, TextBuffer:DWORD

   mov esi,BinData
   mov edi,AsciiData
   mov edx,TextBuffer

   lea ebx,ParseHeader      ;address of header asciiz string
   call Zloop         ;write to output buffer
                  
                  
   mov ebx,[offset_Data1]      ;offset to data
   mov cl, byte ptr [ebx+esi]   ;byte count
   call Cloop   

   lea ebx,ParsePRO      ;address of Pro asciiz
   call Zloop      
                  
   mov ebx,[offset_DataPro]   ;offset to ProData data
   mov cl, byte ptr [ebx+esi]   ;byte count
   call Cloop   
                  
   lea ebx,ParseCert      ;address of Cert asciiz
   call Zloop      
                  
   mov ebx,[offset_CertData]   ;offset to Cert data
   mov cl, byte ptr [ebx+esi]   ;byte count
   call Cloop
                  
   ret


Zloop:
      mov al,byte ptr [ebx]               
      .if al!=0                  
         mov byte ptr [edx],al
         inc ebx                  ;next char
         inc edx                  ;next char
         jmp Zloop
      .endif
      mov eax,eax
      ret


Cloop:                              
      shl cl,1                  ;two bytes in ascii for 1 byte in bin
      add ebx,1                  ;first char is one byte after len byte
      shl ebx,1                  ;ebx = ebx*2            

      mov al,byte ptr [ebx+edi]      ;get ascii char
      mov byte ptr [edx],al         ;and store in output buffer
      inc ebx                     ;next source
      inc edx                   ;next dest
      dec cl
      .if cl!=0                  ;until cl = 0
         jmp Cloop
      .endif                        
      ret

ParseMess endp

What is happening in this instance is the first time Zloop is called, it copies the first asciiz string

correctly, then the Zloop subroutine does not RET properly.  It quits the ParseMess procedure without the rest

of the processing occuring.  I was interested to see what is causing this so I used OllyDebug.

Now this is how MASM is assembling ZLoop


00401590  /$ 8A03           /MOV AL,BYTE PTR DS:[EBX]
00401592  |. 0AC0           |OR AL,AL
00401594  |. 74 06          |JE SHORT LemonAid.0040159C
00401596  |. 8802           |MOV BYTE PTR DS:[EDX],AL
00401598  |. 43             |INC EBX
00401599  |. 42             |INC EDX
0040159A  |.^EB F4          \JMP SHORT LemonAid.00401590
0040159C  |> 8BC0           MOV EAX,EAX
0040159E  |. 5F             POP EDI
0040159F  |. 5E             POP ESI
004015A0  |. 5B             POP EBX
004015A1  |. C9             LEAVE
004015A2  \. C2 0C00        RETN 0C      <-- My ParseMess Proc incorrectly terminates here :(

And this is how it assembles Cloop.

004015A5  /$ D0E1           /SHL CL,1
004015A7  |. 83C3 01        |ADD EBX,1
004015AA  |. D1E3           |SHL EBX,1
004015AC  |. 8A041F         |MOV AL,BYTE PTR DS:[EDI+EBX]
004015AF  |. 8802           |MOV BYTE PTR DS:[EDX],AL
004015B1  |. 43             |INC EBX
004015B2  |. 42             |INC EDX
004015B3  |. FEC9           |DEC CL
004015B5  |. 0AC9           |OR CL,CL
004015B7  |. 74 02          |JE SHORT LemonAid.004015BB
004015B9  |.^EB EA          \JMP SHORT LemonAid.004015A5
004015BB  |> 5F             POP EDI
004015BC  |. 5E             POP ESI
004015BD  |. 5B             POP EBX
004015BE  |. C9             LEAVE
004015BF  \. C2 0C00        RETN 0C

Also at the RET on the end of ParseMess I also have the following, but that never gets executed

           POP EDI
           POP ESI
           POP EBX
           LEAVE
           RETN 0C

So first question - why is MASM changing my simple RET at the end of Zloop and Cloop to add this extra code -

as that is what is causing my program to behave in this strange manner.  My best guess is I have found some

strange bug in the Proc USES macro???




So next I changed my software like below example 2/


ParseMess proc  BinData:DWORD, AsciiData:DWORD, TextBuffer:DWORD

   push ebx
   push esi
   push edi

   mov esi,BinData
   mov edi,AsciiData
   mov edx,TextBuffer

   lea ebx,ParseHeader      ;address of header asciiz string
   call Zloop         ;write to output buffer
                  
                  
   mov ebx,[offset_Data1]      ;offset to data
   mov cl, byte ptr [ebx+esi]   ;byte count
   call Cloop   

   lea ebx,ParsePRO      ;address of Pro asciiz
   call Zloop      
                  
   mov ebx,[offset_DataPro]   ;offset to ProData data
   mov cl, byte ptr [ebx+esi]   ;byte count
   call Cloop   
                  
   lea ebx,ParseCert      ;address of Cert asciiz
   call Zloop      
                  
   mov ebx,[offset_CertData]   ;offset to Cert data
   mov cl, byte ptr [ebx+esi]   ;byte count
   call Cloop
   
                  
   pop edi
   pop esi
   pop ebx
   ret



Zloop:
      mov al,byte ptr [ebx]               
      .if al!=0                  
         mov byte ptr [edx],al
         inc ebx                  ;next char
         inc edx                  ;next char
         jmp Zloop
      .endif
      mov eax,eax
      ret



Cloop:                           
      shl cl,1                  ;two bytes in ascii for 1 byte in bin
      add ebx,1                  ;first char is one byte after len byte
      shl ebx,1                  ;ebx = ebx*2            

      
      mov al,byte ptr [ebx+edi]      ;get ascii char
      mov byte ptr [edx],al         ;and store in output buffer
      inc ebx                     ;next source
      inc edx                   ;next dest
      dec cl
      .if cl!=0                  ;until cl = 0
         jmp Cloop
      .endif                        
      ret
      

This now assembles Zloop as follows

00401590  /$ 8A03           /MOV AL,BYTE PTR DS:[EBX]
00401592  |. 0AC0           |OR AL,AL
00401594  |. 74 06          |JE SHORT LemonAid.0040159C
00401596  |. 8802           |MOV BYTE PTR DS:[EDX],AL
00401598  |. 43             |INC EBX
00401599  |. 42             |INC EDX
0040159A  |.^EB F4          \JMP SHORT LemonAid.00401590
0040159C  |> 8BC0           MOV EAX,EAX
0040159E  |. C9             LEAVE
0040159F  \. C2 0C00        RETN 0C

And Cloop as follows

004015A2  /$ D0E1           /SHL CL,1
004015A4  |. 83C3 01        |ADD EBX,1
004015A7  |. D1E3           |SHL EBX,1
004015A9  |. 8A041F         |MOV AL,BYTE PTR DS:[EDI+EBX]
004015AC  |. 8802           |MOV BYTE PTR DS:[EDX],AL
004015AE  |. 43             |INC EBX
004015AF  |. 42             |INC EDX
004015B0  |. FEC9           |DEC CL
004015B2  |. 0AC9           |OR CL,CL
004015B4  |. 74 02          |JE SHORT LemonAid.004015B8
004015B6  |.^EB EA          \JMP SHORT LemonAid.004015A2
004015B8  |> C9             LEAVE
004015B9  \. C2 0C00        RETN 0C

However that still did not fix the bug, my simple CALL - RET just does not return to the right place becuse it

seems like MASM is adding code of it's own

PS - I know I have a bug in Cloop looping to the wrong place - just ignore that pls.

dicky96

Ahh after trying a few combinations I now have this one that works


ParseMess proc uses ebx esi edi BinData:DWORD, AsciiData:DWORD, TextBuffer:DWORD

   mov esi,BinData
   mov edi,AsciiData
   mov edx,TextBuffer

   lea ebx,ParseHeader      ;address of header asciiz string
   invoke Zproc         ;write to output buffer
                 
                 
   mov ebx,[offset_Data1]      ;offset to data
   mov cl, byte ptr [ebx+esi]   ;byte count
   invoke Cproc   

   lea ebx,ParsePRO      ;address of Pro asciiz
   invoke Zproc     
                 
   mov ebx,[offset_DataPro]   ;offset to ProData data
   mov cl, byte ptr [ebx+esi]   ;byte count
   invoke Cproc   
                 
   lea ebx,ParseCert      ;address of Cert asciiz
   invoke Zproc     
                 
   mov ebx,[offset_CertData]   ;offset to Cert data
   mov cl, byte ptr [ebx+esi]   ;byte count
   invoke Cproc
                 
   ret

Zproc proc
Zloop:
      mov al,byte ptr [ebx]               
      .if al!=0                 
         mov byte ptr [edx],al
         inc ebx                  ;next char
         inc edx                  ;next char
         jmp Zloop
      .endif
      mov eax,eax
      ret
Zproc enp

Cproc proc
Cloop:                           
      shl cl,1                  ;two bytes in ascii for 1 byte in bin
      add ebx,1                  ;first char is one byte after len byte
      shl ebx,1                  ;ebx = ebx*2           

     
      mov al,byte ptr [ebx+edi]      ;get ascii char
      mov byte ptr [edx],al         ;and store in output buffer
      inc ebx                     ;next source
      inc edx                   ;next dest
      dec cl
      .if cl!=0                  ;until cl = 0
         jmp Cloop
      .endif                       
      ret
Cproc endp


Now MASM assembles my code like this
Zproc
0040145A  /$ 8A03           /MOV AL,BYTE PTR DS:[EBX]
0040145C  |. 0AC0           |OR AL,AL
0040145E  |. 74 06          |JE SHORT LemonAid.00401466
00401460  |. 8802           |MOV BYTE PTR DS:[EDX],AL
00401462  |. 43             |INC EBX
00401463  |. 42             |INC EDX
00401464  |.^EB F4          \JMP SHORT LemonAid.0040145A
00401466  |> 8BC0           MOV EAX,EAX
00401468  \. C3             RETN

Cproc
00401469  /$ D0E1           /SHL CL,1
0040146B  |. 83C3 01        |ADD EBX,1
0040146E  |. D1E3           |SHL EBX,1
00401470  |. 8A041F         |MOV AL,BYTE PTR DS:[EDI+EBX]
00401473  |. 8802           |MOV BYTE PTR DS:[EDX],AL
00401475  |. 43             |INC EBX
00401476  |. 42             |INC EDX
00401477  |. FEC9           |DEC CL
00401479  |. 0AC9           |OR CL,CL
0040147B  |. 74 02          |JE SHORT LemonAid.0040147F
0040147D  |.^EB EA          \JMP SHORT LemonAid.00401469
0040147F  \> C3             RETN


Which has the effect I expected all along.....   but noiw i'm really confused,  if an invoke is just push some stuff on the stack, followed by a CALL.... then surely an invoke with no parameters is the same as a CALL

Please help save my sanity as it feels like MASM is doing very strange things to my code - adding stuff to ret rather than just assembling it, etc etc.

TIA
dicky



dicky96

Ahaaa I just realised that tenkey explained all this about 9 days ago when i first posted this thread

quote:
ParseZLoop is not recognized as a PROC, but the RET in it is interpreted as a return from ParseString. With EBP unchanged, it will simply return directly from ParseString.


Please forgive me for being so dumb not to undertand his post until I did enough experiments to find all this out for myself the hard way....  thanks Tenkey, now I understand what you were trying to tell me all along:  :U

So, in retrospect, is all this due to a bug (or limitation) in the PROC macro?  not being able to tell the difference between a RET from the procedure, and a RET from a subroutine called within the procedure.  At least that is how I understand it now. 

And as a matter of interest where are these things like PROC USES.... defined?  can they be changed or, errmmmm, debugged?  :wink

Thanks again   
dicky.........  still a beginner i guess   :red  ::)  :red

zooba

Quote from: dicky96 on September 14, 2006, 09:19:24 PM
So, in retrospect, is all this due to a bug (or limitation) in the PROC macro?  not being able to tell the difference between a RET from the procedure, and a RET from a subroutine called within the procedure.  At least that is how I understand it now. 

Basically, yes, though it's as much the fault of the standard form of a procedure. The epilogue macro (more on this next) contains code to clean up a stack frame which, as part of this, moves the stack pointer to just below the original procedure entry. So then when the actual return command is called it returns from the original procedure.

There are two solutions. The first is to give your subroutine a stack frame. This is (generally) bad. If your subroutine needs a stack frame it should be its own procedure.

The second is to use RETN at the end of your subroutine. RET is actually a directive (like PROC) which calls an epilogue macro. RETN is the actual instruction and will simply pop the return address off and go there, rather than cleaning up the stack.

Quote from: dicky96 on September 14, 2006, 09:19:24 PM
And as a matter of interest where are these things like PROC USES.... defined?  can they be changed or, errmmmm, debugged?  :wink

I mentioned the epilogue macro used for the RET directive above. There is also a prologue macro for PROC. Both of these macros can be substituted for your own. There is a bit of information in the MASM32.HLP file which comes with MASM32. I have also posted everything I know about them here, at the MSDN Wiki.

However, I don't think that overriding the prologue and epilogue code will help. Just use RETN at the end of your subroutine :wink

Cheers,

Zooba :U

dicky96

#13
quote
RET is actually a directive

Ahh that explains why I could not find RET in the assembler mnemonics but could see RETN - I thought it was just a missprint

but then again I once got lost on the London Underground as I thought that "Bank" was short for Enbankment"  :bdg