Wine源码中添加新的DLL模块

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是一个模块文件,用于生成makefile文件, 其示例内容如下:(IMPORTLIB是为了生成.a文件)

MODULE    = nfs.dll # 生成windows动态链接库的名字
UNIXLIB   = nfs.so  # 生成Linux下的共享库的名字
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文件,文件中会包含2处新加的模块变更:

ac_user_opts='
...
enable_nfs
...
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后。
加载nfs.dll

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 共享库中对外函数的实现

分两种调用情况:

  1. 当调用nfs模块中的函数是在如下三个文件中时
    dllmain.c systray.c xdnd.c ,可以直接成功调用,因为这三个目标文件是生成在i386-windows中,也就是模拟windows的dll库,虽然他的名字叫winex11.drv,实际是一个dll.
  2. ,在winex11drv中调用我们自定义的nfs时,当果在上述三个文件之外,会发现在link时会报错说找不到函数的实现,这是因为winex11drv 本身需要生成so库,如果在生成so库的源文件中,调用其它的模块时,实际上需要的是被调用模块的so文件,因此需要显示的将so文件加到winex11drv/makefile.in 文件的UNIX_LIBS后边。
UNIX_LIBS = -lwin32u $(X_LIBS) $(X_EXTRA_LIBS) $(PTHREAD_LIBS) -lm -lnfs

理论上这样添加后就可以调用到nfs.so中的方法了,但是添加完后还是会提示找不到实现,
使用nm来查看so中我们添加的函数时,可以看到函数是加进去了,但是修饰符号是小写的t,表示你编入so文件中的函数是仅限模块内部使用,其实在configure中可以看到, wine在编译时会默认加上下边的选项:

CFLAGS="$CFLAGS -fvisibility=hidden"

如果需要so中的函数定义在外部模块使用,需要显示的将函数声明为 attribute_((visibility (“default”))), 加好后重新编译,再用nm来查看生成的so文件时,可以看到编入so的函数的修饰符由小写的t变为大写的T了,这时就允许外部调用了。

5.3 添加系统级的封装函数

wine6开始代码中的dll是用来模拟windows调用的,实际执行时还是要运行对应的windows代码,因此dlls中的函数通常是fake的,也就是说需要建立一个dll中函数调用时,映射到对so中的linux函数的调用。

系统级别的函数有几个好处:

  1. 可自动生成声明函数;
  2. 执行效率优化后更高;
  3. 跨平台可移植性更好;
  4. 异常错误处理统一化;
    举个例子:如果我们需要包装一个与文件操作相关的函数时,可以使用-syscall来封装不同操作系统下的不同文件操作方式,以及对路径的不同处理方式,而对上层调用方来说,是统一无差异的。
    wine源码中的NtTerminateThread函数就使用了-syscall标识符,其代码实现如下:
NTSTATUS WINAPI NtTerminateThread( HANDLE handle, LONG exit_code )
{
    unsigned int ret;
    BOOL self;

    SERVER_START_REQ( terminate_thread )
    {
        req->handle    = wine_server_obj_handle( handle );
        req->exit_code = exit_code;
        ret = wine_server_call( req );
        self = !ret && reply->self;
    }
    SERVER_END_REQ;

    if (self)
    {
        server_select( NULL, 0, SELECT_INTERRUPTIBLE, 0, NULL, NULL );
        exit_thread( exit_code );
    }
    return ret;
}

大部分-syscall 的方法是由wineserver来实现的,将复杂的逻辑和错误处理都包装了起来,让外部更容易使用。

接着说明关于so的文件生成:
如果要生成.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模块,大部分模块在被ntdll加载时会调用模块内的__wine_unix_call_funcs数据中的函数指针,数据中的函数指针对应的函数都会顺序执行。类似如下这种:

const unixlib_entry_t __wine_unix_call_funcs[] =
{
    init1,
    init2,
    init3,
};

这个__wine_unix_call_funcs数组之所以能被调用是因为每一个dll模块dllmain的执行时触发的,具体流程如下:
在这里插入图片描述
可以将需要初始化的操作放到相关的init中,你可以自己尝试一下如何将新加的模块也放入ntdll中来启动。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xuanwenchao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值