⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄

📁 兼容内核漫谈 适合想将Windows上的程序移植到其它平台上的朋友研究查看
💻
📖 第 1 页 / 共 4 页
字号:
    exit(1);
}[/code]
    如前所述,由于wld_start()的配合,这里的指针wine_main_preload_info已经指向由wine-preloader提供的保留地址区间表。实际上每个Linux进程都有自身的保留区间队列,因为例如从地址0xc0000000往下的区间就是要保留用于堆栈的。所以这里要把它们合并到自身的保留区间队列中。
    而main()的主体,则毫无疑问是对wine_init()的调用。

[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()根据环境变量WINEDLLPATH设置好装入各种DLL的目录路径。这是为装入DLL做好准备。注意这里把调用参数argc、argv、environ转移到了__wine_main_argc等全局量中。
下面还有两件事是实质性的:
    1. 通过dlopen_dll()装入由Wine提供的动态连接库ntdll.dll。由于Wine特殊的系统结构,它不能使用微软的ntdll.dll,而只能使用Wine自己的DLL,这样的DLL称为“内置(built-in)”DLL。
    2. 执行内置ntdll.dll中的函数__wine_process_init()。先通过wine_dlsym()取得这个函数的入口地址,然后通过指针init_func进行调用。之所以要以这样的方式调用,是因为这个函数在ntdll.dll中,而dlopen_dll()只是装入了这个DLL、却并未完成与此模块的动态链接。
    Windows的DLL是PE格式的,而在Linux环境下由gcc生成的.so文件为COFF或ELF格式(但是PE格式的主体部分就是COFF,二者基本上只差一个头)。一般而言,Wine既可以安装使用本身的“内置”动态库,也可以安装使用Windows上相应的“原装(native)”动态库。但是,其中有几个动态库是特殊的,因而只能使用Wine自己的版本,ntdll就是其一。实际上Windows上的ntdll.dll中根本不会有__wine_process_init()这么个函数。Wine自己的“内置”DLL实际上就是Linux的.so模块,是在Linux环境下由gcc产生的。GNU的C库glibc中提供了一组用来处理.so模块的函数,上面的dlopen_dll()和wine_dlsym()最终都是调用这些库函数(例如dlopen()和dlsym())来完成操作。
    下面就是执行由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]
    也许需要加深一下读者的印像,现在是在wine-kthread中运行,目的是要装入目标PE格式映像、对于我们这儿是notepad.exe的映像。
    简而言之,函数__wine_process_init()主要完成以下操作:
    一、 thread_init()
    a. 分配并初始化TEB, info等数据结构。
    b. 通过server_init_process()与服务进程建立socket连接。在此过程中,如果连接失败就说明服务进程尚不存在,此时要通过start_server()先fork()一个子进程,让其执行wine_exec_wine_binary(),以装入并运行wineserver,再试图与其建立连接。
    c. 请求服务进程执行init_thread()。
    二、 由load_builtin_dll("kernel32.dll")装入Wine的另一个“内置”动态连接库kernel32.dll。可见kernel32.dll也必须是个“built-in(内置)”DLL。
    三、 由LdrGetProcedureAddress()从装入的kernel32.dll映像中取得函数__wine_kernel_init()的入口。
    四、 执行kernel32.dll中的函数__wine_kernel_init()。

    读者也许注意到这里装入kernel32.dll和从中获取函数入口时所调用的函数与前面装入ntdll.dll时所用的不同,但是这只是一些细节上的不同,在这里可以忽略。
    有了ntdll和kernel32两个动态连接库,就可以通过__wine_kernel_init()装入目标程序的映像并加以执行了。这个函数的伪代码如下:

[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”,使目标程序名变成新的argv[0]。
将目标程序名转换成unicode。
通过find_exe_file()找到并打开目标映像文件,例如“notepad.exe”。
MODULE_GetBinaryType();  //取得目标文件的类型。
Switch(目标文件类型)
{
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]

    这里的第一个函数process_init()是个很大的过程,包括向wineserver登记、对包括PEB在内的各种数据结构的初始化、还有对注册本的查询、对当前目录和环境的设置等等,所以源码中说是“initialize everything”。其实这是很好理解的:因为目标映像、这里是notepad.exe、可不会向wineserver登记,在Windows平台上根本就没有wineserver。另一方面,在目标进程开始运行之前,还得为其做好许多准备,例如开始运行时的工作目录在那里,人机界面上应使用哪一种文字,等等。注意这里所说的进程既是指内核意义上的进程,更是指Wine层面上的进程。从内核的意义上说,眼下正在运行的程序就属于当前进程;而从Wine的意义上说,则目标进程尚未开始运行,还正在为此进行准备。
    到process_init()执行完毕的时候,为Wine进程进行的准备就基本就绪了。可是,舞台搭好了,演员却还没有到,目标映像本身尚未装入。于是接着就通过find_exe_file()找到并打开目标映像,准备装入。之所以先要找到,而不是直接打开,是因为可能需要按若干不同的路径寻找目标映像文件。
    然后,打开目标映像文件以后,就通过MODULE_GetBinaryType()获取文件的类型,读者已经在上一篇漫谈中看到这个函数是怎样实现此项功能的了。
    下面的操作就要视目标映像的类型而定了。在这里我们只关心类型为BINARY_PE_EXE的32位.exe映像。当然,那是PE格式的。从代码中看到,BINARY_PE_EXE映像是由load_pe_exe()装入的。注意这只是装入目标映像,而并不包括DLL以及目标映像与所需DLL的动态连接,那还在后面。

    装入了目标EXE映像以后,回到__wine_kernel_init()的代码中,下面为目标映像的执行准备好argc,、argv[]、以及命令行等参数。就我们这个情景而言,已经只剩下“notepad.exe”一项了。然后是通过THREAD_InitStack()为目标进程的主线程分配堆栈,映像头部的OptionalHeader中提供了所建议的堆栈大小。
    最后的wine_switch_to_stack(start_process, NULL, NtCurrentTeb()->Tib.StackBase)是最为关键的。这是一小段汇编代码,实现的是对函数start_process()的调用,而对start_process()的调用在正常情况下是不返回的。

[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指令所调用的就是start_process(),对这个函数的调用不应返回,如果返回就执行指令“int 3”,那是用于Debug的自陷指令。

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -