日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

BTrace实现浅析

發布時間:2023/12/15 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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到目標VMclient.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發送InstrumentCommandWireIO.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.jarString 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);// 生成RemoteClientClient 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());// 讀取客戶端提交過來的InstrumentCommandCommand cmd = WireIO.read(ois);if (cmd.getType() == Command.INSTRUMENT) {if (debug) Main.debugPrint("got instrument command");// 保存編譯后的btrace腳本代碼到Client#btraceCodeClass 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來完成字節碼的修改工作,具體細節暫時也不深究了。

最后簡單總結一下,

  • BTrace腳本編譯;
  • BTrace客戶端使用Attach API attach到目標VM,并加載agent包;
  • agent打開socket來與客戶端進行通信;
  • 客戶端給agent發送InstrumentCommand,其中包含BTrace腳本編譯后的字節碼;
  • agent通過Attach API和ASM來完成滿足BTrace腳本的字節碼修改工作;
  • 總結

    以上是生活随笔為你收集整理的BTrace实现浅析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。