Wine源码中添加新的DLL模块
1. 基础环境准备
编译环境:debootstrap 安装 debian bullseye
源码版本:Wine 9.0-rc4
基础环境搭建
2. 创建DLL模块目录
在dlls目录下新建一个文件夹:nfs
将amsi目录下的三个文件全部复制到nfs目录下:
main.c 文件内容中新加一个函数如下:
BOOLEAN WINAPI Test_In_CreateWindowEx( const WCHAR *classname, ULONG* style, ULONG* dwExStyle )
{
TRACE( "classname=%s, style=0x08x, style=0x08x \n", debugstr_w(classname), style, dwExStyle);
return TRUE;
}
spec文件改名为nfs.spec, 将上面实现的函数导出给外部调用,nfs.spec内容如下:
@ stdcall Test_In_CreateWindowEx(ptr ptr ptr)
Makefile.in文件内容如下:(IMPORTLIB是为了生成.a文件)
MODULE = nfs.dll
UNIXLIB = nfs.so
IMPORTLIB = nfs
SOURCES = \
main.c
3. 修改配置文件
将源码根目录下的 configure和configure.ac两个文件的权限改为可以编辑。
chmod 777 ./configure
chmod 777 ./configure.ac
打开configure.ac 文件找到dlls/amsi配置所在的行,按其样式,在他下方添加新的模块名
...
WINE_CONFIG_MAKEFILE(dlls/amsi)
WINE_CONFIG_MAKEFILE(dlls/nfs)
...
修改完成后,执行autoconf命令,重新生成configure文件,文件中会包含如下内容:
...
wine_fn_config_makefile dlls/nfs enable_nfs
...
4. 验证配置文件
执行./configure
...
configure: Finished. Do 'make' to compile Wine.
运行成功后,在dlls/nfs目录下可以看到,一个名为Makefile的文件生成出来, 文件内容如下:
all:
all install install-lib clean i386-windows/main.o i386-windows/nfs.dll:
@cd ../.. && $(MAKE) dlls/nfs/$@
.PHONY: all install install-lib clean
5. user32模块中调用
如果要在user32模块中调用新加的DLL中的函数,编辑dlls/user32/Makefile.in文件,将nfs加到IMPORTS后。
IMPORTS = $(PNG_PE_LIBS) gdi32 sechost advapi32 kernelbase win32u nfs
dlls/user32/win.c文件中, 声明一下Test_In_CreateWindowEx方法,然后在WIN_CreateWindowEx方法内就可以直接调用了。
5. winex11drv模块中调用
5.1 添加头文件
在winex11drv模块中调用自定义模块的函数时,需要添加函数的声明头文件test.h,然后将test.h文件放到wine代码中的include目录下,同时需要在include/Makefile.in中添加头文件,不然会编译出错,提示找不到头文件。
5.2 共享库中对外函数的实现
当使用上述同样的方法,在winex11drv中调用我们自定义的nfs时,会发现在link时会报错说找不到函数的实现,这是因为winex11drv 中如果调用其它的模块时,实际上调用的是被调用模块的so文件,因此需要显示的将so文件加到winex11drv/makefile.in 文件的UNIX_LIBS后边。
UNIX_LIBS = -lwin32u $(X_LIBS) $(X_EXTRA_LIBS) $(PTHREAD_LIBS) -lm -lnfs
添加完后编译还是会提示找不到,使用nm来查看so中我们添加的函数时,可以看到函数是加进去了,但是修饰符号是小写的t,表示你编入so文件中的函数是仅限模块内部使用,其实在configure中可以看到, wine在编译时会默认加上下边的选项:
CFLAGS="$CFLAGS -fvisibility=hidden"
如果需要so中的函数定义在外部模块使用,需要显示的将函数声明为 attribute_((visibility (“default”))), 加好后重新编译,再用nm来查看生成的so文件时,可以看到编入so的函数的修饰符由小写的t变为大写的T了,这时就允许外部调用了。
5.3 映射dll中的声明到so
wine中的dll是用来模拟windows调用的,实际执行时还是要运行对应的windows代码,因此dlls中的函数通常是fake的,也就是说需要建立一个dll中函数调用时,映射到对so中的linux函数的调用。
如果要生成.so文件,单纯的添加UNIXLIB = nfs.so,是无法加入函数的实现的。
首选创建一个test_so.c,然后在文件中添加函数test_so_fun(),
为了让test_so.c生成的目标文件不放到i386-windows中,需要在文件顶部添加一段代码:
#if 0
#pragma makedep unix
#endif
然后将导出函数添加到spec文件中,如果没有syscall修饰可能生成失败:
@ stdcall -syscall test_so_fun()
最后将 test_so.c 加到Makefile.in中;
这时执行如下编译命令:
configure && make -j16
会报错,提示找不到test_so_fun的实现,原因是: 生成的dll文件是在i386-windows目录生成的,然后包含了实现的目标文件 test_so.o 并不在 i386-windows中,而是在模块的根目录dlls/nfs下,此时 nfs.so 中包含了test_so_fun的实现。
那么如何才能编译通过,并使其调用时链接到nfs.so 中的实现,就是要解决的问题。
通过对比可以发现,win32u模块中可以编译通过是因为有如下的代码:
//win32syscalls.h
#define ALL_SYSCALLS32 \
SYSCALL_ENTRY( 0x0000, NtGdiAbortDoc, 4 ) \
SYSCALL_ENTRY( 0x0001, NtGdiAbortPath, 4 ) \
SYSCALL_ENTRY( 0x0002, NtGdiAddFontMemResourceEx, 20 ) \
因此我们使用一下wine源码中tools目录下的 make_specfiles 在wine源码的根目录执行后,会通过spec文件中指定的函数,自动生成上述的定义。
接下来需要在dlls/nfs/main.c文件中添加引用代码:
void *__wine_syscall_dispatcher = NULL;
static unixlib_handle_t nfs_handle;
#ifdef _WIN64
#define SYSCALL_ENTRY(id,name,args) __ASM_SYSCALL_FUNC( id + 0x1000, name )
ALL_SYSCALLS64
#else
#define SYSCALL_ENTRY(id,name,args) __ASM_SYSCALL_FUNC( id + 0x2000, name, args )
DEFINE_SYSCALL_HELPER32()
ALL_SYSCALLS32
#endif
#undef SYSCALL_ENTRY
BOOL WINAPI DllMain( HINSTANCE inst, DWORD reason, void *reserved )
{
HMODULE ntdll;
void **dispatcher_ptr;
const UNICODE_STRING ntdll_name = RTL_CONSTANT_STRING( L"ntdll.dll" );
switch (reason)
{
case DLL_PROCESS_ATTACH:
LdrDisableThreadCalloutsForDll( inst );
if (__wine_syscall_dispatcher) break; /* already set through Wow64Transition */
LdrGetDllHandle( NULL, 0, &ntdll_name, &ntdll );
dispatcher_ptr = RtlFindExportedRoutineByName( ntdll, "__wine_syscall_dispatcher" );
__wine_syscall_dispatcher = *dispatcher_ptr ;
if (!NtQueryVirtualMemory( GetCurrentProcess(), inst, MemoryWineUnixFuncs,
&nfs_handle, sizeof(nfs_handle), NULL ))
__wine_unix_call( nfs_handle, 0, NULL );
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
这些都完成后,编译就可以通过了,编译出来的nfs.dll和nfs.so可以用nm来查看一下,都包含了test_so_fun
6. 新加模块的初始化
先简单说明一下启动流程:
loader模块: main函数: 加载ntdll.so -> 执行dlsym( handle, "__wine_main" ) -> 调用dlls/ntdll模块中的 __wine_main
dlls/ntdll 模块: __wine_main函数: check_command_line -> loader_exec -> start_main_thread -> load_ntdll
load_ntdll模块启动后,它会负责加载其它的dll模块,大部分模块在被ntrdll加载时会调用模块内的__wine_unix_call_funcs数据中的函数指针,数据中的函数指针对应的函数都会顺序执行。类似如下这种:
const unixlib_entry_t __wine_unix_call_funcs[] =
{
init1,
init2,
init3,
};
这个__wine_unix_call_funcs数组之所以能被调用是因为每一个dll模块dllmain的执行时触发的,具体流程如下:
可以将需要初始化的操作放到相关的init中,你可以自己尝试一下如何将新加的模块也放入ntdll中来启动。文章来源:https://www.toymoban.com/news/detail-792825.html
7. 新加模块中调用其它模块
这个问题需要区分要调用的其它模块是so形式还是i386下.a或.dll形式, 如果在user32模块调用,可以发现直接修改Makefile.in中IMPORTS加上要引用的新模块就可以直接使用了,但是如果是win32u中,可以发现无法直接使用,因为win32u还要生成so文件,因此会提示找不到新模块中的函数定义,猜猜应该如何解决?文章来源地址https://www.toymoban.com/news/detail-792825.html
到了这里,关于Wine源码中添加新的DLL模块的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!