【问题标题】:Android app crashes after switching from .lib to .so从 .lib 切换到 .so 后,Android 应用程序崩溃
【发布时间】:2015-06-17 10:50:33
【问题描述】:

我在我的原生 android 应用程序中使用了一些静态预构建的静态库,一切正常。现在我想将我的静态库之一切换为 .so。通过在其 android.mk 中将 BUILD_STATIC_LIBRARY 替换为 BUILD_SHARED_LIBRARY 并添加所需的依赖项,我成功地构建了 .so 库。

我还能够通过在其 android.mk 中将相应的 PREBUILT_STATIC_LIBRARY 替换为 PREBUILT_SHARED_LIBRARY 来构建我的应用程序。生成的应用程序现在无法启动。我什至无法指出调试器附加到应用程序的位置。

除此之外,我不明白的是构建系统如何知道该函数应该从库中导入。我的so库应该导出一个函数,但我没有将它声明为dllexport/import之类的。我的应用程序中仍然没有未解析的符号(当我从列表中删除我的预构建库时,未解析的符号会按预期显示)。

另一个问题是我看到生成了两个 .so 文件。 obj/local/$(TARGET_ARCH_ABI) 文件夹中的一个大文件和 libs/$(TARGET_ARCH_ABI) 中的另一个小文件。在声明我的预建库时,我引用了 libs 文件夹中的第二个库。

我确实尝试在stackoverflow中搜索答案,并找到了很多相关的帖子:

但我看不到这些帖子与我的问题有什么关系,因为我可以成功构建甚至链接我的应用程序。

【问题讨论】:

  • 当您的应用无法启动时。你在 logcat 中看到了什么吗?
  • 确实,logcat 应该是您的首选。连接断点调试器(在 java 或本机级别)只是在日志未能使问题明显时才需要打扰 - 特别是当(正如您在此处发现的那样)问题似乎在您有机会之前发生时。虽然如果你真的想要,你可以在你的程序之前尝试加载本地库。
  • 您正确地指向预建库的较小 (stripped) 副本。另一个可能对调试非常有用。
  • @Alex Cohn 我仍然不明白链接器如何理解符号应该动态解析。函数声明对此没有任何线索。在 Windows 中,您可以先显式地LoadLibrary,然后再显式地GetProcAddress,或者将函数声明为__declspec(dllimport),它告诉链接器要做什么。这在 Linux/Android 上如何工作?我是否需要显式加载库(在 Java 或 C++ 中)?链接器如何识别导入的符号?
  • 链接器会查看您可以使用nm -D 列出的符号,如下面的答案所述。

标签: android c++ android-ndk


【解决方案1】:

您需要在 java 代码中以相反的依赖顺序加载库。你以前可能有这样的事情:

System.loadLibrary("mylib");

现在,如果您的预构建库(以前是静态库,现在是共享库)命名为 dependencylib,则需要更改将库加载到此的代码:

System.loadLibrary("dependencylib");
System.loadLibrary("mylib");

至于您的问题,链接器如何解决;链接libmylib.so 时,它会在您指定的所有其他库中查找所有未定义的符号(即在libdependencylib.solibc.so 和其他系统库中)。只要在某处找到所有未定义的符号,链接器就可以了。然后在运行时,当libmylib.so 被加载时,它再次执行相同的例程;在当前进程中加载​​的符号列表中查找所有未定义的符号。在 linux 上,您通常不需要像在 windows 上那样手动将符号标记为 dllexport - 默认情况下会导出所有非静态符号。

【讨论】:

  • 谢谢,解决了我的问题!我还有一个问题:我的应用程序是一个本地应用程序,Java 代码很少。我从 NativeTeapot 开始,然后只使用 c++ 代码。在我的任何 java 源代码中都没有像 System.loadLibrary("mylib"); 这样的东西,所以我随机将 System.loadLibrary("mydependencylib"); 放在我的应用程序的 onCreate() 函数的开头。有没有合适的地方这样做?
  • 如果您的应用基于 NativeActivity,它会以不同方式加载库。一般来说,加载 JNI 库最安全的地方是从 静态构造函数,请参阅我的更新答案。
  • 这可能是一个足够好的解决方案 - NativeActivity 基类在其 onCreate 方法中与 System.loadLibrary 等效,因此只要在调用 super.onCreate 之前加载依赖项,它应该没问题,尽管像 Alex 建议的那样使用静态构造函数更安全。
  • @mstorsjo 请务必记住,NativeActivity 不会调用 System.loadLibrary(),请参阅 stackoverflow.com/questions/29453755/…
  • “通常”所有这些都会自动处理; libmylib.so 确实存储了它需要加载 libmydependencylib.so 才能运行,因此所有信息都已经存在以自动执行此操作,如果我没记错的话,它从 Android 5.0 开始就这样做了。早期版本的问题只是动态链接器在加载传递依赖项时没有在正确的目录中查找特定于应用程序的库(System.loadLibrary 检查的库),据我所知,现在已修复。
【解决方案2】:

更改STATIC -> SHARED后应用无法启动可能有两种原因。

  1. 未安装预构建库。连接设备后,运行adb ls -l /data/your.package.name/lib/。你看到那里的图书馆了吗?

  2. 预建库未加载。在您的主要 Java 类中,尝试

    static { System.loadLibrary("prebuiltname"); System.loadLibrary("yourlib"); }

    这是一个静态构造函数,是加载JNI库最安全的地方。

【讨论】:

  • 谢谢! System.loadLibrary("prebuiltname"); 确实解决了我的问题!
【解决方案3】:

如果您使用的是 linux,您将看到使用 nm -D 导出的符号。例如 nm -D libzip.so:

...
0000000000009dc0 T zip_unchange
0000000000009dd0 T zip_unchange_all
0000000000009e30 T zip_unchange_archive
0000000000009e60 T _zip_unchange_data

如果您想控制函数的可见性,请使用 __attribute__ ((visibility ("default"))) 和命令行 -fvisibility=hidden。更多信息here.

【讨论】:

  • Android 是一个相当奇怪的 Linux,但这基本上是成立的,并且 NDK 发行版中提供的各种 Android ABI 都有 nm 的交叉版本。
  • @fukanchik: nm -D -C 转储库中的所有符号(包括我要导入的函数),而 nm -g 什么也不显示。按照建议,我将-fvisibility=hidden 添加到LOCAL_CFLAGS,并使用__attribute__((visibility("default"))) 声明了我的导出函数。不过,还是出了点问题……
  • 你在 logcat 中看到了什么吗?
【解决方案4】:

现在我想将我的一个静态库切换为 .so。通过在其 android.mk 中将 BUILD_STATIC_LIBRARY 替换为 BUILD_SHARED_LIBRARY 并添加所需的依赖项,我成功地构建了 .so 库。

我认为你做不到 如果它是一个 C++ 库。来自<doc>/CPLUSPLUS-SUPPORT.html

请记住,给定 C++ 的静态库变体 运行时应仅链接到单个二进制文件以获得最佳效果 条件。

这意味着如果您的项目包含一个 单个共享库,您可以链接到例如 stlport_static 和 一切都会正常工作。

另一方面,如果你有两个 项目中的共享库(例如 libfoo.so 和 libbar.so) 两者都链接到同一个静态运行时,它们中的每一个都将 在其最终二进制映像中包含运行时代码的副本。这 是有问题的,因为使用/提供了某些全局变量 运行时在内部被复制。

这很可能导致代码无法正常工作,例如:

* 在一个库中分配的内存,在另一个库中释放会泄漏 甚至破坏堆。
* libfoo.so 中引发的异常不能在 libbar.so 中捕获(并且可能 只需让程序崩溃)。
* cout 的缓冲不能正常工作

如果您想链接可执行文件和共享文件,也会出现此问题 库到同一个静态库。

换句话说,如果您的项目需要多个共享库模块, 然后使用 C++ 运行时的共享库变体。

从上面看,这意味着一切都需要链接到同一个 C++ 标准运行时共享对象。

【讨论】:

  • 感谢您的回答,但这似乎不是我的问题。我可能不够清楚,但我说的不是运行时库,而是我自己的库之一。从 libfoo.so 导出的对象在 libbar.so 中使用。他们通过避免您提到的所有问题的界面进行通信。我真正的问题是我不能让 libfoo.so 和 libbar.so 这两个库一起工作。应用在进入 android_main() 之前就崩溃了。
  • 当且仅当您以前的静态(但现在共享)库仍然针对 C++ 运行时库的静态版本构建时,才会存在此答案中提出的问题。如果它们与共享版本正确链接,这将不是问题。
  • 我的两个共享库都链接到静态版本的 C++ 运行时库,现在一切正常。只要您不删除在其他库中分配的内存或跨库抛出异常,我看不出它不应该工作的任何原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-09
  • 1970-01-01
  • 1970-01-01
  • 2019-02-27
  • 2016-03-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多