初階技能:Android 應用異常如何豐富線索

来源:https://www.cnblogs.com/iofomo/p/18327072
-Advertisement-
Play Games

在我們容器虛擬化產品開發過程中,時長會遇到某些應用無法啟動或運行時異常崩潰的問題;讓應用行為信息豐富,則能還原應用異常發生過程,對我們快速分析問題至關重要。 ...


在我們容器虛擬化產品開發過程中,時長會遇到某些應用無法啟動或運行時異常崩潰的問題;讓應用行為信息豐富,則能還原應用異常發生過程,對我們快速分析問題至關重要。

image

在這裡需要用到以下幾個開源工具:

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. 列印異常傳送

AndroidAMS服務中增加了一個應用向服務提交異常的介面,我們通過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 codeaidl介面函數的對應關係:

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, ...);
...

至此,通過以上基本的方法,可以快速的增加應用運行軌跡信息。

若還不夠,則需要針對應用內部調試,動態庫載入的進階方法深一步剖析,後面會給大家整理進階技能。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...