当你可以使用RET时,为什么在Win32下需要ExitProcess?

byt*_*ptr 5 assembly winapi

我注意到许多使用直接Win32调用构建的汇编语言示例(没有C运行时依赖性)说明了使用对ExitProcess()的显式调用来在入口点代码的末尾结束程序.我不是在谈论使用ExitProcess()在程序中的某个嵌套位置退出.令人惊讶的是,入口点代码只是通过RET指令退出的示例较少.想到的一个例子是着名的TinyPE,其中程序变量以RET指令退出,因为RET指令是单字节.使用ExitProcess()或RET似乎都可以完成这项工作.

来自可执行文件入口点的RET将EAX的值返回给KERNEL32中的Windows加载程序,最终将退出代码传播回NtTerminateProcess(),至少在Windows 7上.在Windows XP上,我想我记得看过ExitProcess ()甚至直接在线程清理链的末尾调用.

由于汇编语言中有很多受人尊敬的优化,纯粹是在生成较小的代码时选择的,我想知道为什么更多的代码浮动更喜欢显式调用ExitProcess()而不是RET.这个习惯还是有其他原因吗?

从最纯粹的意义上讲,RET指令不会优于直接调用ExitProcess()吗?直接调用ExitProcess()似乎类似于通过从任务管理器中删除它来退出程序,因为这会使返回到Windows加载程序称为入口点的正常流程短路,从而跳过各种线程清理操作?

我似乎找不到任何特定于这个问题的信息,所以我希望有人可以对这个话题有所了解.

Har*_*ton 11

如果从C运行时库调用主函数,则退出将导致调用ExitProcess(),并且进程将退出.

如果您的主函数是由Windows直接调用的,那么汇编代码就是这种情况,那么退出只会导致线程退出.当且仅当没有其他线程时,该进程才会退出.这就是现在的问题,因为即使没有创建任何线程,Windows也可能代表您创建了一个或多个线程.

据我所知,这种行为没有正确记录,但在Raymond Chen的博客文章中有描述,"如果你从主线程返回,进程是否会退出?" .

(我自己也在Windows 7和Windows 10上对此进行了测试,并确认他们的行为与Raymond描述的一样.)

附录:在最新版本的Windows 10中,进程加载器本身是多线程的,因此在进程首次启动时总会有其他线程存在.

  • 为了澄清一点,虽然看起来好像在任何一种情况下都会调用ntdll.RtlExitUserProcess,但事实并非如此,因为对ntdll.NtTerminateThread的调用永远不会返回,即线程正在自行终止. (4认同)
  • 使用Win7 64位和32位纯asm程序,在入口点RET之后,我返回到嵌套自ntdll .___ RtlUserThreadStart的KERNEL32中的一个未命名点.下一个CALL是ntdll.RtlExitUserThread,它调用了名为ntdll.NtTerminateProcess的ntdll.RtlExitUserProcess,它执行了一个"CALL DWORD PTR FS:[0C0]"退出进程后返回.当与MSVCRT 7.1链接时:一旦main()RET返回到_mainCRTStartup,这将导致MSVCR71.exit - > MSVCR71.doexit - > KERNEL32.ExitProcess,后者又调用ntdll.RtlExitUserProcess,这与上面相同. (3认同)
  • @PeterCordes:作为上述评论的附录,我没有观察到Windows加载器在pure-asm或CRT测试中人为地将ExitProcess的地址放在堆栈上.我对此感到好奇,因为它已被多人提及.如果确实发生了这种情况,我认为它一定是Windows的旧版本?有谁记得哪个? (3认同)
  • @PeterCordes:我的测试程序是从主函数返回的。这没有导致进程退出。 (2认同)
  • @PeterCordes:byteptr的调试会话显示`RtlExitUserThread`被调用,这与Raymond的帖子和我自己的观察一致。可以肯定地假设,当退出线程是进程中的最后一个线程时,`RtlExitUserThread`仅调用`RtlExitUserProcess`。(我本来会天真地希望在内核中发生这种情况,但是现在考虑到这一点,它必须在用户模式下发生才能支持DllMain。)在时间允许的情况下,我回来时会仔细检查一下自己明天去办公室。 (2认同)
  • 在我的仅主线程控制台示例返回之后,ntdll.RtlExitUserThread执行以下操作:调用ntdll.NtQueryInformationThread(-2,0x0C,&stack_dword,4,0)(注意:0x0C不是在公共winternl.h中定义的THREADINFOCLASS。 ),该函数返回零(NTSTATUS成功),并且双字返回1。如果API通过双字返回负的NTSTATUS或非零值(这是真的),则将首先调用ntdll.LdrShutdownThread,ntdll.TpCheckTerminateWorker和ntdll.NtTerminateThread。 。在我看来,这不是正确的,这些调用被跳过,我们直接转到ntdll.RtlExitUserProcess。 (2认同)
  • @byteptr-0x0C-这是`ThreadAmILastThread`,因此仅当“我是最后一个线程”时,`RtlExitUserThread`称为“ RtlExitUserProcess`”,否则为LdrShutdownThread +`ZwTerminateThread(0,exitcode)`。有趣的是,如果调用`ZwTerminateThread(0,exitcode)`-它终止调用者线程,但前提是它不是最后一个正在处理的线程。在另一种情况下-我们得到了“ STATUS_CANT_TERMINATE_SELF”(在“ ntstatus.h”中读取其说明。可能有两种情况,在同一时间同时有两个线程被称为ExitThread。两个线程均因“ ThreadAmILastThread”而为false,而都进入了“ ZwTerminateThread”,但其中一个从这2个呼叫将失败。 (2认同)
  • 和`RtlExitUserProcess`将会被调用-这样,在`ZwTerminateThread(0,exitcode)`之后,对`RtlExitUserProcess(exitcode)`的调用比第一次查找更具意义。如果不这样做,则将导致崩溃或不确定的行为-NtTerminateThread可能返回错误-表示默认情况下试图终止自身的线程(称为NtTerminateThread并为NULL),并且它是当前进程中的最后一个线程。 (2认同)