【问题标题】:LD_LIBRARY_PATH ignored on Android sometimesLD_LIBRARY_PATH 有时在 Android 上被忽略
【发布时间】:2012-10-04 02:33:59
【问题描述】:

我有一个 android 应用程序,它生成许多与我随包分发的库动态链接的本机可执行文件。 要启动这些二进制文件,我使用 LD_LIBRARY_PATH 环境变量让它们知道从中加载库的位置,但在某些设备上这根本不起作用,LD_LIBRARY_PATH 已正确更新,但二进制文件无论如何都找不到库. 这不是我可以重现的东西,因为在我的两台设备上(Galaxy Nexus 和 Nexus 7 with stock roms)它运行良好。

我尝试了很多方法,例如我生成:

LD_LIBRARY_PATH=/my/package/custom/libs:$LD_LIBRARY_PATH && cd /binary/directory && ./binary

还有:

    String[] envp = { "LD_LIBRARY_PATH=" + libPath + ":$LD_LIBRARY_PATH" };

    Process process = Runtime.getRuntime().exec( "su", envp );

    writer = new DataOutputStream( process.getOutputStream() );
    reader = new BufferedReader( new InputStreamReader( process.getInputStream() ) );

    writer.writeBytes( "export LD_LIBRARY_PATH=" + libPath + ":$LD_LIBRARY_PATH\n" );
    writer.flush();

但是在那些设备上似乎没有任何工作......所以我开始认为这是一个与内核相关的问题,一些内核(比如我的)使用 LD_LIBRARY_PATH,其他内核不使用(简单地忽略它,或者他们只使用在应用程序启动时设置的 LD_LIBRARY_PATH,因此无法在运行时更改它)。

我也尝试使用 System.load 但它不起作用,可能是因为这些库不是 JNI ...在开始考虑使用静态链接的二进制文件之前我可以尝试什么吗?

【问题讨论】:

  • >>但是在某些设备上这根本不起作用,你能提供我不起作用的设备型号吗?不确定是否有帮助,但请尝试查看(如果我有这样的设备)。
  • 它是处理 LD_LIBRARY_PATH 的链接器 // github.com/android/platform_bionic/tree/master/linker

标签: java android android-ndk shared-libraries native


【解决方案1】:

这是我写的一个简单的包装器:

#include <android/log.h>
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>

typedef int (*main_t)(int argc, char** argv);

static int help(const char* argv0)
{
    printf("%s: simple wrapper to work around LD_LIBRARY_PATH\n\n", argv0);
    printf("Args: executable, list all the libraries you need to load in dependency order, executable again, optional parameters\n");
    printf("example: %s /data/local/ttte /data/data/app/com.testwrapper/lib/ttt.so /data/local/ttte 12345\n", argv0);
    printf("Note: the executable should be built with CFLAGS=\"-fPIC -pie\", LDFLAGS=\"-rdynamic\"\n");

    return -1;
}

int main(int argc, char** argv)
{
    int rc, nlibs;
    void *dl_handle;

    if (argc < 2)
    {
        return help(argv[0]);
    }

    __android_log_print(ANDROID_LOG_DEBUG, "wrapper", "running '%s'", argv[1]);

    for (nlibs = 2; ; nlibs++)
    {
        if (nlibs >= argc)
        {
            return help(argv[0]);
        }

        __android_log_print(ANDROID_LOG_DEBUG, "wrapper", "loading '%s'", argv[nlibs]);
        dl_handle = dlopen(argv[nlibs], 0); // do not keep the handle, except for the last
        __android_log_print(ANDROID_LOG_DEBUG, "wrapper", "loaded '%s' -> %p", argv[nlibs], dl_handle);
        if (strcmp(argv[1], argv[nlibs]) == 0)
        {
            break;
        }
    }

    main_t pmain = (main_t)dlsym(dl_handle, "main");
    __android_log_print(ANDROID_LOG_DEBUG, "wrapper", "found '%s' -> %p", "main", pmain);
    rc = pmain(argc - nlibs, argv + nlibs);

//   we are exiting the process anyway, don't need to clean the handles actually

//   __android_log_print(3, "wrapper", "closing '%s'", argv[1]);
//   dlclose(dl_handle);

    return 0;
}

为了保持可读性,我放弃了大部分错误处理、不必要的清理和特殊情况的处理。

Android.mk 这个可执行文件:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := wrapper
LOCAL_SRC_FILES := wrapper/main.c
LOCAL_LDLIBS    := -llog

include $(BUILD_EXECUTABLE)

请注意,您必须注意部署:将此wrapper 打包到 APK 中,提取到某个本地路径(永远不要到 USB 存储或 /sdcard!),将其标记为可执行文件 (chmod 777)。

这些是您在构建通过wrapper 运行的可执行文件时必须提供的附加参数。如果你使用ndk-build 构建它们,它看起来如下:

LOCAL_C_FLAGS   += -fPIC -pie
LOCAL_LDFLAGS   += -rdynamic 

请注意,这些可执行文件不再需要chmod。另一个技巧:您可以将 secondary 可执行文件构建到共享库中,并且相同的包装器将继续工作!这省去了部署这些二进制文件的麻烦。 NDK 和 Android 构建将通过 APK 的 libs/armeabi 自动将它们安全地传送到您应用的 lib 目录。

更新

似乎有一个更简单的解决方案,使用带有修改环境的ProcessBuilderhttps://stackoverflow.com/a/8962189/192373

【讨论】:

    【解决方案2】:

    这不是内核相关的问题,而是LD相关的问题:库的加载完全是在用户模式,而不是内核模式,所以内核与它无关。内核只负责将控制权转移给 ld,ld 循环遍历 ELF 二进制文件的 DYNAMIC 部分,加载任何库,并确定如何处理 LD_PRELOAD 和 LD_PRELOAD 等环境变量。

    但是,这些变量可能会带来固有的安全风险:LD_LIBRARY_PATH 允许您将自己的路径放在系统默认值之前(/lib 或 - 在 Android 中为 /system/lib)。 LD_PRELOAD 更糟糕,因为它强制将您的库加载(推入)到进程中,无论进程是否请求它 - 这可能导致将恶意代码注入敏感进程。出于这个原因,这在根 Setuid 进程中是不允许的。但是,如果您在 root shell 中(即在有 root 权限的手机上),大多数设备都会允许它。供应商可能会修改 LD,从而导致您的问题。

    你可以做些什么来解决:

    • 最简单:将 /system 重新挂载为读/写(mount -o remount /system),然后将库复制到 /system/lib。需要root手机

    • 最智能:将您的库静态链接到您的二进制文件,同时保持其他(系统)库动态

    希望这会有所帮助,

    TG

    【讨论】:

      【解决方案3】:

      我认为 LD_LIBRARY_PATH 不会被忽略。您更新系统环境的方式是可以的。 LD_LIBRARY_PATH 将在 root 用户下更新。

      解决方案将无法在某些机器上工作可能有几个原因:

      1. Android 应用程序始终在其自己的用户 ID 下运行。它不是在 root 权限下运行的。如果只修改root用户的系统变量。应用程序可能仍然无法在其自己的环境中找到正确的库路径。

      2. 即使您在应用程序初始化时正确更新了 LD_LIBRARY_PATH,您仍然需要确保应用程序的相应用户 ID 确实有权读取这些库。

      3. 出于某些安全原因,某些供应商会修改 Android 内核。他们可能会禁用从外部存储或类似的东西加载第 3 方库。

      【讨论】:

        【解决方案4】:

        没错,您不能在 Android 上依赖 LD_LIBRARY_PATH。您可以将 System.load() 与任何库一起使用,但这在生成子进程时并没有真正的帮助。您可以在代码中使用 dlopen,但通常这意味着必须重写很多行代码。

        一个有趣的技巧是将您的可执行文件转换为共享库,然后从包装可执行文件中加载它们,这将按依赖顺序 dlopen 库。

        强烈建议将尽可能多的 spawn 转换为进程内调用。

        【讨论】:

        • 那么为什么这在某些设备上有效(80%)而在其他设备上无效?例如,在带有 Android 版本 2.3.3 的华为 U8510 Ideos X3 上无法正常工作。
        • 我没有说“LD_LIBRARY_PATH 在 Android 上不起作用”。我说“你不能依赖”。我估计 80% 相当高。但也许您主要关注的是高端当代设备......
        • 好的,因为我无法转换我的可执行文件,因为源代码非常大(我们正在谈论 ettercap、nmap 等),我无法将 spawn 转换为进程调用既不避免在流程执行期间应用程序冻结......您对如何解决这个问题有任何想法吗? :)
        • 好的,经过一些测试:如果您可以使用-fPIC -pie 标志(如described elsewhere on stackoverflow)重建您的可执行文件(依赖于您安装的共享库的可执行文件),那么您可以准备您的自己的包装器可执行文件(一种替代或增强系统动态链接器),它将启动,动态加载必要的共享库,最后用 dlopen 加载所需的可执行文件,并调用它的 main ()dlsym()
        猜你喜欢
        • 2018-04-16
        • 2023-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-16
        相关资源
        最近更新 更多