Android NDK入门教程之快速定位Crash问题

Android NDK入门教程之快速定位Crash问题,第1张

文章目录 首先制造一个 so crash问题addr2lineaddr2line工具位置addr2line命令如下:分析crash logso strip欢迎联系、指正、批评

首先制造一个 so crash问题
public class MainActivity extends AppCompatActivity {
    // Used to load the 'ndkcrashdemo' library on application startup.
    static {
        System.loadLibrary("ndkcrashdemo");
    }
    private ActivityMainBinding binding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }
    /**
     * A native method that is implemented by the 'ndkcrashdemo' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

故意空指针

#include 
#include 
extern "C" JNIEXPORT jstring JNICALL
Java_com_cy_ndkcrashdemo_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello=NULL;//故意空指针
    hello.c_str();
    return env->NewStringUTF(hello.c_str());
}

运行到真机,发生crash,部分LOG如图:

发现并不能看出代码哪里有毛病

工欲善其事必先利其器

addr2line
addr2line translates addresses into file names and line numbers. 
Given an address in an executable or an offset in a section of a relocatable object, 
it uses the debugging information to figure out which file name and line number are associated with it.

addr2line工具是一个可以将指令的地址和可执行映像转换成文件名、函数名和源代码行数的工具。
一般适用于 debug 版本或带有 symbol 信息的库。

addr2line工具位置

addr2line工具在NDK 里的路径如下(注意:每个版本都不一样)

Windows:  
32位:D:\AndroidSDK\ndk\21.4.7075529\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\
arm-linux-androideabi-addr2line.exe  
64位:D:\AndroidSDK\ndk\21.4.7075529\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\bin\
aarch64-linux-android-addr2line.exe  
addr2line命令如下:
The options are:
  @<file>                Read options from <file>
  -a --addresses         Show addresses//显示地址
  -b --target=<bfdname>  Set the binary file format
  -e --exe=<executable>  Set the input file name (default is a.out)//设置so库的路径
  -i --inlines           Unwind inlined functions
  -j --section=<name>    Read section-relative offsets instead of addresses
  -p --pretty-print      Make the output easier to read for humans//设置输出信息可读性更强
  -s --basenames         Strip directory names
  -f --functions         Show function names//显示函数名称
  -C --demangle[=style]  Demangle function names
  -h --help              Display this information
  -v --version           Display the program's version
分析crash log
com.cy.ndkcrashdemo A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 22618 (cy.ndkcrashdemo), pid 22618 (cy.ndkcrashdemo)
DEBUG: Softversion: PD2073B_A_1.8.15
DEBUG: Time: 2022-04-20 15:34:49
DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
DEBUG: Build fingerprint: 'vivo/PD2073/PD2073:11/RP1A.200720.012/compiler1228233519:user/release-keys'
DEBUG: Revision: '0'
DEBUG: ABI: 'arm64'
DEBUG: Timestamp: 2022-04-20 15:34:49+0800
DEBUG: pid: 22618, tid: 22618, name: cy.ndkcrashdemo  >>> com.cy.ndkcrashdemo <<<
DEBUG: uid: 10252
DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
DEBUG: Cause: null pointer dereference
DEBUG:     x0  0000000000000000  x1  0000000000000000  x2  0000000000000018  x3  00000076eecbf000
DEBUG:     x4  0000000000000000  x5  b4000076ee95ac00  x6  00000076497d53af  x7  000000000000001b
DEBUG:     x8  0**************1  x9  ad1a1eb4209bd757  x10 0000000000430000  x11 00000000069d2cd0
DEBUG:     x12 0000000000371314  x13 00000000006476c0  x14 000000000051d6c0  x15 ffffffffffffffff
DEBUG:     x16 000000765387ce98  x17 00000076ec1cf4b0  x18 00000076eeec6000  x19 b4000076ee95ac00
DEBUG:     x20 0000000000000000  x21 b4000076ee95ac00  x22 00000076eecbf000  x23 b4000076ee95acb8
DEBUG:     x24 0000007667d48478  x25 00000076eecbf000  x26 000000000000000b  x27 0000000000000002
DEBUG:     x28 0000007fdeb3db00  x29 0000007fdeb3da10
DEBUG:     lr  000000765385757c  sp  0000007fdeb3da00  pc  00000076ec1cf4c0  pst 0000000080001000
DEBUG: backtrace:
DEBUG:       #00 pc 00000000000894c0  /apex/com.android.runtime/lib64/bionic/libc.so (strlen_default+16) (BuildId: d010ec9d0da07ff241689a4e9691c733)
DEBUG:       #01 pc 000000000000f578  /data/app/~~ipBdrIwT8qi72hDEkPx-ZQ==/com.cy.ndkcrashdemo-bwJhACJJBkJfPLjIt-tS0Q==/lib/arm64/libndkcrashdemo.so (std::__ndk1::char_traits<char>::length(char const*)+20) (BuildId: 34e2ac7e22782a65c51f760e5d9b4376d0cbea1d)
DEBUG:       #02 pc 000000000000f064  /data/app/~~ipBdrIwT8qi72hDEkPx-ZQ==/com.cy.ndkcrashdemo-bwJhACJJBkJfPLjIt-tS0Q==/lib/arm64/libndkcrashdemo.so (std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >::basic_string<std::nullptr_t>(char const*)+48) (BuildId: 34e2ac7e22782a65c51f760e5d9b4376d0cbea1d)
DEBUG:       #03 pc 000000000000efa4  /data/app/~~ipBdrIwT8qi72hDEkPx-ZQ==/com.cy.ndkcrashdemo-bwJhACJJBkJfPLjIt-tS0Q==/lib/arm64/libndkcrashdemo.so (Java_com_cy_ndkcrashdemo_MainActivity_stringFromJNI+56) (BuildId: 34e2ac7e22782a65c51f760e5d9b4376d0cbea1d)
DEBUG:       #04 pc 000000000013ced4  /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+148) (BuildId: 5aeb6fe6030b3ee85ca5c14f3f2f06e9)
DEBUG:       #05 pc 0000000000133564  /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+548) (BuildId: 5aeb6fe6030b3ee85ca5c14f3f2f06e9)
DEBUG:       #06 pc 0000000000197e94  /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+204) (BuildId: 5aeb6fe6030b3ee85ca5c14f3f2f06e9)
DEBUG:       #07 pc 000000000030347c  /apex/com.android.art/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+376) (BuildId: 5aeb6fe6030b3ee85ca5c14f3f2f06e9)
DEBUG:       #84 pc 0000000000529d10  /apex/com.android.art/lib64/libart.so (art::JValue art::InvokeWithVarArgs<_jmethodID*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, std::__va_list)+92) (BuildId: 5aeb6fe6030b3ee85ca5c14f3f2f06e9)
DEBUG:       #85 pc 000000000041bd84  /apex/com.android.art/lib64/libart.so (art::JNI<true>::CallStaticVoidMethodV(_JNIEnv*, _jclass*, _jmethodID*, std::__va_list)+656) (BuildId: 5aeb6fe6030b3ee85ca5c14f3f2f06e9)
DEBUG:       #86 pc 0000000000099434  /system/lib64/libandroid_runtime.so (_JNIEnv::CallStaticVoidMethod(_jclass*, _jmethodID*, ...)+124) (BuildId: c8f8eb3a06894fb8e5258f8c2f08e801)
DEBUG:       #87 pc 00000000000a0ca0  /system/lib64/libandroid_runtime.so (android::AndroidRuntime::start(char const*, android::Vector<android::String8> const&, bool)+836) (BuildId: c8f8eb3a06894fb8e5258f8c2f08e801)
DEBUG:       #88 pc 0000000000003564  /system/bin/app_process64 (main+1308) (BuildId: a4535eefb34c582385a2cf7c5548aea0)
DEBUG:       #89 pc 0000000000088188  /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+108) (BuildId: d010ec9d0da07ff241689a4e9691c733)

Cause: null pointer dereference 告诉我们出现了空指针
backtrace 以下,显示了程序调用过程
可以看到自己写的JNI方法Java_com_cy_ndkcrashdemo_MainActivity_stringFromJNI+56,这个函数对应的pc值(函数编译地址)是000000000000efa4

根据 .so 是 32 位还是 64 位选择对应的 addr2line 工具
使用方式如下:

addr2line -f -p -e so文件路径  报错函数对应的pc值

打开命令行窗口,进入aarch64-linux-android-addr2line(这里举例用的64位)所在目录,输入如下命令:

D:\AndroidSDK\ndk\21.4.7075529\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\bin>
aarch64-linux-android-addr2line -f -p -e 
E:\AndroidStudioWorkspace\NDKCrashDemo\app\build\intermediates\merged_native_libs\
debug\out\lib\arm64-v8a\libndkcrashdemo.so 000000000000efa4

输出如下:

Java_com_cy_ndkcrashdemo_MainActivity_stringFromJNI at
E:/AndroidStudioWorkspace/NDKCrashDemo/app/src/main/cpp/native-lib.cpp:8

指出了crash发生在哪个文件的哪个函数,以及行号

你也可以将addr2line工具所在目录添加到系统环境变量path中,在命令行窗口中不用再进入addr2line所在目录,也可执行

so strip

一个完整的 so 由C/C++代码编译后的输出和debug信息组成,这些debug信息会记录 so
中所有方法的对照表,就是方法名和其编译地址的对应表,也叫做符号表,这种 so 也叫做未 strip 的,通常体积会比较大。

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

如下可以看到strip之前和之后的大小对比

strip的so

strip的so

如果debug信息丢了,无法再定位代码。

所以,这些debug信息尤为重要,是我们分析native crash问题的关键信息,那么我们在编译 so
时候务必保留一份未被strip的so 或者剥离后的符号表信息,以供后面问题分析,并且每次编译的so
都需要保存,一旦产生代码修改重新编译,那么修改前后的符号表信息会无法对应,也无法进行分析。

如下图所示,merged_native_libs下是含有debug信息的so,striped_native_libs下是去除了debug信息的so。
注意:不同版本死丢丢目录不一致。


所以开发so库的时候,每次编译之后,如果需要提供给别人使用,我们需要保留一份未strip的so库和JNI代码strip的so库提供给别人使用,当出现native crash的时候,我们可以通过未strip的so库定位strip的so库发生crash的代码位置,因为未strip已strip的so库的函数的编译地址是一致的,strip去除的只是debug信息。

欢迎联系、指正、批评

Github:https://github.com/AnJiaoDe

CSDN:https://blog.csdn.net/confusing_awakening

OpenCV入门教程:https://blog.csdn.net/confusing_awakening/article/details/113372425

ffmpeg入门教程:https://blog.csdn.net/confusing_awakening/article/details/102007792

微信公众号

QQ群

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/992235.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存