在我們容器虛擬化產品開發過程中,時長會遇到某些應用無法啟動或運行時異常崩潰的問題;讓應用行為信息豐富,則能還原應用異常發生過程,對我們快速分析問題至關重要。 ...
在我們容器虛擬化產品開發過程中,時長會遇到某些應用無法啟動或運行時異常崩潰的問題;讓應用行為信息豐富,則能還原應用異常發生過程,對我們快速分析問題至關重要。
在這裡需要用到以下幾個開源工具:
- 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
層的異常退出:
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
層的異常退出:
void exit(int status);
int kill(pid_t pid, int sig);
列印調用棧:
// 非 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();
}
輸出結果:
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
語句中,異常時列印調用棧是常規操作,在這裡可以攔截並列印棧日誌信息。
import java.lang;
public class Throwable implements Serializable {
private static native StackTraceElement[] nativeGetStackTrace(Object stackState);
private static native Object nativeFillInStackTrace();
}
04. 列印VM
異常調用棧
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
,我們可以替換掉輸出列印更多內容。在這裡要考慮應用自身也會設置的情況。
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// TODO
}
});
06. 列印異常傳送
Android
在AMS
服務中增加了一個應用向服務提交異常的介面,我們通過android.reflect.Proxy
方式代理IActivityManager
的實例,列印輸出。
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
介面函數的對應關係:
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);
}
排序後可以得到輸出的結果:
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
層方法的函數,列印調用信息。
env->FindClass(...);
env->GetMethodID(...);
env->GetStaticMethodID(...);
env->RegisterNatives(...);
09. 列印訪問文件行為
通過攔截open
系列函數(為什麼是系列,因為libc.so
經過多年迭代,衍生出來很多函數,所以說Android
的相容性讓廣大開發者開發Demo
容易,做穩定則難上加難),列印文件訪問記錄。
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, ...);
...
至此,通過以上基本的方法,可以快速的增加應用運行軌跡信息。
若還不夠,則需要針對應用內部調試,動態庫載入的進階方法深一步剖析,後面會給大家整理進階技能。