Android 平台 Native Crash (二)问题分析与定位

Android 平台 Native Crash 系列文章:

Android 平台 Native Crash (一)捕获原理详解

Android 平台 Native Crash (二)问题分析与定位

一 Native Crash 简介

Native Crash 是发生在 Android 系统中 C/C++ 层面的 Crash,具体可参考: Android 平台 Native Crash (一)捕获原理详解

二 Native C/C++ Libraries 简介

Android 开发中通常是将 Native 层代码打包为.so格式的动态库文件,然后供 Java 层调用,.so库文件通常有以下三种来源:

  • Android 系统自带的核心组件和服务,如多媒体库、OpenGL ES 图形库等
  • 引入的第三方库
  • 开发者自行编译生成的动态库

2.1 .so文件组成

一个完整的 .so 文件由 C/C++代码和一些 debug 信息组成,这些 debug 信息会记录 .so中所有方法的对照表,就是方法名和其偏移地址的对应表,也叫做符号表 symbolic 信息,这种 .so被称为未 strip 的,通常体积会比较大。

2023-03-01-image-3.png

通常 release 的.so都是需要经过 strip 操作,strip 之后的.so中的 debug 信息会被剥离,整个 so 的体积也会缩小许多。

可以简单将这个 debug 信息理解为 Java 代码混淆中的 mapping 文件,只有拥有这个 mapping 文件才能进行堆栈分析。如果堆栈信息丢了,基本上堆栈无法还原,问题也无法解决。

所以,这些 debug 信息尤为重要,是我们分析 Native Crash 问题的关键信息,那么我们在编译 .so 时 候务必保留一份未被 strip 的.so或者剥离后的符号表信息,以供后面问题分析。

2.2 查看 so 状态

也可以通过命令行来查看.so的状态,Linux 下使用 file 命令即可,在命令返回值里面可以查看到.so的一 些基本信息。

如下代码所示,stripped 代表是没有 debug 信息的.so,with debug_info, not stripped 代表携带 debug 信息的.so

file libbreakpad-core-s.so
libbreakpad-core-s.so: *******, BuildID[sha1]=54ad86d708f4dc0926ad220b098d2a9e71da235a, stripped
file libbreakpad-core.so
libbreakpad-core.so: ******, BuildID[sha1]=54ad86d708f4dc0926ad220b098d2a9e71da235a, with debug_info, not stripped

2.3 获取 strip 和未被 strip 的 so

目前 Android Studio 无论是使用 mk 或者 Cmake 编译的方式都会同时输出 strip 和未 strip 的 so,如下图是 Cmake 编译 so 产生的两个对应的 so。

2023-03-01-image -1--4.png

2023-03-01-image -2--3.png

strip 之前的 so 路径:{project}/app/build/intermediates/merged_native_libs

strip 之后的 so 路径:{project}/app/build/intermediates/stripped_native_libs

三 Native Crash 捕获与解析

3.1 通过 DropBox 日志解析

Android Dropbox 是 Android 在 Froyo(API level 8) 引入的用来持续化存储系统数据的机制。主要用于记录 Android 运行过程中, 内核, 系统进程, 用户进程等出现严重问题时的 log, 可以认为这是一个可持续存储的系统级别的 logcat。

相关文件记录存储目录:/data/system/dropbox

只需要将 DropBox 的日志获取到即可进行分析解决,下面贴上一份 Log 示例。

2023-03-01-image -3--3.png

DropBox 中的 Tombstone 文件显示,Native Crash 发生在动态库 libnativedemo.so 中,具体的方法和行数可以用 Android/SDK/NDK 提供的工具 linux-android-addr2line 来进一步定位。

addr2line 工具通常在 ndk 目录下,例如:

${SDK Path}/ndk/21.4.7075529/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line

然后使用命令行,既可将偏移地址转换为 crash 方法和行数

arm-linux-androideabi-addr2line [option(s)] [addr(s)]

简单来说就是 arm-linux-androideabi-addr2line + 可选项 + 异常地址

[option(s)]介绍
@从文件中读取 options
-a在结果中显示地址 addr
-b设置二进制文件的格式
-e设置输入文件(常用:选项后面需要跟报错的共享库,用于 addr2line 程序分析)
-iunwind inline function
-jRead section-relative offsets instead of addresses
-p让输出更易读
-s在输出中,剥离文件夹名称
-f显示函数名称
-C(大写的) 将输出的函数名 demangle
-h输出帮助
-v输出版本信息

使用 addr2line 进行解析,结果可以看到,Native Crash 发生在文件 native-lib.cpp17 行的 Crash() 方法

2023-03-01-image -4--3.png

结合代码分析,在 Crash() 中,对空指针 *a 进行了赋值操作,所以造成了 crash。

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_elijah_nativedemo_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

/**
 * 引起 crash
 */
void Crash() {
    volatile int *a = (int *) (NULL);
    *a = 1;
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_elijah_nativedemo_MainActivity_nativeCrash(JNIEnv *env, jobject thiz) {
    Crash();
}

示例代码:https://github.com/elijah398/NativeCrashDemo

通过读取 DropBox 获得 crash log -> addr2line 解析偏移地址的方法确实可以定位到 native crash 发生的现场,但是 DropBox 只有系统应用能访问,非系统应用拿不到日志。对于非系统应用,可以使用 google 提供的开源工具 BreakPad 进行监测分析。

3.2 通过 BreakPad 捕获解析

3.2.1 breakpad 简介

BreakPad 是 Google 开发的一个跨平台 C/C++ dump捕获开源库,崩溃文件使用微软的 minidump格式存储,也支持发送这个 dump 文件到你的服务器,breakpad 可以在程序崩溃时触发 dump 写入操作,也可以在没有触发 dump 时主动写 dump 文件。breakpad 支持 windows、linux、macos、android、ios 等。目前已有 Google Chrome, Firefox, Google Picasa, Camino, Google Earth 等项目使用。

3.2.2 实现原理

在不同平台下使用平台特有的函数以及方式实现异常捕获:

Windows: 通过 SetUnhandledExceptionFilter()设置崩溃回掉函数

Max OS: 监听 Mach Exception Port 获取崩溃事件

Linux: 监听 SIGILL SIGSEGV 等异常信号 获取崩溃事件

工作原理示意图

2023-03-01-breakpad.png

图片右上角是一个完整的应用程序,它包含了三部分即程序代码、Breakpad Client(即 brekapad 提供出来的静态库),调式信息

  • Build System中 breakpad 的 symbol 生成工具借助应用层序中的 Debugging Information 这一部分生成一个 Google 自己的符号文件,最终在发布应用层序的时候使用 strip 将调式信息去除

  • User’s System中运行的应用程序是通过 strip 去除了调式信息的,若应用程序发生 Crash,Breakpad client 就会写 minidump 文件到指定目录,也可以将产生的 minidump 文件发送到远端服务器即 Crash Colletcor。

  • Crash Collector就可以利用 Build System 中产生的 symol 文件和 User’s System 中上报的 minidump 文件生成用户可读的 stack trace

3.2.3 使用示例

获取 breakpad 源码

https://github.com/google/breakpad

执行安装 breakpad
1. cd breakpad 目录
2. 直接命令窗口输入:

./configure && make
移植 Breakpad 到客户端程序

breakpad 源码导入应用程序 cpp 目录下

2023-03-01-image -5--3.png

然后在 breakpad 中创建 CMakeLists.txt

cmake_minimum_required(VERSION 3.18.1)
 
#导入头文件
include_directories(src src/common/android/include)
#支持汇编文件的编译
enable_language(ASM)
#源文件编译为静态库
add_library(breakpad STATIC
        src/client/linux/crash_generation/crash_generation_client.cc
        src/client/linux/dump_writer_common/thread_info.cc
        src/client/linux/dump_writer_common/ucontext_reader.cc
        src/client/linux/handler/exception_handler.cc
        src/client/linux/handler/minidump_descriptor.cc
        src/client/linux/log/log.cc
        src/client/linux/microdump_writer/microdump_writer.cc
        src/client/linux/minidump_writer/linux_dumper.cc
        src/client/linux/minidump_writer/linux_ptrace_dumper.cc
        src/client/linux/minidump_writer/minidump_writer.cc
        src/client/linux/minidump_writer/pe_file.cc
        src/client/minidump_file_writer.cc
        src/common/convert_UTF.cc
        src/common/md5.cc
        src/common/string_conversion.cc
        src/common/linux/breakpad_getcontext.S
        src/common/linux/elfutils.cc
        src/common/linux/file_id.cc
        src/common/linux/guid_creator.cc
        src/common/linux/linux_libc_support.cc
        src/common/linux/memory_mapped_file.cc
        src/common/linux/safe_readlink.cc)
#导入相关的库
target_link_libraries(breakpad log)

breakpad 中的 CMakeLists.txt 创建完成后,还需要在 cpp 目录下的 CMakeLists.txt 中进行配置,将刚刚创建的 CMakeLists.txt 引入进去

cmake_minimum_required(VERSION 3.18.1)
 
#引入头文件
include_directories(breakpad/src breakpad/src/common/android/include)
 
add_library(nativecrash SHARED nativecrashlib.cpp)
 
#添加子目录,会自动查找这个目录下的 CMakeList
add_subdirectory(breakpad)
 
target_link_libraries(nativecrash log breakpad)
breakpad 初始化

然后在自己项目的 native 文件中对 breakpad 进行初始化,如下

#include <jni.h>
#include <string>
#include "breakpad/src/client/linux/handler/exception_handler.h"
#include "breakpad/src/client/linux/handler/minidump_descriptor.h"

/**
 * 引起 crash
 */
void Crash() {
    volatile int *a = (int *) (NULL);
    *a = 1;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_elijah_nativedemo_MainActivity_nativeCrash(JNIEnv *env, jobject thiz) {
    Crash();
}

//回调函数
bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
                  void* context,
                  bool succeeded) {
    printf("Dump path: %s\n", descriptor.path());
    return false;
}

//breakpad 初始化
extern "C"
JNIEXPORT void JNICALL
Java_com_elijah_nativedemo_MainActivity_initNative(JNIEnv *env, jclass clazz, jstring path_) {
    const char *path = env->GetStringUTFChars(path_, 0);
    google_breakpad::MinidumpDescriptor descriptor(path);
    static google_breakpad::ExceptionHandler eh(descriptor, NULL, DumpCallback,
                                                NULL, true, -1);
    env->ReleaseStringUTFChars(path_, path);
}
Java 层代码

Java 层传入 Crash dump 文件的保存路径,用于崩溃时文件的生成

package com.elijah.nativedemo;

import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import java.io.File;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("nativedemo");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init(this);
        findViewById(R.id.crash)
                .setOnClickListener(
                        new View.OnClickListener() {
                            @Override
                            public void onClick(View view) {
                                nativeCrash();
                            }
                        });
    }

    public static void init(Context context){
        Context applicationContext = context.getApplicationContext();
        File file = new File(applicationContext.getExternalCacheDir(),"native_crash");
        if(!file.exists()){
            file.mkdirs();
        }
        initNative(file.getAbsolutePath());
    }

    /**
     * 模拟崩溃
     */
    public static native void nativeCrash();

    /**
     * 初始化 breakpad
     * @param path
     */
    private static native void initNative(String path);
}
捕获 Crash,解析 dump

Native Crash 产生后,breakpad 会捕获 crash 信息,生成后缀为.dmp的 dump 文件到指定目录下。

2023-03-01-image -6--3.png

.dmp 格式的文件通常无法查看,需要解析工具对这个文件进行解析。解析工具在步骤“执行安装 breakpad”中就已经生成在 breakpad/src/processor目录下,名为 minidump_stackwalk

输入如下指令即可解析 dump 文件

./minidump_stackwalk my.dump > crash.txt

生成的 crash.txt 如下图所示,关键代码是红框的部分,Thread 0 后面有一个 crashed 标识,说明这里是发生崩溃的线程,而下面就是崩溃的文件以及内存地址,使用 3.1 中介绍的 addr2line 工具进行解析即可得到问题方法与行号

2023-03-01-image -7--2.png

示例代码:https://github.com/elijah398/NativeCrashDemo

参考文献

Android NativeCrash 捕获与解析

Android—Native层崩溃的监听工具BreakPad

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Android原生崩溃(android native crash)是指在Android平台上,由于代码执行错误或者资源耗尽等原因,导致应用程序无法正常运行而崩溃或者闪退的现象。原生崩溃产生的原因可能是由于C或者C++代码编写错误、内存溢出、线程竞争等。针对原生崩溃问题,开发人员需要使用调试工具进行定位和修复。 ### 回答2: Android Native Crash发生在安卓应用程序运行时,由于C或C++库的错误或者其他原因导致应用程序崩溃。有时候Native Crash可能会影响整个设备,尤其是当Native Crash发生在系统级别的代码中时。 产生Native Crash的原因通常包括以下几个方面: 1. 内存管理问题Native Crash通常与内存管理问题相关,这可能是由于访问未初始化的内存,使用错误的指针或释放已释放的内存等原因引起的。 2. 硬件问题Native Crash也可能与设备相关的硬件问题有关,例如访问不可用的硬件资源或硬件设备故障。 3. 应用程序代码问题Native Crash可能发生在应用程序代码的错误、资源泄漏、堆栈溢出等问题引起的。 4. 第三方库问题Native Crash也可能由第三方库中的错误或bug引起。这些库可能没有经过充分的测试,或者与设备硬件不兼容。 为了更好地解决Native Crash问题,开发者可以通过日志或崩溃报告(Crash Report)来检测和分析崩溃日志,并查看堆栈跟踪信息来确定导致Native Crash的来源。在开发过程中,经常使用除了自己编写的代码之外的第三方库时,还可以考虑使用崩溃的回溯工具,如Firebase Crashlytics等。 总之,Native CrashAndroid应用程序开发过程中经常遇到的问题,它可能会对用户体验和开发进度产生重大影响,因此开发者需要强化对Native Crash的理解和分析能力,以更好地解决Native Crash问题。 ### 回答3: Android Native Crash,指的是在 Android 系统中发生的本地崩溃。本地崩溃是指应用程序使用本地代码,而不是 Java 代码,导致应用程序崩溃的问题。本地代码可以是编写在 C/C++ 等语言中的库,或是应用程序本身所编写的 Native 代码。 本地代码崩溃后,会在应用程序崩溃的同时发生。本地崩溃可发生在 Android 应用程序中的任何部分,比如,应用程序本地库、Android 系统库等等。大多数情况下,本地崩溃是由于访问无效内存、访问不合法指针、数组越界等问题引起的。 为了解决本地崩溃问题Android 提供了一些工具和技术。比如,使用 ndk-stack 工具可以解析本地崩溃日志。Android Studio 也提供了一些工具来分析应用程序崩溃的原因。同时,我们也可以在应用程序中添加自定义的日志跟踪信息,以便更好地了解应用程序的崩溃原因。 还有一些其他的技术可以使用,如使用 Google 的 Crashlytics 来跟踪应用程序的崩溃问题。这个平台可以帮助开发者收集和分析应用程序在用户设备上的崩溃信息,并彻底解决这些问题。此外,Android 还提供了一些实用工具和技术,如 ANR(Application Not Responding)错误处理器、Tracer for OpenGL ES 和 Traceview 示例等。 总之,Android Native CrashAndroid 系统中常见的崩溃问题之一。了解它的原因并采用适当的解决方案可以使得我们更好地保持我们的应用程序的稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值