阿里巴巴Arthas源碼分析--jad反編譯原理

Arthas是阿里巴巴開源的Java應用診斷利器,本文介紹Arthas 3.1.1版本里jad命令的實現原理。

  • https://github.com/alibaba/arthas

jad命令介紹

  • https://alibaba.github.io/arthas/jad.html

jad即java decompiler,把JVM已加載類的字節碼反編譯成Java代碼。比如反編譯String類:

$ jad java.lang.String

ClassLoader:

Location:

/*
* Decompiled with CFR .
*/
package java.lang;

import java.io.ObjectStreamField;
...
public final class String
implements Serializable,
Comparable<String>,
CharSequence {
    private final char[] value;
    private int hash;
    private static final long serialVersionUID = -6849794470754667710L;
    private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
    public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();

    public String(byte[] arrby, int n, int n2) {
        String.checkBounds(arrby, n, n2);
        this.value = StringCoding.decode(arrby, n, n2);
    }
...

獲取到類的字節碼

反編譯有兩部分工作:

  1. 獲取到字節碼

  2. 反編譯爲Java代碼

那麼怎麼從運行的JVM裏獲取到字節碼?

最常見的思路是,在classpaths下面查找,比如 ClassLoader.getResource("java/lang/String.class"),但是這樣子查找到的字節碼不一定對。比如可能有多個衝突的jar,或者有Java Agent修改了字節碼。

ClassFileTransformer機制

從JDK 1.5起,有一套ClassFileTransformer的機制,Java Agent通過Instrumentation註冊ClassFileTransformer,那麼在類加載或者retransform時就可以回調修改字節碼。

顯然,在Arthas裏,要增強的類是已經被加載的,所以它們的字節碼都是在retransform時被修改的。
通過顯式調用Instrumentation.retransformClasses(Class<?>...)可以觸發回調。

Arthas裏增強字節碼的watch/trace/stack/tt等命令都是通過ClassFileTransformer來實現的。

ClassFileTransformer的接口如下:

public interface ClassFileTransformer {
    byte[]
    transform(  ClassLoader         loader,
                String              className,
                Class<?>            classBeingRedefined,
                ProtectionDomain    protectionDomain,
                byte[]              classfileBuffer)
        throws IllegalClassFormatException;

看到這裏,讀者應該猜到jad是怎麼獲取到字節碼的了:

  1. 註冊一個ClassFileTransformer

  2. 通過Instrumentation.retransformClasses觸發回調

  3. 在回調的transform函數裏獲取到字節碼

  4. 刪掉註冊的ClassFileTransformer

使用cfr來反編譯

獲取到字節碼之後,怎樣轉換爲Java代碼呢?

以前大家使用比較多的反編譯軟件可能是jd-gui,但是它不支持JDK8的lambda語法和一些新版本JDK的特性。

後面比較成熟的反編譯軟件是cfr,它以前是不開源的。直到最近的0.145版本,作者終於開源了,可喜可賀。地址是

  • https://github.com/leibnitz27/cfr

在Arthas jad命令裏,通過調用cfr來完成反編譯。

jad命令的缺陷

99%的情況下,jad命令dump下來的字節碼是準確的,除了一些極端情況。

  1. 因爲JVM裏註冊的ClassFileTransformer可能有多個,那麼在JVM裏運行的字節碼裏,可能是被多個ClassFileTransformer處理過的。

  2. 觸發了retransformClasses之後,這些註冊的ClassFileTransformer會被依次回,上一個處理的字節碼傳遞到下一個。
    所以不能保證這些ClassFileTransformer第二次執行會返回同樣的結果。

  3. 有可能一些ClassFileTransformer會被刪掉,觸發retransformClasses之後,之前的一些修改就會丟失掉。

所以目前在Arthas裏,如果開兩個窗口,一個窗口執行watch/tt等命令,另一個窗口對這個類執行jad,那麼可以觀察到watch/tt停止了輸出,實際上是因爲字節碼在觸發了retransformClasses之後,watch/tt所做的修改丟失了。

精確獲取JVM內運行的java字節碼的辦法

如果想精確獲取到JVM內運行的Java字節碼,可以使用這個dumpclass工具,它是通過sa-jdi.jar來實現的,保證dump下來的字節碼是JVM內所運行的。

  • https://github.com/hengyunabc/dumpclass

總結

總結jad命令的工作原理:

  • 通過註冊ClassFileTransformer,再觸發retransformClasses來獲取字節碼

  • 通過cfr來反編譯

  • ClassFileTransformer的方式來獲取字節碼有一定缺陷

  • 通過dumpclass工具可以精確獲取字節碼

jad命令可以在線上快速檢查運行時的代碼,並且結合mc/redefine命令可以熱更新代碼:

鏈接

  • Arthas開源:https://github.com/alibaba/arthas

  • dumpclass: https://github.com/hengyunabc/dumpclass

  • https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/ClassFileTransformer.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章