From fb459742adacd3d1b8426e628233beb76ddfebcc Mon Sep 17 00:00:00 2001 From: shiq Date: Thu, 5 Sep 2024 21:13:51 +0800 Subject: [PATCH] [AAB] Feature: Support AAB And extractNativeLibs=false MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 项目未配置 `useLegacyPackaging=true` 时, 使用AAB安装, 或最低版本为Android M以上的APK Native库不会从APK中解压, 参考: https://developer.android.com/guide/topics/manifest/application-element#extractNativeLibs https://github.com/google/bundletool/issues/39 此时xCrash通过 `ctx.getApplicationInfo().nativeLibraryDir` 获取的目录内容是空的 因此会出现加载xcrash_dumper失败 而通过 PathClassloader.findLibrary(libName) 获取的路径是虚拟路径, 不是真实存在的 因此直接执行也会失败 此时我们参考 Crashpad 的逻辑, 使用linker加载 参考: https://github.com/chromium/crashpad/blob/main/client/crashpad_client_linux.cc#L114 为了避免大的改动, 默认仅在加载虚拟路径时使用linker, 实际上在所有场景都可以直接通过linker加载 支持外部自定义配置 ``` .setNativeLibPath() .setLoadNativeWithLinker() ``` 如果外部没有配置,内部会自动查找 参考: AbiPathProvider.java 逻辑 --- build.gradle | 2 +- xcrash_lib/src/main/cpp/common/xcc_util.h | 8 ++ xcrash_lib/src/main/cpp/common/xcc_version.h | 2 +- xcrash_lib/src/main/cpp/xcrash/xc_common.c | 4 + xcrash_lib/src/main/cpp/xcrash/xc_common.h | 2 + xcrash_lib/src/main/cpp/xcrash/xc_crash.c | 6 +- xcrash_lib/src/main/cpp/xcrash/xc_jni.c | 3 + .../src/main/java/xcrash/AbiPathProvider.java | 134 ++++++++++++++++++ .../src/main/java/xcrash/NativeHandler.java | 20 ++- xcrash_lib/src/main/java/xcrash/Version.java | 2 +- xcrash_lib/src/main/java/xcrash/XCrash.java | 25 +++- .../xcrash/sample/MyCustomApplication.java | 8 ++ 12 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 xcrash_lib/src/main/java/xcrash/AbiPathProvider.java diff --git a/build.gradle b/build.gradle index d7bfef5..7c67951 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,7 @@ ext { POM_GROUP_ID = "com.iqiyi.xcrash" POM_ARTIFACT_ID = "xcrash-android-lib" - POM_VERSION_NAME = "3.1.0" + POM_VERSION_NAME = "3.1.1" POM_NAME = "xCrash Android Lib" POM_DESCRIPTION = "xCrash provides the Android app with the ability to capture java crash, native crash and ANR." diff --git a/xcrash_lib/src/main/cpp/common/xcc_util.h b/xcrash_lib/src/main/cpp/common/xcc_util.h index 792ba82..b6dd499 100644 --- a/xcrash_lib/src/main/cpp/common/xcc_util.h +++ b/xcrash_lib/src/main/cpp/common/xcc_util.h @@ -67,6 +67,14 @@ extern "C" { #define XCC_UTIL_SYSCALL_GETDENTS SYS_getdents64 #endif +#if defined(__LP64__) +#define LINKER_PATH "/system/bin/linker64" +#define LINKER_NAME "linker64" +#else +#define LINKER_PATH "/system/bin/linker" +#define LINKER_NAME "linker" +#endif + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" typedef struct diff --git a/xcrash_lib/src/main/cpp/common/xcc_version.h b/xcrash_lib/src/main/cpp/common/xcc_version.h index 7791bf5..701aea0 100644 --- a/xcrash_lib/src/main/cpp/common/xcc_version.h +++ b/xcrash_lib/src/main/cpp/common/xcc_version.h @@ -24,6 +24,6 @@ #ifndef XCC_VERSION_H #define XCC_VERSION_H 1 -#define XCC_VERSION_STR "xCrash 3.1.0" +#define XCC_VERSION_STR "xCrash 3.1.1" #endif diff --git a/xcrash_lib/src/main/cpp/xcrash/xc_common.c b/xcrash_lib/src/main/cpp/xcrash/xc_common.c index 682ebdf..123f890 100644 --- a/xcrash_lib/src/main/cpp/xcrash/xc_common.c +++ b/xcrash_lib/src/main/cpp/xcrash/xc_common.c @@ -64,6 +64,7 @@ char *xc_common_app_id = NULL; char *xc_common_app_version = NULL; char *xc_common_app_lib_dir = NULL; char *xc_common_log_dir = NULL; +int xc_use_linker = 0; //process info pid_t xc_common_process_id = 0; @@ -130,6 +131,7 @@ int xc_common_init(int api_level, const char *app_id, const char *app_version, const char *app_lib_dir, + int use_linker, const char *log_dir) { int r = 0; @@ -179,6 +181,8 @@ int xc_common_init(int api_level, XC_COMMON_DUP_STR(app_version); XC_COMMON_DUP_STR(app_lib_dir); XC_COMMON_DUP_STR(log_dir); + + xc_use_linker = use_linker; //save kernel version xc_util_get_kernel_version(buf, sizeof(buf)); diff --git a/xcrash_lib/src/main/cpp/xcrash/xc_common.h b/xcrash_lib/src/main/cpp/xcrash/xc_common.h index 57b354a..9d2e136 100644 --- a/xcrash_lib/src/main/cpp/xcrash/xc_common.h +++ b/xcrash_lib/src/main/cpp/xcrash/xc_common.h @@ -61,6 +61,7 @@ extern char *xc_common_app_id; extern char *xc_common_app_version; extern char *xc_common_app_lib_dir; extern char *xc_common_log_dir; +extern int xc_use_linker; //process info extern pid_t xc_common_process_id; @@ -86,6 +87,7 @@ int xc_common_init(int api_level, const char *app_id, const char *app_version, const char *app_lib_dir, + int use_linker, const char *log_dir); int xc_common_open_crash_log(char *pathname, size_t pathname_len, int *from_placeholder); diff --git a/xcrash_lib/src/main/cpp/xcrash/xc_crash.c b/xcrash_lib/src/main/cpp/xcrash/xc_crash.c index 1bd8e6c..682b070 100644 --- a/xcrash_lib/src/main/cpp/xcrash/xc_crash.c +++ b/xcrash_lib/src/main/cpp/xcrash/xc_crash.c @@ -224,7 +224,11 @@ static int xc_crash_exec_dumper(void *arg) //escape to the dumper process errno = 0; - execl(xc_crash_dumper_pathname, XCC_UTIL_XCRASH_DUMPER_FILENAME, NULL); + if (xc_use_linker) { + execl(LINKER_PATH, LINKER_NAME, xc_crash_dumper_pathname, NULL); + } else { + execl(xc_crash_dumper_pathname, XCC_UTIL_XCRASH_DUMPER_FILENAME, NULL); + } return 100 + errno; } diff --git a/xcrash_lib/src/main/cpp/xcrash/xc_jni.c b/xcrash_lib/src/main/cpp/xcrash/xc_jni.c index 697b80c..471e856 100644 --- a/xcrash_lib/src/main/cpp/xcrash/xc_jni.c +++ b/xcrash_lib/src/main/cpp/xcrash/xc_jni.c @@ -55,6 +55,7 @@ static jint xc_jni_init(JNIEnv *env, jstring app_id, jstring app_version, jstring app_lib_dir, + jboolean use_linker, jstring log_dir, jboolean crash_enable, jboolean crash_rethrow, @@ -133,6 +134,7 @@ static jint xc_jni_init(JNIEnv *env, c_app_id, c_app_version, c_app_lib_dir, + use_linker ? 1 : 0, c_log_dir)) goto clean; r_crash = 0; @@ -243,6 +245,7 @@ static JNINativeMethod xc_jni_methods[] = { "Ljava/lang/String;" "Ljava/lang/String;" "Ljava/lang/String;" + "Z" "Ljava/lang/String;" "Z" "Z" diff --git a/xcrash_lib/src/main/java/xcrash/AbiPathProvider.java b/xcrash_lib/src/main/java/xcrash/AbiPathProvider.java new file mode 100644 index 0000000..24e43db --- /dev/null +++ b/xcrash_lib/src/main/java/xcrash/AbiPathProvider.java @@ -0,0 +1,134 @@ +package xcrash; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import java.io.File; + +import dalvik.system.PathClassLoader; + +public class AbiPathProvider { + + private static final String TAG = "AbiPathProvider"; + + public static final String XCRASH_DUMPER_LIB_NAME = "xcrash_dumper"; + + public static void test(Context context, String libName) { + Log.d(TAG, "getAbiPathFromDefault: " + getAbiPathFromDefault(context, libName)); + Log.d(TAG, "getAbiPathFromClassloader: " + getAbiPathFromClassloader(context, libName)); + Log.d(TAG, "getAbiPathFromSplit: " + getAbiPathFromSplit(context)); + } + + public static Pair getAbiPath(Context context, String libName) { + String abiPathFromDefault = getAbiPathFromDefault(context, libName); + if (!TextUtils.isEmpty(abiPathFromDefault) && isSoExist(abiPathFromDefault, libName)) { + return new Pair<>(abiPathFromDefault, false); + } + + String abiPathFromClassloader = getAbiPathFromClassloader(context, libName); + if (!TextUtils.isEmpty(abiPathFromClassloader)) { + return new Pair<>(abiPathFromClassloader, true); + } + + String abiPathFromSplit = getAbiPathFromSplit(context); + if (!TextUtils.isEmpty(abiPathFromSplit)) { + return new Pair<>(abiPathFromSplit, true); + } + + return new Pair<>(context.getApplicationInfo().nativeLibraryDir, false); + } + + private static String getAbiPathFromDefault(Context context, String libName) { + return context.getApplicationInfo().nativeLibraryDir; + } + + private static boolean isSoExist(String nativeLibDir, String libName) { + File file = new File(nativeLibDir); + if (file.exists() && file.isDirectory()) { + File libFile = new File(file, System.mapLibraryName(libName)); + return libFile.exists(); + } + return false; + } + + private static String getAbiPathFromClassloader(Context context, String libName) { + ClassLoader classLoader = context.getClassLoader(); + + Log.d(TAG, "classLoader: " + classLoader); + + if (classLoader instanceof PathClassLoader) { + String libPath = ((PathClassLoader) classLoader).findLibrary(libName); + if (!TextUtils.isEmpty(libPath)) { + File parentFile = new File(libPath).getParentFile(); + if (parentFile != null) { + return parentFile.getAbsolutePath(); + } + } + } + + return ""; + } + + private static String getAbiPathFromSplit(Context context) { + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + return ""; + } + + String[] splitSourceDirs = context.getApplicationInfo().splitSourceDirs; + AbiName currentAbi = AbiName.ARM64_V8A; + String path = null; + for (String splitSourceDir : splitSourceDirs) { + for (AbiName abiName : AbiName.values()) { + if (splitSourceDir.contains(abiName.getBundleAbi())) { + path = splitSourceDir; + break; + } + } + if (path != null) { + break; + } + } + + if (!TextUtils.isEmpty(path)) { + return path + "!/lib/" + currentAbi.platformName; + } + + return ""; + } + + enum AbiName { + ARM64_V8A("arm64-v8a", 64), + ARMEABI_V7A("armeabi-v7a", 32), + X86_64("x86_64", 64), + X86("x86", 32); + + private final String platformName; + private final int bitSize; + private final String bundleAbi; + + // Constructor for enum fields + AbiName(String platformName, int bitSize) { + this.platformName = platformName; + this.bitSize = bitSize; + this.bundleAbi = platformName.replace("-", "_"); + } + + // Getter for platformName + public String getPlatformName() { + return platformName; + } + + // Getter for bitSize + public int getBitSize() { + return bitSize; + } + + // Getter for bundleAbi + public String getBundleAbi() { + return bundleAbi; + } + } + +} diff --git a/xcrash_lib/src/main/java/xcrash/NativeHandler.java b/xcrash_lib/src/main/java/xcrash/NativeHandler.java index f41c68a..0fac3a4 100644 --- a/xcrash_lib/src/main/java/xcrash/NativeHandler.java +++ b/xcrash_lib/src/main/java/xcrash/NativeHandler.java @@ -27,6 +27,7 @@ import android.os.Build; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import java.io.File; import java.util.Map; @@ -82,7 +83,9 @@ int initialize(Context ctx, boolean anrDumpFds, boolean anrDumpNetworkInfo, ICrashCallback anrCallback, - ICrashCallback anrFastCallback) { + ICrashCallback anrFastCallback, + String nativeLibPath, + boolean loadNativeWithLinker) { //load lib if (libLoader == null) { try { @@ -109,6 +112,17 @@ int initialize(Context ctx, this.anrFastCallback = anrFastCallback; this.anrTimeoutMs = anrRethrow ? 25 * 1000 : 45 * 1000; //setting rethrow to "false" is NOT recommended + String nativeLibraryDir; + boolean useLinker; + if (!TextUtils.isEmpty(nativeLibPath)) { + nativeLibraryDir = nativeLibPath; + useLinker = loadNativeWithLinker; + } else { + Pair abiPath = AbiPathProvider.getAbiPath(ctx, AbiPathProvider.XCRASH_DUMPER_LIB_NAME); + nativeLibraryDir = abiPath.first; + useLinker = abiPath.second; + } + //init native lib try { int r = nativeInit( @@ -121,7 +135,8 @@ int initialize(Context ctx, Build.FINGERPRINT, appId, appVersion, - ctx.getApplicationInfo().nativeLibraryDir, + nativeLibraryDir, + useLinker, logDir, crashEnable, crashRethrow, @@ -291,6 +306,7 @@ private static native int nativeInit( String appId, String appVersion, String appLibDir, + boolean useLinker, String logDir, boolean crashEnable, boolean crashRethrow, diff --git a/xcrash_lib/src/main/java/xcrash/Version.java b/xcrash_lib/src/main/java/xcrash/Version.java index afcb82e..a5bbe79 100644 --- a/xcrash_lib/src/main/java/xcrash/Version.java +++ b/xcrash_lib/src/main/java/xcrash/Version.java @@ -27,6 +27,6 @@ class Version { private Version() { } - static final String version = "3.1.0"; + static final String version = "3.1.1"; static final String fullVersion = "xCrash " + version; } diff --git a/xcrash_lib/src/main/java/xcrash/XCrash.java b/xcrash_lib/src/main/java/xcrash/XCrash.java index 73821b0..90ffc30 100644 --- a/xcrash_lib/src/main/java/xcrash/XCrash.java +++ b/xcrash_lib/src/main/java/xcrash/XCrash.java @@ -214,7 +214,9 @@ public static synchronized int init(Context ctx, InitParameters params) { params.anrDumpFds, params.anrDumpNetworkInfo, params.anrCallback, - params.anrFastCallback); + params.anrFastCallback, + params.nativeLibPath, + params.loadNativeWithLinker); } //maintain tombstone and placeholder files in a background thread with some delay @@ -517,6 +519,9 @@ public InitParameters setJavaCallback(ICrashCallback callback) { String[] nativeDumpAllThreadsWhiteList = null; ICrashCallback nativeCallback = null; + String nativeLibPath = null; + boolean loadNativeWithLinker = false; + /** * Enable the native crash capture feature. (Default: enable) * @@ -707,6 +712,24 @@ public InitParameters setNativeCallback(ICrashCallback callback) { return this; } + /** + * Set native library path + * @param nativeLibPath native library path + */ + public InitParameters setNativeLibPath(String nativeLibPath) { + this.nativeLibPath = nativeLibPath; + return this; + } + + /** + * Set whether load native library with linker + * @param loadNativeWithLinker load native library with linker + */ + public InitParameters setLoadNativeWithLinker(boolean loadNativeWithLinker) { + this.loadNativeWithLinker = loadNativeWithLinker; + return this; + } + //anr boolean enableAnrHandler = true; boolean anrRethrow = true; diff --git a/xcrash_sample/src/main/java/xcrash/sample/MyCustomApplication.java b/xcrash_sample/src/main/java/xcrash/sample/MyCustomApplication.java index 3a3590c..ee2094b 100644 --- a/xcrash_sample/src/main/java/xcrash/sample/MyCustomApplication.java +++ b/xcrash_sample/src/main/java/xcrash/sample/MyCustomApplication.java @@ -25,12 +25,14 @@ import android.app.Application; import android.content.Context; import android.util.Log; +import android.util.Pair; import org.json.JSONObject; import java.io.File; import java.io.FileWriter; +import xcrash.AbiPathProvider; import xcrash.TombstoneManager; import xcrash.TombstoneParser; import xcrash.XCrash; @@ -78,6 +80,10 @@ public void onCrash(String logPath, String emergency) throws Exception { Log.d(TAG, "xCrash SDK init: start"); + Pair abiPath = AbiPathProvider.getAbiPath(this, AbiPathProvider.XCRASH_DUMPER_LIB_NAME); + String nativeLibraryDir = abiPath.first; + boolean loadNativeWithLinker = abiPath.second; + // Initialize xCrash. XCrash.init(this, new XCrash.InitParameters() .setAppVersion("1.2.3-beta456-patch789") @@ -91,6 +97,8 @@ public void onCrash(String logPath, String emergency) throws Exception { .setNativeDumpAllThreadsWhiteList(new String[]{"^xcrash\\.sample$", "^Signal Catcher$", "^Jit thread pool$", ".*(R|r)ender.*", ".*Chrome.*"}) .setNativeDumpAllThreadsCountMax(10) .setNativeCallback(callback) +// .setNativeLibPath(nativeLibraryDir) +// .setLoadNativeWithLinker(loadNativeWithLinker) // .setAnrCheckProcessState(false) .setAnrRethrow(true) .setAnrLogCountMax(10)