An overview of the

XCrash is an open source component of IQiyi for monitoring Java and Native crashes. It only needs to be initialized in the Application class to enable:

XCrash.init(this);
Copy the code

Some options can also be configured:

XCrash.InitParameters params = new XCrash.InitParameters(); }}}}}}}}}}}}}}}}}}}}}}}}}} .setPlaceholderSizeKb(DEFAULT_PLACEHOLDER_SIZE_KB) .setLogFileMaintainDelayMs(DEFAULT_LOG_FILE_MAINTAIN_DELAY_MS); // Java crash configuration params.setJavArethrow (true).setJavadumpfds (false) //... .setJavaCallback(callback); // Native crash configuration params.setNativerethRow (true) //... .setNativeCallback(callback); // ANR configures params.setanrcallback (callback); XCrash.init(mConfig.getApp(), params);Copy the code

An example of json after a crash occurs is as follows:

{ "logcat":"..." , "java stacktrace":"..." , "Brand":"vivo", "Model":"vivo Y66", "pid":"18227", "network info":"..." , "memory info":"..." , "App version":" 1.2.3-beta456-Patch789 ", "tname":"main ", "pname":"xcrash.sample", "Manufacturer":"vivo", "Rooted":"No", "open files":"..." , "other threads":"..." , "OS version":"6.0.1", "ABI List ":" armeabi-v7A, Armeabi ", "Start time":"2020-06-01T14:47:11.768+0800", "foreground":"yes", "tid":"18227", Build "fingerprint" : "vivo \ / PD1621 \ / PD1621: the 6.0.1 \ / MMB29M \ / compiler04111924: user \ / release - the keys". "App ID":"xcrash.sample", "Crash type":" Java ", "API level":"23", "Crash time":"2020-06-01T14:47:36.029+0800", "After a maker" : "xCrash 2.4.9}"Copy the code

You can customize the callback processing logic, such as reporting information to the server:

ICrashCallback callback = new ICrashCallback() { @Override public void onCrash(String logPath, String emergency) { if (emergency ! = null) { sendReport(logPath, emergency); }}} private void sendReport(String logPath, String emergency) { Parse (logPath, emergency); generate json report Map<String, String> Map = tombstoneParser. parse(logPath, emergency); String crashReport = new JSONObject(map).toString(); // Send to server //... / / delete the log file TombstoneManager. DeleteTombstone (logPath); }Copy the code

Catching a Java crash

Java crash capture is simple, xCrash is implemented through the UncaughtExceptionHandler interface, processing logic is as follows:

class JavaCrashHandler implements UncaughtExceptionHandler { void initialize(...) {/ / get the original handler enclosing defaultHandler = Thread. GetDefaultUncaughtExceptionHandler (); Thread.setDefaultUncaughtExceptionHandler(this); } @Override public void uncaughtException(Thread thread, Throwable throwable) { if (defaultHandler ! = null) { Thread.setDefaultUncaughtExceptionHandler(defaultHandler); } // Handle crash handleException(thread, throwable); If (this.rethrow) {// If rethrow is true, then throw the exception to the original Hanlder if (defaultHandler! = null) { defaultHandler.uncaughtException(thread, throwable); }} else {/ / ActivityMonitor or exit the application. The getInstance () finishAllActivities (); Process.killProcess(this.pid); System.exit(10); }}}Copy the code

After a crash, The JavaCrashHandler collects logCAT, exception stack, file handles, memory, and other information and writes it to a tombstone file.

Note that xCrash pre-creates multiple placeholder files and renames them as tombstone files after crashes:

File createLogFile(String filePath) {
    File newFile = new File(filePath);

    File dir = new File(logDir);
    File cleanFile = dir.listFiles()[cleanFilesCount - 1];
    if (cleanFile.renameTo(newFile)) {
        return newFile;
    }

    newFile.createNewFile();
    return newFile;
}
Copy the code

Doing so prevents the log file from being created due to insufficient file handles.

Getting crash information

Java stack

For the Java stack, xCrash is obtained using two methods:

Throwable. PrintStackTrace () Thread. GetAllStackTraces () / / for other Thread stackCopy the code

The advantages of Thread.getallStacktraces () are simplicity and compatibility, but the disadvantages are low success rates, Thread pausing, and the inability to fetch the main stack after 7.0.

logcat

To obtain Logcat logs, xCrash is implemented using the Logcat command line tool:

static String getLogcat(int logcatMainLines, int logcatSystemLines, int logcatEventsLines) {
    int pid = android.os.Process.myPid();
    StringBuilder sb = new StringBuilder();

    getLogcatByBufferName(pid, sb, "main", logcatMainLines, 'D');
    getLogcatByBufferName(pid, sb, "system", logcatSystemLines, 'W');
    getLogcatByBufferName(pid, sb, "events", logcatSystemLines, 'I');

    return sb.toString();
}

private static void getLogcatByBufferName(int pid, StringBuilder sb, String bufferName, int lines, char priority) {
    List<String> command = new ArrayList<String>();
    command.add("/system/bin/logcat");
    command.add("-b");
    command.add(bufferName);
    command.add("-d");
    command.add("-v");
    command.add("threadtime");
    command.add("-t");

    //append logs
    String line;
    Process process = new ProcessBuilder().command(command).start();
    BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
    while ((line = br.readLine()) != null) {
        sb.append(line).append("\n");
    }
}
Copy the code

According to Android Developer Pro, the advantages of this approach are simplicity and compatibility, while the disadvantages are poor control and high failure rate.

File handle

File handle information is obtained by reading the /proc/self/fd file:

static String getFds() {
    StringBuilder sb = new StringBuilder("open files:\n");

    File dir = new File("/proc/self/fd");
    File[] fds = dir.listFiles();

    for (File fd : fds) {
        String path = Os.readlink(fd.getAbsolutePath());
        sb.append(fd.getName()).append(": ").append(path.trim()).append('\n');
    }
    return sb.toString();
}
Copy the code

Memory information

Memory information is obtained by reading /proc/meminfo, /proc/self-/status, /proc/self-/limits, and calling debug.getMemoryInfo:

static String getMemoryInfo() {
    return "memory info:\n"
        + Util.getFileContent("/proc/meminfo")
        + Util.getFileContent("/proc/self/status")
        + Util.getFileContent("/proc/self/limits")
        + Util.getProcessMemoryInfo()
}

static String getProcessMemoryInfo() {
    StringBuilder sb = new StringBuilder();
    Debug.MemoryInfo mi = new Debug.MemoryInfo();
    Debug.getMemoryInfo(mi);
    sb.append(mi.getMemoryStat("summary.java-heap"));
    sb.append(mi.getMemoryStat("summary.native-heap"));
    sb.append(mi.getMemoryStat("summary.code"));
    sb.append(mi.getMemoryStat("summary.stack")));
    sb.append(mi.getMemoryStat("summary.graphics")));
    sb.append(mi.getMemoryStat("summary.private-other")));
    sb.append(mi.getMemoryStat("summary.system")));
    sb.append(mi.getMemoryStat("summary.total-pss"));
    sb.append(mi.getMemoryStat("summary.total-swap"));
    return sb.toString();
}
Copy the code

Catching Native crashes

Initialize the

Native Crash detection is realized by monitoring system signals. Before starting the monitoring, some initialization operations need to be performed first. For example, since Crash information needs to be written into files, and considering the exhaustion of file handles, two file handles can be obtained in advance (one for Crash event, A to anR event) :

int xc_common_init(...) {
    //create prepared FD for FD exhausted case
    xc_common_open_prepared_fd(1);
    xc_common_open_prepared_fd(0);
}
Copy the code

Also, set up Java callbacks to pass information to the Java layer in the event of a crash:

static void xc_crash_init_callback(JNIEnv *env) { xc_crash_cb_method = (*env)->GetStaticMethodID(env, xc_common_cb_class, "crashCallback", "..." ); }Copy the code

Then start the background thread, create an EventFD, and listen. If an EventFD message is received, the crash occurs, and the child thread handles it:

static void xc_crash_init_callback(JNIEnv *env) {
    //eventfd and a new thread for callback
    xc_crash_cb_notifier = eventfd(0, EFD_CLOEXEC);
    pthread_create(&xc_crash_cb_thd, NULL, xc_crash_callback_thread, NULL);
}
Copy the code

The child thread is started to fetch the env variable, otherwise the Java layer cannot be called back. Using EventFD instead of condition variables or other synchronization methods is because eventFD is lighter, has less performance degradation, and has less impact on crash handling.

Registered signal processor

Next, use the SIGAction interface to register signal handlers and monitor system signals:

Static xcc_signal_crash_info_t xcc_signal_crash_info[] = {{. Signum = SIGABRT}, {.signum = SIGFPE}, {.signum = SIGILL}, {.signum = SIGSEGV}, Signum = SIGTRAP}, // breakpoint instruction or other trap instruction {.signum = SIGSYS}, // invalid system call {.signum = SIGSTKFLT} // stack overflow}; int xcc_signal_crash_register(void (*handler)(int, siginfo_t *, void *)) { struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_sigaction = handler; Size_t I; // Register the signal handler and save the old handler so that when the handler is finished, the signal can be sent to the old handler again. for (i = 0; i < sizeof(xcc_signal_crash_info) / sizeof(xcc_signal_crash_info[0]); i++) sigaction(xcc_signal_crash_info[i].signum, &act, &(xcc_signal_crash_info[i].oldact)); return 0; }Copy the code

Writes crash information to the file

When the signal is heard, consider a crash has occurred and open the log folder (if this fails, use the previously reserved fd) :

static int xc_common_open_log(...) { if ((fd = open(xc_common_log_dir, flags))) < 0) { //try again with the prepared fd xc_common_close_prepared_fd(is_crash); fd = open(xc_common_log_dir, flags); }}Copy the code

Placeholder rename file to tombstone file, ready to record crash information:

//try to rename a placeholder file and open it while ((n = syscall(XCC_UTIL_SYSCALL_GETDENTS, fd, buf, }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} return open(pathname, flags); }}Copy the code

To prevent a secondary crash from causing log writes to fail, start a new process that writes stack, memory, file handles, etc to the tombstone file:

static void xc_crash_signal_handler(int sig, siginfo_t *si, void *uc) { ... Pid_t dumper_pid = xc_crash_fork(xc_crash_exec_dumper); // Create a new process waitpid(dumper_pid, &status, __WALL); // Wait for the process to complete}Copy the code

The callback Java layer

After the file is written, send a message to the eventfd created earlier:

static void xc_crash_signal_handler(int sig, siginfo_t *si, void *uc) { xc_crash_callback(); } static void xc_crash_callback() { write(xc_crash_cb_notifier, &data, sizeof(data); pthread_join(xc_crash_cb_thd, NULL); // Wait for the thread to complete}Copy the code

When the child thread reads the EventFD message, it calls back to the Java layer:

static void *xc_crash_callback_thread(void *arg) { read(xc_crash_cb_notifier, &data, sizeof(data)); //do callback (*env)->CallStaticVoidMethod(env, ...) ; }Copy the code

The Java layer’s ICrashCallback then does the actual crash handling, such as sending JSON data to the server.

Heavy thrown exception

Just like Java Crash, Native Crash also has a rethrow exception mechanism. The realization principle is very simple: Cancel registration of its own signal processor and register the old signal processor saved before. After xCrash processing is completed, the signal is sent again and processed by the old signal processor:

Static void xc_crash_signal_handler(int sig, siginfo_t *si, void *uc) { If (xc_crash_rethrow) {xcc_signal_crash_unregister(); }... // Get crash log // callback to Java layer xc_crash_callback(); Xcc_signal_crash_queue (si)}Copy the code

conclusion

Java crash monitoring is implemented with UncaughtExceptionHandler. When a crash occurs, logcat logs are obtained using the logcat command line tool, and information about file handles and memory is collected using the /proc file system.

Native crash detection is realized by monitoring system signals, and the process is as follows:

  1. Register your own signal handler and save the old signal handler
  2. When a crash occurs, a new process is started, information about the crash environment is collected, written to a tombstone file, and called back to the Java layer
  3. Register the old signal processor, re-send the signal, to the old signal processor processing

Notable details include:

  1. In case of file handle exhaustion, xCrash will pre-fetch file handles and pre-create multiple placeholder files in case the file cannot be created when a crash occurs
  2. To prevent log generation from failing due to a secondary crash, xCrash created a new process to collect crash scenes
  3. The child thread for Java callbacks listens for crashes using EventFD

PS: just detection, originally the official already wrote an article on the xCrash, some teach fish to swim, recommend reading blog.itpub.net/69945252/vi…