Btrace - a safe, dynamic tracing tool for the Java platform

Btrace 加载过程

client

  • Javac 加载 Btrace Verifier
  • 编译成字节码
  • 通过 attach 方法动态 attach 到 JVM Attach API
  • submit 提交 Btrace 编译后的字节码到对应 PID VM.(socket 通信)

    try {
    Client client = new Client(port, OUTPUT_FILE, PROBE_DESC_PATH,
        DEBUG, TRACK_RETRANSFORM, TRUSTED, DUMP_CLASSES, DUMP_DIR, statsdDef);
    if (! new File(fileName).exists()) {
        errorExit("File not found: " + fileName, 1);
    }
    byte[] code = client.compile(fileName, classPath, includePath);
    if (code == null) {
        errorExit("BTrace compilation failed", 1);
    }
    client.attach(pid, null, classPath);
    registerExitHook(client);
    if (con != null) {
        registerSignalHandler(client);
    }
    if (isDebug()) debugPrint("submitting the BTrace program");
    client.submit(fileName, code, btraceArgs,
        createCommandListener(client));
    } catch (IOException exp) {
    errorExit(exp.getMessage(), 1);
    }
/**
 * Attach the BTrace client to the given Java process.
 * Loads BTrace agent on the target process if not loaded
 * already.
 */
public void attach(String pid, String sysCp, String bootCp) throws IOException {
    try {
        String agentPath = "/btrace-agent.jar";
        String tmp = Client.class.getClassLoader().getResource("com/sun/btrace").toString();
        tmp = tmp.substring(0, tmp.indexOf('!'));
        tmp = tmp.substring("jar:".length(), tmp.lastIndexOf('/'));
        agentPath = tmp + agentPath;
        agentPath = new File(new URI(agentPath)).getAbsolutePath();
        attach(pid, agentPath, sysCp, bootCp);
    } catch (RuntimeException re) {
        throw re;
    } catch (IOException ioexp) {
        throw ioexp;
    } catch (Exception exp) {
        throw new IOException(exp.getMessage());
    }
}

agent

  • 添加 classpath
  • 开启一个 ServerSocket
  • 收到指令后, 主要有
    • 重写类 遍历当前class, 正则匹配到class
    • 转换类 替换原来的class

processClasspaths(libs);

// ignore String bootClassPath = argMap.get("bootClassPath");
nst.appendToBootstrapClassLoaderSearch(jf);
// ignore

// ignore String systemClassPath = argMap.get("systemClassPath");
inst.appendToSystemClassLoaderSearch(jf);
// ignore

ddPreconfLibs(libs);
private static synchronized void main(final String args, final Instrumentation inst) {
    if (Main.inst != null) {
        return;
    } else {
        Main.inst = inst;
    }

    try {
        loadArgs(args);
        // set the debug level based on cmdline config
        settings.setDebug(Boolean.parseBoolean(argMap.get("debug")));
        if (isDebug()) {
            debugPrint("parsed command line arguments");
        }
        parseArgs();

        int startedScripts = startScripts();

        String tmp = argMap.get("noServer");
        // noServer is defaulting to true if startup scripts are defined
        boolean noServer = tmp != null ? Boolean.parseBoolean(tmp) : hasScripts();
        if (noServer) {
            if (isDebug()) {
                debugPrint("noServer is true, server not started");
            }
            return;
        }
        Thread agentThread = new Thread(new Runnable() {
            @Override
            public void run() {
                BTraceRuntime.enter();
                try {
                    startServer();
                } finally {
                    BTraceRuntime.leave();
                }
            }
        });
        BTraceRuntime.initUnsafe();
        BTraceRuntime.enter();
        try {
            agentThread.setDaemon(true);
            if (isDebug()) {
                debugPrint("starting agent thread");
            }
            agentThread.start();
        } finally {
            BTraceRuntime.leave();
        }
    } finally {
        inst.addTransformer(transformer, true);
        Main.debugPrint("Agent init took: " + (System.nanoTime() - ts) + "ns");
    }
}
void retransformLoaded() throws UnmodifiableClassException {
    if (runtime != null) {
        if (probe.isTransforming() && settings.isRetransformStartup()) {
            ArrayList<Class> list = new ArrayList<>();
            debugPrint("retransforming loaded classes");
            debugPrint("filtering loaded classes");
            ClassCache cc = ClassCache.getInstance();
            for (Class c : inst.getAllLoadedClasses()) {
                if (c != null) {
                    cc.get(c);
                    if (inst.isModifiableClass(c) &&  isCandidate(c)) {
                        debugPrint("candidate " + c + " added");
                        list.add(c);
                    }
                }
            }
            list.trimToSize();
            int size = list.size();
            if (size > 0) {
                Class[] classes = new Class[size];
                list.toArray(classes);
                startRetransformClasses(size);
                if (isDebug()) {
                    for(Class c : classes) {
                        try {
                            debugPrint("Attempting to retransform class: " + c.getName());
                            inst.retransformClasses(c);
                        } catch (VerifyError e) {
                            debugPrint("verification error: " + c.getName());
                        }
                    }
                } else {
                    inst.retransformClasses(classes);
                }
            }
        }
        runtime.send(new OkayCommand());
    }
}
@Override
public synchronized byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
    if (probes.isEmpty()) return null;

    className = className != null ? className : "<anonymous>";

    if ((loader == null || loader.equals(ClassLoader.getSystemClassLoader())) && isSensitiveClass(className)) {
        if (isDebug()) {
            debugPrint("skipping transform for BTrace class " + className); // NOI18N
        }
        return null;
    }

    if (filter.matchClass(className) == Filter.Result.FALSE) return null;

    boolean entered = BTraceRuntime.enter();
    try {
        if (isDebug()) {
            debug.dumpClass(className.replace('.', '/') + "_orig", classfileBuffer);
        }
        BTraceClassReader cr = InstrumentUtils.newClassReader(loader, classfileBuffer);
        BTraceClassWriter cw = InstrumentUtils.newClassWriter(cr);
        for(BTraceProbe p : probes) {
            p.notifyTransform(className);
            cw.addInstrumentor(p, loader);
        }
        byte[] transformed = cw.instrument();
        if (transformed == null) {
            // no instrumentation necessary
            if (isDebug()) {
                debugPrint("skipping class " + cr.getJavaClassName());
            }
            return classfileBuffer;
        } else {
            if (isDebug()) {
                debugPrint("transformed class " + cr.getJavaClassName());
            }
            if (debug.isDumpClasses()) {
                debug.dumpClass(className.replace('.', '/'), transformed);
            }
        }
        return transformed;
    } catch (Throwable th) {
        debugPrint(th);
        throw th;
    } finally {
        if (entered) {
            BTraceRuntime.leave();
        }
    }
}

Brtace 使用

btrace {pid} Debug.java

方法内方法调用

@OnMethod(clazz = "/.*className.*/", method = "/.*methodName.*/",
        location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/", where = Where.AFTER))
    public static void methodCall(@ProbeClassName String probeClassName,
                                   @ProbeMethodName String probeMethod,
                                   @TargetInstance Object tInstance,
                                   @TargetMethodOrField String tMethod,
                                   @Duration long time) {
        if (time / 100000 > 0) {
            BTraceUtils.println(probeClassName + "#" + probeMethod + " \n\t " + tInstance + "#" + tMethod);
            BTraceUtils.println("cost: " + time / 100000);
        }
    }

方法调用盏

@OnMethod(clazz = "/.*className.*/", method = "/.*methodName.*/",
        location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/", where = Where.AFTER))
    public static void callStack(@ProbeClassName String probeClassName,
                                 @ProbeMethodName String probeMethod) {
        BTraceUtils.println("----------------" + probeClassName + "#" + probeMethod);
        BTraceUtils.jstack();
    }