-
-
初阶技能:Android 应用异常如何丰富线索
-
发表于: 2024-7-27 15:23 1857
-
在我们容器虚拟化产品开发过程中,时长会遇到某些应用无法启动或运行时异常崩溃的问题;让应用行为信息丰富,则能还原应用异常发生过程,对我们快速分析问题至关重要。
在这里需要用到以下几个开源工具:
- ByteHook: https://github.com/bytedance/bhook
- ShadowHook: https://github.com/bytedance/android-inline-hook
- Binderceptor: https://www.github.com/iofomo/binderceptor
01. 举个栗子
我们在做应用容器时,尝尝会遇到应用进程崩溃,通过adb
抓取的日志,却只有很少的几行日志,没有其他任何信息,因此通过一些简单的方式让应用行为的痕迹还原,是我们第一步要做的事情。
9402 9402 D AndroidRuntime: Shutting down VM 9402 9402 I Process : Sending signal. PID: 9402 SIG: 9
02. 打印退出痕迹
通过拦截native
函数,打印Java
层的异常退出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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
层的异常退出:
1 2 | void exit ( int status); int kill(pid_t pid, int sig); |
打印调用栈:
1 2 3 4 5 6 7 8 9 | // 非 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(); } |
输出结果:
1 2 3 4 5 6 7 8 | 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
语句中,异常时打印调用栈是常规操作,在这里可以拦截并打印栈日志信息。
1 2 3 4 5 6 7 8 9 | import java.lang; public class Throwable implements Serializable { private static native StackTraceElement[] nativeGetStackTrace(Object stackState); private static native Object nativeFillInStackTrace(); } |
04. 打印VM
异常调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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); } |
输出:
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
,我们可以替换掉输出打印更多内容。在这里要考虑应用自身也会设置的情况。
1 2 3 4 5 6 | Thread.setDefaultUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { // TODO } }); |
06. 打印异常传送
Android
在AMS
服务中增加了一个应用向服务提交异常的接口,我们通过android.reflect.Proxy
方式代理IActivityManager
的实例,打印输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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 { } } |
如输出结果为:
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
项目来完成。
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 code
与aidl
接口函数的对应关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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); } |
排序后可以得到输出的结果:
1 2 3 4 5 6 7 8 | 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
层方法的函数,打印调用信息。
1 2 3 4 | env->FindClass(...); env->GetMethodID(...); env->GetStaticMethodID(...); env->RegisterNatives(...); |
09. 打印访问文件行为
通过拦截open
系列函数(为什么是系列,因为libc.so
经过多年迭代,衍生出来很多函数,所以说Android
的兼容性让广大开发者开发Demo
容易,做稳定则难上加难),打印文件访问记录。
1 2 3 4 5 6 | 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, ...); ... |
至此,通过以上基本的方法,可以快速的增加应用运行轨迹信息。
若还不够,则需要针对应用内部调试,动态库加载的进阶方法深一步剖析,后面会给大家整理进阶技能。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课