/** * 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.viewcontainers; import java.io.IOException; import java.lang.reflect.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.log4j.Logger; import de.codesourcery.jasm16.Address; import de.codesourcery.jasm16.compiler.io.*; import de.codesourcery.jasm16.compiler.io.IResource.ResourceType; import de.codesourcery.jasm16.emulator.EmulationListener; import de.codesourcery.jasm16.emulator.IEmulationListener; import de.codesourcery.jasm16.emulator.IEmulator; import de.codesourcery.jasm16.exceptions.ResourceNotFoundException; import de.codesourcery.jasm16.ide.*; import de.codesourcery.jasm16.ide.ui.utils.UIUtils; import de.codesourcery.jasm16.ide.ui.views.*; import de.codesourcery.jasm16.utils.Misc; public class DebuggingPerspective extends Perspective { private static final Logger LOG = Logger.getLogger(DebuggingPerspective.class); public static final String VIEW_ID = "debugger"; private final ProjectWrapper resourceResolver=new ProjectWrapper(); private final IWorkspace workspace; private final EmulatorProxy proxy = new EmulatorProxy(); private volatile IAssemblyProject project; private IResource executable; private final IWorkspaceListener workspaceListener = new WorkspaceListener() { private volatile boolean buildRunning = false; public void projectDeleted(IAssemblyProject deletedProject) { if ( deletedProject.isSame( project ) ) { dispose(); } } public void projectClosed(IAssemblyProject closedProject) { if ( closedProject.isSame( project ) ) { dispose(); } } public void resourceDeleted(IAssemblyProject affectedProject, IResource deletedResource) { if ( ! buildRunning && // do not dispose the perspective if executable is deleted as part of the build process affectedProject.isSame( project ) && DefaultResourceMatcher.INSTANCE.isSame( deletedResource , executable ) ) { dispose(); } } public void buildStarted(IAssemblyProject p) { if ( p.isSame( project ) ) { buildRunning = true; } } public void buildFinished(IAssemblyProject p , boolean success) { if ( ! p.isSame( project ) ) { return; } buildRunning = false; final IResource objectFile; if ( success ) { final List<IResource> objectFiles = project.getResources( ResourceType.EXECUTABLE ); if ( objectFiles.size() == 1 ) { objectFile = objectFiles.get(0); } else if ( objectFiles.isEmpty() ) { objectFile = null; } else { throw new RuntimeException("Project "+p+" has more than one executable?"); } } else { objectFile = null; } if ( objectFile != null ) { try { openExecutable( p , objectFile ); } catch (IOException e) { LOG.error("buildFinished()",e); } } else { dispose(); } }; }; private final IEmulationListener listener = new EmulationListener() { @Override public void afterMemoryLoad(IEmulator emulator, Address startAddress, int lengthInBytes) { UIUtils.invokeLater( new Runnable() { @Override public void run() { setupPerspective(); }} ); } }; private ViewContainerManager viewContainerManager; private final class ProjectWrapper implements IResourceResolver { private ProjectWrapper() { } private EditorContainer getEditorPerspective() { List<IViewContainer> result = viewContainerManager.getPerspectives( EditorContainer.VIEW_ID ); return result.isEmpty() ? null : (EditorContainer) result.get(0); } @Override public IResource resolve(String identifier) throws ResourceNotFoundException { final EditorContainer perspective = getEditorPerspective(); if ( perspective != null ) { return perspective.resolve( identifier ); } return project.resolve( identifier ); } @Override public IResource resolveRelative(String identifier, IResource parent) throws ResourceNotFoundException { final EditorContainer perspective = getEditorPerspective(); if ( perspective != null ) { return perspective.resolveRelative( identifier , parent ); } if ( parent instanceof InMemorySourceResource) { return project.resolveRelative( identifier , ((InMemorySourceResource) parent).getPersistentResource() ); } return project.resolveRelative( identifier , parent ); } } public DebuggingPerspective(IWorkspace workspace , ViewContainerManager viewContainerManager, IApplicationConfig appConfig) { super(VIEW_ID, viewContainerManager , appConfig); if ( workspace == null ) { throw new IllegalArgumentException("workspace must not be null"); } this.viewContainerManager = viewContainerManager; this.workspace = workspace; this.workspace.addWorkspaceListener( workspaceListener ); } public IResourceResolver getResourceResolver() { return resourceResolver; } protected void disposeHook() { try { workspace.removeWorkspaceListener( workspaceListener ); } finally { if ( emulator() != null ) { try { this.emulator().dispose(); } finally { this.proxy.setTarget( null ); } } } } private IEmulator emulator() { return proxy.hasTarget() ? proxy.getProxyInstance() : null; } public void openExecutable(IAssemblyProject project,IResource executable) throws IOException { if ( emulator() != null ) { this.emulator().dispose(); this.proxy.setTarget( project.getEmulationOptions().createEmulator() ); } else { this.proxy.setTarget( project.getEmulationOptions().createEmulator() ); this.emulator().addEmulationListener( listener ); } // set project & executable BEFORE loading object code // so any IEmulationListener that implements #afterMemoryLoad() // can query the DebuggingPerspective for the current project this.project = project; this.executable = executable; final byte[] objectCode = Misc.readBytes( executable ); emulator().loadMemory( Address.wordAddress( 0 ) , objectCode ); // triggers IEmulationListener#afterMemoryLoad() } public IAssemblyProject getCurrentProject() { return project; } public void reloadEmulator() { if ( emulator() != null && project != null ) { this.emulator().dispose(); this.proxy.setTarget( project.getEmulationOptions().createEmulator() ); } } public void resetEmulator() { if ( emulator() != null ) { emulator().reset( true ); } } private void setupPerspective() { final List<IView> createdViews = new ArrayList<>(); // setup CPU view if ( getCPUView() == null ) { CPUView view = new CPUView( emulator() ); createdViews.add( addView( view ) ); } // setup disassembler view if ( getDisassemblerView() == null ) { DisassemblerView view = new DisassemblerView( this, emulator() ); createdViews.add( addView( view ) ); } // setup stack view if ( getStackView() == null ) { StackView view = new StackView( emulator() ); createdViews.add( addView( view ) ); } // setup hex-dump view if ( getHexDumpView() == null ) { final HexDumpView view = new HexDumpView( emulator() ); createdViews.add( addView( view ) ); } // setup screen view if ( getScreenView() == null ) { final ScreenView view = new ScreenView( project , emulator() ); view.setDebugCustomFonts( false ); createdViews.add( addView( view ) ); } // setup SPED-3 vector display view if ( getVectorDisplayView() == null ) { final VectorDisplayView view = new VectorDisplayView( project , emulator() ); createdViews.add( addView( view ) ); } // setup source level debug view if ( getSourceLevelDebugView() == null ) { final SourceLevelDebugView view = new SourceLevelDebugView( resourceResolver , workspace , this ,new NavigationHistory(), emulator() ); createdViews.add( addView( view ) ); } // setup screen view if ( getBreakpointView() == null ) { final BreakpointView view = new BreakpointView( getDisassemblerView() , getSourceLevelDebugView() , emulator() ) { @Override protected IAssemblyProject getCurrentProject() { return DebuggingPerspective.this.getCurrentProject(); } }; createdViews.add( addView( view ) ); } // refresh views AFTER // all have been created, views // may have interdependencies for ( IView v : createdViews ) { v.refreshDisplay(); } } private SourceLevelDebugView getSourceLevelDebugView() { return (SourceLevelDebugView) getViewByID( SourceLevelDebugView.VIEW_ID ); } private BreakpointView getBreakpointView() { return (BreakpointView) getViewByID( BreakpointView.VIEW_ID ); } private ScreenView getScreenView() { return (ScreenView) getViewByID( ScreenView.VIEW_ID ); } private VectorDisplayView getVectorDisplayView() { return (VectorDisplayView) getViewByID( VectorDisplayView.VIEW_ID ); } private DisassemblerView getDisassemblerView() { return (DisassemblerView) getViewByID( DisassemblerView.VIEW_ID ); } private StackView getStackView() { return (StackView) getViewByID( StackView.VIEW_ID ); } private HexDumpView getHexDumpView() { return (HexDumpView) getViewByID( HexDumpView.VIEW_ID ); } private CPUView getCPUView() { return (CPUView) getViewByID( CPUView.VIEW_ID ); } protected static final class EmulatorProxy implements InvocationHandler { private final List<IEmulationListener> listeners = new ArrayList<>(); private final IEmulator proxy; private volatile IEmulator emulator; private Address address; private byte[] data; private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); public EmulatorProxy() { this.proxy = (IEmulator) Proxy.newProxyInstance( EmulatorProxy.class.getClassLoader() , new Class<?>[]{IEmulator.class},this); } public boolean hasTarget() { lock( readLock ); try { return emulator != null; } finally { readLock.unlock(); } } private void lock(Lock l) { try { l.lockInterruptibly(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Failed to aquire lock "+l); } } public void setTarget(IEmulator e) { lock(writeLock); try { if ( this.emulator == e ) { return; } removeEmulationListeners(); this.emulator=e; if ( e != null ) { restoreEmulatorState(); } else { address = null; data = null; } } finally { writeLock.unlock(); } } private void restoreEmulatorState() { if ( this.emulator != null ) { for ( IEmulationListener l : listeners ) { this.emulator.addEmulationListener( l ); } if ( address != null ) { this.emulator.loadMemory( address , data ); } } } private void removeEmulationListeners() { if ( this.emulator != null ) { for ( IEmulationListener l : listeners ) { this.emulator.removeEmulationListener( l ); } } } public IEmulator getTarget() { lock(readLock); try { return emulator; } finally { readLock.unlock(); } } public IEmulator getProxyInstance() { return proxy; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final IEmulator emu; lock(readLock); try { if ( method.getName().equals("loadMemory" ) ) { final Address adr = (Address) args[0]; final byte[] data = (byte[]) args[1]; this.data = data; this.address = adr; } else if ( method.getName().equals("addEmulationListener" ) ) { IEmulationListener listener = (IEmulationListener) args[0]; if ( listener != null && ! listener.belongsToHardwareDevice() ) { listeners.add( listener ); } } else if ( method.getName().equals("removeEmulationListener" ) ) { IEmulationListener listener = (IEmulationListener) args[0]; if ( listener != null ) { listeners.remove( listener ); } } else if ( method.getName().equals("removeAllEmulationListeners" ) ) { listeners.clear(); } emu = this.emulator; } finally { readLock.unlock(); } // alien method,invoke outside synchronized block boolean success = false; try { if ( emu != null ) { Object result = method.invoke( emu , args ); success = true; return result; } System.err.println("Cannot invoke method "+method+" on NULL emulator"); return null; } catch(Exception e) { if ( e instanceof InvocationTargetException) { if ( ((InvocationTargetException) e).getTargetException() != null ) { ((InvocationTargetException) e).getTargetException().printStackTrace(); throw ((InvocationTargetException) e).getTargetException(); } } if ( e.getCause() != null ) { Throwable cause = e.getCause(); while ( cause.getCause() != null ) { cause = cause.getCause(); } cause.printStackTrace(); } else { e.printStackTrace(); } throw e; } finally { if ( success && method.getName().equals("reset" ) ) { if ( address != null ) { emu.loadMemory( address , data ); } } } } } }