android 开发过程中有时候需要使用JNI的方式调用C/C++的库。因此在调试的过程如果发现崩溃异常,如果能够获取C/C++ 的异常堆栈,则可以方便的确定哪一行代码出现了问题,方便快速的定位问题。
在捕获Native异常中,原理上面基本是采用linux的信号机制。
关于Unix-like系统的信号机制可以参见《深入Linux内核》第4章 中断和异常 ;第11章 信号。
关于信号和异常介绍比较好的博客有:
https://blog.csdn.net/ypt523/article/details/80290208。感谢博主的无私贡献。
Linux 信号相关编程,进程编程,需要查询相关资料,本文不进行介绍。
与Native异常相关的配置属性有
public final class XCrash{
...
public static class InitParameters {
// anr异常处理器,默认为true,如果为false不捕获anr异常
boolean enableAnrHandler = true;
// 是否恢复捕获anr异常。默认为true
boolean anrRethrow = true;
// 是否设置anr的状态标志给进程状态(具体参见源码中的注释)
boolean anrCheckProcessState = true;
// anr日志最大保留文件数量
int anrLogCountMax = 10;
// 执行命令 logcat -b system 输出的日志行数
int anrLogcatSystemLines = 50;
// 执行命令 logcat -b event 输出的日志行数
int anrLogcatEventsLines = 50;
// 执行命令 logcat -b maint输出的日志行数
int anrLogcatMainLines = 200;
// 是否输出app进程的下打开的文件描述符
boolean anrDumpFds = true;
// 发生anr异常的应用回调
ICrashCallback anrCallback = null;
}
}
public final class XCrash{
// 默认初始化接口 默认捕获native异常
public static int init(Context ctx) {
return init(ctx, null);
}
// 自定义配置初始化接口
public static synchronized int init(Context ctx, InitParameters params){
// Native 异常处理器初始化
//init native crash handler / ANR handler (API level >= 21)
int r = Errno.OK;
if (params.enableNativeCrashHandler || (params.enableAnrHandler && Build.VERSION.SDK_INT >= 21)) {
r = NativeHandler.getInstance().initialize(
ctx, // context上下文
params.libLoader,// so库加载路径
appId, //appID
params.appVersion,// 应用版本号
params.logDir, // 日志输出路径
params.enableNativeCrashHandler,
params.nativeRethrow,
params.nativeLogcatSystemLines,
params.nativeLogcatEventsLines,
params.nativeLogcatMainLines,
params.nativeDumpElfHash,
params.nativeDumpMap,
params.nativeDumpFds,
params.nativeDumpAllThreads,
params.nativeDumpAllThreadsCountMax,
params.nativeDumpAllThreadsWhiteList,
params.nativeCallback,
// 以下配置与ANR相关
params.enableAnrHandler && Build.VERSION.SDK_INT >= 21,
params.anrRethrow,
params.anrCheckProcessState,
params.anrLogcatSystemLines,
params.anrLogcatEventsLines,
params.anrLogcatMainLines,
params.anrDumpFds,
params.anrCallback);
}
}
}
类NativeHandler是处理Native异常,NativeHandler为单例模式,源码参见如下。
class NativeHandler {
...
private static final NativeHandler instance = new NativeHandler();
private NativeHandler() {
}
static NativeHandler getInstance() {
return instance;
}
}
NativeHandler 中最重要的是初始化接口,初始化接口主要功能有:
int initialize(...)
{
// 加载libxcrash.so
if (libLoader == null) {
try {
System.loadLibrary("xcrash");
} catch (Throwable e) {
XCrash.getLogger().e(Util.TAG, "NativeHandler System.loadLibrary failed", e);
return Errno.LOAD_LIBRARY_FAILED;
}
} else {
try {
// 加载指定路径下面的
libLoader.loadLibrary("xcrash");
} catch (Throwable e) {
XCrash.getLogger().e(Util.TAG, "NativeHandler ILibLoader.loadLibrary failed", e);
return Errno.LOAD_LIBRARY_FAILED;
}
}
// 初始化接口
int r = nativeInit(...)
}
// JNI接口,初始化接口
private static native int nativeInit(
int apiLevel,
String osVersion,
String abiList,
String manufacturer,
String brand,
String model,
String buildFingerprint,
String appId,
String appVersion,
String appLibDir,
String logDir,
boolean crashEnable,
boolean crashRethrow,
int crashLogcatSystemLines,
int crashLogcatEventsLines,
int crashLogcatMainLines,
boolean crashDumpElfHash,
boolean crashDumpMap,
boolean crashDumpFds,
boolean crashDumpAllThreads,
int crashDumpAllThreadsCountMax,
String[] crashDumpAllThreadsWhiteList,
boolean traceEnable,
boolean traceRethrow,
int traceLogcatSystemLines,
int traceLogcatEventsLines,
int traceLogcatMainLines,
boolean traceDumpFds);
// JNI 接口,通知natvie异常信息。
private static native void nativeNotifyJavaCrashed();
xCrash工程目录下面有java文件夹和native文件夹,natvie文件夹内的源码用于捕获native异常。
xCrash源码结构中除了Jave源码还有native源码,native源码的结构如下
从源码结构中可以分为:
#!/bin/bash
ndk-build -C ./libxcrash/jni
ndk-build -C ./libxcrash_dumper/jni
#!/bin/bash
mkdir -p ../java/xcrash/xcrash_lib/src/main/jniLibs/armeabi
mkdir -p ../java/xcrash/xcrash_lib/src/main/jniLibs/armeabi-v7a
mkdir -p ../java/xcrash/xcrash_lib/src/main/jniLibs/arm64-v8a
mkdir -p ../java/xcrash/xcrash_lib/src/main/jniLibs/x86
mkdir -p ../java/xcrash/xcrash_lib/src/main/jniLibs/x86_64
cp -f ./libxcrash/libs/armeabi/libxcrash.so ../java/xcrash/xcrash_lib/src/main/jniLibs/armeabi/libxcrash.so
cp -f ./libxcrash/libs/armeabi-v7a/libxcrash.so ../java/xcrash/xcrash_lib/src/main/jniLibs/armeabi-v7a/libxcrash.so
cp -f ./libxcrash/libs/arm64-v8a/libxcrash.so ../java/xcrash/xcrash_lib/src/main/jniLibs/arm64-v8a/libxcrash.so
cp -f ./libxcrash/libs/x86/libxcrash.so ../java/xcrash/xcrash_lib/src/main/jniLibs/x86/libxcrash.so
cp -f ./libxcrash/libs/x86_64/libxcrash.so ../java/xcrash/xcrash_lib/src/main/jniLibs/x86_64/libxcrash.so
cp -f ./libxcrash_dumper/libs/armeabi/xcrash_dumper ../java/xcrash/xcrash_lib/src/main/jniLibs/armeabi/libxcrash_dumper.so
cp -f ./libxcrash_dumper/libs/armeabi-v7a/xcrash_dumper ../java/xcrash/xcrash_lib/src/main/jniLibs/armeabi-v7a/libxcrash_dumper.so
cp -f ./libxcrash_dumper/libs/arm64-v8a/xcrash_dumper ../java/xcrash/xcrash_lib/src/main/jniLibs/arm64-v8a/libxcrash_dumper.so
cp -f ./libxcrash_dumper/libs/x86/xcrash_dumper ../java/xcrash/xcrash_lib/src/main/jniLibs/x86/libxcrash_dumper.so
cp -f ./libxcrash_dumper/libs/x86_64/xcrash_dumper ../java/xcrash/xcrash_lib/src/main/jniLibs/x86_64/libxcrash_dumper.so
#!/bin/bash
ndk-build -C ./libxcrash/jni clean
ndk-build -C ./libxcrash_dumper/jni clean
编译libxcrash.so是标准的使用ndk编译so的标准文件结构。在源码中需要Application.mk和Android.mk。
Application.mk源文件如下
APP_ABI := armeabi armeabi-v7a arm64-v8a x86 x86_64
APP_PLATFORM := android-14
# 当前路径
LOCAL_PATH := $(call my-dir)
# CLEAR_VARS 变量由Build System提供。并指向一个指定的GNU Makefile,由它负责清理很多LOCAL_xxx
include $(CLEAR_VARS)
# LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块。名字必须唯一且不包含空格
LOCAL_MODULE := test
# 编译 选项
LOCAL_CFLAGS := -std=c11 -Weverything -Werror -O0
# 自定义头文件路径
LOCAL_C_INCLUDES := $(LOCAL_PATH)
# LOCAL_SRC_FILES变量必须包含将要打包如模块的C/C++ 源码
LOCAL_SRC_FILES := xc_test.c
# 编译成静态库
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := xcrash
LOCAL_CFLAGS := -std=c11 -Weverything -Werror -fvisibility=hidden
LOCAL_LDLIBS := -ldl -llog
LOCAL_STATIC_LIBRARIES := test
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(LOCAL_PATH)/../../common
LOCAL_SRC_FILES := xc_jni.c \
xc_common.c \
xc_crash.c \
xc_trace.c \
xc_dl.c \
xc_fallback.c \
xc_util.c \
$(wildcard $(LOCAL_PATH)/../../common/*.c)
# 编译为动态库
include $(BUILD_SHARED_LIBRARY)
从上面源码可以获取:
libxcrash_dumper 文件夹下面也有Application.mk和Android.mk。Application.mk的文件内核和libxcrash 一样,不重复写。
libxcrash_dumper下的Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := xcrash_dumper
LOCAL_CFLAGS := -std=c11 -Weverything -Werror -fvisibility=hidden -fPIE
LOCAL_LDFLAGS := -pie
LOCAL_LDLIBS := -ldl -llog
LOCAL_STATIC_LIBRARIES := lzma
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(LOCAL_PATH)/../../common
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.c) $(wildcard $(LOCAL_PATH)/../../common/*.c)
# 编译成一个可执行文件
include $(BUILD_EXECUTABLE)
include $(LOCAL_PATH)/lzma/Android.mk
根据源码可知:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := lzma
LOCAL_CFLAGS := -std=c11 -Weverything -Werror \
-Wno-enum-conversion \
-Wno-reserved-id-macro \
-Wno-undef \
-Wno-missing-prototypes \
-Wno-missing-variable-declarations \
-Wno-cast-align \
-Wno-sign-conversion \
-Wno-assign-enum \
-Wno-unused-macros \
-Wno-padded \
-Wno-cast-qual \
-Wno-strict-prototypes \
-fPIE \
-D_7ZIP_ST
LOCAL_C_INCLUDES := $(LOCAL_PATH)
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
LOCAL_SRC_FILES := 7zCrc.c \
7zCrcOpt.c \
CpuArch.c \
Bra.c \
Bra86.c \
BraIA64.c \
Delta.c \
Lzma2Dec.c \
LzmaDec.c \
Sha256.c \
Xz.c \
XzCrc64.c \
XzCrc64Opt.c \
XzDec.c
include $(BUILD_STATIC_LIBRARY)
通过源码可以确定是编译成为一个静态库liblzma.a
由于xCrah是通过JNI调用C/C++代码,因此需要确定JNI调用的接口,通过查询代码确认libxcrash文件夹内的xc_jni.c是JNI调用的入口,因此从此处开始分析源码。
通过上面的Android.mk可以知,xc_jni.c ; xc_common.c;xc_crash.c;xc_trace.c; xc_dl.c; xc_fallback.c;
xc_util.c ;以及common文件夹内的源文件编译成一个动态库。
xc_jni.c : JNI调用接口
xc_common.c: 日志文件操作
;xc_crash.c: 捕获natvie异常的核心功能
xc_dl.c:通过堆栈地址获取函数名
xc_fallback.c:获取堆栈信息
xc_util.c:常用的处理工具
xc_jni的功能是JNI的调用接口。
当Java层代码中执行
System.loadLibrary("xcrash");
Native 中的 JNI_OnLoad(JavaVM *vm, void *reserved) 方法会被调用。此时可以注册对应于Java层调用的navtive方法。
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv *env;
jclass cls;
(void)reserved;
if(NULL == vm) return -1;
//register JNI methods
if(JNI_OK != (*vm)->GetEnv(vm, (void**)&env, XC_JNI_VERSION)) return -1;
if(NULL == env || NULL == *env) return -1;
// 查找类 XC_JNI_CLASS_NAME
if(NULL == (cls = (*env)->FindClass(env, XC_JNI_CLASS_NAME))) return -1;
// 注册java 层native 接口
if((*env)->RegisterNatives(env, cls, xc_jni_methods, sizeof(xc_jni_methods) / sizeof(xc_jni_methods[0]))) return -1;
// 保存信息
xc_common_set_vm(vm, env, cls);
return XC_JNI_VERSION;
}
nativeInit ----> xc_jni_init
nativeNotifyJavaCrashed ----> xc_jni_notify_java_crashed
nativeTestCrash ----> xc_jni_test_crash
// libxcrash/
/**
* JavaVM *vm : 虚拟机在JNI中的表示
* JNIEnv *env 类型是一个指向全部JNI方法的指针
* jclass cls 调用JNI的类的引用
**/
void xc_common_set_vm(JavaVM *vm, JNIEnv *env, jclass cls)
{
// 在进程信息中保存
xc_common_vm = vm;
// 创建一个新的全局类引用,并保存在全局的进程信息中。
xc_common_cb_class = (*env)->NewGlobalRef(env, cls);
// 检查是否有异常
XC_JNI_CHECK_NULL_AND_PENDING_EXCEPTION(xc_common_cb_class, err);
return;
err:
xc_common_cb_class = NULL;
}
java 层中调用
private static native int nativeInit(
int apiLevel,
String osVersion,
String abiList,
String manufacturer,
String brand,
String model,
String buildFingerprint,
String appId,
String appVersion,
String appLibDir,
String logDir,
boolean crashEnable,
boolean crashRethrow,
int crashLogcatSystemLines,
int crashLogcatEventsLines,
int crashLogcatMainLines,
boolean crashDumpElfHash,
boolean crashDumpMap,
boolean crashDumpFds,
boolean crashDumpAllThreads,
int crashDumpAllThreadsCountMax,
String[] crashDumpAllThreadsWhiteList,
boolean traceEnable,
boolean traceRethrow,
int traceLogcatSystemLines,
int traceLogcatEventsLines,
int traceLogcatMainLines,
boolean traceDumpFds);
Native中的xc_jni_init 会被调用
static jint xc_jni_init(JNIEnv *env,
jobject thiz,
jint api_level,
jstring os_version,
jstring abi_list,
jstring manufacturer,
jstring brand,
jstring model,
jstring build_fingerprint,
jstring app_id,
jstring app_version,
jstring app_lib_dir,
jstring log_dir,
jboolean crash_enable,
jboolean crash_rethrow,
jint crash_logcat_system_lines,
jint crash_logcat_events_lines,
jint crash_logcat_main_lines,
jboolean crash_dump_elf_hash,
jboolean crash_dump_map,
jboolean crash_dump_fds,
jboolean crash_dump_all_threads,
jint crash_dump_all_threads_count_max,
jobjectArray crash_dump_all_threads_whitelist,
jboolean trace_enable,
jboolean trace_rethrow,
jint trace_logcat_system_lines,
jint trace_logcat_events_lines,
jint trace_logcat_main_lines,
jboolean trace_dump_fds)
xc_jni_init 的主要功能有两个:
在xc_common.c中申明了全局变量用于存储通用信息,通用信息分为以下几类。
变量 | 含义 |
---|---|
int xc_common_api_level | api level |
char * xc_common_os_version | os version |
char * xc_common_abi_list | 支持的CPU指令集 |
char *xc_common_manufacturer | 硬件产商 |
char *xc_common_brand | 产品品牌 |
char *xc_common_model | 产品名称 |
char *xc_common_build_fingerprint | 设备指纹 |
char *xc_common_kernel_version | 内核版本 |
char* xc_common_time_zone | 时区 |
备注
在定制化时,可以在此部分新增一些变量,比如说硬件序列号等,方便确定唯一台终端。
变量 | 含义 | 备注 |
---|---|---|
char * xc_common_app_id | 应用的applId | |
char * xc_common_app_version | 应用版本名称 | 一般Android 应用的 versionName |
char * xc_common_app_lib_dir | so库加载路径 | 用于执行 libxcrash_dump.so |
char* xc_common_log_dir | 日志输出路径 |
变量 | 含义 | 备注 |
---|---|---|
pid_t xc_common_process_id | 进程ID | |
char * xc_common_process_name | 进程名称 | |
uint64_t xc_common_start_time | libxcrash.so初始化时间 | |
JavaVM xc_common_vm | java 虚拟机在JNI层的引用 | |
jclass xc_common_cb_class | 加载libxcrash.so的java类 | |
int xc_common_fd_null | 空设备 |
变量 | 含义 |
---|---|
sig_atomic_t xc_common_native_crashed | 标志产生native 异常 |
sig_atomic_t xc_common_java_crashed | 标志产生java 异常 |
int xc_crash_init(...){
// 打开设备 /dev/null
xc_crash_prepared_fd = XCC_UTIL_TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR));
// 标志是否将异常抛给java 层
xc_crash_rethrow = rethrow;
// 申请空间保存,异常日志头部信息
if(NULL == (xc_crash_emergency = calloc(XC_CRASH_EMERGENCY_BUF_LEN, 1))) return XCC_ERRNO_NOMEM;
// 创建文件路径
if(NULL == (xc_crash_dumper_pathname = xc_util_strdupcat(xc_common_app_lib_dir, "/"XCC_UTIL_XCRASH_DUMPER_FILENAME))) return XCC_ERRNO_NOMEM;
// 根据API创建初始化函数堆栈解析库
// api >= 16 && api <= 20 使用 libcorkscrew.so
// api >-21 && api <=23 使用 libunwind.so
xcc_unwind_init(xc_common_api_level);
// 初始化线程用于将异常抛出给java层
xc_crash_init_callback(env);
// 信息保存
...
// fork 或者clone
#ifndef __i386__
if(NULL == (xc_crash_child_stack = calloc(XC_CRASH_CHILD_STACK_LEN, 1))) return XCC_ERRNO_NOMEM;
xc_crash_child_stack = (void *)(((uint8_t *)xc_crash_child_stack) + XC_CRASH_CHILD_STACK_LEN);
#else
if(0 != pipe2(xc_crash_child_notifier, O_CLOEXEC)) return XCC_ERRNO_SYS;
#endif
// 注册信号处理器
return xcc_signal_crash_register(xc_crash_signal_handler);
}
小结
从初始化函数中需要特别注意的有以下几点: