blob: a7ad6c53a3f2b8e088231a14c9a59d71262a346d [file] [log] [blame]
/*
* Copyright (c) 2000, 2008, 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 sun.jvm.hotspot.ui;
import java.math.*;
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
import sun.jvm.hotspot.debugger.*;
import sun.jvm.hotspot.debugger.dummy.*;
import sun.jvm.hotspot.utilities.*;
/** A subclass of JPanel which displays a hex dump of memory along
with annotations describing the significance of various
pieces. This can be used to implement either a stack or heap
inspector. */
public class AnnotatedMemoryPanel extends JPanel {
private boolean is64Bit;
private Debugger debugger;
private long addressSize;
private HighPrecisionJScrollBar scrollBar;
private Font font;
private int bytesPerLine;
private int paintCount;
private String unmappedAddrString;
// Type of this is an IntervalTree indexed by Interval<Address> and
// with user data of type Annotation
private IntervalTree annotations =
new IntervalTree(new Comparator() {
public int compare(Object o1, Object o2) {
Address a1 = (Address) o1;
Address a2 = (Address) o2;
if ((a1 == null) && (a2 == null)) {
return 0;
} else if (a1 == null) {
return -1;
} else if (a2 == null) {
return 1;
}
if (a1.equals(a2)) {
return 0;
} else if (a1.lessThan(a2)) {
return -1;
}
return 1;
}
});
// Keep track of the last start address at which we painted, so we
// can scroll annotations
private Address lastStartAddr;
// This contains the list of currently-visible IntervalNodes, in
// sorted order by their low endpoint, in the form of a
// List<Annotation>. These annotations have already been laid out.
private java.util.List visibleAnnotations;
// Darker colors than defaults for better readability
private static Color[] colors = {
new Color(0.0f, 0.0f, 0.6f), // blue
new Color(0.6f, 0.0f, 0.6f), // magenta
new Color(0.0f, 0.8f, 0.0f), // green
new Color(0.8f, 0.3f, 0.0f), // orange
new Color(0.0f, 0.6f, 0.8f), // cyan
new Color(0.2f, 0.2f, 0.2f), // dark gray
};
/** Default is 32-bit mode */
public AnnotatedMemoryPanel(Debugger debugger) {
this(debugger, false);
}
public AnnotatedMemoryPanel(Debugger debugger, boolean is64Bit, Address addrValue, Address addrLow, Address addrHigh) {
super();
init(debugger, is64Bit, addressToBigInt(addrValue), addressToBigInt(addrLow), addressToBigInt(addrHigh));
}
public AnnotatedMemoryPanel(Debugger debugger, boolean is64Bit ) {
super();
init(debugger, is64Bit, defaultMemoryLocation(is64Bit), defaultMemoryLow(is64Bit), defaultMemoryHigh(is64Bit));
}
static class AnnoX {
int lineX;
Address highBound;
public AnnoX(int lineX, Address highBound) {
this.lineX = lineX;
this.highBound = highBound;
}
}
public synchronized void paintComponent(Graphics g) {
// System.err.println("AnnotatedMemoryPanel.paintComponent() " + ++paintCount);
super.paintComponent(g);
// Clone the Graphics so we don't screw up its state for Swing
// drawing (as this code otherwise does)
g = g.create();
g.setFont(font);
g.setColor(Color.black);
Rectangle rect = new Rectangle();
getBounds(rect);
String firstAddressString = null;
int lineHeight;
int addrWidth;
{
Rectangle2D bounds = GraphicsUtilities.getStringBounds(unmappedAddrString, g);
lineHeight = (int) bounds.getHeight();
addrWidth = (int) bounds.getWidth();
}
int addrX = (int) (0.25 * addrWidth);
int dataX = (int) (addrX + (1.5 * addrWidth));
int lineStartX = dataX + addrWidth + 5;
int annoStartX = (int) (lineStartX + (0.75 * addrWidth));
int numLines = rect.height / lineHeight;
BigInteger startVal = scrollBar.getValueHP();
BigInteger perLine = new BigInteger(Integer.toString((int) addressSize));
// lineCount and maxLines are both 1 less than expected
BigInteger lineCount = new BigInteger(Integer.toString((int) (numLines - 1)));
BigInteger maxLines = scrollBar.getMaximumHP().subtract(scrollBar.getMinimumHP()).divide(perLine);
if (lineCount.compareTo(maxLines) > 0) {
lineCount = maxLines;
}
BigInteger offsetVal = lineCount.multiply(perLine);
BigInteger endVal = startVal.add(offsetVal);
if (endVal.compareTo(scrollBar.getMaximumHP()) > 0) {
startVal = scrollBar.getMaximumHP().subtract(offsetVal);
endVal = scrollBar.getMaximumHP();
// Sure seems like this call will cause us to immediately redraw...
scrollBar.setValueHP(startVal);
}
scrollBar.setVisibleAmountHP(offsetVal.add(perLine));
scrollBar.setBlockIncrementHP(offsetVal);
Address startAddr = bigIntToAddress(startVal);
Address endAddr = bigIntToAddress(endVal);
// Scroll last-known annotations
int scrollOffset = 0;
if (lastStartAddr != null) {
scrollOffset = (int) lastStartAddr.minus(startAddr);
} else {
if (startAddr != null) {
scrollOffset = (int) (-1 * startAddr.minus(lastStartAddr));
}
}
scrollOffset = scrollOffset * lineHeight / (int) addressSize;
scrollAnnotations(scrollOffset);
lastStartAddr = startAddr;
int curY = lineHeight;
Address curAddr = startAddr;
for (int i = 0; i < numLines; i++) {
String s = bigIntToHexString(startVal);
g.drawString(s, addrX, curY);
try {
s = addressToString(startAddr.getAddressAt(i * addressSize));
}
catch (UnmappedAddressException e) {
s = unmappedAddrString;
}
g.drawString(s, dataX, curY);
curY += lineHeight;
startVal = startVal.add(perLine);
}
// Query for visible annotations (little slop to ensure we get the
// top and bottom)
// FIXME: it would be nice to have a more static layout; that is,
// if something scrolls off the bottom of the screen, other
// annotations still visible shouldn't change position
java.util.List va =
annotations.findAllNodesIntersecting(new Interval(startAddr.addOffsetTo(-addressSize),
endAddr.addOffsetTo(2 * addressSize)));
// Render them
int curLineX = lineStartX;
int curTextX = annoStartX;
int curColorIdx = 0;
if (g instanceof Graphics2D) {
Stroke stroke = new BasicStroke(3.0f);
((Graphics2D) g).setStroke(stroke);
}
Stack drawStack = new Stack();
layoutAnnotations(va, g, curTextX, startAddr, lineHeight);
for (Iterator iter = visibleAnnotations.iterator(); iter.hasNext(); ) {
Annotation anno = (Annotation) iter.next();
Interval interval = anno.getInterval();
if (!drawStack.empty()) {
// See whether we can pop any items off the stack
boolean shouldContinue = true;
do {
AnnoX annoX = (AnnoX) drawStack.peek();
if (annoX.highBound.lessThanOrEqual((Address) interval.getLowEndpoint())) {
curLineX = annoX.lineX;
drawStack.pop();
shouldContinue = !drawStack.empty();
} else {
shouldContinue = false;
}
} while (shouldContinue);
}
// Draw a line covering the interval
Address lineStartAddr = (Address) interval.getLowEndpoint();
// Give it a little slop
int lineStartY = (int) (lineStartAddr.minus(startAddr) * lineHeight / addressSize) +
(lineHeight / 3);
Address lineEndAddr = (Address) interval.getHighEndpoint();
drawStack.push(new AnnoX(curLineX, lineEndAddr));
int lineEndY = (int) (lineEndAddr.minus(startAddr) * lineHeight / addressSize);
g.setColor(anno.getColor());
g.drawLine(curLineX, lineStartY, curLineX, lineEndY);
// Draw line to text
g.drawLine(curLineX, lineStartY, curTextX - 10, anno.getY() - (lineHeight / 2));
curLineX += 8;
anno.draw(g);
++curColorIdx;
}
}
/** Add an annotation covering the address range [annotation.lowAddress,
annotation.highAddress); that is, it includes the low address and does not
include the high address. */
public synchronized void addAnnotation(Annotation annotation) {
annotations.insert(annotation.getInterval(), annotation);
}
/** Makes the given address visible somewhere in the window */
public synchronized void makeVisible(Address addr) {
BigInteger bi = addressToBigInt(addr);
scrollBar.setValueHP(bi);
}
public void print() {
printOn(System.out);
}
public void printOn(PrintStream tty) {
annotations.printOn(tty);
}
//----------------------------------------------------------------------
// Internals only below this point
//
private void init(Debugger debugger, boolean is64Bit, BigInteger addrValue, BigInteger addrLow, BigInteger addrHigh) {
this.is64Bit = is64Bit;
this.debugger = debugger;
if (is64Bit) {
addressSize = 8;
unmappedAddrString = "??????????????????";
} else {
addressSize = 4;
unmappedAddrString = "??????????";
}
setLayout(new BorderLayout());
setupScrollBar(addrValue, addrLow, addrHigh);
add(scrollBar, BorderLayout.EAST);
visibleAnnotations = new ArrayList();
setBackground(Color.white);
addHierarchyBoundsListener(new HierarchyBoundsListener() {
public void ancestorMoved(HierarchyEvent e) {
}
public void ancestorResized(HierarchyEvent e) {
// FIXME: should perform incremental layout
// System.err.println("Ancestor resized");
}
});
if (font == null) {
font = GraphicsUtilities.lookupFont("Courier");
}
if (font == null) {
throw new RuntimeException("Error looking up monospace font Courier");
}
getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), "PageDown");
getActionMap().put("PageDown", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
scrollBar.setValueHP(scrollBar.getValueHP().add(scrollBar.getBlockIncrementHP()));
}
});
getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), "PageUp");
getActionMap().put("PageUp", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
scrollBar.setValueHP(scrollBar.getValueHP().subtract(scrollBar.getBlockIncrementHP()));
}
});
getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "Down");
getActionMap().put("Down", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
scrollBar.setValueHP(scrollBar.getValueHP().add(scrollBar.getUnitIncrementHP()));
}
});
getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "Up");
getActionMap().put("Up", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
scrollBar.setValueHP(scrollBar.getValueHP().subtract(scrollBar.getUnitIncrementHP()));
}
});
setEnabled(true);
}
private void setupScrollBar(BigInteger value, BigInteger min, BigInteger max) {
scrollBar = new HighPrecisionJScrollBar( Scrollbar.VERTICAL, value, min, max);
if (is64Bit) {
bytesPerLine = 8;
// 64-bit mode
scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x08}));
scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x40}));
} else {
// 32-bit mode
bytesPerLine = 4;
scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x04}));
scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x20}));
}
scrollBar.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
HighPrecisionJScrollBar h = (HighPrecisionJScrollBar) e.getSource();
repaint();
}
});
}
private static BigInteger defaultMemoryLocation(boolean is64Bit) {
if (is64Bit) {
return new BigInteger(1, new byte[] {
(byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
} else {
return new BigInteger(1, new byte[] { (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00});
}
}
private static BigInteger defaultMemoryLow(boolean is64Bit) {
if (is64Bit) {
return new BigInteger(1, new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
} else {
return new BigInteger(1, new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
}
}
private static BigInteger defaultMemoryHigh(boolean is64Bit) {
if (is64Bit) {
return new BigInteger(1, new byte[] {
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC});
} else {
return new BigInteger(1, new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC});
}
}
private void setupScrollBar() {
setupScrollBar(defaultMemoryLocation(is64Bit),
defaultMemoryLow(is64Bit),
defaultMemoryHigh(is64Bit));
}
private String bigIntToHexString(BigInteger bi) {
StringBuffer buf = new StringBuffer();
buf.append("0x");
String val = bi.toString(16);
for (int i = 0; i < ((2 * addressSize) - val.length()); i++) {
buf.append('0');
}
buf.append(val);
return buf.toString();
}
private Address bigIntToAddress(BigInteger i) {
String s = bigIntToHexString(i);
return debugger.parseAddress(s);
}
private BigInteger addressToBigInt(Address a) {
String s = addressToString(a);
if (!s.startsWith("0x")) {
throw new NumberFormatException(s);
}
return new BigInteger(s.substring(2), 16);
}
private String addressToString(Address a) {
if (a == null) {
if (is64Bit) {
return "0x0000000000000000";
} else {
return "0x00000000";
}
}
return a.toString();
}
/** Scrolls the visible annotations by the given Y amount */
private void scrollAnnotations(int y) {
for (Iterator iter = visibleAnnotations.iterator(); iter.hasNext(); ) {
Annotation anno = (Annotation) iter.next();
anno.setY(anno.getY() + y);
}
}
/** Takes the list of currently-visible annotations (in the form of
a List<IntervalNode>) and lays them out given the current
visible position and the already-visible annotations. Does not
perturb the layouts of the currently-visible annotations. */
private void layoutAnnotations(java.util.List va,
Graphics g,
int x,
Address startAddr,
int lineHeight) {
// Handle degenerate case early: no visible annotations.
if (va.size() == 0) {
visibleAnnotations.clear();
return;
}
// We have two ranges of visible annotations: the one from the
// last repaint and the currently visible set. We want to preserve
// the layouts of the previously-visible annotations that are
// currently visible (to avoid color flashing, jumping, etc.)
// while making the coloring of the new annotations fit as well as
// possible. Note that annotations may appear and disappear from
// any point in the previously-visible list, but the ordering of
// the visible annotations is always the same.
// This is really a constrained graph-coloring problem. This
// simple algorithm only takes into account half of the
// constraints (for example, the layout of the previous
// annotation, where it should be taking into account the layout
// of the previous and next annotations that were in the
// previously-visible list). There are situations where it can
// generate overlapping annotations and adjacent annotations with
// the same color; generally visible when scrolling line-by-line
// rather than page-by-page. In some of these situations, will
// have to move previously laid-out annotations. FIXME: revisit
// this.
// Index of last annotation which we didn't know how to lay out
int deferredIndex = -1;
// We "lay out after" this one
Annotation constraintAnnotation = null;
// The first constraint annotation
Annotation firstConstraintAnnotation = null;
// The index from which we search forward in the
// visibleAnnotations list. This reduces the amount of work we do.
int searchIndex = 0;
// The new set of annotations
java.util.List newAnnos = new ArrayList();
for (Iterator iter = va.iterator(); iter.hasNext(); ) {
Annotation anno = (Annotation) ((IntervalNode) iter.next()).getData();
// Search forward for this one
boolean found = false;
for (int i = searchIndex; i < visibleAnnotations.size(); i++) {
Annotation el = (Annotation) visibleAnnotations.get(i);
// See whether we can abort the search unsuccessfully because
// we went forward too far
if (el.getLowAddress().greaterThan(anno.getLowAddress())) {
break;
}
if (el == anno) {
// Found successfully.
found = true;
searchIndex = i;
constraintAnnotation = anno;
if (firstConstraintAnnotation == null) {
firstConstraintAnnotation = constraintAnnotation;
}
break;
}
}
if (!found) {
if (constraintAnnotation != null) {
layoutAfter(anno, constraintAnnotation, g, x, startAddr, lineHeight);
constraintAnnotation = anno;
} else {
// Defer layout of this annotation until later
++deferredIndex;
}
}
newAnnos.add(anno);
}
if (firstConstraintAnnotation != null) {
// Go back and lay out deferred annotations
for (int i = deferredIndex; i >= 0; i--) {
Annotation anno = (Annotation) newAnnos.get(i);
layoutBefore(anno, firstConstraintAnnotation, g, x, startAddr, lineHeight);
firstConstraintAnnotation = anno;
}
} else {
// Didn't find any overlap between the old and new annotations.
// Lay out in a feed-forward fashion.
if (Assert.ASSERTS_ENABLED) {
Assert.that(constraintAnnotation == null, "logic error in layout code");
}
for (Iterator iter = newAnnos.iterator(); iter.hasNext(); ) {
Annotation anno = (Annotation) iter.next();
layoutAfter(anno, constraintAnnotation, g, x, startAddr, lineHeight);
constraintAnnotation = anno;
}
}
visibleAnnotations = newAnnos;
}
/** Lays out the given annotation before the optional constraint
annotation, obeying constraints imposed by that annotation if it
is specified. */
private void layoutBefore(Annotation anno, Annotation constraintAnno,
Graphics g, int x,
Address startAddr, int lineHeight) {
anno.computeWidthAndHeight(g);
// Color
if (constraintAnno != null) {
anno.setColor(prevColor(constraintAnno.getColor()));
} else {
anno.setColor(colors[0]);
}
// X
anno.setX(x);
// Tentative Y
anno.setY((int) (((Address) anno.getInterval().getLowEndpoint()).minus(startAddr) * lineHeight / addressSize) +
(5 * lineHeight / 6));
// See whether Y overlaps with last anno's Y; if so, move this one up
if ((constraintAnno != null) && (anno.getY() + anno.getHeight() > constraintAnno.getY())) {
anno.setY(constraintAnno.getY() - anno.getHeight());
}
}
/** Lays out the given annotation after the optional constraint
annotation, obeying constraints imposed by that annotation if it
is specified. */
private void layoutAfter(Annotation anno, Annotation constraintAnno,
Graphics g, int x,
Address startAddr, int lineHeight) {
anno.computeWidthAndHeight(g);
// Color
if (constraintAnno != null) {
anno.setColor(nextColor(constraintAnno.getColor()));
} else {
anno.setColor(colors[0]);
}
// X
anno.setX(x);
// Tentative Y
anno.setY((int) (((Address) anno.getInterval().getLowEndpoint()).minus(startAddr) * lineHeight / addressSize) +
(5 * lineHeight / 6));
// See whether Y overlaps with last anno's Y; if so, move this one down
if ((constraintAnno != null) && (anno.getY() < (constraintAnno.getY() + constraintAnno.getHeight()))) {
anno.setY(constraintAnno.getY() + constraintAnno.getHeight());
}
}
/** Returns previous color in our color palette */
private Color prevColor(Color c) {
int i = findColorIndex(c);
if (i == 0) {
return colors[colors.length - 1];
} else {
return colors[i - 1];
}
}
/** Returns next color in our color palette */
private Color nextColor(Color c) {
return colors[(findColorIndex(c) + 1) % colors.length];
}
private int findColorIndex(Color c) {
for (int i = 0; i < colors.length; i++) {
if (colors[i] == c) {
return i;
}
}
throw new IllegalArgumentException();
}
public static void main(String[] args) {
JFrame frame = new JFrame();
DummyDebugger debugger = new DummyDebugger(new MachineDescriptionIntelX86());
AnnotatedMemoryPanel anno = new AnnotatedMemoryPanel(debugger);
frame.getContentPane().add(anno);
anno.addAnnotation(new Annotation(debugger.parseAddress("0x80000000"),
debugger.parseAddress("0x80000040"),
"Stack Frame for \"foo\""));
anno.addAnnotation(new Annotation(debugger.parseAddress("0x80000010"),
debugger.parseAddress("0x80000020"),
"Locals for \"foo\""));
anno.addAnnotation(new Annotation(debugger.parseAddress("0x80000020"),
debugger.parseAddress("0x80000030"),
"Expression stack for \"foo\""));
frame.setSize(400, 300);
frame.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
System.exit(0);
}
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.setVisible(true);
}
}