JVM

04 December 2020

JVM背景介紹

一般C/C++編譯方式都是直接編譯成Machine code的部分,也就是我的c code在linux環境就編譯成linux看得懂的Machine code, 在windows環境就會編譯成windows看得懂的machine code. 會面臨的問題是我在linux環境下編譯出來的Machine code並沒有辦法在Windows環境下執行, Windows看不懂那份Machine code,或是link不到那些library. 因此要讓Windows環境能執行那份程式的辦法,就是拿原始碼在那個環境在編譯一次產生的Machine code.

JVM(Java virtual Machine)顧名思義是一種用來跑Java code的平台, 他就是某方面能夠解決上述C/C++面臨的跨平台的問題. 他的想法就是把Java的語言編譯成一種中介語言bytecode, bytecode再交給JVM來執行. 因此只要該平台上有裝JVM那他就能看得懂那份bytecode. 也就是我在linux寫的java編成的bytecode能夠在windows上的JVM執行. 有JVM後就不需要再不同平台重新編譯,只需要編譯一次,在其他的平台上都裝JVM, 由JVM解讀bytecode就可以跑.

但相反的, JVM就是一個浩大工程,他要定義好完整的spec,然後個個平台都要implement JVM才行. 而且JVM是Just-in-time compiler(JIT), 他是動態的把bytecode轉成machine code來執行, 所以效率上比C/C++這種直接編譯成Machine code的方式還差一些

JIT(Just-in-Time Compiler)介紹

前面講到Java的程式會先由javac編譯成bytecode進入JVM執行, JVM裡會有另一個優化的compiler叫做JIT, 目的在於使那些很常被執行的bytecode轉成machine code存在cache, 減少直譯編譯器的overhead
JIT的特色就是他會在runtime解讀程式碼並且執行,相較於C/C++的方式,在某些情況下會快很多, 一般而言,直譯式compiler編譯速度是相對比C compiler那種快很多, 所以在某些狀況下直譯+執行花的時間會來的比C compile +執行的時間還快很多. 但直譯是很大的問題就會像是下個section所提到的那樣,當一段一直重複的code(例如for迴圈), 直譯式因為一次只讀一行或者一段的編譯方式會讓他for loop不斷的重新直譯同樣的程式碼並執行,因此JIT優化是傾向於出現頻率高的程式片段先編譯成machine code放在cache裡,要用直接取, 避免一直重複編譯.

JVM架構

基本上PC, JVM stack, native stack frame是per thread, 其他都是共有的
JVM stack就跟一般程式的stack一樣, 存local variable跟一些資料

關於JVM Stack跟Stack Frame的差別, 可以參考下面的連結

Reference

jyt0532’s Blog

JVM Details

Execution Engine

基本上就是由class loader把class檔load進來,找到entry point並且開始一行一行直譯程式碼並執行

JIT的特色就是會在執行階段翻譯byte code並且執行,缺點是在runtime多了解譯的步驟外 也沒辦法real time對程式做像C/C++那樣的優化. 所以為了提升效能, JVM有一個特色是他會在bytecode中找執行平率高的程式碼(像是迴圈)把它compile成machine code後放在cache裡,目的就是避免一值在解譯重複的code來提升效能

Class Loader

把import的class讀進來, class會是被javac編譯完後得到的bytecode檔(*.class)

  1. 把Object存在Stack上
  2. 把Object得metadata(變數描述, method描述)存在method area

Class Loader也是採取lazy binding的方式, 需要時才去load library, 不會一開始就把所有import的library load進來

流程

這裡表示把一個object import進來的流程

  1. Loading: 把bytecode讀進來轉成object
  2. Verification: 檢查class的byte stream是否合法而不是惡意的java code, 詳情要看java byte code架構, 像是magic number之類的
  3. Preparation: static變數分配空間
  4. Resolution: 把const load進來
  5. Initialization: 把final以外的靜態變數賦予初始值

Reference: jyt blog

Garbace collection

JVM提供一套Garbage collection的功能,讓使用者創建object但沒有要用時會自動把記憶體空間free掉避免記憶體用量過多. 在C/C++裡就必須自己注意Garbage collection的部分, 要自己call free把空間給讓出來.

一種很簡單判斷要不要清掉object的方法就是看有多少人現在reference或指向他,如果現在沒人指向這個object就可以自動把它清除. 但一個問題點是,如果有兩個object他們彼此是循環reference(或者互相reference彼此), 那不管何時他們reference的數量只少有一個,就永遠不會清掉了QQ

因此就透過Java為了避免這樣互相指向的垃圾沒被清掉, 使用的方法為GC Root, 他會traverse他底下的object, 如果發現有object是從這個Root Object到不了的, 那就會被Garbage Collect掉. 至於GC Root有哪些則可以參考下面的連結

詳情可以參考jyt0523 blog

Dalvik VM(DVM)

因為手機上更要求的是要執行程式時要花更少的資源(電池店耗很快),所以在優化方面變得非常重要, 而JVM當時設計就是以在桌機跑為前提下設計的,這樣其實對於手機來講能量消耗會相對過重. 尤其到後來app可能大小都超過1G的狀況下, 要能更低電耗的執行App變成一個重點.

因此Dalvik VM就是一個基於JVM的架構下再新增一些feature的VM. 主要架構如下, 前半部編譯成的class bytecode後, 會再由DVM轉成dex的中介語言最後再包成apk由手機執行. 優化方面包括從stack-based的JVM變成register-based的DVM,而且dex的架構關係,整體檔案大小也會小很多, 能夠省下非常多的記憶體空間.

DVM一個很重要的process就是zygote, 他負責啟動system server跟執行Android的app, 一個跟JVM很大的差別他是preload class, 也就是他會把所有java class包成jar檔load到記憶體內,這樣就不用執行期間再去把library或資料load到記憶體,可以加快效能

Reference

towardsdatascience.com
zygote

Android Runtime(ART)

Dalviky在2014年被ART取代了, 之後App都會跑在ART上而不是JVM上

相較於DVM, ART是採用Ahead of Time(AoT) Compilation. 使用者在安裝App時,ART就會利用dex2oat把dex code直接轉譯成machine code存在手機內存裡, 因此相對於JVM DVM這種JIT模式, 他不用每次load進runtime裡都要重新轉譯bytecode.但第一次安裝時就會相對多花一點時間, 另外就是machine code相較於compact很好的dex bytecode, 他佔的空間相對更大, 會耗掉更多手機內存.

Reference

youtube intro

File format(apk,dex,smali code)

就我們上述所說, 一個java code會先被compile成java bytecode(class), 接著再轉換成DVM,ART要看的更compact的dex code, dex code再加上一些用到的資源(如圖片, 音樂, xml設定檔)這些會再把它們全部壓縮成一個apk檔, 因此apk檔就是一個zip檔, 裡面含有很多dex code跟一堆資源

基本上將dex,或apk檔deassemble的結果就會是smali code

smali code基本上就是一個介於readable跟bytecode之間的語言,它的好處是可以直接對他做改寫patch,接著直接在重新編譯回apk檔,等於我們可以用這種方式來改變app的函式或內容

Reference

apk and dex smali reverse

Reverse 工具

apktool

將apk檔的檔案內容轉成smali code跟把資源萃取出來, code本身蠻不好讀的

Dex2Jar

將DVM的dex code轉成java bytecode的形式,所以可以用這個配合jdgui把app轉成java code來看,但好像可能會有錯誤的地方QQ

ByteCode Viewer

可以同時看到java code跟smali code的對照

Frida

動態分析android app工具

Frida trace (2/5)
Frida trace (3/5)

ADB(Android Debugger)

$ adb connect "device ip"
$ adb -s "ip:port" shell  # give command
$ adb install ... # install apk to device 
$ adb uninstall ...
# adb shell input keyevent 3 # keycode to device
$ adb shell input text "hello" # send text to device
$ adb reboot

詳情可見ithome

Reference

reverse challenge