BTrace实现浅析
2015-07-19 22:38
351 查看
在之前的文章中我们简单介绍了BTrace的用法,今天我们通过源代码来看看BTrace是如何实现的。
从BTrace的启动脚本中可以找到相关入口,
所以
BTrace脚本的编译细节(包括脚本解析等)我们暂不深究。看第3步之前先来看第4步提交的请求,
现在我们再来看第3步,也就是
可以看到这个地方使用了Attach API。最后调用了
来看下
处理客户端请求,
其实主要就是Attach API的使用,通过
上面使用的很多类的包名都是
最后简单总结一下,
BTrace脚本编译;
BTrace客户端使用Attach API attach到目标VM,并加载agent包;
agent打开socket来与客户端进行通信;
客户端给agent发送
agent通过Attach API和ASM来完成满足BTrace脚本的字节码修改工作;
从BTrace的启动脚本中可以找到相关入口,
[code]${J***A_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方法,
[code] 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,
[code] /** * 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,
[code] /** * 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()); } }
[code] /** * 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是这样的,
[code]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-Class的
agentmain方法,这里也就是
com.sun.btrace.agent.Main#agentmain,
[code] public static void agentmain(String args, Instrumentation inst) { main(args, inst); }
[code] 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方法,
[code] 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的构造函数,
[code] 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!"); } ... }
处理客户端请求,
[code] 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,
[code] void registerTransformer() { inst.addTransformer(clInitTransformer, false); inst.addTransformer(this, true); }
其实主要就是Attach API的使用,通过
java.lang.instrument.Instrumentation#addTransformer添加了
ClassFileTransformer,当调用
java.lang.instrument.Instrumentation#retransformClasses时,上面所添加的
ClassFileTransformer的
transform方法就会被调用,这里也就是
com.sun.btrace.agent.Client#transformer了,该方法最后是调用了
com.sun.btrace.agent.Client#instrument来完成真正的字节码修改工作,
[code] 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来完成字节码的修改工作,具体细节暂时也不深究了。
最后简单总结一下,
BTrace脚本编译;
BTrace客户端使用Attach API attach到目标VM,并加载agent包;
agent打开socket来与客户端进行通信;
客户端给agent发送
InstrumentCommand,其中包含BTrace脚本编译后的字节码;
agent通过Attach API和ASM来完成满足BTrace脚本的字节码修改工作;
相关文章推荐
- ArcGIS Engine渲染
- GO语言练习:channel 工程实例
- html5视频播放
- Python笔记——break的注意事项
- UVALive - 4329 Ping pong 数状数组
- matlab中GUI界面点击图片获取坐标问题的解决方法
- Python笔记——break的注意事项
- 指针初始化为NULL的作用
- 7,19 周日 小结
- C++ string操作(转载)
- MATLAB中的一些小技巧(基础)
- Tempter of the Bone
- [剑指Offer]6.替换空格
- Android架构实战(一)—— 核心思想
- 简述FPS的计算方法
- 文件复制
- 你不努力,谁也给不了你想要的生活
- 在ios中举个简单的protocol例子,关于两个类用协议方式传值。
- 归并排序C++
- linux学习之yum命令的使用