1. 程式人生 > 其它 >[轉]DVM執行 java 程式的工具

[轉]DVM執行 java 程式的工具

  1. jvm 執行位元組碼原理:java 程式執行時,是由一個 java 虛擬機器來解釋 java 位元組碼的,它將這些位元組碼翻譯成本地 CPU 的指令碼,然後執行。
  2. Android 執行指令碼原理:Android 應用程式打包成 dex 包後,通過系統程式 dalvikvm 建立一個虛擬機器來執行引數中指定的 java 類。相對於 java 而言,負責解釋並執行的就是一個虛擬機器,而對於 Linux 而言,這就是一個普通的程序,它與一個只有一行程式碼的 Hello World 的可執行程式無本質區別。
  3. Android 啟動一個虛擬機器的方法跟啟動任何一個可執行程式的方法是相同的,在命令列下輸入可執行程式的名稱,並在引數中指定要執行的 java 類即可。

dalvikvm

dalvikvm 作用:建立一個虛擬機器並執行指定的 java 類

dalvikvm 命令:dalvikvm -cp 檔案路徑 許可權類名
如:
dalvikvm -cp /data/app/Demo.dex Demo

例項演示:
  • JVM 執行 java 程式的過程:

    1. 編譯成二進位制檔案:javac Demo.java
    2. 翻譯成機器碼並執行:java Demo

        /**
         * 2019-05-18
         * java code for simple Demo
         */
    public class Demo {
        public static void main(String[] args) {
            System.out.println("Demo:: Hello Android");
        }
    }
    
  • DVM 執行 java 程式過程:對於 Android 而言,可執行程式碼需要轉化成可執行的 dex 優化檔案才能被系統載入執行。核心思想是將位元組碼檔案轉 dex 後,由 dvm 翻譯執行。

    1. 打包為 jar 包: jar cvf Demo.jar Demo.class
    2. jar 轉 dex:dx --dex --output= Demo1.jar Demo.jar
    3. 開一個 Android 模擬器,使用 Android 原生或 Genermotion 模擬器即可。
    4. 掛載裝置:adb root;adb remount
    5. 安裝程式:adb push Demo1.jar /data/app/Demo.dex
    6. 執行程式:adb shell dalvikvm -cp /data/app/Demo.dex Demo
  • DVM 執行 java 程式的過程中可能遇到的錯誤:

    • java sdk 和 dx 工具的要求版本不一致時,解決這種轉換問題一般發生在第二步,比如我本地的 java 版本是 1.8.0_101-b13,可以使用 27.0.0 中的 build-tools 下的 dx 工具。

        PARSE ERROR:
        unsupported class file version 52.0
        ...while parsing Demo.class
        1 error; aborting
    
    • push 了非 dex 優化檔案到系統,執行 dalvikvm 命令,出現如下錯誤:

        Unable to locate class 'Demo' java.lang.ClassNotFoundException: Didn't find class "Demo" on path: DexPathList[[zip file "/data/app/Demo.jar"],nativeLibraryDirectories=[/system/lib, /vendor/lib, /system/lib, /vendor/lib]]
    at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:380)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
    Suppressed: java.io.IOException: No original dex files found for dex location /data/app/Demo.jar
        at dalvik.system.DexFile.openDexFileNative(Native Method)
        at dalvik.system.DexFile.openDexFile(DexFile.java:367)
        at dalvik.system.DexFile.<init>(DexFile.java:112)
        at dalvik.system.DexFile.<init>(DexFile.java:77)
        at dalvik.system.DexPathList.loadDexFile(DexPathList.java:359)
        at dalvik.system.DexPathList.makeElements(DexPathList.java:323)
        at dalvik.system.DexPathList.makeDexElements(DexPathList.java:263)
        at dalvik.system.DexPathList.<init>(DexPathList.java:126)
        at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48)
        at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:64)
        at java.lang.ClassLoader.createSystemClassLoader(ClassLoader.java:224)
        at java.lang.ClassLoader.-wrap0(ClassLoader.java)
        at java.lang.ClassLoader$SystemClassLoader.<clinit>(ClassLoader.java:183)
        at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1097)
        Exception in thread "main" java.lang.ClassNotFoundException: Didn't find class "Demo" on path: DexPathList[[zip file "/data/app/Demo.jar"],nativeLibraryDirectories=[/system/lib, /vendor/lib, /system/lib, /vendor/lib]]
    at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:380)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
    Suppressed: java.io.IOException: No original dex files found for dex location /data/app/Demo.jar
        at dalvik.system.DexFile.openDexFileNative(Native Method)
        at dalvik.system.DexFile.openDexFile(DexFile.java:367)
        at dalvik.system.DexFile.<init>(DexFile.java:112)
        at dalvik.system.DexFile.<init>(DexFile.java:77)
        at dalvik.system.DexPathList.loadDexFile(DexPathList.java:359)
        at dalvik.system.DexPathList.makeElements(DexPathList.java:323)
        at dalvik.system.DexPathList.makeDexElements(DexPathList.java:263)
        at dalvik.system.DexPathList.<init>(DexPathList.java:126)
        at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48)
        at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:64)
        at java.lang.ClassLoader.createSystemClassLoader(ClassLoader.java:224)
        at java.lang.ClassLoader.-wrap0(ClassLoader.java)
        at java.lang.ClassLoader$SystemClassLoader.<clinit>(ClassLoader.java:183)
        at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1097)
    
    
  • 這種錯誤很常見,和之前應用程式載入類,載入函式的錯誤類似,這種錯誤如果在 APK 程式內發生。
    出現這種情況,一般是應用程式未簽名,引用的函式在系統中並不存在。而在 java 程式內,則是 java 程式不正確導致,需要確認此 jar 是否經過 dex 轉化或當前虛擬機器版本要求的系統優化。


dvz

dvz 作用:從 zygote 程序中孵化出一個新的程序,新的程序也是一個 Dalvik 虛擬機器。該程序與 dalvikvm 啟動的虛擬機器相比,區別在於該程序中已經預裝了 Framework 中的大部分類和資源。

dvz 命令:dvz -classpath 檔案路徑 許可權類名
如:
dvz -classpath /system/app/Demo.apk com.my.demo.DemoActivity

關於 Demo.apk,其程式碼如下:

package com.my.demo;

import android.app.*;

/**
 * 2019-05-18
 * java code for dvz Demo
 */
public class DemoActivity extends Activity {
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);
    }
    
    public static void main(String[] args) {
        System.out.println("DemoActivity::Hello Android:: DemoActivity");
    }
}

上面的 main 函式並不是該程式的入口,只是用來作為開發除錯。app 的主入口在 ActivityThread 中。

遺憾的是在系統中暫無 dvz 工具,暫時也未找到 dvz 的相關資料


app_process

app_process 作用:Framework 啟動過程中,載入 ZygoteInit.java 和 SystemServer.java,也可以用來除錯 java 程式

app_process 命令:app_process -Djava.class.path=檔案路徑 路徑 許可權類名

如:app_process -Djava.class.path=/data/app/Demo.dex /data/app Demo

  • app_process 執行 java 程式的過程:

    1. 建立 Demo.java
    2. 執行 javac Demo.java
    3. 打包 jar 檔案:java cvf Demo.jar Demo.class
    4. jar 轉 dex:dx --dex --output=Demo1.jar Demo.jar
    5. 掛載裝置:adb root;adb remount
    6. 將檔案 push 到模擬器:adb push Demo1.jar /data/app/Demo.dex
    7. 執行命令:adb shell app_process -Djava.class.path=/data/app/Demo.dex /data/app Demo
  • app_process 命令引數:app_process [java-options] cmd-dir start-class-name [options]


由 app_process 想到的?
  • 系統命令封裝:am, pm, wm, svc ...
  • 自定義命令封裝:my_tool 適配不同平臺工具差異。
  • 深度定製 zygoteframework,原因是:app_process 是初始化 zygote 的入口,屬於安卓系統和Framework 啟動的一個關鍵點。
  • 優化開機流程,減少開機過程耗時。

補充:
  • 補充1:
    • 執行 app_process,每次執行 Java 程式時,系統都會給其分配一個pid,並且程序名是app_process,通過追蹤 pidppid,可以發現 fork 程序的大致流程為:/init --> /sbin/adbd -–> /system/bin/sh --> app_process,不同的安卓版本,會有差異,dalvikart 也不完全相同。例如在 Android 9.0 的裝置上,流程則變為 /init --> zygote -->當前程式
  • 補充2:
    • app_process 啟動的 Java 程式擁有shell級別的許可權,所以像系統中的 am, pm, wm, svc 等程式才能執行。在 /system/bin 目錄下執行:cat /system/bin/am 可以看到它其實並非一個二進位制檔案,而是一個可執行的 shell 指令碼,由 app_process 執行了 com.android.commands.am.Am "$@",其執行原理是在當前視窗,將引數傳遞給 jvm,找到 Am 類的主函式,進行執行。

      console:/ # cat /system/bin/am
      #!/system/bin/sh
      
      if [ "$1" != "instrument" ] ; then
          cmd activity "$@"
      else
          base=/system
          export CLASSPATH=$base/framework/am.jar
          exec app_process $base/bin com.android.commands.am.Am "$@"
      fi
      console:/ #
      
    • 在 java 程式中執行 shellcmd 是可行的,分享一個可執行的案例,使用命令為:pm -l

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * 2019-05-18
 * java code for shell command demo, pm -l 
 */
public class DemoShellCmd {
    public static void main(String[] args) {
        System.out.println("");
        System.out.println("");
        System.out.println("DemoShellCmd::PMS 程式開始執行...");
        String cmd = "pm -l";
        try {
            Process exec = Runtime.getRuntime().exec(cmd);
            BufferedReader br = new BufferedReader(new InputStreamReader(exec.getInputStream()));
            String readLine=br.readLine();
            while(readLine!=null){
                System.out.println(readLine);
                readLine=br.readLine();
            }
            br.close();
            exec.destroy();
            exec=null;
            System.out.println("");
            System.out.println("");
            System.out.println("DemoShellCmd::PMS 程式執行完成");
        } catch (IOException e) {
            System.out.println("DemoShellCmd::PMS 程式執行異常");
            e.printStackTrace();
        }
    }
}
  • 補充3:
    • 程序狀態如何檢視: cat /proc/$pid/status 即可,其中 /proc 下遍佈著系統執行過程中,所有程序 id 的資訊,此資料夾下可以瀏覽你關注的進行狀態,記憶體資訊,執行緒資訊...

小結:
  1. 如上總結了 DVM 執行 java 程式的三種工具,也是谷歌早期除錯 java 程式的重要工具。其中 dvz 工具暫未發現系統中有整合,也少有此工具的相關介紹。重要的是要學會使用 dalvikvimapp_process 工具,並對 app_process 的觸發和流程做拓展,在原始碼中理解它的工作過程。

  2. 歡迎大家下方評論區留言,拍磚,交流。

  3. qq 郵箱: [email protected]

作者:迷你小豬
連結:https://www.jianshu.com/p/471cfc2a0032
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。