qunarcorp / bistoury

Bistoury是去哪儿网的java应用生产问题诊断工具,提供了一站式的问题诊断方案
GNU General Public License v3.0
4.04k stars 830 forks source link

在线debug时,是否可以展示方法返回值? #167

Open 724027069 opened 1 year ago

bigfish1913 commented 1 year ago

这是来自QQ邮箱的自动回复邮件。    来信已经收到 感谢您的来信  付威

724027069 commented 1 year ago

我想到一种解决方案:在qunar.tc.bistoury.instrument.client.debugger.DebuggerMethodVisitor中新增onMethodExit方法,该方法继承自org.objectweb.asm.commons.AdviceAdapter,可以再目标方法返回值之前获取到返回值,由于每个方法都会插桩,所以我们需要记录断点所在方法名称(qunar.tc.bistoury.instrument.client.debugger.DebuggerMethodVisitor#methodUniqueName),用于后续比较,代码如下:

    @Override
    protected void onMethodExit(int opcode) {
        if (opcode == RETURN) {
            super.visitLdcInsn("void method");
        } else if (opcode == ATHROW) {
            super.visitLdcInsn("Exception happened");
        } else if (opcode == ARETURN) {
            // 复制操作数栈顶的1个数值,并将复制结果压入操作数栈顶,此时操作数栈上有2个连续相同的数值
            // 复制的目的是,多出来的这个数值用来打印到控制台,原来栈顶的数值不受影响
            dup();
        } else if (opcode == LRETURN || opcode == DRETURN) {
            // 因为double和long类型(64bit)占2个slot,所以要复制操作数栈顶的2个数值,并将其压入操作数栈顶
            dup2();
            // 对栈顶的数据按照返回值类型进行包装,并用包装好的值替换原来栈顶的这个数值
            // double类型会用Double.valueOf()进行包装,long类型会用Long.valueOf()进行包装
            box(Type.getReturnType(methodDesc));
        } else {
            dup();
            // 这里排除上面几种返回值类型,这里的opcode应该是 FRETURN 和 IRETURN
            // 对相应类型的数据进行Float.valueOf()或者Integer.valueOf()包装
            box(Type.getReturnType(methodDesc));
        }

        boxingIfShould(methodDesc);

        // 将methodName压入栈顶
        super.visitLdcInsn(methodUniqueName);
        // 所以要调用swap方法,将栈顶最顶端的两个数值互换
        swap();

        // 因为这里打印时,需要参数是Object类型,所以上面的2个box(getReturnType())必须有,目的是将基本数据类型转成包装类
        // 否则打印时,传的是基本数据类型,不是Object一定会报错
        super.visitMethodInsn(INVOKESTATIC, SPY_NAME, BistourySpys1.FILL_RETURN_OBJ,
            "(Ljava/lang/String;Ljava/lang/Object;)V", false);
    }

DefaultSnapshotStore类中:

    @Override
    public void fillReturnObj(String breakpointId, String methodUniqueName, Object returnObj) {
        Snapshot snapshot = snapshotCache.get(breakpointId);
        if (snapshot == null) {
            logger.debug("fill returnObj error, {}, breakpoint not exist now", breakpointId);
            return;
        }
        if (Strings.isNullOrEmpty(snapshot.getMethodUniqueName()) || !snapshot.getMethodUniqueName().equals(methodUniqueName)) {
            return;
        }

        logger.debug("start fill returnObj, {}", breakpointId);
        String stacktraceRecord = DebugJsonWriter.write(returnObj);
        snapshot.setReturnObj(stacktraceRecord);
        logger.debug("end fill returnObj, {}, {}", breakpointId, stacktraceRecord);
    }
xleiy commented 1 year ago

bistoury这边是没有支持的,可以看下你这种方案是否可行。现在得到方法的调用方看方法返回值