android動態載入dex
A. Android類載入機制
Android手寫熱修復(一)--ClassLoader
我們平時編寫的 .java 文件不是可執行文件,需要先編譯成 .class 文件才可以被虛擬機執行。所謂類載入是指通過 類載入器 把class文件載入到虛擬機的內存空間,具體來說是方法區。類通常是按需載入,即第一次使用該類時才載入。
首先,Java與Android都是把類載入到虛擬機內存中,然後由虛擬機轉換成設備識別的機器碼。但是由於二者使用的虛擬機不同,所以在類載入方面也是有所區別的。Java的虛擬機是JVM,Android的虛擬機是dalvik/art(5.0以後虛擬機是art,是對dalvik的一種升級)。 Java虛擬機運行的是class文件,而Android 虛擬機運行的是dex文件。 dex其實是class文件的集合,是對class文件優化的產物,是為了避免出現重復的class。
從上面的講解中,我們已經知道我們平時寫的類是被 類載入器 載入盡虛擬機內存才能運行。下面就通過Framework源碼來為大家講解Android中最主要的5個類載入器。
在Activity做個簡單驗證:
結果:
可以看出系統類由BootClassLoader載入,apk中的類由PathClassLoader載入,PathClassLoader的父類載入器是BootClassLoader。如果暫時不能理解父類載入器是什麼,沒關系,後面講雙親委託機制的時候會理解的。
下面的源碼解析基於 Android SDK API28 ,這幾個類載入器(除了ClassLoader)沒辦法直接在AS上查看源碼,AS搜索到的是反編譯的class的內容,是不可信的,為大家推薦一個在線工具查看, 在線查看Android Framework源碼 。
用來載入本地文件系統上的文件或目錄,通常是用來載入apk中我們自己寫的類,而像 Activity.class 這種系統的類不是由它載入。注意:這里,並不像很多網上文章說的那樣只能載入apk,本地的其他目錄的文件也是可以的,這一點我會在後面驗證說明。
也是被用來載入 jar 、apk、dex,通常用來載入未安裝到應用中的文件。注意,它需要一個應用私有的可寫的目錄來存放優化後的dex文件。千萬不要選擇外部存儲路徑,因為這樣可能會導致你的應用遭到注入攻擊。
關於dex文件優化,可能很多人還是不理解,水平有限,我簡單解釋一下,
構造器參數解釋:
關於optimizedDirectory:
1、這是dex優化後的路徑,它必須是一個應用私有的可寫的目錄否則會存在注入攻擊的風險;
2、這個參數在API 26(8.0)之前是有值的,之後的話,這個參數已經沒有影響了,因為在調用父構造器的時候這個參數始終為null,也就是說Android 8.0 以後DexClassLoader和PathClassLoader基本一樣的來;
3、在載入app的時候,apk內部的dex已經執行過優化了,優化之後放在系統目錄/data/dalvik-cache下。
這個構造器的關鍵是初始化了一個DexPathList對象,這個是後面載入class的關鍵類。
這個構造方法等關鍵是通過 makeDexElements() 方法來獲取Element數組,這個Element數組非常關鍵,後面查找class就會用到它,也是熱修復的關鍵點之一。
splitDexPath(dexPath) 方法是把dexPath目錄下的所有文件轉換成一個File集合,如果是多個文件的話,會用 : 作為分隔符。
makeDexElements()
小結一下,這個方法就是把指定目錄下的文件apk/jar/zip/dex按不同的方式封裝成Element對象,然後按順序添加到Element[]數組中。
DexPathList#loadDexFile()
可以看到 DexFile 最終是調用了openDexFile、native方法openDexFileNative去打開Dex文件的,如果outputName為空,則自動生成一個緩存目錄,具體來說是 /data/dalvik-cache/[email protected] 。openDexFileNative這個native方法就不具體分析了,主要是對dex文件進行了優化操作,將優化後得odex文件通過mmap映射到內存中。感興趣的同學可以參考:
《DexClassLoader和PathClassLoader載入Dex流程》
現在在回頭看看DexClassLoader與PathClassLoader的區別。DexClassLoader可以指定odex的路徑,而PathClassLoader則採用系統默認的緩存路徑,在8.0以後沒有區別。
ClassLoader是一個抽象類,有3個構造方法,最終調用的還是第一個構造方法,主要功能是保存實現類傳入的parent參數,也就是父類載入器。ClassLoader的實現類主要有2個,一個是前面講過的BaseDexClassLoader,另一個是BootClassLoader。
BootClassLoader是ClassLoader的內部類,而且繼承了ClassLoader。
這是載入一個類的入口,流程如下:
1、 先檢查這個類是否已經被載入,有的話直接返回Class對象;
2、如果沒有載入過,通過父類載入器去載入,可以看出parent是通過遞歸的方式去載入class的;
3、如果所有的父類載入器都沒有載入過,就由當前的類載入器去載入。
通常我們自己寫的類是通過當前類載入器調用 findClass 方法去載入的,但是在 ClassLoader 中這是個空方法,具體的實現在它的子類 BaseDexClassLoader 中。
BaseDexClassLoader # findClass
可以看到是通過pathList去查找class的,這個對象其實之前講過,它是在BaseDexClassLoader 的構造方法中初始化的,它實際上是一個 DexPathList 對象。
DexPathList # findClass()
對Element數組遍歷,再通過Element對象的 findClass 方法去查找class,有的話就直接返回這個class,找不到則返回null。 這里可以看出獲取Class是通過DexFile來實現的,而各種類載入器操作的是Dex。Android虛擬機載入的dex文件,而不是class文件。
1、載入一個類是通過雙親委託機制來實現的。
2、如果是第一次載入class,那是通過 BaseDexClassLoader 中的findClass方法實現的;接著進入 DexPathList 中的findClass方法,內部通過遍歷Element數組,從Element對象中去查找類;Element實際上是對Dex文件的包裝,最終還是從dexfile去查找的class。
3、一般app運行主要用到2個類載入器,一個是PathClassLoader:主要用於載入自己寫的類;另一個是BootClassLoader:用於載入Framework中的類;
4、熱修復和插件化一般是利用DexClassLoader來實現。
5、PathClassLoader和DexClassLoader其實都可以載入apk/jar/dex,區別是 DexClassLoader 可以指定 optimizedDirectory ,也就是 dex2oat 的產物 .odex 存放的位置,而 PathClassLoader 只能使用系統默認位置。但是在8.0 以後二者是沒有區別的,只能使用系統默認的位置了。
這張圖來源於:
Android虛擬機框架:類載入機制
在類載入流程分析中,我們已經知道,查找class是通過DexPathList來完成的,實際上DexPathList最終還是遍歷其Element數組,獲取DexFile對象來載入Class文件。 由於數組是有序的,如果2個dex文件中存在相同類名的class,那麼類載入器就只會載入數組前面的dex中的class。如果apk中出現了有bug的class,那隻要把修復的class打包成dex文件並且放在 DexPathList 中Element數組`的前面,就可以實現bug修復了 。下一篇為大家帶來的手寫熱修復。
Android類載入機制的細枝末節
從JVM到Dalivk再到ART(class,dex,odex,vdex,ELF)
類載入機制系列2——深入理解Android中的類載入器
Android 熱修復核心原理,ClassLoader類載入
B. android dex何時載入
1 問題
在Android系統中,一個App的所有代碼都在一個Dex文件裡面。Dex是一個類似Jar的存
儲了多有Java編譯位元組碼的歸檔文件。因為Android系統使用Dalvik虛擬機,所以需要把
使用Java Compiler編譯之後的class文件轉換成Dalvik能夠執行的class文件。這里需要強
調的是,Dex和Jar一樣是一個歸檔文件,裡面仍然是Java代碼對應的位元組碼文件。
當Android系統啟動一個應用的時候,有一步是對Dex進行優化,這個過程有一個專門的
工具來處理,叫DexOpt。DexOpt的執行過程是在第一次載入Dex文件的時候執行的。這
個過程會生成一個ODEX文件,即Optimised Dex。執行ODex的效率會比直接執行Dex文
件的效率要高很多。但是在早期的Android系統中,DexOpt有一個問題,也就是這篇文
章想要說明並解決的問題。DexOpt會把每一個類的方法id檢索起來,存在一個鏈表結構
裡面。但是這個鏈表的長度是用一個short類型來保存的,導致了方法id的數目不能夠超過
65536個。當一個項目足夠大的時候,顯然這個方法數的上限是不夠的。盡管在新版本的
Android系統中,DexOpt修復了這個問題,但是我們仍然需要對老系統做兼容。
2 思路
一種有效的解決思路是把Dex文件分割成多個較小的Dex。這就如同很多項目會把自己分
割成多個Jar文件一樣,不同的功能在不同的Jar文件裡面,通過一些配置和額外的操作,
可以讓虛擬機有選擇性的載入Jar文件。但是在Android系統中,一個應用是只允許有一
個Dex文件的。也就是說在編譯期的時候,所有的Jar文件最終會被合並成一個Dex文件。
我們沒有辦法在Apk文件裡面打包兩個Dex,讓DexOpt分別對兩個Dex文件做處理,而
Android系統也不會同時為一個Apk載入兩個Dex。
1
2.1 動態載入
如果我們把Dex分成多個文件,然後在程序運行的時候,再把多的那幾個動態的載入進來
是否可行呢?也就是說我們能否在運行時階段把代碼加入虛擬機中。對於虛擬機來說,其
實所有的代碼都是在運行時被載入進來的。而不同於C語言還存在著靜態鏈接。虛擬機在
所有Java代碼執行之前被啟動,然後開始把位元組碼載入到環境中執行,我們可以理解成所
有的代碼都是動態載入到虛擬機里的。
而說到載入,不得不說的是ClassLoader。它的工作就是載入.class文件。在Android的
Dalvik環境中,對應的是DexClassLoader,它們的功能是完全一樣的。ClassLoader的
一大特點就是它是一個樹狀結構。每個ClassLoader都有一個父親ClassLoader。也就是
說,ClassLoader不是把所有的Class放到一個巨大的數組或別的什麼數據結構中來處理。
ClassLoader在載入一個Jar中的類的時候,需要制定另一個ClassLoader作為父親節點,
當我們需要通過ClassLoader得到一個類類型的時候,ClassLoader會把請求優先交給父
親ClassLoader來處理,而父親ClassLoader又會交給它的父親,一直到根ClassLoader。
如果根ClassLoader有這個類,而返回這個類的類類型,否則把這個請求交給這個請求的
來源子ClassLoader。這是一種向上傳遞,向下分發的機制。這種情況下,對於調用著來
說,子ClassLoader永遠都是包含最多Class的ClassLoader。有一點我們需要注意,父親
ClassLoader只會向請求來源分發自己的處理結果。所以如果來源是自己,那麼如果沒有
請求類它就會返回空,而不是遍歷所有子ClassLoader去請求是否有被請求的類。
在Android系統中,對於一個應用來說,其實有兩個ClassLoader,一個是SystemClassLoader,這個ClassLoader裡面除了Java標準的類庫之外,還有一個android.jar,所有
Android Framework層的類都在這里。而另外一個重要的ClassLoader就是基於Android
Context的ClassLoader。所有屬於當前應用的類都是用這個ClassLoader來載入的,我們
可以在Android源碼中看到,所有的Activity,Service,View都是使用這個ClassLoader
來反射並創建的。我們暫時把它叫做ContextClassLoader。
3 載入外部Dex
3.1 構建一個Dex文件
這一步並不復雜,首先我們把所需要的.class文件或者是Jar文件和一些源碼一起編譯生
成一個Jar文件。然後使用Android SDK提供的dx工具把Jar文件轉成Dex文件。我們可以
提前對它進行ODex操作,讓它在被DexClassLoader載入的時候,跳過DexOpt的部分工
作,從而加快載入的過程。
2
3.2 DexClassLoader
現在的工作就是在運行時載入這個Dex文件了。我們可以在Application啟動的onCreate
方法裡面載入Dex,但是如果你的Dex太大,那麼它會讓你的App啟動變慢。我們也可以
使用線程去載入,但我們必須保證載入完成之後再進行某個外部類的請求。當然也可以真
正等到需要某個外部類的時候再進行Dex載入。這根本上取決於Dex文件本身的大小,太
大了可以預載入,而比較小可以等到實際需要的時候再載入。我們暫且把這個載入了外部
Dex的ClassLoader成為ExternalClassLoader
上面我們提到了樹形結構和系統中的多個ClassLoader,當我們載入外部Dex的時候,我
們是否需要指定一個父ClassLoader呢?我們當然需要一個父ClassLoader,否則我們ExternalClassLoader連一些基本的Java類都沒有,它根本不可能成功的載入一個Dex。進一
步的,我們要選擇哪一個ClassLoader來作為我們的父親呢?是SystemClassLoader還是
ExternalClassLoader?這是根據情況來定的,如果外部的Dex文件里沒有任何和Android
相關的代碼,那麼SystemClassLoader是我們的首選,否則我們就應該用ContextClassLoader。如果是後者的情況,我們的樹可以被看成一個鏈表。
3.3 外部的View, Acitivity等
我們知道,我們編寫的四大組建都不是由我們自己來創建的,是由系統來給我們構造並
管理其生命周期的。那麼這個過程是什麼樣的呢?拿Activity來舉例,我們需要通過調用
當前Activity/Context的startActivity,傳入一個Intent來調用啟動一個新的Activity。系
統有一個ActivityManager來處理這里的邏輯。這里的邏輯相當的復雜,但簡單來說,
ActivityManager會收到並處理這個Intent,從而決定是是啟動一個新的,還是把舊的放
到前台。它會先查找這個Activity在哪個應用裡面,這是通過掃描每個應用的AndroidManifest來確定。這些信息是在PackageManager裡面被檢索的。總之如果這個Activity
不再任何的manifest裡面,它就不可能被啟動。所以僅有一個Activity類是不夠的,我們
需要在manifest裡面聲明它。
上面是Activity的情況,Service之類的也是同理。那麼View怎麼辦?盡管我們可以直接創
建View,但是大部分的View都不是我們創建的,而是通過XML布局文件Inflate出來的。
也就是說,我們在XML定義了一些外部Dex裡面的View,那麼顯然這個XML是不能被成
功的Inflate的。因為除非系統會使用我們的ExternalClassLoader,否則它肯定是找不到
我們的類的:ContextClassLoader裡面並沒有外部Dex中的類。
也就是說問題的根本在於,對於那些Android系統為我們創建的對象,它是不能包含在外
部Dex裡面的。而Android系統中大部分的組建類的生命周期都交給了系統來管理。我們
3
不可能自己來創建這些類對象。那麼另一種思路:我們是不是可以通過使用我們的ExternalClassLoader來代替ContextClassLoader呢?盡管系統的ContextClassLoader是私有
的,但是我們可以通過反射強制的把它替換成我們的ExternalClassLoader。而對於那些
外部的組建(Activity等),盡管我們沒有它們的類,但是並不影響我們在AndroidManifest裡面聲明這個Activity。因為Android系統只是把它作為一個檢索,並不會真正檢查它
裡面的組建是不是真的在虛擬機環境中已經被載入了,只有真正使用Intent啟動某個組建
的時候才會去檢查。而只要我們保證這個時候我們已經載入了外部的ClassLoader,那麼
這個組建就可以被正常的啟動。
還有一點,除了我們要為外部可能有的組建在AndroidManifest裡面做聲明一外,那些外
部組建可能用到的許可權我們也需要一一聲明,例如如果外部Activity使用了相機功能,那
么如果我們的Manifest裡面沒有聲明使用相機功能的許可權的話,即便這個Activity能成功
為載入出來,仍然是不能使用的。
4 核心代碼段
載入外部Dex
mClassLoader = new DexClassLoader ( f . getAbsolutePath ( ) ,
mContext . getCacheDir ( ) . getAbsolutePath ( ) ,
null , mContext . getClassLoader ( ) ) ;
讓系統使用ExternalClassLoader
t r y {
F ie ld mMainThread = ge t F ie ld ( A c t i v i t y . class , 」mMainThread」) ;
Object mainThread = mMainThread . get ( a c t i v i t y ) ;
Class t hreadClass = mainThread . get Class ( ) ;
F ie ld mPackages = ge t F ie ld ( threadClass , 」mPackages」) ;
WeakReference<?> r e f ;
Map< St ring , ?> map =(Map< St ring , ? >) mPackages . get (mainThread ) ;
r e f = (WeakReference<? >) map . get (mContext . getPackageName ( ) ) ;
Object apk = r e f . get ( ) ;
Class apkClass = apk . get Class ( ) ;
F ie ld mClassLoader = ge t F ie ld ( apkClass , 」mClassLoader」) ;
mClassLoader . set (apk , classLoader ) ;
} catch ( I llegalArgument Except ion e) {
i f (DEBUG) {
e . print St ackTrace ( ) ;
}
} catch ( I llega lAc c essEx c ept ion e) {
i f (DEBUG) {
4
e . print St ackTrace ( ) ;
}
}
5
C. android 怎麼動態更新apk中jar包
核心類
1.1 DexClassLoader類
可以載入jar/apk/dex,可以從SD卡中載入為安裝的apk。
1.2 PathClassLoader類
只能載入已經安裝到Android系統中的apk文件。
一、正文
1.1
類似於eclipse的插件化實現, 首先定義好介面, 用戶實現介面功能後即可通過動態載入的方式載入jar文件, 以實現具體功能。 注意 , 這里的jar包需要經過android dx工具的處理 , 否則不能使用。
D. Android Native庫的載入及動態鏈接
我們從一個簡單的NDK Demo開始分析。
下面從 System.loadLibrary() 開始分析。
下面看 loadLibrary0()
參數 loader 為Android的應用類載入器,它是 PathClassLoader 類型的對象,繼承自 BaseDexClassLoader 對象,下面看 BaseDexClassLoader 的 findLibrary() 方法。
下面看 DexPathList 的 findLibrary() 方法
回到 loadLibrary0() ,有了動態庫的全路徑名就可以裝載庫了,下面看 doLoad() 。
nativeLoad() 最終調用 LoadNativeLibrary() ,下面直接分析 LoadNativeLibrary() 。
對於JNI注冊,這里暫不討論,下面看 OpenNativeLibrary() 的實現。
下面看 android_dlopen_ext() 的實現
接下來就Android鏈接器linker的工作了。
下面從 do_dlopen() 開始分析。
find_library() 當參數translated_name不為空時,直接調用 find_libraries() ,這是裝載鏈接的關鍵函數,下面看它的實現。
find_libraries() 中動態庫的裝載可以分為兩部分
下面從 find_library_internal() 開始分析。
下面分析 load_library()
下面看另一個 load_library() 的實現
下面分析ELF文件頭以及段信息的讀取過程,也就是LoadTask的 read() ,它直接調用ElfReader的 Read() 方法。
動態庫的裝載在LoadTask的 load() 中實現。
下面看ElfReader的 Load() 方法
動態庫的裝載已經完成,下面看鏈接過程。
下面看 prelink_image()
鏈接主要完成符號重定位工作,下面從 link_image() 開始分析
下面以函數引用重定位為例分析 relocate() 方法
E. android 怎麼動態的載入類
android 如何動態的載入類----app插件技術
轉自:http://blog.csdn.net/mingli198611/article/details/8858076
?
前言:
?
? ? ? 在目前的軟硬體環境下,Native App與Web App在用戶體驗上有著明顯的優勢,但在實際項目中有些會因為業務的頻繁變更而頻繁的升級客戶端,造成較差的用戶體驗,而這也恰恰是Web App的優勢。現如今很多項目要求需要採用類似於微信或Q游這樣的插件化開發模式越來越多,本文就是闡述android的動態載入技術來滿足插件化開發模式的文章。
?
1.基本概念
1.1??在Android中可以動態載入,但無法像Java中那樣方便動態載入jar。
Android的虛擬機(DalvikVM)是不認識Java打出jar的byte code,需要通過dx工具來優化轉換成Dalvikbyte code才行。這一點在咱們Android項目打包的apk中可以看出:引入其他Jar的內容都被打包進了classes.dex。即android要載入的java類必須dex格式的代碼文件.
1.2??在Android中可以載入基於NDK的so庫。
NDK的執行效率很高,加密性很好,但同時開發入門難度大,一般用於加解密、數學運算等場合。so的載入很簡單,如果APK發布時已經攜帶了so文件,只需要在載入時調用System.loadLibrary(libName)方法即可。由於軟體的安裝目錄中存放so的目錄是沒有寫許可權的,開發者不能更改該目錄的內容,所以如果要動態載入存放在其他地方的so文件,用System.load(pathName)方法即可。
1.3??在Android中支持動態載入dex文件的兩種方式:
DexClassLoader:這個可以載入jar/apk/dex,也可以從SD卡中載入,也是本文的重點
PathClassLoader:只能載入已經安裝到Android系統中的apk文件。也就是 /data/app 目錄下的 apk 文件。其它位置的文件載入的時候都會出現 ClassNotFoundException.因為 PathClassLoader 會去讀取 /data/dalvik-cache 目錄下的經過 Dalvik 優化過的 dex 文件,這個目錄的 dex 文件是在安裝 apk 包的時候由 Dalvik 生成的。
?
2.注意
2.1 採用不用安裝的插件開發模式,只能夠使用?DexClassLoader進行載入.不過動態載入是有一些限制的,比如插件(子apk)包中的Activity、Service類是不能動態載入的,因為缺少聲明;即使你在Manifest文件中進行了聲明,系統默認也是到安裝apk所在的路徑中去尋找類,所以你會遇到一個ClassNotFound的異常。插件里你可以用主apk中先前放入的layout、strings等資源。但是插件中自帶的界面只能用純代碼進行編寫,插件中是不能載入插件(子apk)中的xml作為layout等資源使用的。所以在開發上一些特效會比較困難些,建議預先植入主apk中。
2.2?大家可以看看DexClassLoader的API文檔,裡面不提倡從SD卡載入,不安全
3.如何製作插件
3.1 把工程導出為jar包
3.2 執行SDK安裝目錄android-sdk-windows\platform-tools下的dx命令,把jar包轉換為dex格式
dx?--dex?--output=dex名 jar包名
4.如何做到啟動未安裝的apk中的activity?
採用反射機制,把主apk中的activity的context傳遞到插件的activity中,然後採用反射進行回調插件activity的方法。不足之出就是,插件中的activity並不是真正的activity,它只是運行在主activity中。比如:點擊返回直接退出當前activity而不是回到主activity。實例如下:
?
這是調用的Activity:
?
[java]?view plain ? ?
package?com.beyondsoft.activity;??
??
import?java.lang.reflect.Constructor;??
import?java.lang.reflect.InvocationTargetException;??
import?java.lang.reflect.Method;??
??
import?dalvik.system.DexClassLoader;??
import?android.app.Activity;??
import?android.content.pm.PackageInfo;??
import?android.os.Bundle;??
import?android.util.Log;??
??
public?class?PlugActivity?extends?Activity?{??
??
????private?Class?mActivityClass;??
????private?Object?mActivityInstance;??
????Class?localClass;??
????private?Object?instance;??
??
????@Override??
????protected?void?onCreate(Bundle?savedInstanceState)?{??
????????super.onCreate(savedInstanceState);??
??
????????Bundle?paramBundle?=?new?Bundle();??
????????paramBundle.putBoolean("KEY_START_FROM_OTHER_ACTIVITY",?true);??
????????paramBundle.putString("str",?"PlugActivity");??
????????String?dexpath?=?"/sdcard/FragmentProject.apk";??
????????String?dexoutputpath?=?"/mnt/sdcard/";??
????????LoadAPK(paramBundle,?dexpath,?dexoutputpath);??
????}??
??
????@Override??
????protected?void?onStart()?{??
????????super.onStart();??
????????Method?start;??
????????try?{??
????????????start?=?localClass.getMethod("onStart");??
????????????????start.invoke(instance);??
????????}?catch?(Exception?e)?{??
????????????//?TODO?Auto-generated?catch?block??
????????????e.printStackTrace();??
????????}??
????}??
??
????@Override??
????protected?void?onResume()?{??
????????//?TODO?Auto-generated?method?stub??
????????super.onResume();??
????????Method?resume;??
????????try?{??
????????????resume?=?localClass.getMethod("onResume");??
????????????resume.invoke(instance);??
????????}?catch?(Exception?e)?{??
????????????//?TODO?Auto-generated?catch?block??
????????????e.printStackTrace();??
????????}??
????}??
??
????@Override??
????protected?void?onPause()?{??
????????super.onPause();??
????????Method?pause;??
????????try?{??
????????????pause?=?localClass.getMethod("onPause");??
????????????pause.invoke(instance);??
????????}?catch?(Exception?e)?{??
????????????e.printStackTrace();??
????????}??
????}??
??
????@Override??
????protected?void?onStop()?{??
????????super.onStop();??
????????try?{??
????????????Method?stop?=?localClass.getMethod("onStop");??
????????????stop.invoke(instance);??
????????}?catch?(Exception?e)?{??
????????????e.printStackTrace();??
????????}??
????}??
??
????@Override??
????protected?void?onDestroy()?{??
????????//?TODO?Auto-generated?method?stub??
????????super.onDestroy();??
????????try?{??
????????????Method?des?=?localClass.getMethod("onDestroy");??
????????????des.invoke(instance);??
????????}?catch?(Exception?e)?{??
????????????//?TODO?Auto-generated?catch?block??
????????????e.printStackTrace();??
????????}??
????}??
F. android怎麼動態調試dex
1.1 安裝JDK
JAVA環境的搭建請自行查找資料,這里不做詳述。
1.2 安裝Android SDK
下載地址:http://developer.android.com/sdk/index.html。
下載完安裝包後解壓到任意一目錄,然後點擊運行SDK Manager.exe,然後選擇你需要的版本進行安裝,如圖:
1.3 安裝Eclipse集成開發環境
下載地址:http://www.eclipse.org/downloads。選擇Eclipse for Mobile Developers,解壓到任意目錄即可。
1.4 創建Android Virtual Device
動態調試可以用真實的手機來做調試環境,也可以用虛擬機來做調試環境,本文採用虛擬機環境。因此創建虛擬機步驟如下:
1打開Eclipse –>windows->Android Virtual Device
2點擊Create,然後選擇各個參數如圖:
這里Target 就是前面步驟中安裝的SDK 選擇任意你覺得喜歡的版本就可以。點擊OK 就創建完畢。
1.5 安裝 APK改之理
這個是一個很好用的輔助調試的軟體,請自行搜索下載。
1.6 安裝 IDA6.6
IDA6.6開始支持安卓APP指令的調試,現該版本已經提供免費下載安裝,請自行搜搜。
0x02 Dalvik指令動態調試
2.1 准備工作
安卓APP應用程序後綴為apk,實際上是一個壓縮包,我們把它改後綴為rar打開如圖:
其中classes.dex是應用的主要執行程序,包含著所有Dalvik指令。我們用APK改之理打開apk,軟體會自動對其進行反編譯。反編譯後會有很多smail文件,這些文件保存的就是APP的Dalvik指令。
在APK改之理里雙擊打開AndroidManifest.xml,為了讓APP可調試,需要在application 標簽里添加一句android:debuggable="true" 如圖:
然後點擊保存按鈕,然後編譯生成新的apk文件。接著打開Eclipse –>windows->Android Virtual Device,選擇剛才創建的虛擬機,然後點擊start,虛擬機便開始運行。偶爾如果Eclipse啟動失敗,報錯,可以同目錄下修改配置文件:
把配置參數原本為512的改為256 原本為1024的改為512,然後再嘗試啟動。
在SDK安裝目錄有個命令行下的調試工具adb shell,本機所在目錄為E:\adt-bundle-windows-x86-20140702\sdk\platform-tools,把adb.exe注冊到系統環境變數中,打開dos命令行窗口執行adb shell 就可以進入APP命令行調試環境,或者切換到adb所在目錄來執行adb shell。
這里先不進入adb shell,在DOS命令行下執行命令:adb install d:\1.apk 來安裝我們剛才重新編譯好的APK文件。安裝完畢會有成功提示。
2.2 利用IDA動態調試
將APP包里的classes.dex解壓到任意一目錄,然後拖進IDA。等待IDA載入分析完畢,點擊Debugger->Debugger Options如圖
按圖所示勾選在進程入口掛起,然後點擊Set specific options 填入APP包名稱和入口activity 如圖:
其中包的名稱和入口activity 都可以通過APK改之理里的AndroidManifest.xml 文件獲取:
1
2
3
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.simpleencryption">
<application android:allowBackup="true" android:debuggable="true" android:icon="@drawable/creakme_bg2" android:label="@string/app_name" android:theme="@style/AppTheme">
<activity android:label="@string/app_name" android:name=".MainActivity">
然後在IDA點擊Debugger->Process Options
其他默認不變,埠這里改為8700。這里默認埠是23946,我在這里困擾了很久,就是因為這個埠沒有改為8700所致。然後我們看看這個8700埠是怎麼來的。在Android SDK里提供了一款工具DDMS,用來監視APP的運行狀態和結果。在SDK的TOOLS目錄有個DDMS.BAT的腳步,運行後就會啟動DDMS。由於我的本機安裝了SDK的ADT插件,DDMS集成到了Eclips中,打開Eclips->Open perspective->ddms就啟動了DDMS。
如圖所示:
在DDMS選中某個進程後面就會注釋出它的調試埠,本機這里是8700。
到此所有的工作就准備就緒,然後就可以下斷點來調試該APP了。我們在APK改之理中在com目錄下查看smali文件 發現MainActivity.smali里有一個感興趣的函數getPwdFromPic(),那麼我們就對它下斷以跟蹤APP的運行。
在IDA里搜索字元串getPwdFromPic,發現onClick有調用該函數
我們在onClick 函數開始位置按F2下斷如圖:
然後點擊上圖中綠色三角形按鈕啟動調試如圖:
調試過程中有一個問題出現了很多次,浪費了我大量的時間,就在寫文章的時候,操作時還是遇到了這樣的問題。就是點擊啟動後IDA提示can』t bind socket,琢磨了很久終於找到原因了,當打開過一次DDMS後 每次啟動Eclips都會啟動DDMS 而8700埠正是被這個DDMS給佔用了,然後每次都會啟動失敗,解決辦法就是 虛擬機運行起來後關閉掉Eclips,這時一切就正常了!
事例中是一個APP crackme 提示輸入密碼才能進入正確界面。這個時候我們輸入123,點擊登陸,IDA中斷在了我們設置斷點的地方,這時選中ida->debugger->use source level debugger,然後點擊ida->debugger->debugger windows->locals打開本地變數窗口,如圖:
然後按F7或F8單步跟蹤程序流程,同時可以觀察到變數值的變化,也可以在IDA右鍵選擇圖形視圖,可以看到整個APP執行的流程圖:
如上圖所示 變數窗口中我們輸入了123 被轉化成的密碼是么廣亡,pw變數也顯示出了正確的密碼,其實這個時候已經很容易判斷出正確密碼了。
0x03 Andoid原生動態鏈接庫動態調試
通常為了加密保護等措施,有時dex執行過程中會調用動態鏈接庫文件,該文件以so為後綴,存在於APP文件包里。
這里我們以動態附加的方式來調試原生庫。
3.1 准備工作
1、將IDA->dbgsrv目錄下的android_server拷貝到虛擬機里,並賦予可執行許可權
DOS命令分別為:
adb shell pull d:\ android_server /data/data/sv
adb shell chmod 755 /data/data/sv
2、啟動調試伺服器android_server
命令:adb shell /data/data/sv
伺服器默認監聽23946埠。
3、重新打開DOS窗口進行埠轉發,命令:
adb forward tcp:23946 tcp:23946 如圖:
3.2 利用IDA進行動態調試
1、虛擬機里啟動要調試的APP 2、啟動IDA,打開debugger->attach->remote Armlinux/andoid debugger
埠改為23946 其他保持不變,點擊OK
如上圖,選中要調試的APP 的數據包名,然後點擊OK。
正常情況下,IDA會把APP進程掛起。
3、由於當前程序不是在動態鏈接庫領空,這時我們要重新打開一個IDA,用它打開需要調試的so文件,找到需要下斷的位置的文件偏移,並做記錄,然後關閉後面打開的這個IDA。
4、在原IDA界面按下ctrl+s鍵,找到並找到需要調試的so,同時記錄該文件的載入基址。然後點擊OK 或者cancel按鈕關閉對話框。
5、按下快捷鍵G 輸入基址+文件偏移所得地址,點擊OK 就跳轉到SO文件需要下斷的地方,這時按下F2鍵設置斷點。當APP執行到此處時便可以斷下來。
3.3 在反調試函數運行前進行動態調試
程序載入so的時候,會執行JNI_OnLoad函數,做一系列的准備工作。通常反調試函數也會放到JNI_OnLoad函數里。進行4.2中第2步時也許會遇到如下情況:
這時APP檢測到了調試器,會自動退出,那麼這時調試策略需要有所改變。
接著4.1第3步後,在DOS命令行執行命令:
adb shell am start -D -n com.yaotong.crackme/com.yaotong.crackme.MainActivity
來以調試模式啟動APP 如圖:
com.yaotong.crackme是APP包名稱,com.yaotong.crackme.MainActivity是執行入口 這些可以用APK改之理查看。
這時由於APP還未運行,那麼反調試函數也起不了作用,按照4.2中第2步把APP掛起。這時IDA會中斷在某個位置
然後點擊debugger->debugger opions設置如下:
點擊OK 後按F9運行APP,然後再DOS命令下執行命令:
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
這時APP會斷下來,然後按照4.2中的3、4、5補找到JNI_OnLoad函數的地址並下斷,然後按F9 會中斷下來。然後便可以繼續動態跟蹤調試分析。
G. Android apk中包含另一個apk
根據你所說的,我懷疑這個apk包起始並不是安裝來用的,而是在360運行的時候,使用動態載入技術,動態載入了這個apk中的dex中的class,這也就解釋了為什麼manifest沒有注冊activity,因為這些都是由安裝的那個包來執行的,你可以網路下android 動態載入,相信對你有幫助。
H. 【Android】Android中的類載入
前文: 【Java】ClassLoader與雙親委派機制
Android中的類載入器有三種, DexClassLoader 、 PathClassLoader 、 BootClassLoader 。
其中 BootClassLoader 是系統啟動時預載入常用類的,一般使用不到。 DexClassLoader 、 PathClassLoader 都是繼承自 BaseDexClassLoader 。
但 DexClassLoader 和 PathClassLoader 並沒有重寫 BaseDexClassLoader 中的任何方法,所以源碼只需要看 BaseDexClassLoader 即可。
由於Android SDK並沒有包含 BaseDexClassLoader ,所以需要到源碼查詢網站查詢源碼,如下:
復制這個java文件到對應源碼文件夾下就可以在Android Studio中查看了。
通過調試可以看到,Android中普通類的載入器其實是 PathClassLoader 。追蹤 PathClassLoader.findClass 方法,即可獲取Android的類載入過程:
PathClassLoader.findClass -- 繼承自 --> BaseDexClassLoader.findClass()
-> BaseDexClassLoader.pathList.findClass()
-> DexPathList.dexElements.foreach { element.findClass() }
-> Element.findClass()
-> Element.dexFile.loadClassBinaryName()
-> DexFile.defineClass()
即類載入過程通過 BaseDexClassLoader.findClass 、 DexPathList.findClass 、 Element.findClass 、 DexFile.loadClassBinaryName ,最終會落到 DexFile.defineClass 方法中,然後就交給native層了。
其中需要注意的是,在 BaseDexClassLoader.findClass 的開頭有這么一段:
這段是在Android 10新加入的,據稱是為了實現 shared library 功能的,在之前的版本中沒有這一段。
在上一節中知道了,類載入的流程如下:
BaseDexClassLoader.findClass() ->
BaseDexClassLoader.pathList.findClass() ->
DexPathList.dexElements.foreach { element.findClass() } ->
Element.findClass() -> ...
看 DexPathList.findClass 方法:
可以發現, DexPathList 載入類的方法是遍歷 dexElements 數組依次載入,知道獲取到值為止。所以可以通過修改這個數組,把新的dex文件放在數組的前面,使其載入修改後的類,從而實現熱修復。
根據以上原理,寫下這個工具類,有效性待驗證: