mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +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();
|
||||
});
|
||||
|
||||
|
|
|
@ -16,11 +16,12 @@
|
|||
package ghidra.pcode.exec;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.*;
|
||||
import ghidra.app.plugin.processors.sleigh.template.ConstructTpl;
|
||||
import ghidra.pcode.utils.MessageFormattingUtils;
|
||||
import ghidra.pcodeCPort.pcoderaw.VarnodeData;
|
||||
import ghidra.pcodeCPort.sleighbase.SleighBase;
|
||||
import ghidra.pcodeCPort.slghsymbol.*;
|
||||
|
@ -47,6 +48,94 @@ public enum SleighProgramCompiler {
|
|||
private static final String EXPRESSION_SOURCE_NAME = "expression";
|
||||
public static final String NIL_SYMBOL_NAME = "__nil";
|
||||
|
||||
public interface PcodeLogEntry {
|
||||
public static String formatList(List<PcodeLogEntry> list) {
|
||||
return list.stream().map(e -> e.format()).collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
Location loc();
|
||||
|
||||
String msg();
|
||||
|
||||
String type();
|
||||
|
||||
default String format() {
|
||||
return "%s: %s".formatted(type(), MessageFormattingUtils.format(loc(), msg()));
|
||||
}
|
||||
}
|
||||
|
||||
record PcodeError(Location loc, String msg) implements PcodeLogEntry {
|
||||
@Override
|
||||
public String type() {
|
||||
return "ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
record PcodeWarning(Location loc, String msg) implements PcodeLogEntry {
|
||||
@Override
|
||||
public String type() {
|
||||
return "WARNING";
|
||||
}
|
||||
}
|
||||
|
||||
public static class DetailedSleighException extends SleighException {
|
||||
private final List<PcodeLogEntry> details;
|
||||
|
||||
public DetailedSleighException(List<PcodeLogEntry> details) {
|
||||
super(PcodeLogEntry.formatList(details));
|
||||
this.details = List.copyOf(details);
|
||||
}
|
||||
|
||||
public List<PcodeLogEntry> getDetails() {
|
||||
return details;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A p-code parser that provides programmatic access to error diagnostics.
|
||||
*/
|
||||
public static class ErrorCollectingPcodeParser extends PcodeParser {
|
||||
private final List<PcodeLogEntry> entries = new ArrayList<>();
|
||||
|
||||
public ErrorCollectingPcodeParser(SleighLanguage language) {
|
||||
super(language, UniqueLayout.INJECT.getOffset(language));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportError(Location location, String msg) {
|
||||
entries.add(new PcodeError(location, msg));
|
||||
super.reportError(location, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Location location, String msg) {
|
||||
entries.add(new PcodeWarning(location, msg));
|
||||
super.reportWarning(location, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstructTpl compilePcode(String pcodeStatements, String srcFile, int srcLine)
|
||||
throws SleighException {
|
||||
try {
|
||||
return super.compilePcode(pcodeStatements, srcFile, srcLine);
|
||||
}
|
||||
finally {
|
||||
if (getErrors() != 0) {
|
||||
throw new DetailedSleighException(entries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SleighSymbol findSymbol(String nm) {
|
||||
SleighSymbol symbol = super.findSymbol(nm);
|
||||
if (symbol == null) {
|
||||
throw new SleighException("Unknown register: '" + nm + "'");
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a p-code parser for the given language
|
||||
*
|
||||
|
@ -54,7 +143,7 @@ public enum SleighProgramCompiler {
|
|||
* @return a parser
|
||||
*/
|
||||
public static PcodeParser createParser(SleighLanguage language) {
|
||||
return new PcodeParser(language, UniqueLayout.INJECT.getOffset(language));
|
||||
return new ErrorCollectingPcodeParser(language);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,7 +158,7 @@ public enum SleighProgramCompiler {
|
|||
*/
|
||||
public static ConstructTpl compileTemplate(Language language, PcodeParser parser,
|
||||
String sourceName, String source) {
|
||||
return parser.compilePcode(source, EXPRESSION_SOURCE_NAME, 1);
|
||||
return parser.compilePcode(source, sourceName, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,22 +251,22 @@ public enum SleighProgramCompiler {
|
|||
}
|
||||
|
||||
/**
|
||||
* Compile the given Sleigh source into a simple p-code program
|
||||
* Compile the given Sleigh source into a simple p-code program with the given parser
|
||||
*
|
||||
* <p>
|
||||
* This is suitable for modifying program state using Sleigh statements. Most likely, in
|
||||
* scripting, or perhaps in a Sleigh repl. The library given during compilation must match the
|
||||
* library given for execution, at least in its binding of userop IDs to symbols.
|
||||
*
|
||||
* @param the parser to use
|
||||
* @param language the language of the target p-code machine
|
||||
* @param sourceName a diagnostic name for the Sleigh source
|
||||
* @param source the Sleigh source
|
||||
* @param library the userop library or stub library for binding userop symbols
|
||||
* @return the compiled p-code program
|
||||
*/
|
||||
public static PcodeProgram compileProgram(SleighLanguage language, String sourceName,
|
||||
String source, PcodeUseropLibrary<?> library) {
|
||||
PcodeParser parser = createParser(language);
|
||||
public static PcodeProgram compileProgram(PcodeParser parser, SleighLanguage language,
|
||||
String sourceName, String source, PcodeUseropLibrary<?> library) {
|
||||
Map<Integer, UserOpSymbol> symbols = library.getSymbols(language);
|
||||
addParserSymbols(parser, symbols);
|
||||
|
||||
|
@ -186,7 +275,18 @@ public enum SleighProgramCompiler {
|
|||
}
|
||||
|
||||
/**
|
||||
* Compile the given Sleigh expression into a p-code program that can evaluate it
|
||||
* Compile the given Sleigh source into a simple p-code program
|
||||
*
|
||||
* @see #compileProgram(PcodeParser, SleighLanguage, String, String, PcodeUseropLibrary)
|
||||
*/
|
||||
public static PcodeProgram compileProgram(SleighLanguage language, String sourceName,
|
||||
String source, PcodeUseropLibrary<?> library) {
|
||||
return compileProgram(createParser(language), language, sourceName, source, library);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the given Sleigh expression into a p-code program that can evaluate it, using the
|
||||
* given parser
|
||||
*
|
||||
* <p>
|
||||
* TODO: Currently, expressions cannot be compiled for a user-supplied userop library. The
|
||||
|
@ -198,8 +298,8 @@ public enum SleighProgramCompiler {
|
|||
* @return a p-code program whose {@link PcodeExpression#evaluate(PcodeExecutor)} method will
|
||||
* evaluate the expression on the given executor and its state.
|
||||
*/
|
||||
public static PcodeExpression compileExpression(SleighLanguage language, String expression) {
|
||||
PcodeParser parser = createParser(language);
|
||||
public static PcodeExpression compileExpression(PcodeParser parser, SleighLanguage language,
|
||||
String expression) {
|
||||
Map<Integer, UserOpSymbol> symbols = PcodeExpression.CAPTURING.getSymbols(language);
|
||||
addParserSymbols(parser, symbols);
|
||||
|
||||
|
@ -208,6 +308,15 @@ public enum SleighProgramCompiler {
|
|||
return constructProgram(PcodeExpression::new, language, template, symbols);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the given Sleigh expression into a p-code program that can evaluate it
|
||||
*
|
||||
* @see #compileExpression(PcodeParser, SleighLanguage, String)
|
||||
*/
|
||||
public static PcodeExpression compileExpression(SleighLanguage language, String expression) {
|
||||
return compileExpression(createParser(language), language, expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a Sleigh symbol for context when compiling a userop definition
|
||||
*
|
||||
|
|
|
@ -290,6 +290,39 @@ public enum SleighUtils {
|
|||
});
|
||||
}
|
||||
|
||||
public static void matchDereference(Tree tree, Consumer<Tree> onSpace, Consumer<Tree> onSize,
|
||||
Consumer<Tree> onOffset) {
|
||||
switch (tree.getChildCount()) {
|
||||
case 3:
|
||||
match(tree, SleighParser.OP_DEREFERENCE, onSpace, onSize, onOffset);
|
||||
return;
|
||||
case 2:
|
||||
Tree child0 = tree.getChild(0);
|
||||
switch (child0.getType()) {
|
||||
case SleighParser.OP_IDENTIFIER:
|
||||
match(tree, SleighParser.OP_DEREFERENCE, onSpace, onOffset);
|
||||
return;
|
||||
case SleighParser.OP_BIN_CONSTANT:
|
||||
case SleighParser.OP_DEC_CONSTANT:
|
||||
case SleighParser.OP_HEX_CONSTANT:
|
||||
match(tree, SleighParser.OP_DEREFERENCE, onSize, onOffset);
|
||||
return;
|
||||
default:
|
||||
throw new AssertionError(
|
||||
"OP_DEREFERENCE with 2 children where child[0] is " +
|
||||
SleighParser.tokenNames[child0.getType()]);
|
||||
}
|
||||
case 1:
|
||||
match(tree, SleighParser.OP_DEREFERENCE, onOffset);
|
||||
return;
|
||||
default:
|
||||
// Likely, the op is mismatched. Ensure the error message says so.
|
||||
match(tree, SleighParser.OP_DEREFERENCE);
|
||||
throw new AssertionError(
|
||||
"OP_DEREFERENCE with " + tree.getChildCount() + " children");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given tree represents an unconditional breakpoint in the emulator
|
||||
*
|
||||
|
@ -390,6 +423,36 @@ public enum SleighUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public record AddressOf(String space, Tree offset) {
|
||||
}
|
||||
|
||||
public static AddressOf recoverAddressOf(String defaultSpace, Tree tree) {
|
||||
var l = new Object() {
|
||||
String space = defaultSpace;
|
||||
Tree offset;
|
||||
};
|
||||
matchDereference(tree, wantSpaceId -> {
|
||||
match(wantSpaceId, SleighParser.OP_IDENTIFIER, id -> {
|
||||
l.space = getIdentifier(id);
|
||||
});
|
||||
}, wantSize -> {
|
||||
// I don't care about size
|
||||
}, wantOffset -> {
|
||||
l.offset = wantOffset;
|
||||
});
|
||||
return new AddressOf(l.space, removeParenthesisTree(Objects.requireNonNull(l.offset)));
|
||||
}
|
||||
|
||||
public static AddressOf recoverAddressOf(String defaultSpace, String expression) {
|
||||
try {
|
||||
Tree tree = parseSleighExpression(expression);
|
||||
return recoverAddressOf(defaultSpace, tree);
|
||||
}
|
||||
catch (SleighParseError | MismatchException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a tree (node)
|
||||
*
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/* ###
|
||||
* 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.pcode.exec;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.Unique;
|
||||
import generic.test.AbstractGTest;
|
||||
import ghidra.GhidraTestApplicationLayout;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguageHelper;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.ApplicationConfiguration;
|
||||
import ghidra.pcode.exec.SleighProgramCompiler.DetailedSleighException;
|
||||
import ghidra.pcode.exec.SleighProgramCompiler.PcodeLogEntry;
|
||||
import ghidra.sleigh.grammar.Location;
|
||||
import utility.function.ExceptionalCallback;
|
||||
|
||||
public class SleighProgramCompilerTest extends AbstractGTest {
|
||||
protected <T> T rfail(String message) {
|
||||
fail(message);
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
protected <E extends Exception> E expect(Class<E> cls, ExceptionalCallback<E> cb) {
|
||||
try {
|
||||
cb.call();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
if (!cls.isInstance(e)) {
|
||||
e.printStackTrace();
|
||||
return rfail("Expected " + cls + ". Got " + e.getClass());
|
||||
}
|
||||
return cls.cast(e);
|
||||
}
|
||||
return rfail("Expected " + cls + ". Got success");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
if (!Application.isInitialized()) {
|
||||
Application.initializeApplication(
|
||||
new GhidraTestApplicationLayout(new File(getTestDirectoryPath())),
|
||||
new ApplicationConfiguration());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompileProgramErrLocations() throws Throwable {
|
||||
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
|
||||
DetailedSleighException exc = expect(DetailedSleighException.class, () -> {
|
||||
PcodeProgram program =
|
||||
SleighProgramCompiler.compileProgram(language, "test", "noreg = noreg;",
|
||||
PcodeUseropLibrary.NIL);
|
||||
// Shouldn't get here, but if we do, I'd like to see the program:
|
||||
System.err.println(program);
|
||||
});
|
||||
PcodeLogEntry entry = Unique.assertOne(exc.getDetails());
|
||||
Location loc = entry.loc();
|
||||
assertEquals("test", loc.filename);
|
||||
assertEquals(1, loc.lineno);
|
||||
assertEquals(
|
||||
"unknown start, end, next2, operand, epsilon, or varnode 'noreg' in varnode reference",
|
||||
entry.msg());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompileExpressionErrLocations() throws Throwable {
|
||||
SleighLanguage language = SleighLanguageHelper.getMockBE64Language();
|
||||
DetailedSleighException exc = expect(DetailedSleighException.class, () -> {
|
||||
PcodeProgram program = SleighProgramCompiler.compileExpression(language, "noreg");
|
||||
// Shouldn't get here, but if we do, I'd like to see the program:
|
||||
System.err.println(program);
|
||||
});
|
||||
PcodeLogEntry entry = Unique.assertOne(exc.getDetails());
|
||||
// TODO: It'd be nice if loc included a column number and token length
|
||||
Location loc = entry.loc();
|
||||
assertEquals("expression", loc.filename);
|
||||
assertEquals(1, loc.lineno);
|
||||
assertEquals(
|
||||
"unknown start, end, next2, operand, epsilon, or varnode 'noreg' in varnode reference",
|
||||
entry.msg());
|
||||
}
|
||||
}
|
|
@ -15,13 +15,13 @@
|
|||
*/
|
||||
package ghidra.pcode.exec;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.antlr.runtime.RecognitionException;
|
||||
import org.antlr.runtime.tree.Tree;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.pcode.exec.SleighUtils.AddressOf;
|
||||
import ghidra.pcode.exec.SleighUtils.SleighParseError;
|
||||
|
||||
public class SleighUtilsTest {
|
||||
|
@ -63,6 +63,40 @@ public class SleighUtilsTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecoverAddressOfMismatchErr() {
|
||||
AddressOf addrOf = SleighUtils.recoverAddressOf(null, "ptr + 8");
|
||||
assertNull(addrOf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecoverAddressOfForm1() {
|
||||
AddressOf addrOf = SleighUtils.recoverAddressOf(null, "*ptr");
|
||||
assertEquals(null, addrOf.space());
|
||||
assertEquals("ptr", SleighUtils.generateSleighExpression(addrOf.offset()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecoverAddressOfForm2a() {
|
||||
AddressOf addrOf = SleighUtils.recoverAddressOf(null, "*:8 ptr");
|
||||
assertEquals(null, addrOf.space());
|
||||
assertEquals("ptr", SleighUtils.generateSleighExpression(addrOf.offset()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecoverAddressOfForm2b() {
|
||||
AddressOf addrOf = SleighUtils.recoverAddressOf(null, "*[ram] ptr");
|
||||
assertEquals("ram", addrOf.space());
|
||||
assertEquals("ptr", SleighUtils.generateSleighExpression(addrOf.offset()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecoverAddressOfForm3() {
|
||||
AddressOf addrOf = SleighUtils.recoverAddressOf(null, "*[ram]:8 ptr");
|
||||
assertEquals("ram", addrOf.space());
|
||||
assertEquals("ptr", SleighUtils.generateSleighExpression(addrOf.offset()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecoverConditionEqDec() {
|
||||
assertEquals("RAX == 0",
|
||||
|
|
|
@ -155,7 +155,7 @@ public class PcodeParser extends PcodeCompile {
|
|||
if (sym != null) {
|
||||
return sym;
|
||||
}
|
||||
return PcodeParser.this.sleigh.findSymbol(nm);
|
||||
return sleigh.findSymbol(nm);
|
||||
}
|
||||
|
||||
public SleighBase getSleigh() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue