BTrace is a safe, dynamic tracing tool for the Java platform.
BTrace can be used to dynamically trace a running Java program (similar to DTrace for OpenSolaris applications and OS). BTrace dynamically instruments the classes of the target application to inject tracing code (“bytecode tracing”).
上面是BTrace官网对于BTrace的介绍。我们可以用BTrace来探测系统运行时的状况。下面就小试牛刀,写个例子跑跑看。
下面是一个用来探测哪些方法的执行时间超过某个阈值的探针。如果超过阈值,会记录该次调用的调用栈,
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class Tracker {
@OnMethod(
clazz = "/me\\.kisimple\\.just4fun\\..*/",
method = "/.*/",
location = @Location(Kind.RETURN)
)
public static void trace(@ProbeClassName String pcn,
@ProbeMethodName String pmn,
@Duration long duration) {
// duration的单位是纳秒
if(duration > 3000000000l) {
print(Strings.strcat("#Duration of ",
Strings.strcat(Strings.strcat(pcn, "."), pmn)));
print(" is ");
print(duration);
println("#");
jstack();
}
}
}
BTrace支持正则表达式,a regular expression specified within two forward slashes,也就是"/<regexp>/"这样子的。我们的例子是探测me.kisimple.just4fun包下的任何类的任何方法。
模拟一个被探测对象,
public class Main {
public static void main(String[] args) throws Exception{
while(true) {
bar();
foo();
}
}
private static void bar() throws InterruptedException {
Thread.sleep(5000);
}
private static void foo() throws InterruptedException {
Thread.sleep(1000);
}
}
执行btrace命令,
$ btrace 6860 Projects/just4fun/src/me/kisimple/just4fun/Tracker.java
#Duration of me.kisimple.just4fun.Main.bar is 5000179210#
me.kisimple.just4fun.Main.bar(Main.java:17)
me.kisimple.just4fun.Main.main(Main.java:10)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:606)
com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
#Duration of me.kisimple.just4fun.Main.bar is 5000131459#
me.kisimple.just4fun.Main.bar(Main.java:17)
me.kisimple.just4fun.Main.main(Main.java:10)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:606)
com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
这么强大的功能是怎么做到的?下面我们就RTFSC一探究竟。
从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);
}
// 调用 Attach API
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);
}
// 加载Instrumentation支持的agent
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
方法,加载java.lang.instrument指定的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-Class
的agentmain
方法,这里也就是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);
}
通过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
来完成真正的字节码修改工作,
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通过ASM和Instrumentation API来完成满足BTrace脚本的字节码修改工作;
Easter egg
通过BTrace的实现我们可以看到HotSpot Serviceability的几个技术点:Dynamic Attach,Instrumentation API以及JVM Tool Interface。下面来一一讨论下。
Dynamic Attach
Attach API底层使用的就是Dynamic Attach机制。我们平时jstack经常看到一个Attach Listener线程就与这个机制的实现有关。具体可参考这篇博文,这里就不展开了。
Instrumentation及JVM TI
上面我们看到了通过VirtualMachine#loadAgent
来加载java.lang.instrument
指定规范的agent。我们来看下在JDK8里面这个方法的代码,
/**
* Loads an agent.
*
* <p> The agent provided to this method is a path name to a JAR file on the file
* system of the target virtual machine. This path is passed to the target virtual
* machine where it is interpreted. The target virtual machine attempts to start
* the agent as specified by the {@link java.lang.instrument} specification.
* That is, the specified JAR file is added to the system class path (of the target
* virtual machine), and the <code>agentmain</code> method of the agent class, specified
* by the <code>Agent-Class</code> attribute in the JAR manifest, is invoked. This
* method completes when the <code>agentmain</code> method completes.
*
* @param agent
* Path to the JAR file containing the agent.
*
* @param options
* The options to provide to the agent's <code>agentmain</code>
* method (can be <code>null</code>).
*
* @throws AgentLoadException
* If the agent does not exist, or cannot be started in the manner
* specified in the {@link java.lang.instrument} specification.
*
* @throws AgentInitializationException
* If the <code>agentmain</code> throws an exception
*
* @throws IOException
* If an I/O error occurs
*
* @throws NullPointerException
* If <code>agent</code> is <code>null</code>.
*/
public abstract void loadAgent(String agent, String options)
throws AgentLoadException, AgentInitializationException, IOException;
HotSpot的VirtualMachine
实现是HotSpotVirtualMachine
,来看下HotSpotVirtualMachine#loadAgent
,
/*
* 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 {
//
// 加载 libinstrument.so/instrument.dll
/
loadAgentLibrary("instrument", args);
} catch (AgentLoadException x) {
throw new InternalError("instrument library is missing in target VM", x);
} 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);
}
}
}
/*
* Load agent library - library name will be expanded in target VM
*/
public void loadAgentLibrary(String agentLibrary, String options)
throws AgentLoadException, AgentInitializationException, IOException
{
loadAgentLibrary(agentLibrary, false, options);
}
/*
* 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
{
//
// 向Attach Listener发送load命令
//
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();
}
}
最后使用execute
方法与Attach Listener进行通信,
/*
* Execute the given command in the target VM - specific platform
* implementation must implement this.
*/
abstract InputStream execute(String cmd, Object ... args)
throws AgentLoadException, IOException;
Linux平台上的实现,LinuxVirtualMachine
,通过LinuxVirtualMachine#execute
可以看到最终是通过UNIX socket与目标VM进行通信。
下面来看下Attach Listener收到load
命令后是怎么处理的,Attach Listener的代码在attachListener_linux.cpp,接收到的命令所对应的操作在attachListener.cpp
// names must be of length <= AttachOperation::name_length_max
static AttachOperationFunctionInfo funcs[] = {
{ "agentProperties", get_agent_properties },
{ "datadump", data_dump },
{ "dumpheap", dump_heap },
{ "load", JvmtiExport::load_agent_library },
{ "properties", get_system_properties },
{ "threaddump", thread_dump },
{ "inspectheap", heap_inspection },
{ "setflag", set_flag },
{ "printflag", print_flag },
{ "jcmd", jcmd },
{ NULL, NULL }
};
load
所对应的JvmtiExport::load_agent_library
方法在jvmtiExport.cpp。到这里其实就可以看到是使用了JVM TI。该方法会加载agent的库文件,并且会执行该库的Agent_OnAttach
方法。在HotSpotVirtualMachine#loadAgent
已经看到agent库名称传入的是instrument
,因此在Linux平台上将会加载libinstrument.so
。也就是说,libinstrument.so
与JPDA中的libjdwp.so
一样,都是JVM TI的agent,我们也可以用-agentlib:instrument
这样的方式来执行instrument。
libinstrument.so
的Agent_OnAttach
实现在InvocationAdapter.c,
/*
* This will be called once for every -javaagent on the command line.
* Each call to Agent_OnLoad will create its own agent and agent data.
*
* The argument tail string provided to Agent_OnLoad will be of form
* <jarfile>[=<options>]. The tail string is split into the jarfile and
* options components. The jarfile manifest is parsed and the value of the
* Premain-Class attribute will become the agent's premain class. The jar
* file is then added to the system class path, and if the Boot-Class-Path
* attribute is present then all relative URLs in the value are processed
* to create boot class path segments to append to the boot class path.
*/
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) { ... }
其中会创建JPLIS Agent,具体实现这里就不深究了,感兴趣的同学可以继续读代码。
最后的最后,总结下Dynamic Attach,Instrumentation以及JVMTI三者的关系。我们通过Attach API拿到VirtualMachine
后,有以下两种方式加载agent,
/**
* Loads an agent.
*
* <p> The agent provided to this method is a path name to a JAR file on the file
* system of the target virtual machine. This path is passed to the target virtual
* machine where it is interpreted. The target virtual machine attempts to start
* the agent as specified by the {@link java.lang.instrument} specification.
* That is, the specified JAR file is added to the system class path (of the target
* virtual machine), and the <code>agentmain</code> method of the agent class, specified
* by the <code>Agent-Class</code> attribute in the JAR manifest, is invoked. This
* method completes when the <code>agentmain</code> method completes.
*
* @param agent
* Path to the JAR file containing the agent.
*
* @param options
* The options to provide to the agent's <code>agentmain</code>
* method (can be <code>null</code>).
*
* @throws AgentLoadException
* If the agent does not exist, or cannot be started in the manner
* specified in the {@link java.lang.instrument} specification.
*
* @throws AgentInitializationException
* If the <code>agentmain</code> throws an exception
*
* @throws IOException
* If an I/O error occurs
*
* @throws NullPointerException
* If <code>agent</code> is <code>null</code>.
*/
public abstract void loadAgent(String agent, String options)
throws AgentLoadException, AgentInitializationException, IOException;
/**
* Loads an agent library.
*
* <p> A <a href="../../../../../../../../technotes/guides/jvmti/index.html">JVM
* TI</a> client is called an <i>agent</i>. It is developed in a native language.
* A JVM TI agent is deployed in a platform specific manner but it is typically the
* platform equivalent of a dynamic library. Alternatively, it may be statically linked into the VM.
* This method causes the given agent library to be loaded into the target
* VM (if not already loaded or if not statically linked into the VM).
* It then causes the target VM to invoke the <code>Agent_OnAttach</code> function
* or, for a statically linked agent named 'L', the <code>Agent_OnAttach_L</code> function
* as specified in the
* <a href="../../../../../../../../technotes/guides/jvmti/index.html"> JVM Tools
* Interface</a> specification. Note that the <code>Agent_OnAttach[_L]</code>
* function is invoked even if the agent library was loaded prior to invoking
* this method.
*
* <p> The agent library provided is the name of the agent library. It is interpreted
* in the target virtual machine in an implementation-dependent manner. Typically an
* implementation will expand the library name into an operating system specific file
* name. For example, on UNIX systems, the name <tt>L</tt> might be expanded to
* <tt>libL.so</tt>, and located using the search path specified by the
* <tt>LD_LIBRARY_PATH</tt> environment variable. If the agent named 'L' is
* statically linked into the VM then the VM must export a function named
* <code>Agent_OnAttach_L</code>.</p>
*
* <p> If the <code>Agent_OnAttach[_L]</code> function in the agent library returns
* an error then an {@link com.sun.tools.attach.AgentInitializationException} is
* thrown. The return value from the <code>Agent_OnAttach[_L]</code> can then be
* obtained by invoking the {@link
* com.sun.tools.attach.AgentInitializationException#returnValue() returnValue}
* method on the exception. </p>
*
* @param agentLibrary
* The name of the agent library.
*
* @param options
* The options to provide to the <code>Agent_OnAttach[_L]</code>
* function (can be <code>null</code>).
*
* @throws AgentLoadException
* If the agent library does not exist, the agent library is not
* statically linked with the VM, or the agent library cannot be
* loaded for another reason.
*
* @throws AgentInitializationException
* If the <code>Agent_OnAttach[_L]</code> function returns an error.
*
* @throws IOException
* If an I/O error occurs
*
* @throws NullPointerException
* If <code>agentLibrary</code> is <code>null</code>.
*
* @see com.sun.tools.attach.AgentInitializationException#returnValue()
*/
public abstract void loadAgentLibrary(String agentLibrary, String options)
throws AgentLoadException, AgentInitializationException, IOException;
分别是Instrumentation的agent以及JVMTI的agent,而从上面也看到了,HotSpot的实现,HotSpotVirtualMachine
,loadAgent
最终是调用了loadAgentLibrary("instrument", args)
,也就是说,Instrumentation本身就是JVMTI的agent(libinstrument.so
),而BTrace的btrace-agent.jar
则是Instrumentation的agent,有点绕哈:)
嗯,今天就到这吧,have fun ^_^