【问题标题】:Loading shared libs that depend on other shared libs加载依赖于其他共享库的共享库
【发布时间】:2012-06-19 00:13:08
【问题描述】:

问题:

我正在 Eclipse 中构建 Android 应用程序,它使用共享库 libgstreamer-0.10.so(为 android-8 平台编译的 GStreamer-android NDK Bundle 库)。我在项目根文件夹中创建了新文件夹libs/armeabi 并将其放在那里。此外,我已将它附带的所有其他库(其中 158 个)放在同一个文件夹中。如果我把它放在我的主要活动代码中:

static{
    System.loadLibrary("gstreamer-0.10");
}

并在 Android-8 模拟器上构建/安装/运行我的应用程序,它会引发此错误:

06-15 21:54:00.835: E/AndroidRuntime(402): Caused by: java.lang.UnsatisfiedLinkError: Cannot load library: link_image[1962]:    33 could not load needed library 'libglib-2.0.so' for 'libgstreamer-0.10.so' (load_library[1104]: Library 'libglib-2.0.so' not found)

现在,libglib-2.0.solibgstreamer-0.10.so 在同一个文件夹中,为什么没有加载?我知道链接器试图从/system/lib 加载它,而libglib-2.0.so 只是不存在,但为什么它不从libgstreamer-0.10.so 所在的位置加载它?

所以我用这个命令去发现libgstreamer-0.10.so依赖于哪些库:

arm-linux-androideabi-readelf -d libgstreamer-0.10.so

结果:

Dynamic section at offset 0x118b64 contains 29 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libglib-2.0.so]
 0x00000001 (NEEDED)                     Shared library: [libgobject-2.0.so]
 0x00000001 (NEEDED)                     Shared library: [libgthread-2.0.so]
 0x00000001 (NEEDED)                     Shared library: [libgmodule-2.0.so]
 0x00000001 (NEEDED)                     Shared library: [libdl.so]
 0x00000001 (NEEDED)                     Shared library: [libm.so]
 0x00000001 (NEEDED)                     Shared library: [libstdc++.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so]
 0x0000000e (SONAME)                     Library soname: [libgstreamer-0.10.so]
 0x00000010 (SYMBOLIC)                   0x0

前四个libglib-2.0.so, libgobject-2.0.so, libgthread-2.0.so, libgmodule-2.0.so 都位于设备上的同一文件夹中libgstreamer-0.10.so 位于(/data/data/com.marko.gstreamer_test/lib)中。

逻辑解决方案:

所以,我尝试在加载 libgstreamer-0.10.so 之前加载这四个库,并且成功了:

static{
    System.loadLibrary("glib-2.0");
    System.loadLibrary("gthread-2.0");
    System.loadLibrary("gobject-2.0");
    System.loadLibrary("gmodule-2.0");
    System.loadLibrary("gstreamer-0.10");
}

我的问题是:

  1. 我能否以某种方式告诉链接器也从应用程序位置加载库?就像添加路径到一些环境变量或其他东西......类似于 Linux 上的 PATH。

  2. 我的解决方案是否有一些不好的副作用?我的意思是,链接器在加载 libgstreamer-0.10.so 之前也会这样做。但这会带来什么问题吗?

  3. 我可以将我的库安装到无根设备上的 /system/lib 文件夹吗?

【问题讨论】:

  • 那个 hack 实际上是 Android 开发者自己推荐的解决方案:groups.google.com/forum/?fromgroups#!topic/android-ndk/… 有了这样的技术决策,难怪 Android 有这么多 bug。
  • 您如何确定要显式加载哪些库?
  • @dpk arm-linux-androideabi-readelf -d libgstreamer-0.10.so 给出了依赖项列表。其中一些已经加载(libc 等),但有些需要显式加载。

标签: android linker android-ndk gstreamer


【解决方案1】:

根据https://groups.google.com/forum/?fromgroups#!msg/android-ndk/J3lzK4X--bM/4YaijymZy_AJ

是的,这是记录在案的行为:您必须明确地以反向依赖顺序加载库。 [...] 这是系统的限制。

简而言之,动态链接器对您的应用程序一无所知(例如,它的库所在的位置),它只知道在创建进程时设置的 LD_LIBRARY_PATH 值。当你启动一个 Android 应用程序时,你实际上是 fork Zygote 进程,你没有创建一个新的,所以库搜索路径是初始路径并且不包括你的应用程序的 /data/data//lib/ 目录,其中您的本机库已上线。这意味着 dlopen("libfoo.so") 将不起作用,因为只会搜索 /system/lib/libfoo.so。

当你从 Java 调用 System.loadLibrary("foo") 时,VM 框架知道应用程序的目录,因此它可以将 "foo" 翻译成 "/data/data//lib/libfoo.so",然后调用 dlopen () 使用此完整路径,这将起作用。

如果libfoo.so引用了“libbar.so”,那么动态链接器将找不到后者。

此外,即使您从本机代码更新 LD_LIBRARY_PATH,动态链接器也不会看到新值。由于各种低级原因,动态链接器包含它自己的程序环境副本,就像创建进程时(不是分叉)一样。而且根本没有办法从本机代码更新它。这是设计使然,改变这一点会产生严重的安全限制。作为记录,这也是 Linux 动态链接器的工作方式,这会强制任何需要自定义库搜索路径的程序使用包装脚本来启动其可执行文件(例如 Firefox、Chrome 和许多其他程序)。

我已经给作者发了邮件询问这是在哪里记录的。

Tor Lillqvist 继续提供解决方法:https://groups.google.com/d/msg/android-ndk/J3lzK4X--bM/n2zUancIFUEJ

更详细地说,lo_dlopen() 函数的作用是:

  • 搜索有问题的共享对象所在的位置。它搜索一组由 Java 代码传递给它的目录。 Java 代码查看 LD_LIBRARY_PATH 并将应用的 lib 目录添加到其中。
  • 打开找到的共享对象文件并读取其中的 ELF 结构。不是全部,但足以找出它需要哪些共享对象(由 arm-linux-androideabi-readelf -d 显示的 DT_NEEDED 对象)。它在所需的共享对象上递归调用自身。
  • 仅在此之后,即确保已加载所有需要的其他共享对象之后,它才会在找到的共享对象的完整路径名上调用真正的 dlopen()。

你可以在http://cgit.freedesktop.org/libreoffice/core/tree/sal/android/lo-bootstrap.c?id=5510127e89d6971a219ce3664e4631d6c6dda2b1找到他的代码

更新:根据http://code.google.com/p/android/issues/detail?id=34416,截至 2012 年 12 月,此代码已集成到 Android 中。耶!对于 API 级别 18 及更高级别的设备,会自动加载依赖项。如果您支持较旧的 API 级别,则仍需要列出依赖项。

【讨论】:

    【解决方案2】:
    1. 我不确定您可以为 Java 应用程序做些什么。对于本机命令行应用程序,您可以通过在声明应用程序之前设置 LD_LIBRARY_PATH 环境变量来做到这一点。

    2. 这是正确的解决方案。在 NDK 文档的某处提到您需要以这种方式加载所有依赖库。

    3. 不,你不能那样做。

    【讨论】:

      猜你喜欢
      • 2016-06-06
      • 2014-03-22
      • 1970-01-01
      • 2011-10-01
      • 2018-04-07
      • 1970-01-01
      • 2011-01-01
      • 2012-02-24
      • 1970-01-01
      相关资源
      最近更新 更多