首先先参考笔者前期的博客(http://blog.csdn.net/raintungli/article/details/7034005),先了解在jvm启动的过程中的两个线程Signal Dispatcher和Attach Listener
在博客中,已经探讨了在Attach Listener 的线程在linux环境中创建了socket的文件,接着我们的关注点讲成为客户端如何写这个文件。
在attach 的java代码中,使用sun自用的tool.jar中的VirtualMachine的attach的方式
VirtualMachine vm = VirtualMachine.attach(processid);
vm.loadAgent(agentpath, args)
在HotSpotVirtualMachine.java 中
public void loadAgent(String agent, String options)
throws AgentLoadException, AgentInitializationException, IOException
{
String args = agent;
if (options != null) {
args = args + "=" + options;
}
try {
loadAgentLibrary("instrument", args);
} .....
}
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();
}
}
在LinuxVirtualMachine.java中的execute方法
InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException {
assert args.length <= 3; // includes null
// did we detach?
String p;
synchronized (this) {
if (this.path == null) {
throw new IOException("Detached from target VM");
}
p = this.path;
}
// create UNIX socket
int s = socket();
// connect to target VM
try {
connect(s, p);
} catch (IOException x) {
close(s);
throw x;
}
IOException ioe = null;
// connected - write request
//
try {
writeString(s, PROTOCOL_VERSION);
writeString(s, cmd);
for (int i=0; i<3; i++) {
if (i < args.length && args[i] != null) {
writeString(s, (String)args[i]);
} else {
writeString(s, "");
}
}
} catch (IOException x) {
ioe = x;
}
// Create an input stream to read reply
SocketInputStream sis = new SocketInputStream(s);
// Read the command completion status
int completionStatus;
try {
completionStatus = readInt(sis);
} catch (IOException x) {
sis.close();
if (ioe != null) {
throw ioe;
} else {
throw x;
}
}
....
}
也就是向socket的中写入了
格式为:
1 load instrument agentPath=path.jar
既然Load Agent 往socket里发了load指令,匹配到JVM的操作
static AttachOperationFunctionInfo funcs[] = {
{ "agentProperties", get_agent_properties },
{ "datadump", data_dump },
#ifndef SERVICES_KERNEL
{ "dumpheap", dump_heap },
#endif // SERVICES_KERNEL
{ "load", JvmtiExport::load_agent_library },
{ "properties", get_system_properties },
{ "threaddump", thread_dump },
{ "inspectheap", heap_inspection },
{ "setflag", set_flag },
{ "printflag", print_flag },
{ NULL, NULL }
};
"load", JvmtiExport::load_agent_library
jint JvmtiExport::load_agent_library(AttachOperation* op, outputStream* st) {
char ebuf[1024];
char buffer[JVM_MAXPATHLEN];
void* library;
jint result = JNI_ERR;
const char* agent = op->arg(0);
const char* absParam = op->arg(1);
const char* options = op->arg(2);
bool is_absolute_path = (absParam != NULL) && (strcmp(absParam,"true")==0);
if (is_absolute_path) {
library = os::dll_load(agent, ebuf, sizeof ebuf);
} else {
// Try to load the agent from the standard dll directory
os::dll_build_name(buffer, sizeof(buffer), Arguments::get_dll_dir(), agent);
library = os::dll_load(buffer, ebuf, sizeof ebuf);
if (library == NULL) {
// not found - try local path
char ns[1] = {0};
os::dll_build_name(buffer, sizeof(buffer), ns, agent);
library = os::dll_load(buffer, ebuf, sizeof ebuf);
}
}
if (library != NULL) {
// Lookup the Agent_OnAttach function
OnAttachEntry_t on_attach_entry = NULL;
const char *on_attach_symbols[] = AGENT_ONATTACH_SYMBOLS;
for (uint symbol_index = 0; symbol_index < ARRAY_SIZE(on_attach_symbols); symbol_index++) {
on_attach_entry =
CAST_TO_FN_PTR(OnAttachEntry_t, os::dll_lookup(library, on_attach_symbols[symbol_index]));
if (on_attach_entry != NULL) break;
}
if (on_attach_entry == NULL) {
// Agent_OnAttach missing - unload library
os::dll_unload(library);
} else {
// Invoke the Agent_OnAttach function
JavaThread* THREAD = JavaThread::current();
{
extern struct JavaVM_ main_vm;
JvmtiThreadEventMark jem(THREAD);
JvmtiJavaThreadEventTransition jet(THREAD);
result = (*on_attach_entry)(&main_vm, (char*)options, NULL);
}
if (HAS_PENDING_EXCEPTION) {
CLEAR_PENDING_EXCEPTION;
}
if (result == JNI_OK) {
Arguments::add_loaded_agent(agent, (char*)options, is_absolute_path, library);
}
// Agent_OnAttach executed so completion status is JNI_OK
st->print_cr("%d", result);
result = JNI_OK;
}
}
return result;
}
#define AGENT_ONATTACH_SYMBOLS {"Agent_OnAttach"}
加载instrument的动态库,并且调用方法instrument动态库中的Agent_OnAttach方法
JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char *args, void * reserved) {
.....
initerror = createNewJPLISAgent(vm, &agent);
if ( initerror == JPLIS_INIT_ERROR_NONE ) {
......
if (parseArgumentTail(args, &jarfile, &options) != 0) {
return JNI_ENOMEM;
}
attributes = readAttributes( jarfile );
if (attributes == NULL) {
fprintf(stderr, "Error opening zip file or JAR manifest missing: %s\n", jarfile);
free(jarfile);
if (options != NULL) free(options);
return AGENT_ERROR_BADJAR;
}
agentClass = getAttribute(attributes, "Agent-Class");
if (agentClass == NULL) {
fprintf(stderr, "Failed to find Agent-Class manifest attribute from %s\n",
jarfile);
free(jarfile);
if (options != NULL) free(options);
freeAttributes(attributes);
return AGENT_ERROR_BADJAR;
}
if (appendClassPath(agent, jarfile)) {
fprintf(stderr, "Unable to add %s to system class path "
"- not supported by system class loader or configuration error!\n",
jarfile);
free(jarfile);
if (options != NULL) free(options);
freeAttributes(attributes);
return AGENT_ERROR_NOTONCP;
}
oldLen = strlen(agentClass);
newLen = modifiedUtf8LengthOfUtf8(agentClass, oldLen);
if (newLen == oldLen) {
agentClass = strdup(agentClass);
} else {
char* str = (char*)malloc( newLen+1 );
if (str != NULL) {
convertUtf8ToModifiedUtf8(agentClass, oldLen, str, newLen);
}
agentClass = str;
}
if (agentClass == NULL) {
free(jarfile);
if (options != NULL) free(options);
freeAttributes(attributes);
return JNI_ENOMEM;
}
bootClassPath = getAttribute(attributes, "Boot-Class-Path");
if (bootClassPath != NULL) {
appendBootClassPath(agent, jarfile, bootClassPath);
}
convertCapabilityAtrributes(attributes, agent);
success = createInstrumentationImpl(jni_env, agent);
jplis_assert(success);
/*
* Turn on the ClassFileLoadHook.
*/
if (success) {
success = setLivePhaseEventHandlers(agent);
jplis_assert(success);
}
if (success) {
success = startJavaAgent(agent,
jni_env,
agentClass,
options,
agent->mAgentmainCaller);
}
if (!success) {
fprintf(stderr, "Agent failed to start!\n");
result = AGENT_ERROR_STARTFAIL;
}
if (options != NULL) free(options);
free(agentClass);
freeAttributes(attributes);
}
return result;
}
代码里createNewJPLISAgent是和on_load是一样的注册了一些钩子函数,也就是说在on_attach的情况下,依然可以对没有进行解析的class通过钩子函数进行修改class的字节码。那么问题来了,如果已经解析过的class的呢?
在代码中我们看到了
agentClass = getAttribute(attributes, "Agent-Class");
也就是在on_attach中,会读取加载的jar中MANIFEST Agent-Class的配置
success = createInstrumentationImpl(jni_env, agent);
生成sun.instrument.InstrumentationImpl对象
success = startJavaAgent(agent,
jni_env,
agentClass,
options,
agent->mAgentmainCaller);
通过InstrumentationImpl对象中的loadClassAndCallAgentmain方法去初始化在Agent-Class中的类,并调用class里的agentmain的方法。
也就是说定义的on_attach的class里需要有agentmain的方法
public class MyTransformer {
public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException, NotFoundException, CannotCompileException, IOException{
....
}
}