Android so库开发——使用addr2line查看so异常信息

Posted c小旭

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android so库开发——使用addr2line查看so异常信息相关的知识,希望对你有一定的参考价值。

        在NDK开发中经常会出现应用Crash的情况,而JNI层的报错信息,不像Java层报错信息那样可以直接在日志中看到错误的行数,JNI层中出现的错误直接看根本定位不到错误的位置。通常来说,JNI报的基本都是堆栈信息,需要NDK的一些工具进行地址转换,转换后即可看到错误的位置。addr2line就是这些地址转换的工具。

一、环境配置

1、工具路径

        路径一般都在 Sdk/ndk 下

工具选择

         可以看到在 toolchains 下有多个选择,这应该是根据需要解析的 so 库类型进行选择,这里我选择了第一个。

bin 路径

 配置环境变量

        将上面的 bin 路径复制到环境变量中

环境验证

在 bin 目录下输入如下命令

aarch64-linux-android-addr2line --version

运行结果,表示环境变量配置成功

二、异常分析

1、错误查看

        错误信息,主要看 backtrace 部分

DEBUG: backtrace:
DEBUG:     #00 pc 0000000000000000  <unknown>
DEBUG:     #01 pc 00000000000307cc  /system/lib64/libxiaoxu.so (offset 0x15000) (doIp_handle_vehicle_identifica)
DEBUG:     #02 pc 000000000003278c  /system/lib64/libxiaoxu.so (offset +0x18000) (posix_timers_timeout_func+80)
DEBUG:     #03 pc 000000000002bcf0  /system/lib64/libc.so (__timer_thread_start(void*)+56)
DEBUG:     #04 pc 000000000008192c  /system/lib64/libc.so (__pthread_start(void*)+36)
DEBUG:     #05 pc 0000000000023478  /system/lib64/libc.so (__start_thread+68)

2、提取错误信息

        #03、#04 和 #05 为 libc.so 异常,这是系统的库我们不用管。#01 和 #02 为 libxiaoxu.so 异常,我们主要分析这个。

        pc 后面的十六进制数就是对应 so 库中的具体错误信息地址,我们要做的主要是将这个地址信息转换成 so 库中代码的位置。

三、使用addr2line

1、转换地址的命令

aarch64-linux-android-addr2line -C -f -e $SOPATH $Address
# 如果前面选择 arm-linux-androideabi-4.9,则用下面命令
arm-linux-androideabi-addr2line -C -f -e $SOPATH $Address
-C -f               //打印错误行数所在的函数名称
-e                   //打印错误地址的对应路径及行数
$SOPATH    //so库路径
$Address     //需要转换的堆栈错误信息地址,可以添加多个,但是中间要用空格隔开

2、 异常分析

so库路径

# Android项目三方so库
/app/src/main/jniLibs/

# 自己生成的so库
/app/build/intermediates/cmake/debug/obj/

        这里我将上面路径下的so库另存到自己新建的文件夹下 F:\\sotest\\libxiaoxu.so,这里改变 so 路径或修改 so 文件名都没影响。

执行命令

        注意要在 aarch64-linux-android-addr2line 工具的 bin 路径下执行,如果没进入到 bin 路径下,命令前面要带上该路径。

aarch64-linux-android-addr2line -C -f -e F:\\sotest\\libxiaoxu.so 00000000000307cc

        运行结果:

Java_com_xiaoxu_sotestdemo_SotestUtil_getUsetName
F:\\sotest\\SoTestDemo\\app\\src\\main\\cpp/native_lib.cpp:33

        点击一下转换后的地址就可以跳转到错误的位置了。 

3、其他问题

1)提示:不是内部或外部命令,也不是可运行的程序或批处理文件。

环境变量没有配置好,注意选择的bin路径要匹配环境变量和命令中的路径。

2)输出"??0"

注意地址后面对应的是哪个so库,转换的地址显示为 " ? ? 0 ":一个 so 库的错误地址信息与so库不匹配;二是两个错误位置在两个 so 库中的堆栈地址相同。

3)输出"??:?"

说明 so 库不是 debug 版本的看不了。这时候就要修改Applicatiao.mk文件添加下面代码:

APP_OPTIM := debug

 4)异常提示:File format not recognized

使用 arm-linux-androideabi-4.9 路径下的 bin 再试一下。注意环境变量和命令中的路径都要修改。

参考文档:

Android studio中NDK开发(四)——使用addr2line分析Crash日志

Jni:05.ndk调试

so库报错根据地址查看,addr2line定位出错位置

Android JNI开发二: SO库的使用

目录

        Android JNI开发一: 如何生成SO库

        Android JNI开发二: SO库的使用​​​​​​​

本篇主要介绍如何在普通的安卓工程中使用编译好的SO库文件。首先,新建一个普通的安卓项目工程,我的工程名叫SOTest。项目工程建好后,就将SO库引入到工程中。

2.1 将SO库引入工程中

在不同的CPU架构下,会被编译成不同的SO库,所以我们需要将整个的lib文件拷贝至我们的项目工程中。SO库需要拷贝到app目录下,路径一定要正确,否则有可能出现因为路径不对而加载不到SO库。

2.2 配置SO库的路径

在build.gradle文件中添加so库的路径配置和依赖配置,如下图所示:

配置代码:

sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
  }

 implementation fileTree(include: ['*.jar'], dir: 'libs')

至此,SO库的引入和配置都好了,特别要注意SO库拷贝的目录和配置的路径一定要对应,否则会出现加载不到SO库的。

2.3 引入 JNITools 文件

SO库文件是一个动态库,无法打开,所以我们看不到里面有些什么,那么就有一个问题,如果我们把自己编译好的SO库给别人去用,别人又看不到SO里面的内容,那他怎么调SO库里面的方法呢?所以我们还需要提供一个配套的头文件给使用SO库的开发人员。这个JNITools就是头文件,当然你也可以取其他的名字。这个JNITools在我们上一节已经出现过了,这里还是把代码贴出来:

//这个包名要和原来创建SO库文件工程的包名一样,而不是现在新工程的包名
//因为SO库里面的包名也是 package com.example.jnidemo
package com.example.jnidemo;

public class JNITools {

    static {
        System.loadLibrary("native-lib");
    }
    public static native int addNumber(int a, int b);
    public static native int subNumber(int a, int b);
    public static native int mulNumber(int a, int b);
    public static native int divNumber(int a, int b);
}

在com.example目录下再新建一个包,包名为:jnidemo,然后再引入JNITools文件。

2.4 调用SO文件中的代码

这个就跟上一节讲的安卓代码是完全一样的,这里还是把代码再重复贴一遍。

(1) activity_main.xml 里添加布局代码:

package com.example.jnidemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.example.jnidemo.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btnAdd,btnSub,btnMul,btnDiv;
    private EditText inputA,inputB;
    private TextView tvResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setupView();
        addListener();
    }

    private void addListener() {
        btnAdd.setOnClickListener(this);
        btnDiv.setOnClickListener(this);
        btnMul.setOnClickListener(this);
        btnSub.setOnClickListener(this);
    }

    private void setupView() {
        btnAdd=this.findViewById(R.id.add);
        btnDiv=this.findViewById(R.id.div);
        btnMul=this.findViewById(R.id.mul);
        btnSub=this.findViewById(R.id.sub);

        inputA=this.findViewById(R.id.inputa);
        inputB=this.findViewById(R.id.inputb);

        tvResult=this.findViewById(R.id.result);
    }

    @Override
    public void onClick(View v) {
        double result=0;
        String strA=inputA.getText().toString();
        String strB=inputB.getText().toString();
        int a=Integer.parseInt(strA);
        int b=Integer.parseInt(strB);
        switch (v.getId()){
            case R.id.add:
                result=JNITools.addNumber(a,b);
                break;
            case R.id.div:
                result=JNITools.divNumber(a,b);
                break;
            case R.id.mul:
                result=JNITools.mulNumber(a,b);
                break;
            case R.id.sub:
                result=JNITools.subNumber(a,b);
                break;
        }
        tvResult.setText(""+result);
    }
}

(2) string.xml 里添加代码:

<resources>
    <string name="app_name">JniDemo</string>
    <string name="add">相加</string>
    <string name="sub">相减</string>
    <string name="mul">相乘</string>
    <string name="div">相除</string>
</resources>

(3) MainActivity 里添加代码:

package com.example.jnidemo;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
    private Button btnAdd,btnSub,btnMul,btnDiv;
    private EditText inputA,inputB;
    private TextView tvResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setupView();
        addListener();
    }

    private void addListener() {
        btnAdd.setOnClickListener(this);
        btnDiv.setOnClickListener(this);
        btnMul.setOnClickListener(this);
        btnSub.setOnClickListener(this);
    }
    private void setupView() {
        btnAdd=this.findViewById(R.id.add);
        btnDiv=this.findViewById(R.id.div);
        btnMul=this.findViewById(R.id.mul);
        btnSub=this.findViewById(R.id.sub);

        inputA=this.findViewById(R.id.inputa);
        inputB=this.findViewById(R.id.inputb);

        tvResult=this.findViewById(R.id.result);
    }
    @Override
    public void onClick(View v) {
        double result=0;
        String strA=inputA.getText().toString();
        String strB=inputB.getText().toString();
        int a=Integer.parseInt(strA);
        int b=Integer.parseInt(strB);
      
        //这里就是通过JNI调用C语言的代码
        switch (v.getId()){
            case R.id.add:
                result=JNITools.addNumber(a,b);
                break;
            case R.id.div:
                result=JNITools.divNumber(a,b);
                break;
            case R.id.mul:
                result=JNITools.mulNumber(a,b);
                break;
            case R.id.sub:
                result=JNITools.subNumber(a,b);
                break;
        }
        tvResult.setText(""+result);
    }
}

(4) 运行效果

至此,就完整的介绍了如何生成SO库文件,以及如何使用SO库文件,你学会了么。

以上是关于Android so库开发——使用addr2line查看so异常信息的主要内容,如果未能解决你的问题,请参考以下文章

我的Android进阶之旅如何在Android Studio开发NDK的时候,通过addr2line来定位出错代码的位置

我的Android进阶之旅如何在Android Studio开发NDK的时候,通过addr2line来定位出错代码的位置

so 动态库崩溃问题定位(addr2line与objdump)

我的Android进阶之旅如何在Android Studio开发NDK的时候,通过addr2line或者ndk-stack来定位出错代码的位置

Android NDK 开发NDK C/C++ 代码崩溃调试 - Tombstone 报错信息日志文件分析 ( 使用 addr2line 命令行工具查找动态库中的报错代码位置 )

Android NDK 开发NDK C/C++ 代码崩溃调试 - Tombstone 报错信息日志文件分析 ( 使用 addr2line 命令行工具查找动态库中的报错代码位置 )