8318124: JFR: Rewrite instrumentation to use Class-File API
Reviewed-by: mgronlun
This commit is contained in:
parent
c1aeac79ba
commit
69c0ae23a3
src
java.base/share/classes
jdk.jfr/share/classes/jdk/jfr
test/jdk/jdk/jfr/jvm
@ -187,15 +187,20 @@ module java.base {
|
||||
exports jdk.internal.classfile to
|
||||
jdk.jartool,
|
||||
jdk.jdeps,
|
||||
jdk.jfr,
|
||||
jdk.jlink,
|
||||
jdk.jshell;
|
||||
exports jdk.internal.classfile.attribute to
|
||||
jdk.jartool,
|
||||
jdk.jdeps,
|
||||
jdk.jfr,
|
||||
jdk.jlink;
|
||||
exports jdk.internal.classfile.components to
|
||||
jdk.jfr;
|
||||
exports jdk.internal.classfile.constantpool to
|
||||
jdk.jartool,
|
||||
jdk.jdeps,
|
||||
jdk.jfr,
|
||||
jdk.jlink;
|
||||
exports jdk.internal.classfile.instruction to
|
||||
jdk.jdeps,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2024, 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
|
||||
@ -31,7 +31,7 @@ import java.util.List;
|
||||
|
||||
import jdk.jfr.EventType;
|
||||
import jdk.jfr.ValueDescriptor;
|
||||
import jdk.jfr.internal.EventInstrumentation;
|
||||
import jdk.jfr.internal.util.Utils;
|
||||
import jdk.jfr.internal.consumer.ObjectContext;
|
||||
|
||||
/**
|
||||
@ -57,7 +57,7 @@ public final class RecordedEvent extends RecordedObject {
|
||||
* @return stack trace, or {@code null} if doesn't exist for the event
|
||||
*/
|
||||
public RecordedStackTrace getStackTrace() {
|
||||
return getTyped(EventInstrumentation.FIELD_STACK_TRACE, RecordedStackTrace.class, null);
|
||||
return getTyped(Utils.FIELD_STACK_TRACE, RecordedStackTrace.class, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,7 +67,7 @@ public final class RecordedEvent extends RecordedObject {
|
||||
* @return thread, or {@code null} if doesn't exist for the event
|
||||
*/
|
||||
public RecordedThread getThread() {
|
||||
return getTyped(EventInstrumentation.FIELD_EVENT_THREAD, RecordedThread.class, null);
|
||||
return getTyped(Utils.FIELD_EVENT_THREAD, RecordedThread.class, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2022, 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. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package jdk.jfr.internal;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.ClassReader;
|
||||
import jdk.internal.org.objectweb.asm.Type;
|
||||
import jdk.internal.org.objectweb.asm.util.TraceClassVisitor;
|
||||
import jdk.jfr.ValueDescriptor;
|
||||
|
||||
final class ASMToolkit {
|
||||
public static final Type TYPE_STRING = Type.getType(String.class);
|
||||
private static final Type TYPE_THREAD = Type.getType(Thread.class);
|
||||
private static final Type TYPE_CLASS = Type.getType(Class.class);
|
||||
|
||||
public static Type toType(ValueDescriptor v) {
|
||||
return switch (v.getTypeName()) {
|
||||
case "byte" -> Type.BYTE_TYPE;
|
||||
case "short" -> Type.SHORT_TYPE;
|
||||
case "int" -> Type.INT_TYPE;
|
||||
case "long" ->Type.LONG_TYPE;
|
||||
case "double" -> Type.DOUBLE_TYPE;
|
||||
case "float" -> Type.FLOAT_TYPE;
|
||||
case "char" -> Type.CHAR_TYPE;
|
||||
case "boolean" -> Type.BOOLEAN_TYPE;
|
||||
case "java.lang.String" -> TYPE_STRING;
|
||||
case "java.lang.Thread" -> TYPE_THREAD;
|
||||
case "java.lang.Class" -> TYPE_CLASS;
|
||||
default -> throw new Error("Not a valid type " + v.getTypeName());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts "int" into "I" and "java.lang.String" into "Ljava/lang/String;"
|
||||
*
|
||||
* @param typeName
|
||||
* type
|
||||
*
|
||||
* @return descriptor
|
||||
*/
|
||||
public static String getDescriptor(String typeName) {
|
||||
return switch (typeName) {
|
||||
case "int" -> "I";
|
||||
case "long" -> "J";
|
||||
case "boolean" -> "Z";
|
||||
case "float" -> "F";
|
||||
case "double" -> "D";
|
||||
case "short" -> "S";
|
||||
case "char" -> "C";
|
||||
case "byte" -> "B";
|
||||
default -> Type.getObjectType(getInternalName(typeName)).getDescriptor();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts java.lang.String into java/lang/String
|
||||
*
|
||||
* @param className
|
||||
*
|
||||
* @return internal name
|
||||
*/
|
||||
public static String getInternalName(String className) {
|
||||
return className.replace(".", "/");
|
||||
}
|
||||
|
||||
public static void logASM(String className, byte[] bytes) {
|
||||
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Generated bytecode for class " + className);
|
||||
if (Logger.shouldLog(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.TRACE)) {
|
||||
ClassReader cr = new ClassReader(bytes);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
PrintWriter w = new PrintWriter(baos);
|
||||
w.println("Bytecode:");
|
||||
cr.accept(new TraceClassVisitor(w), 0);
|
||||
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.TRACE, baos.toString());
|
||||
};
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2023, 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
|
||||
@ -25,117 +25,112 @@
|
||||
|
||||
package jdk.jfr.internal;
|
||||
|
||||
import static jdk.jfr.internal.util.Bytecode.invokespecial;
|
||||
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.constant.ConstantDescs;
|
||||
import java.lang.reflect.AccessFlag;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.AnnotationVisitor;
|
||||
import jdk.internal.org.objectweb.asm.ClassWriter;
|
||||
import jdk.internal.org.objectweb.asm.Label;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.internal.org.objectweb.asm.Opcodes;
|
||||
import jdk.internal.org.objectweb.asm.Type;
|
||||
import jdk.internal.org.objectweb.asm.commons.GeneratorAdapter;
|
||||
import jdk.internal.org.objectweb.asm.commons.Method;
|
||||
import jdk.internal.classfile.AnnotationValue;
|
||||
import jdk.internal.classfile.ClassBuilder;
|
||||
import jdk.internal.classfile.Classfile;
|
||||
import jdk.internal.classfile.Label;
|
||||
import jdk.internal.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
|
||||
import jdk.jfr.AnnotationElement;
|
||||
import jdk.jfr.Event;
|
||||
import jdk.jfr.ValueDescriptor;
|
||||
|
||||
import jdk.jfr.internal.util.Bytecode;
|
||||
import jdk.jfr.internal.util.Bytecode.MethodDesc;
|
||||
|
||||
// Helper class for building dynamic events
|
||||
public final class EventClassBuilder {
|
||||
|
||||
private static final Type TYPE_EVENT = Type.getType(Event.class);
|
||||
private static final Type TYPE_IOBE = Type.getType(IndexOutOfBoundsException.class);
|
||||
private static final Method DEFAULT_CONSTRUCTOR = Method.getMethod("void <init> ()");
|
||||
private static final Method SET_METHOD = Method.getMethod("void set (int, java.lang.Object)");
|
||||
private static final ClassDesc TYPE_EVENT = Bytecode.classDesc(Event.class);
|
||||
private static final ClassDesc TYPE_IOBE = Bytecode.classDesc(IndexOutOfBoundsException.class);
|
||||
private static final MethodDesc DEFAULT_CONSTRUCTOR = MethodDesc.of("<init>", "()V");
|
||||
private static final MethodDesc SET_METHOD = MethodDesc.of("set", "(ILjava/lang/Object;)V");
|
||||
private static final AtomicLong idCounter = new AtomicLong();
|
||||
private final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
|
||||
|
||||
private final String fullClassName;
|
||||
private final Type type;
|
||||
private final ClassDesc type;
|
||||
private final List<ValueDescriptor> fields;
|
||||
private final List<AnnotationElement> annotationElements;
|
||||
|
||||
public EventClassBuilder(List<AnnotationElement> annotationElements, List<ValueDescriptor> fields) {
|
||||
this.fullClassName = "jdk.jfr.DynamicEvent" + idCounter.incrementAndGet();
|
||||
this.type = Type.getType("L" + fullClassName.replace(".", "/") + ";");
|
||||
this.type = ClassDesc.of(fullClassName);
|
||||
this.fields = fields;
|
||||
this.annotationElements = annotationElements;
|
||||
}
|
||||
|
||||
public Class<? extends Event> build() {
|
||||
buildClassInfo();
|
||||
buildConstructor();
|
||||
buildFields();
|
||||
buildSetMethod();
|
||||
endClass();
|
||||
byte[] bytes = classWriter.toByteArray();
|
||||
ASMToolkit.logASM(fullClassName, bytes);
|
||||
byte[] bytes = Classfile.of().build(ClassDesc.of(fullClassName), cb -> build(cb));
|
||||
Bytecode.log(fullClassName, bytes);
|
||||
return SecuritySupport.defineClass(Event.class, bytes).asSubclass(Event.class);
|
||||
}
|
||||
|
||||
private void endClass() {
|
||||
classWriter.visitEnd();
|
||||
void build(ClassBuilder builder) {
|
||||
buildClassInfo(builder);
|
||||
buildConstructor(builder);
|
||||
buildFields(builder);
|
||||
buildSetMethod(builder);
|
||||
}
|
||||
|
||||
private void buildSetMethod() {
|
||||
GeneratorAdapter ga = new GeneratorAdapter(Opcodes.ACC_PUBLIC, SET_METHOD, null, null, classWriter);
|
||||
int index = 0;
|
||||
for (ValueDescriptor v : fields) {
|
||||
ga.loadArg(0);
|
||||
ga.visitLdcInsn(index);
|
||||
Label notEqual = new Label();
|
||||
ga.ifICmp(GeneratorAdapter.NE, notEqual);
|
||||
ga.loadThis();
|
||||
ga.loadArg(1);
|
||||
Type fieldType = ASMToolkit.toType(v);
|
||||
ga.unbox(ASMToolkit.toType(v));
|
||||
ga.putField(type, v.getName(), fieldType);
|
||||
ga.visitInsn(Opcodes.RETURN);
|
||||
ga.visitLabel(notEqual);
|
||||
index++;
|
||||
}
|
||||
ga.throwException(TYPE_IOBE, "Index must between 0 and " + fields.size());
|
||||
ga.endMethod();
|
||||
}
|
||||
|
||||
private void buildConstructor() {
|
||||
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, DEFAULT_CONSTRUCTOR.getName(), DEFAULT_CONSTRUCTOR.getDescriptor(), null, null);
|
||||
mv.visitIntInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, TYPE_EVENT.getInternalName(), DEFAULT_CONSTRUCTOR.getName(), DEFAULT_CONSTRUCTOR.getDescriptor(), false);
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
mv.visitMaxs(0, 0);
|
||||
}
|
||||
|
||||
private void buildClassInfo() {
|
||||
String internalSuperName = ASMToolkit.getInternalName(Event.class.getName());
|
||||
String internalClassName = type.getInternalName();
|
||||
classWriter.visit(52, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, internalClassName, null, internalSuperName, null);
|
||||
|
||||
for (AnnotationElement a : annotationElements) {
|
||||
String descriptor = ASMToolkit.getDescriptor(a.getTypeName());
|
||||
AnnotationVisitor av = classWriter.visitAnnotation(descriptor, true);
|
||||
for (ValueDescriptor v : a.getValueDescriptors()) {
|
||||
Object value = a.getValue(v.getName());
|
||||
String name = v.getName();
|
||||
if (v.isArray()) {
|
||||
AnnotationVisitor arrayVisitor = av.visitArray(name);
|
||||
Object[] array = (Object[]) value;
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
arrayVisitor.visit(null, array[i]);
|
||||
}
|
||||
arrayVisitor.visitEnd();
|
||||
} else {
|
||||
av.visit(name, value);
|
||||
}
|
||||
private void buildSetMethod(ClassBuilder builder) {
|
||||
// void Event::set(int index, Object value);
|
||||
builder.withMethod(SET_METHOD.name(), SET_METHOD.descriptor(), Classfile.ACC_PUBLIC, methodBuilder -> methodBuilder.withCode(codeBuilder -> {
|
||||
int index = 0;
|
||||
for (ValueDescriptor v : fields) {
|
||||
codeBuilder.iload(1);
|
||||
codeBuilder.ldc(index);
|
||||
Label notEqual = codeBuilder.newLabel();
|
||||
codeBuilder.if_icmpne(notEqual);
|
||||
codeBuilder.aload(0); // this
|
||||
codeBuilder.aload(2); // value
|
||||
ClassDesc cd = Bytecode.classDesc(v);
|
||||
Bytecode.unbox(codeBuilder, cd);
|
||||
codeBuilder.putfield(type, v.getName(), cd);
|
||||
codeBuilder.return_();
|
||||
codeBuilder.labelBinding(notEqual);
|
||||
index++;
|
||||
}
|
||||
av.visitEnd();
|
||||
}
|
||||
Bytecode.throwException(codeBuilder, TYPE_IOBE, "Index must between 0 and " + fields.size());
|
||||
}));
|
||||
}
|
||||
|
||||
private void buildFields() {
|
||||
private void buildConstructor(ClassBuilder builder) {
|
||||
builder.withMethod(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void, Classfile.ACC_PUBLIC, methodBuilder -> methodBuilder.withCode(codeBuilder -> {
|
||||
codeBuilder.aload(0);
|
||||
invokespecial(codeBuilder, TYPE_EVENT, DEFAULT_CONSTRUCTOR);
|
||||
codeBuilder.return_();
|
||||
}));
|
||||
}
|
||||
|
||||
private void buildClassInfo(ClassBuilder builder) {
|
||||
builder.withSuperclass(Bytecode.classDesc(Event.class));
|
||||
builder.withFlags(AccessFlag.FINAL, AccessFlag.PUBLIC, AccessFlag.SUPER);
|
||||
List<jdk.internal.classfile.Annotation> annotations = new ArrayList<>();
|
||||
for (jdk.jfr.AnnotationElement a : annotationElements) {
|
||||
List<jdk.internal.classfile.AnnotationElement> list = new ArrayList<>();
|
||||
for (ValueDescriptor v : a.getValueDescriptors()) {
|
||||
// ValueDescriptor can only hold primitive
|
||||
// No need to care about classes/enums
|
||||
var value = a.getValue(v.getName());
|
||||
var av = AnnotationValue.of(value);
|
||||
var ae = jdk.internal.classfile.AnnotationElement.of(v.getName(), av);
|
||||
list.add(ae);
|
||||
}
|
||||
ClassDesc cd = ClassDesc.of(a.getTypeName());
|
||||
annotations.add(jdk.internal.classfile.Annotation.of(cd, list));
|
||||
}
|
||||
builder.with(RuntimeVisibleAnnotationsAttribute.of(annotations));
|
||||
}
|
||||
|
||||
private void buildFields(ClassBuilder builder) {
|
||||
for (ValueDescriptor v : fields) {
|
||||
String internal = ASMToolkit.getDescriptor(v.getTypeName());
|
||||
classWriter.visitField(Opcodes.ACC_PRIVATE, v.getName(), internal, null, null);
|
||||
builder.withField(v.getName(), Bytecode.classDesc(v), Classfile.ACC_PRIVATE);
|
||||
// No need to store annotations on field since they will be replaced anyway.
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -25,38 +25,38 @@
|
||||
|
||||
package jdk.jfr.internal;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.commons.Method;
|
||||
import jdk.jfr.internal.EventInstrumentation.FieldInfo;
|
||||
import jdk.jfr.internal.event.EventConfiguration;
|
||||
import jdk.jfr.internal.util.Bytecode.FieldDesc;
|
||||
import jdk.jfr.internal.util.Bytecode.MethodDesc;
|
||||
import jdk.jfr.internal.util.Utils;
|
||||
|
||||
public enum EventWriterMethod {
|
||||
|
||||
BEGIN_EVENT("(" + jdk.internal.org.objectweb.asm.Type.getType(EventConfiguration.class).getDescriptor() + "J)Z", "???", "beginEvent"),
|
||||
END_EVENT("()Z", "???", "endEvent"),
|
||||
PUT_BYTE("(B)V", "byte", "putByte"),
|
||||
PUT_SHORT("(S)V", "short", "putShort"),
|
||||
PUT_INT("(I)V", "int", "putInt"),
|
||||
PUT_LONG("(J)V", "long", "putLong"),
|
||||
PUT_FLOAT("(F)V", "float", "putFloat"),
|
||||
PUT_DOUBLE("(D)V", "double", "putDouble"),
|
||||
PUT_CHAR("(C)V", "char", "putChar"),
|
||||
PUT_BOOLEAN("(Z)V", "boolean", "putBoolean"),
|
||||
PUT_THREAD("(Ljava/lang/Thread;)V", Type.THREAD.getName(), "putThread"),
|
||||
PUT_CLASS("(Ljava/lang/Class;)V", Type.CLASS.getName(), "putClass"),
|
||||
PUT_STRING("(Ljava/lang/String;)V", Type.STRING.getName(), "putString"),
|
||||
PUT_EVENT_THREAD("()V", Type.THREAD.getName(), "putEventThread"),
|
||||
PUT_STACK_TRACE("()V", Type.TYPES_PREFIX + "StackTrace", "putStackTrace");
|
||||
BEGIN_EVENT("beginEvent", "(Ljdk/jfr/internal/event/EventConfiguration;J)Z", "???"),
|
||||
END_EVENT("endEvent", "()Z", "???"),
|
||||
PUT_BYTE("putByte", "(B)V", "B"),
|
||||
PUT_SHORT("putShort", "(S)V", "S"),
|
||||
PUT_INT("putInt", "(I)V", "I"),
|
||||
PUT_LONG("putLong", "(J)V", "J"),
|
||||
PUT_FLOAT("putFloat", "(F)V", "F"),
|
||||
PUT_DOUBLE("putDouble", "(D)V", "D"),
|
||||
PUT_CHAR("putChar", "(C)V", "C"),
|
||||
PUT_BOOLEAN("putBoolean", "(Z)V", "Z"),
|
||||
PUT_THREAD("putThread", "(Ljava/lang/Thread;)V", "Ljava/lang/Thread;"),
|
||||
PUT_CLASS("putClass", "(Ljava/lang/Class;)V", "Ljava/lang/Class;"),
|
||||
PUT_STRING("putString", "(Ljava/lang/String;)V", "Ljava/lang/String;"),
|
||||
PUT_EVENT_THREAD("putEventThread", "()V", "???"),
|
||||
PUT_STACK_TRACE("putStackTrace", "()V", "???");
|
||||
|
||||
final Method asmMethod;
|
||||
final String typeDescriptor;
|
||||
final MethodDesc method;
|
||||
final String fieldType;
|
||||
|
||||
EventWriterMethod(String paramSignature, String typeName, String methodName) {
|
||||
this.typeDescriptor = ASMToolkit.getDescriptor(typeName);
|
||||
this.asmMethod = new Method(methodName, paramSignature);
|
||||
EventWriterMethod(String methodName, String paramType, String fieldType) {
|
||||
this.fieldType = fieldType;
|
||||
this.method = MethodDesc.of(methodName, paramType);
|
||||
}
|
||||
|
||||
public Method asASM() {
|
||||
return asmMethod;
|
||||
public MethodDesc method() {
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,16 +67,16 @@ public enum EventWriterMethod {
|
||||
*
|
||||
* @return the method
|
||||
*/
|
||||
public static EventWriterMethod lookupMethod(FieldInfo field) {
|
||||
public static EventWriterMethod lookupMethod(FieldDesc field) {
|
||||
// event thread
|
||||
if (field.name().equals(EventInstrumentation.FIELD_EVENT_THREAD)) {
|
||||
if (field.name().equals(Utils.FIELD_EVENT_THREAD)) {
|
||||
return EventWriterMethod.PUT_EVENT_THREAD;
|
||||
}
|
||||
for (EventWriterMethod m : EventWriterMethod.values()) {
|
||||
if (field.descriptor().equals(m.typeDescriptor)) {
|
||||
if (field.type().descriptorString().equals(m.fieldType)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
throw new Error("Unknown type " + field.descriptor());
|
||||
throw new Error("Unknown field type " + field.type());
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import java.lang.reflect.Modifier;
|
||||
|
||||
import jdk.jfr.internal.event.EventConfiguration;
|
||||
import jdk.jfr.internal.instrument.JDKEvents;
|
||||
import jdk.jfr.internal.util.Utils;
|
||||
import jdk.jfr.internal.util.Bytecode;
|
||||
/**
|
||||
* All upcalls from the JVM should go through this class.
|
||||
*
|
||||
@ -75,7 +75,7 @@ final class JVMUpcalls {
|
||||
Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Adding instrumentation to event class " + clazz.getName() + " using retransform");
|
||||
EventInstrumentation ei = new EventInstrumentation(clazz.getSuperclass(), oldBytes, traceId, bootClassLoader, false);
|
||||
byte[] bytes = ei.buildInstrumented();
|
||||
ASMToolkit.logASM(clazz.getName(), bytes);
|
||||
Bytecode.log(clazz.getName(), bytes);
|
||||
return bytes;
|
||||
}
|
||||
return JDKEvents.retransformCallback(clazz, oldBytes);
|
||||
@ -126,7 +126,7 @@ final class JVMUpcalls {
|
||||
EventWriterKey.ensureEventWriterFactory();
|
||||
Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Adding " + (forceInstrumentation ? "forced " : "") + "instrumentation for event type " + eventName + " during initial class load");
|
||||
byte[] bytes = ei.buildInstrumented();
|
||||
ASMToolkit.logASM(ei.getClassName() + "(" + traceId + ")", bytes);
|
||||
Bytecode.log(ei.getClassName() + "(" + traceId + ")", bytes);
|
||||
return bytes;
|
||||
} catch (Throwable t) {
|
||||
Logger.log(LogTag.JFR_SYSTEM, LogLevel.WARN, "Unexpected error when adding instrumentation for event type " + eventName);
|
||||
|
@ -76,26 +76,26 @@ public final class TypeLibrary {
|
||||
private static ValueDescriptor createStartTimeField() {
|
||||
var annos = createStandardAnnotations("Start Time", null);
|
||||
annos.add(new jdk.jfr.AnnotationElement(Timestamp.class, Timestamp.TICKS));
|
||||
return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_START_TIME, Type.LONG, annos, 0, false,
|
||||
EventInstrumentation.FIELD_START_TIME);
|
||||
return PrivateAccess.getInstance().newValueDescriptor(Utils.FIELD_START_TIME, Type.LONG, annos, 0, false,
|
||||
Utils.FIELD_START_TIME);
|
||||
}
|
||||
|
||||
private static ValueDescriptor createStackTraceField() {
|
||||
var annos = createStandardAnnotations("Stack Trace", "Stack Trace starting from the method the event was committed in");
|
||||
return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_STACK_TRACE, Type.STACK_TRACE, annos, 0, true,
|
||||
EventInstrumentation.FIELD_STACK_TRACE);
|
||||
return PrivateAccess.getInstance().newValueDescriptor(Utils.FIELD_STACK_TRACE, Type.STACK_TRACE, annos, 0, true,
|
||||
Utils.FIELD_STACK_TRACE);
|
||||
}
|
||||
|
||||
private static ValueDescriptor createThreadField() {
|
||||
var annos = createStandardAnnotations("Event Thread", "Thread in which event was committed in");
|
||||
return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_EVENT_THREAD, Type.THREAD, annos, 0, true,
|
||||
EventInstrumentation.FIELD_EVENT_THREAD);
|
||||
return PrivateAccess.getInstance().newValueDescriptor(Utils.FIELD_EVENT_THREAD, Type.THREAD, annos, 0, true,
|
||||
Utils.FIELD_EVENT_THREAD);
|
||||
}
|
||||
|
||||
private static ValueDescriptor createDurationField() {
|
||||
var annos = createStandardAnnotations("Duration", null);
|
||||
annos.add(new jdk.jfr.AnnotationElement(Timespan.class, Timespan.TICKS));
|
||||
return PrivateAccess.getInstance().newValueDescriptor(EventInstrumentation.FIELD_DURATION, Type.LONG, annos, 0, false, EventInstrumentation.FIELD_DURATION);
|
||||
return PrivateAccess.getInstance().newValueDescriptor(Utils.FIELD_DURATION, Type.LONG, annos, 0, false, Utils.FIELD_DURATION);
|
||||
}
|
||||
|
||||
public static synchronized void initialize() {
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
package jdk.jfr.internal.consumer;
|
||||
|
||||
import static jdk.jfr.internal.EventInstrumentation.FIELD_DURATION;
|
||||
import static jdk.jfr.internal.util.Utils.FIELD_DURATION;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
166
src/jdk.jfr/share/classes/jdk/jfr/internal/util/Bytecode.java
Normal file
166
src/jdk.jfr/share/classes/jdk/jfr/internal/util/Bytecode.java
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package jdk.jfr.internal.util;
|
||||
|
||||
import jdk.jfr.ValueDescriptor;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.constant.ConstantDescs;
|
||||
import java.lang.constant.MethodTypeDesc;
|
||||
import java.util.Objects;
|
||||
import jdk.jfr.internal.Logger;
|
||||
import jdk.jfr.internal.LogLevel;
|
||||
import jdk.jfr.internal.LogTag;
|
||||
import jdk.internal.classfile.CodeBuilder;
|
||||
import jdk.internal.classfile.ClassModel;
|
||||
import jdk.internal.classfile.Classfile;
|
||||
import jdk.internal.classfile.components.ClassPrinter;
|
||||
|
||||
/**
|
||||
* Helper class when working with bytecode.
|
||||
*/
|
||||
public final class Bytecode {
|
||||
|
||||
private static final ClassDesc CD_Thread = classDesc(Thread.class);
|
||||
|
||||
public record ClassMethodDesc(ClassDesc type, MethodDesc method) {
|
||||
public static ClassMethodDesc of(Class<?> clazz, String method, String desrciptor) {
|
||||
return new ClassMethodDesc(classDesc(clazz), MethodDesc.of(method, desrciptor));
|
||||
}
|
||||
}
|
||||
|
||||
public record FieldDesc(ClassDesc type, String name) {
|
||||
public static FieldDesc of(ClassDesc type, String name) {
|
||||
return new FieldDesc(type, name);
|
||||
}
|
||||
|
||||
public static FieldDesc of(Class<?> type, String name) {
|
||||
return of(classDesc(type), name);
|
||||
}
|
||||
}
|
||||
|
||||
public record MethodDesc(String name, MethodTypeDesc descriptor) {
|
||||
public static MethodDesc of(String methodName, String descriptor) {
|
||||
return new MethodDesc(methodName, MethodTypeDesc.ofDescriptor(descriptor));
|
||||
}
|
||||
|
||||
public static MethodDesc of(String methodName, Class<?> returnType, Class<?>... parameters) {
|
||||
ClassDesc[] parameterDesc = new ClassDesc[parameters.length];
|
||||
for (int i = 0; i < parameterDesc.length; i++) {
|
||||
parameterDesc[i] = classDesc(parameters[i]);
|
||||
}
|
||||
ClassDesc returnDesc = classDesc(returnType);
|
||||
MethodTypeDesc mtd = MethodTypeDesc.of(returnDesc, parameterDesc);
|
||||
return new MethodDesc(methodName, mtd);
|
||||
}
|
||||
}
|
||||
|
||||
public static ClassDesc classDesc(ValueDescriptor v) {
|
||||
String typeName = v.getTypeName();
|
||||
return switch (typeName) {
|
||||
case "boolean" -> ConstantDescs.CD_boolean;
|
||||
case "byte" -> ConstantDescs.CD_byte;
|
||||
case "short" -> ConstantDescs.CD_short;
|
||||
case "char" -> ConstantDescs.CD_char;
|
||||
case "int" -> ConstantDescs.CD_int;
|
||||
case "long" -> ConstantDescs.CD_long;
|
||||
case "double" -> ConstantDescs.CD_double;
|
||||
case "float" -> ConstantDescs.CD_float;
|
||||
case "java.lang.String" -> ConstantDescs.CD_String;
|
||||
case "java.lang.Class" -> ConstantDescs.CD_Class;
|
||||
case "java.lang.Thread" -> CD_Thread;
|
||||
default -> throw new InternalError("Unsupported JFR type " + v.getTypeName());
|
||||
};
|
||||
}
|
||||
|
||||
public static ClassDesc classDesc(Class<?> clazz) {
|
||||
return ClassDesc.ofDescriptor(clazz.descriptorString());
|
||||
}
|
||||
|
||||
public static void getfield(CodeBuilder codeBuilder, ClassDesc owner, FieldDesc field) {
|
||||
codeBuilder.getfield(owner, field.name(), field.type());
|
||||
}
|
||||
|
||||
public static void putfield(CodeBuilder codeBuilder, ClassDesc owner, FieldDesc field) {
|
||||
codeBuilder.putfield(owner, field.name(), field.type());
|
||||
}
|
||||
|
||||
public static void invokestatic(CodeBuilder codeBuilder, ClassDesc owner, MethodDesc method) {
|
||||
codeBuilder.invokestatic(owner, method.name(), method.descriptor());
|
||||
}
|
||||
|
||||
public static void invokespecial(CodeBuilder codeBuilder, ClassDesc owner, MethodDesc method) {
|
||||
codeBuilder.invokespecial(owner, method.name(), method.descriptor());
|
||||
}
|
||||
|
||||
public static void invokevirtual(CodeBuilder codeBuilder, ClassDesc owner, MethodDesc method) {
|
||||
codeBuilder.invokevirtual(owner, method.name(), method.descriptor());
|
||||
}
|
||||
|
||||
public static void invokevirtual(CodeBuilder codeBuilder, ClassMethodDesc cmd) {
|
||||
invokevirtual(codeBuilder, cmd.type(), cmd.method());
|
||||
}
|
||||
|
||||
public static void unbox(CodeBuilder codeBuilder, ClassDesc type) {
|
||||
if (!type.isPrimitive()) {
|
||||
codeBuilder.checkcast(type);
|
||||
return;
|
||||
}
|
||||
ClassMethodDesc unboxer = switch (type.descriptorString()) {
|
||||
case "B" -> ClassMethodDesc.of(Byte.class, "byteValue", "()B");
|
||||
case "S" -> ClassMethodDesc.of(Short.class, "shortValue", "()S");
|
||||
case "C" -> ClassMethodDesc.of(Character.class, "charValue", "()C");
|
||||
case "I" -> ClassMethodDesc.of(Integer.class, "intValue", "()I");
|
||||
case "J" -> ClassMethodDesc.of(Long.class, "longValue", "()J");
|
||||
case "F" -> ClassMethodDesc.of(Float.class, "floatValue", "()F");
|
||||
case "D" -> ClassMethodDesc.of(Double.class, "doubleValue", "()D");
|
||||
case "Z" -> ClassMethodDesc.of(Boolean.class, "booleanValue", "()Z");
|
||||
default -> throw new InternalError("Unsupported JFR type " + type.descriptorString());
|
||||
};
|
||||
codeBuilder.checkcast(unboxer.type());
|
||||
invokevirtual(codeBuilder, unboxer);
|
||||
}
|
||||
|
||||
public static void throwException(CodeBuilder cb, ClassDesc type, String message) {
|
||||
Objects.requireNonNull(message);
|
||||
cb.new_(type);
|
||||
cb.dup();
|
||||
cb.ldc(message);
|
||||
MethodDesc md = MethodDesc.of("<init>", void.class, String.class);
|
||||
invokespecial(cb, type, md);
|
||||
cb.athrow();
|
||||
}
|
||||
|
||||
public static void log(String className, byte[] bytes) {
|
||||
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.INFO, "Generated bytecode for class " + className);
|
||||
if (Logger.shouldLog(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.TRACE)) {
|
||||
StringBuilder out = new StringBuilder();
|
||||
out.append("Bytecode:");
|
||||
out.append(System.lineSeparator());
|
||||
ClassModel classModel = Classfile.of().parse(bytes);
|
||||
ClassPrinter.toYaml(classModel, ClassPrinter.Verbosity.TRACE_ALL, out::append);
|
||||
Logger.log(LogTag.JFR_SYSTEM_BYTECODE, LogLevel.TRACE, out.toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -56,6 +56,11 @@ import jdk.jfr.internal.settings.StackTraceSetting;
|
||||
import jdk.jfr.internal.settings.ThresholdSetting;
|
||||
|
||||
public final class Utils {
|
||||
public static final String FIELD_DURATION = "duration";
|
||||
public static final String FIELD_STACK_TRACE = "stackTrace";
|
||||
public static final String FIELD_START_TIME = "startTime";
|
||||
public static final String FIELD_EVENT_THREAD = "eventThread";
|
||||
|
||||
private static final Object flushObject = new Object();
|
||||
private static final String LEGACY_EVENT_NAME_PREFIX = "com.oracle.jdk.";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, 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
|
||||
@ -37,6 +37,6 @@ public class TestEventWriterLog {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-Xlog:jfr+system+bytecode=trace", "-XX:StartFlightRecording", "-version");
|
||||
OutputAnalyzer output = new OutputAnalyzer(pb.start());
|
||||
output.shouldContain("extends jdk/jfr/events/AbstractJDKEvent");
|
||||
output.shouldContain("superclass: jdk/jfr/events/AbstractJDKEvent");
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user