Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/hotspot/share/classfile/modules.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "runtime/handles.hpp"

class ModuleEntryTable;
class SerializeClosure;
class Symbol;

class Modules : AllStatic {
Expand Down
187 changes: 187 additions & 0 deletions src/hotspot/share/jfr/instrumentation/jfrClassTransformer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/

#include "classfile/classFileParser.hpp"
#include "classfile/classFileStream.hpp"
#include "classfile/classLoadInfo.hpp"
#include "classfile/javaClasses.inline.hpp"
#include "classfile/symbolTable.hpp"
#include "jfr/instrumentation/jfrClassTransformer.hpp"
#include "jfr/recorder/service/jfrOptionSet.hpp"
#include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp"
#include "logging/log.hpp"
#include "memory/allocation.inline.hpp"
#include "memory/resourceArea.hpp"
#include "oops/instanceKlass.hpp"
#include "oops/klass.inline.hpp"
#include "prims/jvmtiRedefineClasses.hpp"
#include "prims/jvmtiThreadState.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/javaThread.hpp"
#include "utilities/exceptions.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/growableArray.hpp"
#include "utilities/macros.hpp"

static void log_pending_exception(oop throwable) {
assert(throwable != nullptr, "invariant");
oop msg = java_lang_Throwable::message(throwable);
if (msg != nullptr) {
char* text = java_lang_String::as_utf8_string(msg);
if (text != nullptr) {
log_error(jfr, system) ("%s", text);
}
}
}

// On initial class load.
void JfrClassTransformer::cache_class_file_data(InstanceKlass* new_ik, const ClassFileStream* new_stream, const JavaThread* thread) {
assert(new_ik != nullptr, "invariant");
assert(new_stream != nullptr, "invariant");
assert(thread != nullptr, "invariant");
assert(!thread->has_pending_exception(), "invariant");
if (!JfrOptionSet::allow_retransforms()) {
return;
}
const jint stream_len = new_stream->length();
JvmtiCachedClassFileData* p =
(JvmtiCachedClassFileData*)NEW_C_HEAP_ARRAY_RETURN_NULL(u1, offset_of(JvmtiCachedClassFileData, data) + stream_len, mtInternal);
if (p == nullptr) {
log_error(jfr, system)("Allocation using C_HEAP_ARRAY for %zu bytes failed in JfrEventClassTransformer::cache_class_file_data",
static_cast<size_t>(offset_of(JvmtiCachedClassFileData, data) + stream_len));
return;
}
p->length = stream_len;
memcpy(p->data, new_stream->buffer(), stream_len);
new_ik->set_cached_class_file(p);
}

InstanceKlass* JfrClassTransformer::create_instance_klass(InstanceKlass*& ik, ClassFileStream* stream, bool is_initial_load, JavaThread* thread) {
if (stream == nullptr) {
if (is_initial_load) {
log_error(jfr, system)("JfrClassTransformer: unable to create ClassFileStream for %s", ik->external_name());
}
return nullptr;
}
InstanceKlass* const new_ik = create_new_instance_klass(ik, stream, thread);
if (new_ik == nullptr) {
if (is_initial_load) {
log_error(jfr, system)("JfrClassTransformer: unable to create InstanceKlass for %s", ik->external_name());
}
}
return new_ik;
}

void JfrClassTransformer::copy_traceid(const InstanceKlass* ik, const InstanceKlass* new_ik) {
assert(ik != nullptr, "invariant");
assert(new_ik != nullptr, "invariant");
new_ik->set_trace_id(ik->trace_id());
assert(TRACE_ID(ik) == TRACE_ID(new_ik), "invariant");
}

InstanceKlass* JfrClassTransformer::create_new_instance_klass(InstanceKlass* ik, ClassFileStream* stream, TRAPS) {
assert(stream != nullptr, "invariant");
ResourceMark rm(THREAD);
ClassLoaderData* const cld = ik->class_loader_data();
Handle pd(THREAD, ik->protection_domain());
Symbol* const class_name = ik->name();
ClassLoadInfo cl_info(pd);
ClassFileParser new_parser(stream,
class_name,
cld,
&cl_info,
ClassFileParser::INTERNAL, // internal visibility
THREAD);
if (HAS_PENDING_EXCEPTION) {
log_pending_exception(PENDING_EXCEPTION);
CLEAR_PENDING_EXCEPTION;
return nullptr;
}
const ClassInstanceInfo* cl_inst_info = cl_info.class_hidden_info_ptr();
InstanceKlass* const new_ik = new_parser.create_instance_klass(false, *cl_inst_info, THREAD);
if (HAS_PENDING_EXCEPTION) {
log_pending_exception(PENDING_EXCEPTION);
CLEAR_PENDING_EXCEPTION;
return nullptr;
}
assert(new_ik != nullptr, "invariant");
assert(new_ik->name() != nullptr, "invariant");
assert(ik->name() == new_ik->name(), "invariant");
return new_ik;
}

// Redefining / retransforming?
const Klass* JfrClassTransformer::find_existing_klass(const InstanceKlass* ik, JavaThread* thread) {
assert(ik != nullptr, "invariant");
assert(thread != nullptr, "invariant");
JvmtiThreadState* const state = thread->jvmti_thread_state();
return state != nullptr ? klass_being_redefined(ik, state) : nullptr;
}

const Klass* JfrClassTransformer::klass_being_redefined(const InstanceKlass* ik, JvmtiThreadState* state) {
assert(ik != nullptr, "invariant");
assert(state != nullptr, "invariant");
const GrowableArray<Klass*>* const redef_klasses = state->get_classes_being_redefined();
if (redef_klasses == nullptr || redef_klasses->is_empty()) {
return nullptr;
}
for (int i = 0; i < redef_klasses->length(); ++i) {
const Klass* const existing_klass = redef_klasses->at(i);
assert(existing_klass != nullptr, "invariant");
if (ik->name() == existing_klass->name() && ik->class_loader_data() == existing_klass->class_loader_data()) {
// 'ik' is a scratch klass. Return the klass being redefined.
return existing_klass;
}
}
return nullptr;
}

// On redefine / retransform, in case an agent modified the class, the original bytes are cached onto the scratch klass.
void JfrClassTransformer::transfer_cached_class_file_data(InstanceKlass* ik, InstanceKlass* new_ik, const ClassFileParser& parser, JavaThread* thread) {
assert(ik != nullptr, "invariant");
assert(new_ik != nullptr, "invariant");
JvmtiCachedClassFileData* const p = ik->get_cached_class_file();
if (p != nullptr) {
new_ik->set_cached_class_file(p);
ik->set_cached_class_file(nullptr);
return;
}
// No cached classfile indicates that no agent modified the klass.
// This means that the parser is holding the original bytes. Hence, we cache it onto the scratch klass.
const ClassFileStream* const stream = parser.clone_stream();
cache_class_file_data(new_ik, stream, thread);
}

void JfrClassTransformer::rewrite_klass_pointer(InstanceKlass*& ik, InstanceKlass* new_ik, ClassFileParser& parser, const JavaThread* thread) {
assert(ik != nullptr, "invariant");
assert(new_ik != nullptr, "invariant");
assert(thread != nullptr, "invariant");
assert(TRACE_ID(ik) == TRACE_ID(new_ik), "invariant");
assert(!thread->has_pending_exception(), "invariant");
// Assign original InstanceKlass* back onto "its" parser object for proper destruction.
parser.set_klass_to_deallocate(ik);
// Finally rewrite the original pointer to the newly created InstanceKlass.
ik = new_ik;
}

52 changes: 52 additions & 0 deletions src/hotspot/share/jfr/instrumentation/jfrClassTransformer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/

#ifndef SHARE_JFR_INSTRUMENTATION_JFRCLASSTRANSFORMER_HPP
#define SHARE_JFR_INSTRUMENTATION_JFRCLASSTRANSFORMER_HPP

#include "memory/allStatic.hpp"
#include "utilities/exceptions.hpp"

class ClassFileParser;
class ClassFileStream;
class InstanceKlass;

/*
* Contains common functionality used by method and event instrumentation.
*/
class JfrClassTransformer : AllStatic {
private:
static InstanceKlass* create_new_instance_klass(InstanceKlass* ik, ClassFileStream* stream, TRAPS);
static const Klass* klass_being_redefined(const InstanceKlass* ik, JvmtiThreadState* state);

public:
static const Klass* find_existing_klass(const InstanceKlass* ik, JavaThread* thread);
static InstanceKlass* create_instance_klass(InstanceKlass*& ik, ClassFileStream* stream, bool is_initial_load, JavaThread* thread);
static void copy_traceid(const InstanceKlass* ik, const InstanceKlass* new_ik);
static void transfer_cached_class_file_data(InstanceKlass* ik, InstanceKlass* new_ik, const ClassFileParser& parser, JavaThread* thread);
static void rewrite_klass_pointer(InstanceKlass*& ik, InstanceKlass* new_ik, ClassFileParser& parser, const JavaThread* thread);
static void cache_class_file_data(InstanceKlass* new_ik, const ClassFileStream* new_stream, const JavaThread* thread);
};

#endif // SHARE_JFR_INSTRUMENTATION_JFRCLASSTRANSFORMER_HPP
Loading