From double Click to Main: The initialization of Process in OS
从双击一个应用程序到生成一个OS的process,然后到执行这个exe文件的Main方法,中间做了些什么呢?经常看着call stack的这块灰色的地方,总是想看看其是如何实现的。
Process的creation分为两个级别的创建:NT级别的和Windows级别的。CreateProcess这个方法,就会进行这两个级别的创建,同时创建Threads。俺们找到
c:\Program Files\Microsoft SDKs\Windows\v6.0A\Include\WinBase.h,来查看关于这个部分的定义:
#ifdef UNICODE
#define CreateProcess CreateProcessW
#else
#define CreateProcess CreateProcessA
#endif // !UNICODE
如果定义了UNICODE,就使用CreateProcessW方法来创建,反之对于ANSI的系统,就用CreateProcessA方法来创建,这是一个thin shim。可以看看CreateProcessW的定义方法:
WINBASEAPI
BOOL
WINAPI
CreateProcessW(
__in_opt LPCWSTR lpApplicationName,
__inout_opt LPWSTR lpCommandLine,
__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in BOOL bInheritHandles,
__in DWORD dwCreationFlags,
__in_opt LPVOID lpEnvironment,
__in_opt LPCWSTR lpCurrentDirectory,
__in LPSTARTUPINFOW lpStartupInfo,
__out LPPROCESS_INFORMATION lpProcessInformation
);
CreateProcess的目标,就是留下一个APC的queue。这个queue里面保存了new process里面需要初始化的thread。这里WinExec和LoadModule还是被支持的,只是调用的还是CreateProcess这个方法。
可以使用下面的代码来创建一个Process:
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
// Start the child process.
if( !CreateProcess( NULL, // No module name (use command line)
argv[1], // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi ) // Pointer to PROCESS_INFORMATION structure
)
{
printf( "CreateProcess failed (%d)\n", GetLastError() );
return;
}
// Wait until child process exits.
WaitForSingleObject( pi.hProcess, INFINITE );
// Close process and thread handles.
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
首先看看Clix启动的时候的Call Stack:
看看BaseProcessStart启动的时候是做了些什么:
VOID
BaseProcessStart( PPROCESS_START_ROUTINE lpStartAddress )
{
try {
// Set the state of Thread Object.
NtSetInformationThread( NtCurrentThread(),
ThreadQuerySetWin32StartAddress,
&lpStartAddress,
sizeof( lpStartAddress )
);
ExitThread((lpStartAddress)());
}
except(UnhandledExceptionFilter( GetExceptionInformation() )) {
if ( !BaseRunningInServerProcess ) {
ExitProcess(GetExceptionCode());
}
else {
ExitThread(GetExceptionCode());
}
}
}
NtSetInformationThread主要是设定启动Process的Thread的一些状态信息。可以在反编译中看到这个函数的信息:
_BaseProcessStart@4:
push 0Ch
push 7C816FE0h
call __SEH_prolog (7C8024C6h)
and dword ptr [ebp-4],0
push 4
lea eax,[ebp+8]
push eax
push 9
push 0FFFFFFFEh
call dword ptr [__imp__NtSetInformationThread@16 (7C8013A0h)]
call dword ptr [ebp+8]
push eax //Break Point
call _ExitThread@4 (7C80C058h)
nop
ok,这个时候设置好了启动线程之后,就引导到了crtexe.c的__tmainCRTStartup()函数中了。
是如何从Kerner的BaseProcessStart跳转到tmainCRTStartup的呢?位于两个不同的dll中。我们写的C/C++的程序,运行的时候需要库文件的支持,再启动一个程序的时候,链接器会首先找到程序的启动函数:
如果建立了一个console的程序,编译器的开关可能是下面的形式:
/subsystem:"console" /entry:"mainCRTStartup" (ANSI)
/subsystem:"console" /entry:"wmainCRTStartuup" (UNICODE)
现在,再mainCRTStartup中,调用了下列方法和函数:
#ifdef WPRFLAG
int wmainCRTStartup(
#else /* WPRFLAG */
int mainCRTStartup(
#endif /* WPRFLAG */
#endif /* _WINMAIN_ */
void
)
{
/*
* The /GS security cookie must be initialized before any exception
* handling targetting the current image is registered. No function
* using exception handling can be called in the current image until
* after __security_init_cookie has been called.
*/
__security_init_cookie();
return __tmainCRTStartup();
}
这个函数中,也是做了一个转换,真正的Main函数调用再__tmainCRTStartup方法实现中的:
#ifdef WPRFLAG
__winitenv = envp;
mainret = wmain(argc, argv, envp);
#else /* WPRFLAG */
__initenv = envp;
mainret = main(argc, argv, envp);
#endif /* WPRFLAG */
下面一句的调用,就transfer到了d:\Rotor\sscli20\palrt\inc\palstartup.h的对Rotor的PAL层操作上面:
#ifdef __cplusplus
extern "C"
#endif
int __cdecl main(int argc, char **argv) {
struct _mainargs mainargs;
#ifdef _MSC_VER
if (PAL_Initialize(0, NULL)) {
return 1;
}
#else
if (PAL_Initialize(argc, argv)) {
return 1;
}
#endif
对启动部分的分析,就到这个地方,很多涉及到选择exe入口函数的link选项,结合#pragma指令还有许多中配置方法。
下一篇从PAL的初始化讲起,一直到执行应用程序的Main方法。
Sunday, December 07, 2008 9:46:49 PM
Lbq1221119@cnblogs.com first post at sscli.cnblogs.com
posted on 2008-12-07 22:03 lbq1221119 阅读(1927) 评论(5) 编辑 收藏 举报