News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

About the stack

Started by buraks, January 26, 2020, 11:00:47 AM

Previous topic - Next topic

buraks

Hello,

There is a caller in an exe:


   push par1
   push par2
   call myFunction


And myFunction is in a DLL:


myFunction proc param1:dword, param2:dword
   invoke MessageBox........

   pop ebp
   mov eax, 1
   retn
myFunction endp


why do we need the last 3 lines in myFunction instead of just "ret"?
That is:


myFunction proc param1:dword, param2:dword
   invoke MessageBox.........

   ret
myFunction endp



jj2007

You don't need them, a simple "ret" would do the job:

include \masm32\include\masm32rt.inc ; plain Masm32 for the fans of pure assembler

.code
myFunction proc param1:dword, param2:dword
   invoke MessageBox, 0, param1, param2, MB_OK

   ret ; comment out to see the difference

   pop ebp
   mov eax, 1
   retn 2*DWORD
myFunction endp

start:
print str$(esp), " stack", 13, 10
push chr$("Title")
push chr$("Text")
call myFunction
inkey str$(esp), " stack", 13, 10
exit

end start


"ret" is a built-in macro, "retn" is an instruction; to understand what really happens, use a debugger like Olly

aw27

AFAIK, RETN means return near and is obsolete because in the flat model all calls are near (or far, you decide).
The Intel manuals from this century do not even mention RETN, they mention RET and it has the opcode C3.

What MASM does is a trick, when it sees RET, it fills up the Epilogue for you.

You can prevent MASM from being a smart ass by prepend this before the procedure(s):

OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE




jj2007

Here is a more detailed version showing what happens under the hood:
include \masm32\include\masm32rt.inc ; plain Masm32 for the fans of pure assembler

.code
myFunction1 proc param1:dword, param2:dword
   print param1
   print str$(param2), 13, 10

   nops 2
   ret ; the ret macro
myFunction1 endp
  nop

myFunction2 proc param1:dword, param2:dword
   print param1
   print str$(param2), 13, 10

   nops 2
   pop ebp ; the version that UAsm will use for the ret macro
   mov eax, 1
   retn 2*DWORD
myFunction2 endp
  nop

myFunction3 proc param1:dword, param2:dword
   print param1
   print str$(param2), 13, 10

   nops 2
   leave ; the version that MASM will use for the ret macro
   retn 2*DWORD
myFunction3 endp
  nop

start:
  print str$(esp), " stack", 13, 10
  int 3
  invoke myFunction1, chr$("calling "), 1
  invoke myFunction2, chr$("calling "), 2
  invoke myFunction3, chr$("calling "), 3
  inkey str$(esp), " stack", 13, 10
  exit

end start


The nops serve to make the disassembly look clearer. "print" is a Masm32 macro that uses Stdout:
Command                             Comments
push ebp                            ; NewFile.myFunction1(guessed Arg1,Arg2)
mov ebp, esp
push dword ptr [ebp+8]              ; ÚArg1 => [Arg1]
call StdOut                         ; ÀNewFile.StdOut
push offset ??001F                  ; ÚArg2 = NewFile.??001F
push dword ptr [ebp+0C]             ; ³Arg1 => [Arg2]
call dwtoa                          ; ÀNewFile.dwtoa
push offset ??001F                  ; ÚArg1 = NewFile.??001F
call StdOut                         ; ÀNewFile.StdOut
push offset ??0026                  ; ÚArg1 = ASCII CR,LF
call StdOut                         ; ÀNewFile.StdOut
nop
nop
pop ebp                             ; voilà, this is
retn 8                              ; the ret MACRO!
nop                                 ;
push ebp                            ; NewFile.myFunction2(guessed Arg1,Arg2)
mov ebp, esp
push dword ptr [ebp+8]              ; ÚArg1 => [ARG.1]
call StdOut                         ; ÀNewFile.StdOut
push offset ??002D                  ; ÚArg2 = NewFile.??002D
push dword ptr [ebp+0C]             ; ³Arg1 => [ARG.2]
call dwtoa                          ; ÀNewFile.dwtoa
push offset ??002D                  ; ÚArg1 = NewFile.??002D
call StdOut                         ; ÀNewFile.StdOut
push offset ??0034                  ; ÚArg1 = ASCII CR,LF
call StdOut                         ; ÀNewFile.StdOut
nop
nop
pop ebp                             ; looks like the
mov eax, 1                          ; ret MACRO, but you
retn 8                              ; added a return value
nop
push ebp                            ; NewFile.myFunction3(guessed Arg1,Arg2)
mov ebp, esp
push dword ptr [ebp+8]              ; ÚArg1 => [ARG.1]
call StdOut                         ; ÀNewFile.StdOut
push offset ??003B                  ; ÚArg2 = NewFile.??003B
push dword ptr [ebp+0C]             ; ³Arg1 => [ARG.2]
call dwtoa                          ; ÀNewFile.dwtoa
push offset ??003B                  ; ÚArg1 = NewFile.??003B
call StdOut                         ; ÀNewFile.StdOut
push offset ??0042                  ; ÚArg1 = ASCII CR,LF
call StdOut                         ; ÀNewFile.StdOut
nop
nop
leave                               ; the shortest version
retn 8
nop


Normally, procedures ("functions") first create a stack frame using
push ebp                            ; NewFile.myFunction2(guessed Arg1,Arg2)
mov ebp, esp

... then do their job, and after that quit using either...
pop ebp
retn DWORD*(number of args)

... or ...
leave
retn DWORD*(number of args)

It gets slightly more complicated with local variables:
myFunction1 proc arg1, arg2
Local v1, v2, v3

  Command                             Comments
  push ebp                            ; NewFile.myFunction1(guessed Arg1,Arg2)
  mov ebp, esp
  add esp, -0C                        ; create space for 3 local DWORDs
...
  mov esp, ebp                        ; the ret MACRO again...
  pop ebp
  retn 8


This would be an equivalent but shorter return:
  leave
  retn 8


As you can when comparing the screenshot from the Intel manual to the disassembly, it is the C2 0800 encoding, i.e. near return and pop imm16.

Debuggers like Olly use retn to distinguish the instruction from the ret macro used by MASM and compatible assemblers like UAsm and AsmC.

Likewise, with MASM & friends you can use retn to override the macro creation as in your code:

   pop ebp
   mov eax, 1
   retn

So ret means "insert the macro, e.g. leave plus retn 8", retn means "use only the ret instruction"

aw27

Holly crap  :badgrin:, Intel says RET is an instruction but some people insist it is a macro.

Yeah, everything is a macro and we are all Charlie.  :icon_idea:




hutch--

 :biggrin:

If in doubt, trust the opcode.  :tongue:

jj2007

Quote from: AW on January 26, 2020, 09:21:43 PM
Holly crap  :badgrin:, Intel says RET is an instruction but some people insist it is a macro.

The Microsoft Macro Assembler (MASM) inserts one, two, three or more instructions when it sees ret. Same for UAsm and AsmC. Commonly such behaviour is expected from a macro :cool:

In the words of an assembler developer:
Quote from: johnsa on October 16, 2017, 08:45:12 PMRET is what triggers epilogue generation

I am no expert for other assemblers (this is a MASM forum, including the UAsm and AsmC clones), but it seems that FASM does the same (answer by vid):
Quotethere is no "ret" isntruction, i believe you mean "retn". "ret" is pseudo-opcode (macro in FASM) to return

It seems even more confusing in FASM:
QuoteHave you spelled RET as "ret" (i.e. all lowercase)? It is important because the proc macro defines the ret macro but not RET nor any other combination of lower and upper case.

Google "nasm" "ret macro" to see how NASM behaves (messy as well, apparently RET is the macro, ret is the instruction; FASM does it the other way round :rolleyes:).

Another disassembly of a simple MASM ret - quitting from somealgo proc uses esi edi ebx arg1, arg2, arg3:
pop ebx
pop edi
pop esi
mov esp, ebp
pop ebp
retn 0C


The conclusion:
- use ret (the macro) to let the assembler (MASM, UAsm, AsmC) decide which instructions are needed to quit the proc
- use retn (the instruction) if you want to do it manually, and you know what you are doing.

aw27

The only conclusions are:
1) RETN is not an Intel instruction. It is a clue for some  debuggers and assemblers to insert opcode C3 straight away.
2) RET does not trigger anything, it is an Intel Instruction. Assemblers watch it to insert the Epilogue, in the same sense that you watch the Traffic Lights to move on when they turn green.

Anyway, I think you got the idea by now.

felipe

I found this a little weird. I have always used "ret" and i have review some code that i wrote in 32 and 64 bits, that uses it, with dissasemblers and debuggers just now and i didn't see any extra line added to my code, even if i don't used "option epilogue:none". So any idea on this? I have always used masm (masm, ml, ml64).  :icon_idea:

daydreamer

if you use qeditor,there is lots of old windows help,in help meny,from the simplest macro usage to the advanced macro making,even mnemonic/opcodes dated for older cpus,but mostly still in use
my none asm creations
https://masm32.com/board/index.php?topic=6937.msg74303#msg74303
I am an Invoker
"An Invoker is a mage who specializes in the manipulation of raw and elemental energies."
Like SIMD coding

jj2007

Quote from: felipe on January 27, 2020, 05:22:05 AMi didn't see any extra line added to my code

Post an example, Felipe.

felipe

As an example you can check this lousy program (just to see the "ret" in action in some procedures in the program, ignore the errors mentioned in the post, i mean that's another topic  :rolleyes:):
http://masm32.com/board/index.php?topic=6235.0

Checking the code in the post, with the dissasembly with ida or using a debugger like x32dbg will show you the same code pertaining to the "ret" instruction.  :icon_idea:

I don't want to put images of that here, you can try if you want and it's possible to you.

I have olly too, but i have not tried with it yet to see how this "rets" show up. I will do it soon as i can.  :thup:

avcaballero

I have not done the test. Maybe all of this go from here. If I'm not wrong, you can define a macro with the name of an instruction that replaces it. In the case of "RET" this can be useful to exit from procedures using "PROC", for example to exit with arguments, etc. Perhaps the clean "RET" can be in a procedure of the type

myproc:
...
ret


felipe

I have done the same with olly and nothing is changed. As you can see in the link i posted above, as example this piece of code:


send_messag1    proc                                                    ; Makes the first message box.
                push    MB_OKCANCEL
                lea     ebx,titl
                push    ebx
                lea     ebx,content1
                push    ebx
                push    0
                call    MessageBox
                mov     value_retorn,eax                                ; We store here the button pushed.
                ret
send_messag1    endp


Looks in x32dbg as this:
Quote
00401029 | 6A 01                         | push 1                                                  |
0040102B | 8D 1D 00 30 40 00             | lea ebx,dword ptr ds:[403000]                           | 00403000:"System message"
00401031 | 53                            | push ebx                                                |
00401032 | 8D 1D 0F 30 40 00             | lea ebx,dword ptr ds:[40300F]                           | 0040300F:"Log out now?"
00401038 | 53                            | push ebx                                                |
00401039 | 6A 00                         | push 0                                                  |
0040103B | E8 5E 00 00 00                | call <logout.MessageBoxA>                               |
00401040 | A3 6C 30 40 00                | mov dword ptr ds:[40306C],eax                           |
00401045 | C3                            | ret                                                     |

And as i said, with ida and olly i see the same, there are no lines aggregated to the "original" code. So any ideas?  :icon_idea:

felipe

Oh i think i got it now, probably this "masm" behavior (don't care about other assemblers for now) of adding code when you use "ret" only happens when you use the "proc" directive, passing parameters with it, like in this code:


myFunction1 proc param1:dword, param2:dword


Correct me if i'm wrong or ignore me if i'm right... :tongue: