blob: 7e02cae18aa6079b0c137d9fce918204b616b7c4 [file] [log] [blame]
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;
}
}
}