1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-04-22 06:02:11 +02:00

Bug 542488: Disassembly support

Change-Id: I8a280fba5147ed3ebd8ecace8b943d3e5350dacf
This commit is contained in:
Jonah Graham 2019-10-31 15:31:03 -04:00
parent 6fa96ca549
commit fe13a037b7
11 changed files with 505 additions and 37 deletions

View file

@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: org.eclipse.cdt.debug.dap;singleton:=true
Bundle-Version: 1.0.100.qualifier
Bundle-Version: 1.1.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Require-Bundle: org.apache.commons.io,
org.eclipse.core.runtime,
@ -20,7 +20,7 @@ Require-Bundle: org.apache.commons.io,
com.google.gson;bundle-version="2.8.2",
org.eclipse.lsp4j.jsonrpc,
org.eclipse.cdt.launch,
org.eclipse.lsp4e.debug,
org.eclipse.lsp4e.debug;bundle-version="0.11.0",
org.eclipse.debug.core,
org.eclipse.debug.ui,
org.eclipse.cdt.debug.core,
@ -28,7 +28,8 @@ Require-Bundle: org.apache.commons.io,
org.eclipse.lsp4j.jsonrpc.debug,
com.google.guava,
org.eclipse.xtext.xbase.lib,
org.eclipse.cdt.dsf.gdb;bundle-version="5.7.200"
org.eclipse.cdt.dsf.gdb;bundle-version="5.7.200",
org.eclipse.cdt.debug.ui
Bundle-Vendor: %Bundle-Vendor
Export-Package: org.eclipse.cdt.debug.dap
Bundle-Activator: org.eclipse.cdt.debug.dap.Activator

View file

@ -12,7 +12,7 @@
<relativePath>../../pom.xml</relativePath>
</parent>
<version>1.0.100-SNAPSHOT</version>
<version>1.1.0-SNAPSHOT</version>
<artifactId>org.eclipse.cdt.debug.dap</artifactId>
<packaging>eclipse-plugin</packaging>

View file

@ -12,7 +12,9 @@
package org.eclipse.cdt.debug.dap;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.lsp4e.debug.debugmodel.DSPDebugElement;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
@ -29,6 +31,8 @@ public class Activator extends AbstractUIPlugin {
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
Platform.getAdapterManager().registerAdapters(new DapDisassemblyBackendFactory(), DSPDebugElement.class);
}
@Override

View file

@ -10,6 +10,11 @@
*******************************************************************************/
package org.eclipse.cdt.debug.dap;
import java.util.Objects;
import org.eclipse.lsp4j.debug.DisassembleArguments;
import org.eclipse.lsp4j.debug.services.IDebugProtocolServer;
import org.eclipse.lsp4j.debug.util.Preconditions;
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
@ -32,11 +37,8 @@ public class CDTDebugProtocol {
return this.body;
}
public void setBody(@NonNull final MemoryContents address) {
if (address == null) {
throw new IllegalArgumentException("Property must not be null: body"); //$NON-NLS-1$
}
this.body = address;
public void setBody(@NonNull final MemoryContents body) {
this.body = Preconditions.checkNotNull(body, "body"); //$NON-NLS-1$
}
@Override
@ -93,11 +95,8 @@ public class CDTDebugProtocol {
return this.data;
}
public void setData(@NonNull final String address) {
if (address == null) {
throw new IllegalArgumentException("Property must not be null: data"); //$NON-NLS-1$
}
this.data = address;
public void setData(@NonNull final String data) {
this.data = Preconditions.checkNotNull(data, "data"); //$NON-NLS-1$
}
@Pure
@ -107,10 +106,7 @@ public class CDTDebugProtocol {
}
public void setAddress(@NonNull final String address) {
if (address == null) {
throw new IllegalArgumentException("Property must not be null: address"); //$NON-NLS-1$
}
this.address = address;
this.address = Preconditions.checkNotNull(address, "address"); //$NON-NLS-1$
}
@Override
@ -177,10 +173,7 @@ public class CDTDebugProtocol {
}
public void setAddress(@NonNull final String address) {
if (address == null) {
throw new IllegalArgumentException("Property must not be null: address"); //$NON-NLS-1$
}
this.address = address;
this.address = Preconditions.checkNotNull(address, "address"); //$NON-NLS-1$
}
@Pure
@ -190,10 +183,7 @@ public class CDTDebugProtocol {
}
public void setLength(@NonNull final Long length) {
if (length == null) {
throw new IllegalArgumentException("Property must not be null: length"); //$NON-NLS-1$
}
this.length = length;
this.length = Preconditions.checkNotNull(length, "length"); //$NON-NLS-1$
}
@Pure
@ -201,11 +191,8 @@ public class CDTDebugProtocol {
return this.offset;
}
public void setOffset(@NonNull final Long length) {
if (length == null) {
throw new IllegalArgumentException("Property must not be null: offset"); //$NON-NLS-1$
}
this.offset = length;
public void setOffset(final Long offset) {
this.offset = offset;
}
@Override
@ -256,6 +243,60 @@ public class CDTDebugProtocol {
return false;
return true;
}
}
/**
* An extension to standard {@link DisassembleArguments} that can
* be passed to {@link IDebugProtocolServer#disassemble(DisassembleArguments)}
*
* When endMemoryReference is provided, the disassemble command will return
* the minimum number of instructions to get to the end address or number
* of lines (instructionCount), whichever is smaller.
*/
public static class CDTDisassembleArguments extends DisassembleArguments {
private String endMemoryReference;
@Pure
public String getEndMemoryReference() {
return this.endMemoryReference;
}
public void setEndMemoryReference(final String endMemoryReference) {
this.endMemoryReference = endMemoryReference;
}
@Override
@Pure
public String toString() {
ToStringBuilder b = new ToStringBuilder(this);
b.add("memoryReference", this.getMemoryReference()); //$NON-NLS-1$
b.add("offset", this.getOffset()); //$NON-NLS-1$
b.add("instructionOffset", this.getInstructionOffset()); //$NON-NLS-1$
b.add("instructionCount", this.getInstructionCount()); //$NON-NLS-1$
b.add("resolveSymbols", this.getResolveSymbols()); //$NON-NLS-1$
b.add("endMemoryReference", this.endMemoryReference); //$NON-NLS-1$
return b.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(endMemoryReference);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
CDTDisassembleArguments other = (CDTDisassembleArguments) obj;
return Objects.equals(endMemoryReference, other.endMemoryReference);
}
}
}

View file

@ -94,5 +94,4 @@ public class DapDebugTarget extends DSPDebugTarget implements IMemoryBlockRetrie
}
return new MemoryBlock(this, expression, bigBaseAddress, context);
}
}

View file

@ -0,0 +1,382 @@
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<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();
}
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<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;
}
}
}

View file

@ -0,0 +1,29 @@
package org.eclipse.cdt.debug.dap;
import org.eclipse.cdt.debug.internal.ui.disassembly.dsf.IDisassemblyBackend;
import org.eclipse.core.runtime.IAdapterFactory;
import org.eclipse.lsp4e.debug.debugmodel.DSPStackFrame;
public class DapDisassemblyBackendFactory implements IAdapterFactory {
private static final Class<?>[] ADAPTERS = { IDisassemblyBackend.class };
@Override
@SuppressWarnings("unchecked")
public <T> T getAdapter(Object adaptableObject, Class<T> adapterType) {
if (IDisassemblyBackend.class.equals(adapterType)) {
if (adaptableObject instanceof DSPStackFrame) {
DSPStackFrame dspDebugElement = (DSPStackFrame) adaptableObject;
if (dspDebugElement.getDebugTarget() instanceof DapDebugTarget) {
return (T) new DapDisassemblyBackend();
}
}
}
return null;
}
@Override
public Class<?>[] getAdapterList() {
return ADAPTERS;
}
}

View file

@ -26,5 +26,4 @@ public interface ICDTDebugProtocolServer extends IDebugProtocolServer {
default CompletableFuture<MemoryContents> memory(MemoryRequestArguments args) {
throw new UnsupportedOperationException();
}
}

View file

@ -14,7 +14,7 @@ Export-Package: org.eclipse.cdt.debug.internal.ui;x-friends:="org.eclipse.cdt.ds
org.eclipse.cdt.debug.internal.ui.dialogfields;x-friends:="org.eclipse.cdt.dsf.gdb.ui",
org.eclipse.cdt.debug.internal.ui.dialogs;x-internal:=true,
org.eclipse.cdt.debug.internal.ui.disassembly.commands;x-internal:=true,
org.eclipse.cdt.debug.internal.ui.disassembly.dsf;x-friends:="org.eclipse.cdt.dsf.ui",
org.eclipse.cdt.debug.internal.ui.disassembly.dsf;x-friends:="org.eclipse.cdt.dsf.ui,org.eclipse.cdt.debug.dap",
org.eclipse.cdt.debug.internal.ui.disassembly.editor;x-internal:=true,
org.eclipse.cdt.debug.internal.ui.disassembly.viewer;x-internal:=true,
org.eclipse.cdt.debug.internal.ui.editors;x-internal:=true,

View file

@ -50,6 +50,19 @@ public class DisassemblyUtils {
}
public static BigInteger decodeAddress(String string) {
// Handle case where address has type info, such as:
// {int (const char *, ...)} 0x7ffff7a48e80 <__printf>
if (string.startsWith("{")) { //$NON-NLS-1$
int indexOf = string.indexOf('}');
if (indexOf >= 0 && indexOf < string.length()) {
string = string.substring(indexOf + 1);
}
indexOf = string.indexOf('<');
if (indexOf >= 0) {
string = string.substring(0, indexOf);
}
string = string.trim();
}
if (string.startsWith("0x")) { //$NON-NLS-1$
return new BigInteger(string.substring(2), 16);
}

View file

@ -92,7 +92,7 @@
<location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit">
<unit id="org.eclipse.lsp4e" version="0.0.0"/>
<unit id="org.eclipse.lsp4e.debug" version="0.0.0"/>
<repository location="https://download.eclipse.org/lsp4e/releases/0.11.0/"/>
<repository location="http://download.eclipse.org/lsp4e/snapshots/"/>
</location>
</locations>
<targetJRE path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>