¿Por qué es necesario ExitProcess en Win32 cuando puede usar un RET?

Me he dado cuenta de que muchos ejemplos de lenguaje ensamblador creados con llamadas directas de Win32 (sin dependencia de C Runtime) ilustran el uso de una llamada explícita a ExitProcess () para finalizar el progtwig al final del código de punto de entrada. No estoy hablando de usar ExitProcess () para salir en alguna ubicación anidada dentro del progtwig. Hay sorprendentemente menos ejemplos donde el código de punto de entrada simplemente sale con una instrucción RET. Un ejemplo que viene a la mente es el famoso TinyPE , donde las variaciones del progtwig salen con una instrucción RET, porque una instrucción RET es un byte único. Usar ExitProcess () o un RET ambos parecen hacer el trabajo.

Un RET desde el punto de entrada de un ejecutable devuelve el valor de EAX al cargador de Windows en KERNEL32, que finalmente propaga el código de salida a NtTerminateProcess (), al menos en Windows 7. En Windows XP, creo que recuerdo haber visto ese ExitProcess () incluso se llamó directamente al final de la cadena de limpieza de subprocesos.

Dado que hay muchas optimizaciones respetadas en el lenguaje ensamblador que se eligen exclusivamente para generar código más pequeño, me pregunto por qué hay más código flotando alrededor que prefiere la llamada explícita a ExitProcess () en lugar de RET. ¿Es este hábito o hay alguna otra razón?

En su sentido más puro, ¿no sería preferible una instrucción RET a una llamada directa a ExitProcess ()? Una llamada directa a ExitProcess () parece similar a salir de tu progtwig eliminándolo del administrador de tareas, ya que esto cortocircuita el flujo normal de regreso al lugar donde el cargador de Windows llamó tu punto de entrada y omitiendo así varias operaciones de limpieza de subprocesos.

Parece que no puedo encontrar ninguna información específica sobre este tema, así que esperaba que alguien pudiera arrojar algo de luz sobre el tema.

Si se está llamando a su función principal desde la biblioteca C runtime, entonces al salir se producirá una llamada a ExitProcess () y el proceso se cerrará.

Si su función principal está siendo llamada directamente por Windows, como bien puede ser el caso con el código de ensamblado, entonces salir solo hará que el hilo salga. El proceso saldrá si y solo si no hay otros hilos. Eso es un problema hoy en día, porque incluso si no creó ningún subproceso, Windows puede haber creado uno o más en su nombre.

Hasta donde sé, este comportamiento no está documentado adecuadamente, pero se describe en la publicación del blog de Raymond Chen, “Si vuelves del hilo principal, ¿sale el proceso?” .

(También probé esto en Windows 7 y Windows 10 y confirmó que se comportaron como describe Raymond).

Adición: en versiones recientes de Windows 10, el cargador de procesos es en sí mismo de subprocesos múltiples, por lo que siempre habrá hilos adicionales presentes cuando se inicie el proceso por primera vez.