一種基于class字節(jié)碼的快速掃描技術(shù)

日常工作中有時(shí)候可能會(huì)遇到需要統(tǒng)計(jì)某個(gè)方法的使用地方会喝,項(xiàng)目里有沒(méi)有代碼調(diào)用了某些違規(guī)函數(shù)陡叠,某類到底被哪些類給依賴了等等問(wèn)題,這種需求通常會(huì)通過(guò)寫python腳步去掃描整個(gè)項(xiàng)目代碼肢执,這種方式優(yōu)點(diǎn)是簡(jiǎn)單枉阵,但缺點(diǎn)也十分的明顯,就是效率很低预茄,因?yàn)槟_步通常都是一行行的文本內(nèi)容掃描然后通過(guò)正則去匹配兴溜,即便是遇到了注釋或空行也照樣會(huì)進(jìn)行匹配,顯然的效率極低耻陕,其實(shí)對(duì)于這類問(wèn)題還可以使用字節(jié)碼掃描的方式去實(shí)現(xiàn)拙徽,效率十分的高,即便是要在十幾萬(wàn)個(gè)類中掃描某方法也僅僅是幾秒的時(shí)間就能完成了诗宣。

前言

所謂字節(jié)碼掃描就是通過(guò)讀取class文件膘怕,解析class文件結(jié)構(gòu),最后通過(guò)檢索class的內(nèi)部結(jié)構(gòu)召庞,如常量池岛心、方法表、局部變量表等等裁眯,去找我們想要的信息布讹。舉個(gè)列子京革,譬如我們想要知道class A 被哪些類引用了,只需要掃描所有class常量池的CONSTANT_Class_info結(jié)構(gòu)就可以了,又譬如我們想知道某方法(譬如是getUserInfo)它被哪些代碼依賴了宾符,也是可以通過(guò)掃描常量池里面的CONSTANT_Methodref_info結(jié)構(gòu)可以獲取到嚼沿。在增量編譯系統(tǒng)里蒸绩,假如A類被修改了祭钉,那么使用到A類的代碼也得被找出來(lái)重新編譯,早期的Gradle版本實(shí)現(xiàn)增量編譯功能它改,就是通過(guò)這種掃描class字節(jié)碼結(jié)構(gòu)的方式去查找類依賴關(guān)系的(18年看過(guò)Gradle 3.x的源碼是這樣實(shí)現(xiàn)的疤孕,現(xiàn)在的版本不清楚有沒(méi)有修改過(guò))。

認(rèn)識(shí)class內(nèi)部結(jié)構(gòu)

假設(shè)我們有下面Demo代碼

package com.nls.lib;
/**
 * Create by nls on 2022/1/19
 * description: Test
 */
public class Test {
    public int a;

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

編譯成class文件后央拖,我們用二進(jìn)制編輯工具打開(kāi)是這樣的

一般人都看不懂這串16進(jìn)制數(shù)據(jù)祭阀,實(shí)際上這些16進(jìn)制字節(jié)流數(shù)據(jù)是按照一定的格式去組合的鹉戚,它對(duì)應(yīng)的格式如下:

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

其中u4代表的是4字節(jié)長(zhǎng)度,u2专控,代表的是2字節(jié)長(zhǎng)度抹凳,如此類推。
JDK也提供了javap工具伦腐,可以把上面的16進(jìn)制二進(jìn)制流轉(zhuǎn)換成可讀的class結(jié)構(gòu)赢底,命令如下:
javap -verbose Test.class

  • 魔法數(shù)
    class文件最開(kāi)始4字節(jié)內(nèi)容是魔法數(shù),固定內(nèi)容CAFE BABE柏蘑,所有class文件都是一樣幸冻,譬如例子里面的

  • 版本號(hào)
    跟在魔法數(shù)后面的是副版本號(hào)(2字節(jié)長(zhǎng)度)跟主版本號(hào)(2字節(jié)長(zhǎng)度),Demo里面的是

    副版本號(hào)是0x00咳焚,主版本號(hào)是0x34洽损,代表著是jdk1.8.0

  • 常量池
    跟在版本號(hào)后面的是常量池長(zhǎng)度,用2字節(jié)長(zhǎng)度標(biāo)識(shí)革半,Demo這里是0x21趁啸,轉(zhuǎn)換成10進(jìn)制是33,意思是常量池里面有33種數(shù)據(jù)

常量池說(shuō)白了就是個(gè)數(shù)組督惰,里面存放了各種常量數(shù)據(jù),常量池里面的常量類型大概有以下十幾種旅掂,這里直接給出:

每種常量類型對(duì)應(yīng)著不一樣的數(shù)據(jù)結(jié)構(gòu)赏胚,但不管是哪種常量,它的第一個(gè)字節(jié)都是tag字段商虐,用來(lái)表示此常量是什么類型觉阅,譬如Demo里面常量池里的第一個(gè)常量tag字段是0x0A,轉(zhuǎn)換成10進(jìn)制是10秘车,對(duì)應(yīng)的就是CONSTANT_Methodref_info常量典勇,格式如下:

CONSTANT_Methodref_info {
    u1 tag;   //10
    u2 class_index; //類索引
    u2 name_and_type_index; //字段名索引
}

第一個(gè)字段是類型,固定為10叮趴,第二個(gè)字段是類索引id割笙,通過(guò)這個(gè)索引id我們可以找到聲明此方法的類,這里的類索引號(hào)為0x6眯亦,代表著此方法的類信息在常量池里的第6個(gè)位置伤溉,方法名索引是0x13,轉(zhuǎn)換成10進(jìn)制就是19妻率,代表著此方法的方法名信息在常量池里面的第19個(gè)位置乱顾。對(duì)照上面的常量池結(jié)構(gòu)可以找到此CONSTANT_Methodref_info常量描述的正是構(gòu)造函數(shù)init方法。當(dāng)類引用了一個(gè)外部方法時(shí)宫静,常量池里就會(huì)多一條CONSTANT_Methodref_info常量數(shù)據(jù)走净。

跟在第一個(gè)CONSTANT_Methodref_info常量后面的是0x9券时,對(duì)應(yīng)的是CONSTANT_Fieldref_info常量,格式如下:

CONSTANT_Fieldref_info {
    u1 tag;     //9
    u2 class_index; //類索引
    u2 name_and_type_index; //方法名索引
}

每個(gè)字段的含義跟上面的CONSTANT_Methodref_info結(jié)構(gòu)類似伏伯,其中0x14轉(zhuǎn)換成10進(jìn)制是20橘洞,0x15轉(zhuǎn)換成10進(jìn)制21,代表的也是常量池里面的索引id舵鳞,對(duì)照著上面的常量池表我們可以得出這條CONSTANT_Fieldref_info常量數(shù)據(jù)描述的正是System類的out字段震檩。當(dāng)類引用了一個(gè)外部字段時(shí),常量池里就會(huì)多一條CONSTANT_Fieldref_info常量數(shù)據(jù)蜓堕。

通過(guò)這種方式就可以把常量池里面的所有常量都解析出來(lái)了抛虏,這里只是拋磚引玉,介紹一下常量池的解析方式套才,剩下的常量解析這里就不再一一分析了迂猴。

  • 訪問(wèn)標(biāo)志
    跟在常量池后面是類訪問(wèn)標(biāo)志,Java提供了下面幾種標(biāo)志類型:
標(biāo)志名稱 標(biāo)志值 含義
ACC_PUBLIC 0x0001 public 類型
ACC_FINAL 0x0010 final 類型
ACC_SUPER 0x0020 JDK1.0.2之后編譯出來(lái)的類默認(rèn)會(huì)帶上此標(biāo)志
ACC_INTERFACE 0x0200 接口標(biāo)志
ACC_ABSTRACT 0x0400 abstract抽象類型標(biāo)志
ACC_SYNTHETIC 0x1000 非代碼生成標(biāo)志
ACC_ANNOTATION 0x2000 注解類型標(biāo)志
ACC_ENUM x4000 枚舉類型標(biāo)志

譬如上面的Demo是public類型背伴,所以它的訪問(wèn)標(biāo)志就是 ACC_PUBLIC | ACC_SUPER 對(duì)應(yīng)的16進(jìn)制值就是0x21了沸毁,如下:
  • 類索引 父類索引 接口索引
    在訪問(wèn)標(biāo)志后面是本類索引 父類索引以及接口索引,0x05是本類的索引id傻寂,意思是在常量池里的第5個(gè)位置有本類的索引信息息尺,對(duì)照著上面的常量池結(jié)構(gòu)可以知道本類正是com/nls/lib/Test 同理在常量池里的第6個(gè)位置是本類的父類索引信息,這里是java/lang/Object 由于Demo里的Test類并沒(méi)有實(shí)現(xiàn)任何接口疾掰,所以跟在后面的接口索引信息是空

  • 字段表
    跟在接口索引表后面是字段表搂誉,字段表記錄了類里面定義的所有的字段信息。首先是2字節(jié)長(zhǎng)度字段用來(lái)描述字段數(shù)静檬,Demo里面的Test類只有一個(gè)字段炭懊,所以這里是0x01

    在class字節(jié)碼內(nèi)部用這樣的結(jié)構(gòu)來(lái)描述類里面定義的每一種字段類型

field_info {
    u2             access_flags; //訪問(wèn)類型
    u2             name_index; // 字段名索引
    u2             descriptor_index; //字段簽名索引
    u2             attributes_count; // 屬性數(shù)
    attribute_info attributes[attributes_count]; //屬性表
}

字段的訪問(wèn)類型又有以下幾種

標(biāo)志名稱 標(biāo)志值 含義
ACC_PUBLIC 0x0001 public 類型
ACC_PRIVATE 0x0002 private 類型
ACC_PROTECTED 0x0004 protected 類型
ACC_STATIC 0x0008 靜態(tài)類型
ACC_FINAL 0x0010 final 類型
ACC_VOLATILE 0x0040 volatile 類型
ACC_TRANSTENT 0x0080 transient 類型
ACC_SYNCHETIC 0x1000 編譯器自動(dòng)產(chǎn)生
ACC_ENUM ACC_ENUM 枚舉類型

Demo的Test類的a字段類型為public 對(duì)應(yīng)的值就是0x01,跟在后面的是名字索引跟簽名索引等等信息拂檩,代表的就是在常量池里面的索引號(hào)侮腹,如下:


對(duì)照著上面的常量池表結(jié)構(gòu),索引id 7的位置是a稻励,就是本字段名字父阻,索引id 8的位置是I 代表著是本字段是int類型,類里面每增加一個(gè)字段字段表里面就會(huì)多一條field_info 結(jié)構(gòu)數(shù)據(jù)

  • 方法表
    跟在字段表后面的是方法表钉迷,方法表記錄的是本類的所有方法信息至非,先用2字段長(zhǎng)度記錄方法表大小,這里是0x02糠聪,代表著Demo里面有兩個(gè)方法(除了代碼里面的test1方法 還有編譯器自動(dòng)生成的init方法)

在class字節(jié)碼里方法的定義結(jié)構(gòu)定義如下:

method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

方法結(jié)構(gòu)定義是跟字段結(jié)構(gòu)的定義是一樣的荒椭,這里重點(diǎn)介紹一下方法結(jié)構(gòu)里面的屬性表,方法是有方法實(shí)體的舰蟆,方法里面的代碼會(huì)以Code 屬性被存放在屬性表里趣惠。Code屬性結(jié)構(gòu)定義如下:

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

其中code字段數(shù)組存放的就是編譯后的方法實(shí)體代碼了狸棍,code里面也是一些列的字節(jié)流,這些16進(jìn)制的字節(jié)流對(duì)應(yīng)著一條條的jvm指令

實(shí)戰(zhàn)

通過(guò)上面的分析味悄,我們對(duì)class的內(nèi)部結(jié)構(gòu)有了一定的了解了草戈,Java會(huì)把編譯后的類成員、類方法侍瑟、以及方法實(shí)體整理歸類好并且放到同一個(gè)地方去唐片,這大大的方便了代碼的檢索任務(wù),譬如需要搜索類成員只需要遍歷class字節(jié)碼的字段表就可以了涨颜,需要搜索某方法只需要遍歷class字節(jié)碼的方法表就可以了费韭,需要搜索類依賴了哪些外部類只需要遍歷常量池里的CONSTANT_Class_info常量就可以 了。

Case one 類依賴掃描

手y在做32位uid轉(zhuǎn)64位任務(wù)時(shí)庭瑰,需要掃描出哪些地方使用了Uint32類星持,前面介紹class文件結(jié)構(gòu)時(shí)我們已經(jīng)介紹了,當(dāng)類依賴了某個(gè)外部類時(shí)弹灭,常量池里就會(huì)有一條與之對(duì)應(yīng)的CONSTANT_Class_info常量督暂,因?yàn)槲覀兛梢栽O(shè)計(jì)以下方案:

  • 把整個(gè)項(xiàng)目的所有class字節(jié)碼讀取到ClassPool里面
  • 遍歷ClassPool里面的所有class對(duì)象
  • 遍歷class常量池里面的CONSTANT_Class_info常量,名字是Uint32就是要查找目標(biāo)類

首先我們需要讀取.class文件穷吮,然后按照上面介紹的class文件結(jié)構(gòu)逻翁,逐字節(jié)的把class內(nèi)容解析出來(lái),class文件結(jié)構(gòu)比較復(fù)雜捡鱼,這里我們可以使用ASM的ClassReader或proguard的ProgramClassReader等等現(xiàn)成的解析邏輯卢未。

ClassPool 本質(zhì)上就是個(gè)Map結(jié)構(gòu),把讀取出來(lái)的class對(duì)象以key value的形式保存起來(lái)

public class ClassPool
{
    private final TreeMap<String, Clazz> classes = new TreeMap<>();

    public void addClass(Clazz clazz)
    {
        addClass(clazz.getName(), clazz);
    }

    public void classesAccept(ClassVisitor classVisitor)
    {
        Iterator iterator = classes.values().iterator();
        while (iterator.hasNext())
        {
            Clazz clazz = (Clazz)iterator.next();
            clazz.accept(classVisitor);
        }
    }
}

最后是遍歷所有class對(duì)象的常量池結(jié)構(gòu)堰汉,拿到CONSTANT_Class_info常量后比較它的名字是否Uint32即可,代碼大致如下:

/**
 * Create by nls on 2022/5/30
 * description: Uint32ClassMatcher
 */
class Uint32ClassMatcher(private val visitor: ClassVisitor) : ClassVisitor, ConstantVisitor {

    override fun visitAnyConstant(clazz: Clazz?, constant: Constant?) {

    }

    override fun visitAnyClass(clazz: Clazz) {
        clazz.constantPoolEntriesAccept(this)
    }

    override fun visitClassConstant(clazz: Clazz, classConstant: ClassConstant) {
        val className = classConstant.getName(clazz)
        if (className == "com/yy/mobile/yyprotocol/core/Uint32" ||
            className == "tv/athena/live/streambase/services/core/Uint32"
        ) {
            visitor.visitAnyClass(clazz)
        }
    }
}
//調(diào)用地方如下
fun execute(classPool: ClassPool) {
    val memberMatcher = Uint32MemberMatcher()
    val start = System.currentTimeMillis()
    classPool.accept(AllClassVisitor(FilterClassVisitor(Uint32ClassMatcher(this))))
    val end = System.currentTimeMillis()
    println("scan finish total ${classPool.size()}, match: ${matchClassPool.size()}, cost: ${end-start}")
    //matchClassPool.accept(AllClassVisitor(memberMatcher))
    //memberMatcher.print()
}

最終掃描了總共四萬(wàn)多個(gè)類伟墙,匹配的類有一千多條翘鸭,整個(gè)掃描過(guò)程也是僅僅花了不到300毫秒的時(shí)間,效率可以說(shuō)是極高的
Case two 方法依賴掃描

在研究proguard優(yōu)化時(shí)需要知道項(xiàng)目里哪些地方使用了反射去實(shí)例化類對(duì)象戳葵,反射實(shí)例化類有兩種方式就乓,一種是調(diào)java/lang/reflect/ConstructornewInstance方法,另外一種是調(diào)用 java/lang/ClassnewInstance方法拱烁,前面我們已經(jīng)提到過(guò)了生蚁,當(dāng)一個(gè)類引用了外部類的某個(gè)方法是,class常量池里會(huì)有一條與之對(duì)應(yīng)的CONSTANT_Methodref_info常量戏自,因此我們可以設(shè)計(jì)以下方案:

  • 把整個(gè)項(xiàng)目的所有class字節(jié)碼讀取到ClassPool里面
  • 遍歷ClassPool里面的所有class對(duì)象
  • 遍歷class常量池里面的CONSTANT_Methodref_info常量邦投,名字是newInstance并且類名是ConstructorClass類的就是要查找目標(biāo)類

由于原理跟代碼跟上面的類搜索相似,這里直接給出核心代碼

/**
 * Create by nls on 2022/5/29
 * description: ReflectionClassMatcher
 */
class ReflectionClassMatcher : ClassMatcher {

    override fun match(clazz: Clazz, refConstant: MethodrefConstant): Boolean {
        val className = refConstant.getClassName(clazz)
        val methodName = refConstant.getName(clazz)
        if (className == "java/lang/reflect/Constructor" || className == "java/lang/Class") {
            return methodName == "newInstance"
        }
        return false
    }
}

方法引用常量里面會(huì)有方法名跟類名的索引擅笔,我們只需要判斷下類名跟方法名便能找到自己想要的志衣,最終執(zhí)行效果如下屯援,掃描了四萬(wàn)多個(gè)類,四十多萬(wàn)個(gè)方法念脯,耗時(shí)才300多毫秒狞洋,效率是相當(dāng)?shù)捏@人的
Case three 依賴鏈掃描

前面介紹的兩種掃描方式都比較簡(jiǎn)單,都是通過(guò)直接掃描常量池就可以達(dá)到效果了绿店,但掃描常量池只能得到有依賴某個(gè)外部類吉懊,某個(gè)外部方法等信息,卻并不能知道外部類或外部方法是被本類的哪些方法引入進(jìn)來(lái)的假勿,下面我們來(lái)分析下這種場(chǎng)景該如何進(jìn)行掃描借嗽。

上面的Demo類test1方法依賴了println方法废登,我們反編譯看下test1方法的內(nèi)部指令

jvm的指令集里淹魄,方法調(diào)用會(huì)用到invoke系列指令(invokestatic invokespecial invokeinterface invokevirtual invokedynamic) 指令后面的操作數(shù)便是需要調(diào)用的方法在常量池里的索引甲锡。前面介紹class文件結(jié)構(gòu)時(shí)我們已經(jīng)提到過(guò)了,方法體編譯后的代碼會(huì)以二進(jìn)制流的形式被保存到Code屬性里羽戒,因此我們可以設(shè)計(jì)以下方案:

  • 掃描class常量池找到調(diào)用方法并且記錄下它的索引id
  • 掃描class的方法表找到類的所有方法
  • 掃描方法表里每個(gè)方法的Code屬性
  • 遍歷Code屬性里面的所有指令集缤沦,找出invoke指令跟指令操作數(shù)
  • 指令操作數(shù)為第一步掃描出來(lái)的索引id,那么就建立一條調(diào)用關(guān)系并且記錄
  • 一直的遞歸繼續(xù)掃

手y的頻道模版入口是LiveTemplateView::onCreate易稠,但是在調(diào)用到onCreate前面有一套很復(fù)雜的上下滑框架缸废,假如我們并不熟悉那套框架的代碼邏輯,又想快速的找到進(jìn)頻道的調(diào)用邏輯是怎么樣的驶社,這時(shí)候我們就可以通過(guò)class掃描的方法把調(diào)用鏈給掃描出來(lái)
第一步我們先遍歷常量池企量,找到引用方法的索引id

override fun visitProgramClass(programClass: ProgramClass) {
    kotlin.run {
        //1.遍歷常量池,找到目標(biāo)調(diào)用方法在常量池里的索引id.
        programClass.constantPool.forEachIndexed { index, constant ->
            constant?.accept(programClass, this)
            if (methodFind) {
                methodRefIndex = index
                return@run
            }
        }
    }
    //省略部分代碼
}

第二步遍歷方法表以及每個(gè)方法的Code屬性

override fun visitProgramClass(programClass: ProgramClass) {
    //省略部分代碼
    //2.遍歷方法表,事實(shí)上類里面可能有多個(gè)方法都調(diào)用了外部引入的方法,
    //這里為了方便演示,找到一處調(diào)用就return. 只檢測(cè)一條的調(diào)用鏈。
    kotlin.run {
        programClass.methods.forEach {
            //跳過(guò)橋接方法,免得引起死循環(huán).
            if (it.accessFlags and AccessConstants.BRIDGE == 0) {
                it.accept(programClass, this)
                if (methodFind) {
                    listener.onFindMethod(programClass.name, it.getName(programClass))
                    return@run
                }
            }
        }
    }
}

override fun visitProgramMethod(programClass: ProgramClass, programMethod: ProgramMethod) {
    //3.遍歷方法屬性表
    programMethod.attributesAccept(programClass, this)
}

override fun visitCodeAttribute(clazz: Clazz, method: Method, codeAttribute: CodeAttribute) {
    //4.我們只管Code屬性,其他屬性不管它.
    codeAttribute.instructionsAccept(clazz, method, this)
}

第三步遍歷Code屬性里面的所有指令亡电,我們只關(guān)心常量指令届巩,如果常量指令的操作數(shù)為第一步索引到的id,那么此方法就是調(diào)用方法

override fun visitConstantInstruction(
    clazz: Clazz,
    method: Method,
    codeAttribute: CodeAttribute,
    offset: Int,
    constantInstruction: ConstantInstruction
) {
    //5. 我們只管常量指令,其他指令不管它.
    if (constantInstruction.constantIndex == methodRefIndex) {
        methodFind = true
    }
}

最后把找出來(lái)的類名方法名作為新的參數(shù)一直遞歸掃描就可以把整個(gè)調(diào)用鏈給檢索出來(lái)了份乒,效果如下:

總結(jié)

雖然某些場(chǎng)景下掃描項(xiàng)目代碼可以通過(guò)寫腳本的方式去實(shí)現(xiàn)恕汇,但基于class字節(jié)碼的掃描方式會(huì)更加快,效率更加高或辖,而且能做的事情更加廣瘾英,如敏感方法使用,無(wú)用代碼掃描等等颂暇,這些都是腳本方式無(wú)法實(shí)現(xiàn)的缺谴。

附錄

  • 常量池常量結(jié)構(gòu)
    CONSTANT_Class_info常量
CONSTANT_Class_info {
    u1 tag;//7
    u2 name_index;
}

CONSTANT_Fieldref_info常量

CONSTANT_Fieldref_info {
    u1 tag;     //9
    u2 class_index;
    u2 name_and_type_index;
}

CONSTANT_Methodref_info常量

CONSTANT_Methodref_info {
    u1 tag;   //10
    u2 class_index;
    u2 name_and_type_index;
}

CONSTANT_InterfaceMethodref_info常量

CONSTANT_InterfaceMethodref_info {
    u1 tag;    //11
    u2 class_index;
    u2 name_and_type_index;
}

CONSTANT_String_info常量

CONSTANT_String_info {
    u1 tag;    //8
    u2 string_index;
}

CONSTANT_Integer_info常量

CONSTANT_Integer_info {
    u1 tag;  //3
    u4 bytes;
}

CONSTANT_Float_info常量

CONSTANT_Float_info {
    u1 tag;  //4
    u4 bytes;
}

CONSTANT_Long_info常量

CONSTANT_Long_info {
    u1 tag;   //5
    u4 high_bytes;
    u4 low_bytes;
}

CONSTANT_Double_info常量

CONSTANT_Double_info {
    u1 tag;   //6
    u4 high_bytes;
    u4 low_bytes;
}

CONSTANT_NameAndType_info常量

CONSTANT_NameAndType_info {
    u1 tag;  //12
    u2 name_index;
    u2 descriptor_index;
}

CONSTANT_Utf8_info常量

CONSTANT_Utf8_info {
    u1 tag;  //1
    u2 length;
    u1 bytes[length];
}

CONSTANT_MethodHandle_info常量

CONSTANT_MethodHandle_info {
    u1 tag;  //15
    u1 reference_kind;
    u2 reference_index;
}

CONSTANT_MethodType_info常量

CONSTANT_MethodType_info {
    u1 tag;   //16
    u2 descriptor_index;
}

CONSTANT_InvokeDynamic_info常量

CONSTANT_InvokeDynamic_info {
    u1 tag;  //18
    u2 bootstrap_method_attr_index;
    u2 name_and_type_index;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市耳鸯,隨后出現(xiàn)的幾起案子瓣赂,更是在濱河造成了極大的恐慌榆骚,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件煌集,死亡現(xiàn)場(chǎng)離奇詭異妓肢,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)苫纤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門碉钠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人卷拘,你說(shuō)我怎么就攤上這事喊废。” “怎么了栗弟?”我有些...
    開(kāi)封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵污筷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我乍赫,道長(zhǎng)瓣蛀,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任雷厂,我火速辦了婚禮惋增,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘改鲫。我一直安慰自己诈皿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布像棘。 她就那樣靜靜地躺著稽亏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缕题。 梳的紋絲不亂的頭發(fā)上措左,一...
    開(kāi)封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音避除,去河邊找鬼。 笑死胸嘁,一個(gè)胖子當(dāng)著我的面吹牛瓶摆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播性宏,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼群井,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了毫胜?” 一聲冷哼從身側(cè)響起书斜,我...
    開(kāi)封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诬辈,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后荐吉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焙糟,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年样屠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了穿撮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡痪欲,死狀恐怖悦穿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情业踢,我是刑警寧澤栗柒,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站知举,受9級(jí)特大地震影響瞬沦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜负蠕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一蛙埂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧遮糖,春花似錦绣的、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至赛不,卻和暖如春惩嘉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背踢故。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工文黎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人殿较。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓耸峭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親淋纲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子劳闹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345