/**
* 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.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JPanel;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import org.apache.log4j.Logger;
import de.codesourcery.jasm16.Address;
import de.codesourcery.jasm16.Size;
import de.codesourcery.jasm16.ast.ASTNode;
import de.codesourcery.jasm16.ast.ASTUtils;
import de.codesourcery.jasm16.ast.ISimpleASTNodeVisitor;
import de.codesourcery.jasm16.ast.InstructionNode;
import de.codesourcery.jasm16.ast.LabelNode;
import de.codesourcery.jasm16.ast.ObjectCodeOutputNode;
import de.codesourcery.jasm16.ast.RegisterReferenceNode;
import de.codesourcery.jasm16.ast.StatementNode;
import de.codesourcery.jasm16.ast.SymbolReferenceNode;
import de.codesourcery.jasm16.compiler.Equation;
import de.codesourcery.jasm16.compiler.Executable;
import de.codesourcery.jasm16.compiler.ICompilationUnit;
import de.codesourcery.jasm16.compiler.ISymbol;
import de.codesourcery.jasm16.compiler.ISymbolTable;
import de.codesourcery.jasm16.compiler.Label;
import de.codesourcery.jasm16.compiler.SourceLocation;
import de.codesourcery.jasm16.compiler.io.DefaultResourceMatcher;
import de.codesourcery.jasm16.compiler.io.IResourceResolver;
import de.codesourcery.jasm16.emulator.Breakpoint;
import de.codesourcery.jasm16.emulator.EmulationListener;
import de.codesourcery.jasm16.emulator.IEmulationListener;
import de.codesourcery.jasm16.emulator.IEmulator;
import de.codesourcery.jasm16.emulator.memory.MemUtils;
import de.codesourcery.jasm16.ide.IAssemblyProject;
import de.codesourcery.jasm16.ide.IWorkspace;
import de.codesourcery.jasm16.ide.NavigationHistory;
import de.codesourcery.jasm16.ide.ui.MenuManager;
import de.codesourcery.jasm16.ide.ui.MenuManager.MenuEntry;
import de.codesourcery.jasm16.ide.ui.utils.UIUtils;
import de.codesourcery.jasm16.ide.ui.viewcontainers.DebuggingPerspective;
import de.codesourcery.jasm16.utils.ITextRegion;
import de.codesourcery.jasm16.utils.Misc;
public class SourceLevelDebugView extends SourceCodeView
{
public static final String VIEW_ID = "source-level-debug";
private static final Logger LOG = Logger.getLogger(SourceLevelDebugView.class);
private JPanel panel;
private final IEmulator emulator;
private volatile Object currentHighlight;
private IAssemblyProject currentProject;
private volatile ICompilationUnit currentUnit;
private final DebuggingPerspective perspective;
// @GuardedBy( breakpointHighlights )
private final Map<Address,Object> breakpointHighlights = new HashMap<Address,Object>();
private final IEmulationListener listener = new EmulationListener() {
@Override
public void breakpointAdded(IEmulator emulator, final Breakpoint breakpoint) {
UIUtils.invokeLater( new Runnable() {
@Override
public void run()
{
highlightBreakpoint( breakpoint , true );
}
});
}
@Override
public void breakpointDeleted(IEmulator emulator, final Breakpoint breakpoint) {
UIUtils.invokeLater( new Runnable() {
@Override
public void run()
{
highlightBreakpoint( breakpoint , false );
}
});
}
@Override
public void afterCommandExecution(IEmulator emulator, int commandDuration) {
refreshDisplayHook();
}
@Override
public void onStopHook(final IEmulator emulator, final Address previousPC, final Throwable emulationError)
{
UIUtils.invokeLater( new Runnable() {
@Override
public void run()
{
if ( emulationError != null )
{
if ( ! scrollToVisible( emulator.getCPU().getPC() , true , false ) ) {
scrollToVisible( previousPC , true , false );
}
} else {
refreshDisplayHook();
}
}
});
}
@Override
public void afterReset(IEmulator emulator) {
refreshDisplayHook();
}
@Override
public void afterMemoryLoad(final IEmulator emulator, Address startAddress, int lengthInBytes) {
UIUtils.invokeLater( new Runnable() {
public void run() {
scrollToVisible( emulator.getCPU().getPC() , true ,true);
}
});
}
};
public SourceLevelDebugView(IResourceResolver resourceResolver,
IWorkspace workspace,
DebuggingPerspective perspective,
NavigationHistory navigationHistory,
IEmulator emulator)
{
super(resourceResolver,workspace, navigationHistory,false);
if ( perspective == null ) {
throw new IllegalArgumentException("perspective must not be NULL.");
}
if (emulator == null) {
throw new IllegalArgumentException("emulator must not be NULL.");
}
this.perspective = perspective;
this.emulator = emulator;
this.emulator.addEmulationListener( listener );
}
private final MouseAdapter mouseListener = new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e)
{
final ASTNode n = getASTNode( e.getPoint() );
if ( n != null && ( n instanceof SymbolReferenceNode || n instanceof LabelNode) )
{
ISymbol symbol = null;
if ( n instanceof SymbolReferenceNode) {
symbol = ((SymbolReferenceNode) n).resolve( currentUnit.getSymbolTable() , true );
if ( symbol == null ) {
System.err.println("Failed to resolve symbol: "+n);
}
}
else
{
symbol = ((LabelNode) n).getLabel();
}
if ( symbol != null && symbol instanceof Label)
{
final Address dumpStartAddress = ((Label) symbol).getAddress();
final int WORDS_TO_SHOW = 6;
final byte[] bytes = MemUtils.getBytes( emulator.getMemory() , dumpStartAddress , Size.words(WORDS_TO_SHOW) , true );
final String tooltip = Misc.toHexDumpWithAddresses(dumpStartAddress,
bytes, bytes.length , WORDS_TO_SHOW , true , true );
showTooltip( tooltip );
}
else if ( symbol != null && symbol instanceof Equation )
{
Equation eq = (Equation) symbol;
ISymbolTable table = currentUnit.getSymbolTable();
if ( table.getParent() != null ) {
table = table.getParent();
}
Long value = eq.getValue( table );
if ( value != null )
{
if ( value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
showTooltip( "0x"+Misc.toHexString( value.intValue() ) +" ("+value.intValue()+")" );
} else {
showTooltip( "0x"+Misc.toHexString( value.longValue() ) +" ("+value.longValue()+")" );
}
}
} else {
clearTooltip();
}
} else if ( n != null && n instanceof RegisterReferenceNode) {
RegisterReferenceNode reg = (RegisterReferenceNode) n;
final int value = emulator.getCPU().getRegisterValue( reg.getRegister() );
showTooltip( "0x"+Misc.toHexString( value ) +" ("+value+")" );
} else {
clearTooltip();
}
}
@Override
public void mouseClicked(java.awt.event.MouseEvent e)
{
if ( e.getButton() != MouseEvent.BUTTON3 ) {
return;
}
final ASTNode n = getASTNode( e.getPoint() );
if ( n == null ) {
return;
}
final StatementNode statementNode = getStatementNode( n );
if ( statementNode == null ) {
return;
}
for ( ASTNode child : statementNode.getChildren() )
{
if ( child instanceof InstructionNode) {
Address address = ((InstructionNode) child).getAddress();
if ( address != null ) {
toggleBreakpoint( address );
}
}
}
}
private ASTNode getASTNode(Point p)
{
final int offset = getModelOffsetForLocation( p );
if ( offset == -1 ) {
return null;
}
final ICompilationUnit unit = getCurrentCompilationUnit();
if ( unit == null ) {
return null;
}
return unit.getAST().getNodeInRange( offset );
}
};
protected void disposeHook2() {
if ( panel != null ) {
removeMouseListener( mouseListener );
}
emulator.removeEmulationListener( listener );
}
private void toggleBreakpoint(Address address)
{
Breakpoint existing = emulator.getBreakPoint( address );
if ( existing != null ) {
emulator.deleteBreakpoint( existing );
} else {
emulator.addBreakpoint( new Breakpoint(address) );
}
}
@Override
public JPanel getPanel()
{
if ( panel == null )
{
panel = createPanel();
addMouseListener( mouseListener );
setupMenu( getViewContainer().getMenuManager() );
}
return panel;
}
private void setupMenu(MenuManager menuManager) {
final MenuEntry entry = new MenuEntry("Options/Emulation options") {
@Override
public void onClick()
{
if ( currentProject == null ) {
return;
}
EmulationOptionsView view = (EmulationOptionsView) getViewContainer().getViewByID( EmulationOptionsView.ID );
if ( view == null )
{
view = new EmulationOptionsView()
{
protected void onSave(de.codesourcery.jasm16.emulator.EmulationOptions options)
{
// close window
getViewContainer().disposeView( this );
// apply changes
options.apply( emulator );
currentProject.setEmulationOptions( options );
try {
workspace.saveProjectConfiguration( currentProject );
}
catch (IOException e) {
LOG.error("setupMenu(): Failed to save options for project "+currentProject,e);
}
if ( options.isNewEmulatorInstanceRequired() )
{
perspective.reloadEmulator();
}
}
protected void onCancel() {
getViewContainer().disposeView( this );
}
};
getViewContainer().addView( view );
}
getViewContainer().toFront( view );
view.setData( currentProject.getEmulationOptions() );
}
};
menuManager.addEntry( entry );
}
private JPanel createPanel() {
final JPanel result = new JPanel();
setColors( result );
result.setLayout( new GridBagLayout() );
final EmulatorControllerView controller = new EmulatorControllerView( perspective , emulator );
addChild( controller );
GridBagConstraints cnstrs = constraints( 0 , 0, true , false , GridBagConstraints.HORIZONTAL );
cnstrs.weighty=0.0;
result.add( controller.getPanel( getViewContainer() ) , cnstrs );
final JPanel sourceView = super.getPanel();
cnstrs = constraints( 0 , 1, true , true, GridBagConstraints.BOTH );
cnstrs.weighty=1.0;
result.add( sourceView , cnstrs );
return result;
}
public void scrollToVisible(Address address) {
scrollToVisible(address,false,false);
}
protected boolean scrollToVisible(Address address,boolean highlight,boolean reloadSource) {
if ( perspective.getCurrentProject() == null ) {
return false;
}
final boolean updateParentView = reloadSource || perspective.getCurrentProject() != this.currentProject;
if ( updateParentView ) {
this.currentProject = perspective.getCurrentProject();
}
final Executable executable = this.currentProject.getProjectBuilder().getExecutable();
if ( executable == null ) {
return false;
}
final SourceLocation loc = executable.getDebugInfo().getSourceLocation( address );
if ( loc == null ) {
System.out.println("Found no source for address "+address);
return false;
}
if ( updateParentView || this.currentUnit == null ||
! DefaultResourceMatcher.INSTANCE.isSame( this.currentUnit.getResource() , loc.getCompilationUnit().getResource() ) )
{
switchToCompilationUnit( perspective.getCurrentProject() , loc.getCompilationUnit() );
highlightBreakpoints();
}
// scroll to current location
gotoLocation( loc.getStartingOffset() );
if ( highlight )
{
// highlight location
try
{
if ( currentHighlight == null )
{
currentHighlight = getHighlighter().addHighlight(
loc.getStartingOffset() ,
loc.getEndOffset() ,
new DefaultHighlighter.DefaultHighlightPainter(Color.GREEN) );
} else {
getHighlighter().changeHighlight( currentHighlight ,
loc.getStartingOffset() ,
loc.getEndOffset() );
}
return true;
} catch (BadLocationException e) {
LOG.error("refreshDisplayHook(): ",e);
}
}
return false;
}
@Override
protected void refreshDisplayHook()
{
UIUtils.invokeLater( new Runnable() {
@Override
public void run()
{
scrollToVisible( emulator.getCPU().getPC() , true ,false);
}
});
}
private void highlightBreakpoints()
{
final List<Breakpoint> copy;
synchronized(breakpointHighlights) {
copy = emulator.getBreakPoints();
}
for ( Breakpoint bp : copy ) {
highlightBreakpoint(bp,true);
}
}
private void highlightBreakpoint(Breakpoint bp,boolean renderHighlight)
{
StatementNode node = getStatementNodeForAddress( this.currentUnit , bp.getAddress() );
if ( node == null ) {
return;
}
final Object existingHighlight;
synchronized (breakpointHighlights) {
existingHighlight = breakpointHighlights.get(bp.getAddress());
}
if ( existingHighlight != null && ! renderHighlight )
{
getHighlighter().removeHighlight( existingHighlight );
}
else if ( existingHighlight == null && renderHighlight )
{
final ITextRegion region = node.getTextRegion();
try {
final Object tag = getHighlighter().addHighlight(
region.getStartingOffset() ,
region.getEndOffset() ,
new DefaultHighlighter.DefaultHighlightPainter(Color.RED) );
synchronized (breakpointHighlights)
{
breakpointHighlights.put( bp.getAddress() , tag );
}
}
catch (BadLocationException e) {
LOG.error("highlightBreakpoint(): ",e);
}
}
}
private void switchToCompilationUnit(IAssemblyProject project,ICompilationUnit unit)
{
try {
disableDocumentListener();
clearHighlights();
openResource( this.currentProject , unit.getResource() , false );
} catch (IOException e) {
LOG.error("refreshDisplayHook(): Caught ",e);
return;
} finally {
enableDocumentListener();
}
this.currentUnit = unit;
}
private void clearHighlights()
{
synchronized (breakpointHighlights) {
getHighlighter().removeAllHighlights();
breakpointHighlights.clear();
this.currentHighlight = null;
}
}
protected StatementNode getStatementNodeForAddress(final ICompilationUnit unit , final Address address)
{
return getStatementNode( getASTNodeForAddress(unit,address) );
}
protected StatementNode getStatementNode(ASTNode node) {
ASTNode current = node;
while ( current != null ) {
if ( current instanceof StatementNode) {
return (StatementNode) current;
}
current = current.getParent();
}
return null;
}
protected ASTNode getASTNodeForAddress(final ICompilationUnit unit , final Address address)
{
final ObjectCodeOutputNode[] result = {null};
final ISimpleASTNodeVisitor<ASTNode> visitor = new ISimpleASTNodeVisitor<ASTNode>() {
@Override
public boolean visit(ASTNode node)
{
if ( node instanceof ObjectCodeOutputNode) {
if ( ((ObjectCodeOutputNode) node).getAddress().equals( address ) ) {
result[0] = (ObjectCodeOutputNode) node;
return false;
}
}
return true;
}
};
ASTUtils.visitInOrder( unit.getAST() , visitor );
return result[0];
}
@Override
public String getTitle()
{
return "Source-level debug";
}
@Override
public String getID()
{
return VIEW_ID;
}
}