Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

需要修补WINE以使用不同的ld-linux.so #11

Closed
probonopd opened this issue Jul 8, 2018 · 30 comments
Closed

需要修补WINE以使用不同的ld-linux.so #11

probonopd opened this issue Jul 8, 2018 · 30 comments

Comments

@probonopd
Copy link

为了运行32位Windows应用程序,必须使用32位Windows,这又需要32位ld-linux.so.2glibc。但是现在大多数64位系统都没有安装32位兼容性库。

对于其他程序,通常可以手动加载32位ELF文件,并使用一个私有的“ld-linux.so.2”捆绑版本,如下所示:

wget -c http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc6-i386_2.24-9ubuntu2.2_amd64.deb
dpkg -x ./libc6*.deb .

HERE="$(dirname "$(readlink -f "${0}")")"

WINEPREFIX="$HERE/wineprefix/" \
  "$HERE/lib32/ld-linux.so.2"  \
  --library-path "$HERE/lib32" \
  "$HERE/wine-stable/bin/wine" \
  "$WINEPREFIX/drive_c/Program Files/App/App.exe"  "$@"

但是,对于WINE,这是行不通的。我的猜测是,WINE通过后台的其他机制启动其他的WINE实例,而后者不会使用指定的 "$HERE/lib32/ld-linux.so.2"--library-path "$HERE/lib32"

葡萄酒开发商亚历山大Julliard [葡萄酒开发回答](https://www.winehq.org/pipermail/wine-devel/2017-November/119944.html):

It's not possible in standard Wine, but you could probably hack
wine_exec_wine_binary() in libs/wine/config.c to add your special magic.

:建设:这需要完成。我们将非常欢迎捐款。

与此同时,我们可以通过在/ tmp这个固定位置放置一个符号链接到我们自定义的ld-linux.so.2来避开这个限制,但这是一个很丑恶的事情。


In order to run 32-bit Windows applications, 32-bit Windows must be used, which in turn requires 32-bit ld-linux.so.2 and glibc. But most 64-bit systems these days don't have the 32-bit compatibility libraries installed anymore.

With other programs it is usually possible to manually load the 32-bit ELF file with a private, bundled version of ld-linux.so.2 like so:

wget -c http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc6-i386_2.24-9ubuntu2.2_amd64.deb
dpkg -x ./libc6*.deb .

HERE="$(dirname "$(readlink -f "${0}")")"

WINEPREFIX="$HERE/wineprefix/" \
  "$HERE/lib32/ld-linux.so.2"  \
  --library-path "$HERE/lib32" \
  "$HERE/wine-stable/bin/wine" \
  "$WINEPREFIX/drive_c/Program Files/App/App.exe"  "$@"

However, with WINE, this does not work. My guess is that WINE launches other WINE instances through other mechanisms in the background, which in turn don't get loaded using the specified
"$HERE/lib32/ld-linux.so.2" and --library-path "$HERE/lib32".

WINE developer Alexandre Julliard answered on wine-devel:

It's not possible in standard Wine, but you could probably hack
wine_exec_wine_binary() in libs/wine/config.c to add your special magic.

🚧 This needs to be done. We would highly welcome contributions.

In the meantime, we may get around this limitation by placing a symlink to our custom ld-linux.so.2 at a fixed location such as /tmp, but it is an ugly hack.

@Hackerl
Copy link
Owner

Hackerl commented Jul 10, 2018

wine第一次启动会wine_exec_wine_binary函数调用execv 运行 wine-preloader,并将 wine path、exe path传递进去,类似于 execv("wine-preloader", {"wine-preloader","wine","TIM.exe"})。

而在wine-preloader中不会执行wine,而是使用:

    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 );

使用map_so_lib加载主程序段与解释器ld-linux,如果找不到ld-linux则会报错。


The first time the wine starts, the wine_exec_wine_binary function calls execv to run wine-preloader, and passes the wine path and exe path, similar to execv("wine-preloader", {"wine-preloader","wine","TIM.exe "}).

Instead of executing wine in the wine-preloader, use:

    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 );

Use the map_so_lib function to load the main program segment and the interpreter ld-linux. If ld-linux is not found, an error will be reported.

@Hackerl
Copy link
Owner

Hackerl commented Dec 9, 2018

ld.so是由preloader中的map_so_lib函数手动加载的,所以只需要修改一下preloader对环境变量的支持即可。

    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 );

修改为:

    map_so_lib( argv[1], &main_binary_map );

    /* load the ELF interpreter */
    interp = (char *)main_binary_map.l_addr + main_binary_map.l_interp;
    char fullpath[256] = {};
    wld_memset(fullpath, 0, sizeof(fullpath));
    if (rootpath != NULL)
        wld_strcat(fullpath, rootpath);
    wld_strcat(fullpath, interp);
    map_so_lib( fullpath, &ld_so_map );

具体参考 Commit

AppRun修改为:

#!/bin/bash
HERE="$(dirname "$(readlink -f "${0}")")"

export LD_LIBRARY_PATH="$HERE/usr/lib":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/lib":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/lib/i386-linux-gnu":$LD_LIBRARY_PATH

#Sound Library
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu/pulseaudio":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu/alsa-lib":$LD_LIBRARY_PATH

#LD Root Path
export LINUXFILEROOT="$HERE"

"$HERE/lib/ld-linux.so.2" "$HERE/bin/wine" "$@" | cat

我已经测试成功,之后会打包文件。


Ld.so is manually loaded by the map_so_lib function in the preloader, so you only need to modify the preloader support for environment variables.

    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 );

change into:

    Map_so_lib( argv[1], &main_binary_map );

    /* load the ELF interpreter */
    Interp = (char *) main_binary_map.l_addr + main_binary_map.l_interp;
    Char fullpath[256] = {};
    Wld_memset(fullpath, 0, sizeof(fullpath));
    If (rootpath != NULL)
        Wld_strcat(fullpath, rootpath);
    Wld_strcat(fullpath, interp);
    Map_so_lib( fullpath, &ld_so_map );

Specific reference Commit

AppRun is modified to:

#!/bin/bash
HERE="$(dirname "$(readlink -f "${0}")")"

Export LD_LIBRARY_PATH="$HERE/usr/lib":$LD_LIBRARY_PATH
Export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu":$LD_LIBRARY_PATH
Export LD_LIBRARY_PATH="$HERE/lib":$LD_LIBRARY_PATH
Export LD_LIBRARY_PATH="$HERE/lib/i386-linux-gnu":$LD_LIBRARY_PATH

#Sound Library
Export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu/pulseaudio":$LD_LIBRARY_PATH
Export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu/alsa-lib":$LD_LIBRARY_PATH

#LD Root Path
Export LINUXFILEROOT="$HERE"

"$HERE/lib/ld-linux.so.2" "$HERE/bin/wine" "$@" | cat

I have tested it successfully and will package the file later.

@probonopd
Copy link
Author

probonopd commented Dec 9, 2018

这看起来很好。

您想向葡萄酒开发人员发送补丁吗?他们有邮寄名单。也许他们想把它融入官方葡萄酒中。(如果你不想做,我可以代表你去做。我能用英语写作。我会给你全部的信用。)

也许PREFIX 作为变量名(而不是LINUXFILEROOT)更常见?


This looks excellent.

Do you plan to send a patch to the WINE developers mailing list so that it can be discussed by the WINE developers and perhaps be integrated there? (If you don't want to do it, I can do it on your behalf and giving your full credit.)

Maybe it is more common to use PREFIX as the variable name (instead of LINUXFILEROOT )?

@Hackerl
Copy link
Owner

Hackerl commented Dec 10, 2018

wine可能通过wine_exec_wine_binary启动其他程序,而wine_exec_wine_binary调用preloader_exec,然后使用wine-preloader或者系统调用execv启动进程。

static void preloader_exec( char **argv, int use_preloader )
{
    if (use_preloader)
    {
        static const char preloader[] = "wine-preloader";
        static const char preloader64[] = "wine64-preloader";
        char *p, *full_name;
        char **last_arg = argv, **new_argv;

        if (!(p = strrchr( argv[0], '/' ))) p = argv[0];
        else p++;

        full_name = xmalloc( p - argv[0] + sizeof(preloader64) );
        memcpy( full_name, argv[0], p - argv[0] );
        if (strendswith( p, "64" ))
            memcpy( full_name + (p - argv[0]), preloader64, sizeof(preloader64) );
        else
            memcpy( full_name + (p - argv[0]), preloader, sizeof(preloader) );

        /* make a copy of argv */
        while (*last_arg) last_arg++;
        new_argv = xmalloc( (last_arg - argv + 2) * sizeof(*argv) );
        memcpy( new_argv + 1, argv, (last_arg - argv + 1) * sizeof(*argv) );
        new_argv[0] = full_name;
        execv( full_name, new_argv );
        free( new_argv );
        free( full_name );
    }
    execv( argv[0], argv );
}

我再次修改了wine-preloader.c,只需要一点点的改变:

    while (*p)
    {
        static const char res[] = "WINEPRELOADRESERVE=", loader[] = "WINELDLIBRARY=";

        if (!wld_strncmp( *p, res, sizeof(res)-1 )) reserve = *p + sizeof(res) - 1;
        if (!wld_strncmp( *p, loader, sizeof(loader)-1 )) interp = *p + sizeof(loader) - 1;

        p++;
    }
    //...
    /* load the ELF interpreter */
    if (interp == NULL)
        interp = (char *)main_binary_map.l_addr + main_binary_map.l_interp;

    map_so_lib( interp, &ld_so_map );

使用环境变量(WINELDLIBRARY)传递ld.so的路径。
但是对于wineserver等程序,使用execv系统调用启动,所以需要hook execv syscall,让它使用特定的ld.so。

#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <stdlib.h>

/*
 * gcc -shared -fPIC -ldl
 * for i386: gcc -shared -fPIC -m32 -ldl
 * 
 * hook wine execv syscall. use special ld.so
 * */

typedef int(*EXECV)(const char*, char**);

static inline int strendswith( const char* str, const char* end )
{
    size_t len = strlen( str );
    size_t tail = strlen( end );
    return len >= tail && !strcmp( str + len - tail, end );
}

int execv(char *path, char ** argv)
{
    static void *handle = NULL;
    static EXECV old_execv = NULL;
    char **last_arg = argv;

    if( !handle )
    {
        handle = dlopen("libc.so.6", RTLD_LAZY);
        old_execv = (EXECV)dlsym(handle, "execv");
    }

    char * wineloader = getenv("WINELDLIBRARY");

    if (wineloader == NULL || strendswith(path, "wine-preloader"))
    {
        return old_execv(path, argv);
    }

    while (*last_arg) last_arg++;

    char ** new_argv = (char **) malloc( (last_arg - argv + 2) * sizeof(*argv) );
    memcpy( new_argv + 1, argv, (last_arg - argv + 1) * sizeof(*argv) );

    new_argv[0] = wineloader;

    int res = old_execv(wineloader, new_argv);

    free( new_argv );

    return res;
}

依旧使用环境变量(WINELDLIBRARY)传递ld.so的路径。
修改AppRun:

#!/bin/bash
HERE="$(dirname "$(readlink -f "${0}")")"

export LD_LIBRARY_PATH="$HERE/usr/lib":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/lib":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/lib/i386-linux-gnu":$LD_LIBRARY_PATH

#Sound Library
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu/pulseaudio":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu/alsa-lib":$LD_LIBRARY_PATH

#LD
export WINELDLIBRARY="$HERE/lib/ld-linux.so.2"

LD_PRELOAD="$HERE/bin/libhookexecv.so" "$WINELDLIBRARY" "$HERE/bin/wine" "$@" | cat

增加了libhookexecv.so文件,修改了wine-preloader文件,可以指定ld.so。
我还在测试中,大概可以运行了。


Wine may start other programs with wine_exec_wine_binary, and wine_exec_wine_binary calls preloader_exec, then use wine-preloader or system call execv to start the process.

Static void preloader_exec( char **argv, int use_preloader )
{
    If (use_preloader)
    {
        Static const char preloader[] = "wine-preloader";
        Static const char preloader64[] = "wine64-preloader";
        Char *p, *full_name;
        Char **last_arg = argv, **new_argv;

        If (!(p = strrchr( argv[0], '/' ))) p = argv[0];
        Else p++;

        Full_name = xmalloc( p - argv[0] + sizeof(preloader64) );
        Memcpy( full_name, argv[0], p - argv[0] );
        If (strendswith( p, "64" ))
            Memcpy( full_name + (p - argv[0]), preloader64, sizeof(preloader64) );
        Else
            Memcpy( full_name + (p - argv[0]), preloader, sizeof(preloader) );

        /* make a copy of argv */
        While (*last_arg) last_arg++;
        New_argv = xmalloc( (last_arg - argv + 2) * sizeof(*argv) );
        Memcpy( new_argv + 1, argv, (last_arg - argv + 1) * sizeof(*argv) );
        New_argv[0] = full_name;
        Execv( full_name, new_argv );
        Free( new_argv );
        Free( full_name );
    }
    Execv( argv[0], argv );
}

I modified wine-preloader.c again, with only a few changes:

    While (*p)
    {
        Static const char res[] = "WINEPRELOADRESERVE=", loader[] = "WINELDLIBRARY=";

        If (!wld_strncmp( *p, res, sizeof(res)-1 )) reserve = *p + sizeof(res) - 1;
        If (!wld_strncmp( *p, loader, sizeof(loader)-1 )) interp = *p + sizeof(loader) - 1;

        p++;
    }
    //...
    /* load the ELF interpreter */
    If (interp == NULL)
        Interp = (char *) main_binary_map.l_addr + main_binary_map.l_interp;

    Map_so_lib( interp, &ld_so_map );

Use the environment variable (WINELDLIBRARY) to pass the path to ld.so.
But for programs such as wineserver, use the execv system call to start, so you need to hook execv syscall to use a specific ld.so.

#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <stdlib.h>

/*
 * gcc -shared -fPIC -ldl
 * for i386: gcc -shared -fPIC -m32 -ldl
 * 
 * hook wine execv syscall. use special ld.so
 * */

typedef int(*EXECV)(const char*, char**);

static inline int strendswith( const char* str, const char* end )
{
    size_t len = strlen( str );
    size_t tail = strlen( end );
    return len >= tail && !strcmp( str + len - tail, end );
}

int execv(char *path, char ** argv)
{
    static void *handle = NULL;
    static EXECV old_execv = NULL;
    char **last_arg = argv;

    if( !handle )
    {
        handle = dlopen("libc.so.6", RTLD_LAZY);
        old_execv = (EXECV)dlsym(handle, "execv");
    }

    char * wineloader = getenv("WINELDLIBRARY");

    if (wineloader == NULL || strendswith(path, "wine-preloader"))
    {
        return old_execv(path, argv);
    }

    while (*last_arg) last_arg++;

    char ** new_argv = (char **) malloc( (last_arg - argv + 2) * sizeof(*argv) );
    memcpy( new_argv + 1, argv, (last_arg - argv + 1) * sizeof(*argv) );

    new_argv[0] = wineloader;

    int res = old_execv(wineloader, new_argv);

    free( new_argv );

    return res;
}

The path to ld.so is still passed using the environment variable (WINELDLIBRARY).
Modify AppRun:

#!/bin/bash
HERE="$(dirname "$(readlink -f "${0}")")"

export LD_LIBRARY_PATH="$HERE/usr/lib":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/lib":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/lib/i386-linux-gnu":$LD_LIBRARY_PATH

#Sound Library
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu/pulseaudio":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu/alsa-lib":$LD_LIBRARY_PATH

#LD
export WINELDLIBRARY="$HERE/lib/ld-linux.so.2"

LD_PRELOAD="$HERE/bin/libhookexecv.so" "$WINELDLIBRARY" "$HERE/bin/wine" "$@" | cat

Added libhookexecv.so file, modified wine-preloader file, you can specify ld.so.
I am still testing, probably running.

@Hackerl
Copy link
Owner

Hackerl commented Dec 10, 2018

hook syscall可能显示错误,我不知道原因是什么,ld.so和libhookexecv.so都是32位程序,但是不影响程序的运行。

ERROR: ld.so: object '/tmp/.mount_winexlirMw/bin/libhookexecv.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.

如果不想hook syscall,可以直接修改preloader_exec函数,在里面获取WINELDLIBRARY,然后使用指定的ld.so

static void preloader_exec( char **argv, int use_preloader )
{
    if (use_preloader)
    {
        static const char preloader[] = "wine-preloader";
        static const char preloader64[] = "wine64-preloader";
        char *p, *full_name;
        char **last_arg = argv, **new_argv;

        if (!(p = strrchr( argv[0], '/' ))) p = argv[0];
        else p++;

        full_name = xmalloc( p - argv[0] + sizeof(preloader64) );
        memcpy( full_name, argv[0], p - argv[0] );
        if (strendswith( p, "64" ))
            memcpy( full_name + (p - argv[0]), preloader64, sizeof(preloader64) );
        else
            memcpy( full_name + (p - argv[0]), preloader, sizeof(preloader) );

        /* make a copy of argv */
        while (*last_arg) last_arg++;
        new_argv = xmalloc( (last_arg - argv + 2) * sizeof(*argv) );
        memcpy( new_argv + 1, argv, (last_arg - argv + 1) * sizeof(*argv) );
        new_argv[0] = full_name;
        execv( full_name, new_argv );
        free( new_argv );
        free( full_name );
    }
    else
    {
        char * wineloader = getenv("WINELDLIBRARY");

        if (wineloader == NULL)
            execv( argv[0], argv );

        char **last_arg = argv;

        while (*last_arg) last_arg++;

        char ** new_argv = (char **) malloc( (last_arg - argv + 2) * sizeof(*argv) );
        memcpy( new_argv + 1, argv, (last_arg - argv + 1) * sizeof(*argv) );

        new_argv[0] = wineloader;

        execv(wineloader, new_argv);

        free( new_argv );
    }
}

Hook syscall may display an error, I don't know what the reason is, ld.so and libhookexecv.so are 32-bit programs, but does not affect the operation of the program.

ERROR: ld.so: object '/tmp/.mount_winexlirMw/bin/libhookexecv.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.

If you don't want to hook syscall, you can directly modify the preloader_exec function, get WINELDLIBRARY in it, and then use the specified ld.so

static void preloader_exec( char **argv, int use_preloader )
{
    if (use_preloader)
    {
        static const char preloader[] = "wine-preloader";
        static const char preloader64[] = "wine64-preloader";
        char *p, *full_name;
        char **last_arg = argv, **new_argv;

        if (!(p = strrchr( argv[0], '/' ))) p = argv[0];
        else p++;

        full_name = xmalloc( p - argv[0] + sizeof(preloader64) );
        memcpy( full_name, argv[0], p - argv[0] );
        if (strendswith( p, "64" ))
            memcpy( full_name + (p - argv[0]), preloader64, sizeof(preloader64) );
        else
            memcpy( full_name + (p - argv[0]), preloader, sizeof(preloader) );

        /* make a copy of argv */
        while (*last_arg) last_arg++;
        new_argv = xmalloc( (last_arg - argv + 2) * sizeof(*argv) );
        memcpy( new_argv + 1, argv, (last_arg - argv + 1) * sizeof(*argv) );
        new_argv[0] = full_name;
        execv( full_name, new_argv );
        free( new_argv );
        free( full_name );
    }
    else
    {
        char * wineloader = getenv("WINELDLIBRARY");

        if (wineloader == NULL)
            execv( argv[0], argv );

        char **last_arg = argv;

        while (*last_arg) last_arg++;

        char ** new_argv = (char **) malloc( (last_arg - argv + 2) * sizeof(*argv) );
        memcpy( new_argv + 1, argv, (last_arg - argv + 1) * sizeof(*argv) );

        new_argv[0] = wineloader;

        execv(wineloader, new_argv);

        free( new_argv );
    }
}

@Hackerl
Copy link
Owner

Hackerl commented Dec 10, 2018

你可以测试这两种修改的程序(testing releases)


You can test these two modified programs(testing releases)

@probonopd
Copy link
Author

非常感谢您抽出时间来实现这一点。我对你的解决方案印象非常深刻。

这两个版本对我来说都很好。你更喜欢哪一个?

你想把你的补丁发送到 https://www.winehq.org/mailman/listinfo/wine-devel 吗?如果你喜欢,我也可以代表你去做。


Thank you very much for taking the time to implement this. I am very impressed by your solution.

Both versions work very well for me. Which one do you prefer?

Do you want to send your patch to https://www.winehq.org/mailman/listinfo/wine-devel? If you like I can also do it on your behalf.

@Hackerl
Copy link
Owner

Hackerl commented Dec 11, 2018

我觉得两种方法都可以,因为我英文比较差,所以就拜托您了。您可以都向他们提供这两种方法,让他们自己选择,如果他们觉得没有必要修改,也没什么关系。


I think both methods are OK, because my English is poor, so I need your help. You can provide them with these two methods, let them choose, if they feel that there is no need to modify, it does not matter.

@probonopd
Copy link
Author

让我们把一个补丁发送到 WINE 的 nohook 版本。如果他们不接受补丁,那么我们可以使用 LD_PRELOAD 版本。你同意吗?然后我代表你把它寄给你。

下面的提交是否遗漏了什么?你能把它更新一下吗?

Hackerl/wine@c609773


Let's send a patch to WINE for the nohook version. If they will not accept the patch, then we can use the LD_PRELOAD version. Do you agree? Then I will send it on your behalf.

Is something missing in the following commit? Can you please update it?

Hackerl/wine@c609773

@Hackerl
Copy link
Owner

Hackerl commented Dec 12, 2018

非常同意,我刚刚整理了一遍代码,重新做了一次提交,谢谢你的帮助 -- Hackerl/wine@4f7f784


Very much agree, I just sorted out the code and made a new commit. Thank you for your help -- Hackerl/wine@4f7f784

@probonopd
Copy link
Author

@probonopd
Copy link
Author

你有没有用某种方式修改过 ld-linux.so ?还是你使用了未修改过的?


Did you modify ld-linux.so in some way? Or did you used the unmodified one?

@Hackerl
Copy link
Owner

Hackerl commented Dec 16, 2018

你为什么这样问,我没修改过,是不是因为hook execv报错。

ERROR: ld.so: object '/tmp/.mount_winexlirMw/bin/libhookexecv.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.


Why are you asking this, I have not modified it.
Is it because hook execv gives an error?

ERROR: ld.so: object '/tmp/.mount_winexlirMw/bin/libhookexecv.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.

@probonopd
Copy link
Author

probonopd commented Dec 16, 2018

The original ./Wine-hook-x86_64.AppImage explorer.exe works. But when I try to do it on my own, it does not work. Getting /lib/ld-linux.so.2: could not open:

# For testing, disable system /lib/ld-linux.so.2
sudo mv /lib/ld-linux.so.2 /lib/ld-linux.so.2.disabled || true

# Get Wine
wget https://www.playonlinux.com/wine/binaries/linux-x86/PlayOnLinux-wine-3.5-linux-x86.pol
tar xfvj PlayOnLinux-wine-*-linux-x86.pol wineversion/
cd wineversion/*/

# Get suitable old ld-linux.so and the stuff that comes with it
wget http://ftp.us.debian.org/debian/pool/main/g/glibc/libc6_2.19-18+deb8u10_i386.deb
dpkg -x libc6_2.19-18+deb8u10_i386.deb  .

# Make absolutely sure it will not load stuff from /lib or /usr
sed -i -e 's|/usr|/xxx|g' lib/ld-linux.so.2
sed -i -e 's|/lib|/XXX|g' lib/ld-linux.so.2

# Remove duplicate (why is it there?)
rm -f lib/i386-linux-gnu/ld-*.so

# Get libhookexecv.so
wget -c https://github.com/probonopd/libhookexecv/releases/download/continuous/libhookexecv.so -O lib/libhookexecv.so 

Then run like this:

cat > AppRun <<\EOF
#!/bin/bash
HERE="$(dirname "$(readlink -f "${0}")")"
export LDLINUX="$HERE/lib/ld-linux.so.2" # Patched to not load stuff from /lib
export WINELDLIBRARY="$LDLINUX" # libhookexecv uses the WINELDLIBRARY variable to patch wineloader on the fly
export LD_PRELOAD=$(readlink -f "$HERE/lib/libhookexecv.so")
export LD_LIBRARY_PATH=$(readlink -f "$HERE/lib/"):$(readlink -f "$HERE/lib/i386-linux-gnu"):$LD_LIBRARY_PATH
"$LDLINUX" --inhibit-cache "$HERE/bin/wine" "$@"
EOF
chmod +x AppRun

./AppRun explorer.exe

However I get this error:

./AppRun explorer.exe

ERROR: ld.so: object '/home/me/Downloads/wineversion/3.5/lib/libhookexecv.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.
ERROR: ld.so: object '/home/me/Downloads/wineversion/3.5/lib/libhookexecv.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.
/lib/ld-linux.so.2: could not open

@Hackerl
Copy link
Owner

Hackerl commented Dec 16, 2018

因为你没修改wine-preloader,wine-preloader不使用任何库函数,使用中断系统调用加载ld.so.

    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 );
static inline int wld_open( const char *name, int flags )
{
    int ret;
    __asm__ __volatile__( "pushl %%ebx; movl %2,%%ebx; int $0x80; popl %%ebx"
                          : "=a" (ret) : "0" (5 /* SYS_open */), "r" (name), "c" (flags) );
    return SYSCALL_RET(ret);
}
static void map_so_lib( const char *name, struct wld_link_map *l)
{
    int fd;
    unsigned char buf[0x800];
    ElfW(Ehdr) *header = (ElfW(Ehdr)*)buf;
    ElfW(Phdr) *phdr, *ph;
    /* Scan the program header table, collecting its load commands.  */
    struct loadcmd
      {
        ElfW(Addr) mapstart, mapend, dataend, allocend;
        off_t mapoff;
        int prot;
      } loadcmds[16], *c;
    size_t nloadcmds = 0, maplength;

    fd = wld_open( name, O_RDONLY );
    if (fd == -1) fatal_error("%s: could not open\n", name );
  //....
}

补丁

    while (*p)
    {
        static const char res[] = "WINEPRELOADRESERVE=", loader[] = "WINELDLIBRARY=";

        if (!wld_strncmp( *p, res, sizeof(res)-1 )) reserve = *p + sizeof(res) - 1;
        if (!wld_strncmp( *p, loader, sizeof(loader)-1 )) interp = *p + sizeof(loader) - 1;

        p++;
    }
    //...
    /* load the ELF interpreter */
    if (interp == NULL)
        interp = (char *)main_binary_map.l_addr + main_binary_map.l_interp;

    map_so_lib( interp, &ld_so_map );

已修改文件: wine-preloader-patched.zip


Because you didn't modify the wine-preloader, the wine-preloader doesn't use any library functions, and uses the interrupt system call to load ld.so.

    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 );
static inline int wld_open( const char *name, int flags )
{
    int ret;
    __asm__ __volatile__( "pushl %%ebx; movl %2,%%ebx; int $0x80; popl %%ebx"
                          : "=a" (ret) : "0" (5 /* SYS_open */), "r" (name), "c" (flags) );
    return SYSCALL_RET(ret);
}
static void map_so_lib( const char *name, struct wld_link_map *l)
{
    int fd;
    unsigned char buf[0x800];
    ElfW(Ehdr) *header = (ElfW(Ehdr)*)buf;
    ElfW(Phdr) *phdr, *ph;
    /* Scan the program header table, collecting its load commands.  */
    struct loadcmd
      {
        ElfW(Addr) mapstart, mapend, dataend, allocend;
        off_t mapoff;
        int prot;
      } loadcmds[16], *c;
    size_t nloadcmds = 0, maplength;

    fd = wld_open( name, O_RDONLY );
    if (fd == -1) fatal_error("%s: could not open\n", name );
  //....
}

patch preload.c

    while (*p)
    {
        static const char res[] = "WINEPRELOADRESERVE=", loader[] = "WINELDLIBRARY=";

        if (!wld_strncmp( *p, res, sizeof(res)-1 )) reserve = *p + sizeof(res) - 1;
        if (!wld_strncmp( *p, loader, sizeof(loader)-1 )) interp = *p + sizeof(loader) - 1;

        p++;
    }
    //...
    /* load the ELF interpreter */
    if (interp == NULL)
        interp = (char *)main_binary_map.l_addr + main_binary_map.l_interp;

    map_so_lib( interp, &ld_so_map );

Patched file: wine-preloader-patched.zip

@probonopd
Copy link
Author

也可以使用LD_PRELOAD吗?
我不想构建Wine...


Can that be done using LD_PRELOAD too?
I don't want to build Wine...

@probonopd
Copy link
Author

能否给出一个完整的脚本来创建Wine-hook-x86_64.AppImage
我想对你的善意表示感谢。


Can you please give a whole script how to create Wine-hook-x86_64.AppImage?
Thank you for your kindness.

@Hackerl
Copy link
Owner

Hackerl commented Dec 16, 2018

wine-preload未来应该不会再有很大的变更,所以你可以使用我编译好的二进制文件替换原始文件.LD_PRELOAD是由ld.so预先加载的,用于hook一些库函数,例如open函数(in libc.so).但是wine-preload使用 int 0x80进行系统调用,或许只能考虑ptrace进行hook syscall.


Wine-preload should not change much in the future, so you can replace the original file with my compiled binary. LD_PRELOAD is preloaded by ld.so and is used to hook some library functions, such as the open function (in Libc.so). But wine-preload uses int 0x80 for system calls, perhaps only consider ptrace for hook syscall.

@Hackerl
Copy link
Owner

Hackerl commented Dec 16, 2018

能否给出一个完整的脚本来创建Wine-hook-x86_64.AppImage
我想对你的善意表示感谢。

Can you please give a whole script how to create Wine-hook-x86_64.AppImage?
Thank you for your kindness.

# Get Wine
wget https://www.playonlinux.com/wine/binaries/linux-x86/PlayOnLinux-wine-3.5-linux-x86.pol
tar xfvj PlayOnLinux-wine-*-linux-x86.pol wineversion/
cd wineversion/*/

# Get suitable old ld-linux.so and the stuff that comes with it
wget -c http://ftp.us.debian.org/debian/pool/main/g/glibc/libc6_2.24-11+deb9u3_i386.deb
dpkg -x libc6_2.24-11+deb9u3_i386.deb .

# Add a dependency library, such as freetype font library
# .....

# Get libhookexecv.so
wget -c https://github.com/probonopd/libhookexecv/releases/download/continuous/libhookexecv.so -O lib/libhookexecv.so

# Get patched wine-preload
wget -c https://github.com/Hackerl/Wine_Appimage/releases/download/testing/wine-preloader-patched.zip
unzip wine-preloader-patched.zip
mv wine-preloader bin/

# Clean
rm wine-preloader-patched.zip
rm libc6_2.24-11+deb9u3_i386.deb

cat > AppRun <<\EOF
#!/bin/bash
HERE="$(dirname "$(readlink -f "${0}")")"

export LD_LIBRARY_PATH="$HERE/usr/lib":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/lib":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/lib/i386-linux-gnu":$LD_LIBRARY_PATH

#Sound Library
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu/pulseaudio":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu/alsa-lib":$LD_LIBRARY_PATH

#LD
export WINELDLIBRARY="$HERE/lib/ld-linux.so.2"

LD_PRELOAD="$HERE/lib/libhookexecv.so" "$WINELDLIBRARY" "$HERE/bin/wine" "$@" | cat
EOF

chmod +x AppRun

# Run
./AppRun explorer.exe

wine-preload will not change much in the future, so it can be directly replaced with a patch file.Now wine can run, but it will report an error because there is no dependent library installed.You can use "apt download" to download the listed dependencies and then extract them using "dpkg -x".

gcc-6-base:i386 i965-va-driver:i386 libasound2:i386 libasound2-plugins:i386 libasyncns0:i386 libavahi-client3:i386 libavahi-common-data:i386 libavahi-common3:i386 libavcodec57:i386 libavresample3:i386 libavutil55:i386 libbsd0:i386 libc6:i386 libcairo2:i386 libcap2:i386 libcomerr2:i386 libcrystalhd3:i386 libcups2:i386 libdb5.3:i386 libdbus-1-3:i386 libdrm-amdgpu1:i386 libdrm-intel1:i386 libdrm-nouveau2:i386 libdrm-radeon1:i386 libdrm2:i386 libedit2:i386 libelf1:i386 libexpat1:i386 libffi6:i386 libflac8:i386 libfontconfig1:i386 libfreetype6:i386 libgcc1:i386 libgcrypt20:i386 libgl1-mesa-dri:i386 libgl1-mesa-glx:i386 libglapi-mesa:i386 libglu1-mesa:i386 libgmp10:i386 libgnutls30:i386 libgomp1:i386 libgpg-error0:i386 libgpm2:i386 libgsm1:i386 libgssapi-krb5-2:i386 libhogweed4:i386 libice6:i386 libicu57:i386 libidn11:i386 libjack-jackd2-0:i386 libjbig0:i386 libjpeg62-turbo:i386 libk5crypto3:i386 libkeyutils1:i386 libkrb5-3:i386 libkrb5support0:i386 liblcms2-2:i386 libldap-2.4-2:i386 libllvm3.9:i386 libltdl7:i386 liblz4-1:i386 liblzma5:i386 libmp3lame0:i386 libmpg123-0:i386 libncurses5:i386 libnettle6:i386 libnuma1:i386 libodbc1:i386 libogg0:i386 libopenal1:i386 libopenjp2-7:i386 libopus0:i386 libosmesa6:i386 libp11-kit0:i386 libpcap0.8:i386 libpciaccess0:i386 libpcre3:i386 libpixman-1-0:i386 libpng16-16:i386 libpulse0:i386 libsamplerate0:i386 libsasl2-2:i386 libsasl2-modules:i386 libsasl2-modules-db:i386 libselinux1:i386 libsensors4:i386 libshine3:i386 libsm6:i386 libsnappy1v5:i386 libsndfile1:i386 libsndio6.1:i386 libsoxr0:i386 libspeex1:i386 libspeexdsp1:i386 libssl1.1:i386 libstdc++6:i386 libswresample2:i386 libsystemd0:i386 libtasn1-6:i386 libtheora0:i386 libtiff5:i386 libtinfo5:i386 libtwolame0:i386 libtxc-dxtn-s2tc:i386 libuuid1:i386 libva-drm1:i386 libva-x11-1:i386 libva1:i386 libvdpau-va-gl1:i386 libvdpau1:i386 libvorbis0a:i386 libvorbisenc2:i386 libvpx4:i386 libwavpack1:i386 libwebp6:i386 libwebpmux2:i386 libwrap0:i386 libx11-6:i386 libx11-xcb1:i386 libx264-148:i386 libx265-95:i386 libxau6:i386 libxcb-dri2-0:i386 libxcb-dri3-0:i386 libxcb-glx0:i386 libxcb-present0:i386 libxcb-render0:i386 libxcb-shm0:i386 libxcb-sync1:i386 libxcb1:i386 libxcomposite1:i386 libxcursor1:i386 libxdamage1:i386 libxdmcp6:i386 libxext6:i386 libxfixes3:i386 libxi6:i386 libxinerama1:i386 libxml2:i386 libxrandr2:i386 libxrender1:i386 libxshmfence1:i386 libxslt1.1:i386 libxtst6:i386 libxvidcore4:i386 libxxf86vm1:i386 libzvbi0:i386 mesa-va-drivers:i386 mesa-vdpau-drivers:i386 ocl-icd-libopencl1:i386 va-driver-all:i386 vdpau-driver-all:i386 zlib1g:i386

@Hackerl
Copy link
Owner

Hackerl commented Dec 17, 2018

I found a way to not modify any files. Because the "wine-preloader" uses "int 0x80" for system calls, ptrace is used for hook syscall.

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <stdio.h>
#include <syscall.h>
#include <fcntl.h>
#include <string.h>

/* 
 * sudo apt-get -y install gcc-multilib
 * gcc -static preloaderhook.c -o wine-preloader_hook
 * for i386: gcc -m32 -static preloaderhook.c -o wine-preloader_hook
 * Put the file in the /bin directory, in the same directory as the wine-preloader.
 * hook int 0x80 open syscall. use special ld.so
 * */

#define LONGSIZE sizeof(long)
#define TARGET_PATH "/lib/ld-linux.so.2"
#define HasZeroByte(v) ~((((v & 0x7F7F7F7F) + 0x7F7F7F7F) | v) | 0x7F7F7F7F)

int main(int argc, char ** argv)
{
    printf("======= Ptrace Hook =======\n");
    if (argc < 2)
        return 0;

    int LD_fd = -1;
    char * wineloader = (char *) getenv("WINELDLIBRARY");

    if (wineloader != NULL)
        LD_fd = open(wineloader, O_RDONLY);

    pid_t child = fork();

    if(child == 0)
    {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execv(*(argv + 1), argv + 1);
    }
    else
    {
        while(1)
        {
            int status = 0;
            wait(&status);

            if(WIFEXITED(status))
                break;

            long orig_eax = ptrace(PTRACE_PEEKUSER, 
                            child, 4 * ORIG_EAX, 
                            NULL);

            static int insyscall = 0;
            static int hasfind = 0;

            if (orig_eax == SYS_open)
            {
                if(insyscall == 0)
                {    
                    /* Syscall entry */
                    insyscall = 1;

                    //Get Path Ptr
                    long ebx = ptrace(PTRACE_PEEKUSER, 
                                child, 4 * EBX, NULL);

                    char Path[256];
                    memset(Path, 0, 256);

                    //Read Path String
                    for (int i = 0; i < sizeof(Path)/LONGSIZE; i ++)
                    {
                        union 
                        {
                            long val;
                            char chars[LONGSIZE];
                        } data;

                        data.val = ptrace(PTRACE_PEEKDATA, child, ebx + i * 4, NULL);
                        
                        memcpy(Path + i * 4, data.chars, LONGSIZE);

                        if (HasZeroByte(data.val))
                            break;
                    }
                    
                    if (strcmp(Path, TARGET_PATH) == 0)
                        hasfind = 1;
                }
                else
                { 
                    /* Syscall exit */
                    insyscall = 0;

                    long eax = ptrace(PTRACE_PEEKUSER, 
                                child, 4 * EAX, NULL);

                    //Modify Open Syscall Ret
                    if (hasfind && LD_fd != -1)
                    {
                        ptrace(PTRACE_POKEUSER, child, 4 * EAX, LD_fd);
                        ptrace(PTRACE_DETACH, child, NULL, NULL);
                        break;
                    }
                }
            }

            ptrace(PTRACE_SYSCALL, child, NULL, NULL);
        }
    }

    close(LD_fd);
    return 0;
}
}

Then modify libhookexecv.so and jump to wine-preloader_hook when executing wine-preloader.

#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <stdlib.h>

/* Author: https://github.com/Hackerl/
 * https://github.com/Hackerl/Wine_Appimage/issues/11#issuecomment-445724165
 * sudo apt-get -y install gcc-multilib
 * gcc -shared -fPIC -ldl libhookexecv.c -o libhookexecv.so
 * for i386: gcc -shared -fPIC -m32 -ldl libhookexecv.c -o libhookexecv.so
 * 
 * hook wine execv syscall. use special ld.so
 * */

typedef int(*EXECV)(const char*, char**);

static inline int strendswith( const char* str, const char* end )
{
    size_t len = strlen( str );
    size_t tail = strlen( end );
    return len >= tail && !strcmp( str + len - tail, end );
}

int execv(char *path, char ** argv)
{
    static void *handle = NULL;
    static EXECV old_execv = NULL;
    char **last_arg = argv;

    if( !handle )
    {
        handle = dlopen("libc.so.6", RTLD_LAZY);
        old_execv = (EXECV)dlsym(handle, "execv");
    }

    char * wineloader = getenv("WINELDLIBRARY");

    if (wineloader == NULL)
    {
        return old_execv(path, argv);
    }

    while (*last_arg) last_arg++;

    char ** new_argv = (char **) malloc( (last_arg - argv + 2) * sizeof(*argv) );
    memcpy( new_argv + 1, argv, (last_arg - argv + 1) * sizeof(*argv) );

    char * pathname = NULL;

    char hookpath[256];
    memset(hookpath, 0, 256);

    if (strendswith(path, "wine-preloader"))
    {
        strcat(hookpath, path);
        strcat(hookpath, "_hook");
        
        wineloader = hookpath;
    }

    new_argv[0] = wineloader;
    int res = old_execv(wineloader, new_argv);
    free( new_argv );

    return res;
}

test file https://github.com/Hackerl/Wine_Appimage/releases/tag/testing

@probonopd
Copy link
Author

Wine-ptrace-x86_64.AppImage 对我工作得很好。你是个天才。祝贺你!


Wine-ptrace-x86_64.AppImage is working very well for me. You are a genius. Congratulations!

@Hackerl
Copy link
Owner

Hackerl commented Dec 18, 2018

上一个版本的程序,如果ld.so存在,会导致打开多余的文件句柄,所以"Open Syscall"不应该被执行。在系统调用发生时,将调用号修改为-1,调用必然会失败,然后再填入正确的fd.


The previous version of the program, if ld.so exists, will cause the extra file handle to be opened, so "Open Syscall" should not be executed. When the system call occurs, the call number is changed to -1, the call will fail, and then fill in the correct fd.

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <stdio.h>
#include <syscall.h>
#include <fcntl.h>
#include <string.h>

/* 
 * sudo apt-get -y install gcc-multilib
 * only for i386: gcc -m32 -static preloaderhook.c -o wine-preloader_hook
 * Put the file in the /bin directory, in the same directory as the wine-preloader.
 * hook int 0x80 open syscall. use special ld.so
 * */

#define LONGSIZE sizeof(long)
#define TARGET_PATH "/lib/ld-linux.so.2"
#define HasZeroByte(v) ~((((v & 0x7F7F7F7F) + 0x7F7F7F7F) | v) | 0x7F7F7F7F)
#define HOOK_OPEN_LD_SYSCALL -1

int main(int argc, char ** argv)
{
    printf("======= Ptrace Hook =======\n");

    if (argc < 2)
        return 0;

    char * wineloader = (char *) getenv("WINELDLIBRARY");

    if (wineloader == NULL)
    {
        printf("WINELDLIBRARY Not Found\n");
        return 0;
    }

    int LD_fd = open(wineloader, O_RDONLY);

    if (LD_fd == -1)
    {
        printf("WINELDLIBRARY Open Failed\n");
        return 0;
    }

    pid_t child = fork();

    if(child == 0)
    {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execv(*(argv + 1), argv + 1);
    }
    else
    {
        while(1)
        {
            int status = 0;
            wait(&status);

            if(WIFEXITED(status))
                break;

            long orig_eax = ptrace(PTRACE_PEEKUSER, 
                            child, 4 * ORIG_EAX, 
                            NULL);

            static int insyscall = 0;

            if (orig_eax == HOOK_OPEN_LD_SYSCALL)
            {
                printf("Modify Open Syscall: %s\n", wineloader);

                ptrace(PTRACE_POKEUSER, child, 4 * EAX, LD_fd);

                //Detch
                ptrace(PTRACE_DETACH, child, NULL, NULL);
                break;
            }

            if (orig_eax == SYS_open)
            {
                if(insyscall == 0)
                {    
                    /* Syscall entry */
                    insyscall = 1;

                    //Get Path Ptr
                    long ebx = ptrace(PTRACE_PEEKUSER, 
                                child, 4 * EBX, NULL);

                    char Path[256];
                    memset(Path, 0, 256);

                    //Read Path String
                    for (int i = 0; i < sizeof(Path)/LONGSIZE; i ++)
                    {
                        union 
                        {
                            long val;
                            char chars[LONGSIZE];
                        } data;

                        data.val = ptrace(PTRACE_PEEKDATA, child, ebx + i * 4, NULL);
                        
                        memcpy(Path + i * 4, data.chars, LONGSIZE);

                        if (HasZeroByte(data.val))
                            break;
                    }
                    
                    if (strcmp(Path, TARGET_PATH) == 0)
                    {
                        printf("Found Open Syscall: %s\n", TARGET_PATH);

                        //Modify Syscall -1. So Will Not Call Open Syscall.
                        ptrace(PTRACE_POKEUSER, child, 4 * ORIG_EAX, HOOK_OPEN_LD_SYSCALL);
                    }
                }
                else
                { 
                    /* Syscall exit */
                    insyscall = 0;
                }
            }

            ptrace(PTRACE_SYSCALL, child, NULL, NULL);
        }
    }

    close(LD_fd);
    return 0;
}

probonopd added a commit to probonopd/libhookexecv that referenced this issue Dec 18, 2018
@probonopd
Copy link
Author

probonopd commented Dec 18, 2018

WINEPREFIX需要可写并且由用户拥有。这就是我们目前使用unionfs-fuse的原因。

我想知道我们是否可以使用LD_PRELOAD,或者ptraceWINEPREFIX只读保存在AppImage中,并将写入重定向到$HOME中的其他位置

AppImage/AppImageKit#267


WINEPREFIX needs to be writeable and owned by the user. This is why we are currently using unionfs-fuse.

I am wondering whether we could use LD_PRELOAD and maybe ptrace to keep WINEPREFIX read-only inside the AppImage, and re-direct writes somewhere else in $HOME

AppImage/AppImageKit#267

@Hackerl
Copy link
Owner

Hackerl commented Dec 18, 2018

我觉得unionfs-fuse很优雅地解决了问题,使用"hook"反而会很麻烦.如果只是针对少数的系统调用,"hook"方式会显得很精致高效.但如果想实现应用程序的读写重定向,将需要"hook"所有读写相关系统调用.工作量十分巨大,而且应用运行会很低效.


I think unionfs-fuse solves the problem elegantly. It is very troublesome to use "hook". If it is only for a few system calls, the "hook" method will be very delicate and efficient. But if you want to implement read and write redirection of the application. , will need to "hook" all read and write related system calls. The workload is very large, and the application will be very inefficient.

@probonopd
Copy link
Author

我在 https://github.com/probonopd/libhookexecv/releases 上进行构建,但是似乎仍然有一些问题可能与unionfs-fuse有关。你能看一下吗?

这就是我用来做的:https://github.com/probonopd/libhookexecv/blob/8d1febe710c3581e3498f940b9e6142e6a7e4d1b/winedeploy.sh


I am making builds at https://github.com/probonopd/libhookexecv/releases but I seem to still have some issues possibly related to unionfs-fuse. Would you have a look?

This is what I have used to make it: https://github.com/probonopd/libhookexecv/blob/8d1febe710c3581e3498f940b9e6142e6a7e4d1b/winedeploy.sh

@Hackerl
Copy link
Owner

Hackerl commented Dec 18, 2018

我测试了你打包的appimage,无法运行的原因是wine虚拟环境(wineprefix文件夹)有问题.我替换成另一个环境,完全可以运行起来.
另外,我觉得你的AppRun脚本有些问题.


I tested your packaged appimage, the reason it can't run is that there is a problem with the wine virtual environment (wineprefix folder). I replaced it with another environment and it works perfectly.
Also, I think there is something wrong with your AppRun script.

#!/bin/bash
HERE="$(dirname "$(readlink -f "${0}")")"

export LD_LIBRARY_PATH="$HERE/usr/lib":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/lib":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/lib/i386-linux-gnu":$LD_LIBRARY_PATH

# Sound Library
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu/pulseaudio":$LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$HERE/usr/lib/i386-linux-gnu/alsa-lib":$LD_LIBRARY_PATH

# LD
export WINELDLIBRARY="$HERE/lib/ld-linux.so.2"

export WINEDLLOVERRIDES="mscoree,mshtml=" # Do not ask to install Mono or Gecko
export WINEDEBUG=-all # Do not print Wine debug messages

# Load Explorer if no arguments given
EXPLORER=""
if [ -z "$@" ] ; then
  EXPLORER="explorer.exe"
fi

# Load bundled WINEPREFIX if existing

MNT_WINEPREFIX="$HOME/.QQ.unionfs" # Use the name of the app
atexit()
{
  killall "$WINELDLIBRARY" && sleep 0.1 && rm -r "$MNT_WINEPREFIX"
}

if [ -d "$HERE/wineprefix" ] ; then
  RO_WINEPREFIX="$HERE/wineprefix" # WINEPREFIX in the AppDir
  TMP_WINEPREFIX_OVERLAY=/tmp/QQ # Use the name of the app
  mkdir -p "$MNT_WINEPREFIX" "$TMP_WINEPREFIX_OVERLAY"
  "$WINELDLIBRARY" "$HERE/usr/bin/unionfs-fuse" -o use_ino,uid=$UID -ocow "$TMP_WINEPREFIX_OVERLAY"=RW:"$RO_WINEPREFIX"=RO "$MNT_WINEPREFIX" || exit 1
  export WINEPREFIX="$MNT_WINEPREFIX"
  echo "Using $HERE/wineprefix mounted to $WINEPREFIX"
  trap atexit EXIT
fi

# LANG=C is a workaround for: "wine: loadlocale.c:129: _nl_intern_locale_data: Assertion (...) failed"; FIXME
LANG=C LD_PRELOAD="$HERE/lib/libhookexecv.so" "$WINELDLIBRARY" "$HERE/bin/wine" "$@" "$EXPLORER" | cat

atexit函数用来退出时清理,但是"killall $WINELDLIBRARY"会杀死wine的进程,使得一些虚拟的win32进程不会退出。我测试几次后发现大量的残留进程,类似于"C:\Windows....",所以你需要针对性的杀死unionfs-fuse进程。

另外,你将"$HOME/.QQ.unionfs"作为顶层虚拟文件节点,读写节点为"/tmp/QQ"。所以wine写入的新数据都存放在"/tmp/QQ",而"$HOME/.QQ.unionfs"文件夹只是虚拟的。在unionfs-fuse结束之后,"$HOME/.QQ.unionfs"将变为空文件夹。

而系统重启之后,"/tmp/QQ"将被清空,所以用户使用应用时写入的数据将会丢失。

可以参考一些下面的脚本:


The atexit function is used to clean up when exiting, but "killall $WINELDLIBRARY" will kill the wine process, making some virtual win32 processes not exit. I tested a few times and found a lot of residual processes, similar to "C:\Windows....", so you need to kill the unionfs-fuse process in a targeted manner.

In addition, you will use "$HOME/.QQ.unionfs" as the top-level virtual file node and the read-write node as "/tmp/QQ". So the new data written by wine is stored in "/tmp/QQ", and the "$HOME/.QQ.unionfs" folder is only virtual.

After the unionfs-fuse ends, "$HOME/.QQ.unionfs" will become an empty folder.
After the system is restarted, "/tmp/QQ" will be cleared, so the data written by the user when using the application will be lost.

Can refer to some of the following scripts:

#!/bin/bash
#......
#......
RO_DATADIR="$HERE/wineprefix"
RW_DATADIR="$HOME/.AppName"
TOP_NODE="/tmp/.AppName.unionfs"

mkdir -p $RW_DATADIR $TOP_NODE

"$WINELDLIBRARY" $HERE/usr/bin/unionfs-fuse -o use_ino,nonempty,uid=$UID -ocow "$RW_DATADIR"=RW:"$RO_DATADIR"=RO "$TOP_NODE" || exit 1

function finish {
  echo "Cleaning up"
  #need to kill unionfs-fuse
}
trap finish EXIT

export WINEPREFIX="$TOP_NODE"

# LANG=C is a workaround for: "wine: loadlocale.c:129: _nl_intern_locale_data: Assertion (...) failed"; FIXME
LANG=C LD_PRELOAD="$HERE/lib/libhookexecv.so" "$WINELDLIBRARY" "$HERE/bin/wine" "$@" "$EXPLORER" | cat

应用写入的新数据存放在"$HOME/.AppName",所以不会发生数据丢失。
另外,需要考虑怎么杀死"unionfs-fuse"进程。
最后我建议,将wine单独打包成一个单独的appimage,建立"/usr/bin/wine"软链接,便于更好的分离Windows应用与wine。
例如当wine有了很大的升级时,仅仅只需要替换wine.appimage即可,应用就能享受更完善的windows操作系统模拟。而且将wine独立出来,节省硬盘空间。
wine独立出来的话,你现在遇到的这个问题也会更容易调试。


The new data written by the application is stored in "$HOME/.AppName", so no data loss will occur.
In addition, you need to consider how to kill the "unionfs-fuse" process.

Finally, I suggest that you package wine as a separate appimage and create a "/usr/bin/wine" soft link to better separate Windows applications and wine.

For example, when wine has a big upgrade, just replace wine.appimage, and the application can enjoy a more complete Windows operating system simulation. And the wine is independent, saving hard disk space.

If the wine is independent, the problem you are encountering now will be easier to debug.

@probonopd
Copy link
Author

probonopd commented Dec 18, 2018

  1. 创建wineprefix。我正在运行“葡萄酒酒靴”来制作葡萄酒配酒。有没有其他方法来创建有效的wineprefix?
  2. 辅助功能,杀死unionfs-fuse。只有在所有Wine(子进程)进程退出后,unionfs-fuse才能被杀死。如何正确地做到这一点?
atexit()
{
  while pgrep -f "$HERE/bin/wineserver" ; do sleep 1 ; done
  pkill -f "$HERE/usr/bin/unionfs-fuse"
}
  1. 将WINE与应用程序分离。我想将应用程序与运行这个应用程序所需的特定子集和库捆绑在一起。类似于linuxdeployqt对Qt所做的。这可以通过观察哪些文件实际被打开,以及仅通过绑定这些文件来实现。它可以大大(!)减小AppImage的大小

  1. Creating the wineprefix. I am creating the wineprefix by running wine wineboot. Is there another way to create a valid wineprefix?
  2. atexit function, kill unionfs-fuse. unionfs-fuse can only be killed once all Wine (sub)processes have exited. How to do this properly?
atexit()
{
  while pgrep -f "$HERE/bin/wineserver" ; do sleep 1 ; done
  pkill -f "$HERE/usr/bin/unionfs-fuse"
}
  1. Separating WINE from the application. I would like to bundle an application with the specific subset and libraries that are needed to run this one application. Similar to what linuxdeployqt is doing for Qt. This can be done by watching which files are actually opened, and by only bundling those. It can greatly(!) reduce the AppImage size

@probonopd
Copy link
Author

https://github.com/probonopd/libhookexecv/releases

./NotepadPlusPlus-3.5-x86_64.AppImage --appimage-extract
squashfs-root/AppRun # does NOT work! <---------
mv squashfs-root/wineprefix/ /home/me/.wine
squashfs-root/AppRun # works!

@Hackerl
Copy link
Owner

Hackerl commented Dec 19, 2018

I just tested it and it seems that there is no problem.
I found that you replaced the 64-bit unionfs-fuse. Is this the reason for success?

image

@probonopd
Copy link
Author

Yes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants