/* * 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 java.util.List; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; import com.sun.max.ins.*; import com.sun.max.ins.gui.*; import com.sun.max.ins.memory.*; import com.sun.max.ins.value.*; import com.sun.max.ins.value.WordValueLabel.ValueMode; import com.sun.max.tele.*; import com.sun.max.unsafe.*; import com.sun.max.vm.stack.VMFrameLayout.Slot; import com.sun.max.vm.stack.VMFrameLayout.Slots; import com.sun.max.vm.value.*; /** * A table that displays the contents of a VM compiled method stack frame in the VM. */ public class CompiledStackFrameTable extends InspectorTable { private final InspectorView view; private final MaxStackFrame.Compiled compiledStackFrame; private final CompiledStackFrameViewPreferences viewPreferences; private final CompiledStackFrameTableModel tableModel; /** * A table specialized to display the slots in a Java method stack frame in the VM. * <br> * Each slot is assumed to occupy one word in memory. * * @param view the view that owns the table * @param compiledStackFrame a stack frame to display * @param viewPreferences preferences for this view */ public CompiledStackFrameTable(Inspection inspection, InspectorView view, MaxStackFrame.Compiled compiledStackFrame, CompiledStackFrameViewPreferences viewPreferences) { super(inspection); this.view = view; this.compiledStackFrame = compiledStackFrame; this.viewPreferences = viewPreferences; this.tableModel = new CompiledStackFrameTableModel(inspection, compiledStackFrame); CompiledStackFrameTableColumnModel columnModel = new CompiledStackFrameTableColumnModel(this, this.tableModel, viewPreferences); configureMemoryTable(tableModel, columnModel); } @Override protected void mouseButton1Clicked(final int row, final int col, MouseEvent mouseEvent) { if (mouseEvent.getClickCount() > 1 && vm().watchpointManager() != null) { final InspectorAction toggleAction = new Watchpoints.ToggleWatchpointRowAction(inspection(), tableModel, row, "Toggle watchpoint") { @Override public MaxWatchpoint setWatchpoint() { final MaxMemoryRegion memoryRegion = tableModel.getMemoryRegion(row); final String regionDescription = "Stack: thread=" + inspection().nameDisplay().shortName(compiledStackFrame.stack().thread()); actions().setRegionWatchpoint(memoryRegion, "Set memory watchpoint", regionDescription).perform(); final List<MaxWatchpoint> watchpoints = tableModel.getWatchpoints(row); if (!watchpoints.isEmpty()) { return watchpoints.get(0); } return null; } }; toggleAction.perform(); } } @Override protected InspectorPopupMenu getPopupMenu(final int row, final int col, MouseEvent mouseEvent) { if (vm().watchpointManager() != null && col == CompiledStackFrameColumnKind.TAG.ordinal()) { final InspectorPopupMenu menu = new InspectorPopupMenu(); final MaxMemoryRegion memoryRegion = tableModel.getMemoryRegion(row); final Slot slot = (Slot) tableModel.getValueAt(row, col); final String regionDescription = "Stack slot : " + slot.name; menu.add(new Watchpoints.ToggleWatchpointRowAction(inspection(), tableModel, row, "Toggle watchpoint (double-click)") { @Override public MaxWatchpoint setWatchpoint() { actions().setRegionWatchpoint(memoryRegion, "Set memory watchpoint", regionDescription).perform(); final List<MaxWatchpoint> watchpoints = tableModel.getWatchpoints(row); if (!watchpoints.isEmpty()) { return watchpoints.get(0); } return null; } }); menu.add(actions().setRegionWatchpoint(memoryRegion, "Watch this memory location", regionDescription)); menu.add(Watchpoints.createEditMenu(inspection(), tableModel.getWatchpoints(row))); menu.add(Watchpoints.createRemoveActionOrMenu(inspection(), tableModel.getWatchpoints(row))); return menu; } return null; } @Override public void updateFocusSelection() { // Sets table selection to the memory word, if any, that is the current user focus. final Address address = focus().address(); updateSelection(tableModel.findRow(address)); } @Override public void valueChanged(ListSelectionEvent e) { // The selection in the table has changed; might have happened via user action (click, arrow) or // as a side effect of a focus change. super.valueChanged(e); if (!e.getValueIsAdjusting()) { final int row = getSelectedRow(); if (row >= 0 && row < tableModel.getRowCount()) { focus().setAddress(tableModel.getAddress(row)); } } } /** * {@inheritDoc}. * <br> * Color the text specially in the row where a watchpoint is triggered */ @Override public Color cellForegroundColor(int row, int col) { final MaxWatchpointEvent watchpointEvent = vm().state().watchpointEvent(); if (watchpointEvent != null && tableModel.getMemoryRegion(row).contains(watchpointEvent.address())) { return preference().style().debugIPTagColor(); } return null; } public InspectorView getView() { return view; } /** * A column model for Java stack frames. * Column selection is driven by choices in the parent. * This implementation cannot update column choices dynamically. */ private final class CompiledStackFrameTableColumnModel extends InspectorTableColumnModel<CompiledStackFrameColumnKind> { CompiledStackFrameTableColumnModel(InspectorTable table, InspectorMemoryTableModel tableModel, CompiledStackFrameViewPreferences viewPreferences) { super(inspection(), CompiledStackFrameColumnKind.values().length, viewPreferences); addColumnIfSupported(CompiledStackFrameColumnKind.TAG, new MemoryTagTableCellRenderer(inspection(), table, tableModel), null); addColumnIfSupported(CompiledStackFrameColumnKind.NAME, new NameRenderer(inspection()), null); addColumnIfSupported(CompiledStackFrameColumnKind.ADDRESS, new MemoryAddressLocationTableCellRenderer(inspection(), table, tableModel), null); addColumnIfSupported(CompiledStackFrameColumnKind.OFFSET_SP, new OffsetSPRenderer(inspection()), null); addColumnIfSupported(CompiledStackFrameColumnKind.OFFSET_FP, new OffsetFPRenderer(inspection()), null); addColumnIfSupported(CompiledStackFrameColumnKind.VALUE, new ValueRenderer(inspection()), null); addColumnIfSupported(CompiledStackFrameColumnKind.REGION, new MemoryRegionPointerTableCellRenderer(inspection(), table, tableModel), null); } } /** * A table model that represents the information in a Java stack frame as a table of * slots, one per memory word. * <p> * For the purposes of memory in this view, the origin is assumed to be the Stack Pointer. * Note also that the frame slots are at descending memory addresses, so the origin is * not the first location in the memory table. */ private final class CompiledStackFrameTableModel extends InspectorMemoryTableModel { private final MaxStackFrame.Compiled javaStackFrame; private final int frameSize; private final Slots slots; private final MaxMemoryRegion[] regions; private final String[] slotDescriptions; public CompiledStackFrameTableModel(Inspection inspection, MaxStackFrame.Compiled javaStackFrame) { super(inspection, javaStackFrame.slotBase()); this.javaStackFrame = javaStackFrame; frameSize = javaStackFrame.layout().frameSize(); slots = javaStackFrame.layout().slots(); regions = new MaxMemoryRegion[slots.size()]; slotDescriptions = new String[slots.size()]; int index = 0; for (Slot slot : slots) { regions[index] = new FixedMemoryRegion(inspection(), "", getOrigin().plus(slot.offset), vm().platform().nBytesInWord()); slotDescriptions[index] = "Stack frame slot \"" + slot.name + "\""; index++; } } public int getColumnCount() { return CompiledStackFrameColumnKind.values().length; } public int getRowCount() { return javaStackFrame.layout().slots().size(); } public Object getValueAt(int rowIndex, int columnIndex) { return slots.slot(rowIndex); } @Override public Class< ? > getColumnClass(int col) { return Slot.class; } @Override public int findRow(Address address) { final int wordOffset = regions[0].start().minus(address).dividedBy(vm().platform().nBytesInWord()).toInt(); return (wordOffset >= 0 && wordOffset < slots.size()) ? wordOffset : -1; } @Override public MaxMemoryRegion getMemoryRegion(int row) { return regions[row]; } @Override public int getOffset(int row) { // Slot offsets are relative to Stack Pointer return slots.slot(row).offset; } @Override public String getRowDescription(int row) { return slotDescriptions[row]; } /** * Offset of the slot relative to the Stack Pointer. * * @param row row number of the stack frame slot * @param biasOffset whether offsets should be biased * @return the slot offset relative to the SP */ public int getSPOffset(int row, boolean biasOffset) { if (biasOffset) { return javaStackFrame.biasedFPOffset(getOffset(row)) + frameSize; } return getOffset(row); } /** * Offset of the slot relative to the Frame Pointer. * * @param row row number of the stack frame slot * @param biasOffset whether offsets should be biased * @return the slot offset relative to the FP */ public int getFPOffset(int row, boolean biasOffset) { if (biasOffset) { return javaStackFrame.biasedFPOffset(getOffset(row)); } return getOffset(row) - frameSize; } public String getSlotName(int row) { return slots.slot(row).name; } /** * Gets the Java source variable name (if any) for a given slot. * * @param row the slot for which the Java source variable name is being requested * @return the Java source name for {@code slot} or null if a name is not available */ public String getSourceVariableName(int row) { return javaStackFrame.sourceVariableName(row); } } /** * @return a string representing the currently displayed data in the panel, * not clipped by scrolling. */ public String getContentString() { final StringBuilder result = new StringBuilder(); final int columnCount = getColumnCount(); for (int row = 0; row < tableModel.getRowCount(); row++) { for (int col = 0; col < columnCount; col++) { if (col > 0) { result.append("\t"); } TableCellRenderer cellRenderer = this.getCellRenderer(row, col); Object valueAt = tableModel.getValueAt(row, col); final InspectorLabel label = (InspectorLabel) cellRenderer.getTableCellRendererComponent(this, valueAt, false, false, row, col); result.append(label.getTextDeHtmlify()); } result.append("\n"); } return result.toString(); } private final class NameRenderer extends TextLabel implements TableCellRenderer { public NameRenderer(Inspection inspection) { super(inspection, null); setOpaque(true); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { final Slot slot = (Slot) value; if (slot == null) { return gui().getUnavailableDataTableCellRenderer(); } setText(slot.name); String otherInfo = ""; if (viewPreferences.biasSlotOffsets()) { final int biasedOffset = tableModel.getFPOffset(row, viewPreferences.biasSlotOffsets()); otherInfo = String.format("(%%fp %+d)", biasedOffset); } final String sourceVariableName = tableModel.getSourceVariableName(row); final int offset = tableModel.getSPOffset(row, false); final String toolTipText = String.format("SP %+d%s%s", offset, otherInfo, sourceVariableName == null ? "" : " [" + sourceVariableName + "]"); setWrappedToolTipHtmlText(tableModel.getRowDescription(row) + "<br>" + toolTipText); setForeground(cellForegroundColor(row, col)); setBackground(cellBackgroundColor()); return this; } } private final class OffsetSPRenderer extends LocationLabel.AsOffset implements TableCellRenderer { public OffsetSPRenderer(Inspection inspection) { super(inspection); setToolTipPrefix("Slot memory address"); setOpaque(true); } // TODO (mlvdv) check address computations public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { setValue(tableModel.getSPOffset(row, viewPreferences.biasSlotOffsets()), tableModel.getOrigin()); setToolTipPrefix("Stack frame slot \"" + tableModel.getSlotName(row) + "\" SP-relative location<br>Address= "); setForeground(cellForegroundColor(row, col)); setBackground(cellBackgroundColor()); return this; } } private final class OffsetFPRenderer extends LocationLabel.AsOffset implements TableCellRenderer { public OffsetFPRenderer(Inspection inspection) { super(inspection); setToolTipPrefix("Slot memory address"); setOpaque(true); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { setValue(tableModel.getFPOffset(row, viewPreferences.biasSlotOffsets()), tableModel.getAddress(0)); setToolTipPrefix("Stack frame slot \"" + tableModel.getSlotName(row) + "\" FP-relative location<br>Address= "); setForeground(cellForegroundColor(row, col)); setBackground(cellBackgroundColor()); return this; } } private final class ValueRenderer extends DefaultTableCellRenderer implements Prober{ private final Inspection inspection; // WordValueLabels have important user interaction state, so create one per memory location and keep them around, // even though they may not always appear in the same row. private final Map<Long, WordValueLabel> addressToLabelMap = new HashMap<Long, WordValueLabel>(); public ValueRenderer(Inspection inspection) { this.inspection = inspection; } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { final Address address = tableModel.getAddress(row); WordValueLabel label = addressToLabelMap.get(address.toLong()); if (label == null) { label = new WordValueLabel(inspection, ValueMode.WORD, CompiledStackFrameTable.this) { @Override public Value fetchValue() { return vm().memoryIO().readWordValue(address); } }; label.setOpaque(true); label.setToolTipPrefix(tableModel.getRowDescription(row) + " value = "); addressToLabelMap.put(address.toLong(), label); } label.setBackground(cellBackgroundColor()); return label; } public void redisplay() { for (WordValueLabel label : addressToLabelMap.values()) { if (label != null) { label.redisplay(); } } } public void refresh(boolean force) { for (WordValueLabel label : addressToLabelMap.values()) { if (label != null) { label.refresh(force); } } } } }