/* * Copyright (c) 2009, 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.ins.gui; import java.awt.event.*; import java.util.*; import javax.swing.*; import com.sun.max.ins.*; import com.sun.max.ins.memory.*; import com.sun.max.tele.*; import com.sun.max.tele.MaxWatchpoint.WatchpointSettings; import com.sun.max.tele.MaxWatchpointManager.MaxDuplicateWatchpointException; import com.sun.max.tele.MaxWatchpointManager.MaxTooManyWatchpointsException; import com.sun.max.tele.debug.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.thread.*; /** * Utilities for managing {@linkplain MaxWatchpoint memory watchpoint}s in the VM. */ public final class Watchpoints { public abstract static class ToggleWatchpointRowAction extends InspectorAction { final Inspection inspection; final InspectorMemoryTableModel tableModel; final int row; /** * An action that will toggle a memory watchpoint at the location described in a memory-based table. */ public ToggleWatchpointRowAction(Inspection inspection, InspectorMemoryTableModel tableModel, int row, String title) { super(inspection, title); this.inspection = inspection; this.tableModel = tableModel; this.row = row; } /** * Sets a watchpoint if none exists at location. * * @return newly created watchpoint; null if failed. */ public abstract MaxWatchpoint setWatchpoint(); @Override protected void procedure() { final List<MaxWatchpoint> watchpoints = tableModel.getWatchpoints(row); if (watchpoints.isEmpty()) { final MaxWatchpoint newWatchpoint = setWatchpoint(); if (newWatchpoint != null) { inspection.focus().setWatchpoint(newWatchpoint); } } else { if (watchpoints.size() > 1) { if (!inspection.gui().yesNoDialog(Integer.toString(watchpoints.size()) + " watchpoints active here: DELETE ALL?")) { return; } } inspection.actions().removeWatchpoints(watchpoints, null).perform(); } } } /** * Creates a menu entry for removing a possibly empty collection of VM memory watchpoints. * * @return either a single {@link InspectorAction} or a {@link JMenu} to be used as a sub-menu. */ public static Object createRemoveActionOrMenu(Inspection inspection, final List<MaxWatchpoint> watchpoints) { if (watchpoints.isEmpty()) { return InspectorAction.dummyAction(inspection, "Remove memory watchpoint"); } if (watchpoints.size() == 1) { final MaxWatchpoint watchpoint = watchpoints.get(0); final String description = watchpoint.description(); final String title = description == null ? "Remove watchpont" : "Remove watchpoint: " + description; return inspection.actions().removeWatchpoint(watchpoint, title); } final JMenu menu = new JMenu("Remove watchpoint"); for (MaxWatchpoint watchpoint : watchpoints) { final MaxWatchpoint finalWatchpoint = watchpoint; menu.add(inspection.actions().removeWatchpoint(finalWatchpoint, watchpoint.description())); } menu.add(inspection.actions().removeWatchpoints(watchpoints, "Remove all")); return menu; } /** * Creates a menu entry for editing a possibly empty collection of VM memory watchpoints. */ public static JMenu createEditMenu(Inspection inspection, final List<MaxWatchpoint> watchpoints) { final JMenu menu = new JMenu("Modify memory watchpoint"); if (watchpoints.isEmpty()) { menu.setEnabled(false); } else if (watchpoints.size() == 1) { final MaxWatchpoint watchpoint = watchpoints.get(0); buildWatchpointMenu(inspection, menu, watchpoint); final String description = watchpoint.description(); final String title = description == null ? "Modify watchpoint" : "Modify watchpoint: " + description; menu.setText(title); } else { menu.setText("Modify watchpoints"); for (MaxWatchpoint watchpoint : watchpoints) { final JMenu subMenu = new JMenu(watchpoint.description()); buildWatchpointMenu(inspection, subMenu, watchpoint); menu.add(subMenu); } } return menu; } /** * Populates a menu for editing settings of a single watchpoint. */ private static void buildWatchpointMenu(final Inspection inspection, JMenu menu, final MaxWatchpoint watchpoint) { assert watchpoint != null; final WatchpointSettings settings = watchpoint.getSettings(); final JCheckBoxMenuItem readItem = new JCheckBoxMenuItem("Trap on read", settings.trapOnRead); readItem.addItemListener(new ItemListener() { private boolean undoing = false; public void itemStateChanged(ItemEvent itemEvent) { final JCheckBoxMenuItem item = (JCheckBoxMenuItem) itemEvent.getItem(); if (undoing) { // This is recursive notification that the state has changed when we reverse a user action; ignore it. } else { // This is a real user-initiated change to the item. final boolean newState = item.getState(); try { watchpoint.setTrapOnRead(newState); } catch (MaxVMBusyException maxVMBusyException) { // Can't carry out the change; revert the state of the item. undoing = true; // Record the fact that we're deliberately reversing the user's action so that we can // ignore the recursive notification of this change. item.setSelected(!newState); undoing = false; inspection.announceVMBusyFailure("Watchpoint READ setting"); } } } }); menu.add(readItem); final JCheckBoxMenuItem writeItem = new JCheckBoxMenuItem("Trap on write", settings.trapOnWrite); writeItem.addItemListener(new ItemListener() { private boolean undoing = false; public void itemStateChanged(ItemEvent itemEvent) { final JCheckBoxMenuItem item = (JCheckBoxMenuItem) itemEvent.getItem(); if (undoing) { // This is recursive notification that the state has changed when we reverse a user action; ignore it. } else { // This is a real user-initiated change to the item. final boolean newState = item.getState(); try { watchpoint.setTrapOnWrite(newState); } catch (MaxVMBusyException maxVMBusyException) { // Can't carry out the change; revert the state of the item. undoing = true; // Record the fact that we're deliberately reversing the user's action so that we can // ignore the recursive notification of this change. item.setSelected(!newState); undoing = false; inspection.announceVMBusyFailure("Watchpoint WRITE setting"); } } } }); menu.add(writeItem); final JCheckBoxMenuItem execItem = new JCheckBoxMenuItem("Trap on exec", settings.trapOnExec); execItem.addItemListener(new ItemListener() { private boolean undoing = false; public void itemStateChanged(ItemEvent itemEvent) { final JCheckBoxMenuItem item = (JCheckBoxMenuItem) itemEvent.getItem(); if (undoing) { // This is recursive notification that the state has changed when we reverse a user action; ignore it. } else { // This is a real user-initiated change to the item. final boolean newState = item.getState(); try { watchpoint.setTrapOnExec(newState); } catch (MaxVMBusyException maxVMBusyException) { // Can't carry out the change; revert the state of the item. undoing = true; // Record the fact that we're deliberately reversing the user's action so that we can // ignore the recursive notification of this change. item.setSelected(!newState); undoing = false; inspection.announceVMBusyFailure("Watchpoint EXEC setting"); } } } }); menu.add(execItem); final JCheckBoxMenuItem enabledDuringGCItem = new JCheckBoxMenuItem("Enabled during GC", settings.enabledDuringGC); enabledDuringGCItem.addItemListener(new ItemListener() { private boolean undoing = false; public void itemStateChanged(ItemEvent itemEvent) { final JCheckBoxMenuItem item = (JCheckBoxMenuItem) itemEvent.getItem(); if (undoing) { // This is recursive notification that the state has changed when we reverse a user action; ignore it. } else { // This is a real user-initiated change to the item. final boolean newState = item.getState(); try { watchpoint.setEnabledDuringGC(newState); } catch (MaxVMBusyException maxVMBusyException) { // Can't carry out the change; revert the state of the item. undoing = true; // Record the fact that we're deliberately reversing the user's action so that we can // ignore the recursive notification of this change. item.setSelected(!newState); undoing = false; inspection.announceVMBusyFailure("Watchpoint GC setting"); } } } }); menu.add(enabledDuringGCItem); } /* * Thread Local Watchtpoint support. * Ideally, we want a logical watchpoint on a given thread local variable name, such that a * watchpoint is set on the corresponding thread local variable of every thread. * This includes setting the watchpoint for any new thread entering the system. Thus, when selecting the check box, breakpoint must * be set on thread start to automatically add a watchpoint as soon as the variable is initialized and remove it when the thread exits. * This isn't done at the moment. We only set the watchpoint on thread in the current VM state. */ static final class ThreadLocalWatchpointItemListener implements ItemListener { static final WatchpointSettings watchpointSettings = new WatchpointSettings(false, true, false, true); static class ThreadEventListener implements MaxVMThreadEntryListener, MaxVMThreadDetachedListener { /** * Set used to track thread local variables being watched. This is used to automatically set/remove watchpoint on thread entry/exit. */ final HashSet<ThreadLocalWatchpointItemListener> watchSet = new HashSet<ThreadLocalWatchpointItemListener>(); private String tracePrefix() { return "[ThreadEventListener: " + Thread.currentThread().getName() + "] "; } public void entered(MaxThread thread) { handleTriggerEvent(thread, true, "thread Start Event"); } public void detached(MaxThread thread) { handleTriggerEvent(thread, false, "thread Detach Event"); } private void handleTriggerEvent(MaxThread thread, boolean setWatchpoint, String eventName) { if (watchSet.isEmpty()) { System.out.println("WARNING: " + tracePrefix() + eventName + " triggered with empty watchSet"); } for (ThreadLocalWatchpointItemListener listener : watchSet) { if (listener.watched) { final MaxThreadLocalVariable threadLocalVariable = listener.threadLocalVariable(thread); if (setWatchpoint) { listener.setWatchpoint(threadLocalVariable); } else { listener.removeWatchpoint(threadLocalVariable); } } else { System.out.println("WARNING: " + tracePrefix() + eventName + " found unwatched thread local \"" + listener.watchedVariableName() + "\" in watchSet"); } } } void add(Inspection inspection, ThreadLocalWatchpointItemListener watched) { if (watchSet.isEmpty()) { try { inspection.vm().addThreadEnterListener(this); inspection.vm().addThreadDetachedListener(this); } catch (MaxVMBusyException e) { // FIXME: revisit what to do here. e.printStackTrace(); } } watchSet.add(watched); } void remove(Inspection inspection, ThreadLocalWatchpointItemListener watched) { assert !watchSet.isEmpty() : "watch set must not be empty"; watchSet.remove(watched); if (watchSet.isEmpty()) { try { inspection.vm().removeThreadEnterListener(this); inspection.vm().removeThreadDetachedListener(this); } catch (MaxVMBusyException e) { // FIXME: revisit what to do here. e.printStackTrace(); } } } } static ThreadEventListener threadEventListener = new ThreadEventListener(); final Inspection inspection; final int threadLocalIndex; final String description; boolean watched; // TODO clean this up. inspection == null when watchedVariableName is called from buildThreadLocalWatchpointMenu private String watchedVariableName() { assert inspection != null; return watchedVariableName(inspection); } private String watchedVariableName(Inspection inspection) { return TeleThreadLocalsArea.Static.values(inspection.vm()).get(threadLocalIndex).name; } private MaxThreadLocalVariable threadLocalVariable(MaxThread thread) { return thread.localsBlock().tlaFor(SafepointPoll.State.ENABLED).getThreadLocalVariable(threadLocalIndex); } ThreadLocalWatchpointItemListener(Inspection inspection, VmThreadLocal threadLocal) { threadLocalIndex = threadLocal.index; description = "Write access to " + watchedVariableName(inspection); this.inspection = inspection; watched = false; } private void setWatchpoint(MaxThreadLocalVariable threadLocalVariable) { try { inspection.vm().watchpointManager().createVmThreadLocalWatchpoint(description, threadLocalVariable, watchpointSettings); } catch (MaxTooManyWatchpointsException e) { e.printStackTrace(); } catch (MaxDuplicateWatchpointException e) { // ignore } catch (MaxVMBusyException e) { inspection.announceVMBusyFailure("Watchpoint for thread local \"" + threadLocalVariable.variableName() + "\""); e.printStackTrace(); } } private void removeWatchpoint(MaxThreadLocalVariable threadLocalVariable) { List<MaxWatchpoint> watchpoints = inspection.vm().watchpointManager().findWatchpoints(threadLocalVariable.memoryRegion()); try { for (MaxWatchpoint watchpoint : watchpoints) { if (watchpoint.description().equals(description)) { watchpoint.remove(); } } } catch (MaxVMBusyException e) { inspection.announceVMBusyFailure("Watchpoint for thread local \"" + threadLocalVariable.variableName() + "\""); e.printStackTrace(); } } public void itemStateChanged(ItemEvent itemEvent) { final JCheckBoxMenuItem item = (JCheckBoxMenuItem) itemEvent.getItem(); boolean state = item.getState(); if (state != watched) { if (state) { for (MaxThread thread : inspection.vm().state().threads()) { setWatchpoint(threadLocalVariable(thread)); } threadEventListener.add(inspection, this); } else { for (MaxThread thread : inspection.vm().state().threads()) { removeWatchpoint(threadLocalVariable(thread)); } threadEventListener.remove(inspection, this); } watched = state; } } } /** * Populate a menu for setting / unsetting thread local watchpoints. * */ public static void buildThreadLocalWatchpointMenu(final Inspection inspection, JMenu menu) { for (VmThreadLocal threadLocal : TeleThreadLocalsArea.Static.values(inspection.vm())) { JCheckBoxMenuItem cbox = new JCheckBoxMenuItem(threadLocal.name, false); cbox.addItemListener(new ThreadLocalWatchpointItemListener(inspection, threadLocal)); menu.add(cbox); } } }