/******************************************************************************* * Copyright (c) 2010 Wind River Systems, Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation * Freescale Semiconductor - refactoring * Patrick Chuong (Texas Instruments) - Bug fix (329682) * Patrick Chuong (Texas Instruments) - Bug fix (304108) *******************************************************************************/ package org.eclipse.cdt.debug.internal.ui.disassembly.dsf; import java.math.BigInteger; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.debug.core.cdi.CDIException; import org.eclipse.cdt.debug.core.cdi.model.ICDIExpression; import org.eclipse.cdt.debug.core.model.IAsmInstruction; import org.eclipse.cdt.debug.core.model.IAsmSourceLine; import org.eclipse.cdt.debug.core.model.ICDebugElement; import org.eclipse.cdt.debug.core.model.ICDebugTarget; import org.eclipse.cdt.debug.core.model.ICStackFrame; import org.eclipse.cdt.debug.core.model.ICThread; import org.eclipse.cdt.debug.core.model.ICType; import org.eclipse.cdt.debug.core.model.ICValue; import org.eclipse.cdt.debug.core.model.IDisassemblyBlock; import org.eclipse.cdt.debug.internal.core.CRequest; import org.eclipse.cdt.debug.internal.core.model.CDebugTarget; import org.eclipse.cdt.debug.internal.core.model.CExpression; import org.eclipse.cdt.debug.internal.core.model.CStackFrame; import org.eclipse.cdt.debug.internal.ui.CDebugUIMessages; import org.eclipse.cdt.debug.ui.CDebugUIPlugin; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IDebugEventSetListener; import org.eclipse.debug.core.model.ISourceLocator; import org.eclipse.debug.core.model.IStackFrame; import org.eclipse.debug.core.model.IValue; import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector; import org.eclipse.debug.core.sourcelookup.containers.LocalFileStorage; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Position; import com.ibm.icu.text.MessageFormat; /** * The CDI backend to the DSF disassembly view. * */ public class DisassemblyBackendCdi extends AbstractDisassemblyBackend implements IDebugEventSetListener { private ICThread fTargetContext; private String fCdiSessionId; private ICStackFrame fTargetFrameContext; private CDIDisassemblyRetrieval fDisassemblyRetrieval; /* The frame level as the disassembly callback expects it (0 = topmost frame) */ private int fFrameLevel; public DisassemblyBackendCdi() { } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#init(org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyPartCallback) */ @Override public void init(IDisassemblyPartCallback callback) { super.init(callback); DebugPlugin.getDefault().addDebugEventListener(this); } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#hasDebugContext() */ public boolean hasDebugContext() { return fTargetContext != null; } /** * Unlike DSF, CDI sessions don't have an ID. But to appease the DSF * Disassembly view, we fabricate one. * * @param debugElement * the debug element which represents the process being debugged * @return the session ID */ private String getSessionId(ICDebugElement debugElement) { return "cdi-" + System.identityHashCode(debugElement.getDebugTarget()); //$NON-NLS-1$ } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#setDebugContext(org.eclipse.core.runtime.IAdaptable) */ public SetDebugContextResult setDebugContext(IAdaptable context) { assert supportsDebugContext(context) : "caller should not have invoked us"; //$NON-NLS-1$ SetDebugContextResult result = new SetDebugContextResult(); ICDebugTarget cdiDebugTarget = (ICDebugTarget)((ICDebugElement)context).getDebugTarget(); String cdiSessionId = getSessionId(cdiDebugTarget); fDisassemblyRetrieval = new CDIDisassemblyRetrieval(cdiDebugTarget); if (!cdiSessionId.equals(fCdiSessionId)) { fTargetContext = null; fTargetFrameContext = null; result.contextChanged = true; if (context instanceof ICStackFrame) { fFrameLevel = 0; fTargetContext = (ICThread)((ICStackFrame)context).getThread(); try { // Get the topmost stack frame. Note that the state of the // thread may have changed by now. It may be running, in // which case we'll get null here. See bugzilla 317226 IStackFrame topFrame = fTargetContext.getTopStackFrame(); if (topFrame != null) { fTargetFrameContext = (ICStackFrame)context; // CDI frame levels are ordered opposite of DSF. Frame 0 is the // root frame of the thread where in DSF it's the topmost frame // (where the PC is). Do a little math to flip reverse the value fFrameLevel = ((CStackFrame)topFrame).getLevel() - fTargetFrameContext.getLevel(); } } catch (DebugException e) { } } if (fTargetContext != null) { result.sessionId = fCdiSessionId = cdiSessionId; } } else if (context instanceof ICStackFrame) { result.sessionId = fCdiSessionId; fTargetFrameContext = null; fFrameLevel = 0; ICThread newTargetContext = (ICThread)((ICStackFrame)context).getThread(); ICThread oldTargetContext = fTargetContext; fTargetContext = newTargetContext; if (oldTargetContext == null) { result.contextChanged = true; } else if (/*oldTargetContext != null && */newTargetContext != null) { result.contextChanged = !oldTargetContext.getDebugTarget().equals(newTargetContext.getDebugTarget()); } try { // Get the topmost stack frame. Note that the state of the // thread may have changed by now. It may be running, in // which case we'll get null here. See bugzilla 317226 IStackFrame topFrame = fTargetContext.getTopStackFrame(); if (topFrame != null) { fTargetFrameContext = (ICStackFrame)context; // CDI frame levels are ordered opposite of DSF. Frame 0 is the // root frame of the thread where in DSF it's the topmost frame // (where the PC is). Do a little math to flip reverse the value fFrameLevel = ((CStackFrame)topFrame).getLevel() - fTargetFrameContext.getLevel(); } } catch (DebugException e) { } if (!result.contextChanged) { fCallback.gotoFrame(fFrameLevel); } } else { fTargetContext = null; fTargetFrameContext = null; result.contextChanged = true; } return result; } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#supportsDebugContext(org.eclipse.core.runtime.IAdaptable) */ public boolean supportsDebugContext(IAdaptable context) { return supportsDebugContext_(context); } /** * @param context * @return */ public static boolean supportsDebugContext_(IAdaptable context) { return (context != null) && (context.getAdapter(ICDebugElement.class) != null); } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#clearDebugContext() */ public void clearDebugContext() { fTargetContext= null; fCdiSessionId = null; fTargetFrameContext = null; fDisassemblyRetrieval = null; fFrameLevel = 0; } private class AddressRequest extends CRequest implements IDisassemblyRetrieval.AddressRequest { private BigInteger fAddress; public BigInteger getAddress() { return fAddress; } public void setAddress(BigInteger address) { fAddress = address; } }; /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#retrieveFrameAddress(int) */ public void retrieveFrameAddress(final int targetFrame) { try { final IStackFrame[] stackFrames= fTargetContext.getStackFrames(); if (stackFrames.length <= targetFrame) { fCallback.setUpdatePending(false); return; } IStackFrame stackFrame= stackFrames[targetFrame]; fDisassemblyRetrieval.asyncGetFrameAddress(stackFrame, new AddressRequest() { @Override public void done() { fCallback.setUpdatePending(false); if (isSuccess()) { BigInteger address= getAddress(); if (targetFrame == 0) { fCallback.updatePC(address); } else { fCallback.gotoFrame(targetFrame, address); } } } }); } catch (DebugException exc) { DisassemblyUtils.internalError(exc); } } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#getFrameLevel() */ public int getFrameLevel() { return fFrameLevel; } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#isSuspended() */ public boolean isSuspended() { return fTargetContext != null && fTargetContext.isSuspended(); } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#getFrameFile() */ public String getFrameFile() { return fTargetFrameContext != null ? fTargetFrameContext.getFile() : null; } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#getFrameLine() */ public int getFrameLine() { return fTargetFrameContext.getFrameLineNumber(); } private class DisassemblyRequest extends CRequest implements IDisassemblyRetrieval.DisassemblyRequest { private IDisassemblyBlock fBlock; public IDisassemblyBlock getDisassemblyBlock() { return fBlock; } public void setDisassemblyBlock(IDisassemblyBlock block) { fBlock = block; } }; /* (non-Javadoc) * @see org.eclipse.cdt.dsf.debug.internal.ui.disassembly.IDisassemblyBackend#retrieveDisassembly(java.math.BigInteger, java.math.BigInteger, java.lang.String, boolean, boolean, boolean, int, int, int) */ public void retrieveDisassembly(final BigInteger startAddress, BigInteger endAddress, final String file, int lineNumber, final int lines, final boolean mixed, final boolean showSymbols, final boolean showDisassembly, final int linesHint) { if (fTargetContext == null || fTargetContext.isTerminated()) { return; } final BigInteger addressLength= BigInteger.valueOf(lines * 4); if (endAddress.subtract(startAddress).compareTo(addressLength) > 0) { endAddress= startAddress.add(addressLength); } // make sure address range is no less than 32 bytes // this is an attempt to get better a response from the backend (bug 302925) final BigInteger finalEndAddress= startAddress.add(BigInteger.valueOf(32)).max(endAddress); final IDisassemblyRetrieval.DisassemblyRequest disassemblyRequest= new DisassemblyRequest() { @Override public void done() { if (isSuccess() && getDisassemblyBlock() != null) { if (!insertDisassembly(startAddress, finalEndAddress, getDisassemblyBlock(), mixed, showSymbols, showDisassembly)) { // did not get disassembly data for startAddress - try fallbacks if (file != null) { // previous attempt used the file; retry using the address fCallback.setUpdatePending(true); retrieveDisassembly(startAddress, finalEndAddress, null, -1, lines, mixed, showSymbols, showDisassembly, linesHint); } else if (mixed) { // retry using non-mixed mode fCallback.setUpdatePending(true); retrieveDisassembly(startAddress, finalEndAddress, null, -1, lines, false, showSymbols, showDisassembly, linesHint); } else { // give up fCallback.doScrollLocked(new Runnable() { public void run() { fCallback.insertError(startAddress, "Unable to retrieve disassembly data from backend."); //$NON-NLS-1$ } }); } } } else { final IStatus status= getStatus(); if (status != null && !status.isOK()) { fCallback.doScrollLocked(new Runnable() { public void run() { fCallback.insertError(startAddress, status.getMessage()); } }); } fCallback.setUpdatePending(false); } } }; fDisassemblyRetrieval.asyncGetDisassembly(startAddress, finalEndAddress, file, lineNumber, lines, mixed, disassemblyRequest); } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#insertSource(org.eclipse.jface.text.Position, java.math.BigInteger, java.lang.String, int) */ public Object insertSource(Position pos, BigInteger address, String file, int lineNumber) { ISourceLocator locator = fTargetContext.getLaunch().getSourceLocator(); if (locator instanceof ISourceLookupDirector) { return ((ISourceLookupDirector)locator).getSourceElement(file); } return null; } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#hasFrameContext() */ public boolean hasFrameContext() { return fTargetFrameContext != null; } /* (non-Javadoc) * @see org.eclipse.cdt.dsf.debug.internal.ui.disassembly.IDisassemblyBackend#gotoSymbol(java.lang.String) */ public void gotoSymbol(String symbol) { final BigInteger address = evaluateAddressExpression(symbol, false); if (address != null) { fCallback.asyncExec(new Runnable() { public void run() { fCallback.gotoAddress(address); }}); } } /* * (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#evaluateSymbolAddress(java.lang.String, boolean) */ @Override public BigInteger evaluateAddressExpression(String symbol, final boolean suppressError) { if (fTargetFrameContext != null) { try { // This logic was lifted from CMemoryBlockRetrievalExtension.getExtendedMemoryBlock(String, Object) CStackFrame cstackFrame = (CStackFrame)fTargetFrameContext; ICDIExpression cdiExpression = cstackFrame.getCDITarget().createExpression(symbol); CExpression cdtExpression = new CExpression(cstackFrame, cdiExpression, null); IValue value = cdtExpression.getValue(); if (value instanceof ICValue) { ICType type = ((ICValue)value).getType(); if (type != null) { // get the address for the expression, allow all types String rawExpr = cdtExpression.getExpressionString(); String voidExpr = "(void *)(" + rawExpr + ')'; //$NON-NLS-1$ String attempts[] = { rawExpr, voidExpr }; for (int i = 0; i < attempts.length; i++) { String expr = attempts[i]; String addressStr = cstackFrame.evaluateExpressionToString(expr); if (addressStr != null) { try { return (addressStr.startsWith("0x")) ? new BigInteger(addressStr.substring(2), 16) : new BigInteger(addressStr); //$NON-NLS-1$ } catch (NumberFormatException e) { if (i >= attempts.length) { throw new DebugException(new Status(IStatus.ERROR, CDebugUIPlugin.PLUGIN_ID, MessageFormat.format(CDebugUIMessages.getString("DisassemblyBackendCdi_Symbol_Evaluation_Unusable"), new String[]{symbol}))); //$NON-NLS-1$ } } } } } } else { throw new DebugException(new Status(IStatus.ERROR, CDebugUIPlugin.PLUGIN_ID, MessageFormat.format(CDebugUIMessages.getString("DisassemblyBackendCdi_Symbol_Didnt_Evaluate"), new String[]{symbol}))); //$NON-NLS-1$ } } catch (final CDIException exc) { if (!suppressError) { fCallback.asyncExec(new Runnable() { public void run() { ErrorDialog.openError(fCallback.getSite().getShell(), CDebugUIMessages.getString("DisassemblyBackendCdi_Error_Dlg_Title"), //$NON-NLS-1$ null, new Status(IStatus.ERROR, CDebugUIPlugin.PLUGIN_ID, exc.getLocalizedMessage())); }}); } } catch (final DebugException exc) { if (!suppressError) { fCallback.asyncExec(new Runnable() { public void run() { ErrorDialog.openError(fCallback.getSite().getShell(), CDebugUIMessages.getString("DisassemblyBackendCdi_Error_Dlg_Title"), //$NON-NLS-1$ null, exc.getStatus()); }}); } } } return null; } /* (non-Javadoc) * @see org.eclipse.cdt.dsf.debug.internal.ui.disassembly.IDisassemblyBackend#retrieveDisassembly(java.lang.String, int, java.math.BigInteger, boolean, boolean, boolean) */ public void retrieveDisassembly(String file, int lines, final BigInteger endAddress, final boolean mixed, final boolean showSymbols, final boolean showDisassembly) { final IDisassemblyRetrieval.DisassemblyRequest disassemblyRequest= new DisassemblyRequest() { @Override public void done() { if (isSuccess() && getDisassemblyBlock() != null) { insertDisassembly(null, endAddress, getDisassemblyBlock(), mixed, showSymbols, showDisassembly); } else { final IStatus status= getStatus(); if (status != null && !status.isOK()) { fCallback.asyncExec(new Runnable() { public void run() { ErrorDialog.openError(fCallback.getSite().getShell(), "Error", null, getStatus()); //$NON-NLS-1$ } }); } fCallback.setUpdatePending(false); } } }; assert !fCallback.getUpdatePending(); fCallback.setUpdatePending(true); fDisassemblyRetrieval.asyncGetDisassembly(null, endAddress, file, 1, lines, mixed, disassemblyRequest); } /** * @param startAddress * an address the caller is hoping will be covered by this * insertion. I.e., [disassemblyBlock] may or may not contain * that address; the caller wants to know if it does, and so we * indicate that via our return value. Can be null to indicate n/a, * in which case we return true as long as any instruction was inserted * @param endAddress * cut-off address. Any elements in [disassemblyBlock] that * extend beyond this address are ignored. * @param disassemblyBlock * @param mixed * @param showSymbols * @param showDisassembly * @return whether [startAddress] was inserted */ private boolean insertDisassembly(BigInteger startAddress, BigInteger endAddress, IDisassemblyBlock disassemblyBlock, boolean mixed, boolean showSymbols, boolean showDisassembly) { if (!fCallback.hasViewer() || fCdiSessionId == null) { // return true to avoid a retry return true; } if (!fCallback.getUpdatePending()) { // safe-guard in case something weird is going on assert false; // return true to avoid a retry return true; } // indicates whether [startAddress] was inserted boolean insertedStartAddress = startAddress == null; try { fCallback.lockScroller(); final IDisassemblyDocument document = fCallback.getDocument(); // for convenience IAsmSourceLine[] srcLines= disassemblyBlock.getSourceLines(); AddressRangePosition p = null; Object srcElement= disassemblyBlock.getSourceElement(); for (int i = 0; i < srcLines.length; i++) { IAsmSourceLine srcLine= srcLines[i]; // If the caller doesn't want mixed, set line number to -1 so we // create a pure disassembly position object int lineNumber= mixed ? srcLine.getLineNumber() - 1 : -1; IAsmInstruction[] instructions= srcLine.getInstructions(); for (int j = 0; j < instructions.length; j++) { IAsmInstruction instruction = instructions[j]; BigInteger address= instruction.getAdress().getValue(); if (startAddress == null) { startAddress = address; fCallback.setGotoAddressPending(address); } if (p == null || !p.containsAddress(address)) { p = fCallback.getPositionOfAddress(address); } if (p instanceof ErrorPosition && p.fValid) { p.fValid = false; document.addInvalidAddressRange(p); } else if (p == null || address.compareTo(endAddress) > 0) { return insertedStartAddress; } else if (p.fValid) { if (srcElement != null && lineNumber >= 0 || p.fAddressLength == BigInteger.ONE) { // override probably unaligned disassembly p.fValid = false; document.addInvalidAddressRange(p); } else { return insertedStartAddress; } } boolean hasSource= false; String compilationPath= null; if (srcElement != null && lineNumber >= 0) { if (srcElement instanceof LocalFileStorage) { compilationPath = ((LocalFileStorage)srcElement).getFullPath().toString(); } else if (srcElement instanceof IFile) { compilationPath = ((IFile)srcElement).getLocation().toString(); } else if (srcElement instanceof java.io.File) { compilationPath = ((java.io.File)srcElement).getAbsolutePath(); } else if (srcElement instanceof ITranslationUnit) { IPath location = ((ITranslationUnit) srcElement).getLocation(); if (location != null) { compilationPath = location.toString(); } } else { assert false : "missing support for source element of type " + srcElement.getClass().toString(); //$NON-NLS-1$ } if (compilationPath != null) { p = fCallback.insertSource(p, address, compilationPath, lineNumber); hasSource = fCallback.getStorageForFile(compilationPath) != null; } else { hasSource = false; } } // insert symbol label final String functionName= instruction.getFunctionName(); if (functionName != null && functionName.length() > 0 && instruction.getOffset() == 0) { p = document.insertLabel(p, address, functionName, showSymbols && (!hasSource || showDisassembly)); } // determine instruction byte length BigInteger instrLength= null; if (j < instructions.length - 1) { instrLength= instructions[j+1].getAdress().distanceTo(instruction.getAdress()).abs(); } else if (i < srcLines.length - 1) { int nextSrcLineIdx= i+1; while (nextSrcLineIdx < srcLines.length) { IAsmInstruction[] nextInstrs= srcLines[nextSrcLineIdx].getInstructions(); if (nextInstrs.length > 0) { instrLength= nextInstrs[0].getAdress().distanceTo(instruction.getAdress()).abs(); break; } ++nextSrcLineIdx; } if (nextSrcLineIdx >= srcLines.length) { break; } } else { // if (instructions.length == 1) { // if (p.fAddressLength.compareTo(BigInteger.valueOf(8)) <= 0) { // instrLength= p.fAddressLength; // } // } } if (instrLength == null) { // cannot determine length of last instruction break; } final String opCode; // insert function name+offset instead of opcode bytes if (functionName != null && functionName.length() > 0) { opCode= functionName + '+' + instruction.getOffset(); } else { opCode= ""; //$NON-NLS-1$ } if (!showDisassembly && hasSource) { p = document.insertDisassemblyLine(p, address, instrLength.intValue(), opCode, "", compilationPath, lineNumber); //$NON-NLS-1$ } else { p = document.insertDisassemblyLine(p, address, instrLength.intValue(), opCode, instruction.getInstructionText(), compilationPath, lineNumber); //$NON-NLS-1 } insertedStartAddress= insertedStartAddress || address.compareTo(startAddress) == 0; if (p == null && insertedStartAddress) { break; } } } } catch (BadLocationException e) { // should not happen DisassemblyUtils.internalError(e); } finally { fCallback.setUpdatePending(false); if (insertedStartAddress) { fCallback.updateInvalidSource(); fCallback.unlockScroller(); fCallback.doPending(); fCallback.updateVisibleArea(); } else { fCallback.unlockScroller(); } } return insertedStartAddress; } /* (non-Javadoc) * @see org.eclipse.debug.core.IDebugEventSetListener#handleDebugEvents(org.eclipse.debug.core.DebugEvent[]) */ public void handleDebugEvents(DebugEvent[] events) { for (DebugEvent event : events) { if (event.getKind() == DebugEvent.TERMINATE) { Object eventSource = event.getSource(); if ((eventSource instanceof CDebugTarget) && (getSessionId((CDebugTarget)eventSource).equals(fCdiSessionId))) { fCallback.handleTargetEnded(); return; } } } } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#dispose() */ public void dispose() { DebugPlugin.getDefault().removeDebugEventListener(this); } /* (non-Javadoc) * @see org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend#evaluateExpression(java.lang.String) */ public String evaluateExpression(String expression) { // This is called to service text hovering. We either resolve the // expression or we don't. No error reporting needed. if (fTargetFrameContext != null) { try { // This logic was lifted from CMemoryBlockRetrievalExtension.getExtendedMemoryBlock(String, Object) CStackFrame cstackFrame = (CStackFrame)fTargetFrameContext; ICDIExpression cdiExpression = cstackFrame.getCDITarget().createExpression(expression); CExpression cdtExpression = new CExpression(cstackFrame, cdiExpression, null); IValue value = cdtExpression.getValue(); if (value instanceof ICValue) { ICType type = ((ICValue)value).getType(); if (type != null) { return cstackFrame.evaluateExpressionToString(cdtExpression.getExpressionString()); } } } catch (Exception exc) { } } return ""; //$NON-NLS-1$ } }