Gradle+ASM實戰(zhàn)——隱私方法問題徹底解決之理論篇

前言

需求背景

  • 第三方sdk會總是頻繁調(diào)用某些隱私方法锈至,比如MAC地址储狭,AndroidId等
  • 現(xiàn)在想要的需求是嵌纲,比如調(diào)用設備id的時候,會調(diào)用telephoneManger方法的getDeviceId,如果我們能找到調(diào)用getDeviceId的方法姆坚,然后將其替換成我們自己的方法或者將方法體清空屯掖,問題不就解決了嘛
  • 按程序員的本質(zhì),我本想去偷個懶纬傲,找個庫满败,也看過幾篇文章,但是都沒有達到自己的想要的,當前有關(guān)隱私方法調(diào)用或者隱私政策整改的文章叹括,有的也只是簡單的用別人的第三方如Epic算墨,AOP,而這些實際也達不到我們想要的效果汁雷,有的也只是說檢查隱私方法被那些方法調(diào)用
  • 所以就有了這篇文章和實現(xiàn)的庫净嘀,希望可以幫助到大家,徹底解決第三方sdk頻繁調(diào)用隱私方法被通報或者下架的問題侠讯,也可供學習ASM哦挖藏。
  • 通過 Gradle+ASM實戰(zhàn)——進階篇這篇文章我們知道我們實際只需要關(guān)注自己繼承的ClassVisitor即可

基礎知識

ClassVisitor

image.png
方法執(zhí)行的順序

我們直接看ClassVisitor的注解

image.png
  • []: 表示最多調(diào)用一次,可以不調(diào)用厢漩,但最多調(diào)用一次
  • ()|: 表示在多個方法之間膜眠,可以選擇任意一個,并且多個方法之間不分前后順序
  • *: 表示方法可以調(diào)用0次或多次

我們主要關(guān)注以下幾個方法

visit
(visitField |visitMethod)* 
visitEnd
四個方法
1溜嗜、visit方法,掃描類的時候會進入這里宵膨,最多被執(zhí)行一次
 /**
    * @param version 類版本 ASM4~ASM9可選
    * @param access 修飾符 如public、static炸宵、final
    * @param name 類名 如:com/peakmain/asm/utils/Utils
    * @param signature 泛型信息 
    * @param superName 父類
    * @param interfaces 實現(xiàn)的接口
    */
   @Override
   void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {}
2辟躏、visitField:訪問屬性的時候用到,用到不多焙压,用到的時候細說
    @Override
    FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        return super.visitField(access, name, descriptor, signature, value)
    }
3鸿脓、visitMethod:掃描到方法的時候調(diào)用抑钟,這也是我們主要介紹的方法涯曲,細節(jié)下面介紹
    /**
     * 掃描類的方法進行調(diào)用
     * @param access 修飾符
     * @param name 方法名字
     * @param descriptor 方法簽名
     * @param signature 泛型信息
     * @param exceptions 拋出的異常
     * @return
     */
    @Override
    MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        return  super.visitMethod(access, name, descriptor, signature, exceptions)
    }
4、visitEnd:這是這些visitXxx()方法之后最后一個執(zhí)行的方法在塔,最多被調(diào)用一次
   @Override
    void visitEnd() {
        super.visitEnd()
    }

MethodVisitor

通過調(diào)用ClassVisitor類的visitMethod()方法幻件,會返回一個MethodVisitor類型的對象

Method
public abstract class MethodVisitor {
    public void visitCode();

    public void visitInsn(final int opcode);
    public void visitIntInsn(final int opcode, final int operand);
    public void visitVarInsn(final int opcode, final int var);
    public void visitTypeInsn(final int opcode, final String type);
    public void visitFieldInsn(final int opcode, final String owner, final String name, final String descriptor);
    public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor,
                                final boolean isInterface);
    public void visitInvokeDynamicInsn(final String name, final String descriptor, final Handle bootstrapMethodHandle,
                                       final Object... bootstrapMethodArguments);
    public void visitJumpInsn(final int opcode, final Label label);
    public void visitLabel(final Label label);
    public void visitLdcInsn(final Object value);
    public void visitIincInsn(final int var, final int increment);
    public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels);
    public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels);
    public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions);

    public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type);

    public void visitMaxs(final int maxStack, final int maxLocals);
    public void visitEnd();
}

假設我們有以下方法

public static String getMeid(Context context) {//方法體
    TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        return manager.getMeid();
    }
    return "getMeid";
}

visitXxxInsn負責的就是這個方法的方法體內(nèi)的內(nèi)容,也就是指{}這個里面包含的屬性蛔溃,方法

方法調(diào)用的順序
(visitParameter)*
[visitAnnotationDefault]
(visitAnnotation | visitAnnotableParameterCount | visitParameterAnnotation | visitTypeAnnotation | visitAttribute)*
[
    visitCode
    (
        visitFrame |
        visitXxxInsn |
        visitLabel |
        visitInsnAnnotation |
        visitTryCatchBlock |
        visitTryCatchAnnotation |
        visitLocalVariable |
        visitLocalVariableAnnotation |
        visitLineNumber
    )*
    visitMaxs
]
visitEnd
分組

我們可以分成三組

  • 第一組:visitCode方法之前的方法绰沥,主要負責parameter篱蝇、annotation和attributes等內(nèi)容。對于我們來說主要關(guān)注visitAnnotation即可
  • 第二組:visitCode和visitMaxs方法之間的方法徽曲,這些之間的方法零截,主要負責方法的“方法體”內(nèi)的opcode內(nèi)容。visitCode代表方法體的開始秃臣,visitMaxs代表方法體的結(jié)束
  • 第三組:visitEnd()方法涧衙,是最后一個進行調(diào)用的方法
注意點

我們需要注意的是:

  • visitAnnotation:會被調(diào)用多次
  • visitCode:只會被調(diào)用一次
  • visitXxxInsn:可以調(diào)用多次,這些方法的調(diào)用奥此,就是在構(gòu)建方法的方法體
  • visitMaxs:只會被調(diào)用一次
  • visitEnd:只會被調(diào)用一次

AdviceAdapter

我們在項目中用了AdviceAdapter弧哎,那么AdviceAdapter的是什么呢?
AdviceAdapter實際是引入了兩個方法onMethodEnter()方法和onMethodExit()方法稚虎。并且這個類屬于MethodVisitor撤嫩,也就是我們要講的第三個方法

源碼分析
onMethodEnter
  @Override
  public void visitCode() {
    super.visitCode();
    if (isConstructor) {//判斷是否是構(gòu)造函數(shù)
      stackFrame = new ArrayList<>();
      forwardJumpStackFrames = new HashMap<>();
    } else {
      onMethodEnter();
    }
  }

實際還是調(diào)用了visitCode方法,只是處理了構(gòu)造函數(shù)(<init>())相關(guān)邏輯,如果直接使用visitCode()方法則可能導致<init>()方法出現(xiàn)錯誤

onMethodExit

[圖片上傳失敗...(image-f92fa5-1649898740300)]
我們會發(fā)現(xiàn)調(diào)用的方法是在visitInsn方法中蠢终,那肯定有人問序攘,為什么在visitInsn中而不是visitEnd里面呢?不是說它是最后一個方法調(diào)用寻拂。
假設我們有個方法是獲取AndroidId的

public static String getAndroidId(Context context) {
    return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
}

這個方法現(xiàn)在的正常ASM應該是

mv.visitCode()
mv.visitxxxInsn()
mv.visitInsn(AReturn)
mv.visitMaxs()
mv.visitEnd()

這時候我們在visitEnd的時候添加或者visitMaxs添加两踏,因為前面已經(jīng)Return了,所以后面是不會執(zhí)行的

方法初始化Frame

  • 在JVM Stack當中兜喻,是棧的結(jié)構(gòu)梦染,里面存儲的是frames;
  • 每一個frame空間可以稱之為Stack Frame朴皆。
  • 當調(diào)用一個新方法的時候帕识,就會在JVM Stack上分配一個frame空間
  • 當方法退出時,相應的frame空間也會JVM Stack上進行清除掉(出棧操作)遂铡。
  • 在frame空間當中肮疗,有兩個重要的結(jié)構(gòu),即local variables(局部變量表)和operand stack(操作數(shù)棧)
image.png

方法剛開始的時候扒接,操作數(shù)棧operand stack為空伪货,不需要存儲任何數(shù)據(jù),局部變量表需要考慮三個因素

  • 當前方法是否為static方法钾怔。如果當前方法是non-static方法碱呼,則需要在local variables索引為0的位置存在一個this變量;如果當前方法是static方法宗侦,則不需要存儲this愚臀。
  • 當前方法是否接收參數(shù)。方法接收的參數(shù)矾利,會按照參數(shù)的聲明順序放到local variables當中姑裂。
  • 方法的參數(shù)是否包含long或double馋袜,如果參數(shù)是long或者double類型,那么它在local variables占用兩個位置

Type

在.java文件中,我們經(jīng)常使用java.lang.Class類舶斧;而在.class文件中欣鳖,需要經(jīng)常用到internal name、type descriptor和method descriptor茴厉;而在ASM中观堂,org.objectweb.asm.Type類就是幫助我們進行兩者之間的轉(zhuǎn)換。

image.png

獲取Type的幾個方式

Type類有一個private的構(gòu)造方法呀忧,因此Type對象實例不能通過new關(guān)鍵字來創(chuàng)建师痕。但是,Type類提供了static method和static field來獲取對象而账。

  • 方式一:java.lang.class
Type type=Type.getType(String.class)
  • 方式二:descriptor
Type type = Type.getType("Ljava/lang/String;");
  • 方式三:internal name
Type type = Type.getObjectType("java/lang/String");
  • 方式四:static field
 Type type = Type.INT_TYPE;

常用的幾個方法

  • getArgumentTypes()方法胰坟,用于獲取“方法”接收的參數(shù)類型
  • getReturnType()方法,用于獲取“方法”返回值的類型
  • getSize()方法泞辐,用于返回某一個類型所占用的slot空間的大小
  • getArgumentsAndReturnSizes()方法笔横,用于返回方法所對應的slot空間的大小

實戰(zhàn)

上面的基礎知識大家學完了,那么就可以開始實戰(zhàn)了咐吼。下面所有的實戰(zhàn)都是繼承AdviceAdapter

實戰(zhàn)一:監(jiān)控方法的耗時時間

  • 假設有以下代碼:
public String getMethodTime(long var1) {

    try {
        Thread.sleep(1000L);
    } catch (InterruptedException var4) {
        var4.printStackTrace();
    }
    return "getMethod";
}
目標

通過注解來監(jiān)控獲取該方法的耗時時間,代碼的位置MonitorPrintParametersReturnValueAdapter

方案
- 每個方法動態(tài)添加一個long屬性吹缔,名字是方法的前面+timer_,如上面的方法定義的屬性是timer_getMethodTime
- 方法前后插入代碼锯茄,實現(xiàn)效果如下
public class TestActivity extends AppCompatActivity {
    public static long timer_getMethodTime;

    public String getMethodTime(long var1) {
        timer_getMethodTime -= System.currentTimeMillis();

        try {
            Thread.sleep(1000L);
        } catch (InterruptedException var4) {
            var4.printStackTrace();
        }

        timer_getMethodTime += System.currentTimeMillis();
        LogManager.println(timer_getMethodTime);
        return "getMethod";
    }
}
代碼實現(xiàn)
  • 首先我們定義一個注解類com.peakmain.sdk.annotation.LogMessage
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogMessage {
    /**
     * 是否打印方法耗時時間
     */
    boolean isLogTime() default false;

    /**
     *
     * 是否打印方法的參數(shù)和返回值
     */
    boolean isLogParametersReturnValue() default false;

}
  • 需要判斷方法是否有注解厢塘,毫無疑問我們用到的是visitAnnotation
AnnotationVisitor visitAnnotation(String descriptor, boolean b) {
    if (descriptor == "Lcom/peakmain/sdk/annotation/LogMessage;") {
        return new AnnotationVisitor(OpcodesUtils.ASM_VERSION) {
            @Override
            void visit(String name, Object value) {
                super.visit(name, value)
                if (name == "isLogTime") {
                    isLogMessageTime = (Boolean) value
                } else if (name == "isLogParametersReturnValue") {
                    isLogParametersReturnValue = (Boolean) value
                }
            }
        }
    }
    return super.visitAnnotation(descriptor, b)
}
  • 我們需要在方法體開始的時候插入屬性,因為是方法開始位置肌幽,所以肯定是visitCode方法
private String mFieldDescriptor = "J"
@Override
void visitCode() {
    if (isLogMessageTime && !OpcodesUtils.isNative(mMethodAccess) && !OpcodesUtils.isAbstract(mMethodAccess) && !OpcodesUtils.isInitMethod(mMethodName)) {
        FieldVisitor fv = mClassWriter.visitField(ACC_PUBLIC | ACC_STATIC, MethodFieldUtils.getTimeFieldName(mMethodName), mFieldDescriptor, null, null)
        if (fv != null) {
            fv.visitEnd()
        }
    }
    super.visitCode()
}
//獲取時間屬性
static String getTimeFieldName(String methodName) {
    return "timer_" + methodName
}

我們需要創(chuàng)建屬性晚碾,那就需要用到classWriter屬性,通過visitField去創(chuàng)建屬性喂急,需要注意的是格嘁,我們創(chuàng)建屬性之后,一定要調(diào)用visitEnd

  • 接下來就是方法體開始的時候廊移,添加timer_getMethodTime -= System.currentTimeMillis();糕簿,大家一定還記得AdviceAdapter的兩個方法把,沒錯就是onMethodEnter和onMethodExit兩個方法狡孔,因為是方法的開始懂诗,所以我們需要在onMethodEnter插入代碼
@Override
protected void onMethodEnter() {
    super.onMethodEnter()
    if (isLogMessageTime && !OpcodesUtils.isNative(mMethodAccess) && !OpcodesUtils.isAbstract(mMethodAccess) && !OpcodesUtils.isInitMethod(mMethodName)) {
        mv.visitFieldInsn(GETSTATIC, mClassName, MethodFieldUtils.getTimeFieldName(mMethodName), mFieldDescriptor)
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false)
        mv.visitInsn(LSUB)
        mv.visitFieldInsn(PUTSTATIC, mClassName, MethodFieldUtils.getTimeFieldName(mMethodName), mFieldDescriptor)
    }

}

其實代碼也很簡單:首先我們獲取自己在visitCode時定義的屬性timer_getMethod,隨后就是獲取當前時間,獲取當前時間是方法步氏,所以用的是visitMethodInsn,隨后進行相減响禽,相減之后我們需要將結(jié)果給屬性timer_getMethod,所以用到的還是visitFieldInsn

  • 方法結(jié)束的時候
@Override
protected void onMethodExit(int opcode) {
    if (isLogMessageTime && !OpcodesUtils.isNative(mMethodAccess) && !OpcodesUtils.isAbstract(mMethodAccess) && !OpcodesUtils.isInitMethod(mMethodName)) {
        mv.visitFieldInsn(GETSTATIC, mClassName, MethodFieldUtils.getTimeFieldName(mMethodName), mFieldDescriptor)
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false)
        mv.visitInsn(LADD)
        mv.visitFieldInsn(PUTSTATIC, mClassName, MethodFieldUtils.getTimeFieldName(mMethodName), mFieldDescriptor)
        mv.visitFieldInsn(GETSTATIC, mClassName, MethodFieldUtils.getTimeFieldName(mMethodName), mFieldDescriptor)
        mv.visitMethodInsn(INVOKESTATIC,LOG_MANAGER,"println","(J)V",false)
    }
    super.onMethodExit(opcode)
}

實戰(zhàn)二:方法替換

目標

我們以TelephonyManager的getDeviceId方法為例
看需求的代碼

public static String getDeviceId(Context context) {
    String tac = "";
    TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    if (manager.getDeviceId() == null || manager.getDeviceId().equals("")) {
        if (Build.VERSION.SDK_INT >= 23) {
            tac = manager.getDeviceId(0);
        }
    } else {
        tac = manager.getDeviceId();
    }
    return tac;
}

我們定義一個靜態(tài)類和方法com.peakmain.sdk.utils.ReplaceMethodUtils

public class ReplaceMethodUtils {

    public static String getDeviceId(TelephonyManager manager) {
        return "";
    }

    public static String getDeviceId(TelephonyManager manager, int slotIndex) {
        return "";
    }
}
  • 實現(xiàn)的目標是將manager.getDeviceId()替換成我們ReplaceMethodUtils的getDeviceId()
    這時候肯定有人問為什么將傳入TelephonyManager實例徒爹,我們看TelephonyManager的getDeviceId方法,我們發(fā)現(xiàn)是個非靜態(tài)方法,非靜態(tài)方法會怎樣卵牍?它會在局部變量表索引0的位置存在一個this變量闯团,我們替換肯定是要把它給消費掉,那同理如果方法是靜態(tài)方法就不需要添加this變量身弊。注意我們這里說的this變量是TelephonyManager這個實例。
代碼實現(xiàn)
class MonitorMethodCalledReplaceAdapter extends MonitorDefalutMethodAdapter {
    private String mMethodOwner = "android/telephony/TelephonyManager"
    private String mMethodName = "getDeviceId"
    private String mMethodDesc = "()Ljava/lang/String;"
    private String mMethodDesc1 = "(I)Ljava/lang/String;"

    private final int newOpcode = INVOKESTATIC
    private final String newOwner = "com/peakmain/sdk/utils/ReplaceMethodUtils"
    private final String newMethodName = "getDeviceId"
    private int mAccess
    private ClassVisitor classVisitor
    private String newMethodDesc = "(Landroid/telephony/TelephonyManager;)Ljava/lang/String;"
    private String newMethodDesc1 = "(Landroid/telephony/TelephonyManager;I)Ljava/lang/String;"

    /**
     * Constructs a new {@link AdviceAdapter}.
     *
     * @param mv @param access the method's access flags (see {@link Opcodes}).
     * @param name the method's name.
     * @param desc
     */
    MonitorMethodCalledReplaceAdapter(MethodVisitor mv, int access, String name, String desc, ClassVisitor classVisitor) {
        super(mv, access, name, desc)
        mAccess = access
        this.classVisitor = classVisitor
    }

    @Override
    void visitMethodInsn(int opcodeAndSource, String owner, String name, String descriptor, boolean isInterface) {
        if (mMethodOwner == owner && name == mMethodName) {
            if(descriptor == mMethodDesc){
                super.visitMethodInsn(newOpcode,newOwner,newMethodName,newMethodDesc,false)
            }else if(mMethodDesc1 == descriptor){
                super.visitMethodInsn(newOpcode,newOwner,newMethodName,newMethodDesc1,false)
            }

        } else {
            super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface)
        }
    }
}

我們發(fā)現(xiàn)代碼很簡單,就是在方法體visitMethodInsn方法里面去找當前的方法名字+owner+desc是否相等贮竟,如果是TelephoneManager.getDeviceId()我們就替換成自己的方法,直接將super.visitMethodInsn里面的參數(shù)換成我們要替換的就可以了

實戰(zhàn)三:清空方法體

class MonitorMethodCalledClearAdapter extends MonitorDefalutMethodAdapter {
    private String mMethodOwner = "android/telephony/TelephonyManager"
    private String mMethodName = "getDeviceId"
    private String mMethodDesc = "()Ljava/lang/String;"
    private String mMethodDesc1 = "(I)Ljava/lang/String;"
    private String mClassName

    private int mAccess
    ConcurrentHashMap<String, MethodCalledBean> methodCalledBeans = new ConcurrentHashMap<>()

    /**
     * Constructs a new {@link MonitorMethodCalledClearAdapter}.
     *
     * @param mv
     * @param access the method's access flags (see {@link Opcodes}).
     * @param name the method's name.
     * @param desc
     */
    MonitorMethodCalledClearAdapter(MethodVisitor mv, int access, String name, String desc, String className, ConcurrentHashMap<String, MethodCalledBean> methodCalledBeans) {
        super(mv, access, name, desc)
        mClassName = className
        mAccess = access
        this.methodCalledBeans=methodCalledBeans
    }

    @Override
    void visitMethodInsn(int opcodeAndSource, String owner, String name, String descriptor, boolean isInterface) {
        if (mMethodOwner == owner && name == mMethodName && (descriptor == mMethodDesc || mMethodDesc1 == descriptor)) {
            methodCalledBeans.put(mClassName + mMethodName + descriptor, new MethodCalledBean(mClassName, mAccess, name, descriptor))
            clearMethodBody(mv,mClassName,access,name,descriptor)
            return
        }
        super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
    }


    static void clearMethodBody(MethodVisitor mv, String className, int access, String name, String descriptor) {
        Type type = Type.getType(descriptor)
        Type[] argumentsType = type.getArgumentTypes()
        Type returnType = type.getReturnType()
        int stackSize = returnType.getSize()
        int localSize = OpcodesUtils.isStatic(access) ? 0 : 1
        for (Type argType : argumentsType) {
            localSize += argType.size
        }
        mv.visitCode()
        if (returnType.getSort() == Type.VOID) {
            mv.visitInsn(RETURN)
        } else if (returnType.getSort() >= Type.BOOLEAN && returnType.getSort() <= Type.DOUBLE) {
            mv.visitInsn(returnType.getOpcode(ICONST_1))
            mv.visitInsn(returnType.getOpcode(IRETURN))
        } else {
            mv.visitInsn(ACONST_NULL)
            mv.visitInsn(ARETURN)
        }
        mv.visitMaxs(stackSize, localSize)
        mv.visitEnd()
    }
}
  • 當我們調(diào)用到visitMethodInsn直接return的時候较剃,就可以可以清空方法體了
  • 但是我們?nèi)绻蟹祷刂档臅r候咕别,還是需要返回默認值,不然會直接報錯
  • 上面我們說過写穴,方法的返回類型和大小都在Type中惰拱,所以我們首先需要定義一個Type類型(ams的Type)
  • 判斷當前是否是靜態(tài)方法,如果是則接下來的參數(shù)按照順序從零開始放到局部變量表啊送,localSize大小就是參數(shù)大小+1偿短,如果不是則從1開始放到局部變量表localSize大小就是參數(shù)大小
  • stack的大小實際是返回值的大小就可

總結(jié)

  • 至此Gradle+ASM實戰(zhàn)——隱私方法問題徹底解決之理論篇就結(jié)束了,整體來說其實還是比較簡單的馋没,難點就是市場上對ASM的文章非常少昔逗,還有就是需要大家對ASM+Gradle熟悉使用
  • 大家是不是非常心動了呢,那就可以動手搞起來了篷朵。
  • 這個項目呢勾怒,我還在完善,后期我會開源成依賴庫并再寫一篇文章声旺,方便大家直接使用控硼,希望大家可以多關(guān)注關(guān)注
  • 最后再填上我的github地址:https://github.com/Peakmain/AsmActualCombat
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市艾少,隨后出現(xiàn)的幾起案子卡乾,更是在濱河造成了極大的恐慌,老刑警劉巖缚够,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幔妨,死亡現(xiàn)場離奇詭異,居然都是意外死亡谍椅,警方通過查閱死者的電腦和手機误堡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雏吭,“玉大人锁施,你說我怎么就攤上這事。” “怎么了悉抵?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵肩狂,是天一觀的道長。 經(jīng)常有香客問我姥饰,道長傻谁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任列粪,我火速辦了婚禮审磁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岂座。我一直安慰自己态蒂,他們只是感情好,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布费什。 她就那樣靜靜地躺著吃媒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吕喘。 梳的紋絲不亂的頭發(fā)上赘那,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音氯质,去河邊找鬼募舟。 笑死,一個胖子當著我的面吹牛闻察,可吹牛的內(nèi)容都是我干的拱礁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼辕漂,長吁一口氣:“原來是場噩夢啊……” “哼呢灶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钉嘹,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤鸯乃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后跋涣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缨睡,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年陈辱,在試婚紗的時候發(fā)現(xiàn)自己被綠了奖年。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡沛贪,死狀恐怖陋守,靈堂內(nèi)的尸體忽然破棺而出震贵,到底是詐尸還是另有隱情,我是刑警寧澤水评,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布猩系,位于F島的核電站,受9級特大地震影響之碗,放射性物質(zhì)發(fā)生泄漏蝙眶。R本人自食惡果不足惜季希,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一褪那、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧式塌,春花似錦博敬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至武学,卻和暖如春祭往,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背火窒。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工硼补, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人熏矿。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓已骇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親票编。 傳聞我的和親對象是個殘疾皇子褪储,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

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

  • 前言 前面一篇文章 ASM 簡介[http://www.reibang.com/p/a85e8f83fa14] ...
    Whyn閱讀 11,452評論 1 22
  • 前面幾篇文章介紹了 .class 文件的結(jié)構(gòu)、JVM 如何加載 .class 文件慧域、JVM 中如何執(zhí)行方法的調(diào)用和...
    lijiankun24閱讀 26,523評論 7 59
  • 預期目標 假如有一個HelloWorld類鲤竹,代碼如下: 我們想實現(xiàn)的預期目標:對于test()方法,在“方法進入”...
    舍是境界閱讀 2,500評論 1 3
  • 前言 之前一直使用greys及其內(nèi)部升級二次開發(fā)版來排查問題昔榴。最近周末剛好事情不多宛裕,作為一名程序員本能地想要弄懂這...
    LNAmp閱讀 8,282評論 1 11
  • 1、ASM概述 ASM是一個功能比較齊全的java字節(jié)碼操作與分析框架论泛,通過ASM框架揩尸,我們可以動態(tài)的生成類或者增...
    Joker_Wan閱讀 13,485評論 11 18