/* * Copyright (c) 2012, 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. */ package com.sun.max.vm.ext.jvmti; import static com.sun.max.vm.ext.jvmti.JVMTIConstants.*; import java.util.*; import com.sun.max.annotate.*; import com.sun.max.unsafe.*; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.ext.jvmti.JVMTIBreakpoints.*; import com.sun.max.vm.heap.Heap; import com.sun.max.vm.log.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.thread.*; /** * Support for JVMTI event handling. * * Most of the events can be set globally or per-thread, and are set per-agent. * * An event is dispatched to an agent if it is either set globally * or it is enabled for the generating thread, for that agent. * * Pan-agent caches are maintained for fast lookup. Event sets are represented as * little-endian bitset values where the bit is the ordinal value of the enum. * TODO Consider using EnumSet. * * Certain events, e.g. breakpoints, frame pop, require the method to be specially compiled. * To avoid routinely compiling in the support, a pan-agent event setting is maintained * that can be consulted quickly. * */ public class JVMTIEvents { /* N.B. The START phase is considered to have been entered when the VM sends the VM_START event, and similarly for VM_INIT. So when JVMTI.event receives these events it is still in the previous phase. To avoid a special case we simply add the previous phase to their bit setting. */ private static final int LIVE_PHASE = JVMTIConstants.JVMTI_PHASE_LIVE; private static final int START_LIVE_PHASES = JVMTIConstants.JVMTI_PHASE_START | JVMTIConstants.JVMTI_PHASE_LIVE; private static final int PRIMORDIAL_START_LIVE_PHASES = JVMTIConstants.JVMTI_PHASE_PRIMORDIAL | JVMTIConstants.JVMTI_PHASE_START | JVMTIConstants.JVMTI_PHASE_LIVE; /** * Enum form of int constants. * Note that there are gaps in the integer values defined by JVMTI, so in order to maintain * the relationship based on {@code ordinal} we define the missing values as {@code MISSINGn}. */ public enum E { VM_INIT(JVMTIConstants.JVMTI_EVENT_VM_INIT, START_LIVE_PHASES), VM_DEATH(JVMTIConstants.JVMTI_EVENT_VM_DEATH, LIVE_PHASE), THREAD_START(JVMTIConstants.JVMTI_EVENT_THREAD_START, START_LIVE_PHASES), THREAD_END(JVMTIConstants.JVMTI_EVENT_THREAD_END, LIVE_PHASE), CLASS_FILE_LOAD_HOOK(JVMTIConstants.JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, PRIMORDIAL_START_LIVE_PHASES), CLASS_LOAD(JVMTIConstants.JVMTI_EVENT_CLASS_LOAD, START_LIVE_PHASES), CLASS_PREPARE(JVMTIConstants.JVMTI_EVENT_CLASS_PREPARE, START_LIVE_PHASES), VM_START(JVMTIConstants.JVMTI_EVENT_VM_START, PRIMORDIAL_START_LIVE_PHASES), EXCEPTION(JVMTIConstants.JVMTI_EVENT_EXCEPTION, LIVE_PHASE), EXCEPTION_CATCH(JVMTIConstants.JVMTI_EVENT_EXCEPTION_CATCH, LIVE_PHASE), SINGLE_STEP(JVMTIConstants.JVMTI_EVENT_SINGLE_STEP, LIVE_PHASE), FRAME_POP(JVMTIConstants.JVMTI_EVENT_FRAME_POP, LIVE_PHASE), BREAKPOINT(JVMTIConstants.JVMTI_EVENT_BREAKPOINT, LIVE_PHASE), FIELD_ACCESS(JVMTIConstants.JVMTI_EVENT_FIELD_ACCESS, LIVE_PHASE), FIELD_MODIFICATION(JVMTIConstants.JVMTI_EVENT_FIELD_MODIFICATION, LIVE_PHASE), METHOD_ENTRY(JVMTIConstants.JVMTI_EVENT_METHOD_ENTRY, LIVE_PHASE), METHOD_EXIT(JVMTIConstants.JVMTI_EVENT_METHOD_EXIT, LIVE_PHASE), NATIVE_METHOD_BIND(JVMTIConstants.JVMTI_EVENT_NATIVE_METHOD_BIND, PRIMORDIAL_START_LIVE_PHASES), COMPILED_METHOD_LOAD(JVMTIConstants.JVMTI_EVENT_COMPILED_METHOD_LOAD, LIVE_PHASE), COMPILED_METHOD_UNLOAD(JVMTIConstants.JVMTI_EVENT_COMPILED_METHOD_UNLOAD, LIVE_PHASE), DYNAMIC_CODE_GENERATED(JVMTIConstants.JVMTI_EVENT_DYNAMIC_CODE_GENERATED, PRIMORDIAL_START_LIVE_PHASES), DATA_DUMP_REQUEST(JVMTIConstants.JVMTI_EVENT_DATA_DUMP_REQUEST, LIVE_PHASE), MISSING1(-1, 0), MONITOR_WAIT(JVMTIConstants.JVMTI_EVENT_MONITOR_WAIT, LIVE_PHASE), MONITOR_WAITED(JVMTIConstants.JVMTI_EVENT_MONITOR_WAITED, LIVE_PHASE), MONITOR_CONTENDED_ENTER(JVMTIConstants.JVMTI_EVENT_MONITOR_CONTENDED_ENTER, LIVE_PHASE), MONITOR_CONTENDED_ENTERED(JVMTIConstants.JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, LIVE_PHASE), MISSING2(-2, 0), MISSING3(-3, 0), MISSING4(-4, 0), RESOURCE_EXHAUSTED(JVMTIConstants.JVMTI_EVENT_RESOURCE_EXHAUSTED, LIVE_PHASE), GARBAGE_COLLECTION_START(JVMTIConstants.JVMTI_EVENT_GARBAGE_COLLECTION_START, LIVE_PHASE), GARBAGE_COLLECTION_FINISH(JVMTIConstants.JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, LIVE_PHASE), OBJECT_FREE(JVMTIConstants.JVMTI_EVENT_OBJECT_FREE, LIVE_PHASE), VM_OBJECT_ALLOC(JVMTIConstants.JVMTI_EVENT_VM_OBJECT_ALLOC, LIVE_PHASE); /** * The JVMTI code for this event. */ public final int code; /** * The zero based bit for bitsets containing this event. */ public final long bit; /** * The phases, as bit set, in which this event can be delivered. */ public final int phases; public static final E[] VALUES = values(); private static final int EVENT_COUNT = VALUES.length; public static E fromEventId(int eventId) { if (eventId < JVMTIConstants.JVMTI_MIN_EVENT_TYPE_VAL || eventId > JVMTIConstants.JVMTI_MAX_EVENT_TYPE_VAL) { return null; } return VALUES[eventId - JVMTIConstants.JVMTI_MIN_EVENT_TYPE_VAL]; } @HOSTED_ONLY private E(int code, int phases) { if (code > 0) { FatalError.check(code - JVMTIConstants.JVMTI_MIN_EVENT_TYPE_VAL == ordinal(), "JVMTIEvent code mismatch"); } this.code = code; this.phases = phases; bit = 1L << ordinal(); } } /** * A logger for JVMTI events. * It does not define an {@code Operation} class but used the {@link JVMTIEvents.E} class directly as the operation. */ public static class JVMTIEventLogger extends VMLogger { public static final int DELIVERED = 0; public static final int NO_INTEREST = 1; public static final int WRONG_PHASE = 2; private JVMTIEventLogger() { super("JVMTIEvents", E.EVENT_COUNT, "log JVMTI events"); } @Override public String operationName(int op) { return E.VALUES[op].name(); } void logEvent(E event, int status, JVMTI.Env env, Object arg) { int ord = event.ordinal(); Word statusArg = intArg(status); Word envArg = objectArg(env); // arg 2 switch (event) { case CLASS_LOAD: case CLASS_PREPARE: { log(ord, statusArg, envArg, classActorArg((ClassActor) arg)); break; } case SINGLE_STEP: case BREAKPOINT: { EventBreakpointID bptId = (EventBreakpointID) arg; log(ord, statusArg, envArg, Address.fromLong(bptId.methodID), intArg(bptId.location)); break; } case COMPILED_METHOD_LOAD: { log(ord, statusArg, envArg, methodActorArg((MethodActor) arg)); break; } default: log(ord, statusArg, envArg); } } } static final JVMTIEventLogger logger = new JVMTIEventLogger(); static class MutableLong { long value; MutableLong(long value) { this.value = value; } } private static class GCCallback implements Heap.GCCallback{ public void gcCallback(Heap.GCCallbackPhase gcCallbackPhase) { if (gcCallbackPhase == Heap.GCCallbackPhase.BEFORE) { JVMTI.event(E.GARBAGE_COLLECTION_START); } else if (gcCallbackPhase == Heap.GCCallbackPhase.AFTER) { JVMTI.event(E.GARBAGE_COLLECTION_FINISH); } } } /** * Per-agent, per-thread event settings. */ static class PerThreadSettings { private Map<Thread, JVMTIEvents.MutableLong> settings; /** * per-agent setting across all threads. */ long panThreadSettings; long get(Thread thread) { checkMap(); MutableLong ml = settings.get(thread); if (ml == null) { return 0; } else { return ml.value; } } void set(Thread thread, long value) { checkMap(); MutableLong ml = settings.get(thread); if (ml == null) { ml = new MutableLong(value); settings.put(thread, ml); } else { ml.value = value; } panThreadSettings = 0; for (MutableLong mlx : settings.values()) { panThreadSettings |= mlx.value; } } private Map<Thread, JVMTIEvents.MutableLong> checkMap() { if (settings == null) { settings = new HashMap<Thread, JVMTIEvents.MutableLong>(); } return settings; } } /** * These events require compiler support. */ static long CODE_EVENTS_SETTING = E.FIELD_ACCESS.bit | E.FIELD_MODIFICATION.bit | E.METHOD_ENTRY.bit | E.METHOD_EXIT.bit | E.BREAKPOINT.bit | E.SINGLE_STEP.bit | E.FRAME_POP.bit | E.EXCEPTION_CATCH.bit; /** * This provides a fast check for compiled code event checks. * It is the union of the event setting for all agents, both global and per-thread */ private static long panAgentEventSettingCache; /** * Fast check just for global settings across all agents. */ private static long panAgentGlobalEventSettingCache; /** * Fast check just for per-thread settings across all agents. */ private static long panAgentThreadEventSettingCache; /** * Checks whether the given event is set for any agent, either globally or for any thread. * @param eventType */ public static boolean isEventSet(JVMTIEvents.E event) { return (event.bit & panAgentEventSettingCache) != 0; } /** * Checks whether the given event is set for given agent, either globally or for a given thread. * @param env environment to check * @param event * @param vmThread thread to check */ static boolean isEventSet(JVMTI.Env env, JVMTIEvents.E event, VmThread vmThread) { long setting = event.bit; if ((setting & env.globalEventSettings) != 0) { return true; } else { return (env.perThreadEventSettings.get(vmThread.javaThread()) & setting) != 0; } } /** * Check whether any of the events requiring compiler support are set for any agent, either globally or for any thread. */ static boolean anyCodeEventsSet() { return (panAgentEventSettingCache & JVMTIEvents.CODE_EVENTS_SETTING) != 0; } static { Heap.registerGCCallback(new GCCallback()); } /** * Gets the bit setting to determine if an event should be delivered based on the phase. */ static int getPhases(E event) { return event.phases; } @NEVER_INLINE private static void debug() { } static int setEventNotificationMode(JVMTI.Env jvmtiEnv, int mode, E event, Thread thread) { if (thread == null) { // Global long newBits = newEventBits(event, mode, jvmtiEnv.globalEventSettings); if (newBits < 0) { return JVMTI_ERROR_ILLEGAL_ARGUMENT; } jvmtiEnv.globalEventSettings = newBits; } else { // Per-thread if (JVMTIThreadFunctions.checkThread(thread) == null) { return JVMTI_ERROR_THREAD_NOT_ALIVE; } long newBits = newEventBits(event, mode, jvmtiEnv.perThreadEventSettings.get(thread)); if (newBits < 0) { return JVMTI_ERROR_ILLEGAL_ARGUMENT; } jvmtiEnv.perThreadEventSettings.set(thread, newBits); } // recompute pan agent caches panAgentEventSettingCache = 0; panAgentThreadEventSettingCache = 0; panAgentGlobalEventSettingCache = 0; for (int i = 0; i < JVMTI.jvmtiEnvs.length; i++) { jvmtiEnv = JVMTI.jvmtiEnvs[i]; if (jvmtiEnv == null || jvmtiEnv.isFree()) { continue; } panAgentGlobalEventSettingCache |= jvmtiEnv.globalEventSettings; panAgentThreadEventSettingCache |= jvmtiEnv.perThreadEventSettings.panThreadSettings; panAgentEventSettingCache |= jvmtiEnv.globalEventSettings | jvmtiEnv.perThreadEventSettings.panThreadSettings; } if (event == E.SINGLE_STEP) { JVMTIBreakpoints.setSingleStep(mode == JVMTI_ENABLE); } return JVMTI_ERROR_NONE; } /** * Implementation of upcall to enable/disable event notification. */ static int setEventNotificationMode(JVMTI.Env jvmtiEnv, int mode, int eventId, Thread thread) { if (eventId == JVMTI_EVENT_METHOD_ENTRY) { debug(); } E event = E.fromEventId(eventId); if (event == null) { return JVMTI_ERROR_INVALID_EVENT_TYPE; } return setEventNotificationMode(jvmtiEnv, mode, event, thread); } static long codeEventSettings(JVMTI.Env jvmtiEnv, VmThread vmThread) { long settings; if (jvmtiEnv == null || vmThread == null) { // called from compiled code on a frame pop settings = panAgentEventSettingCache; } else { settings = jvmtiEnv.perThreadEventSettings.get(vmThread.javaThread()); } return settings & JVMTIEvents.CODE_EVENTS_SETTING; } private static long newEventBits(E event, int mode, long oldBits) { long bitSetting = event.bit; if (mode == JVMTI_ENABLE) { return oldBits | bitSetting; } else if (mode == JVMTI_DISABLE) { return oldBits & ~bitSetting; } else { return -1; } } @HOSTED_ONLY public static String bitToName(long bitSetting) { for (E event : E.VALUES) { if (bitSetting == event.bit) { return event.name(); } } return "???"; } @HOSTED_ONLY public static String inspectEventSettings(long settings) { StringBuilder sb = new StringBuilder(); boolean first = true; for (int i = 0; i < 63; i++) { long bit = 1L << i; if ((settings & bit) != 0) { if (!first) { sb.append(", "); } else { first = false; } sb.append(JVMTIEvents.bitToName(bit)); } } return sb.toString(); } }