package com.lzy.javaagent;
/**
* @author liuzhengyang
* 2022/4/13
*/
public class Test {
public void hello() {
System.out.println("hello");
}
public static void main(String[] args) {
new Test().hello();
}
}
void Threads::create_vm_init_agents() {
extern struct JavaVM_ main_vm;
AgentLibrary* agent;
JvmtiExport::enter_onload_phase();
for (agent = Arguments::agents(); agent != NULL; agent = agent->next()) {
// CDS dumping does not support native JVMTI agent.
// CDS dumping supports Java agent if the AllowArchivingWithJavaAgent diagnostic option is specified.
if (Arguments::is_dumping_archive()) {
if(!agent->is_instrument_lib()) {
vm_exit_during_cds_dumping("CDS dumping does not support native JVMTI agent, name", agent->name());
} else if (!AllowArchivingWithJavaAgent) {
vm_exit_during_cds_dumping(
"Must enable AllowArchivingWithJavaAgent in order to run Java agent during CDS dumping");
}
}
OnLoadEntry_t on_load_entry = lookup_agent_on_load(agent);
if (on_load_entry != NULL) {
// Invoke the Agent_OnLoad function
jint err = (*on_load_entry)(&main_vm, agent->options(), NULL);
if (err != JNI_OK) {
vm_exit_during_initialization("agent library failed to init", agent->name());
}
} else {
vm_exit_during_initialization("Could not find Agent_OnLoad function in the agent library", agent->name());
}
}
JvmtiExport::enter_primordial_phase();
}
// Find a command line agent library and return its entry point for
// -agentlib: -agentpath: -Xrun
// num_symbol_entries must be passed-in since only the caller knows the number of symbols in the array.
static OnLoadEntry_t lookup_on_load(AgentLibrary* agent,
const char *on_load_symbols[],
size_t num_symbol_entries) {
OnLoadEntry_t on_load_entry = NULL;
void *library = NULL;
if (!agent->valid()) {
char buffer[JVM_MAXPATHLEN];
char ebuf[1024] = "";
const char *name = agent->name();
const char *msg = "Could not find agent library ";
// First check to see if agent is statically linked into executable
if (os::find_builtin_agent(agent, on_load_symbols, num_symbol_entries)) {
library = agent->os_lib();
} else if (agent->is_absolute_path()) {
library = os::dll_load(name, ebuf, sizeof ebuf);
if (library == NULL) {
const char *sub_msg = " in absolute path, with error: ";
size_t len = strlen(msg) + strlen(name) + strlen(sub_msg) + strlen(ebuf) + 1;
char *buf = NEW_C_HEAP_ARRAY(char, len, mtThread);
jio_snprintf(buf, len, "%s%s%s%s", msg, name, sub_msg, ebuf);
// If we can't find the agent, exit.
vm_exit_during_initialization(buf, NULL);
FREE_C_HEAP_ARRAY(char, buf);
}
} else {
// Try to load the agent from the standard dll directory
if (os::dll_locate_lib(buffer, sizeof(buffer), Arguments::get_dll_dir(),
name)) {
library = os::dll_load(buffer, ebuf, sizeof ebuf);
}
if (library == NULL) { // Try the library path directory.
if (os::dll_build_name(buffer, sizeof(buffer), name)) {
library = os::dll_load(buffer, ebuf, sizeof ebuf);
}
if (library == NULL) {
const char *sub_msg = " on the library path, with error: ";
const char *sub_msg2 = "\nModule java.instrument may be missing from runtime image.";
size_t len = strlen(msg) + strlen(name) + strlen(sub_msg) +
strlen(ebuf) + strlen(sub_msg2) + 1;
char *buf = NEW_C_HEAP_ARRAY(char, len, mtThread);
if (!agent->is_instrument_lib()) {
jio_snprintf(buf, len, "%s%s%s%s", msg, name, sub_msg, ebuf);
} else {
jio_snprintf(buf, len, "%s%s%s%s%s", msg, name, sub_msg, ebuf, sub_msg2);
}
// If we can't find the agent, exit.
vm_exit_during_initialization(buf, NULL);
FREE_C_HEAP_ARRAY(char, buf);
}
}
}
agent->set_os_lib(library);
agent->set_valid();
}
// Find the OnLoad function.
on_load_entry =
CAST_TO_FN_PTR(OnLoadEntry_t, os::find_agent_function(agent,
false,
on_load_symbols,
num_symbol_entries));
return on_load_entry;
}
instrument动态链接库的实现位于 java/instrumentat/share/native/libinstrument 入口为InvocationAdapter.c,on_load_entry方法实现是DEF_Agent_OnLoad方法。 createNewJPLISAgent是创建一个JPLISAgent(Java Programming Language Instrumentation Services) 创建完成JPLISAgent后,会读取保存premainClass、jarfile、bootClassPath等信息。
JNIEXPORT jint JNICALL
DEF_Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) {
JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;
jint result = JNI_OK;
JPLISAgent * agent = NULL;
initerror = createNewJPLISAgent(vm, &agent);
if ( initerror == JPLIS_INIT_ERROR_NONE ) {
int oldLen, newLen;
char * jarfile;
char * options;
jarAttribute* attributes;
char * premainClass;
char * bootClassPath;
/*
* Parse [=options] into jarfile and options
*/
if (parseArgumentTail(tail, &jarfile, &options) != 0) {
fprintf(stderr, "-javaagent: memory allocation failure.\n");
return JNI_ERR;
}
/*
* Agent_OnLoad is specified to provide the agent options
* argument tail in modified UTF8. However for 1.5.0 this is
* actually in the platform encoding - see 5049313.
*
* Open zip/jar file and parse archive. If can't be opened or
* not a zip file return error. Also if Premain-Class attribute
* isn't present we return an error.
*/
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 JNI_ERR;
}
premainClass = getAttribute(attributes, "Premain-Class");
if (premainClass == NULL) {
fprintf(stderr, "Failed to find Premain-Class manifest attribute in %s\n",
jarfile);
free(jarfile);
if (options != NULL) free(options);
freeAttributes(attributes);
return JNI_ERR;
}
/* Save the jarfile name */
agent->mJarfile = jarfile;
/*
* The value of the Premain-Class attribute becomes the agent
* class name. The manifest is in UTF8 so need to convert to
* modified UTF8 (see JNI spec).
*/
oldLen = (int)strlen(premainClass);
newLen = modifiedUtf8LengthOfUtf8(premainClass, oldLen);
if (newLen == oldLen) {
premainClass = strdup(premainClass);
} else {
char* str = (char*)malloc( newLen+1 );
if (str != NULL) {
convertUtf8ToModifiedUtf8(premainClass, oldLen, str, newLen);
}
premainClass = str;
}
if (premainClass == NULL) {
fprintf(stderr, "-javaagent: memory allocation failed\n");
free(jarfile);
if (options != NULL) free(options);
freeAttributes(attributes);
return JNI_ERR;
}
/*
* If the Boot-Class-Path attribute is specified then we process
* each relative URL and add it to the bootclasspath.
*/
bootClassPath = getAttribute(attributes, "Boot-Class-Path");
if (bootClassPath != NULL) {
appendBootClassPath(agent, jarfile, bootClassPath);
}
/*
* Convert JAR attributes into agent capabilities
*/
convertCapabilityAttributes(attributes, agent);
/*
* Track (record) the agent class name and options data
*/
initerror = recordCommandLineData(agent, premainClass, options);
/*
* Clean-up
*/
if (options != NULL) free(options);
freeAttributes(attributes);
free(premainClass);
}
switch (initerror) {
case JPLIS_INIT_ERROR_NONE:
result = JNI_OK;
break;
case JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT:
result = JNI_ERR;
fprintf(stderr, "java.lang.instrument/-javaagent: cannot create native agent.\n");
break;
case JPLIS_INIT_ERROR_FAILURE:
result = JNI_ERR;
fprintf(stderr, "java.lang.instrument/-javaagent: initialization of native agent failed.\n");
break;
case JPLIS_INIT_ERROR_ALLOCATION_FAILURE:
result = JNI_ERR;
fprintf(stderr, "java.lang.instrument/-javaagent: allocation failure.\n");
break;
case JPLIS_INIT_ERROR_AGENT_CLASS_NOT_SPECIFIED:
result = JNI_ERR;
fprintf(stderr, "-javaagent: agent class not specified.\n");
break;
default:
result = JNI_ERR;
fprintf(stderr, "java.lang.instrument/-javaagent: unknown error\n");
break;
}
return result;
}
/*
* JVMTI callback support
*
* We have two "stages" of callback support.
* At OnLoad time, we install a VMInit handler.
* When the VMInit handler runs, we remove the VMInit handler and install a
* ClassFileLoadHook handler.
*/
void JNICALL
eventHandlerVMInit( jvmtiEnv * jvmtienv,
JNIEnv * jnienv,
jthread thread) {
JPLISEnvironment * environment = NULL;
jboolean success = JNI_FALSE;
environment = getJPLISEnvironment(jvmtienv);
/* process the premain calls on the all the JPL agents */
if (environment == NULL) {
abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", getting JPLIS environment failed");
}
jthrowable outstandingException = NULL;
/*
* Add the jarfile to the system class path
*/
JPLISAgent * agent = environment->mAgent;
if (appendClassPath(agent, agent->mJarfile)) {
fprintf(stderr, "Unable to add %s to system class path - "
"the system class loader does not define the "
"appendToClassPathForInstrumentation method or the method failed\n",
agent->mJarfile);
free((void *)agent->mJarfile);
abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", appending to system class path failed");
}
free((void *)agent->mJarfile);
agent->mJarfile = NULL;
outstandingException = preserveThrowable(jnienv);
success = processJavaStart( environment->mAgent, jnienv);
restoreThrowable(jnienv, outstandingException);
/* if we fail to start cleanly, bring down the JVM */
if ( !success ) {
abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART ", processJavaStart failed");
}
}
/*
* If this call fails, the JVM launch will ultimately be aborted,
* so we don't have to be super-careful to clean up in partial failure
* cases.
*/
jboolean
processJavaStart( JPLISAgent * agent,
JNIEnv * jnienv) {
jboolean result;
/*
* OK, Java is up now. We can start everything that needs Java.
*/
/*
* First make our fallback InternalError throwable.
*/
result = initializeFallbackError(jnienv);
jplis_assert_msg(result, "fallback init failed");
/*
* Now make the InstrumentationImpl instance.
*/
if ( result ) {
result = createInstrumentationImpl(jnienv, agent);
jplis_assert_msg(result, "instrumentation instance creation failed");
}
/*
* Register a handler for ClassFileLoadHook (without enabling this event).
* Turn off the VMInit handler.
*/
if ( result ) {
result = setLivePhaseEventHandlers(agent);
jplis_assert_msg(result, "setting of live phase VM handlers failed");
}
/*
* Load the Java agent, and call the premain.
*/
if ( result ) {
result = startJavaAgent(agent, jnienv,
agent->mAgentClassName, agent->mOptionsString,
agent->mPremainCaller);
jplis_assert_msg(result, "agent load/premain call failed");
}
/*
* Finally surrender all of the tracking data that we don't need any more.
* If something is wrong, skip it, we will be aborting the JVM anyway.
*/
if ( result ) {
deallocateCommandLineData(agent);
}
return result;
}
VirtualMachineImpl(AttachProvider provider, String vmid)
throws AttachNotSupportedException, IOException
{
super(provider, vmid);
int pid;
try {
pid = Integer.parseInt(vmid);
} catch (NumberFormatException x) {
throw new AttachNotSupportedException("Invalid process identifier");
}
// Find the socket file. If not found then we attempt to start the
// attach mechanism in the target VM by sending it a QUIT signal.
// Then we attempt to find the socket file again.
File socket_file = new File(tmpdir, ".java_pid" + pid);
socket_path = socket_file.getPath();
if (!socket_file.exists()) {
File f = createAttachFile(pid);
sendQuitTo(pid);
// ...
int s = socket();
try {
connect(s, socket_path);
} finally {
close(s);
}
}
public synchronized void
addTransformer(ClassFileTransformer transformer, boolean canRetransform) {
if (transformer == null) {
throw new NullPointerException("null passed as 'transformer' in addTransformer");
}
if (canRetransform) {
if (!isRetransformClassesSupported()) {
throw new UnsupportedOperationException(
"adding retransformable transformers is not supported in this environment");
}
if (mRetransfomableTransformerManager == null) {
mRetransfomableTransformerManager = new TransformerManager(true);
}
mRetransfomableTransformerManager.addTransformer(transformer);
if (mRetransfomableTransformerManager.getTransformerCount() == 1) {
setHasRetransformableTransformers(mNativeAgent, true);
}
} else {
mTransformerManager.addTransformer(transformer);
if (mTransformerManager.getTransformerCount() == 1) {
setHasTransformers(mNativeAgent, true);
}
}
}
// WARNING: the native code knows the name & signature of this method
private byte[]
transform( Module module,
ClassLoader loader,
String classname,
Class> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer,
boolean isRetransformer) {
TransformerManager mgr = isRetransformer?
mRetransfomableTransformerManager :
mTransformerManager;
// module is null when not a class load or when loading a class in an
// unnamed module and this is the first type to be loaded in the package.
if (module == null) {
if (classBeingRedefined != null) {
module = classBeingRedefined.getModule();
} else {
module = (loader == null) ? jdk.internal.loader.BootLoader.getUnnamedModule()
: loader.getUnnamedModule();
}
}
if (mgr == null) {
return null; // no manager, no transform
} else {
return mgr.transform( module,
loader,
classname,
classBeingRedefined,
protectionDomain,
classfileBuffer);
}
}
public byte[]
transform( Module module,
ClassLoader loader,
String classname,
Class> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
boolean someoneTouchedTheBytecode = false;
TransformerInfo[] transformerList = getSnapshotTransformerList();
byte[] bufferToUse = classfileBuffer;
// order matters, gotta run 'em in the order they were added
for ( int x = 0; x < transformerList.length; x++ ) {
TransformerInfo transformerInfo = transformerList[x];
ClassFileTransformer transformer = transformerInfo.transformer();
byte[] transformedBytes = null;
try {
transformedBytes = transformer.transform( module,
loader,
classname,
classBeingRedefined,
protectionDomain,
bufferToUse);
}
catch (Throwable t) {
// don't let any one transformer mess it up for the others.
// This is where we need to put some logging. What should go here? FIXME
}
if ( transformedBytes != null ) {
someoneTouchedTheBytecode = true;
bufferToUse = transformedBytes;
}
}
// if someone modified it, return the modified buffer.
// otherwise return null to mean "no transforms occurred"
byte [] result;
if ( someoneTouchedTheBytecode ) {
result = bufferToUse;
}
else {
result = null;
}
return result;
}