ASM插桩--多线程运行监测,详解系列文章

此时该方法中没有Thread的变量,因此我们要增加指令,在start指令前,增加创建变量(newLocal)、存储对象(Opcodes.ASTORE)、读取变量(Opcodes.ALOAD)指令即可。因此我们要记住在start方法前的指令,若指令直接为调用init构造方法的指令,则需要增加刚刚说的指令,若在start方法前,调用的是ALOAD指令,那我们只需要记住ALOAD指令中的var参数即可。

看下核心代码(稍后有全部代码):


 @Override

    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {

        MethodVisitor methodVisitor = this.cv.visitMethod(access, name, descriptor, signature, exceptions);

        boolean injectLambda = hasLambda(name);

        boolean isInject = injectMethods.contains(new Method(name, descriptor));

        if (!isInject && !injectLambda) {

            return methodVisitor;

        }

        return new AdviceAdapter(groovyjarjarasm.asm.Opcodes.ASM5, methodVisitor, access, name, descriptor) {



            int lastThreadVarIndex = -1; //记住thread变量的位置

            String lastThreadInstruction;  //上一条执行thread的instruction



            @Override

            public void visitVarInsn(int opcode, int var) {

                super.visitVarInsn(opcode, var);

                if(isInject) {

                    if (opcode == ALOAD) {

                        lastThreadInstruction = VISIT_VAR_INSN_LOAD;

                        lastThreadVarIndex = var;

                    }

                }

            }



            @Override

            public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {

                if (isInject) {

                    if (!THREAD.equals(owner) && !HANDLER_THREAD.equals(owner)) {

                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

                        return;

                    }

                    if (!"<init>".equals(name) && !"start".equals(name)) {

                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

                        return;

                    }

                    //如果走到了thread.start或者是handler thread.start方法

                    if ("<init>".equals(name)) {

                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

                        lastThreadInstruction = VISIT_METHOD_THREAD_INIT;

                    } else if ("start".equals(name)) {

                        //先检测之前thread是否被存储为本地变量

                        if (lastThreadInstruction.equals(VISIT_METHOD_THREAD_INIT)) {

                            //如果start的上一句话是init,则说明thread没有被存储为本地变量,那么创建本地变量

                            Type threadType = Type.getObjectType("java/lang/Thread");

                            lastThreadVarIndex = newLocal(threadType);

                            this.mv.visitVarInsn(ASTORE, lastThreadVarIndex);

                            this.mv.visitVarInsn(ALOAD, lastThreadVarIndex);

                        }

                        //继续调用start方法

                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

                        if (lastThreadVarIndex > 0) {

                            //拿到上一个thread变量

                            this.mv.visitVarInsn(ALOAD, lastThreadVarIndex);

                            //获取thread id值

//                        this.mv.visitMethodInsn(INVOKEVIRTUAL, owner, "getId", "()J", false);

                            this.mv.visitMethodInsn(INVOKESTATIC, "com/example/project/AopUtil", "addThread", "(Ljava/lang/Thread;)V", false);

                        }

                    }

                } else {

                    super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

                }

            }

        };

    }



复制代码 

讲完这里,大部分情况已经实现,接下来将ASM对lambda表达式的处理。


class Java8 {

  interface Logger {

    void log(String s);

  }



  public static void main(String... args) {

    sayHi(s -> System.out.println(s));

  }



  private static void sayHi(Logger logger) {

    logger.log("Hello!");

  }

}



上述代码在编译后变成:

 public class Java8 {

    interface Logger {

        void log(String s);

    }



    public static void main(String... args) {

		//这里使用 Logger 的实现类 Java8$1

        sayHi(s -> new Java8$1());

    }



    private static void sayHi(Logger logger) {

        logger.log("Hello!");

    }



    //方法体中的内容移到这里

    static void lambda$main$0(String str){

        System.out.println(str);

    }

}



public class Java8$1 implements Java8.Logger {

    public Java8$1(){

    }



    @Override

    public void log(String s) {

		//这里调用 Java8 方法的静态方法

        Java8.lambda$main$0(s);

    }

}

复制代码 

在main函数中,会有一个Opcodes.INVOKEDYNAMIC的指令(InvokeDynamicInsnNode),查看下该指令中的参数:

ASM插桩--多线程运行监测,详解系列文章

首先我们判断该指令中的desc是不是包含java/lang/Runnable,且name为run,如果匹配成功,则获取该方法被脱糖后真正的执行函数(bsmArgs[1]),在该函数中增加我们插桩代码。 查看具体代码:


public class HandlerThreadVisitor extends ClassVisitor {



    public static final String HANDLER_THREAD = "android/os/HandlerThread";

    public static final String THREAD = "java/lang/Thread";

    private final String VISIT_VAR_INSN_LOAD = "visitVarInsn-Load";

    private final String VISIT_METHOD_THREAD_INIT = "visitMethod-ThreadInit";



    private ClassNode classnode;

    ArrayList<Method> injectMethods = new ArrayList<>();

    ArrayList<String> lambdaMethods = new ArrayList<>();



    public HandlerThreadVisitor(ClassReader classReader, ClassVisitor classVisitor) {

        super(Opcodes.ASM5, classVisitor);

        classnode = new ClassNode();

        classReader.accept(classnode, 0);

    }



    @Override

    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {

        getInjectMethods(classnode);

        super.visit(version, access, name, signature, superName, interfaces);

    }



    @Override

    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {

        MethodVisitor methodVisitor = this.cv.visitMethod(access, name, descriptor, signature, exceptions);

        boolean injectLambda = hasLambda(name);

        boolean isInject = injectMethods.contains(new Method(name, descriptor));

        if (!isInject && !injectLambda) {

            return methodVisitor;

        }

        return new AdviceAdapter(groovyjarjarasm.asm.Opcodes.ASM5, methodVisitor, access, name, descriptor) {



            int lastThreadVarIndex = -1;

            String lastThreadInstruction;



            @Override

            protected void onMethodEnter() {

                super.onMethodEnter();

                if (injectLambda) {

                    this.mv.visitMethodInsn(INVOKESTATIC, "com/example/project/AopUtil", "runStart", "()V", false);

                }

            }



            @Override

            protected void onMethodExit(int opcode) {

                super.onMethodExit(opcode);

                if (injectLambda) {

                    this.mv.visitMethodInsn(INVOKESTATIC, "com/example/project/AopUtil", "runEnd", "()V", false);

                }

            }



            @Override

            public void visitVarInsn(int opcode, int var) {

                super.visitVarInsn(opcode, var);

                if(isInject) {

                    if (opcode == ALOAD) {

                        lastThreadInstruction = VISIT_VAR_INSN_LOAD;

                        lastThreadVarIndex = var;

                    }

                }

            }



            @Override

            public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {

                if (isInject) {

                    if (!THREAD.equals(owner) && !HANDLER_THREAD.equals(owner)) {

                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

                        return;

                    }

                    if (!"<init>".equals(name) && !"start".equals(name)) {

                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

                        return;

                    }

                    //如果走到了thread.start或者是handler thread.start方法

                    if ("<init>".equals(name)) {

                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

                        lastThreadInstruction = VISIT_METHOD_THREAD_INIT;

                    } else if ("start".equals(name)) {

                        //先检测之前thread是否被存储为本地变量

                        if (lastThreadInstruction.equals(VISIT_METHOD_THREAD_INIT)) {

                            //如果start的上一句话是init,则说明thread没有被存储为本地变量,那么创建本地变量

                            Type threadType = Type.getObjectType("java/lang/Thread");

                            lastThreadVarIndex = newLocal(threadType);

                            this.mv.visitVarInsn(ASTORE, lastThreadVarIndex);

                            this.mv.visitVarInsn(ALOAD, lastThreadVarIndex);

                        }

                        //继续调用start方法

                        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

                        if (lastThreadVarIndex > 0) {

                            //拿到上一个thread变量

                            this.mv.visitVarInsn(ALOAD, lastThreadVarIndex);

                            this.mv.visitMethodInsn(INVOKESTATIC, "com/example/project/AopUtil", "addThread", "(Ljava/lang/Thread;)V", false);

                        }

                    }

                } else {

                    super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

                }

            }

        };

    }



    public void getInjectMethods(ClassNode classnode) {

        for (MethodNode method : classnode.methods) {

            for (int i = 0; i < method.instructions.size(); i++) {

                AbstractInsnNode insnNode = method.instructions.get(i);

                if (insnNode.getOpcode() == Opcodes.NEW) {

                    TypeInsnNode methodInsnNode = (TypeInsnNode) insnNode;

                    if (HANDLER_THREAD.equals(methodInsnNode.desc) || THREAD.equals(methodInsnNode.desc)) {

                        injectMethods.add(new Method(method.name, method.desc));

                    }

                } else if (insnNode instanceof InvokeDynamicInsnNode) {

                    //判断是否为runnable的lambda表达式

                    if (((InvokeDynamicInsnNode) insnNode).desc.contains("Ljava/lang/Runnable;")

                            && ((InvokeDynamicInsnNode) insnNode).name.equals("run")) {

                        lambdaMethods.add(((Handle) ((InvokeDynamicInsnNode) insnNode).bsmArgs[1]).getName());

                    }

                }

            }

        }

    }



    private boolean hasLambda(String name){

        for (int i = 0; i < lambdaMethods.size(); i++) {

            if (lambdaMethods.get(i).contains(name)) {

                return true;

            }

        }

        return false;

    }



    static class Method {

        String name;

        String desc;



        public Method(String name, String desc) {

            this.name = name;

            this.desc = desc;

        }



        @Override

        public boolean equals(Object o) {

            Method temp = (Method) o;

            return name.equals(temp.name) && desc.equals(temp.desc);

        }

    }

}

复制代码 

至此全部代码已经讲完。看下我们AopUtils中的代码:


public class AopUtil {



    static HashSet<Long> allThread = new HashSet<>();

    static HashSet<Long> usedThread = new HashSet<>();

    static ConcurrentHashMap<String, Long> threadRunStartTime = new ConcurrentHashMap<>();



    public static void runStart() {

        logThreadUsage(Thread.currentThread(), true);

        threadRunStartTime.put(getKey(), System.currentTimeMillis());

    }



    private static String getKey(){

        String stackTrace = Log.getStackTraceString(new Throwable());

        stackTrace = stackTrace.split("\n\t")[3];   //获取到第几行执行run函数,作为key存储

        return stackTrace.substring(0,stackTrace.indexOf("("));

    }



    public static void runEnd() {

        String key = getKey();

        Long start = threadRunStartTime.get(key);

        if (start != null) {

            Log.d("ThreadAop-runCost", key + "cost time:" + (System.currentTimeMillis() - start));

            threadRunStartTime.remove(key);

        }

    }



    public static void addThread(Thread thread) {

        logThreadUsage(thread, false);

    }



    private static void logThreadUsage(Thread thread, boolean isFromRun) {

        if (thread.getName().equals("main")) {

            return;

        }

        synchronized (AopUtil.class) {

            if (usedThread == null) {

                usedThread = new HashSet<>();

            }

            if (isFromRun) {

                Log.d("ThreadAop-used1", "thread is used: " + thread.getId() + ", name is " + thread.getName());

                usedThread.add(thread.getId());

            }

            if (allThread == null) {

                allThread = new HashSet<>();

            }

            if (allThread.contains(thread.getId())) {

                Log.d("ThreadAop", Log.getStackTraceString(new Throwable()));

                return;

            }

            allThread.add(thread.getId());

            Log.d("ThreadAop-1", "current size:" + allThread.size() + "  add new thread:" + thread.getName() + ", usedThread:" + usedThread.size());

            Log.d("ThreadAop", Log.getStackTraceString(new Throwable()));

        }

    }

}

复制代码 

现在插件已经开发完成,在我们工程里引入:

首先在buildSrc模块中,添加如下文件:

ASM插桩--多线程运行监测,详解系列文章

然后在app模块中的build.gradle中增加如下代码:


plugins {

    id 'thread-inject'

}



或者是apply plugin: 'thread-inject'



# **学习分享**

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

**2021最新上万页的大厂面试真题**

![image](https://www.icode9.com/i/ll/?i=img_convert/95a67fb22a3d695280085d7165508367.png)

**七大模块学习资料:如NDK模块开发、Android框架体系架构...**

![image](https://www.icode9.com/i/ll/?i=img_convert/c664773193f90ca3b84deb5198f2eaa3.png)

2021大厂面试真题:

![image](https://www.icode9.com/i/ll/?i=img_convert/e0b0ad5f851dfc8092ea625a4a0e8437.png)

只有系统,有方向的学习,才能在短时间内迅速提高自己的技术,只有不断地学习,不懈的努力才能拥有更好的技术,才能在互联网行业中立于不败之地。

**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](

)**

> **本文已被[腾讯CODING开源托管项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://ali1024.coding.net/public/P7/Android/git)收录,自学资源及系列文章持续更新中...**
 {

    id 'thread-inject'

}



或者是apply plugin: 'thread-inject'



# **学习分享**

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

**2021最新上万页的大厂面试真题**

[外链图片转存中...(img-dpltVccT-1631247528229)]

**七大模块学习资料:如NDK模块开发、Android框架体系架构...**

[外链图片转存中...(img-ZTajorw9-1631247528230)]

2021大厂面试真题:

[外链图片转存中...(img-Ztk9Sk42-1631247528232)]

只有系统,有方向的学习,才能在短时间内迅速提高自己的技术,只有不断地学习,不懈的努力才能拥有更好的技术,才能在互联网行业中立于不败之地。

**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](

)**

> **本文已被[腾讯CODING开源托管项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://ali1024.coding.net/public/P7/Android/git)收录,自学资源及系列文章持续更新中...**
上一篇:Java ASM系列:(005)如何编写ASM代码


下一篇:ASM的组成部分