/**
* Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.codesourcery.jasm16.ide.ui.views;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import de.codesourcery.jasm16.Address;
import de.codesourcery.jasm16.Size;
import de.codesourcery.jasm16.WordAddress;
import de.codesourcery.jasm16.disassembler.DisassembledLine;
import de.codesourcery.jasm16.disassembler.Disassembler;
import de.codesourcery.jasm16.emulator.Breakpoint;
import de.codesourcery.jasm16.emulator.EmulationListener;
import de.codesourcery.jasm16.emulator.Emulator;
import de.codesourcery.jasm16.emulator.IEmulationListener;
import de.codesourcery.jasm16.emulator.IEmulator;
import de.codesourcery.jasm16.ide.ui.utils.PagingKeyAdapter;
import de.codesourcery.jasm16.ide.ui.utils.UIUtils;
import de.codesourcery.jasm16.ide.ui.viewcontainers.DebuggingPerspective;
import de.codesourcery.jasm16.utils.Misc;
public class DisassemblerView extends AbstractView
{
public static final String VIEW_ID = "dissassembly-view";
private JPanel panel;
private final JTextArea textArea = new JTextArea();
private EmulatorControllerView emulatorController;
private final DebuggingPerspective perspective;
private IEmulator emulator;
private boolean showHexDump = true;
private volatile Address addressAtTopOfScreen = null;
private volatile Address addressAtBottomOfScreen = null;
private final Disassembler disassembler = new Disassembler();
private final IEmulationListener listener = new EmulationListener() {
public void breakpointAdded(IEmulator emulator, Breakpoint breakpoint) {
refreshDisplay();
};
public void breakpointChanged(IEmulator emulator, Breakpoint breakpoint) {
refreshDisplay();
};
public void onBreakpoint(IEmulator emulator, Breakpoint breakpoint) {
System.out.println("Breakpoint reached: "+breakpoint);
};
public void breakpointDeleted(IEmulator emulator, Breakpoint breakpoint) {
refreshDisplay();
};
@Override
public void afterMemoryLoad(IEmulator emulator, Address startAddress, int lengthInBytes)
{
if ( ! isFullSpeedMode() ) {
refreshDisplay();
}
}
@Override
public void afterCommandExecution(IEmulator emulator, int commandDuration)
{
if ( ! isFullSpeedMode() ) {
refreshDisplay();
}
}
@Override
public void afterReset(IEmulator emulator)
{
refreshDisplay();
}
@Override
public void onStopHook(IEmulator emulator, Address previousPC, Throwable emulationError) {
refreshDisplay();
}
};
public DisassemblerView(DebuggingPerspective perspective, IEmulator emulator) {
if ( perspective == null ) {
throw new IllegalArgumentException("perspective must not be null");
}
this.perspective = perspective;
setEmulator( emulator );
}
@Override
public void refreshDisplay()
{
if ( emulator == null ) {
return;
}
setViewStartingAddress( emulator.getCPU().getPC() );
}
public void setViewStartingAddress(Address startingAddress)
{
setViewStartingAddress(startingAddress,true);
}
private void setViewStartingAddress(final Address startingAddress,final boolean adjustAddress)
{
UIUtils.invokeLater( new Runnable() {
@Override
public void run()
{
// show some context before the actual address so the
// use is not completely lost where in the program he is
final Address offset = Address.wordAddress( 3 );
final Address realStart = adjustAddress ? startingAddress.minus( offset ) : startingAddress;
int rows = calculateVisibleTextRowCount( textArea );
if ( rows < 5 ) {
rows = 5;
}
final List<DisassembledLine> lines = disassembler.disassemble(emulator.getMemory() , realStart , rows , showHexDump );
renderDisassembly(lines);
}} );
}
private void renderDisassembly(final List<DisassembledLine> lines)
{
final Address pc = emulator.getCPU().getPC(); // used to mark the current PC value
final StringBuilder result = new StringBuilder();
final Iterator<DisassembledLine> it = lines.iterator();
boolean first = true;
while( it.hasNext() )
{
final DisassembledLine line = it.next();
if ( first ) {
first = false;
addressAtTopOfScreen = line.getAddress();
}
if ( ! it.hasNext() ) {
addressAtBottomOfScreen = line.getAddress();
}
// create disassembled line
result.append( toString( pc , line ) );
if ( it.hasNext() ) {
result.append("\n");
}
}
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run()
{
textArea.setText( result.toString() );
}
});
}
private String toString(Address pc , DisassembledLine line)
{
final Address realAddress = line.getAddress();
return createLinePrefix(pc,realAddress,line)+ Misc.toHexString( realAddress )+": "+line.getContents();
}
private String createLinePrefix(Address pc , Address realAddress , DisassembledLine line)
{
/*
* The prefix may contain a flag indicating that
* a breakpoint is present on this line as well
* as a caret for the current program counter (PC) position.
*
* Example:
*
* [B] >> 0000: SET a,1
*/
final Breakpoint breakpoint = emulator.getBreakPoint( realAddress);
String prefix1 = breakpoint != null ? breakpoint.isEnabled() ? "[B] " : "[_] " : " ";
String prefix2 = realAddress.equals( pc ) ? ">> " : " ";
return prefix1+prefix2;
}
private DisassembledLine parseDisassembledLine(String text) {
// [B] >> 0000:
final Pattern pattern = Pattern.compile( "^(\\[[B_]{1}\\]){0,1}[ ]*(>>){0,1}[ ]*([0-9a-f]+):(.*?);(.*)");
final Matcher m = pattern.matcher( text );
if ( ! m.matches() ) {
throw new RuntimeException("Unparseable line '"+text+"'");
}
@SuppressWarnings("unused")
final String hasBreakpoint = m.group(1);
@SuppressWarnings("unused")
final String isAtCurrentPC = m.group(2);
final Address address = Address.wordAddress( Misc.parseHexString( m.group(3) ) );
final String disassembly = m.group(4);
final String instructionWordsHexDump = m.group(5).trim();
final Size instructionSize = Size.words( instructionWordsHexDump.split(" ").length );
return new DisassembledLine( address , disassembly , instructionSize );
}
private void setEmulator(IEmulator emulator)
{
if (emulator == null) {
throw new IllegalArgumentException("emulator must not be NULL.");
}
if ( this.emulator == emulator ) {
return;
}
if ( this.emulator != null ) {
this.emulator.removeEmulationListener( listener );
}
this.emulator = emulator;
this.emulatorController = new EmulatorControllerView( perspective , emulator );
emulator.addEmulationListener( listener );
}
@Override
public void disposeHook()
{
if ( this.emulator != null )
{
this.emulator.removeEmulationListener( listener );
this.emulatorController.dispose();
this.emulator = null;
}
}
protected JPanel createPanel()
{
textArea.setEditable( false );
setColors( textArea );
textArea.setFont( getMonospacedFont() );
textArea.setEditable( false );
textArea.addMouseListener( new MouseAdapter()
{
public void mouseClicked(java.awt.event.MouseEvent e)
{
if ( e.getButton() != MouseEvent.BUTTON3 ) {
return;
}
String text = getTextAtLocation( textArea , e.getX() , e.getY() );
if ( text != null ) {
text = text.replaceAll( Pattern.quote("\n" ) , "" );
final DisassembledLine line = parseDisassembledLine( text );
toggleBreakpoint( line.getAddress() );
}
}
} );
textArea.addKeyListener( new PagingKeyAdapter() {
@Override
protected void onePageUp() {
if ( addressAtTopOfScreen != null && addressAtBottomOfScreen != null)
{
final Size distanceInBytes = Address.calcDistanceInBytes( addressAtTopOfScreen , addressAtBottomOfScreen );
setViewStartingAddress( addressAtTopOfScreen.minus( distanceInBytes ) , false );
}
}
@Override
protected void onePageDown() {
if ( addressAtTopOfScreen != null && addressAtBottomOfScreen != null)
{
final Size distanceInBytes = Address.calcDistanceInBytes( addressAtTopOfScreen , addressAtBottomOfScreen );
setViewStartingAddress( addressAtTopOfScreen.plus( distanceInBytes , true ) , false );
}
}
@Override
protected void oneLineUp()
{
if ( addressAtTopOfScreen != null ) {
setViewStartingAddress( addressAtTopOfScreen.minus( WordAddress.wordAddress(1) ) , false );
}
}
@Override
protected void oneLineDown() {
if ( addressAtBottomOfScreen != null && addressAtTopOfScreen != null )
{
final int instructionSize = Emulator.calculateInstructionSizeInWords( addressAtBottomOfScreen , emulator.getMemory() );
setViewStartingAddress( addressAtTopOfScreen.plus( Size.words( instructionSize ) , true ) , false );
}
}
});
// setup top panel
final JPanel controlPanel = emulatorController.getPanel( getViewContainer() );
// setup bottom panel
final JPanel bottomPanel = new JPanel();
setColors( bottomPanel );
bottomPanel.setLayout( new GridBagLayout() );
GridBagConstraints cnstrs = constraints( 0 , 0 , true , true , GridBagConstraints.BOTH );
bottomPanel.add( textArea , cnstrs );
// ======== setup result panel ===========
final JPanel result = new JPanel();
result.addComponentListener( new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
refreshDisplay();
}
});
setColors( result );
result.setLayout( new GridBagLayout() );
cnstrs = constraints( 0 , 0 , true, false , GridBagConstraints.HORIZONTAL );
result.add( controlPanel , cnstrs );
cnstrs = constraints( 0 , 1 , true , true , GridBagConstraints.BOTH);
result.add( bottomPanel , cnstrs );
return result;
}
protected void toggleBreakpoint(Address address) {
Breakpoint existing = emulator.getBreakPoint( address );
if ( existing == null ) {
emulator.addBreakpoint( new Breakpoint( address ) );
} else {
emulator.deleteBreakpoint( existing );
}
}
@Override
public JPanel getPanel() {
if ( panel == null ) {
panel = createPanel();
}
return panel;
}
@Override
public String getTitle() {
return "Disassembly view";
}
@Override
public String getID() {
return VIEW_ID;
}
}