catalog
1. instrucment與Attach API 2. BTrace: VM Attach的兩種方式 3. Sun JVM Attach API
?
1. instrucment與Attach API
JDK5中增加了一個包java.lang.instrucment,能夠對JVM底層組件進行訪問。在JDK 5中,Instrument 要求在運行前利用命令行參數或者系統參數來設置代理類,在實際的運行之中,虛擬機在初始化之時(在絕大多數的 Java 類庫被載入之前),instrumentation的設置已經啟動,并在虛擬機中設置了回調函數,檢測特定類的加載情況,并完成實際工作
?在Java5中,開發基于Instrucment的應用,需要以下幾個步驟
1. 編寫premain函數 ?2. jar文件打包 ?3. 運行agent
但是在實際的很多的情況下,我們沒有辦法在虛擬機啟動之時就為其設定代理,這樣實際上限制了instrument的應用。而Java SE 6的新特性改變了這種情況,通過Java Tool API中的attach方式,我們可以很方便地在運行過程中動態地設置加載代理類,以達到instrumentation的目的
?在JDK6中,針對這點做了改進,開發者可以在main開始執行以后,再開啟自己的Instrucment程序
Attach API不是Java的標準API,而是Sun公司提供的一套擴展API,用來向目標JVM"附著"(Attach)代理工具程序的。有了它,開發者可以方便的監控一個JVM,運行一個外加的代理程序,Sun JVM Attach API功能上非常簡單,僅提供了如下幾個功能
1. 列出當前所有的JVM實例描述 2. Attach到其中一個JVM上,建立通信管道 3. 讓目標JVM加載Agent
Relevant Link:
http://iamzhongyong.iteye.com/blog/1843558
?
2. BTrace: VM Attach的兩種方式
BTrace的特點之一就是可以動態Attach到一個運行的JVM進程上,然后根據BTrace腳本來對目標JVM進行相應的操作
JVM的 Attach有兩種方式
1. 指定javaagent參數 2. 運行時動態attach
0x1: 指定javaagent參數
這種方式的特點就是在目標JVM啟動時,就確定好了要加載什么樣的代理對象,例如
java -javaagent:xxxx.jar TestMain
TestMain.java
package test;public class TestMain { public static void main(String[] args) throws InterruptedException{System.out.println("Hello");}}
TestAgent.java
package test;import java.lang.instrument.Instrumentation; import java.io.*;public class TestMain { public static void agentmain(String args, Instrumentation inst) throws Exception {System.out.println("Args:" + args);}public static void premain(String args, Instrumentation inst) throws Exception {System.out.println("Pre Args:" + args);Class[] classes = inst.getAllLoadedClasses();for (Class clazz : classes) {System.out.println(clazz.getName());}} }
TestAgent類比較簡單,最終它會在目標類的Main方法執行之前,執行premain方法,其主要動作是將以及加載的類打印出來。 我們需要將這個類打包成jar文件,以便在目標JVM啟動時候,以參數形式指定給它。打成jar的同時,設定MANIFEST.MF文件的內容。告知目標JVM該如何處理
Agent-Class: TestAgent Premain-Class: TestAgent Can-Redine-Classes: true Can-Retransform-Classes: true
用jar命令將TestAgent打包
1. 編譯TestAgent javac TestAgent.java2. jar打包 jar cvmf MANIFEST.MF xxx.jar TestAgent.class
啟動TestMain,并設置javaagent參數
1. 編譯TestMain javac TestMain.java 2. 啟動TestMain java -javaagent:xxx.jar TestMain
0x2: 動態Attach,load指定Agent
這種方式與之前指定參數的不同在于,其可以在JVM已經運行的情況下,動態的附著上去,并可以動態加載agent
TestMain.java
public class TestMain {public static void main(String[] args) throws InterruptedException { while(true){ Thread.sleep(10000); new Thread(new WaitThread()).start(); } } static class WaitThread implements Runnable { @Override public void run() { System.out.println("Hello"); } } }
TestAgent.java
import java.lang.instrument.Instrumentation; import java.io.*;public class TestAgent { public static void agentmain(String args, Instrumentation inst) throws Exception {System.out.println("Args:" + args);}public static void premain(String args, Instrumentation inst) throws Exception {System.out.println("Pre Args:" + args);Class[] classes = inst.getAllLoadedClasses();for (Class clazz : classes) {System.out.println(clazz.getName());}} }
動態加載agent的情況下,被調用的是agentmain方法, 其會在JVMload的時候,被調用
MANIFEST.MF
Agent-Class: TestAgent Premain-Class: TestAgent Can-Redine-Classes: true Can-Retransform-Classes: true
將類打包為jar包
1. 編譯TestAgent javac TestAgent.java2. jar打包 jar cvmf MANIFEST.MF xxx.jar TestAgent.class
動態附著到對應的JVM需要使用到JDK的Attach API
Main.java
import com.sun.tools.attach.VirtualMachine;public class Main { public static void main(String[] args) throws Exception{ VirtualMachine vm = null; String agentjarpath = "C:/Users/zhenghan.zh/Desktop/新建文件夾/xxx.jar"; //agentjar路徑 vm = VirtualMachine.attach("9730");//目標JVM的進程ID(PID) vm.loadAgent(agentjarpath, "This is Args to the Agent."); vm.detach(); } }
一旦運行這個Main方法, 其就會動態的附著到我們對應的JVM進程中,并為目標JVM加載我們指定的Agent,以達到我們想做的事情, 比如BTrace就為在附著到目標JVM后,開啟一個ServerSocket,以便達到與目標進程通訊的目的
Relevant Link:
http://ivanzhangwb.github.io/btrace-vm-attach-api/
?
3. Sun JVM Attach API
Sun JVM Attach API是Sun JVM中的一套非標準的可以連接到JVM上的API,從JDK6開始引入,除了Solaris平臺的Sun JVM支持遠程的Attach,在其他平臺都只允許Attach到本地的JVM上
0x1: 列出當前所有的JVM實例描述
package test; import java.util.List;import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor;public class Test {public static void main(String[] args) {List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : list) { System.out.println("pid:" + vmd.id() + ":" + vmd.displayName()); } }} //tools.jar needs to be added to the IDE's library path and the program's classpath. The tools.jar file is found in the JDK's lib directory.
0x2: Attach到特定進程的JVM上,并加載Agent
//Attach到JVM上 VirtualMachine virtualmachine = VirtualMachine.attach(pid); //加載Agent String javaHome = virtualmachine.getSystemProperties().getProperty("java.home"); String agentPath = javaHome + File.separator + "jre" + File.separator + "lib" + File.separator + "management-agent.jar"); File file = new File(agentPath); if(!file.exists()) { agentPath = javaHome + File.separator + "lib" + File.separator + "management-agent.jar"; file = new File(agentPath); if(!file.exists()) throw new IOException("Management agent not found"); } } agentPath = file.getCanonicalPath(); try { virtualmachine.loadAgent(agentPath, "com.sun.management.jmxremote"); } catch(AgentLoadException e) { throw new IOException(e); } catch(AgentInitializationException agentinitializationexception) { throw new IOException(e); } Properties properties = virtualmachine.getAgentProperties(); address = (String)properties.get("com.sun.management.jmxremote.localConnectorAddress"); virtualmachine.detach();
0x3: Attach API底層實現(windows)
\openjdk\jdk\src\windows\classes\sun\tools\attach\WindowsAttachProvider.java
public VirtualMachine attachVirtualMachine(String vmid) throws AttachNotSupportedException, IOException {checkAttachPermission();// AttachNotSupportedException will be thrown if the target VM can be determined// to be not attachable. testAttachable(vmid);return new WindowsVirtualMachine(this, vmid); }
\openjdk\jdk\src\windows\classes\sun\tools\attach\WindowsVirtualMachine.java
WindowsVirtualMachine(AttachProvider provider, String id) throws AttachNotSupportedException, IOException {//繼承HotSpotVirtualMachine super(provider, id);int pid;try {pid = Integer.parseInt(id);} catch (NumberFormatException x) {throw new AttachNotSupportedException("Invalid process identifier");}//先連接上目標JVMhProcess = openProcess(pid);// The target VM might be a pre-6.0 VM so we enqueue a "null" command// which minimally tests that the enqueue function exists in the target// VM.try {enqueue(hProcess, stub, null, null);} catch (IOException x) {throw new AttachNotSupportedException(x.getMessage());} }
WindowsVirtualMachine繼承HotSpotVirtualMachine,先看看HotSpotVirtualMachine的loadAgent方法
\openjdk\jdk\src\share\classes\sun\tools\attach\HotSpotVirtualMachine.java
/* * Load JPLIS agent which will load the agent JAR file and invoke * the agentmain method. */ public void loadAgent(String agent, String options) throws AgentLoadException, AgentInitializationException, IOException {String args = agent;if (options != null) {args = args + "=" + options;}try {loadAgentLibrary("instrument", args);} catch (AgentLoadException x) {throw new InternalError("instrument library is missing in target VM");} catch (AgentInitializationException x) {/** Translate interesting errors into the right exception and* message (FIXME: create a better interface to the instrument* implementation so this isn't necessary)*/int rc = x.returnValue();switch (rc) {case JNI_ENOMEM:throw new AgentLoadException("Insuffient memory");case ATTACH_ERROR_BADJAR:throw new AgentLoadException("Agent JAR not found or no Agent-Class attribute");case ATTACH_ERROR_NOTONCP:throw new AgentLoadException("Unable to add JAR file to system class path");case ATTACH_ERROR_STARTFAIL:throw new AgentInitializationException("Agent JAR loaded but agent failed to initialize");default :throw new AgentLoadException("Failed to load agent - unknown reason: " + rc);}} }
loadAgentLibrary("instrument", args);
/* * Load agent library * If isAbsolute is true then the agent library is the absolute path * to the library and thus will not be expanded in the target VM. * if isAbsolute is false then the agent library is just a library * name and it will be expended in the target VM. */ private void loadAgentLibrary(String agentLibrary, boolean isAbsolute, String options) throws AgentLoadException, AgentInitializationException, IOException {InputStream in = execute("load",agentLibrary,isAbsolute ? "true" : "false",options);try {int result = readInt(in);if (result != 0) {throw new AgentInitializationException("Agent_OnAttach failed", result);}} finally {in.close();} }
可以看到,Java在Attach到目標進行后,調用execute讓目標進行加載Agent類,我們繼續分析execute的實現方式,可以看到,JVM進程間通信是JVM Attach API的核心,JVM自身就預留了執行來自Attach進程的指令接口
\openjdk\jdk\src\windows\classes\sun\tools\attach\WindowsVirtualMachine.java
InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {assert args.length <= 3; // includes null// create a pipe using a random nameint r = (new Random()).nextInt();String pipename = "\\\\.\\pipe\\javatool" + r;long hPipe = createPipe(pipename);// check if we are detached - in theory it's possible that detach is invoked// after this check but before we enqueue the command.if (hProcess == -1) {closePipe(hPipe);throw new IOException("Detached from target VM");}try {// enqueue the command to the process enqueue(hProcess, stub, cmd, pipename, args);// wait for command to complete - process will connect with the// completion status connectPipe(hPipe);// create an input stream for the pipePipedInputStream is = new PipedInputStream(hPipe);// read completion statusint status = readInt(is);if (status != 0) {// special case the load command so that the right exception is thrownif (cmd.equals("load")) {throw new AgentLoadException("Failed to load agent library");} else {throw new IOException("Command failed in target VM");}}// return the input streamreturn is;} catch (IOException ioe) {closePipe(hPipe);throw ioe;} }
JVM的execute方法中調用了大量native方法,并且從代碼中可以看出,JVM Attach的進程間通信使用了管道進行通信
Relevant Link:
http://ayufox.iteye.com/blog/655761 http://docs.oracle.com/javase/7/docs/jdk/api/attach/spec/com/sun/tools/attach/VirtualMachine.html http://docs.oracle.com/javase/7/docs/jdk/api/attach/spec/index.html
?
Copyright (c) 2015 LittleHann All rights reserved
?