| 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(lines); |
| args.setEndMemoryReference("1+0x" + endAddress.toString(16)); //$NON-NLS-1$ |
| CompletableFuture<DisassembleResponse> 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(); |
| } |
| Integer 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<IVariable> 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<IVariable> 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; |
| } |
| |
| } |
| } |