初阶技能:Android 应用异常如何丰富线索

在我们容器虚拟化产品开发过程中,时常会遇到某些应用无法启动或运行时异常崩溃的问题;让应用行为信息丰富,则能还原应用异常发生过程,对我们快速分析问题至关重要。

在这里需要用到以下几个开源工具:

01. 举个栗子

我们在做应用容器时,尝尝会遇到应用进程崩溃,通过adb抓取的日志,却只有很少的几行日志,没有其他任何信息,因此通过一些简单的方式让应用行为的痕迹还原,是我们第一步要做的事情。

tex 复制代码
9402 9402 D AndroidRuntime: Shutting down VM  
9402 9402 I Process : Sending signal. PID: 9402 SIG: 9  

02. 打印退出痕迹

通过拦截native函数,打印Java层的异常退出:

java 复制代码
import android.os;  
  
public class Process {  
  
/**  
* Returns the identifier of this process, which can be used with  
* {@link #killProcess} and {@link #sendSignal}.  
*/  
public static final native void sendSignal(int pid, int signal);  
  
/**  
* @hide  
* Private impl for avoiding a log message... DO NOT USE without doing  
* your own log, or the Android Illuminati will find you some night and  
* beat you up.  
*/  
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)  
public static final native void sendSignalQuiet(int pid, int signal);  
  
}  
  
public class Runtime {  
private static native void nativeExit(int code);  
}  

通过拦截以下函数,打印Native层的异常退出:

c 复制代码
void exit(int status);  
int kill(pid_t pid, int sig);  

打印调用栈:

c 复制代码
// 非 JNI 环境获取从当前线程获取 JNIEnv(AttachCurrentThread)  
void jni_thread_dump(JNIEnv* env) {  
jclass jcls = env->FindClass("java/lang/Thread");  
if (!jcls) return;  
jmethodID jm = env->GetStaticMethodID(jcls, "dumpStack", "()V");  
if (!jm) return;  
env->CallStaticVoidMethod(jcls, jm);  
env->ExceptionClear();  
}  

输出结果:

java 复制代码
5511 5511 W System.err: java.lang.Exception: Stack trace  
5511 5511 W System.err: at java.lang.Thread.dumpStack(Thread.java:1527)  
5511 5511 W System.err: at android.os.Process.sendSignal(Native Method)  
5511 5511 W System.err: at android.os.Process.killProcess(Process.java:1295)  
5511 5511 W System.err: at com.android.internal.os.RuntimeInit$KillApplicationHandler.uncaughtException(RuntimeInit.java:207)  
5511 5511 W System.err: at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1073)  
5511 5511 W System.err: at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1068)  
5511 5511 W System.err: at java.lang.Thread.dispatchUncaughtException(Thread.java:2211)  

03. 追踪Throwable异常调用栈

在我们常用的try ... catch语句中,异常时打印调用栈是常规操作,在这里可以拦截并打印栈日志信息。

java 复制代码
import java.lang;  
  
public class Throwable implements Serializable {  
  
private static native StackTraceElement[] nativeGetStackTrace(Object stackState);  
  
private static native Object nativeFillInStackTrace();  
  
}  

04. 打印VM异常调用栈

java 复制代码
package dalvik.system;  
  
/**  
* Provides a limited interface to the Dalvik VM stack. This class is mostly  
* used for implementing security checks.  
*/  
public final class VMStack {  
/**  
* Retrieves the stack trace from the specified thread.  
*  
* @param t  
* thread of interest  
* @return an array of stack trace elements, or null if the thread  
* doesn't have a stack trace (e.g. because it exited)  
*/  
native public static StackTraceElement[] getThreadStackTrace(Thread t);  
  
/**  
* Retrieves an annotated stack trace from the specified thread.  
*  
* @param t  
* thread of interest  
* @return an array of annotated stack frames, or null if the thread  
* doesn't have a stack trace (e.g. because it exited)  
*/  
native public static AnnotatedStackTraceElement[] getAnnotatedThreadStackTrace(Thread t);  
  
/**  
* Retrieves a partial stack trace from the specified thread into  
* the provided array.  
*  
* @param t  
* thread of interest  
* @param stackTraceElements  
* preallocated array for use when only the top of stack is  
* desired. Unused elements will be filled with null values.  
* @return the number of elements filled  
*/  
native public static int fillStackTraceElements(Thread t, StackTraceElement[] stackTraceElements);  
}  

输出:

tex 复制代码
9402 9402 W stack : android.app.LoadedApk.makeApplication(LoadedApk.java:1554)  
9402 9402 W stack : android.app.ActivityThread.handleBindApplication(ActivityThread.java:8522)  
9402 9402 W stack : android.app.ActivityThread.access$2800(ActivityThread.java:311)  
9402 9402 W stack : android.app.ActivityThread$H.handleMessage(ActivityThread.java:2889)  
9402 9402 W stack : android.os.Handler.dispatchMessage(Handler.java:117)  
9402 9402 W stack : android.os.Looper.loopOnce(Looper.java:205)  
9402 9402 W stack : android.os.Looper.loop(Looper.java:293)  
9402 9402 W stack : android.app.ActivityThread.loopProcess(ActivityThread.java:9934)  
9402 9402 W stack : android.app.ActivityThread.main(ActivityThread.java:9923)  
9402 9402 W stack : java.lang.reflect.Method.invoke(Native Method)  
9402 9402 W stack : com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:586)  
9402 9402 W stack : com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1240)  
9402 9402 W stack : ohos.abilityshell.HarmonyLoader.tryLoadHarmony(HarmonyLoader.java:130)  
9402 9402 W stack : ohos.abilityshell.HarmonyApplication.tryLoadHarmony(HarmonyApplication.java:673)  
9402 9402 W stack : ohos.abilityshell.HarmonyApplication.attachBaseContext(HarmonyApplication.java:168)  
9402 9402 W stack : com.demo.app.DemoApp.attachBaseContext(SourceFile:1)  
9402 9402 W stack : android.app.Application.attach(Application.java:338)  
9402 9402 W stack : android.app.Instrumentation.newApplication(Instrumentation.java:1191)  
9402 9402 W stack : android.app.LoadedApk.makeApplication(LoadedApk.java:1546)  
9402 9402 W stack : android.app.ActivityThread.handleBindApplication(ActivityThread.java:8522)  
9402 9402 W stack : android.app.ActivityThread.access$2800(ActivityThread.java:311)  
9402 9402 W stack : android.app.ActivityThread$H.handleMessage(ActivityThread.java:2889)  
9402 9402 W stack : android.os.Handler.dispatchMessage(Handler.java:117)  
9402 9402 W stack : android.os.Looper.loopOnce(Looper.java:205)  
9402 9402 W stack : android.os.Looper.loop(Looper.java:293)  
9402 9402 W stack : android.app.ActivityThread.loopProcess(ActivityThread.java:9934)  
9402 9402 W stack : android.app.ActivityThread.main(ActivityThread.java:9923)  
9402 9402 W stack : java.lang.reflect.Method.invoke(Native Method)  
9402 9402 W stack : com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:586)  
9402 9402 W stack : com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1240)  

05. 打印异常信息

Android框架默认会为每个应用进程设置一个全局的异常Handler,我们可以替换掉输出打印更多内容。在这里要考虑应用自身也会设置的情况。

java 复制代码
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {  
@Override  
public void uncaughtException(Thread t, Throwable e) {  
// TODO  
}  
});  

06. 打印异常传送

AndroidAMS服务中增加了一个应用向服务提交异常的接口,我们通过android.reflect.Proxy方式代理IActivityManager的实例,打印输出。

java 复制代码
package android.app;  
  
// @source code: /frameworks/base/core/java/android/app/IActivityManager.aidl  
interface IActivityManager {  
void handleApplicationCrash(IBinder app, ApplicationErrorReport.ParcelableCrashInfo crashInfo);  
}  
  
// @source code: /frameworks/base/core/java/android/app/ApplicationErrorReport.java  
public class ApplicationErrorReport implements Parcelable {  
public static class CrashInfo {  
/**  
* Dump a CrashInfo instance to a Printer.  
*/  
public void dump(Printer pw, String prefix) {  
pw.println(prefix + "exceptionHandlerClassName: " + exceptionHandlerClassName);  
pw.println(prefix + "exceptionClassName: " + exceptionClassName);  
pw.println(prefix + "exceptionMessage: " + exceptionMessage);  
pw.println(prefix + "throwFileName: " + throwFileName);  
pw.println(prefix + "throwClassName: " + throwClassName);  
pw.println(prefix + "throwMethodName: " + throwMethodName);  
pw.println(prefix + "throwLineNumber: " + throwLineNumber);  
pw.println(prefix + "stackTrace: " + stackTrace);  
}  
}  
public static class ParcelableCrashInfo extends CrashInfo implements Parcelable {  
  
}  
}  

如输出结果为:

tex 复制代码
9402 9402 W Crash : exceptionClassName: java.lang.IllegalStateException  
9402 9402 W Crash : exceptionMessage: failed to attach Application, errorCode=30, errorInfo=bms service error, code is 8519969. 8519797  
9402 9402 W Crash : throwFileName: HarmonyLoader.java  
9402 9402 W Crash : throwClassName: ohos.abilityshell.HarmonyLoader  
9402 9402 W Crash : throwMethodName: tryLoadHarmony  
9402 9402 W Crash : throwLineNumber: 130  
9402 9402 W Crash : stackTrace: java.lang.RuntimeException: Unable to instantiate application com.demo.app.DemoApp package com.demo.app: java.lang.IllegalStateException: failed to attach Application, errorCode=30, errorInfo=bms service error, code is 8519969.  
9402 9402 W Crash : at android.app.LoadedApk.makeApplication(LoadedApk.java:1554)  
9402 9402 W Crash : at android.app.ActivityThread.handleBindApplication(ActivityThread.java:8522)  
9402 9402 W Crash : at android.app.ActivityThread.access$2800(ActivityThread.java:311)  
9402 9402 W Crash : at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2889)  
9402 9402 W Crash : at android.os.Handler.dispatchMessage(Handler.java:117)  
9402 9402 W Crash : at android.os.Looper.loopOnce(Looper.java:205)  
9402 9402 W Crash : at android.os.Looper.loop(Looper.java:293)  
9402 9402 W Crash : at android.app.ActivityThread.loopProcess(ActivityThread.java:9934)  
9402 9402 W Crash : at android.app.ActivityThread.main(ActivityThread.java:9923)  
9402 9402 W Crash : at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:586)  
9402 9402 W Crash : at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1240)  
9402 9402 W Crash : Caused by: java.lang.IllegalStateException: fail  

07. 打印Binder通信信息

可以打印应用访问了那些系统服务。可以通过Binderceptor项目来完成。

tex 复制代码
9402 9402 W Trace : Binder::tgt: 0x3887b52, 3, : android.content.pm.IPackageManager  
9402 9402 W Trace : Binder::tgt: 0xe45f2c2, 2, : android.os.IServiceManager  
9402 9402 W Trace : Binder::tgt: 0x76c2822, 56, : com.huawei.android.view.IHwWindowManager  
9402 9417 W Trace : Binder::tgt: 0x3887b52, 20, : android.content.pm.IPackageManager  
9402 9402 W Trace : Binder::tgt: 0x7db76a2, 30, : android.net.IConnectivityManager  
9402 9402 W Trace : Binder::tgt: 0x3887b52, 20, : android.content.pm.IPackageManager  
9402 9402 W Trace : Binder::tgt: 0x3887b52, 9, : android.content.pm.IPackageManager  
9402 9402 W Trace : Binder::tgt: 0x3887b52, 95, : android.content.pm.IPackageManager  
9402 9402 W Trace : Binder::tgt: 0x3887b52, 3, : android.content.pm.IPackageManager  
......  

binder codeaidl接口函数的对应关系:

java 复制代码
public static void printStub(String clsName) {  
android.util.Log.w(TAG, ">>>>>> " + clsName);  
try {  
Class<?> nmClassStub = Class.findClass(clsName + "$Stub");  
Method[] mm = nmClassStub.getMethods();  
  
ArrayList<TRANSACTION_Item> list = new ArrayList<TRANSACTION_Item>();  
for (Method m : mm) {  
if (TextUtils.equals(m.getName(), "asBinder")) continue;  
try {  
funcName = m.toGenericString();  
funcName = funcName.replace("abstract ", "");  
funcName = funcName.replace(clsName + ".", "");  
  
fieldName = "TRANSACTION_" + m.getName();  
Field f = nmClassStub.getDeclaredField(fieldName);  
f.setAccessible(true);  
int val = f.getInt(null);  
// android.util.Log.w(TAG, fieldName + " " + val);  
list.add(new TRANSACTION_Item(val, funcName, fieldName));  
} catch (Exception e) {  
android.util.Log.e(TAG, e.toString());  
}  
}  
Collections.sort(list, new TRANSACTION_Comparator());  
} catch (Exception e) {  
android.util.Log.e(TAG, e.toString());  
}  
android.util.Log.w(TAG, "<<<<<< " + clsName);  
}  

排序后可以得到输出的结果:

java 复制代码
package android.content.pm;  
  
interface IPackageManager {  
public void checkPackageStartable(java.lang.String,int) throws android.os.RemoteException;// 1  
public boolean isPackageAvailable(java.lang.String,int) throws android.os.RemoteException;// 2  
public android.content.pm.PackageInfo getPackageInfo(java.lang.String,int,int) throws android.os.RemoteException;// 3  
public android.content.pm.PackageInfo getPackageInfoVersioned(android.content.pm.VersionedPackage,int,int) throws android.os.RemoteException;// 4  
}  

08. 打印JNI调用轨迹

通过拦截JNIEnv层提供调用Java层方法的函数,打印调用信息。

c++ 复制代码
env->FindClass(...);  
env->GetMethodID(...);  
env->GetStaticMethodID(...);  
env->RegisterNatives(...);  

09. 打印访问文件行为

通过拦截open系列函数(为什么是系列,因为libc.so经过多年迭代,衍生出来很多函数,所以说Android的兼容性让广大开发者开发Demo容易,做稳定则难上加难),打印文件访问记录。

c 复制代码
int open(const char *fileName, int flags, ...);  
int open2(const char *fileName, int flags, ...);  
int _open(const char *fileName, int flags, ...);  
int openat(int dirfd, const char *fileName, int flags, ...);  
int _openat(int dirfd, const char *fileName, int flags, ...);  
...  

至此,通过以上基本的方法,可以快速的增加应用运行轨迹信息。

若还不够,则需要针对应用内部调试,动态库加载的进阶方法深一步剖析,后面会给大家整理进阶技能。

相关推荐
云中谷7 小时前
Golang 神器!go-decorator 一行注释搞定装饰器,v0.22版本发布
go·敏捷开发
陈哥聊测试15 小时前
软件格局在变,谁能扛起国产替代的大旗?
安全·程序员·产品
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭1 天前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
少年姜太公1 天前
从零开始详解js中的this(下)
前端·javascript·程序员
凌虚1 天前
Kubernetes APF(API 优先级和公平调度)简介
后端·程序员·kubernetes
小华同学ai2 天前
ShowDoc:Star12.3k,福利项目,个人小团队的在线文档“简单、易用、轻量化”还专门针对API文档、技术文档做了优化
前端·程序员·github
catmes3 天前
使用docker compose安装gitlab
运维·docker·容器·gitlab·敏捷开发·devops
小青鱼4 天前
AI编程-Cursor从入门到精通系列之常用概念及解释(二)
人工智能·程序员
捡田螺的小男孩4 天前
参数校验的十个建议!收藏好,别再给测试机会提bug~
java·后端·程序员
哔哩哔哩技术5 天前
B站装机系统实践:从初创到规模化的演进
前端·程序员