mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-1539: Polish the DebuggerGoToDialog. Allow labels and plain addresses.
This commit is contained in:
parent
738e662e82
commit
b51d423d4b
27 changed files with 759 additions and 103 deletions
|
@ -134,10 +134,12 @@
|
|||
<H3><A name="go_to"></A>Go To (G)</H3>
|
||||
|
||||
<P>This action is available whenever a trace is active in the listing. It prompts the user for
|
||||
an address, which can be expressed in Sleigh, then attempts to navigate to it. The expression
|
||||
is evaluated in the context of the current thread, frame, and point in time. If the current
|
||||
trace is live and at the present, the target may be queried to retrieve any machine state
|
||||
required to evaluate the expression.</P>
|
||||
an address, which can be expressed in simple notation or <B>Sleigh</B>, then attempts to
|
||||
navigate to it. The expression is evaluated in the context of the current thread, frame, and
|
||||
point in time. If the current trace is live and at the present, the target may be queried to
|
||||
retrieve any machine state required to evaluate the expression. The expression may be in terms
|
||||
of labels, registers, and constants. Labels may come from the current trace or a program mapped
|
||||
into the trace. Ambiguities are resolved arbitrarily.</P>
|
||||
|
||||
<TABLE width="100%">
|
||||
<TBODY>
|
||||
|
@ -147,6 +149,22 @@
|
|||
</TBODY>
|
||||
</TABLE>
|
||||
|
||||
<P>Some examples:</P>
|
||||
|
||||
<UL>
|
||||
<LI><CODE>00401234</CODE> — A constant address in simple notation</LI>
|
||||
|
||||
<LI><CODE>0x00401234:8</CODE> — A constant address in Sleigh notation</LI>
|
||||
|
||||
<LI><CODE>main + 10</CODE> — 10 bytes past the address of "main"</LI>
|
||||
|
||||
<LI><CODE>RAX</CODE> — The address in RAX</LI>
|
||||
|
||||
<LI><CODE>RSP + 8</CODE> — The address of stack offset 8</LI>
|
||||
|
||||
<LI><CODE>*:8 (RSP+8)</CODE> — The address pointed to by stack offset 8</LI>
|
||||
</UL>
|
||||
|
||||
<H3><A name="auto_sync_cursor_static"></A>Auto-Sync Cursor with Static Listing</H3>
|
||||
|
||||
<P>This action is always available, but only on the primary dynamic listing. It configures
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 7 KiB |
|
@ -78,11 +78,9 @@
|
|||
|
||||
<H3><A name="go_to"></A>Go To (G)</H3>
|
||||
|
||||
<P>This action is available whenever a trace is active in the window. It prompts the user for
|
||||
an address, which can be expressed in Sleigh, then attempts to navigate to it. The expression
|
||||
is evaluated in the context of the current thread, frame, and point in time. If the current
|
||||
trace is live and at the present, the target may be queried to retrieve any machine state
|
||||
required to evaluate the expression.</P>
|
||||
<P>This action is equivalent to the same action in the <A href=
|
||||
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html#go_to">Dynamic Listing</A>
|
||||
window.</P>
|
||||
|
||||
<TABLE width="100%">
|
||||
<TBODY>
|
||||
|
|
|
@ -42,6 +42,10 @@
|
|||
constant 0x7fff0004 is a known issue. Just use the target's pointer size in bytes — 8
|
||||
in the example.</LI>
|
||||
|
||||
<LI><CODE>*:4 my_global</CODE>: Display 4 bytes starting at the label "my_global", e.g., to
|
||||
read the <CODE>int</CODE> value there. The label may be in the trace or in a program mapped
|
||||
to the trace. Ambiguities are resolved arbitrarily.</LI>
|
||||
|
||||
<LI><CODE>*:8 RSP</CODE>: Display 8 bytes of [ram] starting at the offset given by register
|
||||
RSP, e.g., to read a <CODE>long</CODE> on the stack.</LI>
|
||||
|
||||
|
|
|
@ -16,65 +16,69 @@
|
|||
package ghidra.app.plugin.core.debug.gui.action;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToTrait.GoToResult;
|
||||
import ghidra.app.plugin.core.debug.gui.breakpoint.AbstractDebuggerSleighInputDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.util.MessageType;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.framework.plugintool.util.PluginUtils;
|
||||
import ghidra.pcode.exec.SleighUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.util.*;
|
||||
|
||||
public class DebuggerGoToDialog extends DialogComponentProvider {
|
||||
public class DebuggerGoToDialog extends AbstractDebuggerSleighInputDialog {
|
||||
private static final String TEXT = """
|
||||
<html>
|
||||
<body width="400px">
|
||||
<p>
|
||||
Enter an address or Sleigh expression. Press <b>F1</b> for help and examples.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
|
||||
private final DebuggerGoToTrait trait;
|
||||
private final DefaultComboBoxModel<String> modelSpaces;
|
||||
|
||||
final JTextField textExpression;
|
||||
final JComboBox<String> comboSpaces;
|
||||
|
||||
public DebuggerGoToDialog(DebuggerGoToTrait trait) {
|
||||
super("Go To", true, true, true, false);
|
||||
super("Go To", TEXT);
|
||||
setHelpLocation(new HelpLocation(
|
||||
PluginUtils.getPluginNameFromClass(DebuggerListingPlugin.class),
|
||||
DebuggerResources.GoToAction.HELP_ANCHOR));
|
||||
this.trait = trait;
|
||||
|
||||
textExpression = new JTextField();
|
||||
modelSpaces = new DefaultComboBoxModel<>();
|
||||
comboSpaces = new JComboBox<>(modelSpaces);
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
panel.setBorder(new EmptyBorder(16, 16, 16, 16));
|
||||
JLabel help = new JLabel(
|
||||
"<html>Enter any sleigh expression to evaluate against the current thread.<br/>" +
|
||||
"Note that constants and memory derefs must have a resolved size.<br/>" +
|
||||
"Examples:<br/>" +
|
||||
"<ul>" +
|
||||
"<li>To go to a constant address: <code>0x00401234:4</code></li>" +
|
||||
"<li>To go to the address in a register: <code>RAX</code></li>" +
|
||||
"<li>To dereference the pointer at an address in a register: <code>*:8 RAX</code></li>" +
|
||||
"</ul></html>");
|
||||
help.getMaximumSize().width = 400;
|
||||
panel.add(help, BorderLayout.NORTH);
|
||||
Box box = Box.createHorizontalBox();
|
||||
box.setBorder(new EmptyBorder(16, 0, 0, 0));
|
||||
panel.add(box);
|
||||
Box hbox = Box.createHorizontalBox();
|
||||
hbox.add(comboSpaces);
|
||||
hbox.add(new JLabel(":"));
|
||||
panel.add(hbox, BorderLayout.WEST);
|
||||
|
||||
box.add(new JLabel("*["));
|
||||
box.add(comboSpaces);
|
||||
box.add(new JLabel("]"));
|
||||
box.add(textExpression);
|
||||
setFocusComponent(textInput);
|
||||
|
||||
addWorkPanel(panel);
|
||||
setFocusComponent(textExpression);
|
||||
|
||||
addOKButton();
|
||||
addCancelButton();
|
||||
textInput.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
okCallback();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void populateSpaces(SleighLanguage language) {
|
||||
|
@ -94,19 +98,37 @@ public class DebuggerGoToDialog extends DialogComponentProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validate() {
|
||||
TracePlatform platform = trait.current.getPlatform();
|
||||
if (platform == null) {
|
||||
throw new AssertionError("No current trace platform");
|
||||
}
|
||||
Address address = platform.getAddressFactory().getAddress(getInput());
|
||||
if (address != null) {
|
||||
return;
|
||||
}
|
||||
SleighUtils.parseSleighExpression(getInput());
|
||||
}
|
||||
|
||||
@Override // public for tests
|
||||
public void okCallback() {
|
||||
CompletableFuture<Boolean> future;
|
||||
validateAndMarkup();
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
CompletableFuture<GoToResult> future;
|
||||
try {
|
||||
future = trait.goToSleigh((String) comboSpaces.getSelectedItem(),
|
||||
textExpression.getText());
|
||||
future = trait.goTo((String) comboSpaces.getSelectedItem(), getInput());
|
||||
}
|
||||
catch (Throwable t) {
|
||||
future = CompletableFuture.failedFuture(t);
|
||||
}
|
||||
future.thenAccept(success -> {
|
||||
if (!success) {
|
||||
setStatusText("Address not in trace", MessageType.ERROR, true);
|
||||
future.thenAccept(result -> {
|
||||
if (!result.success()) {
|
||||
setStatusText("<html>Address <code>" + result.address() + "</code> not in trace", MessageType.ERROR,
|
||||
true);
|
||||
}
|
||||
else {
|
||||
close();
|
||||
|
@ -124,12 +146,15 @@ public class DebuggerGoToDialog extends DialogComponentProvider {
|
|||
close();
|
||||
}
|
||||
|
||||
public void show(SleighLanguage language) {
|
||||
public void show(SleighLanguage language, GoToInput defaultInput) {
|
||||
populateSpaces(language);
|
||||
trait.tool.showDialog(this);
|
||||
if (language.getAddressFactory().getAddressSpace(defaultInput.space()) != null) {
|
||||
comboSpaces.setSelectedItem(defaultInput.space());
|
||||
}
|
||||
prompt(trait.tool, defaultInput.offset());
|
||||
}
|
||||
|
||||
public void setExpression(String expression) {
|
||||
textExpression.setText(expression);
|
||||
public void setOffset(String offset) {
|
||||
textInput.setText(offset);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,12 +28,17 @@ import ghidra.framework.plugintool.Plugin;
|
|||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
|
||||
public abstract class DebuggerGoToTrait {
|
||||
/**
|
||||
* @see DebuggerGoToTrait#goTo(String, String)
|
||||
*/
|
||||
public record GoToResult(Address address, Boolean success) {
|
||||
}
|
||||
|
||||
protected DockingAction action;
|
||||
|
||||
protected final PluginTool tool;
|
||||
|
@ -48,6 +53,8 @@ public abstract class DebuggerGoToTrait {
|
|||
this.provider = provider;
|
||||
}
|
||||
|
||||
protected abstract GoToInput getDefaultInput();
|
||||
|
||||
protected abstract boolean goToAddress(Address address);
|
||||
|
||||
public void goToCoordinates(DebuggerCoordinates coordinates) {
|
||||
|
@ -66,25 +73,61 @@ public abstract class DebuggerGoToTrait {
|
|||
private void activatedGoTo(ActionContext context) {
|
||||
DebuggerGoToDialog goToDialog = new DebuggerGoToDialog(this);
|
||||
TracePlatform platform = current.getPlatform();
|
||||
goToDialog.show((SleighLanguage) platform.getLanguage());
|
||||
goToDialog.show((SleighLanguage) platform.getLanguage(), getDefaultInput());
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> goToSleigh(String spaceName, String expression) {
|
||||
/**
|
||||
* Go to the given address
|
||||
*
|
||||
* <p>
|
||||
* If parsing or evaluation fails, an exception is thrown, or the future completes
|
||||
* exceptionally. If the address is successfully computed, then a result will be returned. The
|
||||
* {@link GoToResult#address()} method gives the parsed or computed address. The
|
||||
* {@link GoToResult#success()} method indicates whether the cursor was successfully set to that
|
||||
* address.
|
||||
*
|
||||
* @param spaceName the name of the address space
|
||||
* @param offset a simple offset or Sleigh expression
|
||||
* @return the result
|
||||
*/
|
||||
public CompletableFuture<GoToResult> goTo(String spaceName, String offset) {
|
||||
TracePlatform platform = current.getPlatform();
|
||||
Language language = platform.getLanguage();
|
||||
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceName);
|
||||
if (space == null) {
|
||||
throw new IllegalArgumentException("No such address space: " + spaceName);
|
||||
}
|
||||
try {
|
||||
Address address = space.getAddress(offset);
|
||||
if (address == null) {
|
||||
address = language.getAddressFactory().getAddress(offset);
|
||||
}
|
||||
if (address != null) {
|
||||
return CompletableFuture
|
||||
.completedFuture(new GoToResult(address, goToAddress(address)));
|
||||
}
|
||||
}
|
||||
catch (AddressFormatException e) {
|
||||
// Fall-through to try Sleigh
|
||||
}
|
||||
return goToSleigh(spaceName, offset);
|
||||
}
|
||||
|
||||
protected CompletableFuture<GoToResult> goToSleigh(String spaceName, String expression) {
|
||||
TracePlatform platform = current.getPlatform();
|
||||
Language language = platform.getLanguage();
|
||||
if (!(language instanceof SleighLanguage)) {
|
||||
throw new IllegalStateException("Current trace does not use Sleigh");
|
||||
}
|
||||
SleighLanguage slang = (SleighLanguage) language;
|
||||
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceName);
|
||||
if (space == null) {
|
||||
throw new IllegalArgumentException("No such address space: " + spaceName);
|
||||
}
|
||||
PcodeExpression expr = SleighProgramCompiler.compileExpression(slang, expression);
|
||||
PcodeExpression expr = DebuggerPcodeUtils.compileExpression(tool, current, expression);
|
||||
return goToSleigh(platform, space, expr);
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> goToSleigh(TracePlatform platform, AddressSpace space,
|
||||
protected CompletableFuture<GoToResult> goToSleigh(TracePlatform platform, AddressSpace space,
|
||||
PcodeExpression expression) {
|
||||
PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool, current);
|
||||
CompletableFuture<byte[]> result =
|
||||
|
@ -92,7 +135,7 @@ public abstract class DebuggerGoToTrait {
|
|||
return result.thenApplyAsync(offset -> {
|
||||
Address address = space.getAddress(
|
||||
Utils.bytesToLong(offset, offset.length, expression.getLanguage().isBigEndian()));
|
||||
return goToAddress(platform.mapGuestToHost(address));
|
||||
return new GoToResult(address, goToAddress(platform.mapGuestToHost(address)));
|
||||
}, AsyncUtils.SWING_EXECUTOR);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -315,6 +315,13 @@ public class DebuggerTrackLocationTrait {
|
|||
action.setCurrentActionStateByUserData(spec);
|
||||
}
|
||||
|
||||
public GoToInput getDefaultGoToInput(ProgramLocation loc) {
|
||||
if (tracker == null) {
|
||||
return NoneLocationTrackingSpec.INSTANCE.getDefaultGoToInput(tool, current, loc);
|
||||
}
|
||||
return tracker.getDefaultGoToInput(tool, current, loc);
|
||||
}
|
||||
|
||||
protected void locationTracked() {
|
||||
// Listener method
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.action;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
public record GoToInput(String space, String offset) {
|
||||
public static GoToInput fromString(String string) {
|
||||
if (string.contains(":")) {
|
||||
String[] parts = string.split(":", 2);
|
||||
return new GoToInput(parts[0], parts[1]);
|
||||
}
|
||||
return new GoToInput(null, string);
|
||||
}
|
||||
|
||||
public static GoToInput fromAddress(Address address) {
|
||||
return new GoToInput(address.getAddressSpace().getName(), address.toString(false));
|
||||
}
|
||||
|
||||
public static GoToInput offsetOnly(String offset) {
|
||||
return new GoToInput(null, offset);
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import java.util.concurrent.CompletableFuture;
|
|||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
|
@ -53,6 +54,17 @@ public interface LocationTracker {
|
|||
CompletableFuture<Address> computeTraceAddress(PluginTool tool,
|
||||
DebuggerCoordinates coordinates);
|
||||
|
||||
/**
|
||||
* Get the suggested input if the user activates "Go To" while this tracker is active
|
||||
*
|
||||
* @param tool the tool containing the provider
|
||||
* @param coordinates the user's current coordinates
|
||||
* @param location the user's current location
|
||||
* @return the suggested address or Sleigh expression
|
||||
*/
|
||||
GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates,
|
||||
ProgramLocation location);
|
||||
|
||||
// TODO: Is there a way to generalize these so that other dependencies need not
|
||||
// have their own bespoke methods?
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
|||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
|
@ -69,6 +70,15 @@ public enum NoneLocationTrackingSpec implements LocationTrackingSpec, LocationTr
|
|||
return AsyncUtils.nil();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates,
|
||||
ProgramLocation location) {
|
||||
if (location == null) {
|
||||
return GoToInput.fromString("00000000");
|
||||
}
|
||||
return GoToInput.fromAddress(location.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean affectedByBytesChange(TraceAddressSpace space,
|
||||
TraceAddressSnapRange range, DebuggerCoordinates coordinates) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
|||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
|
@ -87,6 +88,17 @@ public enum PCByStackLocationTrackingSpec implements LocationTrackingSpec, Locat
|
|||
return CompletableFuture.supplyAsync(() -> doComputeTraceAddress(tool, coordinates));
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates,
|
||||
ProgramLocation location) {
|
||||
Address address = doComputeTraceAddress(tool, coordinates);
|
||||
if (address == null) {
|
||||
return NoneLocationTrackingSpec.INSTANCE.getDefaultGoToInput(tool, coordinates,
|
||||
location);
|
||||
}
|
||||
return GoToInput.fromAddress(address);
|
||||
}
|
||||
|
||||
// Note it does no good to override affectByRegChange. It must do what we'd avoid anyway.
|
||||
@Override
|
||||
public boolean affectedByStackChange(TraceStack stack, DebuggerCoordinates coordinates) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
|||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
|
@ -81,6 +82,12 @@ public enum PCLocationTrackingSpec implements LocationTrackingSpec, LocationTrac
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates,
|
||||
ProgramLocation location) {
|
||||
return BY_REG.getDefaultGoToInput(tool, coordinates, location);
|
||||
}
|
||||
|
||||
// Note it does no good to override affectByRegChange. It must do what we'd avoid anyway.
|
||||
@Override
|
||||
public boolean affectedByStackChange(TraceStack stack, DebuggerCoordinates coordinates) {
|
||||
|
|
|
@ -22,6 +22,7 @@ import ghidra.framework.plugintool.PluginTool;
|
|||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
|
@ -91,6 +92,13 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec, Loca
|
|||
return CompletableFuture.supplyAsync(() -> doComputeTraceAddress(tool, coordinates));
|
||||
}
|
||||
|
||||
@Override
|
||||
default GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates,
|
||||
ProgramLocation location) {
|
||||
Register register = computeRegister(coordinates);
|
||||
return GoToInput.offsetOnly(register.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean affectedByBytesChange(TraceAddressSpace space,
|
||||
TraceAddressSnapRange range, DebuggerCoordinates coordinates) {
|
||||
|
|
|
@ -24,15 +24,16 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
|||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||
import ghidra.app.plugin.core.debug.gui.watch.WatchRow;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
|
||||
import ghidra.pcode.exec.SleighUtils.AddressOf;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
|
||||
|
@ -44,6 +45,11 @@ public class WatchLocationTrackingSpec implements LocationTrackingSpec {
|
|||
public static final String CONFIG_PREFIX = "TRACK_WATCH_";
|
||||
|
||||
private final String expression;
|
||||
private final String label;
|
||||
|
||||
public static boolean isTrackable(WatchRow watch) {
|
||||
return SleighUtils.recoverAddressOf(null, watch.getExpression()) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a tracking specification from the given watch
|
||||
|
@ -62,6 +68,8 @@ public class WatchLocationTrackingSpec implements LocationTrackingSpec {
|
|||
*/
|
||||
public WatchLocationTrackingSpec(String expression) {
|
||||
this.expression = expression;
|
||||
AddressOf addrOf = SleighUtils.recoverAddressOf(null, expression);
|
||||
this.label = SleighUtils.generateSleighExpression(addrOf.offset());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -97,7 +105,7 @@ public class WatchLocationTrackingSpec implements LocationTrackingSpec {
|
|||
|
||||
@Override
|
||||
public String getLocationLabel() {
|
||||
return "&watch";
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,18 +132,27 @@ public class WatchLocationTrackingSpec implements LocationTrackingSpec {
|
|||
return AsyncUtils.nil();
|
||||
}
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
Language language = current.getPlatform().getLanguage();
|
||||
if (!(language instanceof SleighLanguage slang)) {
|
||||
return null;
|
||||
}
|
||||
if (compiled == null || compiled.getLanguage() != language) {
|
||||
compiled = SleighProgramCompiler.compileExpression(slang, expression);
|
||||
}
|
||||
compiled = DebuggerPcodeUtils.compileExpression(tool, current, expression);
|
||||
WatchValue value = compiled.evaluate(asyncExec);
|
||||
return value == null ? null : value.address();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates,
|
||||
ProgramLocation location) {
|
||||
TracePlatform platform = current.getPlatform();
|
||||
String defaultSpace =
|
||||
platform == null ? "ram" : platform.getLanguage().getDefaultSpace().getName();
|
||||
AddressOf addrOf = SleighUtils.recoverAddressOf(defaultSpace, expression);
|
||||
if (addrOf == null) {
|
||||
return NoneLocationTrackingSpec.INSTANCE.getDefaultGoToInput(tool, coordinates,
|
||||
location);
|
||||
}
|
||||
return new GoToInput(addrOf.space(),
|
||||
SleighUtils.generateSleighExpression(addrOf.offset()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean affectedByBytesChange(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||
DebuggerCoordinates coordinates) {
|
||||
|
|
|
@ -38,6 +38,7 @@ public class WatchLocationTrackingSpecFactory implements LocationTrackingSpecFac
|
|||
}
|
||||
return watchesService.getWatches()
|
||||
.stream()
|
||||
.filter(WatchLocationTrackingSpec::isTrackable)
|
||||
.map(WatchLocationTrackingSpec::fromWatch)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
|
|
@ -184,8 +184,16 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
|||
DebuggerListingProvider.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GoToInput getDefaultInput() {
|
||||
return trackingTrait.getDefaultGoToInput(getLocation());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean goToAddress(Address address) {
|
||||
if (syncTrait.isAutoSyncCursorWithStaticListing()) {
|
||||
syncTrait.doAutoSyncCursorIntoStatic(new ProgramLocation(getProgram(), address));
|
||||
}
|
||||
return getListingPanel().goTo(address);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,13 +103,18 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
|
|||
DebuggerMemoryBytesProvider.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GoToInput getDefaultInput() {
|
||||
return trackingTrait.getDefaultGoToInput(currentLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean goToAddress(Address address) {
|
||||
TraceProgramView view = current.getView();
|
||||
if (view == null) {
|
||||
return false;
|
||||
}
|
||||
return goTo(view, new ProgramLocation(view, address));
|
||||
return DebuggerMemoryBytesProvider.this.goTo(view, new ProgramLocation(view, address));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
import db.Transaction;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.services.DataTypeManagerService;
|
||||
import ghidra.app.services.DebuggerControlService;
|
||||
import ghidra.app.services.DebuggerControlService.StateEditor;
|
||||
|
@ -96,7 +95,8 @@ public class WatchRow {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
compiled = SleighProgramCompiler.compileExpression(provider.language, expression);
|
||||
compiled = DebuggerPcodeUtils.compileExpression(provider.getTool(), provider.current,
|
||||
expression);
|
||||
}
|
||||
catch (Exception e) {
|
||||
error = e;
|
||||
|
@ -106,7 +106,6 @@ public class WatchRow {
|
|||
|
||||
protected void reevaluate() {
|
||||
blank();
|
||||
SleighLanguage language = provider.language;
|
||||
PcodeExecutor<WatchValue> executor = provider.asyncWatchExecutor;
|
||||
PcodeExecutor<byte[]> prevExec = provider.prevValueExecutor;
|
||||
if (executor == null) {
|
||||
|
@ -114,9 +113,7 @@ public class WatchRow {
|
|||
return;
|
||||
}
|
||||
CompletableFuture.runAsync(() -> {
|
||||
if (compiled == null || compiled.getLanguage() != language) {
|
||||
recompile();
|
||||
}
|
||||
recompile();
|
||||
if (compiled == null) {
|
||||
provider.contextChanged();
|
||||
return;
|
||||
|
|
|
@ -22,21 +22,34 @@ import java.util.Map.Entry;
|
|||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.*;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighException;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.emu.ThreadPcodeExecutorState;
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||
import ghidra.pcode.exec.SleighProgramCompiler.ErrorCollectingPcodeParser;
|
||||
import ghidra.pcode.exec.trace.*;
|
||||
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess;
|
||||
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.pcodeCPort.slghsymbol.SleighSymbol;
|
||||
import ghidra.pcodeCPort.slghsymbol.VarnodeSymbol;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
import ghidra.program.model.symbol.SymbolType;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.sleigh.grammar.Location;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.TraceLocation;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.symbol.TraceSymbol;
|
||||
import ghidra.trace.model.symbol.TraceSymbolWithLifespan;
|
||||
import ghidra.util.NumericUtilities;
|
||||
|
||||
/**
|
||||
|
@ -45,6 +58,120 @@ import ghidra.util.NumericUtilities;
|
|||
public enum DebuggerPcodeUtils {
|
||||
;
|
||||
|
||||
/**
|
||||
* A p-code parser that can resolve labels from a trace or its mapped programs.
|
||||
*/
|
||||
public static class LabelBoundPcodeParser extends ErrorCollectingPcodeParser {
|
||||
record ProgSym(String sourceName, String nm, Address address) {
|
||||
}
|
||||
|
||||
private final DebuggerStaticMappingService mappings;
|
||||
private final DebuggerCoordinates coordinates;
|
||||
|
||||
/**
|
||||
* Construct a parser bound to the given coordinates
|
||||
*
|
||||
* @param tool the tool for the mapping service
|
||||
* @param coordinates the current coordinates for context
|
||||
*/
|
||||
public LabelBoundPcodeParser(PluginTool tool, DebuggerCoordinates coordinates) {
|
||||
super((SleighLanguage) coordinates.getPlatform().getLanguage());
|
||||
this.mappings = tool.getService(DebuggerStaticMappingService.class);
|
||||
this.coordinates = coordinates;
|
||||
}
|
||||
|
||||
protected SleighSymbol createSleighConstant(String sourceName, String nm, Address address) {
|
||||
return new VarnodeSymbol(new Location(sourceName, 0), nm, getConstantSpace(),
|
||||
address.getOffset(), address.getAddressSpace().getPointerSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SleighSymbol findSymbol(String nm) {
|
||||
SleighSymbol symbol = null;
|
||||
try {
|
||||
symbol = super.findSymbol(nm);
|
||||
}
|
||||
catch (SleighException e) {
|
||||
// leave null
|
||||
}
|
||||
if (symbol == null) {
|
||||
symbol = findUserSymbol(nm);
|
||||
}
|
||||
if (symbol == null) {
|
||||
throw new SleighException("Unknown register or label: '" + nm + "'");
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
protected SleighSymbol findUserSymbol(String nm) {
|
||||
Trace trace = coordinates.getTrace();
|
||||
long snap = coordinates.getSnap();
|
||||
for (TraceSymbol symbol : trace.getSymbolManager()
|
||||
.labelsAndFunctions()
|
||||
.getNamed(nm)) {
|
||||
if (symbol instanceof TraceSymbolWithLifespan lifeSym &&
|
||||
!lifeSym.getLifespan().contains(snap)) {
|
||||
continue;
|
||||
}
|
||||
return createSleighConstant(trace.getName(), nm, symbol.getAddress());
|
||||
}
|
||||
for (Program program : mappings.getOpenMappedProgramsAtSnap(trace, snap)) {
|
||||
for (Symbol symbol : program.getSymbolTable().getSymbols(nm)) {
|
||||
if (symbol.isDynamic() || symbol.isExternal()) {
|
||||
continue;
|
||||
}
|
||||
if (symbol.getSymbolType() != SymbolType.FUNCTION &&
|
||||
symbol.getSymbolType() != SymbolType.LABEL) {
|
||||
continue;
|
||||
}
|
||||
TraceLocation tloc = mappings.getOpenMappedLocation(trace,
|
||||
new ProgramLocation(program, symbol.getAddress()), snap);
|
||||
if (tloc == null) {
|
||||
return null;
|
||||
}
|
||||
return createSleighConstant(program.getName(), nm, tloc.getAddress());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the given Sleigh source into a p-code program, resolving user labels
|
||||
*
|
||||
* <p>
|
||||
* The resulting program must only be used with a state bound to the same coordinates. Any
|
||||
* symbols which are resolved to labels in the trace or its mapped programs are effectively
|
||||
* substituted for their offsets. If a label moves, the program should be recompiled in order to
|
||||
* update those substitutions.
|
||||
*
|
||||
* @param tool the tool for context
|
||||
* @param coordinates the coordinates for the trace (and programs) from which labels can be
|
||||
* resolved
|
||||
* @see SleighProgramCompiler#compileProgram(PcodeParser, SleighLanguage, String, String,
|
||||
* PcodeUseropLibrary)
|
||||
*/
|
||||
public static PcodeProgram compileProgram(PluginTool tool, DebuggerCoordinates coordinates,
|
||||
String sourceName, String source, PcodeUseropLibrary<?> library) {
|
||||
return SleighProgramCompiler.compileProgram(new LabelBoundPcodeParser(tool, coordinates),
|
||||
(SleighLanguage) coordinates.getPlatform().getLanguage(), sourceName, source, library);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the given Sleigh expression into a p-code program, resolving user labels
|
||||
*
|
||||
* <p>
|
||||
* This has the same limitations as
|
||||
* {@link #compileProgram(PluginTool, DebuggerCoordinates, String, String, PcodeUseropLibrary)}
|
||||
*
|
||||
* @see SleighProgramCompiler#compileExpression(PcodeParser, SleighLanguage, String)
|
||||
*/
|
||||
public static PcodeExpression compileExpression(PluginTool tool,
|
||||
DebuggerCoordinates coordinates, String source) {
|
||||
return SleighProgramCompiler.compileExpression(new LabelBoundPcodeParser(tool, coordinates),
|
||||
(SleighLanguage) coordinates.getPlatform().getLanguage(), source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a p-code executor state for the given coordinates
|
||||
*
|
||||
|
@ -76,7 +203,8 @@ public enum DebuggerPcodeUtils {
|
|||
return shared;
|
||||
}
|
||||
PcodeExecutorState<byte[]> local = new RWTargetRegistersPcodeExecutorState(
|
||||
access.getDataForLocalState(coordinates.getThread(), coordinates.getFrame()), Mode.RW);
|
||||
access.getDataForLocalState(coordinates.getThread(), coordinates.getFrame()),
|
||||
Mode.RW);
|
||||
return new ThreadPcodeExecutorState<>(shared, local) {
|
||||
@Override
|
||||
public void clear() {
|
||||
|
@ -319,7 +447,8 @@ public enum DebuggerPcodeUtils {
|
|||
bytes.binaryOp(opcode, sizeout, sizein1, in1.bytes.bytes, sizein2,
|
||||
in2.bytes.bytes)),
|
||||
STATE.binaryOp(opcode, sizeout, sizein1, in1.state, sizein2, in2.state),
|
||||
location.binaryOp(opcode, sizeout, sizein1, in1.location, sizein2, in2.location),
|
||||
location.binaryOp(opcode, sizeout, sizein1, in1.location, sizein2,
|
||||
in2.location),
|
||||
READS.binaryOp(opcode, sizeout, sizein1, in1.reads, sizein2, in2.reads));
|
||||
}
|
||||
|
||||
|
@ -495,7 +624,8 @@ public enum DebuggerPcodeUtils {
|
|||
}
|
||||
|
||||
@Override
|
||||
public WatchValue getVar(AddressSpace space, WatchValue offset, int size, boolean quantize,
|
||||
public WatchValue getVar(AddressSpace space, WatchValue offset, int size,
|
||||
boolean quantize,
|
||||
Reason reason) {
|
||||
return piece.getVar(space, offset.bytes.bytes, size, quantize, reason);
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@ public class DebuggerListingPluginScreenShots extends GhidraScreenShotGenerator
|
|||
|
||||
performAction(listingProvider.actionGoTo, false);
|
||||
DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||
dialog.setExpression("RAX");
|
||||
dialog.setOffset("RAX");
|
||||
|
||||
captureDialog(dialog);
|
||||
}
|
||||
|
|
|
@ -844,23 +844,31 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
waitForSwing();
|
||||
|
||||
assertTrue(listingProvider.actionGoTo.isEnabled());
|
||||
|
||||
performAction(listingProvider.actionGoTo, false);
|
||||
DebuggerGoToDialog dialog1 = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||
runSwing(() -> {
|
||||
dialog1.setExpression("r0");
|
||||
dialog1.setOffset("00400123");
|
||||
dialog1.okCallback();
|
||||
});
|
||||
|
||||
waitForPass(
|
||||
() -> assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress()));
|
||||
() -> assertEquals(tb.addr(0x00400123), listingProvider.getLocation().getAddress()));
|
||||
|
||||
performAction(listingProvider.actionGoTo, false);
|
||||
DebuggerGoToDialog dialog2 = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||
runSwing(() -> {
|
||||
dialog2.setExpression("*:4 r0");
|
||||
dialog2.setOffset("r0");
|
||||
dialog2.okCallback();
|
||||
});
|
||||
waitForPass(
|
||||
() -> assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress()));
|
||||
|
||||
performAction(listingProvider.actionGoTo, false);
|
||||
DebuggerGoToDialog dialog3 = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||
runSwing(() -> {
|
||||
dialog3.setOffset("*:4 r0");
|
||||
dialog3.okCallback();
|
||||
});
|
||||
waitForPass(
|
||||
() -> assertEquals(tb.addr(0x00404321), listingProvider.getLocation().getAddress()));
|
||||
}
|
||||
|
|
|
@ -629,7 +629,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||
performAction(memBytesProvider.actionGoTo, false);
|
||||
DebuggerGoToDialog dialog1 = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||
runSwing(() -> {
|
||||
dialog1.setExpression("r0");
|
||||
dialog1.setOffset("r0");
|
||||
dialog1.okCallback();
|
||||
});
|
||||
|
||||
|
@ -642,7 +642,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||
performAction(memBytesProvider.actionGoTo, false);
|
||||
DebuggerGoToDialog dialog2 = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||
runSwing(() -> {
|
||||
dialog2.setExpression("*:4 r0");
|
||||
dialog2.setOffset("*:4 r0");
|
||||
dialog2.okCallback();
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue