News:

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

Main Menu

Win10 may suppress return codes(RC) from MASM 32bit console programs

Started by MtheK, August 08, 2015, 06:53:24 AM

Previous topic - Next topic

MtheK

WARNING: this is rather long-winded...

  It appears that the "force-killer" has expanded and now screws over
programs during normal running (ie: not just during shutdown):

http://masm32.com/board/index.php?topic=2546.0

  What is happening is that I am getting an RC=0 from SOME of my MASM
console programs. This drove me CRAZY!! Why would some fail and some
work? Specifically, if I pass, say, an RC=100 back to the OS (in my
case, program CHECKSHU), the .BAT sees %errorlevel% as a 0 (proven
w/ECHOs) instead of what I passed. BUT, merely REMOVING 'user32.dll'
from my .exe (in my case, cmt'ing GetSystemMetrics as I did B 4), and
VOILA! My return code is now being passed correctly!

  As no one has ever explained what the H*$& does this during
shutdown, I can only assume that it's been taken further, for
whatever reason, in Win10 (maybe Win8?).

  Unfortunately, it's not just 'user32.dll'. Back then, I found a few
WINAPI commands that are affected, but, perhaps, there are MANY more.
My REGTODAY and SLEEP does not have 'user32.dll' yet are still
getting an RC=0, while DSNTODAY,etc work properly, so there are
probably more WINAPI commands that are causing this. 
BEEP also fails, but that's 1 of the 4 I found B 4 w/'user32.dll'.

  My "fix#1" is to assume the middle-man role, a la what I did
conceptually in the mainframe a long time ago in a galaxy far far
away; B 4 the screwed-over program ends, it writes EAX, which has the
RC, to a ".txt" file, re-loads EAX, then ends.  The next statement in
the .BAT checks for an RC=0 and if found, I assume this is Win10 and
it runs my new program, UNITVIO, which merely reads the file and
loads EAX from it (or RC=2,etc from this program), then ends.
Without this force-killer reference ('user32.dll'), the .BAT now sees
the TRUE RC that my original program passed.

  So, the key is to ensure 'user32,dll' does not statically or
dynamically (my TESTFK now gets this same S$^@) reference 'user32.dll'.
The rest of the .BAT then runs as expected w/the TRUE RC that my
program passed to the OS and, a la Win7, all is well.

  All my .BATs re-act the same way in that, for me, an RC=0 IS AN
ERROR! I do this because I have gotton RC=0 when the msg

'The process cannot access the file because it is being used by another process.'

occurs and the RC is a 0 even tho it failed! So I always make sure my
programs NEVER pass a 0. If I have to, I "convert" my RC to a 0 in
the .BAT if others are expecting a 0.

  BTW, lest you think you are safe since you don't use .BATs, think
again! For 'INVOKE CreateProcess' and 'INVOKE GetExitCodeProcess',
the latter also returns 0 !!!! So, "fix#2"; the same concept for this
too; all my callees have to save the RC to a file so the parent can
get it.

  I mention this so others don't have to spend DAYS AND DAYS AND DAYS
trying to figure out what they are doing "wrong" in their programs!!!

  Avoid Win10 if this is an issue for you until/unless it is fixed,
or come up with your own DIY to "fix" it as I did. The mainframe
would have been so easy just via a JCL DD UNIT=VIO...oh well.

  Perhaps someone could write a quickie MASM 32bit console program
to set EAX to other than 0 (a la mainframes' IEFBR14), then RET
and/or 'INVOKE ExitThread' and/or 'INVOKE ExitProcess', thru a
simple .BAT which basically just does:

BEEP.exe
set BEEPrc=%errorlevel%
echo  %~n0 %date% %time% 1=%errorlevel%,%BEEPrc% >>BEEPX.txt

(add flavor as desired)...

then add 'INVOKE GetSystemMetrics' in a dummy routine (don't run
it, just have it imbedded, so the .exe gets 'user32.dll'),
then rinse and repeat. Does ERRORLEVEL then become a 0, not what
you set? In a nutshell, that's what I'm getting...perhaps a
doubly-linked list gone awry...        :)

sinsi

How are you passing the return code from your exe, and how are you testing errorlevel in the bat?

MtheK

  As shown above for BEEP:
       MOV   EAX,nnn
       RET
  As shown above in SET for the .bat

MtheK

  From a previous PUSH WORD ML problem, a handful of my programs save ESP
at startup and check it B 4 the RET. Tho BEEP doesn't have it, I added it to test:

        .CODE

;*   This is where DOS gives us control.
mainCRTStartup:

         MOV   ESPSAVE,ESP

and at the end:

.if esp == ESPSAVE
;
.else
         INT   3
.endif

  It does not crash. If I remove the .else, then it crashes w/this:

BEEP Fri 08/07/2015 19:35:13.58 error -2147483645; 80000003h ANY(!!!) INT3 CAUSES THIS???!!!

This shows that ESP is the same as when I came in, so I'm not branching
to some unknown code.

MtheK

  It crashed where I expected it to:

FAULTING_IP:
BEEP!mainCRTStartup+730 [BEEP.ASM @ 286]
00401730 cc              int     3

            .if esp == ESPSAVE
00000728  3B 25 00000A2C R *       cmp    esp, ESPSAVE
0000072E  75 01      *       jne    @C0067
00000730  CC                  INT   3
            .endif

and my EAX is correct:

0:000> .frame /r f
0f 0012ff94 77b8b429 BEEP!mainCRTStartup+0x730 [BEEP.ASM @ 286]
eax=00000064 ebx=7ffd7000 ecx=a6ec61ca edx=77b76344 esi=00000000 edi=00000000
eip=00401730 esp=0012ff8c ebp=0012ff94 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
BEEP!mainCRTStartup+0x730:
00401730 cc              int     3

in my case, rc=100 which, for me, means all is OK.


hutch--


MtheK

  ExitProcess is un-acceptable, hardly "right", and most certainly
when SetConsoleCtrlHandler is necessary. I can't have my main, or
ANY, thread taking out ANY of my other threads, and ESPECIALLY my
abend thread, as that is stable, controls cleanup, and keeps the
force-killer at bay for up to 5 (WTK) seconds during shutdown,etc.

  I've used RET since MASM611 and have NEVER had a problem w/it until
now. Mainframe programmers are encouraged to use something similar in
"good" programs; this technique has worked for almost a half-century,
and I will NOT deviate from a proven method.

  Let's agree to dis-agree.

  Anyway, this is what I found in testing:


. ExitProcess does work, but, as I said, is NOT an option


. ExitThread also gets a fake rc=0 (dwExitCode), seemingly
contradicting the WINAPI doc and ExitProcess (assuming I'm the last
thread):

dwExitCode [in]
The exit code for the thread.
If the thread is the last thread in the process when this function is
called, the thread's process is also terminated.

except this addition:
Thread Exit Convention
Note that by convention, threads that exit normally return an exit
code of 0. Craig Anderson 6/7/2007

  Not quite sure what that means since, when I exit gracefully in
Win7 (via RET; also tested ExitThread which works (see below)), my
RC is passed correctly. It's the same in Win10, but ONLY when NO
"user32.dll" is in it ?!

  Furthermore, nowhere in this doc does it say that the return code
will NOT be honored, and even FAKED(!), under Win10, when the
executable contains "user32.dll", when returning to the OS.


. RET gets a fake rc=0 (which started all this)


  And now the last 2 require my own DIY fix. When abending, the
parent(s) USUALLY get killed within THOUSANDths of a second anyway
and so USUALLY get no chance to deal w/it; only the program can
(except Ctl-C/N; good for testing all this).

---

  All this proves that, on Win10 (at least above Win7), if a program
has a "user32.dll" in its' .exe, static or NOW dynamic, when exiting
"gracefully", its' return code is NOT HONORED and, worse, FAKED with
an rc=0!

  I now print the GetVersionEx results in all my 4 "failing" programs
to show that, whenever an rc=0 occurs, this will indicate it can't be
trusted due to Win10! Even if it stops filling in 6.2, even if the RC
is not 0, my default will be 0.0 which would then indicate that
GetVersionEx failed to work (maybe Win11?).

  Interesting that GetVersionEx may now be "obsolete", in spite of
the many people who complained about losing it. I did test that it
does return 6.2, but w/nothing to counter with (I was hoping to use
that to know when to replace the RET w/ExitThread), all I can say is
the OS is not "good".  I had to laugh when they suggested to test for
the feature itself; how exactly do you test if RET/ExitThread to the
OS works like it's supposed to?

  On the plus side, my code can remain OS-independent as my DIY fix
now is forced to control the return code to the OS properly. No
stupid WINAPI cmd required to exit to the OS, either when called as a
sub-routine or when running stand-alone.

  Finally, in WinDbg, when I reach my RET, doing 't 999999' ends up
w/this:


eax=00000064 ebx=00403a9f ecx=54d4fad5 edx=77526344 esi=00000000 edi=00000000
eip=00401a59 esp=0012ff8c ebp=0012ff94 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
BEEP!mainCRTStartup+0xa59:
00401a59 c3              ret
0:000>
eax=00000064 ebx=00403a9f ecx=54d4fad5 edx=77526344 esi=00000000 edi=00000000
eip=77251114 esp=0012ff90 ebp=0012ff94 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
kernel32!BaseThreadInitThunk+0xe:
77251114 50              push    eax
...
eax=00000172 ebx=00000064 ecx=77543afc edx=7ffe0300 esi=775b8380 edi=775b8340
eip=77525b7a esp=0012ff58 ebp=0012ff70 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ZwTerminateProcess+0xa:
77525b7a ff12            call    dword ptr [edx]      ds:0023:7ffe0300={ntdll!KiFastSystemCall (77526340)}
eax=00000172 ebx=00000064 ecx=77543afc edx=7ffe0300 esi=775b8380 edi=775b8340
eip=77526340 esp=0012ff54 ebp=0012ff70 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCall:
77526340 8bd4            mov     edx,esp
eax=00000172 ebx=00000064 ecx=77543afc edx=0012ff54 esi=775b8380 edi=775b8340
eip=77526342 esp=0012ff54 ebp=0012ff70 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCall+0x2:
77526342 0f34            sysenter
eax=00000172 ebx=00000064 ecx=77543afc edx=0012ff54 esi=775b8380 edi=775b8340
eip=77526344 esp=0012ff54 ebp=0012ff70 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCallRet:
77526344 c3              ret
0:000> t
       ^ No runnable debuggees error in 't'


and ends up the same when I replace my RET w/an ExitThread, tho I'm
not quite sure how my EAX is saved, or where, tho, using 'j' on 't',
I see that it's also POP'd to EBX from the stack (12FED8) somewhere
B 4 the end of this (ntdll!_SEH_epilog4+0xe):


eax=00000064 ebx=00403a9f ecx=51ce7d69 edx=77526344 esi=00000000 edi=00000000
eip=00401a59 esp=0012ff8c ebp=0012ff94 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
BEEP!mainCRTStartup+0xa59:
00401a59 50              push    eax
0:000> t
eax=00000064 ebx=00403a9f ecx=51ce7d69 edx=77526344 esi=00000000 edi=00000000
eip=00401a80 esp=0012ff84 ebp=0012ff94 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
BEEP!ExitThread:
00401a80 ff251c204000    jmp     dword ptr [BEEP!_imp__ExitThread (0040201c)] ds:0023:0040201c={ntdll!RtlExitUserThread (77510689)}
...
eax=00000172 ebx=00000064 ecx=77543afc edx=7ffe0300 esi=775b8380 edi=775b8340
eip=77525b7a esp=0012ff54 ebp=0012ff6c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ZwTerminateProcess+0xa:
77525b7a ff12            call    dword ptr [edx]      ds:0023:7ffe0300={ntdll!KiFastSystemCall (77526340)}
eax=00000172 ebx=00000064 ecx=77543afc edx=7ffe0300 esi=775b8380 edi=775b8340
eip=77526340 esp=0012ff50 ebp=0012ff6c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCall:
77526340 8bd4            mov     edx,esp
eax=00000172 ebx=00000064 ecx=77543afc edx=0012ff50 esi=775b8380 edi=775b8340
eip=77526342 esp=0012ff50 ebp=0012ff6c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCall+0x2:
77526342 0f34            sysenter
eax=00000172 ebx=00000064 ecx=77543afc edx=0012ff50 esi=775b8380 edi=775b8340
eip=77526344 esp=0012ff50 ebp=0012ff6c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCallRet:
77526344 c3              ret
0:000> t
       ^ No runnable debuggees error in 't'


and, tho I can't use it, it also ends up the same when I replace
my RET w/an ExitProcess:


eax=00000064 ebx=00403a9f ecx=b9bcf230 edx=77b36344 esi=00000000 edi=00000000
eip=00401a59 esp=0012ff8c ebp=0012ff94 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
BEEP!mainCRTStartup+0xa59:
00401a59 50              push    eax
0:000> t
eax=00000064 ebx=00403a9f ecx=b9bcf230 edx=77b36344 esi=00000000 edi=00000000
eip=00401a80 esp=0012ff84 ebp=0012ff94 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
BEEP!ExitProcess:
00401a80 ff251c204000    jmp     dword ptr [BEEP!_imp__ExitProcess (0040201c)] ds:0023:0040201c=765d2a6f
eax=00000064 ebx=00403a9f ecx=b9bcf230 edx=77b36344 esi=00000000 edi=00000000
eip=765d2a6f esp=0012ff84 ebp=0012ff94 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
kernel32!FlsFree+0xb:
765d2a6f 8bc0            mov     eax,eax
...
eax=00000172 ebx=00000064 ecx=77b53afc edx=7ffe0300 esi=77bc8380 edi=77bc8340
eip=77b35b7a esp=0012ff54 ebp=0012ff6c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ZwTerminateProcess+0xa:
77b35b7a ff12            call    dword ptr [edx]      ds:0023:7ffe0300={ntdll!KiFastSystemCall (77b36340)}
eax=00000172 ebx=00000064 ecx=77b53afc edx=7ffe0300 esi=77bc8380 edi=77bc8340
eip=77b36340 esp=0012ff50 ebp=0012ff6c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCall:
77b36340 8bd4            mov     edx,esp
eax=00000172 ebx=00000064 ecx=77b53afc edx=0012ff50 esi=77bc8380 edi=77bc8340
eip=77b36342 esp=0012ff50 ebp=0012ff6c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCall+0x2:
77b36342 0f34            sysenter
eax=00000172 ebx=00000064 ecx=77b53afc edx=0012ff50 esi=77bc8380 edi=77bc8340
eip=77b36344 esp=0012ff50 ebp=0012ff6c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCallRet:
77b36344 c3              ret
0:000> t
       ^ No runnable debuggees error in 't'


I think EBX s/b my passed return code (100). The bottom line is
that all 3 produce the same result in Win7 32bit, which is what
I think it should do. I wonder what Win10 will show? I suspect
it might be a 0 for the 1st 2?

  Perhaps someone is willing to test this w/their own sample
program w/"user32.dll", assuming, of course, that you have a
debugger on it that works...

  Cheers.



sinsi

ExitProcess expects the return code to be on the stack, not in EAX.

;invoke ExitProcess,eax
    push eax
    call ExitProcess
;[ESP+0]=return address
;[ESP+4]=return code


By using RET, your return address could be anything

    mov eax,code
    ret   ;should really be RET 16 for winmain

With ExitProcess you will be exiting to a known spot, with RET it's not guaranteed - maybe that's why the return code is OK sometimes, just by a fluke.

rrr314159

Quote from: MtheK... this technique has worked for almost a half-century, and I will NOT deviate from a proven method.

- Spoken like a true Conservative! ... but as a general rule the statement is very suspect. Slide rules worked great for more than 50 years; why are you using a computer? Admittedly, cars can be trouble, but you must get tired of cleaning up after the horse .. ? Isn't your wife sick of washing clothes in the stream by pounding them with rocks (which worked for 50,000 years, not just 50) - when all her friends use washing machines? etc, insert your own joke here ... :biggrin:
I am NaN ;)

qWord

might be interesting: http://blogs.msdn.com/b/oldnewthing/archive/2011/05/25/10168020.aspx.
MREAL macros - when you need floating point arithmetic while assembling!

jj2007

Quote from: qWord on August 15, 2015, 09:22:57 AM
might be interesting: http://blogs.msdn.com/b/oldnewthing/archive/2011/05/25/10168020.aspx.

Nice. C coders discover RawEntryPoint aka start:  :biggrin:

MtheK

  So if I've just been "lucky" this whole time w/RET,
shouldn't ExitThread work? The doc seems to indicate
it should...

MtheK

  Also, the 1st thing that is done by my RET is:

kernel32!BaseThreadInitThunk+0xe:
77251114 50              push    eax

so this shows I'm returning correctly, and EAX goes
on the stack. Besides, I usually ensure that ESP is
the same as when I entered...

MtheK

  Interesting. I tried 'RET 16' and it seems to work in Win7.
What's the purpose of discarding 4 DWORDs from the stack?
Is that in that C++ link? I don't do this when doing my own CALLs?

  From the stack, it seems to get rid of this:
ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])

  A 't 999999' ends up the same way, w/EBX containing the RC.

  So perhaps you're saying that this double underscore thing
is somehow interfering w/this on Win10, but only w/"user32.dll"?
I can try a 'RET 16' on Win10 in BEEP and see if I get the rc=0...
thankx sinsi...

MtheK

  Unfortunately, 'RET 16' made no difference; I still get the fake rc=0.

  I also can't find any commonality in the WINAPI cmds of the 3.5 that
"failed" (the same program "failed" 1 time, but worked other times?!)
and the 9.5 that didn't. First, the cmd had to be in all that "failed",
assuming that it caused the failure (if more than 1, this won't find it).
Second, if that cmd was in any of the others that worked, then it can't(?)
be that cmd causing the "failure". I used the "failed" BEEP as a base.