BTrace实现浅析

之前的文章中我们简单介绍了BTrace的用法,今天我们通过源代码来看看BTrace是如何实现的。

从BTrace的启动脚本中可以找到相关入口,

${JAVA_HOME}/bin/java -Dcom.sun.btrace.probeDescPath=. -Dcom.sun.btrace.dumpClasses=false -Dcom.sun.btrace.debug=false -Dcom.sun.btrace.unsafe=false -cp ${BTRACE_HOME}/build/btrace-client.jar:${TOOLS_JAR}:/usr/share/lib/java/dtrace.jar com.sun.btrace.client.Main $*

所以Main-Class就是com.sun.btrace.client.Main了,来看看它的main方法,

   public static void main(String[] args) {
        ////////////////////////////////////////
        // 1. 参数解析
        ////////////////////////////////////////
        if (args.length < 2) {
            usage();
        }

        int port = BTRACE_DEFAULT_PORT;
        String classPath = ".";
        String includePath = null;

        int count = 0;
        boolean portDefined = false;
        boolean classpathDefined = false;
        boolean includePathDefined = false;

        for (;;) {
            if (args[count].charAt(0) == '-') {
                if (args.length <= count+1) {
                    usage();
                }
                if (args[count].equals("-p") && !portDefined) {
                    try {
                        port = Integer.parseInt(args[++count]);
                        if (isDebug()) debugPrint("accepting port " + port);
                    } catch (NumberFormatException nfe) {
                        usage();
                    }
                    portDefined = true;
                } else if ((args[count].equals("-cp") ||
                    args[count].equals("-classpath"))
                    && !classpathDefined) {
                    classPath = args[++count];
                    if (isDebug()) debugPrint("accepting classpath " + classPath);
                    classpathDefined = true;
                } else if (args[count].equals("-I") && !includePathDefined) {
                    includePath = args[++count];
                    if (isDebug()) debugPrint("accepting include path " + includePath);
                    includePathDefined = true;
                } else {
                    usage();
                }
                count++;
                if (count >= args.length) {
                    break;
                }
            } else {
                break;
            }
        }

        if (! portDefined) {
            if (isDebug()) debugPrint("assuming default port " + port);
        }

        if (! classpathDefined) {
            if (isDebug()) debugPrint("assuming default classpath '" + classPath + "'");
        }

        if (args.length < (count + 1)) {
            usage();
        }

        String pid = args[count];
        String fileName = args[count + 1];
        String[] btraceArgs = new String[args.length - count];
        if (btraceArgs.length > 0) {
            System.arraycopy(args, count, btraceArgs, 0, btraceArgs.length);
        }

        try {
            Client client = new Client(port, PROBE_DESC_PATH,
                DEBUG, TRACK_RETRANSFORM, UNSAFE, DUMP_CLASSES, DUMP_DIR);
            if (! new File(fileName).exists()) {
                errorExit("File not found: " + fileName, 1);
            }

            ////////////////////////////////////////
            // 2. 编译btrace脚本
            ////////////////////////////////////////
            byte[] code = client.compile(fileName, classPath, includePath);
            if (code == null) {
                errorExit("BTrace compilation failed", 1);
            }

            ////////////////////////////////////////
            // 3. attach到目标VM
            ////////////////////////////////////////
            client.attach(pid);
            registerExitHook(client);
            if (con != null) {
                registerSignalHandler(client);
            }
            if (isDebug()) debugPrint("submitting the BTrace program");

            ////////////////////////////////////////
            // 4. 提交btrace请求
            ////////////////////////////////////////
            client.submit(fileName, code, btraceArgs,
                createCommandListener(client));
        } catch (IOException exp) {
            errorExit(exp.getMessage(), 1);
        }
    }

BTrace脚本的编译细节(包括脚本解析等)我们暂不深究。看第3步之前先来看第4步提交的请求,com.sun.btrace.client.Client#submit

    /**
     * Submits the compiled BTrace .class to the VM
     * attached and passes given command line arguments.
     * Receives commands from the traced JVM and sends those
     * to the command listener provided.
     */
    public void submit(String fileName, byte[] code, String[] args,
            CommandListener listener) throws IOException {
        if (sock != null) {
            throw new IllegalStateException();
        }
        submitDTrace(fileName, code, args, listener);
        try {
            if (debug) {
                debugPrint("opening socket to " + port);
            }
            ////////////////////////////////////////
            // 与目标VM通过Socket进行通信
            ////////////////////////////////////////
            sock = new Socket("localhost", port);
            oos = new ObjectOutputStream(sock.getOutputStream());
            if (debug) {
                debugPrint("sending instrument command");
            }
            ////////////////////////////////////////
            // 给目标VM发送InstrumentCommand
            ////////////////////////////////////////
            WireIO.write(oos, new InstrumentCommand(code, args));
            ois = new ObjectInputStream(sock.getInputStream());
            if (debug) {
                debugPrint("entering into command loop");
            }
            commandLoop(listener);
        } catch (UnknownHostException uhe) {
            throw new IOException(uhe);
        }
    }

现在我们再来看第3步,也就是com.sun.btrace.client.Client#attach

    /**
     * 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) 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, null, null);
        } catch (RuntimeException re) {
            throw re;
        } catch (IOException ioexp) {
            throw ioexp;
        } catch (Exception exp) {
            throw new IOException(exp.getMessage());
        }
    }
    /**
     * Attach the BTrace client to the given Java process.
     * Loads BTrace agent on the target process if not loaded
     * already. Accepts the full path of the btrace agent jar.
     * Also, accepts system classpath and boot classpath optionally.
     */
    public void attach(String pid, String agentPath, String sysCp, String bootCp) throws IOException {
        try {
            VirtualMachine vm = null;
            if (debug) {
                debugPrint("attaching to " + pid);
            }
            vm = VirtualMachine.attach(pid);
            if (debug) {
                debugPrint("checking port availability: " + port);
            }
            Properties serverVmProps = vm.getSystemProperties();
            int serverPort = Integer.parseInt(serverVmProps.getProperty("btrace.port", "-1"));
            if (serverPort != -1) {
                if (serverPort != port) {
                    throw new IOException("Can not attach to PID " + pid + " on port " + port + ". There is already a BTrace server active on port " + serverPort + "!");
                }
            } else {
                if (!isPortAvailable(port)) {
                    throw new IOException("Port " + port + " unavailable.");
                }
            }

            if (debug) {
                debugPrint("attached to " + pid);
            }

            if (debug) {
                debugPrint("loading " + agentPath);
            }
            String agentArgs = "port=" + port;
            if (debug) {
                agentArgs += ",debug=true";
            }
            if (unsafe) {
                agentArgs += ",unsafe=true";
            }
            if (dumpClasses) {
                agentArgs += ",dumpClasses=true";
                agentArgs += ",dumpDir=" + dumpDir;
            }
            if (trackRetransforms) {
                agentArgs += ",trackRetransforms=true";
            }
            if (bootCp != null) {
                agentArgs += ",bootClassPath=" + bootCp;
            }
            if (sysCp == null) {
                sysCp = getToolsJarPath(
                    serverVmProps.getProperty("java.class.path"),
                    serverVmProps.getProperty("java.home")
                );
            }
            String cmdQueueLimit = System.getProperty(BTraceRuntime.CMD_QUEUE_LIMIT_KEY, null);
            if (cmdQueueLimit != null) {
                agentArgs += ",cmdQueueLimit=" + cmdQueueLimit;
            }
            agentArgs += ",systemClassPath=" + sysCp;
            agentArgs += ",probeDescPath=" + probeDescPath;
            if (debug) {
                debugPrint("agent args: " + agentArgs);
            }
            vm.loadAgent(agentPath, agentArgs);
            if (debug) {
                debugPrint("loaded " + agentPath);
            }
        } catch (RuntimeException re) {
            throw re;
        } catch (IOException ioexp) {
            throw ioexp;
        } catch (Exception exp) {
            throw new IOException(exp.getMessage());
        }
    }

可以看到这个地方使用了Attach API。最后调用了VirtualMachine#loadAgent方法,加载的agent是$BTRACE_HOME/build/btrace-agent.jar,它的MANIFEST.MF是这样的,

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.0
Created-By: 1.7.0_07-b10 (Oracle Corporation)
Premain-Class: com.sun.btrace.agent.Main
Agent-Class: com.sun.btrace.agent.Main
Boot-Class-Path: btrace-boot.jar
Can-Redefine-Classes: true
Can-Retransform-Classes: true

VirtualMachine#loadAgent的时候会调用Agent-Classagentmain方法,这里也就是com.sun.btrace.agent.Main#agentmain

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

        ////////////////////////////////////////
        // 1. 参数解析
        ////////////////////////////////////////
        if (isDebug()) debugPrint("parsing command line arguments");
        parseArgs(args);
        if (isDebug()) debugPrint("parsed command line arguments");

        /////// Boot-Class-Path: btrace-boot.jar
        String bootClassPath = argMap.get("bootClassPath");
        if (bootClassPath != null) {
            if (isDebug()) {
                 debugPrint("Bootstrap ClassPath: " + bootClassPath);
            }
            StringTokenizer tokenizer = new StringTokenizer(bootClassPath, File.pathSeparator);
            try {
                while (tokenizer.hasMoreTokens()) {
                    String path = tokenizer.nextToken();
                    inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(path)));
                }
            } catch (IOException ex) {
                debugPrint("adding to boot classpath failed!");
                debugPrint(ex);
                return;
            }
        }

        String systemClassPath = argMap.get("systemClassPath");
        if (systemClassPath != null) {
            if (isDebug()) {
                 debugPrint("System ClassPath: " + systemClassPath);
            }
            StringTokenizer tokenizer = new StringTokenizer(systemClassPath, File.pathSeparator);
            try {
                while (tokenizer.hasMoreTokens()) {
                    String path = tokenizer.nextToken();
                    inst.appendToSystemClassLoaderSearch(new JarFile(new File(path)));
                }
            } catch (IOException ex) {
                debugPrint("adding to boot classpath failed!");
                debugPrint(ex);
                return;
            }
        }

        String tmp = argMap.get("noServer");
        boolean noServer = tmp != null && !"false".equals(tmp);
        if (noServer) {
            if (isDebug()) debugPrint("noServer is true, server not started");
            return;
        }

        ////////////////////////////////////////
        // 2. 启动agent线程
        ////////////////////////////////////////
        Thread agentThread = new Thread(new Runnable() {
            public void run() {
                BTraceRuntime.enter();
                try {
                    startServer();
                } finally {
                    BTraceRuntime.leave();
                }
            }
        });
        BTraceRuntime.enter();
        try {
            agentThread.setDaemon(true);
            if (isDebug()) debugPrint("starting agent thread");
            agentThread.start();
        } finally {
            BTraceRuntime.leave();
        }
    }

startServer方法,

  private static void startServer() {
        int port = BTRACE_DEFAULT_PORT;
        String p = argMap.get("port");
        if (p != null) {
            try {
                port = Integer.parseInt(p);
            } catch (NumberFormatException exp) {
                error("invalid port assuming default..");
            }
        }
        ServerSocket ss;
        try {
            if (isDebug()) debugPrint("starting server at " + port);
            System.setProperty("btrace.port", String.valueOf(port));
            if (scriptOutputFile != null && scriptOutputFile.length() > 0) {
                System.setProperty("btrace.output", scriptOutputFile);
            }
            ss = new ServerSocket(port);
        } catch (IOException ioexp) {
            ioexp.printStackTrace();
            return;
        }

        while (true) {
            try {
                if (isDebug()) debugPrint("waiting for clients");
                ////////////////////////////////////////
                // 等待客户端连接上来
                ////////////////////////////////////////
                Socket sock = ss.accept();
                if (isDebug()) debugPrint("client accepted " + sock);
                ////////////////////////////////////////
                // 生成RemoteClient
                ////////////////////////////////////////
                Client client = new RemoteClient(inst, sock);
                registerExitHook(client);
                ////////////////////////////////////////
                // 处理客户端请求
                ////////////////////////////////////////
                handleNewClient(client);
            } catch (RuntimeException re) {
                if (isDebug()) debugPrint(re);
            } catch (IOException ioexp) {
                if (isDebug()) debugPrint(ioexp);
            }
        }
    }

来看下RemoteClient的构造函数,

   RemoteClient(Instrumentation inst, Socket sock) throws IOException {
        super(inst);
        this.sock = sock;
        this.ois = new ObjectInputStream(sock.getInputStream());
        this.oos = new ObjectOutputStream(sock.getOutputStream());
        ////////////////////////////////////////
        // 读取客户端提交过来的InstrumentCommand
        ////////////////////////////////////////
        Command cmd = WireIO.read(ois);
        if (cmd.getType() == Command.INSTRUMENT) {
            if (debug) Main.debugPrint("got instrument command");
            ////////////////////////////////////////
            // 保存编译后的btrace脚本代码到Client#btraceCode
            ////////////////////////////////////////
            Class btraceClazz = loadClass((InstrumentCommand)cmd);
            if (btraceClazz == null) {
                throw new RuntimeException("can not load BTrace class");
            }
        } else {
            errorExit(new IllegalArgumentException("expecting instrument command!"));
            throw new IOException("expecting instrument command!");
        }
        ...
    }

处理客户端请求,

  private static void handleNewClient(final Client client) {
        serializedExecutor.submit(new Runnable() {

            public void run() {
                try {
                    if (isDebug()) debugPrint("new Client created " + client);
                    if (client.shouldAddTransformer()) {
                        /////////////////////////////////
                        // 1. 添加ClassFileTransformer
                        /////////////////////////////////
                        client.registerTransformer();

                        /////////////////////////////////
                        // 2. 获取满足脚本中条件的全部类
                        /////////////////////////////////
                        Class[] classes = inst.getAllLoadedClasses();
                        ArrayList<Class> list = new ArrayList<Class>();
                        if (isDebug()) debugPrint("filtering loaded classes");
                        for (Class c : classes) {
                            if (inst.isModifiableClass(c) && client.isCandidate(c)) {
                                if (isDebug()) debugPrint("candidate " + c + " added");
                                list.add(c);
                            }
                        }
                        list.trimToSize();
                        int size = list.size();
                        if (isDebug()) debugPrint("added as ClassFileTransformer");
                        if (size > 0) {
                            classes = new Class[size];
                            list.toArray(classes);
                            client.startRetransformClasses(size);
                            /////////////////////////////////
                            // 3. 开始进行retransform
                            /////////////////////////////////
                            if (isDebug()) {
                                for(Class c : classes) {
                                    try {
                                      inst.retransformClasses(c);
                                    } catch (VerifyError e) {
                                        debugPrint("verification error: " + c.getName());
                                    }
                                }
                            } else {
                                inst.retransformClasses(classes);
                            }
                            client.skipRetransforms();
                        }
                    }
                    client.getRuntime().send(new OkayCommand());
                } catch (UnmodifiableClassException uce) {
                    if (isDebug()) {
                        debugPrint(uce);
                    }
                    client.getRuntime().send(new ErrorCommand(uce));
                }
            }
        });

    }

com.sun.btrace.agent.Client#registerTransformer方法中会调用java.lang.instrument.Instrumentation#addTransformer

      void registerTransformer() {
            inst.addTransformer(clInitTransformer, false);
            inst.addTransformer(this, true);
        }

其实主要就是Attach API的使用,通过java.lang.instrument.Instrumentation#addTransformer添加了ClassFileTransformer,当调用java.lang.instrument.Instrumentation#retransformClasses时,上面所添加的ClassFileTransformertransform方法就会被调用,这里也就是com.sun.btrace.agent.Client#transformer了,该方法最后是调用了com.sun.btrace.agent.Client#instrument来完成真正的字节码修改工作,

 private byte[] instrument(Class clazz, String cname, byte[] target) {
            byte[] instrumentedCode;
            try {
                ClassWriter writer = InstrumentUtils.newClassWriter(target);
                ClassReader reader = new ClassReader(target);
                Instrumentor i = new Instrumentor(clazz, className,  btraceCode, onMethods, writer);
                InstrumentUtils.accept(reader, i);
                if (Main.isDebug() && !i.hasMatch()) {
                    Main.debugPrint("*WARNING* No method was matched for class " + cname); // NOI18N
                }
                instrumentedCode = writer.toByteArray();
            } catch (Throwable th) {
                Main.debugPrint(th);
                return null;
            }
            Main.dumpClass(className, cname, instrumentedCode);
            return instrumentedCode;
        }

上面使用的很多类的包名都是com.sun.btrace.org.objectweb.asm,BTrace使用了ASM来完成字节码的修改工作,具体细节暂时也不深究了。

最后简单总结一下,

  1. BTrace脚本编译;
  2. BTrace客户端使用Attach API attach到目标VM,并加载agent包;
  3. agent打开socket来与客户端进行通信;
  4. 客户端给agent发送InstrumentCommand,其中包含BTrace脚本编译后的字节码;
  5. agent通过Attach API和ASM来完成满足BTrace脚本的字节码修改工作;
时间: 2024-10-01 14:50:48

BTrace实现浅析的相关文章

linux进程调度浅析

操作系统要实现多进程,进程调度必不可少. 有人说,进程调度是操作系统中最为重要的一个部分.我觉得这种说法说得太绝对了一点,就像很多人动辄就说"某某函数比某某函数效率高XX倍"一样,脱离了实际环境,这些结论是比较片面的. 而进程调度究竟有多重要呢? 首先,我们需要明确一点:进程调度是对TASK_RUNNING状态的进程进行调度(参见<linux进程状态浅析>).如果进程不可执行(正在睡眠或其他),那么它跟进程调度没多大关系. 所以,如果你的系统负载非常低,盼星星盼月亮才出现一

浅析win7下IE8主页被篡改的修复过程

浅析win7下IE8主页被篡改的修复过程 很多网友都有这个烦恼,在打开住页面时,页面就会开始变化,不再是自己熟悉的版本主页,所以要解决IE8被篡改的问题,我们就要充分的挖掘Windows7的系统"潜能",提升让IE8自我保护能力.现在我们深度xp系统下载一起来看看要怎么解决吧! 运行注册表编辑器,一次展开到HKEY_CURRENT_USER/Software/Policies/Microsoft,在此分支下新建一个名为"ControlPanel"的项,(具体操作为:

linux pi_futex浅析

Priority Inheritance,优先级继承,是解决优先级反转的一种办法. 一个经典的例子:A/B/C三个实时进程,优先级A>B>C.C持有a锁,而A等待a锁被挂起.原本C释放a锁之后,A进程就可以继续执行的,但是偏偏有个比C优先级高的B进程存在,导致C得不到运行,也就没法释放a锁,从而导致A进程一直挂起.从整体上看,进程B虽然比A优先级低,但它却成功的抢占掉了A.这就是所谓的优先级反转. 一种解决办法是优先级继承,C在持有a锁期间临时继承等待者A的优先级,那么B进程就无法从中捣乱了.

linux内核SMP负载均衡浅析

需求 在<linux进程调度浅析>一文中提到,在SMP(对称多处理器)环境下,每个CPU对应一个run_queue(可执行队列).如果一个进程处于TASK_RUNNING状态(可执行状态),则它会被加入到其中一个run_queue(且同一时刻仅会被加入到一个run_queue),以便让调度程序安排它在这个run_queue对应的CPU上面运行. 一个CPU对应一个run_queue这样的设计,其好处是: 1.一个持续处于TASK_RUNNING状态的进程总是趋于在同一个CPU上面运行(其间,这

linux文件读写浅析

在<linux内核虚拟文件系统浅析>这篇文章中,我们看到文件是如何被打开.文件的读写是如何被触发的. 对一个已打开的文件fd进行read/write系统调用时,内核中该文件所对应的file结构的f_op->read/f_op->write被调用. 本文将顺着这条路走下去,大致看看普通磁盘文件的读写是怎样实现的. linux内核响应一个块设备文件读写的层次结构如图(摘自ULK3): 1.VFS,虚拟文件系统. 之前我们已经看到f_op->read/f_op->write如

btrace一些你不知道的事(源码入手)

背景     周五下班回家,在公司班车上觉得无聊,看了下btrace的源码(自己反编译). 一些关于btrace的基本内容,可以看下我早起的一篇记录:btrace记忆      上一篇主要介绍的是btrace的一些基本使用以及api,这里我想从btrace源码本身进行下介绍.至于btrace的优势,能用来干些什么,自己上他的官网看下或者google一下,花个半小时就能明白了.      至于为什么会去反编译查看btrace源码,主要是会在部门整个关于btrace的分享.同时btrace的相关技术

linux进程状态浅析

众所周知,现在的分时操作系统能够在一个CPU上运行多个程序,让这些程序表面上看起来是在同时运行的.linux就是这样的一个操作系统. 在linux系统中,每个被运行的程序实例对应一个或多个进程.linux内核需要对这些进程进行管理,以使它们在系统中"同时"运行.linux内核对进程的这种管理分两个方面:进程状态管理,和进程调度.本文主要介绍进程状态管理,进程调度见<linux进程调度浅析>. 进程状态 在linux下,通过ps命令我们能够查看到系统中存在的进程,以及它们的状

linux网络报文接收发送浅析

对于linux内核来说,网络报文由网络设备来进行接收.设备驱动程序从网络设备中读取报文,通过内核提供的网络接口函数,将报文传递到内核中的网络协议栈.报文经过协议栈的处理,或转发.或丢弃.或被传送给某个进程. 网络报文的发送与之相反,进程通过系统调用将数据送入网络协议栈,或者由网络协议栈自己发起报文的发送,然后协议栈通过调用网络接口函数来调度驱动程序,使其将报文传送给网络设备,从而发送出去. 本文讨论的是网络接口层,它是网络设备驱动程序与网络协议栈交互的纽带.见下图中红色部分的netif. 报文的

浅析VC与Matlab联合编程(三)

在"浅析VC与Matlab联合编程<一>"和"浅析VC与Matlab联合编程<二>"中介绍了matcom,这个工具可以将用matlab写的m文件翻译成C++文件,或者是可执行文件(exe)或库文件(dll).但是matcom在很多方面也有限制,比如,对struct等类的支持有缺陷,部分绘图语句无法实现或得不到准确图象,尤其是三维图象. 实际上VC与matlab的接口实现方法有很多种,matcom只是其中一种,本文再介绍一种比较容易实现的方法: