/* * 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.debug; import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.table.*; import com.sun.max.ins.*; import com.sun.max.ins.gui.*; import com.sun.max.ins.util.*; import com.sun.max.tele.*; import com.sun.max.tele.debug.*; import com.sun.max.unsafe.*; import com.sun.max.vm.actor.member.*; /** * A table specialized for displaying code breakpoints in the VM. */ public final class BreakpointsTable extends InspectorTable { private final InspectorView view; private final BreakpointsTableModel tableModel; public BreakpointsTable(Inspection inspection, InspectorView view, BreakpointsViewPreferences viewPreferences) { super(inspection); this.view = view; tableModel = new BreakpointsTableModel(inspection); BreakpointsColumnModel columnModel = new BreakpointsColumnModel(viewPreferences); configureDefaultTable(tableModel, columnModel); } BreakpointsColumnModel columnModel() { return (BreakpointsColumnModel) getColumnModel(); } @Override protected InspectorPopupMenu getPopupMenu(int row, int col, MouseEvent mouseEvent) { final BreakpointData breakpointData = tableModel.get(row); final InspectorPopupMenu menu = new InspectorPopupMenu("Breakpoints"); final String shortName = breakpointData.shortName(); menu.add(actions().removeBreakpoint(breakpointData.breakpoint(), "Remove: " + shortName)); if (breakpointData.isEnabled()) { menu.add(actions().disableBreakpoint(breakpointData.breakpoint(), "Disable: " + shortName)); } else { menu.add(actions().enableBreakpoint(breakpointData.breakpoint(), "Enable: " + shortName)); } menu.addSeparator(); final JMenu methodEntryBreakpoints = new JMenu("Break at Method Entry"); methodEntryBreakpoints.add(actions().setMachineCodeBreakpointAtEntriesByName()); methodEntryBreakpoints.add(actions().setBytecodeBreakpointAtMethodEntryByName()); methodEntryBreakpoints.add(actions().setBytecodeBreakpointAtMethodEntryByKey()); menu.add(methodEntryBreakpoints); menu.add(actions().setMachineCodeBreakpointAtObjectInitializer()); menu.add(actions().removeAllBreakpoints()); return menu; } @Override public void updateFocusSelection() { // Sets table selection to breakpoint, if any, that is the current user focus. final MaxBreakpoint breakpoint = focus().breakpoint(); final int row = tableModel.findRow(breakpoint); updateSelection(row); } @Override public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) { // Suppress row selection when clicking on the "Enabled" checkbox; final int modelColumnIndex = convertColumnIndexToModel(columnIndex); if (modelColumnIndex != BreakpointsColumnKind.ENABLED.ordinal()) { super.changeSelection(rowIndex, columnIndex, toggle, extend); } } /** * Receives notification that a breakpoint is about to be deleted, and communicates this * to the user. No state change is needed here because a subsequent notification will * announce the general change in the breakpoint set. * * @param breakpoint the breakpoint about to be deleted * @param reason a short explanation for the deletion */ void breakpointToBeDeleted(MaxBreakpoint breakpoint, String reason) { MachineCodeBreakpointData breakpointData = tableModel.findMachineCodeBreakpoint(breakpoint.codeLocation().address()); final String name = breakpointData == null ? breakpoint.toString() : breakpointData.longName(); final Object[] message = new Object[2]; message[0] = "Breakpoint deleted: " + name; message[1] = reason; gui().warningMessage(message); } @Override protected void mouseButton1Clicked(int row, int col, MouseEvent mouseEvent) { if (mouseEvent.getClickCount() > 1) { // Depends on the first click selecting the row, and that changing the current // code location focus to the location under the mouse event. if (row >= 0) { final BreakpointData breakpointData = tableModel.get(row); if (breakpointData != null) { final MaxBreakpoint teleBreakpoint = breakpointData.breakpoint(); focus().setBreakpoint(teleBreakpoint); } } } } /** * {@inheritDoc}. * <br> * color the text specially in the row where a triggered breakpoint is displayed */ @Override public Color cellForegroundColor(int row, int col) { return (tableModel.get(row).triggerThread() == null) ? null : preference().style().debugIPTagColor(); } public InspectorView getView() { return view; } private final class BreakpointsColumnModel extends InspectorTableColumnModel<BreakpointsColumnKind> { private BreakpointsColumnModel(BreakpointsViewPreferences viewPreferences) { super(inspection(), BreakpointsColumnKind.values().length, viewPreferences); addColumnIfSupported(BreakpointsColumnKind.TAG, new TagCellRenderer(inspection()), null); addColumnIfSupported(BreakpointsColumnKind.ENABLED, null, new DefaultCellEditor(new JCheckBox())); addColumnIfSupported(BreakpointsColumnKind.DESCRIPTION, new DescriptionCellRenderer(inspection()), null); addColumnIfSupported(BreakpointsColumnKind.LOCATION, new LocationCellRenderer(inspection()), null); addColumnIfSupported(BreakpointsColumnKind.CONDITION, new ConditionCellRenderer(), new DefaultCellEditor(new JTextField())); addColumnIfSupported(BreakpointsColumnKind.TRIGGER_THREAD, new TriggerThreadCellRenderer(inspection()), null); } } /** * A table data model built around the list of current breakpoints in the VM. * */ private final class BreakpointsTableModel extends InspectorTableModel { public BreakpointsTableModel(Inspection inspection) { super(inspection); } // Cache of information objects for each known breakpoint private final Set<BreakpointData> breakpoints = new TreeSet<BreakpointData>(); public int getColumnCount() { return BreakpointsColumnKind.values().length; } public int getRowCount() { // This gets called during superclass initialization, before the local // data has been initialized, even if you try to set row size to 0 // in the constructor. return breakpoints == null ? 0 : breakpoints.size(); } public Object getValueAt(int row, int col) { final BreakpointData breakpointData = get(row); switch (BreakpointsColumnKind.values()[col]) { case TAG: return breakpointData.kindTag(); case ENABLED: return breakpointData.isEnabled(); case DESCRIPTION: return breakpointData.shortName(); case LOCATION: return breakpointData.location(); case CONDITION: return breakpointData.condition(); case TRIGGER_THREAD: return breakpointData.triggerThreadName(); default: throw InspectorError.unexpected("Unexpected Breakpoint Data column"); } } @Override public void setValueAt(Object value, int row, int column) { final BreakpointData breakpointData = get(row); switch (BreakpointsColumnKind.values()[column]) { case ENABLED: final Boolean newState = (Boolean) value; try { breakpointData.setEnabled(newState); inspection().settings().save(); } catch (MaxVMBusyException maxVMBusyException) { final DefaultCellEditor editor = (DefaultCellEditor) columnModel().columnAt(column).getCellEditor(); final JCheckBox checkBox = (JCheckBox) editor.getComponent(); // System.out.println("Reset enabled checkbox at row=" + row + ", col=" + column); checkBox.setSelected(!newState); inspection().announceVMBusyFailure("Breakpoint ENABLED setting"); } break; case CONDITION: final String conditionText = (String) value; try { breakpointData.setCondition(conditionText); inspection().settings().save(); } catch (MaxVMBusyException maxVMBusyException) { inspection().announceVMBusyFailure("Breakpoint condition setting"); } break; default: } } @Override public boolean isCellEditable(int row, int col) { switch (BreakpointsColumnKind.values()[col]) { case ENABLED: return true; case CONDITION: // TODO (mlvdv) machinery is now in place for bytecode breakpoints to be conditional; untested. return get(row) instanceof MachineCodeBreakpointData; default: break; } return false; } @Override public Class< ? > getColumnClass(int c) { switch (BreakpointsColumnKind.values()[c]) { case TAG: return String.class; case ENABLED: return Boolean.class; case DESCRIPTION: return String.class; case LOCATION: return Number.class; case CONDITION: return String.class; case TRIGGER_THREAD: return String.class; default: throw InspectorError.unexpected("Unexected Breakpoint Data column"); } } @Override public void refresh() { // Check for current and added breakpoints // Initially assume all deleted for (BreakpointData breakpointData : breakpoints) { breakpointData.markDeleted(true); } for (MaxBreakpoint breakpoint : vm().breakpointManager().breakpoints()) { if (breakpoint.isBytecodeBreakpoint()) { // Bytecodes breakpoint final BreakpointData breakpointData = findBytecodeBreakpoint(breakpoint.codeLocation()); if (breakpointData == null) { // new breakpoint since last refresh breakpoints.add(new BytecodeBreakpointData(breakpoint)); //fireTableDataChanged(); } else { // mark as not deleted breakpointData.markDeleted(false); } } else { // Machine code breakpoint final BreakpointData breakpointData = findMachineCodeBreakpoint(breakpoint.codeLocation().address()); if (breakpointData == null) { // new breakpoint in VM since last refresh breakpoints.add(new MachineCodeBreakpointData(breakpoint)); //fireTableDataChanged(); } else { // mark as not deleted breakpointData.markDeleted(false); } } } // now remove the breakpoints that are still marked as deleted final Iterator iter = breakpoints.iterator(); while (iter.hasNext()) { final BreakpointData breakpointData = (BreakpointData) iter.next(); if (breakpointData.isDeleted()) { iter.remove(); } } super.refresh(); } @Override public String getRowDescription(int row) { return get(row).kindName() + " in " + get(row).shortName(); } BreakpointData get(int row) { int count = 0; for (BreakpointData breakpointData : breakpoints) { if (count == row) { return breakpointData; } count++; } throw InspectorError.unexpected("BreakpointsInspector.get(" + row + ") failed"); } /** * Return the table row in which a breakpoint is displayed. */ private int findRow(MaxBreakpoint breakpoint) { int row = 0; for (BreakpointData breakpointData : breakpoints) { if (breakpointData.breakpoint() == breakpoint) { return row; } row++; } return -1; } /** * Locates a machine code breakpoint already known to the Inspector. */ MachineCodeBreakpointData findMachineCodeBreakpoint(Address address) { for (BreakpointData breakpointData : breakpoints) { if (breakpointData instanceof MachineCodeBreakpointData) { final MachineCodeBreakpointData machineCodeBreakpointData = (MachineCodeBreakpointData) breakpointData; if (machineCodeBreakpointData.address().toLong() == address.toLong()) { return machineCodeBreakpointData; } } } return null; } /** * Locates a bytecode breakpoint already known to the Inspector. */ BytecodeBreakpointData findBytecodeBreakpoint(MaxCodeLocation codeLocation) { for (BreakpointData breakpointData : breakpoints) { if (breakpointData instanceof BytecodeBreakpointData) { if (breakpointData.codeLocation().isSameAs(codeLocation)) { return (BytecodeBreakpointData) breakpointData; } } } return null; } } private final class TagCellRenderer extends PlainLabel implements TableCellRenderer { TagCellRenderer(Inspection inspection) { super(inspection, null); setOpaque(true); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { final BreakpointData breakpointData = tableModel.get(row); setIcon((breakpointData.triggerThread() == null) ? null : preference().style().debugIPTagIcon()); setText(breakpointData.kindTag()); setToolTipPrefix(htmlify(tableModel.getRowDescription(row))); setWrappedToolTipHtmlText("<br>" + "Enabled=" + (breakpointData.isEnabled() ? "true" : "false")); setForeground(cellForegroundColor(row, column)); setBackground(cellBackgroundColor()); return this; } } private final class DescriptionCellRenderer extends JavaNameLabel implements TableCellRenderer { public DescriptionCellRenderer(Inspection inspection) { super(inspection, null); setOpaque(true); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { final BreakpointData breakpointData = tableModel.get(row); setToolTipPrefix(htmlify(tableModel.getRowDescription(row))); setValue(breakpointData.shortName(), "<br>" + htmlify(breakpointData.longName())); setToolTipSuffix("<br>" + htmlify(breakpointData.locationDescription())); setForeground(cellForegroundColor(row, column)); setBackground(cellBackgroundColor()); return this; } } private final class LocationCellRenderer extends PlainLabel implements TableCellRenderer { public LocationCellRenderer(Inspection inspection) { super(inspection, null); setOpaque(true); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { final BreakpointData breakpointData = tableModel.get(row); setToolTipPrefix(htmlify(tableModel.getRowDescription(row))); setText(Integer.toString(breakpointData.location())); setWrappedToolTipHtmlText("<br>" + htmlify(breakpointData.locationDescription())); setForeground(cellForegroundColor(row, column)); setBackground(cellBackgroundColor()); return this; } } private final class ConditionCellRenderer extends DefaultTableCellRenderer implements Prober { ConditionCellRenderer() { setFont(preference().style().defaultFont()); setOpaque(true); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { final JComponent component = (JComponent) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (tableModel.isCellEditable(row, column)) { component.setToolTipText("<html>" + InspectorLabel.htmlify(tableModel.getRowDescription(row)) + "<br>Breakpoint condition (editable)"); } else { component.setToolTipText("<html>" + InspectorLabel.htmlify(tableModel.getRowDescription(row)) + "<br>Breakpoint conditions not supported"); } component.setForeground(cellForegroundColor(row, column)); component.setBackground(cellBackgroundColor()); return component; } public void redisplay() { setFont(preference().style().defaultFont()); } public void refresh(boolean force) { } } private final class TriggerThreadCellRenderer extends PlainLabel implements TableCellRenderer { TriggerThreadCellRenderer(Inspection inspection) { super(inspection, null); setOpaque(true); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { final BreakpointData breakpointData = tableModel.get(row); setToolTipPrefix(htmlify(tableModel.getRowDescription(row))); if (breakpointData.triggerThread() != null) { setText(breakpointData.triggerThreadName()); setWrappedToolTipHtmlText("<br>Thread \"" + breakpointData.triggerThreadName() + "\" stopped at this breakpoint"); } else { setText(""); setWrappedToolTipHtmlText("<br>No Thread stopped at this breakpoint"); } setForeground(cellForegroundColor(row, column)); setBackground(cellBackgroundColor()); return this; } } /** * Summary of information about a breakpoint that is useful for inspection. * */ private abstract class BreakpointData implements Comparable { // TODO (mlvdv) The need for subclasses here has diminished, and the differences between the two kinds of breakpoints // could be reflected in renderer code. However, one value of this approach is that some values are cached and can // still display correctly after the process has terminated. private final MaxBreakpoint breakpoint; private boolean deleted = false; BreakpointData(MaxBreakpoint breakpoint) { this.breakpoint = breakpoint; } /** * @return the breakpoint in the VM being described */ MaxBreakpoint breakpoint() { return breakpoint; } /** * @return the location of the breakpoint in the VM in a standard format. */ final MaxCodeLocation codeLocation() { return breakpoint.codeLocation(); } /** * @return textual expression of the condition associated with this breakpoint, if any. */ final String condition() { return breakpoint.getCondition() == null ? "" : breakpoint.getCondition().toString(); } final void setCondition(String condition) throws MaxVMBusyException { try { breakpoint.setCondition(condition); inspection().settings().save(); } catch (BreakpointCondition.ExpressionException expressionException) { gui().errorMessage(String.format("Error parsing saved breakpoint condition:%n expression: %s%n error: " + condition, expressionException.getMessage()), "Breakpoint Condition Error"); } } /** * @return message describing the status of the condition, if any, associated with this breakpoint; * suitable for a ToolTip. */ final String conditionStatus() { final String condition = condition(); if (!condition.equals("")) { return "Breakpoint condition= \"" + condition + "\""; } return "No breakpoint condition set"; } /** * @return is this breakpoint currently enabled in the VM? */ final boolean isEnabled() { return breakpoint.isEnabled(); } /** * Updates the enabled state of this breakpoint. * * @param enabled new state for this breakpoint * @throws MaxVMBusyException */ final void setEnabled(boolean enabled) throws MaxVMBusyException { breakpoint.setEnabled(enabled); } /** * @return whether this breakpoint is still marked deleted after the most recent sweep */ final boolean isDeleted() { return deleted; } /** * @return the thread in the VM, if any, that is currently stopped at this breakpoint. */ final MaxThread triggerThread() { for (MaxBreakpointEvent breakpointEvent : vm().state().breakpointEvents()) { final MaxBreakpoint triggeredBreakpoint = breakpointEvent.breakpoint(); if (triggeredBreakpoint == breakpoint || triggeredBreakpoint.owner() == breakpoint) { return breakpointEvent.thread(); } } return null; } /** * @return display name of the thread, if any, that is currently stopped at this breakpoint. */ final String triggerThreadName() { return inspection().nameDisplay().longName(triggerThread()); } /** * sets the "deleted" state, used to update the list of breakpoints in the VM. */ final void markDeleted(boolean deleted) { this.deleted = deleted; } /** * @return short string identifying the kind of this breakpoint */ abstract String kindTag(); /** * @return longer string identifying the kind of this breakpoint */ abstract String kindName(); /** * @return name of the breakpoint, suitable for display in a table cell */ abstract String shortName(); /** * @return longer textual description suitable for tool tip */ abstract String longName(); /** * @return difference between the breakpoint and the beginning of the method, * described in units appropriate to each kind of breakpoint. */ abstract int location(); /** * @return a description of the breakpoint location in the VM, specifying units */ abstract String locationDescription(); @Override public final String toString() { return shortName(); } public int compareTo(Object o) { // per {@link TreeSet}, comparison must be consistent with equals int result = 0; if (!this.equals(o)) { if (o instanceof BreakpointData) { final BreakpointData breakpointData = (BreakpointData) o; result = shortName().compareTo(breakpointData.shortName()); if (result == 0) { result = longName().compareTo(breakpointData.longName()); } } if (result == 0) { result = 1; } } return result; } } private final class MachineCodeBreakpointData extends BreakpointData { private Address codeStart; private int location = 0; private String shortName; private String longName; MachineCodeBreakpointData(MaxBreakpoint machineCodeBreakpoint) { super(machineCodeBreakpoint); final Address address = codeLocation().address(); final MaxCompilation compilation = vm().machineCode().findCompilation(address); if (compilation != null) { shortName = inspection().nameDisplay().shortName(compilation); final StringBuilder sb = new StringBuilder(); sb.append("("); if (breakpoint().getDescription() == null) { sb.append("Method"); } else { sb.append(breakpoint().getDescription()); } sb.append(") "); sb.append(inspection().nameDisplay().longName(compilation, address)); longName = sb.toString(); codeStart = compilation.getCodeStart(); location = address.minus(codeStart.asAddress()).toInt(); } else { final MaxNativeFunction externalCode = vm().machineCode().findNativeFunction(address); if (externalCode != null) { codeStart = externalCode.getCodeStart(); location = address.minus(codeStart.asAddress()).toInt(); shortName = inspection().nameDisplay().shortName(externalCode); longName = "native function: " + inspection().nameDisplay().longName(externalCode); } else { // Must be an address in an unknown area of native code shortName = address.to0xHexString(); longName = "unknown native code at " + address.to0xHexString(); codeStart = address; location = 0; } } } @Override String kindTag() { return "T"; } @Override String kindName() { return "Machine Code breakpoint"; } @Override String shortName() { return shortName; } @Override String longName() { return longName; } @Override int location() { return location; } @Override String locationDescription() { return "Offset=" + (location > 0 ? "+" : "") + location + ", Address=" + codeLocation().address().to0xHexString(); } Address address() { return codeLocation().address(); } } private final class BytecodeBreakpointData extends BreakpointData { final MaxCodeLocation codeLocation; String shortName; String longName; BytecodeBreakpointData(MaxBreakpoint bytecodeBreakpoint) { super(bytecodeBreakpoint); codeLocation = bytecodeBreakpoint.codeLocation(); final MethodKey key = codeLocation.methodKey(); final int bci = codeLocation.bci(); shortName = key.holder().toJavaString(false) + "." + key.name().toString() + key.signature().toJavaString(false, false); final StringBuilder longBuilder = new StringBuilder("Method: "); longBuilder.append(key.signature().resultDescriptor().toJavaString(false)).append(" "); longBuilder.append(key.name().toString()); longBuilder.append(key.signature().toJavaString(false, false)); if (bci == -1) { longBuilder.append("(prologue)"); } else if (bci == 0) { longBuilder.append("(entry)"); } else { longBuilder.append(" + ").append(bci); } longBuilder.append(" in ").append(key.holder().toJavaString()); longName = longBuilder.toString(); } @Override String kindTag() { return "B"; } @Override String kindName() { return "Bytecodes breakpoint"; } @Override String shortName() { return shortName; } @Override String longName() { return longName; } @Override int location() { return codeLocation.bci(); } @Override String locationDescription() { if (location() > 0) { return "Position = " + location() + " bytes from entry"; } return "Position = method entry"; } } }