業精于勤荒于嬉,寫文章練習表達能力,寫代碼練習基本工。
場景:
項目中調用了第三方sdk中的方法,此方法中關鍵日志被屏蔽,需要跟蹤日志方可分析失敗原因。
問題描述:
- 檢視jar包的class,對應失敗的分支均有log,但log開關設定為false。
- 裝置定制的os關閉了斷點調試的功能。
- 和sdk提供商溝通後,暫時沒有答複什麼時候提供新的sdk(希望對方打開日志開關)
/**
由于内容涉密,我用僞代碼代替
*/
public class SDKImpl extends SDK {
public Info getXXX() {
try {
/*此處省略*/
if (條件1) {
Logger.t( TAG, "ERROR 1 !!");
return null;
} else {
/*此處省略*/
if (條件2) {
Logger.t( TAG, "ERROR 2 !!");
return null;
} else {
if (條件3) {
Logger.t(TAG, "ERROR 3 !!");
return null;
} else if (條件4) {
if (條件5) {
Logger.t(TAG, "ERROR 4 !!");
return null;
} else {
if (條件6) {
Logger.t(TAG, "ERROR 5 !!");
return null;
} else {
//成功
return new Info();
}
}
} else {
Logger.t(TAG, "ERROR 6 !!");
return null;
}
}
}
} catch (Exception e) {
Logger.t(TAG, "ERROR 7 !!");
return null;
}
}
}
如果日志能列印,就知道具體在哪裡失敗了,我們看一下Logger.t()方法。
public class Logger {
//如果調用這個,那麼就直接能看到log
public static void d(String tag, String log) {
Log.d(tag, log);
}
public static void t(String tag, String log) {
if (loggable()) {
d(tag, log);
}
}
private static boolean loggable() {
return false;
}
}
解決方案:
這種情況下,讓我們提供失敗原因,讓我先冷靜幾分鐘再說。
還好,想到了兩個解決方法,在客戶的sdk暫時無法更新的時間裡,讓我們把思緒放飛(當然是事後這樣調侃,客戶着急上火,可沒心思去想這些,肯定是,你不給打開日志,我分析不了!!!)。
- 使用熱修複方案的一種(類加載,替換)。
- Hook,實作SDKImplProxy,劫持并替換sdk中的SDKImpl對象。
類替換
在我們的代碼裡面寫一個一模一樣的Logger類(包括包名),僅僅把loggable()的傳回值改為false,讓PathClassLoader優先加載我們的Logger。
步驟1:編寫Logger (注意包名也和SDK中一模一樣)
private static boolean loggable() {
return true;
}
步驟2:build後,找到class,生成dex,導入sd卡根目錄
dx --dex --output=patch.dex com/…/Logger.class
步驟3:在application中,利用雙親委派加載dex中的Logger.class,忽略sdk中的。
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//越早執行這個,越好。
HotfixUtils.installPatch(this,new File("/sdcard/patch.dex"));
}
}
然後重新開機app,成功列印了錯誤日志,沒有給出HotfixUtils的源碼,因為比較繁瑣,針對不通android版本,需要做相容,我是直接拿來其他人的代碼運作成功的,原理大家在網上查,都差不多,利用反射最終修改dexElements數組,把更新檔中的dex放在第一個。
Hook
等啊等,客戶的sdk還是沒有發過來,就一個變量值,讓人望眼欲穿,在用熱修複實作後,成功的列印了,複現問題後,也知道最終是條件3,可以答複了,這個時間,浏覽着客戶的SDK代碼,SDK這個類中的單例讓我眼前一亮。
public abstract class SDK {
private static SDK instance;
public SDK() {
}
public static SDK getInstance() {
if (instance == null) {
//這裡可以作為hook點
instance = new SDKImpl();
}
return instance;
}
}
符合hook的适用場景,我開始嘗試如下:
建立代理類
由于SDK不是接口類型,我們使用靜态代理
public class SDKImplProxy extends SDK {
/*其他方法省略,一律拷貝*/
//隻修改get方法,把 Logger.t,全部換成沒有開關限制的 Logger.d
public Info getXXX() {
/*此處省略*/
if (條件1) {
Logger.d( TAG, "ERROR 1 !!");
return null;
}
/*後面全部替換成Logger.d*/
}
}
進行反射,替換
Class<?> buspasssdkClass = Class.forName("com.xxx.SDK");
Field mInstanceField = sdkClass.getDeclaredField("instance");
mInstanceField.setAccessible(true);
SDKImplProxy proxy = new SDKImplProxy();
mInstanceField.set(null,proxy);
一定要在SDK.getInstance() 之前完成hook
重新開機,運作成功拿到日志。
總結
有時候遇到問題,第一時間可能想不到最适合的方法,在技術上無法立馬得到改進,或者要去查資料研究的時候,首先還得學習如何溝通,給自己創造時間或空間,去思考,驗證解決方案。