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

插件加载问题:无法找到 "liblog3a-lib.so" #1366

Open
LuYeCong opened this issue Feb 7, 2025 · 8 comments
Open

插件加载问题:无法找到 "liblog3a-lib.so" #1366

LuYeCong opened this issue Feb 7, 2025 · 8 comments

Comments

@LuYeCong
Copy link

LuYeCong commented Feb 7, 2025

作者你好,

我在使用Shadow框架时遇到了一个问题。当我尝试以非动态方式加载插件apk时,我发现在/data/user/0/com.tencent.weworklocal/files/plugin.apk_lib路径下没有找到任何文件,直接报错是找不到"liblog3a-lib.so"库。

错误信息如下:

Caused by:java.lang.UnsatisfiedLinkError: com.tencent.shadow.core.loader.classloaders.PluginClassLoader[DexPathList[[zip file "/data/user/0/com.tencent.weworklocal/files/plugin.apk"],nativeLibraryDirectories=[/data/user/0/com.tencent.weworklocal/files/plugin.apk_lib, /system/lib64, /system_ext/lib64]]] couldn't find "liblog3a-lib.so"

我留意到在com.tencent.shadow.core.manager.BasePluginManager类中有一个extractSo方法,但我发现这个方法只在动态加载插件的情况下被调用。

我想请问,如果我想以非动态方式加载插件,是否还需要调用其他函数来解压so文件?如果是的话,能否提供一些指导或者示例代码?

期待你的回复,谢谢。

@Tencent Tencent deleted a comment from jarlen Feb 7, 2025
@shifujun
Copy link
Collaborator

shifujun commented Feb 7, 2025

你发现extractSo没调用是正确的原因。

没调用的原因是none-dynamic形式的插件框架没有对齐dynamic的功能开发完整。none-dynamic存在是为了解耦,将插件框架本身动态化的代码和插件框架的代码分离开。实际上我们业务应用是不存在none-dynamic形式的。由于插件在动态更新,调用的系统接口随时会新增,所以插件框架不动态起来是不行的。我们没能力开发一个完全对齐系统接口的完善的插件框架。所以插件框架是需要和插件一起开发,一起动态发布的。

但技术层面上,none-dynamic单独跑起来,对齐dynamic的功能是完全可行的。在设计上也是更合理更优雅的。欢迎贡献这些代码。

具体到so加载这个问题。插件之所以能加载so,完全是因为Android系统本身允许随时load一个给定路径的so文件。插件框架只是基于这一点,模仿系统安装apk时解压so的逻辑,再向classloader设置默认的so搜索路径。

现在那个manager的逻辑,没有none-dynamic的设计,默认安装插件的形式一定是有动态的loader等apk。理论上应该将它拆分成none-dynamic manager和dynamic manager。但还需要拆分它管理的数据库。想一想就很麻烦。所以为了一个用不上的需求写这个重构是很不划算的。所以none-dynamic的sample就是那个样子了。

@LuYeCong
Copy link
Author

LuYeCong commented Feb 7, 2025

非常感谢作者详细且深入的解答。对于none-dynamic和dynamic的区别以及Shadow框架的设计理念,我现在有了更深的理解。

如果可能的话,我会尝试贡献代码来完善none-dynamic的功能。

再次感谢你的耐心解答和指导。

@LuYeCong
Copy link
Author

LuYeCong commented Feb 7, 2025

作者你好,我在none-dynamic中增加了extractSo的能力后,又遇到了一个新问题。

当尝试在插件apk中使用jna加载库"djcrypto"时:

DJCryptoAPI djCryptoAPI = (DJCryptoAPI)Native.load("djcrypto", DJCryptoAPI.class);

其中插件apk使用的jna库,是通过白名单使用宿主的jna依赖。djcrypto库则位于插件apk中。

遇到了一个错误,导致应用闪退。错误信息如下:

Caused by:java.lang.UnsatisfiedLinkError: Unable to load library 'djcrypto':
dlopen failed: library "libdjcrypto.so" not found
dlopen failed: library "libdjcrypto.so" not found
dlopen failed: library "libdjcrypto.so" not found
Native library (android-aarch64/libdjcrypto.so) not found in resource path (.)
com.sun.jna.NativeLibrary.loadLibrary(NativeLibrary.java:301)
com.sun.jna.NativeLibrary.getInstance(NativeLibrary.java:461)
com.sun.jna.Library$Handler.<init>(Library.java:192)
com.sun.jna.Native.load(Native.java:596)
com.sun.jna.Native.load(Native.java:570)
com.donjin.djcryptoapi.djcryptojna.DJCryptoAPI.<clinit>(DJCryptoAPI.java:22)

我怀疑这可能是因为Shadow框架没有兼容com.sun.jna.Native#load(java.lang.String, java.lang.Class<T>)的加载方式。
请问,这是否是可能的原因?如果是的话,有没有可能的解决方案或者建议?

期待你的回复,谢谢。
@shifujun

@shifujun
Copy link
Collaborator

shifujun commented Feb 7, 2025

这个JNA只是一个第三方库而已,不需要针对它做什么兼容。
可以看到它最后也是调用system loadlibrary加载so的。所以它也依赖classloader的library path。
https://github.com/java-native-access/jna/blob/master/src%2Fcom%2Fsun%2Fjna%2FNativeLibrary.java#L240-L240

你可以debug一下插件classloader的创建参数。你的log上看它只在.目录中搜索so了。
https://github.com/Tencent/Shadow/blob/master/projects%2Fsdk%2Fcore%2Floader%2Fsrc%2Fmain%2Fkotlin%2Fcom%2Ftencent%2Fshadow%2Fcore%2Floader%2Fblocs%2FLoadApkBloc.kt#L59-L59

另外你怀疑JNA是否需要特殊兼容之前,应该先测试dynamic方式下使用JNA是否正常。还有你修改完的none dynamic方式能否正常加载一个简单普通的apk打包的so。

@LuYeCong
Copy link
Author

LuYeCong commented Feb 9, 2025

作者你好:

问题:none dynamic方式能否正常加载一个简单普通的apk打包的so。

这点确实还有问题,宿主app里,调用System.loadlibrary插件里的so,加载失败,看起来没有在apk_lib里搜索这个so

dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.tencent.weworklocal-WU1AFR8MZwFbnCGvt1IAWQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.tencent.weworklocal-WU1AFR8MZwFbnCGvt1IAWQ==/lib/arm64, /data/app/com.tencent.weworklocal-WU1AFR8MZwFbnCGvt1IAWQ==/base.apk!/lib/arm64-v8a, /system/lib64, /system/product/lib64]]] couldn't find "libdjcrypto.so"

我额外增加了类似extractso的逻辑:

try {
    String filter = "lib/" + getAbi() + "/";
    File soDir = new File(installedApk.libraryPath);
    CopySoBloc.copySo(
            new File(installedApk.apkFilePath),
            soDir,
            AppCacheFolderManager.getLibCopiedFile(soDir, partKey), filter);
} catch (InstallPluginException e) {
    ALog.d(TAG, "extractSo fail", e);
}
// todo:yeconglu 支持odex

ShadowPluginLoader pluginLoader = mPluginLoader;
Future<?> future = null;
try {
    future = pluginLoader.loadPlugin(plugin);
    future.get(10, TimeUnit.SECONDS);
} catch (Exception e) {
    ALog.d(TAG, "loadPlugin fail", e);
}

入参是有的installedApk.libraryPath是有值的:
Image

请问是还需要做什么,才能加载到插件里的so呢?
@shifujun

@shifujun
Copy link
Collaborator

宿主app里,调用System.loadlibrary插件里的so

你还是没明白。so的搜索是在classloader上定义的搜索路径。你构造个插件的classloader,填写了解压so的路径,是为了让插件里的类能搜索到这些so。宿主的classloader你又没改过,你为啥会想在宿主里加载插件的so呢?你有这种需求,直接load一个绝对路径不就行了?

@LuYeCong
Copy link
Author

LuYeCong commented Feb 10, 2025

作者你好,

你说的宿主加载so时,如果需要加载插件的so,可以load一个绝对路径,这点明白了。
这里的原因还是因为插件使用了宿主的jna库来加载so,导致变成了在宿主加载插件的so,通过load绝对路径的方式解决了这个问题。

但是目前我发现,插件apk加载插件里的so还是没有加载成功,表现为调用jni方法时,会报No implementation found for

2025-02-10 18:10:38.835 14063-14063 JSWebHandler            com.tencent.weworklocal              W   |handleJsMessage|java.lang.UnsatisfiedLinkError: No implementation found for long com.sangfor.sdk.entry.SFServerSelector.createNative(java.lang.String, java.lang.Object) (tried Java_com_sangfor_sdk_entry_SFServerSelector_createNative and Java_com_sangfor_sdk_entry_SFServerSelector_createNative__Ljava_lang_String_2Ljava_lang_Object_2)

我有几个问题想请教的:

  1. 请问插件中 System.loadLibrary 是如何跟 classloader 关联的?

你构造个插件的classloader,填写了解压so的路径,是为了让插件里的类能搜索到这些so。

shadow是如何替换插件中System.loadLibrary用到的搜索路径,使其用插件的classloader的so搜索路径呢?

  1. 非动态方式下,我是否还需要特殊处理System.loadLibrary,才能让插件加载到so呢?

@shifujun

@shifujun
Copy link
Collaborator

但是目前我发现,插件apk加载插件里的so还是没有加载成功,表现为调用jni方法时,会报No implementation found for

load找不到so就应该抛出异常了吧?

和so搜索路径相关的代码还有applicationinfo中的参数
https://github.com/Tencent/Shadow/blob/master/projects%2Fsdk%2Fcore%2Floader%2Fsrc%2Fmain%2Fkotlin%2Fcom%2Ftencent%2Fshadow%2Fcore%2Floader%2Fblocs%2FCreatePluginApplicationInfoBloc.kt#L21-L21

这里shadow没什么自己发明的手段。你完全可以参考一般的Android知识。实在搞不懂建议问问AI。

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