6
votes

I've noticed that many assembly language examples built using straight Win32 calls (no C Runtime dependency) illustrate the use of an explicit call to ExitProcess() to end the program at the end of the entry-point code. I'm not talking about using ExitProcess() to exit at some nested location within the program. There are surprisingly fewer examples where the entry-point code simply exits with a RET instruction. One example that comes to mind is the famous TinyPE, where the program variations exit with a RET instruction, because a RET instruction is a single byte. Using either ExitProcess() or a RET both seem to do the job.

A RET from an executable's entry-point returns the value of EAX back to the Windows loader in KERNEL32, which ultimately propagates the exit code back to NtTerminateProcess(), at least on Windows 7. On Windows XP, I think I remember seeing that ExitProcess() was even called directly at the end of the thread-cleanup chain.

Since there are many respected optimizations in assembly language that are chosen purely on generating smaller code, I wonder why more code floating around prefers the explicit call to ExitProcess() rather than RET. Is this habit or is there another reason?

In its purest sense, wouldn't a RET instruction be preferable to a direct call to ExitProcess()? A direct call to ExitProcess() seems akin to exiting your program by killing it from the task manager as this short-circuits the normal flow of returning back to where the Windows loader called your entry-point and thus skipping various thread cleanup operations?

I can't seem to locate any information specific to this issue, so I was hoping someone could shed some light on the topic.

1
Are you sure these programs have their code as the PE entry point, and not the C runtime? Even so, IIRC there's always an ExitProcess waiting on the stack above the executable's entry point (judging from what I've seen from Windows stack traces; I'm not sure if this is fully correct).andlabs
It is a unixism, crept into Windows most of all due to the C language. A counter-example is .NET, the process keeps running as long as it has any non-background threads.Hans Passant
Yes, I'm talking about a pure assembly language program without the CRT dependency. In the case of using the C Runtime Library, I'd imagine you'd always want to return from main or WinMain with a RET to allow _mainCRTStartup/_WinMainCRTStartup to regain control so proper cleanup can be done.byteptr

1 Answers

11
votes

If your main function is being called from the C runtime library, then exiting will result in a call to ExitProcess() and the process will exit.

If your main function is being called directly by Windows, as may well be the case with assembly code, then exiting will only cause the thread to exit. The process will exit if and only if there are no other threads. That's a problem nowadays, because even if you didn't create any threads, Windows may have created one or more on your behalf.

As far as I know this behaviour is not properly documented, but is described in Raymond Chen's blog post, "If you return from the main thread, does the process exit?".

(I have also tested this myself on both Windows 7 and Windows 10 and confirmed that they behaved as Raymond describes.)

Addendum: in recent versions of Windows 10, the process loader is itself multi-threaded, so there will always be additional threads present when the process first starts.