?? 漫談兼容內(nèi)核之七:wine的二進(jìn)制映像裝入和啟動.txt
字號:
exit(1);
}[/code]
如前所述,由于wld_start()的配合,這里的指針wine_main_preload_info已經(jīng)指向由wine-preloader提供的保留地址區(qū)間表。實(shí)際上每個(gè)Linux進(jìn)程都有自身的保留區(qū)間隊(duì)列,因?yàn)槔鐝牡刂?xc0000000往下的區(qū)間就是要保留用于堆棧的。所以這里要把它們合并到自身的保留區(qū)間隊(duì)列中。
而main()的主體,則毫無疑問是對wine_init()的調(diào)用。
[code][main() > wine_init()]
void wine_init( int argc, char *argv[], char *error, int error_size )
{
char *wine_debug;
int file_exists;
void *ntdll;
void (*init_func)(void);
build_dll_path();
wine_init_argv0_path( argv[0] );
__wine_main_argc = argc;
__wine_main_argv = argv;
__wine_main_environ = environ;
mmap_init();
if ((wine_debug = getenv("WINEDEBUG")))
{
if (!strcmp( wine_debug, "help" )) debug_usage();
wine_dbg_parse_options( wine_debug );
}
if (!(ntdll = dlopen_dll( "ntdll.dll", error, error_size, 0, &file_exists ))) return;
if (!(init_func = wine_dlsym( ntdll, "__wine_process_init", error, error_size ))) return;
init_func();
}[/code]
由于篇幅所限,我們只好長話短說了。這里的build_dll_path()根據(jù)環(huán)境變量WINEDLLPATH設(shè)置好裝入各種DLL的目錄路徑。這是為裝入DLL做好準(zhǔn)備。注意這里把調(diào)用參數(shù)argc、argv、environ轉(zhuǎn)移到了__wine_main_argc等全局量中。
下面還有兩件事是實(shí)質(zhì)性的:
1. 通過dlopen_dll()裝入由Wine提供的動態(tài)連接庫ntdll.dll。由于Wine特殊的系統(tǒng)結(jié)構(gòu),它不能使用微軟的ntdll.dll,而只能使用Wine自己的DLL,這樣的DLL稱為“內(nèi)置(built-in)”DLL。
2. 執(zhí)行內(nèi)置ntdll.dll中的函數(shù)__wine_process_init()。先通過wine_dlsym()取得這個(gè)函數(shù)的入口地址,然后通過指針init_func進(jìn)行調(diào)用。之所以要以這樣的方式調(diào)用,是因?yàn)檫@個(gè)函數(shù)在ntdll.dll中,而dlopen_dll()只是裝入了這個(gè)DLL、卻并未完成與此模塊的動態(tài)鏈接。
Windows的DLL是PE格式的,而在Linux環(huán)境下由gcc生成的.so文件為COFF或ELF格式(但是PE格式的主體部分就是COFF,二者基本上只差一個(gè)頭)。一般而言,Wine既可以安裝使用本身的“內(nèi)置”動態(tài)庫,也可以安裝使用Windows上相應(yīng)的“原裝(native)”動態(tài)庫。但是,其中有幾個(gè)動態(tài)庫是特殊的,因而只能使用Wine自己的版本,ntdll就是其一。實(shí)際上Windows上的ntdll.dll中根本不會有__wine_process_init()這么個(gè)函數(shù)。Wine自己的“內(nèi)置”DLL實(shí)際上就是Linux的.so模塊,是在Linux環(huán)境下由gcc產(chǎn)生的。GNU的C庫glibc中提供了一組用來處理.so模塊的函數(shù),上面的dlopen_dll()和wine_dlsym()最終都是調(diào)用這些庫函數(shù)(例如dlopen()和dlsym())來完成操作。
下面就是執(zhí)行由ntdll.dll提供的__wine_process_init()了。
[code][main() > wine_init() > __wine_process_init()]
void __wine_process_init( int argc, char *argv[] )
{
static const WCHAR kernel32W[] = {'k','e','r','n','e','l','3','2','.','d','l','l',0};
WINE_MODREF *wm;
NTSTATUS status;
ANSI_STRING func_name;
void (* DECLSPEC_NORETURN init_func)();
extern mode_t FILE_umask;
thread_init();
/* retrieve current umask */
FILE_umask = umask(0777);
umask( FILE_umask );
/* setup the load callback and create ntdll modref */
wine_dll_set_callback( load_builtin_callback );
if ((status = load_builtin_dll( NULL, kernel32W, 0, &wm )) != STATUS_SUCCESS)
{
MESSAGE( "wine: could not load kernel32.dll, status %lx\n", status );
exit(1);
}
RtlInitAnsiString( &func_name, "__wine_kernel_init" );
if ((status = LdrGetProcedureAddress( wm->ldr.BaseAddress, &func_name,
0, (void **)&init_func )) != STATUS_SUCCESS)
{
MESSAGE(
"wine: could not find __wine_kernel_init in kernel32.dll, status %lx\n", status );
exit(1);
}
init_func();
}[/code]
也許需要加深一下讀者的印像,現(xiàn)在是在wine-kthread中運(yùn)行,目的是要裝入目標(biāo)PE格式映像、對于我們這兒是notepad.exe的映像。
簡而言之,函數(shù)__wine_process_init()主要完成以下操作:
一、 thread_init()
a. 分配并初始化TEB, info等數(shù)據(jù)結(jié)構(gòu)。
b. 通過server_init_process()與服務(wù)進(jìn)程建立socket連接。在此過程中,如果連接失敗就說明服務(wù)進(jìn)程尚不存在,此時(shí)要通過start_server()先fork()一個(gè)子進(jìn)程,讓其執(zhí)行wine_exec_wine_binary(),以裝入并運(yùn)行wineserver,再試圖與其建立連接。
c. 請求服務(wù)進(jìn)程執(zhí)行init_thread()。
二、 由load_builtin_dll("kernel32.dll")裝入Wine的另一個(gè)“內(nèi)置”動態(tài)連接庫kernel32.dll。可見kernel32.dll也必須是個(gè)“built-in(內(nèi)置)”DLL。
三、 由LdrGetProcedureAddress()從裝入的kernel32.dll映像中取得函數(shù)__wine_kernel_init()的入口。
四、 執(zhí)行kernel32.dll中的函數(shù)__wine_kernel_init()。
讀者也許注意到這里裝入kernel32.dll和從中獲取函數(shù)入口時(shí)所調(diào)用的函數(shù)與前面裝入ntdll.dll時(shí)所用的不同,但是這只是一些細(xì)節(jié)上的不同,在這里可以忽略。
有了ntdll和kernel32兩個(gè)動態(tài)連接庫,就可以通過__wine_kernel_init()裝入目標(biāo)程序的映像并加以執(zhí)行了。這個(gè)函數(shù)的偽代碼如下:
[code][main() > wine_init() > __wine_process_init() > __wine_kernel_init()]
void __wine_kernel_init(void)
{
process_init(); //源碼中說:Initialize everything
跳過__wine_main_argv[ 0],即“wine-kthread”,使目標(biāo)程序名變成新的argv[0]。
將目標(biāo)程序名轉(zhuǎn)換成unicode。
通過find_exe_file()找到并打開目標(biāo)映像文件,例如“notepad.exe”。
MODULE_GetBinaryType(); //取得目標(biāo)文件的類型。
Switch(目標(biāo)文件類型)
{
case BINARY_PE_EXE:
if ((peb->ImageBaseAddress = load_pe_exe( main_exe_name, main_exe_file )))
goto found; //裝入映像成功。
. . . . . .
. . . . . .
}
found:
/* build command line */
set_library_wargv( __wine_main_argv );
if (!build_command_line( __wine_main_wargv )) goto error;
stack_size =
RtlImageNtHeader(peb->ImageBaseAddress)->OptionalHeader.SizeOfStackReserve;
/* allocate main thread stack */
if (!THREAD_InitStack( NtCurrentTeb(), stack_size )) goto error;
/* switch to the new stack */
wine_switch_to_stack( start_process, NULL, NtCurrentTeb()->Tib.StackBase );
error:
ExitProcess( GetLastError() );
}
[/code]
這里的第一個(gè)函數(shù)process_init()是個(gè)很大的過程,包括向wineserver登記、對包括PEB在內(nèi)的各種數(shù)據(jù)結(jié)構(gòu)的初始化、還有對注冊本的查詢、對當(dāng)前目錄和環(huán)境的設(shè)置等等,所以源碼中說是“initialize everything”。其實(shí)這是很好理解的:因?yàn)槟繕?biāo)映像、這里是notepad.exe、可不會向wineserver登記,在Windows平臺上根本就沒有wineserver。另一方面,在目標(biāo)進(jìn)程開始運(yùn)行之前,還得為其做好許多準(zhǔn)備,例如開始運(yùn)行時(shí)的工作目錄在那里,人機(jī)界面上應(yīng)使用哪一種文字,等等。注意這里所說的進(jìn)程既是指內(nèi)核意義上的進(jìn)程,更是指Wine層面上的進(jìn)程。從內(nèi)核的意義上說,眼下正在運(yùn)行的程序就屬于當(dāng)前進(jìn)程;而從Wine的意義上說,則目標(biāo)進(jìn)程尚未開始運(yùn)行,還正在為此進(jìn)行準(zhǔn)備。
到process_init()執(zhí)行完畢的時(shí)候,為Wine進(jìn)程進(jìn)行的準(zhǔn)備就基本就緒了??墒?,舞臺搭好了,演員卻還沒有到,目標(biāo)映像本身尚未裝入。于是接著就通過find_exe_file()找到并打開目標(biāo)映像,準(zhǔn)備裝入。之所以先要找到,而不是直接打開,是因?yàn)榭赡苄枰慈舾刹煌穆窂綄ふ夷繕?biāo)映像文件。
然后,打開目標(biāo)映像文件以后,就通過MODULE_GetBinaryType()獲取文件的類型,讀者已經(jīng)在上一篇漫談中看到這個(gè)函數(shù)是怎樣實(shí)現(xiàn)此項(xiàng)功能的了。
下面的操作就要視目標(biāo)映像的類型而定了。在這里我們只關(guān)心類型為BINARY_PE_EXE的32位.exe映像。當(dāng)然,那是PE格式的。從代碼中看到,BINARY_PE_EXE映像是由load_pe_exe()裝入的。注意這只是裝入目標(biāo)映像,而并不包括DLL以及目標(biāo)映像與所需DLL的動態(tài)連接,那還在后面。
裝入了目標(biāo)EXE映像以后,回到__wine_kernel_init()的代碼中,下面為目標(biāo)映像的執(zhí)行準(zhǔn)備好argc,、argv[]、以及命令行等參數(shù)。就我們這個(gè)情景而言,已經(jīng)只剩下“notepad.exe”一項(xiàng)了。然后是通過THREAD_InitStack()為目標(biāo)進(jìn)程的主線程分配堆棧,映像頭部的OptionalHeader中提供了所建議的堆棧大小。
最后的wine_switch_to_stack(start_process, NULL, NtCurrentTeb()->Tib.StackBase)是最為關(guān)鍵的。這是一小段匯編代碼,實(shí)現(xiàn)的是對函數(shù)start_process()的調(diào)用,而對start_process()的調(diào)用在正常情況下是不返回的。
[code][main() > wine_init() > __wine_process_init() > __wine_kernel_init() > wine_switch_to_stack()]
#if defined(__i386__) && defined(__GNUC__)
__ASM_GLOBAL_FUNC( wine_switch_to_stack,
"movl 4(%esp),%ecx\n\t" /* func */
"movl 8(%esp),%edx\n\t" /* arg */
"movl 12(%esp),%esp\n\t" /* stack */
"pushl %edx\n\t"
"xorl %ebp,%ebp\n\t"
"call *%ecx\n\t"
"int $3" /* we never return here */ );[/code]
我把這幾行匯編代碼留給讀者,只是說明:這里的call指令所調(diào)用的就是start_process(),對這個(gè)函數(shù)的調(diào)用不應(yīng)返回,如果返回就執(zhí)行指令“int 3”,那是用于Debug的自陷指令。
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -