近來 Android 十分熱門, Google 的大動作,撼動整個業界。雖已震天撼地,和過去 MS 或 Apple 所興之波瀾相較,還是有些差距。身為一個技術研究者,新聞性似乎不是這麼重要,倒底葫蘆裡賣的是什麼藥,才是吾輩所想知道。小弟最近獲邀加入某團體,而擇 主題研究,企圖改善國內 Open Source 的風氣和態度。於是著手分析 Dalvik 程式碼。

Dalvik 的成分

Dalvik 是一個 VM (Virtual Machine) ,相當於 Java 的 JVM 、 .Net 的 CLI 和 Python 、 Perl 、 Ruby 的 Interpreter 。Dalvik 定義自己的 bytecode ,為 VM 的指令,相當於 CPU 的機械碼。這些 bytecode 指令由 VM 進行解釋、執行。 Android 利用 Dalvik VM ,達到跨平台的目的。Dalvik 的程式碼目錄裡,又豈只 VM 而已,尚有一核心 library ,包括 Java Source Code 和 implement JNI 的 native method code。然,前述皆是 library ,非 VM 本體,不在本文討論之列。本文就 VM 本體進行分析和討論,並示範 trace code 的方法,希望有志學習者能因而獲益一、二。

Dalvik 目錄

Dalvik 目錄下,會看到二十來個檔案和目錄,有文件、Makefile 、library 、工具等。和 VM 有關者,唯

  • dalvikvm/
  • vm/

二者。dalvikvm/ 目錄實為 VM 的 main function ,透過呼叫 vm/ 目錄下所定義的 function ,初始並啟動 VM ,以執行指定程式。而 VM 的真正本體則在 vm/ 目錄之下。概欲研究 Dalvik 程式碼者,因集中於此二目錄,勿被其它目錄內的程式碼所迷惑。然而, docs/ 目錄下的文件,有許多有參考價值。有志者,或可一讀,為預習功課。研究過程中,遇不解名詞時,請隨時透過 search engine 進行查詢。

開始分析

分析任何系統之初,首要為找到程式的進入點。以 C 的 application 而言,就是 main() function ,在 dalvikvm/Main.c 裡。接著著手進行分析。然而,main() function 一開始就有滾滾江水、源源不絕之勢,十之八九,可能從第一行開始看,順著每一個呼叫追蹤而入,覓技微末節而不棄,直至迷途於茫茫程式碼之間,終至意志損耗 殆盡而投降。正是見樹而不見林之害,分析之初唯恐避之不急。因此,謹記研究初衷為 VM 本身,而非相關初始設定。需先觀測 main() function ,再鎖定可疑的主體,再進行分析,以了解大架構為先,以達提綱挈領之效,不至於迷罔。

從 main() function 裡,我們大概能本能性的猜測到

  • JNI_CreateJavaVM()
  • FindClass()
  • GetStaticMethodID()
  • CallStaticVoidMethod()

這幾行,應該和 VM 本體關係較大。透過分析這幾個 function ,應該能了解程式碼的架構

  • 主要 function
  • function 和 .c file 間的關係
  • 每個 .c file 主要 implement 哪一部分的功能
  • 每個子目錄又個別負責什麼功能。

起初應以瞭解上面所提之事,熟悉程式碼的檔案和目錄結構而主,不可一頭栽入程式細節之中。

從上面幾行,幾可猜出, Dalvik 的執行,必需先建立、初始化一個 VM,然後透過 FindClass() 載入、並取得命令列指定的 class ,並透過 GetStaticMethodID() 取得 class 裡的 static method,最後執行該 method 。我們知道, Java 程式的執行,是透過指定一個 class , VM 假設該 class 有一 static method 名為 main ,以執行之。很快的,我們了解到, Dalvik 主要流程大概是上述的過程。

JNI_CreateJavaVM()

JNI_CreateJavaVM() 故名思義,應該就是建立、初始化一個 Dalvik VM ,用以執行指定的程式碼。我們必需先知道, Dalvik 幾乎是一個 Java VM ,因此有許多名詞是 直接延用 Java 的用法,如 JNI 即是 Java Native Interface。作為 Java 和 native code 的介面。因此,此處 JNI 料想和 JNI 有關。有可能是提供給 Native code 呼叫的 function ,或用以呼叫 native code 。此處是 create VM ,因此可假設為提供 native code 呼叫。

接著,我們追入 JNI_CreateJavaVM() 看其葫蘆裡賣的是黑藥、白藥。這裡,請善用 ctags 搭配 editor,否則就用 grep 整個目錄才能找到 JNI_CreateJavaVM() 的位置。以 emacs 而言,將游標移至該 function 的位置,按 meta+. 或 alt+. 即觸發 emacs 查詢 ctags db 裡的相關資料,開啟定義該 function 的檔案,並移至該 function 的開頭。如: 在 JNI_CreateJavaVM() 上,按 alt+. ,emacs 即跳至 JNI_CreateJavaVM() function 首行。 JNI_CreateJavaVM() 是在 vm/Jni.c 檔案裡。因此, Jni.c 應該是 VM 提供的主要介面。

要確定 vm/Jni.c 的主要功能,最好是檢示一次裡面的 function 。若觀察 vm/ 目錄,或許發現 Jni.h 。大部分 programmer 在 coding 時,都有類似的習慣。例如:將 Jni.c 所定義的 function ,透過 Jni.h export 給其它 module 使用。這是一種常見的習慣,若一個 programmer 的 coding 習慣離一般習慣太遠,或變化甚大,甚至於毫無章法,該程式品質必然低落,也無研究價值。在研究它 人程式碼時,需利用常用習慣。用之則有如神助,棄之則如牛犛田。一般 module ,會將其主要功能,以一組 function 和 data structure 加以定義,並 export 。然程式碼則充滿各式的實作細節,一一檢視何者為 export 的介面,何者為非,得花上大半時間。最好的方式是直接看 header file, .h file。header file 一般只有 export 的部分,因此沒有太多雜訊。檢視 module 的 header file 內容,大抵能了解一個 module 的功用。

然而,我們並沒有發現 Jni.h ,這時只有直接檢視 Jni.c 的內容。很幸運的, Dalvik 的 developer 們有良好習慣,將 local 使用的 function 都宣告為 static。這是一個常見的良好的習慣,因些我們可以很快略過 static function ,得知有哪些重要的 function 。為了更加快速,可利用 editor 的 folding 功能,讓 editor 收疊程式的內容,只 show 出最外層的 function 定義。以 emacs 為例,按 ctrl+c @ ctrl+alt+h 能達到前述功能。按 ctrl+c @ ctrl+alt+s 則顯示所有細節。

在 Jni.c 我們看到

  • dvmJniStartup()
  • dvmJniShutdown()
  • dvmGetJNIEnvForThread()
  • dvmCreateJNIEnv()
  • dvmDestroyJNIEnv()
  • dvmGetJNIRefType()
  • dvmReleaseJniMonitors()
  • dvmLateEnableCheckedJni()
  • JNI_GetDefaultJavaVMInitArgs()
  • JNI_GetCreatedJavaVMs()
  • JNI_CreateJavaVM()

裡有數十個 static function 被我們略過,只需注意其中這幾個 global function 。一個 module 在介面定義了許多功能,其中只有一部分是重要的,其它則是補助性質。其中

  • dvmJniStartup()
  • dvmJniShutdown()

看起來是使用這個 module 必需進行初始化的 function 。於是我們 grep 一下目錄,發現在 dvmStartup() (in vm/Init.c) 裡呼叫了這個程式。dvmStartup() 也呼叫了許多其它 *Startup() function ,看來所有子系統的 initialization function 都在這裡呼叫。而 dvmStartup() 則被 JNI_CreateJavaVM() 所呼叫。因而我們可以猜測,所有的 sub-module 的 initialize 都透過 JNI_CreateJavaVM() 呼叫 dvmStartup() 而進行。包括 Jni.c 本身。

gDvm

在 main() function 裡,我們知道 vm 、 env 兩個未 local variable 是 pointer,並且未初始化 。 main() function 傳兩者的reference (address) 於 JNI_CreateJavaVM() ,此意乃 JNI_CreateJavaVm() 需初始化兩個 pointer 的內容。大抵上,developer coding 之時,不會將未初始化的 variable 的 reference 傳入其它 function ,除非該 function 需負責設定 variable 的內容。從變數名,吾者已能臆測 vm 應該是保留 JNI_CreateJavaVM() 所建立的 VM object 的位址。從 main() function 中,透過 env 呼叫 CallStaticVoidMethod() 、FindClass() 等等 function ,想必是 VM 提供之控制介面。

為確定猜想,必然要檢示 JNI_CreateJavaVM() 內容。pVM,即 main() function 的 vm ,是在此 function 裡 allocate memory ,並設定其內容。而 pEnv,即 main() function 的 env,則呼叫 dvmCreateJNIEnv() 而得。檢視 dvmCreateJNIEnv() 和 JNI_CreateJavaVM(),發現 pVM 和 pEnv 之間並沒有資料的關聯,也就是 pVM 和 pEnv 並沒有相互指向對方,應該兩者無關。但在 main() function 又有暗示兩者之間的關聯。於是在 JNI_CreateJavaVM() 裡,可以發現 gDvm 這個 global variable 。這個變數似乎是由 JNI_CreateJavaVM() 所初始化。 Global variable 通常意指系統有些 function 是透過這些 variable ,相互分享資料。難道 pVM 和 pEnv 的關聯,即透過 gDvm 建立?

pVM 和 pEnv 是透過 casting 才 assign 給 main() function 的 vm 和 env

001 *p_env = (JNIEnv*) pEnv;
002 *p_vm = (JavaVM*) pVM;

這通常意謂著 pEnv 開頭第一個欄位是 JNIEnv 型別,而 pVM 的頭一個欄位是 JavaVM 型別。這是 C programmer 常用的技巧,隱藏實作所需的資料,將必需 export 的資料,放在第一個欄位裡透過 casting export 出去,並避免 user 看到其它資料。至此,讀者或會發現,trace 別人的 code 往往必需了解作者的習慣和技巧。所幸好用的習慣的技術,會被大多數人接受、採用。也因此,要想 trace 別人的 code ,自己本身也需要有一定的 coding 的技術,並透過閱讀別人的 code ,累積一些大多數常用的技巧和習慣。幾年前有一本書,名為 Code Reading (by by Diomidis Spinellis)。這本書應該加入這些材料才對。

為確定 pVM 和 pEnv 的頭一個欄位,我們檢識 JniInternal.h 裡的 JavaVMExt 和 JNIEnvExt,發覺分別是 JNIInvokeInterface* 和 JNINativeInterface* 型別。這是怎麼回事? 於是回頭檢查 JavaVM 和 JNIEnv ,發覺

001 #if defined(__cplusplus)
002 typedef _JNIEnv JNIEnv;
003 typedef _JavaVM JavaVM;
004 #else
005 typedef const struct JNINativeInterface* JNIEnv;
006 typedef const struct JNIInvokeInterface* JavaVM;
007 #endif

很明顯,我們是使用 C ,非 C++ ,所以是下半部。沒錯,正和前面所發現的相同。於是確定 pVM 和 pEnv 的第一個欄位 export 了 main() function 裡的 vm 和 env 所提供的內容。

再仔細檢視 JNI_CreateJavaVM() 和 dvmCreateJNIEnv() ,會發覺頭個欄位的內容分別定義在 gInvokeInterface 和 gNativeInterface 。而兩者的內容則是列了一堆 function pointer ,指向許多 function 。完全證實我們之前所猜測的。

至此,讀者應已發現。我在 trace code 時,並不是一行一行的看。而是大膽假設、然後小心求證、再假設、再求證,跳躍式 trace source code 。試想,programmer 在寫程式時也不是線性的,一行一行,一口氣從第一行寫到最後一行。大抵上也是一次 implement 一個概念,然後一個概念接著一個概念,慢慢將 code 填上去。一個 function 或許來來回回修了很多次。

gInvokeInterface 和 gInvokeInterface 所列之 function ,似乎全為 Jni.c 裡面的 static function 。我們在 Jni.c 裡 search gDvm ,看看這些 function 是否有 access gDvm 及其用法,發覺使用的不多。再仔細看看 JavaVMExt 和 JNIEnvExt 的定義,發覺沒有存放什麼狀況。反倒是 gDvm 的型別 DvmGlobals 存放了許多資料,似乎 VM 的狀態大多存放在此。如果一個系統將狀態存放在 global variables,那意謂著該系統在一個 process 只能執行一份。看來 Dalvik 目前在一個 process 裡,只允許一個 VM 。我在寫程式極力避免這種情形,以保有單一 process 執行多份相同的 instance 的可能性。然而, global variable 倒是偷懶的好方法,如果你很確定不想保有此彈性。

http://heaven.branda.to/~thinker/GinGin_CGI.py/show_id_doc/382

Dalvik分析(一)相关推荐

  1. Dalvik分析(二)

    論軟體, Dalvik 算小物,但也非一時三刻能說的完.前篇談到 Dalvik 建 gDvm ,至此算是完成初始化.可開始執行 bytecode.Dalvik 在功能劃分算是明顯, vm/Jni.c ...

  2. Dalvik中类的加载

    Java 源代码经过编译后会生成后缀为class的文件,也即字节码文件.然后在Android中使用dx工具将其转换为后缀为jar 的dex类型文件.Dalvik 虚拟机负责解释并执行编译后的字节码.在 ...

  3. Dalvik class加载的处理

    Java 源代码经过编译后会生成后缀为 class 的文件,也即字节码文件.然后在 Android 中使用 dx 工具将其转换为后缀为 jar 的 dex 类型文件. Dalvik 虚拟机负责解释并执 ...

  4. Android 系统(82)---ART 和 Dalvik

    ART 和 Dalvik Android Runtime (ART) 是 Android 上的应用和部分系统服务使用的托管式运行时.ART 及其前身 Dalvik 最初是专为 Android 项目打造 ...

  5. 【Android 逆向】Dalvik 函数抽取加壳 ( 类加载流程分析 | Class.cpp#findClassNoInit 函数 | DexFile.cpp#dexFindClass 函数分析 )

    文章目录 前言 一.Class.cpp#dvmDefineClass 函数分析 二.Class.cpp#findClassNoInit 函数分析 三.DexFile.cpp#dexFindClass ...

  6. 【Android 逆向】Dalvik 函数抽取加壳 ( 类加载流程分析 | native 函数查询 | dalvik_system_DexFile.cpp#defineClassNative 函数 )

    文章目录 前言 一.查询 defineClassNative 函数 二.dalvik_system_DexFile.cpp#Dalvik_dalvik_system_DexFile_defineCla ...

  7. 【Android 逆向】Dalvik 函数抽取加壳 ( 类加载流程分析 | DexPathList#findClass 函数分析 | DexFile#loadClassBinaryName 函数 )

    文章目录 前言 一.DexPathList.java#findClass 类加载函数源码分析 二.DexFile.java#loadClassBinaryName 函数源码分析 前言 上一篇博客 [A ...

  8. 【Android 逆向】Dalvik 函数抽取加壳 ( 类加载流程分析 | ClassLoader#loadClass 函数分析 | BaseDexClassLoader#findClass 分析 )

    文章目录 一.ClassLoader.java#loadClass 类加载函数源码分析 二.BaseDexClassLoader.java#findClass 函数源码分析 一.ClassLoader ...

  9. 【Java 虚拟机原理】Dalvik 虚拟机 ( 打包 Jar 文件和 Dex 文件 | 反编译 Dex 文件 | 分析 Dex 文件反编译结果 )

    文章目录 前言 一.打包 Jar 文件和 Dex 文件 1.示例代码 2.打包 Jar 文件 3.打包 Dex 文件 二.反编译 Dex 文件 三.分析 Dex 文件 1.Student 类相关信息 ...

最新文章

  1. linux启停was命令,linux下的启停脚本
  2. Mac 安装 Jenkins
  3. Android Paint、Path详解
  4. boost的chrono模块线程时钟的测试程序
  5. Combination Sum 和Combination Sum II
  6. 什么?原来C#还有这两个关键字
  7. 计算机网络体系结构物流层,计算机网络体系结构58029.ppt
  8. GitHub 60000+ Star 登顶,命令行的艺术!
  9. vue-router: $router.push遇到的问题
  10. 编程序常用英语单词是什么
  11. 正则表达式-找出电话号码
  12. var that = this 小坑记
  13. 全国乡镇边界及名称的下载与格式转换方法(水经注万能地图X3.1+CASS10.1.5组合拳)
  14. 导入百度导航SDK遇到的相关问题
  15. 游戏设计模式——观察者模式(Observer)
  16. java短信发送接口代码示例demo分享
  17. 软件测试--------数据库MySQL 常用sql语句
  18. Electron主进程和渲染进程之间通信
  19. 通过npm运行管理端界面并解决npm install安装报错
  20. 用数万元购买个人网站域名是否值得?

热门文章

  1. UI复习练习_优酷布局
  2. 应用程序框架实战三十六:CRUD实战演练介绍
  3. 组件对象模型:COM
  4. 2014年3月26日,今天的收获_______每天都能觉得进步,这是多么好的一件事
  5. hadoop学习记录
  6. C#中listView列自动适应缩放的完美效果
  7. PHP中一些常用知识点
  8. android入门学习-天气预报app(一)
  9. 手势模型和Angular Material的实现
  10. CF 295A - Greg and Array 差分數列