/*
JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine
Release Version 2.4
A project from the Physics Dept, The University of Oxford
Copyright (C) 2007-2010 The University of Oxford
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as published by
the Free Software Foundation.
This program 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 for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Details (including contact information) can be found at:
jpc.sourceforge.net
or the developer website
sourceforge.net/projects/jpc/
Conceived and Developed by:
Rhys Newman, Ian Preston, Chris Dennis
End of licence header
*/
package org.jpc.debugger;
import java.util.*;
import java.util.zip.*;
import java.io.*;
import java.text.DecimalFormat;
import java.awt.Color;
import java.awt.event.*;
import javax.swing.*;
import org.jpc.debugger.util.*;
import org.jpc.emulator.PC;
import org.jpc.emulator.processor.fpu64.FpuState64;
import org.jpc.j2se.Option;
import org.jpc.support.*;
import org.jpc.emulator.memory.*;
import org.jpc.emulator.processor.Processor;
import org.jpc.emulator.peripheral.Keyboard;
import org.jpc.emulator.pci.peripheral.VGACard;
import org.jpc.j2se.VirtualClock;
public class JPC extends ApplicationFrame implements ActionListener {
private static JPC instance = null;
private ObjectDatabase objects;
private CodeBlockRecord codeBlocks;
private RunMenu runMenu;
private JDesktopPane desktop;
private DiskSelector floppyDisk, hardDisk, cdrom;
private JMenuItem createPC, scanForImages, loadSnapshot, saveSnapshot;
private JMenuItem fpuFrame, processorFrame, physicalMemoryViewer, linearMemoryViewer, watchpoints, breakpoints, traceFrame, monitor;
private JPC(boolean fullScreen) {
super("JPC Debugger");
if (fullScreen) {
setBoundsToMaximum();
} else {
setBounds(0, 0, 1024, 900);
}
objects = new ObjectDatabase();
desktop = new JDesktopPane();
add("Center", desktop);
JFileChooser chooser = new JFileChooser(System.getProperty("user.dir"));
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
objects.addObject(chooser);
JMenuBar bar = new JMenuBar();
JMenu actions = new JMenu("Actions");
createPC = actions.add("Create New PC");
createPC.setEnabled(false);
createPC.addActionListener(this);
scanForImages = actions.add("Scan Directory for Images");
scanForImages.addActionListener(this);
actions.addSeparator();
loadSnapshot = actions.add("Load Snapshot");
loadSnapshot.addActionListener(this);
saveSnapshot = actions.add("Save Snapshot");
saveSnapshot.addActionListener(this);
actions.addSeparator();
actions.add("Quit").addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
frameCloseRequested();
}
});
JMenu windows = new JMenu("Windows");
monitor = windows.add("PC Monitor");
monitor.addActionListener(this);
processorFrame = windows.add("Processor Frame");
processorFrame.addActionListener(this);
fpuFrame = windows.add("FPU Frame");
fpuFrame.addActionListener(this);
physicalMemoryViewer = windows.add("Physical Memory Viewer");
physicalMemoryViewer.addActionListener(this);
linearMemoryViewer = windows.add("Linear Memory Viewer");
linearMemoryViewer.addActionListener(this);
breakpoints = windows.add("Breakpoints");
breakpoints.addActionListener(this);
watchpoints = windows.add("Watchpoints");
watchpoints.addActionListener(this);
traceFrame = windows.add("Execution Trace Frame");
traceFrame.addActionListener(this);
// frequencies = windows.add("Opcode Frequency Frame");
// frequencies.addActionListener(this);
JMenu tools = new JMenu("Tools");
tools.add("Create Blank Disk (file)").addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
createBlankHardDisk();
}
});
runMenu = new RunMenu();
floppyDisk = new DiskSelector("FD", Color.red);
hardDisk = new DiskSelector("HD", Color.blue);
cdrom = new DiskSelector("CD", Color.blue);
bar.add(actions);
bar.add(windows);
bar.add(runMenu);
bar.add(tools);
bar.add(floppyDisk);
bar.add(hardDisk);
bar.add(cdrom);
bar.add(Box.createHorizontalGlue());
bar.add(new Hz());
codeBlocks = null;
setJMenuBar(bar);
resyncImageSelection(new File(System.getProperty("user.dir")));
}
public int executeStep()
{
return runMenu.executeStep();
}
private void initialLayout()
{
ProcessorFrame pf = new ProcessorFrame();
objects.addObject(pf);
addInternalFrame(desktop, -10, 10, pf);
ExecutionTraceFrame tr = new ExecutionTraceFrame();
addInternalFrame(desktop, -(20 + pf.getWidth()), 10, tr);
}
private void resyncImageSelection(File dir) {
floppyDisk.rescan(dir);
hardDisk.rescan(dir);
cdrom.rescan(dir);
checkBootEnabled();
}
private void checkBootEnabled() {
createPC.setEnabled(floppyDisk.isBootDevice() || hardDisk.isBootDevice() || cdrom.isBootDevice());
}
class DiskSelector extends JMenu implements ActionListener {
String mainTitle;
ButtonGroup group;
List<File> diskImages;
Map<ButtonModel, File> lookup;
JCheckBoxMenuItem bootFrom;
JMenuItem openFile;
public DiskSelector(String mainTitle, Color fg) {
super(mainTitle);
this.mainTitle = mainTitle;
setForeground(fg);
lookup = new HashMap();
diskImages = new Vector();
group = new ButtonGroup();
bootFrom = new JCheckBoxMenuItem("Set as Boot Device");
bootFrom.addActionListener(this);
openFile = new JMenuItem("Select Image File");
openFile.addActionListener(this);
}
public void actionPerformed(ActionEvent evt) {
if (evt.getSource() == openFile) {
JFileChooser chooser = (JFileChooser) objects.getObject(JFileChooser.class);
if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) {
return;
}
rescan(chooser.getSelectedFile());
}
resetTitle();
}
public void setSelectedFile(File f, boolean isBootDevice) {
rescan(f);
bootFrom.setState(isBootDevice);
resetTitle();
}
private void resetTitle() {
String fileName = "";
File f = getSelectedFile();
if (f != null) {
fileName = f.getAbsolutePath();
}
if (isBootDevice()) {
setText(mainTitle + " >" + fileName + "<");
} else {
setText(mainTitle + " " + fileName);
}
checkBootEnabled();
if (bootFrom.getState() && (getSelectedFile() == null)) {
bootFrom.setState(false);
}
}
public File getSelectedFile() {
ButtonModel selectedModel = group.getSelection();
if (selectedModel == null) {
return null;
}
return lookup.get(selectedModel);
}
public boolean isBootDevice() {
return bootFrom.getState() && (getSelectedFile() != null);
}
void rescan(File f) {
File selected = getSelectedFile();
boolean isBoot = isBootDevice();
for (int i = diskImages.size() - 1; i >= 0; i--) {
if (!diskImages.get(i).exists()) {
diskImages.remove(i);
}
}
if (f.isDirectory()) {
File[] files = f.listFiles();
for (int i = 0; i < files.length; i++) {
if (files[i].getName().toLowerCase().endsWith(".img")) {
if (!diskImages.contains(files[i])) {
diskImages.add(files[i]);
}
}
}
} else if (f.exists()) {
boolean found = false;
for (int i = 0; i < diskImages.size(); i++) {
if (diskImages.get(i).getAbsolutePath().equals(f.getAbsolutePath())) {
selected = diskImages.get(i);
found = true;
}
}
if (!found) {
diskImages.add(f);
selected = f;
}
}
removeAll();
lookup.clear();
group = new ButtonGroup();
bootFrom.setState(isBoot);
add(bootFrom);
addSeparator();
for (int i = 0; i < diskImages.size(); i++) {
File ff = diskImages.get(i);
JRadioButtonMenuItem item = new JRadioButtonMenuItem(ff.getAbsolutePath());
item.addActionListener(this);
lookup.put(item.getModel(), ff);
group.add(item);
add(item);
if (ff.equals(selected)) {
group.setSelected(item.getModel(), true);
}
}
addSeparator();
add(openFile);
}
}
public Object get(Class cls) {
return objects.getObject(cls);
}
public ObjectDatabase objects() {
return objects;
}
public JDesktopPane getDesktop() {
return desktop;
}
protected void frameCloseRequested() {
BreakpointsFrame bp = (BreakpointsFrame) objects.getObject(BreakpointsFrame.class);
if ((bp != null) && bp.isEdited()) {
bp.dispose();
}
WatchpointsFrame wp = (WatchpointsFrame) objects.getObject(WatchpointsFrame.class);
if ((wp != null) && wp.isEdited()) {
wp.dispose();
}
System.exit(0);
}
public void bringToFront(JInternalFrame f) {
desktop.moveToFront(f);
desktop.setSelectedFrame(f);
}
public void actionPerformed(ActionEvent evt) {
Object src = evt.getSource();
if (src == scanForImages) {
JFileChooser chooser = (JFileChooser) objects.getObject(JFileChooser.class);
if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) {
return;
}
File dir = chooser.getSelectedFile();
if (!dir.isDirectory()) {
dir = dir.getParentFile();
}
resyncImageSelection(dir);
} else if (src == createPC) {
try {
File floppyImage = floppyDisk.getSelectedFile();
File hardImage = hardDisk.getSelectedFile();
File cdImage = cdrom.getSelectedFile();
DriveSet.BootType bootType;
if (floppyDisk.isBootDevice()) {
if (!floppyImage.exists()) {
alert("Floppy Image: " + floppyImage + " does not exist", "Boot", JOptionPane.ERROR_MESSAGE);
return;
}
bootType = DriveSet.BootType.FLOPPY;
} else if (hardDisk.isBootDevice())
{
if (!hardImage.exists()) {
alert("Hard disk Image: " + hardImage + " does not exist", "Boot", JOptionPane.ERROR_MESSAGE);
return;
}
bootType = DriveSet.BootType.HARD_DRIVE;
} else {
if (!cdImage.exists()) {
alert("CD Image: " + cdImage + " does not exist", "Boot", JOptionPane.ERROR_MESSAGE);
return;
}
bootType = DriveSet.BootType.CDROM;
}
String[] args;
int argc = 0;
if (floppyImage != null) {
argc += 2;
}
if (hardImage != null) {
argc += 2;
}
if (cdImage != null) {
argc += 2;
}
if (argc > 2) {
argc += 2;
}
args = new String[argc];
int pos = 0;
if (floppyImage != null) {
args[pos++] = "-fda";
args[pos++] = floppyImage.getAbsolutePath();
}
if (hardImage != null) {
args[pos++] = "-hda";
args[pos++] = hardImage.getAbsolutePath();
}
if (cdImage != null) {
args[pos++] = "-cdrom";
args[pos++] = cdImage.getAbsolutePath();
}
if (pos <= (argc - 2)) {
args[pos++] = "-boot";
if (bootType == DriveSet.BootType.HARD_DRIVE) {
args[pos++] = "hda";
} else if (bootType == DriveSet.BootType.CDROM) {
args[pos++] = "cdrom";
} else {
args[pos++] = "fda";
}
}
instance.createPC(args);
resyncImageSelection(new File(System.getProperty("user.dir")));
} catch (Exception e) {
alert("Failed to create PC: " + e, "Boot", JOptionPane.ERROR_MESSAGE);
}
} else if (src == loadSnapshot) {
runMenu.stop();
JFileChooser fc = new JFileChooser();
try {
BufferedReader in = new BufferedReader(new FileReader("prefs.txt"));
String path = in.readLine();
in.close();
if (path != null) {
File f = new File(path);
if (f.isDirectory()) {
fc.setCurrentDirectory(f);
}
}
} catch (Exception e) {
}
int returnVal = fc.showDialog(this, "Load JPC Snapshot");
File file = fc.getSelectedFile();
try {
if (file != null) {
BufferedWriter out = new BufferedWriter(new FileWriter("prefs.txt"));
out.write(file.getPath());
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
if (returnVal == 0) {
try {
System.out.println("Loading a snapshot of JPC");
ZipInputStream zin = new ZipInputStream(new FileInputStream(file));
zin.getNextEntry();
((PC) objects.getObject(PC.class)).loadState(zin);
zin.closeEntry();
((PCMonitorFrame) objects.getObject(PCMonitorFrame.class)).resizeDisplay();
zin.getNextEntry();
((PCMonitorFrame) objects.getObject(PCMonitorFrame.class)).loadMonitorState(zin);
zin.closeEntry();
zin.close();
System.out.println("done");
} catch (IOException e) {
e.printStackTrace();
}
}
} else if (src == saveSnapshot) {
runMenu.stop();
JFileChooser fc = new JFileChooser();
try {
BufferedReader in = new BufferedReader(new FileReader("prefs.txt"));
String path = in.readLine();
in.close();
if (path != null) {
File f = new File(path);
if (f.isDirectory()) {
fc.setCurrentDirectory(f);
}
}
} catch (Exception e) {
}
int returnVal = fc.showDialog(this, "Save JPC Snapshot");
File file = fc.getSelectedFile();
try {
if (file != null) {
BufferedWriter out = new BufferedWriter(new FileWriter("prefs.txt"));
out.write(file.getPath());
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
if (returnVal == 0) {
try {
ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(file));
zip.putNextEntry(new ZipEntry("pc"));
((PC) objects.getObject(PC.class)).saveState(zip);
zip.closeEntry();
zip.putNextEntry(new ZipEntry("monitor"));
((PCMonitorFrame) objects.getObject(PCMonitorFrame.class)).saveState(zip);
zip.closeEntry();
zip.finish();
zip.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} else if (src == processorFrame) {
ProcessorFrame pf = (ProcessorFrame) objects.getObject(ProcessorFrame.class);
if (pf != null) {
bringToFront(pf);
} else {
pf = new ProcessorFrame();
objects.addObject(pf);
addInternalFrame(desktop, 10, 10, pf);
}
} else if (src == fpuFrame) {
FPUFrame pf = (FPUFrame) objects.getObject(FPUFrame.class);
if (pf != null) {
bringToFront(pf);
} else {
pf = new FPUFrame();
addInternalFrame(desktop, 10, 10, pf);
}
} else if (src == physicalMemoryViewer) {
MemoryViewer mv = (MemoryViewer) objects.getObject(MemoryViewer.class);
if (mv != null) {
bringToFront(mv);
} else {
mv = new MemoryViewer("Physical Memory");
addInternalFrame(desktop, 360, 50, mv);
}
} else if (src == linearMemoryViewer) {
LinearMemoryViewer lmv = (LinearMemoryViewer) objects.getObject(LinearMemoryViewer.class);
if (lmv != null) {
bringToFront(lmv);
} else {
lmv = new LinearMemoryViewer("Linear Memory");
addInternalFrame(desktop, 360, 50, lmv);
}
} else if (src == breakpoints) {
BreakpointsFrame bp = (BreakpointsFrame) objects.getObject(BreakpointsFrame.class);
if (bp != null) {
bringToFront(bp);
} else {
bp = new BreakpointsFrame();
addInternalFrame(desktop, 10, 560, bp);
}
} else if (src == watchpoints) {
WatchpointsFrame wp = (WatchpointsFrame) objects.getObject(WatchpointsFrame.class);
if (wp != null) {
bringToFront(wp);
} else {
wp = new WatchpointsFrame();
addInternalFrame(desktop, 550, 560, wp);
}
} else if (src == traceFrame) {
ExecutionTraceFrame tr = (ExecutionTraceFrame) objects.getObject(ExecutionTraceFrame.class);
if (tr != null) {
bringToFront(tr);
} else {
tr = new ExecutionTraceFrame();
objects.addObject(tr);
addInternalFrame(desktop, 30, 100, tr);
}
} else if (src == monitor) {
PCMonitorFrame m = (PCMonitorFrame) objects.getObject(PCMonitorFrame.class);
if (m != null) {
bringToFront(m);
} else {
m = new PCMonitorFrame();
addInternalFrame(desktop, 10, 10, m);
}
}
// else if (src == frequencies)
// {
// OpcodeFrequencyFrame f = (OpcodeFrequencyFrame) objects.getObject(OpcodeFrequencyFrame.class);
// if (f != null)
// bringToFront(f);
// else
// {
// f = new OpcodeFrequencyFrame();
// addInternalFrame(desktop, 550, 30, f);
// }
// }
refresh();
}
public void notifyExecutionStarted() {
for (Object obj : objects.entries()) {
if (!(obj instanceof PCListener)) {
continue;
}
try {
((PCListener) obj).executionStarted();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void notifyExecutionStopped() {
for (Object obj : objects.entries()) {
if (!(obj instanceof PCListener)) {
continue;
}
try {
((PCListener) obj).executionStopped();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void notifyPCDisposed() {
for (Object obj : objects.entries()) {
if (!(obj instanceof PCListener)) {
continue;
}
try {
((PCListener) obj).pcDisposed();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void notifyPCCreated() {
for (Object obj : objects.entries()) {
if (!(obj instanceof PCListener)) {
continue;
}
try {
((PCListener) obj).pcCreated();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void refresh() {
for (Object obj : objects.entries()) {
if (!(obj instanceof PCListener)) {
continue;
}
try {
((PCListener) obj).refreshDetails();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public PC loadNewPC(PC pc) {
PC oldPC = (PC) objects.removeObject(PC.class);
if (oldPC != null) {
notifyPCDisposed();
}
JInternalFrame[] frames = desktop.getAllFrames();
for (int i = 0; i < frames.length; i++) {
frames[i].dispose();
}
objects.removeObject(Processor.class);
objects.removeObject(PhysicalAddressSpace.class);
objects.removeObject(LinearAddressSpace.class);
objects.removeObject(VGACard.class);
objects.removeObject(Keyboard.class);
objects.removeObject(ProcessorAccess.class);
objects.removeObject(CodeBlockRecord.class);
for (int i = 0; i < 10; i++) {
System.gc();
try {
Thread.sleep(100);
} catch (Exception e) {
}
}
setTitle("JPC Debugger - Boot Device: " + ((DriveSet) pc.getComponent(DriveSet.class)).getBootDevice());
objects.addObject(pc);
objects.addObject(pc.getProcessor());
objects.addObject(pc.getComponent(LinearAddressSpace.class));
objects.addObject(pc.getComponent(PhysicalAddressSpace.class));
objects.addObject(pc.getComponent(VGACard.class));
objects.addObject(pc.getComponent(Keyboard.class));
codeBlocks = new CodeBlockRecord(pc);
objects.addObject(codeBlocks);
ProcessorAccess pca = ProcessorAccess.create(runMenu.isTimeTravel(), pc.getProcessor());
objects.addObject(ProcessorAccess.class, pca);
FPUAccess fpua = new FPUAccess((FpuState64)pc.getProcessor().fpu);
objects.addObject(fpua);
runMenu.refresh();
notifyPCCreated();
monitor.doClick();
return pc;
}
public PC createPC(String[] args) throws IOException
{
PC pc = new PC(new VirtualClock(), args);
loadNewPC(pc);
String snapShot = ArgProcessor.findVariable(args, "ss", null);
if (snapShot == null) {
return pc;
}
File file = new File(snapShot);
ZipInputStream zin = new ZipInputStream(new FileInputStream(file));
zin.getNextEntry();
pc.loadState(zin);
zin.closeEntry();
((PCMonitorFrame) objects.getObject(PCMonitorFrame.class)).resizeDisplay();
zin.getNextEntry();
((PCMonitorFrame) objects.getObject(PCMonitorFrame.class)).loadMonitorState(zin);
zin.closeEntry();
zin.close();
return pc;
}
class Hz extends JLabel implements ActionListener {
DecimalFormat fmt;
long lastCount, lastTime;
Hz() {
super("MHz = 0");
fmt = new DecimalFormat("#.##");
lastTime = System.currentTimeMillis();
javax.swing.Timer timer = new javax.swing.Timer(1000, this);
timer.setRepeats(true);
timer.start();
}
public void actionPerformed(ActionEvent evt) {
if (codeBlocks == null) {
return;
}
long ticks = ((PC)objects.getObject(PC.class)).getState()[16];
long count = codeBlocks.getInstructionCount();
long decoded = codeBlocks.getDecodedCount();
long executed = codeBlocks.getExecutedBlockCount();
long now = System.currentTimeMillis();
double mhz = 1000.0 * (count - lastCount) / (now - lastTime) / 1000000;
setText("Decoded: (" + decoded + " x86 Instr) | Executed: (" + commaSeparate(count) + " x86 Instr) (" + executed + " Blocks) ("+commaSeparate(ticks)+" ticks) | " + fmt.format(mhz) + " MHz");
lastCount = count;
lastTime = now;
}
public String commaSeparate(long n) {
String s = Long.toString(n);
if (s.length() < 4) {
return s;
}
StringBuffer buf = new StringBuffer();
int offset = (s.length() % 3);
if (offset == 0) {
buf.append(s.substring(0, 3));
offset = 3;
for (int i = 0; i < (int) ((s.length() - 1) / 3); i++) {
buf.append(",");
buf.append(s.substring(offset + 3 * i, offset + 3 * i + 3));
}
} else {
buf.append(s.substring(0, offset));
for (int i = 0; i < (int) ((s.length() - 1) / 3); i++) {
buf.append(",");
buf.append(s.substring(offset + 3 * i, offset + 3 * i + 3));
}
}
return buf.toString();
}
}
public void createBlankHardDisk() {
try {
JFileChooser chooser = (JFileChooser) objects.getObject(JFileChooser.class);
if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) {
return;
}
String sizeString = JOptionPane.showInputDialog(this, "Enter the size in MB for the disk", "Disk Image Creation", JOptionPane.QUESTION_MESSAGE);
if (sizeString == null) {
return;
}
long size = Long.parseLong(sizeString) * 1024l * 1024l;
if (size < 0) {
throw new Exception("Negative file size");
}
RandomAccessFile f = new RandomAccessFile(chooser.getSelectedFile(), "rw");
f.setLength(size);
f.close();
} catch (Exception e) {
alert("Failed to create blank disk " + e, "Create Disk", JOptionPane.ERROR_MESSAGE);
}
}
public static Object getObject(Class cls) {
return instance.get(cls);
}
public static Object getPC()
{
return instance.getObject(PC.class);
}
public static JPC getInstance() {
return instance;
}
public static void main(String[] args) throws IOException {
List<String> tmp = new ArrayList<String>();
tmp.add("-debug-blocks");
tmp.add("-fullscreen");
tmp.addAll(Arrays.asList(args));
args = Option.parse(tmp.toArray(new String[tmp.size()]));
initialise();
boolean fullScreen = Option.fullscreen.isSet();
instance = new JPC(fullScreen);
instance.validate();
instance.setVisible(true);
if ((args.length > 0) || Option.config.isSet() || Option.boot.isSet()) {
instance.createPC(args);
}
instance.initialLayout();
}
}