package org.eclipse.cdt.debug.dap; import static org.eclipse.cdt.debug.internal.ui.disassembly.dsf.DisassemblyUtils.DEBUG; import java.math.BigInteger; import java.text.MessageFormat; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.eclipse.cdt.debug.dap.CDTDebugProtocol.CDTDisassembleArguments; import org.eclipse.cdt.debug.internal.ui.disassembly.dsf.AbstractDisassemblyBackend; import org.eclipse.cdt.debug.internal.ui.disassembly.dsf.AddressRangePosition; import org.eclipse.cdt.debug.internal.ui.disassembly.dsf.DisassemblyUtils; import org.eclipse.cdt.debug.internal.ui.disassembly.dsf.ErrorPosition; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.model.ISourceLocator; import org.eclipse.debug.core.model.IVariable; import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Position; import org.eclipse.lsp4e.debug.debugmodel.DSPDebugTarget; import org.eclipse.lsp4e.debug.debugmodel.DSPStackFrame; import org.eclipse.lsp4j.debug.DisassembleResponse; import org.eclipse.lsp4j.debug.DisassembledInstruction; import org.eclipse.lsp4j.debug.Source; @SuppressWarnings("restriction") public class DapDisassemblyBackend extends AbstractDisassemblyBackend { private DSPStackFrame dspStackFrame; @Override public boolean supportsDebugContext(IAdaptable context) { return context instanceof DSPStackFrame; } @Override public boolean hasDebugContext() { return dspStackFrame != null; } @Override public SetDebugContextResult setDebugContext(IAdaptable context) { assert context instanceof DSPStackFrame; SetDebugContextResult setDebugContextResult = new SetDebugContextResult(); if (context instanceof DSPStackFrame) { DSPStackFrame newDspStackFrame = (DSPStackFrame) context; setDebugContextResult.contextChanged = !newDspStackFrame.equals(dspStackFrame); dspStackFrame = newDspStackFrame; // sessionId should have been boolean and been hasSessionId, only null/non-null is relevant setDebugContextResult.sessionId = ""; //$NON-NLS-1$ if (!setDebugContextResult.contextChanged) { fCallback.gotoFrameIfActive(dspStackFrame.getDepth()); } } else { setDebugContextResult.contextChanged = true; setDebugContextResult.sessionId = null; } return setDebugContextResult; } @Override public void clearDebugContext() { dspStackFrame = null; } @Override public void retrieveFrameAddress(int frame) { fCallback.setUpdatePending(false); fCallback.asyncExec(() -> { int addressBits = dspStackFrame.getFrameInstructionAddressBits(); BigInteger address = dspStackFrame.getFrameInstructionAddress(); if (addressBits != fCallback.getAddressSize()) { fCallback.addressSizeChanged(addressBits); } if (frame == 0) { fCallback.updatePC(address); } else { fCallback.gotoFrame(frame, address); } }); } @Override public int getFrameLevel() { return dspStackFrame.getDepth(); } @Override public boolean isSuspended() { return dspStackFrame.getDebugTarget().isSuspended(); } @Override public boolean hasFrameContext() { return false; } @Override public String getFrameFile() { return null; } @Override public int getFrameLine() { return 0; } /** * Retrieves disassembly based on either (a) start and end address range, or * (b) file, line number, and line count. If the caller specifies both sets * of information, the implementation should honor (b) and ignore (a). */ @Override public void retrieveDisassembly(BigInteger startAddress, BigInteger endAddress, String file, int lineNumber, int lines, boolean mixed, boolean showSymbols, boolean showDisassembly, int linesHint) { CDTDisassembleArguments args = new CDTDisassembleArguments(); args.setMemoryReference("0x" + startAddress.toString(16)); //$NON-NLS-1$ args.setInstructionCount((long) lines); args.setEndMemoryReference("1+0x" + endAddress.toString(16)); //$NON-NLS-1$ CompletableFuture future = dspStackFrame.getDebugProtocolServer().disassemble(args); future.thenAcceptAsync(res -> { fCallback.asyncExec(() -> insertDisassembly(startAddress, endAddress, res, showSymbols, showDisassembly)); }); } /** * @param startAddress * an address the caller is hoping will be covered by this * insertion. I.e., [mixedInstructions] 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 * as long as any instruction was inserted * @param endAddress * cut-off address. Any elements in [mixedInstructions] that * extend beyond this address are ignored. * @param mixedInstructions * @param showSymbols * @param showDisassembly * @return whether [startAddress] was inserted */ private void insertDisassembly(BigInteger startAddress, BigInteger endAddress, DisassembleResponse response, boolean showSymbols, boolean showDisassembly) { if (!fCallback.hasViewer() || dspStackFrame == null) { if (DEBUG) { System.out.println( MessageFormat.format("insertDisassembly ignored at {0} : missing context: [dspStackFrame={1}]", //$NON-NLS-1$ DisassemblyUtils.getAddressText(startAddress), dspStackFrame)); } if (dspStackFrame == null) { fCallback.setUpdatePending(false); } return; } if (DEBUG) System.out.println("insertDisassembly " + DisassemblyUtils.getAddressText(startAddress)); //$NON-NLS-1$ boolean updatePending = fCallback.getUpdatePending(); assert updatePending; if (!updatePending) { // safe-guard in case something weird is going on return; } boolean insertedAnyAddress = false; try { fCallback.lockScroller(); AddressRangePosition p = null; Source location = null; DisassembledInstruction[] instructions = response.getInstructions(); for (int i = 0; i < instructions.length; ++i) { DisassembledInstruction instruction = instructions[i]; if (instruction.getLocation() != null) { location = instruction.getLocation(); } assert location != null; String file = null; if (location != null) { file = location.getPath(); } Long line = instruction.getLine(); int lineNumber = (line == null ? 0 : line.intValue()) - 1; BigInteger address = getAddress(instruction); 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; fCallback.getDocument().addInvalidAddressRange(p); } else if (p == null || address.compareTo(endAddress) > 0) { if (DEBUG) System.out.println("Excess disassembly lines at " + DisassemblyUtils.getAddressText(address)); //$NON-NLS-1$ return; } else if (p.fValid) { if (DEBUG) System.out.println("Excess disassembly lines at " + DisassemblyUtils.getAddressText(address)); //$NON-NLS-1$ if (!p.fAddressOffset.equals(address)) { // override probably unaligned disassembly p.fValid = false; fCallback.getDocument().addInvalidAddressRange(p); } else { continue; } } boolean hasSource = false; if (file != null && lineNumber >= 0) { p = fCallback.insertSource(p, address, file, lineNumber); hasSource = fCallback.getStorageForFile(file) != null; } // insert symbol label String functionName; int offset; String symbol = instruction.getSymbol(); if (symbol != null) { String[] split = symbol.split("\\+", 2); //$NON-NLS-1$ functionName = split[0]; if (split.length > 1) { try { offset = Integer.parseInt(split[1]); } catch (NumberFormatException e) { offset = 0; } } else { offset = 0; } } else { functionName = null; offset = 0; } if (functionName != null && !functionName.isEmpty() && offset == 0) { p = fCallback.getDocument().insertLabel(p, address, functionName, showSymbols && (!hasSource || showDisassembly)); } // determine instruction byte length BigInteger instrLength = null; if (i < instructions.length - 1) { instrLength = getAddress(instructions[i + 1]).subtract(address).abs(); } else { // cannot determine length of last instruction break; } String funcOffset = instruction.getSymbol(); if (funcOffset == null) { funcOffset = ""; //$NON-NLS-1$ } BigInteger opCodes = null; if (instruction.getInstructionBytes() != null) { opCodes = new BigInteger(instruction.getInstructionBytes().replace(" ", ""), 16); //$NON-NLS-1$//$NON-NLS-2$ } p = fCallback.getDocument().insertDisassemblyLine(p, address, instrLength.intValue(), funcOffset, opCodes, instruction.getInstruction(), file, lineNumber); if (p == null) { break; } insertedAnyAddress = true; } } catch (BadLocationException e) { // should not happen DisassemblyUtils.internalError(e); } finally { fCallback.setUpdatePending(false); if (insertedAnyAddress) { fCallback.updateInvalidSource(); fCallback.unlockScroller(); fCallback.doPending(); fCallback.updateVisibleArea(); } else { fCallback.unlockScroller(); } } } private BigInteger getAddress(DisassembledInstruction instruction) { if (instruction.getAddress().startsWith("0x")) { //$NON-NLS-1$ return new BigInteger(instruction.getAddress().substring(2), 16); } else { return new BigInteger(instruction.getAddress(), 10); } } @Override public Object insertSource(Position pos, BigInteger address, String file, int lineNumber) { ISourceLookupDirector lookupDirector = getSourceLookupDirector(); return lookupDirector.getSourceElement(file); } private ISourceLookupDirector getSourceLookupDirector() { if (dspStackFrame == null) { return null; } DSPDebugTarget debugTarget = dspStackFrame.getDebugTarget(); if (debugTarget == null) { return null; } ILaunch launch = debugTarget.getLaunch(); if (launch == null) { return null; } ISourceLocator sourceLocator = launch.getSourceLocator(); if (sourceLocator instanceof ISourceLookupDirector) { ISourceLookupDirector lookupDirector = (ISourceLookupDirector) sourceLocator; return lookupDirector; } return null; } @Override public void gotoSymbol(String symbol) { String.class.getClass(); } @Override public void retrieveDisassembly(String file, int lines, BigInteger endAddress, boolean mixed, boolean showSymbols, boolean showDisassembly) { String.class.getClass(); } @Override public String evaluateExpression(String expression) { CompletableFuture evaluate = dspStackFrame.evaluate(expression); try { IVariable iVariable = evaluate.get(); return iVariable.getValue().getValueString(); } catch (InterruptedException e) { return null; } catch (ExecutionException | DebugException | NumberFormatException e) { return null; } } @Override public void dispose() { String.class.getClass(); } @Override protected void handleError(IStatus status) { Activator.log(status); } @Override public BigInteger evaluateAddressExpression(String expression, boolean suppressError) { CompletableFuture evaluate = dspStackFrame.evaluate(expression); try { IVariable variable = evaluate.get(); return DisassemblyUtils.decodeAddress(variable.getValue().getValueString()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; } catch (ExecutionException | DebugException | NumberFormatException e) { if (!suppressError) { DapDisassemblyBackend.this.handleError(new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, "Expression does not evaluate to an address (" //$NON-NLS-1$ + e.getMessage() + ")", //$NON-NLS-1$ null)); } return null; } } }