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日志
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 命令行工具查找动态库中的报错代码位置 )