package app; // Java imports import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; // Project imports import util.*; import vue.*; // Disassembler UI class DisassemblerPane extends JScrollPane { // Instance fields private int address; // Address of top row of output private CPUWindow parent; // Containing CPU window private boolean showBytes; // Display the bytes column private boolean shown; // Component has been shown private int[] widths; // Column widths // UI components private UPanel client; // Client area private ArrayList rows; // Disassembler output /////////////////////////////////////////////////////////////////////////// // Classes // /////////////////////////////////////////////////////////////////////////// // One row of disassembler output private class Row extends Disassembler.Row { Instruction inst; // Decoded instruction JLabel[] labels; // Display text } /////////////////////////////////////////////////////////////////////////// // Constructors // /////////////////////////////////////////////////////////////////////////// // Default constructor DisassemblerPane(CPUWindow parent) { super(VERTICAL_SCROLLBAR_NEVER, HORIZONTAL_SCROLLBAR_AS_NEEDED); // Configure instance fields this.parent = parent; rows = new ArrayList(); showBytes = true; widths = new int[5]; // Configure client area client = new UPanel(); client.setBackground(SystemColor.window); client.setFocusable(true); client.addFocusListener( Util.onFocus(e->client.repaint(), e->client.repaint())); client.addKeyListener(Util.onKey(e->onKeyDown(e), null)); client.addMouseListener(Util.onMouse(e->client.requestFocus(), null)); client.addMouseWheelListener(e->onMouseWheel(e)); client.addPaintListener((g,w,h)->onPaint(g, w, h)); // Configure component setViewportView(client); } /////////////////////////////////////////////////////////////////////////// // Package Methods // /////////////////////////////////////////////////////////////////////////// // Update the display void refresh(boolean seekToPC) { if (seekToPC) { int pc = parent.parent.vue.getRegister(Vue.PC, true); if (!isVisible(pc)) seek(pc, tall(false) / 3); } client.repaint(); } /////////////////////////////////////////////////////////////////////////// // Event Handlers // /////////////////////////////////////////////////////////////////////////// // Key down private void onKeyDown(KeyEvent e) { int code = e.getKeyCode(); int count = tall(false); int mods = e.getModifiersEx(); var vue = parent.parent.vue; boolean alt = (mods & InputEvent.ALT_DOWN_MASK ) != 0; boolean ctrl = (mods & InputEvent.CTRL_DOWN_MASK) != 0; // No Alt combinations if (alt) return; // Goto if (ctrl && code == KeyEvent.VK_G) { String text = JOptionPane.showInputDialog( this, "Goto:", "Goto", JOptionPane.PLAIN_MESSAGE); Object eval = vue.evaluate(text); int addr = 0; if (eval instanceof Integer) addr = (Integer) eval; else if (eval instanceof Long) addr = (int) (long) (Long) eval; else return; if (!isVisible(addr)) seek(addr, count / 3); return; } // Processing by key code int pc = vue.getRegister(Vue.PC, true); var step = parent.parent.brkStep; switch (code) { case KeyEvent.VK_UP : seek(address, 1); break; case KeyEvent.VK_DOWN : seek(address, -1); break; case KeyEvent.VK_PAGE_UP : seek(address, count); break; case KeyEvent.VK_PAGE_DOWN: seek(address, -count); break; // Single Step case KeyEvent.VK_F11: step.setCondition("!fetch&&pc!=" + pc); step.setEnabled(true); vue.emulate(20000000); if (step.evaluate()) step.setEnabled(false); parent.parent.refreshDebug(true); break; // Run to Next case KeyEvent.VK_F12: step.setCondition("!fetch&&pc==" + (pc + Instruction.size(vue.read(pc, Vue.U16) >> 10))); step.setEnabled(true); vue.emulate(20000000); if (step.evaluate()) step.setEnabled(false); parent.parent.refreshDebug(true); break; } } // Mouse wheel private void onMouseWheel(MouseWheelEvent e) { seek(address, -e.getUnitsToScroll()); } // Client paint private void onPaint(Graphics2D g, int width, int height) { var vue = parent.parent.vue; int pc = vue.getRegister(Vue.PC, true); // The view is being shown for the first time if (!shown) { shown = true; seek(pc, tall(false) / 3); return; } // Configure working variables int address = this.address; int count = tall(true); var data = new byte[count * 4]; int lineHeight = parent.parent.app.fntMono.metrics.getHeight(); int offset = 0; // Disassemble from the current address vue.readBytes(address, data, 0, data.length); widths[3] = widths[4] = 0; for (int y = 0; y < count; y++) { var row = y < rows.size() ? rows.get(y) : addRow(); var color = SystemColor.windowText; // Disassemble the instruction row.inst.decode(data, offset); Disassembler.disassemble(address, row.inst, row); updateRow(row); // Highlight PC if (address == pc) { Color bg = SystemColor.control; if (client.isFocusOwner()) { bg = SystemColor.textHighlight; color = SystemColor.textHighlightText; } g.setColor(bg); g.fillRect( 0, y * lineHeight, width, lineHeight ); } // Configure label text color for (var label : row.labels) label.setForeground(color); // Advance to the next instruction int size = address + 2 == pc ? 2 : row.inst.size; address += size; offset += size; } // Configure all rows for (int y = 0; y < rows.size(); y++) { var row = rows.get(y); int top = y * lineHeight; // Configure all labels for (int z = 0, x = 0; z < 5; z++) { var label = row.labels[z]; // The label is not part of the output if (y >= count || widths[z] == 0) { label.setVisible(false); continue; } // Configure the label label.setLocation(x, top); label.setSize(label.getPreferredSize()); x += widths[z] + lineHeight; } } // Update the client's size var size = client.getPreferredSize(); width = -lineHeight + 1; for (int x = 0; x < 5; x++) if (widths[x] != 0 && (x != 2 || showBytes)) width += lineHeight + widths[x]; if (width == size.width) return; client.setPreferredSize(new Dimension(width, 0)); revalidate(); } /////////////////////////////////////////////////////////////////////////// // Private Methods // /////////////////////////////////////////////////////////////////////////// // Create a new row object private Row addRow() { var row = new Row(); row.inst = new Instruction(); row.labels = new JLabel[5]; // Initialize columns for (int x = 0; x < 5; x++) { var label = row.labels[x] = new JLabel(); if (x != 4) label.setFont(parent.parent.app.fntMono); client.add(label); } rows.add(row); return row; } // Determine whether the instruction at an address is in the client view private boolean isVisible(int target) { int count = Math.max(1, tall(false)); var data = new byte[count * 4]; int offset = 0; var vue = parent.parent.vue; int pc = vue.getRegister(Vue.PC, true); target &= 0xFFFFFFFE; // Load enough bytes to represent every fully visible instruction vue.readBytes(address, data, 0, data.length); // Iterate through instructions for (int x = 0, address = this.address; x < count; x++) { // Determine the instruction's size int size = address + 2 == pc ? 2 : Instruction.size(data[offset + 1] >> 2 & 0x3F); // The current instruction is the target if (address == target || size == 4 && address + 2 == target) return true; // Advance to the next instruction address += size; offset += size; } // The instruction is not visible in the view return false; } // Determine the address of the top row of output private void seek(int target, int row) { var data = new byte[(Math.abs(row) + 9) * 4]; int offset = 0; var vue = parent.parent.vue; int pc = vue.getRegister(Vue.PC, true); target &= 0xFFFFFFFE; // Scrolling down if (row <= 0) { // Load bytes starting up to 8 instructions prior to the target address = target - 32; vue.readBytes(address, data, 0, data.length); // Iterate through instructions for (boolean found = false;;) { // Determine the instruction's size int size = address + 2 == pc ? 2 : Instruction.size(data[offset + 1] >> 2 & 0x3F); // The current instruction is the target if (address == target || size == 4 && address + 2 == target) found = true; if (found && row++ == 0) break; // Advance to the next instruction address += size; offset += size; } } // Scrolling up else { // Load bytes starting up to 8 instructions prior to the top row address = target - data.length + 4; vue.readBytes(address, data, 0, data.length); // Iterate through instructions var addresses = new int[row]; // Circular buffer for (int index = 0;;) { // Determine the instruction's size int size = pc == address + 2 ? 2 : Instruction.size(data[offset + 1] >> 2 & 63); // The current instruction is the target if (address == target || size == 4 && address + 2 == target) { address = addresses[index]; break; } // Advance to the next instruction addresses[index] = address; if (++index == row) index = 0; address += size; offset += size; } } // Common processing client.repaint(); } // Determine how many rows of output are visible private int tall(boolean partial) { int lineHeight = parent.parent.app.fntMono.metrics.getHeight(); return Math.max(1, (client.getHeight() + (partial ? lineHeight - 1 : 0) ) / lineHeight); } // Update a row with its text and measure the column widths private void updateRow(Row row) { for (int x = 0; x < 5; x++) { var label = row.labels[x]; String text = null; switch (x) { case 0: text = row.address ; break; case 1: text = row.bytes ; break; case 2: text = row.mnemonic; break; case 3: text = row.operands; break; } if (text != null) { label.setText(text); label.setVisible(x == 1 ? showBytes : true); widths[x] = Math.max(widths[x],label.getPreferredSize().width); } else label.setVisible(false); } } }