Windows在调用Main()之前会做什么? [英] What Does Windows Do Before Main() is Called?

查看:110
本文介绍了Windows在调用Main()之前会做什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Windows必须执行某些操作来解析PE标头,将可执行文件加载到内存中并将命令行参数传递给main().

使用OllyDbg,我已将调试器设置为在main()上中断,因此我可以查看调用堆栈:

似乎缺少符号,因此我们无法获得函数名称,只能看到它的内存地址.但是我们可以看到main的调用方是kernel32.767262C4,它是ntdll.77A90FD9的被调用方.在栈的底部,我们看到返回到ntdll.77A90FA4,我认为这是有史以来第一个被调用来运行可执行文件的函数.似乎传递给该函数的引人注目的参数是Windows的结构化异常处理程序地址和可执行文件的入口点.

那么这些功能在将程序加载到内存中并为入口点执行做好准备时到底有多精确?调试器显示的是main()之前操作系统执行的整个过程吗?

解决方案

如果您致电 ZwCreateThread[Ex] 创建进程中的第一个线程

创建线程时-您(如果您直接调用 ZwCreateThread )或系统初始化 CONTEXT 记录新线程-在这里Eip(i386)Rip(amd64)线程的入口点.如果您这样做-您可以指定任何地址.但是当您打电话时说 Create[Remote]Thread[Ex] -我怎么说-系统填充 CONTEXT ,它将自例程设置为线程入口点.您的原始入口点保存在Eax(i386)Rcx(amd64)寄存器中.

此例程的名称取决于Windows版本.

早期是kernel32.dll中的BaseThreadStartThunkBaseProcessStartThunk(如果调用了CreateProcess).

但现在系统从ntdll.dll指定RtlUserThreadStart. RtlUserThreadStart通常从kernel32.dll调用BaseThreadInitThunk(本机(引导执行)应用程序除外,例如smss.exechkdsk.exe在自身地址空间中根本没有kernel32.dll). BaseThreadInitThunk已经调用了原始线程入口点,并且在返回之后(如果返回)- SEH 过滤器.仅因为这可以调用 函数.如果线程直接从您的入口点开始,没有包装器- DLL_THREAD_ATTACH 通知?当新的进程中的线程开始执行时(特殊的系统工作线程除外,例如LdrpWorkCallback),他按加载的DLL列表移动,并使用 DisableThreadLibraryCalls 未为此DLL调用).但是这是如何实现的呢?感谢LdrInitializeThunk调用LdrpInitialize-> LdrpInitializeThread-> LdrpCallInitRoutine(用于DLL EP)

当进程中的第一个线程启动时-这是特例.需要做很多额外的工作来初始化进程.目前,只有两个模块正在加载-EXEntdll.dll. LdrInitializeThunk 致电LdrpInitializeProcess进行这项工作.如果非常简短:

  1. 初始化不同的过程结构
  2. 将EXE静态加载到的所有DLL(及其从属文件) 链接-但不称他们为EP!
  3. 称为LdrpDoDebuggerBreak-此函数的外观-是调试器 附加到进程,如果是,则调用int 3-因此调试器 收到异常消息-STATUS_BREAKPOINT-大多数调试器可以 开始UI调试仅从这一点开始.但是存在 允许从LdrInitializeThunk作为调试过程的调试器- 我从这种调试器获得的所有屏幕截图
  4. 重要点-直到处理中的代码才从 ntdll.dll(可能来自kernel32.dll)-来自另一个的代码 DLL,尚未在进程中执行的任何第三方代码.
  5. 要处理的可选加载的shim dll-Shim Engine初始化.但 这是可选的
  6. 浏览加载的DLL列表并使用以下命令调用其EP DLL_PROCESS_DETACH
  7. TLS 调用的初始化和TLS回调(如果存在)

  8. ZwTestAlert被调用-此调用检查线程中是否存在APC 排队,并执行它.这一点在从NT4到 赢得10.例如,让它在挂起状态下创建进程 然后插入APC调用( )到它的线程 (PROCESS_INFORMATION.hThread)-结果此调用将是 在进程将完全初始化之后执行的所有 DLL_PROCESS_DETACH已调用,但在EXE入口点之前.在上下文中 第一个进程线程.

  9. 和NtContinue最终被调用-此恢复保存的线程上下文 最后我们跳到线程EP

另请阅读 CreateProcess的流程

Windows must do something to parse the PE header, load the executable in memory, and pass command line arguments to main().

Using OllyDbg I have set the debugger to break on main() so I could view the call stack:

It seems as if symbols are missing so we can't get the function name, just its memory address as seen. However we can see the caller of main is kernel32.767262C4, which is the callee of ntdll.77A90FD9. Towards the bottom of the stack we see RETURN to ntdll.77A90FA4 which I assume to be the first function to ever be called to run an executable. It seems like the notable arguments passed to that function are the Windows' Structured Exception Handler address and the entry point of the executable.

So how exactly do these functions end up in loading the program into memory and getting it ready for the entry point to execute? Is what the debugger shows the entire process executed by the OS before main()?

解决方案

if you call CreateProcess system internally call ZwCreateThread[Ex] to create first thread in process

when you create thread - you (if you direct call ZwCreateThread) or system initialize the CONTEXT record for new thread - here Eip(i386) or Rip(amd64) the entry point of thread. if you do this - you can specify any address. but when you call say Create[Remote]Thread[Ex] - how i say - the system fill CONTEXT and it set self routine as thread entry point. your original entry point is saved in Eax(i386) or Rcx(amd64) register.

the name of this routine depended from Windows version.

early this was BaseThreadStartThunk or BaseProcessStartThunk (in case from CreateProcess called) from kernel32.dll.

but now system specify RtlUserThreadStart from ntdll.dll . the RtlUserThreadStart usually call BaseThreadInitThunk from kernel32.dll (except native (boot execute) applications, like smss.exe and chkdsk.exe which no have kernel32.dll in self address space at all ). BaseThreadInitThunk already call your original thread entry point, and after (if) it return - RtlExitUserThread called.

the main goal of this common thread startup wrapper - set the top level SEH filter. only because this we can call SetUnhandledExceptionFilter function. if thread start direct from your entry point, without wrapper - the functional of Top level Exception Filter become unavailable.

but whatever the thread entry point - thread in user space - NEVER begin execute from this point !

early when user mode thread begin execute - system insert APC to thread with LdrInitializeThunk as Apc-routine - this is done by copy (save) thread CONTEXT to user stack and then call KiUserApcDispatcher which call LdrInitializeThunk. when LdrInitializeThunk finished - we return to KiUserApcDispatcher which called NtContinue with saved thread CONTEXT - only after this already thread entry point begin executed.

but now system do some optimization in this process - it copy (save) thread CONTEXT to user stack and direct call LdrInitializeThunk. at the end of this function NtContinue called - and thread entry point being executed.

so EVERY thread begin execute in user mode from LdrInitializeThunk. (this function with exactly name exist and called in all windows versions from nt4 to win10)

what is this function do ? for what is this ? you may be listen about DLL_THREAD_ATTACH notification ? when new thread in process begin executed (with exception for special system worked threads, like LdrpWorkCallback)- he walk by loaded DLL list, and call DLLs entry points with DLL_THREAD_ATTACH notification (of course if DLL have entry point and DisableThreadLibraryCalls not called for this DLL). but how this is implemented ? thanks to LdrInitializeThunk which call LdrpInitialize -> LdrpInitializeThread -> LdrpCallInitRoutine (for DLLs EP)

when the first thread in process start - this is special case. need do many extra jobs for process initialization. at this time only two modules loaded in process - EXE and ntdll.dll . LdrInitializeThunk call LdrpInitializeProcess for this job. if very briefly:

  1. different process structures is initialized
  2. loading all DLL (and their dependents) to which EXE statically linked - but not call they EPs !
  3. called LdrpDoDebuggerBreak - this function look - are debugger attached to process, and if yes - int 3 called - so debugger receive exception message - STATUS_BREAKPOINT - most debuggers can begin UI debugging only begin from this point. however exist debugger(s) which let as debug process from LdrInitializeThunk - all my screenshots from this kind debugger
  4. important point - until in process executed code only from ntdll.dll (and may be from kernel32.dll) - code from another DLLs, any third-party code not executed in process yet.
  5. optional loaded shim dll to process - Shim Engine initialized. but this is OPTIONAL
  6. walk by loaded DLL list and call its EPs with DLL_PROCESS_DETACH
  7. TLS Initializations and TLS callbacks called (if exists)

  8. ZwTestAlert is called - this call check are exist APC in thread queue, and execute its. this point exist in all version from NT4 to win 10. this let as for example create process in suspended state and then insert APC call ( QueueUserAPC ) to it thread (PROCESS_INFORMATION.hThread) - as result this call will be executed after process will be fully initialized, all DLL_PROCESS_DETACH called, but before EXE entry point. in context of first process thread.

  9. and NtContinue called finally - this restore saved thread context and we finally jump to thread EP

read also Flow of CreateProcess

这篇关于Windows在调用Main()之前会做什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆