有时在Android上忽略LD_LIBRARY_PATH

时间:2012-10-13 12:47:51

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

我有一个Android应用程序,它产生许多本机可执行文件与我随软件包分发的库有动态链接。 要启动这些二进制文件,我使用LD_LIBRARY_PATH环境变量使它们知道加载库的位置,但在某些设备上根本不起作用,LD_LIBRARY_PATH正确更新但二进制文件无法找到库。 这不是我可以重现的东西,因为在我的两个设备上(Galaxy Nexus& Nexus 7带有股票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 ...在开始考虑使用静态链接的二进制文件之前我可以尝试一下吗?

4 个答案:

答案 0 :(得分:4)

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

#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!),将其标记为可执行({{ 1}})。

这些是在构建通过chmod 777运行的可执行文件时必须提供的其他参数。如果您使用wrapper构建它们,它看起来如下:

ndk-build

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

<强>更新

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

答案 1 :(得分:2)

这不是与内核相关的问题,而是与LD相关的问题:库的加载完全处于用户模式,而不是内核模式,因此内核与它无关。内核只负责将控制转移到ld,它遍历ELF二进制文件的DYNAMIC部分,加载任何库,并弄清楚如何处理环境变量,如LD_PRELOAD和LD_PRELOAD。

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

你可以做些什么来解决:

  • 最简单:重新安装/系统作为读/写(mount -o remount / system),并将库复制到/ system / lib。将需要root电话

  • Smartest:将您的库静态链接到二进制文件,同时维护其他(系统)库动态

希望这有帮助,

TG

答案 2 :(得分:1)

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

一个有趣的技巧是将您的可执行文件转换为共享库,并从包装器可执行文件加载它们,这将以依赖顺序对库进行删除。

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

答案 3 :(得分:1)

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

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

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

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

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