📄
字号:
"\txor %ecx,%ecx\n"
"\txor %edx,%edx\n"
"\tret\n")[/code]
注意这里的‘\t’就是作为分隔符的TAB,所以例如“\tpushl %eax\n”就是“pushl %eax”。初始堆栈的位置和内容都是内核为其准备好的,堆栈的内容就是argc,argv[]、envp等等。至于argv[]的内容,则相当于命令行“wine-preloader wine-kthread notepad.exe”。
我们暂且不去深究这些汇编代码,但是显然这里主要的操作是对wld_start()的调用,所以我们直接看wld_start()。不过有兴趣的读者不妨自己抠一下这里对于堆栈的处理。
[code]/*
* wld_start
*
* Repeat the actions the kernel would do when loading a dynamically linked .so
* Load the binary and then its ELF interpreter.
* Note, we assume that the binary is a dynamically linked ELF shared object.
*/
void* wld_start( void **stack )
{
int i, *pargc;
char **argv, **p;
char *interp, *reserve = NULL;
ElfW(auxv_t) new_av[12], delete_av[3], *av;
struct wld_link_map main_binary_map, ld_so_map;
struct wine_preload_info **wine_main_preload_info;
pargc = *stack;
argv = (char **)pargc + 1;
if (*pargc < 2) fatal_error( "Usage: %s wine_binary [args]\n", argv[0] );
/* skip over the parameters */
p = argv + *pargc + 1;
/* skip over the environment */
while (*p)
{
static const char res[] = "WINEPRELOADRESERVE=";
if (!wld_strncmp( *p, res, sizeof(res)-1 )) reserve = *p + sizeof(res) - 1;
p++;
}
av = (ElfW(auxv_t)*) (p+1);
page_size = get_auxiliary( av, AT_PAGESZ, 4096 );
page_mask = page_size - 1;
preloader_start = (char *)_start - ((unsigned int)_start & page_mask);
preloader_end = (char *)((unsigned int)(_end + page_mask) & ~page_mask);
#ifdef DUMP_AUX_INFO
wld_printf( "stack = %x\n", *stack );
for( i = 0; i < *pargc; i++ ) wld_printf("argv[%x] = %s\n", i, argv[i]);
dump_auxiliary( av );
#endif
/* reserve memory that Wine needs */
if (reserve) preload_reserve( reserve );
for (i = 0; preload_info[i].size; i++)
wld_mmap( preload_info[i].addr, preload_info[i].size,
PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0 );
/* load the main binary */
map_so_lib( argv[1], &main_binary_map);
/* load the ELF interpreter */
interp = (char *)main_binary_map.l_addr + main_binary_map.l_interp;
map_so_lib( interp, &ld_so_map);
/* store pointer to the preload info into the appropriate main binary variable */
wine_main_preload_info = find_symbol( main_binary_map.l_phdr,
main_binary_map.l_phnum,
"wine_main_preload_info" );
if (wine_main_preload_info) *wine_main_preload_info = preload_info;
else wld_printf( "wine_main_preload_info not found\n" );
#define SET_NEW_AV(n,type,val) new_av[n].a_type = (type); new_av[n].a_un.a_val = (val);
SET_NEW_AV( 0, AT_PHDR, (unsigned long)main_binary_map.l_phdr );
SET_NEW_AV( 1, AT_PHENT, sizeof(ElfW(Phdr)) );
SET_NEW_AV( 2, AT_PHNUM, main_binary_map.l_phnum );
SET_NEW_AV( 3, AT_PAGESZ, page_size );
SET_NEW_AV( 4, AT_BASE, ld_so_map.l_addr );
SET_NEW_AV( 5, AT_FLAGS, get_auxiliary( av, AT_FLAGS, 0 ) );
SET_NEW_AV( 6, AT_ENTRY, main_binary_map.l_entry );
SET_NEW_AV( 7, AT_UID, get_auxiliary( av, AT_UID, wld_getuid() ) );
SET_NEW_AV( 8, AT_EUID, get_auxiliary( av, AT_EUID, wld_geteuid() ) );
SET_NEW_AV( 9, AT_GID, get_auxiliary( av, AT_GID, wld_getgid() ) );
SET_NEW_AV(10, AT_EGID, get_auxiliary( av, AT_EGID, wld_getegid() ) );
SET_NEW_AV(11, AT_NULL, 0 );
#undef SET_NEW_AV
i = 0;
/* delete sysinfo values if addresses conflict */
if (is_in_preload_range( av, AT_SYSINFO )) delete_av[i++].a_type = AT_SYSINFO;
if (is_in_preload_range( av, AT_SYSINFO_EHDR ))
delete_av[i++].a_type = AT_SYSINFO_EHDR;
delete_av[i].a_type = AT_NULL;
/* get rid of first argument */
pargc[1] = pargc[0] - 1;
*stack = pargc + 1;
set_auxiliary_values( av, new_av, delete_av, stack );
#ifdef DUMP_AUX_INFO
wld_printf("new stack = %x\n", *stack);
wld_printf("jumping to %x\n", ld_so_map.l_entry);
#endif
return (void *)ld_so_map.l_entry;
}[/code]
简单地说一下这段程序的作用,就是:
(1)保留本进程用户空间(虚拟地址)的某些区间,将来用于PE映像的装入。
(2)然后通过map_so_lib()装入wine-kthread的映像,但是要避开保留的那些区间。
(3)再通过map_so_lib()装入启动ELF映像所需的辅助工具ld-linux.so.2。
(4)设置一些辅助变量(程序中的new_av[ ]),主要是把有关wine-kthread映像的信息(如程序头数组的位置、数组的大小等等)传递给解释器。
(5)返回ld-linux.so.2的程序入口地址。
这里,眼下要装入的是wine-kthread的映像,但是最终要装入的却是Windows应用软件的PE格式映像。PE格式可执行程序映像的装入地址是固定的,所以不能让wine-kthread映像占了它的位置。后者本身的装入位置是固定的,并且不与前者冲突,但是其启动和实际运行却需要动态分配空间,这就可能发生冲突。因此,需要先把PE映像需要用到的地方先加以保留,把它占住,然后才能装入wine-kthread的映像。完成了装入以后,最终就转入到被装入映像wine-kthread中的main()。那么需要为PE映像保留那些区间呢?数组preload_info[]对此作出了规定:
[code]static struct wine_preload_info preload_info[] =
{
{ (void *)0x00000000, 0x00110000 }, /* DOS area */
{ (void *)0x80000000, 0x01000000 }, /* shared heap */
{ (void *)0x00110000, 0x1fef0000 }, /* PE exe range (may be set with
WINEPRELOADRESERVE),
defaults to 512mb */
{ 0, 0 } /* end of list */
};[/code]
首先,从虚拟地址0开始的1MB加64KB的区间是为DOS及其软件保留的。虽然Windows软件已经不是DOS软件,但这是从DOS发展过来的,仍有可能要用到这块空间,所以仍需保留。再往上,从虚拟地址0x00110000开始,大小为0x1fef0000的区间是为PE格式映像本身保留的。实际上这两块空间是连续的,合在一起占了从0到0x20000000、即512MB的空间。除此之外,从地址0x80000000、即2GB边界开始,大小为16MB的空间是为PE映像保留的heap空间、即动态分配的虚拟地址空间(传统上Windows对于32位地址空间的划分是2G+2G,即用户空间和系统空间各2GB,现在也可以像Linux那样划分成3G+1G)。这些都是Windows软件、即PE格式映像的约定,既要装入执行这样的映像,就必须遵守。而保留这些区间的手段,则就是通过上面代码中调用的wld_mmap(),实际上就是系统调用mmap()。注意这样保留的只是当前进程的虚拟地址空间资源,而并不立即就消耗物理存储空间。
另一方面,wine-preloader本身所占用的虚拟地址空间,则是从0x78000000开始的一块不大的空间,反正不会超过0x02000000、即32MB。这样,从0x20000000到0x78000000,大约1.4GB的地址空间仍是空闲的,足够容纳wine-kthread的映像及有关的.so映像。
为最终要装入的PE映像保留好地址空间以后,就通过map_so_lib()装入wine-kthread的映像。如前所述,此时的argv[1]就是“wine-kthread”,所以装入的就是它的映像。函数map_so_lib()的代码这里就不看了,从函数名看,这个函数是用来装入.so模块、即共享库(动态连接库)的映像的,但实际上也可以用来装入别的ELF映像,所以这里wine-kthread的映像也由map_so_lib()装入。ELF格式的目标映像在装入时需要受到一些协助,以完成与动态连接库、即.so模块的连接。这些协助要由一个工具软件来提供,称为“解释器(interpreter)”。不过这样的解释器并不是作为独立的进程运行,而是本身就以(无需动态连接的)共享库的形式装入目标映像的进程空间中运行;只是这里的动态连接很简单,只要由解释器软件提供一个总的入口即可。解释器模块与创建目标ELF映像时所使用的编译/连接工具是配套的,模块的文件名就写在目标ELF映像的头部数据结构中。所以,一旦装入了目标映像,就可以知道应该配套使用什么解释器,例如“/lib/ld-linux.so.2” (读者不妨这样试一下:“strings wine-kthread | grep ld-linux”,就可以看到wine-kthread的配套解释器就是/lib/ld-linux.so.2)。这里的main_binary_map.l_interp就是该文件名字符串在映像中的位移。所以,上面第二次调用map_so_lib()的目的就是装入ELF映像的解释器。
此外,这里还通过find_symbol()在已装入的wine-kthread映像中寻找一个名为wine_main_preload_info的变量,找到后就把结构数组preload_info[]的起始地址填写到这个变量中,这是因为wine-kthread也需要知道为PE映像和DLL所保留的空间。
最后,wld_start()返回ld_so_map.l_entry,这就是ELF映像解释器的入口地址。我们知道,函数的返回值是通过寄存器%eax传递的。回到前面_start()的汇编代码,可以看到它把%eax的内容压入堆栈,然后执行了一条ret指令,这就跳转到了解释器的入口地址。解释器的细节已经不在本文的范围之内,概而言之就是它会装入目标映像运行所需的共享库(例如linc.so),并完成目标映像与共享库的动态连接,最后跳转到目标映像的入口。然后,目标映像在完成了自身的初始化以后,就会调用目其main()。这当然就是wine-kthread的main(),也就是loader/main.c中的main()了。此外,wld_start()在返回之前对堆栈进行了调整,其效果是在原来的argv[]中跳过了argv[0],使原来的argv[1]变成了进入main()时的argv[0]。于是,对于main()来说,这就相当于命令行“wine-kthread notepad.exe”。
[code]int main( int argc, char *argv[] )
{
char error[1024];
int i;
if (wine_main_preload_info)
{
for (i = 0; wine_main_preload_info[i].size; i++)
wine_mmap_add_reserved_area( wine_main_preload_info[i].addr,
wine_main_preload_info[i].size );
}
wine_init( argc, argv, error, sizeof(error) );
fprintf( stderr, "wine: failed to initialize: %s\n", error );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -