Android ASM字節(jié)碼插樁(下)

由于內(nèi)容篇幅限制,緊接著上篇
http://www.reibang.com/p/c975081b43fd?u_atoken=de4d112a-6864-4703-ad69-f82922d505de&u_asession=01X_C9UMmFccLMXpIfjRM8FYXsq1g_CrgffyPiHHUpgZGBrWiw193wtdhvbyaJ4chEX0KNBwm7Lovlpxjd_P_q4JsKWYrT3W_NKPr8w6oU7K_OI3-HIFVcmZbMNunYRvYCslvTX-jMTLEIhdGFg3rxgWBkFo3NEHBv0PZUm6pbxQU&u_asig=05e9XZPq2Q7yJkKdk86YtiyFfiNPAdPSg-Voed-oPaPvY2QwBnCAT6a9bvIxBzozUzQDvsYuznxmEgCniYpEFR8BE5VfTwKn3vCIpIwgp7gRXcA22dI9OX-SjB0FJAYc2ww3dPL_NNCbygKg7jU_P9L1CtmgFQaXXOFbG5UybdQYj9JS7q8ZD7Xtz2Ly-b0kmuyAKRFSVJkkdwVUnyHAIJzet9GpoGT6ubHEJ3SNQzl3mpaIfenFB8MFcEbsDwJo4a6FPw117USKdEPc8n7HkzU-3h9VXwMyh6PgyDIVSG1W-IddwkY7v-CAh1IBL0sYO20tCNasH120WVmgCT0pzm_wUd5n3s6RPzq9rJ9ybbMWH4abeDNZysnYRhxU5ybSOYmWspDxyAEEo4kbsryBKb9Q&u_aref=%2FnUp0P8E0Oe%2BxeO5Bz5i0j215Eo%3D

3.6 執(zhí)行InjectUnitTest.java的test()方法查看InjectTest.class結(jié)果

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.xyaty.asmdemo;

public class InjectTest {
    public InjectTest() {
        long var1 = System.currentTimeMillis();
        long var3 = System.currentTimeMillis();
        System.out.println("execute: " + (var3 - var1) + "ms");
    }

    public static void main(String[] var0) throws InterruptedException {
        long var1 = System.currentTimeMillis();
        Thread.sleep(1000L);
        long var3 = System.currentTimeMillis();
        System.out.println("execute: " + (var3 - var1) + "ms");
    }

    public void methodA() {
        long var1 = System.currentTimeMillis();
        System.out.println("methodA");
        long var3 = System.currentTimeMillis();
        System.out.println("execute: " + (var3 - var1) + "ms");
    }
}

發(fā)現(xiàn)在每個方法中都加入了

long var1 = System.currentTimeMillis();
和
long var3 = System.currentTimeMillis();
System.out.println("execute: " + (var3 - var1) + "ms");

如果僅僅在main()方法才注入代碼荡碾,就需要引入自定義注解來標(biāo)記指定的方法

四谨读、引入自定義注解,標(biāo)記方法才注入代碼
4.1 注解類ASMTest.java坛吁,并通過javac編譯成ASMTest.class(略)

package com.xyaty.asmdemo;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * DESC   :
 */
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface ASMTest {

}

4.2 在InjectTest.java的main()方法上加上注解@ASMTest劳殖,標(biāo)記此方法铐尚,并通過javac生成class

package com.xyaty.asmdemo;

/**
 * DESC   :
 */
public class InjectTest {

    @ASMTest
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(1000);
    }

    public void methodA() {
        System.out.println("methodA");
    }
}

4.3 在上述MyMethodVisitor的下列方法中加入注解判斷

        @Override
        public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
            System.out.println("visitAnnotation===>methodName="+getName()+", descriptor="+descriptor);
            //如果方法的注解名字是@ASMTest,則給此方法注入代碼
            if ("Lcom/xyaty/asmdemo/ASMTest;".equals(descriptor)) {
                isInject = true;
            } else {
                isInject = false;
            }
            return super.visitAnnotation(descriptor, visible);
        }

        /**
         * 進(jìn)入方法插入內(nèi)容
         */
        @Override
        protected void onMethodEnter() {
            super.onMethodEnter();

            if (!isInject) {
                return;
            }
        }

@Override
        protected void onMethodExit(int opcode) {
            super.onMethodExit(opcode);

            if (!isInject) {
                return;
            }
}

InjectUnitTest.java完整代碼如下:

package com.xyaty.asmdemo;

import org.junit.Test;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.commons.Method;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
 * DESC   :
 */
public class InjectUnitTest {
    /**
     * 單元測試方法哆姻,右擊test()方法宣增,選擇run test()方法即可查看結(jié)果
     */
    @Test
    public void test() {
        try {
            //讀取待插樁的class
            FileInputStream fis = new FileInputStream(
                    new File("src/test/java/com/xyaty/asmdemo/InjectTest.class"));

            /**
             * 執(zhí)行分析與插樁
             * ClassReader是class字節(jié)碼的讀取與分析引擎
             */
            ClassReader classReader = new ClassReader(fis);
            // ClassWriter寫出器, COMPUTE_FRAMES表示自動計算棧幀和局部變量表的大小
            ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
            /**
             * 執(zhí)行分析矛缨,處理結(jié)果寫入classWriter, EXPAND_FRAMES表示棧圖以擴(kuò)展格式進(jìn)行訪問
             * 執(zhí)行插樁的代碼就在MyClassVisitor中實現(xiàn)
             */
            classReader.accept(new MyClassVisitor(Opcodes.ASM9, classWriter), ClassReader.EXPAND_FRAMES);

            //獲得執(zhí)行了插樁之后的字節(jié)碼數(shù)據(jù)
            byte[] bytes = classWriter.toByteArray();
            // 重新寫入InjectTest.class中(也可以寫入到其他class中箕昭,InjectTest1.class)灵妨,完成插樁
            FileOutputStream fos = new FileOutputStream(
                    new File("src/test/java/com/xyaty/asmdemo/InjectTest.class"));
            fos.write(bytes);
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public class MyClassVisitor extends ClassVisitor {

        public MyClassVisitor(int api, ClassVisitor classVisitor) {
            super(api, classVisitor);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            System.out.println("visitMethod==>name="+name);
            /**
             * 會輸出以下方法:
             * visitMethod==>name=<init>
             * visitMethod==>name=main
             */
            MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
            return new MyMethodVisitor(api, methodVisitor, access, name,descriptor);
        }
    }


    /**
     * 之所以繼承自AdviceAdapter,是因為AdviceAdapter是MethodVisitor的子類盟广,
     * AdviceAdapter封裝了指令插入方法闷串,更為直觀與簡單,
     * 要使用其中的onMethodEnter和 onMethodExit方法進(jìn)行字節(jié)碼插樁筋量,
     *
     * 繼承關(guān)系如下:
     * AdviceAdapter extends GeneratorAdapter
     * GeneratorAdapter extends LocalVariablesSorter
     * LocalVariablesSorter extends MethodVisitor
     */
    public class MyMethodVisitor extends AdviceAdapter {
        long start;
        private int startIdentifier;
        private boolean isInject = false;//是否注入代碼

        @Override
        public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
            System.out.println("visitAnnotation===>methodName="+getName()+", descriptor="+descriptor);
            //如果方法的注解名字是@ASMTest烹吵,則給此方法注入代碼
            if ("Lcom/xyaty/asmdemo/ASMTest;".equals(descriptor)) {
                isInject = true;
            } else {
                isInject = false;
            }
            return super.visitAnnotation(descriptor, visible);
        }

        protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
            super(api, methodVisitor, access, name, descriptor);
        }

        /**
         * 進(jìn)入方法插入內(nèi)容
         */
        @Override
        protected void onMethodEnter() {
            super.onMethodEnter();

            if (!isInject) {
                return;
            }

//            start = System.currentTimeMillis();
            /**
             * @Type owner 調(diào)用哪個類
             * @Method method 調(diào)用某個類的靜態(tài)方法(參數(shù)name: 方法名字,descriptor:方法中參數(shù)和方法返回值類型)
             */
            invokeStatic(Type.getType("Ljava/lang/System;"), new Method("currentTimeMillis", "()J"));
            //調(diào)用newLocal創(chuàng)建一個long類型的變量桨武,返回一個int類型索引identifier
            startIdentifier = newLocal(Type.LONG_TYPE);
            //保存到本地變量索引中肋拔,用一個本地變量接收上一步執(zhí)行的結(jié)果
            storeLocal(startIdentifier);
        }

        /**
         * 在方法結(jié)尾插入內(nèi)容
         * @param opcode
         */
        @Override
        protected void onMethodExit(int opcode) {
            super.onMethodExit(opcode);

            if (!isInject) {
                return;
            }

//            long end = System.currentTimeMillis();
//            System.out.println("execute: "+(end - start)+"ms");

            invokeStatic(Type.getType("Ljava/lang/System;"), new Method("currentTimeMillis", "()J"));
            //調(diào)用newLocal創(chuàng)建一個long類型的變量,返回一個int類型索引identifier
            int endIdentifier = newLocal(Type.LONG_TYPE);
            //保存到本地變量索引中呀酸,用一個本地變量接收上一步執(zhí)行的結(jié)果
            storeLocal(endIdentifier);

            //獲取System的靜態(tài)字段out,類型為PrintStream
            getStatic(Type.getType("Ljava/lang/System;"),
                    "out", Type.getType("Ljava/io/PrintStream;"));

            /**
             * "execute: "+(end - start)+"ms"實際是內(nèi)部創(chuàng)建StringBuilder來拼接
             * 源碼:NEW java/lang/StringBuilder
             * 創(chuàng)建一個對象StringBuilder
             */
            newInstance(Type.getType("Ljava/lang/StringBuilder;"));
            // dup壓入棧頂凉蜂,讓下面的INVOKESPECIAL 知道執(zhí)行誰的構(gòu)造方法創(chuàng)建StringBuilder
            dup();
            /**
             * 源碼:INVOKESPECIAL java/lang/StringBuilder.<init> ()V
             * 創(chuàng)建StringBuilder的構(gòu)造方法,用init來代替
             */
            invokeConstructor(Type.getType("Ljava/lang/StringBuilder;"),
                    new Method("<init>", "()V"));

            visitLdcInsn("execute: ");
            /**
             * 源碼:INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
             * 調(diào)用append方法
             */
            invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"),
                    new Method("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"));
            /**
             * 對結(jié)束時間和開始時間進(jìn)行減法操作
             * LLOAD 3 先加載結(jié)束時間
             * LLOAD 1 后加載開始時間
             * LSUB    執(zhí)行減法操作
             */
            loadLocal(endIdentifier);
            loadLocal(startIdentifier);
            //執(zhí)行減法操作性誉,返回long類型
            math(SUB, Type.LONG_TYPE);

            /**
             * 源碼:INVOKEVIRTUAL java/lang/StringBuilder.append (J)Ljava/lang/StringBuilder;
             * LDC "ms"
             */
            invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"),
                    new Method("append", "(J)Ljava/lang/StringBuilder;"));
            //拼接毫秒
            visitLdcInsn("ms");

            /**
             * 源碼:
             * INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
             * INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
             * INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
             */
            invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"),
                    new Method("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"));
            invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"),
                    new Method("toString", "()Ljava/lang/String;"));
            invokeVirtual(Type.getType("Ljava/io/PrintStream;"),
                    new Method("println", "(Ljava/lang/String;)V"));
        }

    }
}

再次運行InjectTest.class結(jié)果如下窿吩,可以看到只有main()方法注入了代碼

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.xyaty.asmdemo;

public class InjectTest {
    public InjectTest() {
    }

    @ASMTest
    public static void main(String[] var0) throws InterruptedException {
        long var1 = System.currentTimeMillis();
        Thread.sleep(1000L);
        long var3 = System.currentTimeMillis();
        System.out.println("execute: " + (var3 - var1) + "ms");
    }

    public void methodA() {
        System.out.println("methodA");
    }
}

到此結(jié)束。

參考文章:
https://blog.csdn.net/zenmela2011/article/details/125586333
https://blog.csdn.net/huangbin123/article/details/123322667

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末错览,一起剝皮案震驚了整個濱河市纫雁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倾哺,老刑警劉巖轧邪,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異羞海,居然都是意外死亡忌愚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門却邓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硕糊,“玉大人,你說我怎么就攤上這事“┠唬” “怎么了衙耕?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長勺远。 經(jīng)常有香客問我橙喘,道長,這世上最難降的妖魔是什么胶逢? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任厅瞎,我火速辦了婚禮,結(jié)果婚禮上初坠,老公的妹妹穿的比我還像新娘和簸。我一直安慰自己,他們只是感情好碟刺,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布锁保。 她就那樣靜靜地躺著,像睡著了一般半沽。 火紅的嫁衣襯著肌膚如雪爽柒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天者填,我揣著相機(jī)與錄音浩村,去河邊找鬼。 笑死占哟,一個胖子當(dāng)著我的面吹牛心墅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播榨乎,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼怎燥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蜜暑?” 一聲冷哼從身側(cè)響起铐姚,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎史煎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驳糯,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡篇梭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了酝枢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恬偷。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖帘睦,靈堂內(nèi)的尸體忽然破棺而出袍患,到底是詐尸還是另有隱情坦康,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布诡延,位于F島的核電站滞欠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏肆良。R本人自食惡果不足惜筛璧,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惹恃。 院中可真熱鬧夭谤,春花似錦、人聲如沸巫糙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽参淹。三九已至醉锄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間承二,已是汗流浹背榆鼠。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留亥鸠,地道東北人妆够。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像负蚊,于是被迫代替她去往敵國和親神妹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容