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>
|
<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
|
<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
|
an address, which can be expressed in simple notation or <B>Sleigh</B>, then attempts to
|
||||||
is evaluated in the context of the current thread, frame, and point in time. If the current
|
navigate to it. The expression is evaluated in the context of the current thread, frame, and
|
||||||
trace is live and at the present, the target may be queried to retrieve any machine state
|
point in time. If the current trace is live and at the present, the target may be queried to
|
||||||
required to evaluate the expression.</P>
|
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%">
|
<TABLE width="100%">
|
||||||
<TBODY>
|
<TBODY>
|
||||||
|
@ -147,6 +149,22 @@
|
||||||
</TBODY>
|
</TBODY>
|
||||||
</TABLE>
|
</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>
|
<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
|
<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>
|
<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
|
<P>This action is equivalent to the same action in the <A href=
|
||||||
an address, which can be expressed in Sleigh, then attempts to navigate to it. The expression
|
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html#go_to">Dynamic Listing</A>
|
||||||
is evaluated in the context of the current thread, frame, and point in time. If the current
|
window.</P>
|
||||||
trace is live and at the present, the target may be queried to retrieve any machine state
|
|
||||||
required to evaluate the expression.</P>
|
|
||||||
|
|
||||||
<TABLE width="100%">
|
<TABLE width="100%">
|
||||||
<TBODY>
|
<TBODY>
|
||||||
|
|
|
@ -42,6 +42,10 @@
|
||||||
constant 0x7fff0004 is a known issue. Just use the target's pointer size in bytes — 8
|
constant 0x7fff0004 is a known issue. Just use the target's pointer size in bytes — 8
|
||||||
in the example.</LI>
|
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
|
<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>
|
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;
|
package ghidra.app.plugin.core.debug.gui.action;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.event.KeyAdapter;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.swing.*;
|
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.app.plugin.processors.sleigh.SleighLanguage;
|
||||||
import ghidra.async.AsyncUtils;
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.program.model.address.AddressFactory;
|
import ghidra.framework.plugintool.util.PluginUtils;
|
||||||
import ghidra.program.model.address.AddressSpace;
|
import ghidra.pcode.exec.SleighUtils;
|
||||||
import ghidra.util.MessageType;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.util.Msg;
|
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 DebuggerGoToTrait trait;
|
||||||
private final DefaultComboBoxModel<String> modelSpaces;
|
private final DefaultComboBoxModel<String> modelSpaces;
|
||||||
|
|
||||||
final JTextField textExpression;
|
|
||||||
final JComboBox<String> comboSpaces;
|
final JComboBox<String> comboSpaces;
|
||||||
|
|
||||||
public DebuggerGoToDialog(DebuggerGoToTrait trait) {
|
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;
|
this.trait = trait;
|
||||||
|
|
||||||
textExpression = new JTextField();
|
|
||||||
modelSpaces = new DefaultComboBoxModel<>();
|
modelSpaces = new DefaultComboBoxModel<>();
|
||||||
comboSpaces = new JComboBox<>(modelSpaces);
|
comboSpaces = new JComboBox<>(modelSpaces);
|
||||||
|
|
||||||
JPanel panel = new JPanel(new BorderLayout());
|
Box hbox = Box.createHorizontalBox();
|
||||||
panel.setBorder(new EmptyBorder(16, 16, 16, 16));
|
hbox.add(comboSpaces);
|
||||||
JLabel help = new JLabel(
|
hbox.add(new JLabel(":"));
|
||||||
"<html>Enter any sleigh expression to evaluate against the current thread.<br/>" +
|
panel.add(hbox, BorderLayout.WEST);
|
||||||
"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.add(new JLabel("*["));
|
setFocusComponent(textInput);
|
||||||
box.add(comboSpaces);
|
|
||||||
box.add(new JLabel("]"));
|
|
||||||
box.add(textExpression);
|
|
||||||
|
|
||||||
addWorkPanel(panel);
|
textInput.addKeyListener(new KeyAdapter() {
|
||||||
setFocusComponent(textExpression);
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
addOKButton();
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
addCancelButton();
|
okCallback();
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void populateSpaces(SleighLanguage language) {
|
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
|
@Override // public for tests
|
||||||
public void okCallback() {
|
public void okCallback() {
|
||||||
CompletableFuture<Boolean> future;
|
validateAndMarkup();
|
||||||
|
if (!isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<GoToResult> future;
|
||||||
try {
|
try {
|
||||||
future = trait.goToSleigh((String) comboSpaces.getSelectedItem(),
|
future = trait.goTo((String) comboSpaces.getSelectedItem(), getInput());
|
||||||
textExpression.getText());
|
|
||||||
}
|
}
|
||||||
catch (Throwable t) {
|
catch (Throwable t) {
|
||||||
future = CompletableFuture.failedFuture(t);
|
future = CompletableFuture.failedFuture(t);
|
||||||
}
|
}
|
||||||
future.thenAccept(success -> {
|
future.thenAccept(result -> {
|
||||||
if (!success) {
|
if (!result.success()) {
|
||||||
setStatusText("Address not in trace", MessageType.ERROR, true);
|
setStatusText("<html>Address <code>" + result.address() + "</code> not in trace", MessageType.ERROR,
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
close();
|
close();
|
||||||
|
@ -124,12 +146,15 @@ public class DebuggerGoToDialog extends DialogComponentProvider {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void show(SleighLanguage language) {
|
public void show(SleighLanguage language, GoToInput defaultInput) {
|
||||||
populateSpaces(language);
|
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) {
|
public void setOffset(String offset) {
|
||||||
textExpression.setText(expression);
|
textInput.setText(offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,17 @@ import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.pcode.exec.*;
|
import ghidra.pcode.exec.*;
|
||||||
import ghidra.pcode.utils.Utils;
|
import ghidra.pcode.utils.Utils;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.address.AddressSpace;
|
|
||||||
import ghidra.program.model.lang.Language;
|
import ghidra.program.model.lang.Language;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
import ghidra.trace.model.guest.TracePlatform;
|
||||||
|
|
||||||
public abstract class DebuggerGoToTrait {
|
public abstract class DebuggerGoToTrait {
|
||||||
|
/**
|
||||||
|
* @see DebuggerGoToTrait#goTo(String, String)
|
||||||
|
*/
|
||||||
|
public record GoToResult(Address address, Boolean success) {
|
||||||
|
}
|
||||||
|
|
||||||
protected DockingAction action;
|
protected DockingAction action;
|
||||||
|
|
||||||
protected final PluginTool tool;
|
protected final PluginTool tool;
|
||||||
|
@ -48,6 +53,8 @@ public abstract class DebuggerGoToTrait {
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract GoToInput getDefaultInput();
|
||||||
|
|
||||||
protected abstract boolean goToAddress(Address address);
|
protected abstract boolean goToAddress(Address address);
|
||||||
|
|
||||||
public void goToCoordinates(DebuggerCoordinates coordinates) {
|
public void goToCoordinates(DebuggerCoordinates coordinates) {
|
||||||
|
@ -66,25 +73,61 @@ public abstract class DebuggerGoToTrait {
|
||||||
private void activatedGoTo(ActionContext context) {
|
private void activatedGoTo(ActionContext context) {
|
||||||
DebuggerGoToDialog goToDialog = new DebuggerGoToDialog(this);
|
DebuggerGoToDialog goToDialog = new DebuggerGoToDialog(this);
|
||||||
TracePlatform platform = current.getPlatform();
|
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();
|
TracePlatform platform = current.getPlatform();
|
||||||
Language language = platform.getLanguage();
|
Language language = platform.getLanguage();
|
||||||
if (!(language instanceof SleighLanguage)) {
|
if (!(language instanceof SleighLanguage)) {
|
||||||
throw new IllegalStateException("Current trace does not use Sleigh");
|
throw new IllegalStateException("Current trace does not use Sleigh");
|
||||||
}
|
}
|
||||||
SleighLanguage slang = (SleighLanguage) language;
|
|
||||||
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceName);
|
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceName);
|
||||||
if (space == null) {
|
if (space == null) {
|
||||||
throw new IllegalArgumentException("No such address space: " + spaceName);
|
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);
|
return goToSleigh(platform, space, expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Boolean> goToSleigh(TracePlatform platform, AddressSpace space,
|
protected CompletableFuture<GoToResult> goToSleigh(TracePlatform platform, AddressSpace space,
|
||||||
PcodeExpression expression) {
|
PcodeExpression expression) {
|
||||||
PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool, current);
|
PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool, current);
|
||||||
CompletableFuture<byte[]> result =
|
CompletableFuture<byte[]> result =
|
||||||
|
@ -92,7 +135,7 @@ public abstract class DebuggerGoToTrait {
|
||||||
return result.thenApplyAsync(offset -> {
|
return result.thenApplyAsync(offset -> {
|
||||||
Address address = space.getAddress(
|
Address address = space.getAddress(
|
||||||
Utils.bytesToLong(offset, offset.length, expression.getLanguage().isBigEndian()));
|
Utils.bytesToLong(offset, offset.length, expression.getLanguage().isBigEndian()));
|
||||||
return goToAddress(platform.mapGuestToHost(address));
|
return new GoToResult(address, goToAddress(platform.mapGuestToHost(address)));
|
||||||
}, AsyncUtils.SWING_EXECUTOR);
|
}, AsyncUtils.SWING_EXECUTOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -315,6 +315,13 @@ public class DebuggerTrackLocationTrait {
|
||||||
action.setCurrentActionStateByUserData(spec);
|
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() {
|
protected void locationTracked() {
|
||||||
// Listener method
|
// 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.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.TraceAddressSnapRange;
|
import ghidra.trace.model.TraceAddressSnapRange;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
import ghidra.trace.model.guest.TracePlatform;
|
||||||
import ghidra.trace.model.stack.TraceStack;
|
import ghidra.trace.model.stack.TraceStack;
|
||||||
|
@ -53,6 +54,17 @@ public interface LocationTracker {
|
||||||
CompletableFuture<Address> computeTraceAddress(PluginTool tool,
|
CompletableFuture<Address> computeTraceAddress(PluginTool tool,
|
||||||
DebuggerCoordinates coordinates);
|
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
|
// TODO: Is there a way to generalize these so that other dependencies need not
|
||||||
// have their own bespoke methods?
|
// have their own bespoke methods?
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||||
import ghidra.async.AsyncUtils;
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.TraceAddressSnapRange;
|
import ghidra.trace.model.TraceAddressSnapRange;
|
||||||
import ghidra.trace.model.stack.TraceStack;
|
import ghidra.trace.model.stack.TraceStack;
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
import ghidra.trace.util.TraceAddressSpace;
|
||||||
|
@ -69,6 +70,15 @@ public enum NoneLocationTrackingSpec implements LocationTrackingSpec, LocationTr
|
||||||
return AsyncUtils.nil();
|
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
|
@Override
|
||||||
public boolean affectedByBytesChange(TraceAddressSpace space,
|
public boolean affectedByBytesChange(TraceAddressSpace space,
|
||||||
TraceAddressSnapRange range, DebuggerCoordinates coordinates) {
|
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.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.TraceAddressSnapRange;
|
import ghidra.trace.model.TraceAddressSnapRange;
|
||||||
import ghidra.trace.model.stack.TraceStack;
|
import ghidra.trace.model.stack.TraceStack;
|
||||||
|
@ -87,6 +88,17 @@ public enum PCByStackLocationTrackingSpec implements LocationTrackingSpec, Locat
|
||||||
return CompletableFuture.supplyAsync(() -> doComputeTraceAddress(tool, coordinates));
|
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.
|
// Note it does no good to override affectByRegChange. It must do what we'd avoid anyway.
|
||||||
@Override
|
@Override
|
||||||
public boolean affectedByStackChange(TraceStack stack, DebuggerCoordinates coordinates) {
|
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.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.TraceAddressSnapRange;
|
import ghidra.trace.model.TraceAddressSnapRange;
|
||||||
import ghidra.trace.model.stack.TraceStack;
|
import ghidra.trace.model.stack.TraceStack;
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
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.
|
// Note it does no good to override affectByRegChange. It must do what we'd avoid anyway.
|
||||||
@Override
|
@Override
|
||||||
public boolean affectedByStackChange(TraceStack stack, DebuggerCoordinates coordinates) {
|
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.address.*;
|
||||||
import ghidra.program.model.lang.Register;
|
import ghidra.program.model.lang.Register;
|
||||||
import ghidra.program.model.lang.RegisterValue;
|
import ghidra.program.model.lang.RegisterValue;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.TraceAddressSnapRange;
|
import ghidra.trace.model.TraceAddressSnapRange;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
import ghidra.trace.model.guest.TracePlatform;
|
||||||
|
@ -91,6 +92,13 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec, Loca
|
||||||
return CompletableFuture.supplyAsync(() -> doComputeTraceAddress(tool, coordinates));
|
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
|
@Override
|
||||||
default boolean affectedByBytesChange(TraceAddressSpace space,
|
default boolean affectedByBytesChange(TraceAddressSpace space,
|
||||||
TraceAddressSnapRange range, DebuggerCoordinates coordinates) {
|
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;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.watch.WatchRow;
|
import ghidra.app.plugin.core.debug.gui.watch.WatchRow;
|
||||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
|
||||||
import ghidra.async.AsyncUtils;
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.pcode.exec.*;
|
import ghidra.pcode.exec.*;
|
||||||
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
|
import ghidra.pcode.exec.DebuggerPcodeUtils.WatchValue;
|
||||||
|
import ghidra.pcode.exec.SleighUtils.AddressOf;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSetView;
|
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.TraceAddressSnapRange;
|
||||||
|
import ghidra.trace.model.guest.TracePlatform;
|
||||||
import ghidra.trace.model.stack.TraceStack;
|
import ghidra.trace.model.stack.TraceStack;
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
import ghidra.trace.util.TraceAddressSpace;
|
||||||
|
|
||||||
|
@ -44,6 +45,11 @@ public class WatchLocationTrackingSpec implements LocationTrackingSpec {
|
||||||
public static final String CONFIG_PREFIX = "TRACK_WATCH_";
|
public static final String CONFIG_PREFIX = "TRACK_WATCH_";
|
||||||
|
|
||||||
private final String expression;
|
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
|
* Derive a tracking specification from the given watch
|
||||||
|
@ -62,6 +68,8 @@ public class WatchLocationTrackingSpec implements LocationTrackingSpec {
|
||||||
*/
|
*/
|
||||||
public WatchLocationTrackingSpec(String expression) {
|
public WatchLocationTrackingSpec(String expression) {
|
||||||
this.expression = expression;
|
this.expression = expression;
|
||||||
|
AddressOf addrOf = SleighUtils.recoverAddressOf(null, expression);
|
||||||
|
this.label = SleighUtils.generateSleighExpression(addrOf.offset());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -97,7 +105,7 @@ public class WatchLocationTrackingSpec implements LocationTrackingSpec {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getLocationLabel() {
|
public String getLocationLabel() {
|
||||||
return "&watch";
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -124,18 +132,27 @@ public class WatchLocationTrackingSpec implements LocationTrackingSpec {
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
Language language = current.getPlatform().getLanguage();
|
compiled = DebuggerPcodeUtils.compileExpression(tool, current, expression);
|
||||||
if (!(language instanceof SleighLanguage slang)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (compiled == null || compiled.getLanguage() != language) {
|
|
||||||
compiled = SleighProgramCompiler.compileExpression(slang, expression);
|
|
||||||
}
|
|
||||||
WatchValue value = compiled.evaluate(asyncExec);
|
WatchValue value = compiled.evaluate(asyncExec);
|
||||||
return value == null ? null : value.address();
|
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
|
@Override
|
||||||
public boolean affectedByBytesChange(TraceAddressSpace space, TraceAddressSnapRange range,
|
public boolean affectedByBytesChange(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||||
DebuggerCoordinates coordinates) {
|
DebuggerCoordinates coordinates) {
|
||||||
|
|
|
@ -38,6 +38,7 @@ public class WatchLocationTrackingSpecFactory implements LocationTrackingSpecFac
|
||||||
}
|
}
|
||||||
return watchesService.getWatches()
|
return watchesService.getWatches()
|
||||||
.stream()
|
.stream()
|
||||||
|
.filter(WatchLocationTrackingSpec::isTrackable)
|
||||||
.map(WatchLocationTrackingSpec::fromWatch)
|
.map(WatchLocationTrackingSpec::fromWatch)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,8 +184,16 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
DebuggerListingProvider.this);
|
DebuggerListingProvider.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GoToInput getDefaultInput() {
|
||||||
|
return trackingTrait.getDefaultGoToInput(getLocation());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean goToAddress(Address address) {
|
protected boolean goToAddress(Address address) {
|
||||||
|
if (syncTrait.isAutoSyncCursorWithStaticListing()) {
|
||||||
|
syncTrait.doAutoSyncCursorIntoStatic(new ProgramLocation(getProgram(), address));
|
||||||
|
}
|
||||||
return getListingPanel().goTo(address);
|
return getListingPanel().goTo(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,13 +103,18 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
|
||||||
DebuggerMemoryBytesProvider.this);
|
DebuggerMemoryBytesProvider.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected GoToInput getDefaultInput() {
|
||||||
|
return trackingTrait.getDefaultGoToInput(currentLocation);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean goToAddress(Address address) {
|
protected boolean goToAddress(Address address) {
|
||||||
TraceProgramView view = current.getView();
|
TraceProgramView view = current.getView();
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
return false;
|
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 db.Transaction;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
|
||||||
import ghidra.app.services.DataTypeManagerService;
|
import ghidra.app.services.DataTypeManagerService;
|
||||||
import ghidra.app.services.DebuggerControlService;
|
import ghidra.app.services.DebuggerControlService;
|
||||||
import ghidra.app.services.DebuggerControlService.StateEditor;
|
import ghidra.app.services.DebuggerControlService.StateEditor;
|
||||||
|
@ -96,7 +95,8 @@ public class WatchRow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
compiled = SleighProgramCompiler.compileExpression(provider.language, expression);
|
compiled = DebuggerPcodeUtils.compileExpression(provider.getTool(), provider.current,
|
||||||
|
expression);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
error = e;
|
error = e;
|
||||||
|
@ -106,7 +106,6 @@ public class WatchRow {
|
||||||
|
|
||||||
protected void reevaluate() {
|
protected void reevaluate() {
|
||||||
blank();
|
blank();
|
||||||
SleighLanguage language = provider.language;
|
|
||||||
PcodeExecutor<WatchValue> executor = provider.asyncWatchExecutor;
|
PcodeExecutor<WatchValue> executor = provider.asyncWatchExecutor;
|
||||||
PcodeExecutor<byte[]> prevExec = provider.prevValueExecutor;
|
PcodeExecutor<byte[]> prevExec = provider.prevValueExecutor;
|
||||||
if (executor == null) {
|
if (executor == null) {
|
||||||
|
@ -114,9 +113,7 @@ public class WatchRow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
if (compiled == null || compiled.getLanguage() != language) {
|
|
||||||
recompile();
|
recompile();
|
||||||
}
|
|
||||||
if (compiled == null) {
|
if (compiled == null) {
|
||||||
provider.contextChanged();
|
provider.contextChanged();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -22,21 +22,34 @@ import java.util.Map.Entry;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.service.emulation.*;
|
import ghidra.app.plugin.core.debug.service.emulation.*;
|
||||||
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
|
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.plugin.processors.sleigh.SleighLanguage;
|
||||||
|
import ghidra.app.services.DebuggerStaticMappingService;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.pcode.emu.ThreadPcodeExecutorState;
|
import ghidra.pcode.emu.ThreadPcodeExecutorState;
|
||||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||||
|
import ghidra.pcode.exec.SleighProgramCompiler.ErrorCollectingPcodeParser;
|
||||||
import ghidra.pcode.exec.trace.*;
|
import ghidra.pcode.exec.trace.*;
|
||||||
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess;
|
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess;
|
||||||
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
|
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
|
||||||
import ghidra.pcode.utils.Utils;
|
import ghidra.pcode.utils.Utils;
|
||||||
|
import ghidra.pcodeCPort.slghsymbol.SleighSymbol;
|
||||||
|
import ghidra.pcodeCPort.slghsymbol.VarnodeSymbol;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.lang.*;
|
import ghidra.program.model.lang.*;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.mem.MemBuffer;
|
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.Trace;
|
||||||
|
import ghidra.trace.model.TraceLocation;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
import ghidra.trace.model.guest.TracePlatform;
|
||||||
import ghidra.trace.model.memory.TraceMemoryState;
|
import ghidra.trace.model.memory.TraceMemoryState;
|
||||||
|
import ghidra.trace.model.symbol.TraceSymbol;
|
||||||
|
import ghidra.trace.model.symbol.TraceSymbolWithLifespan;
|
||||||
import ghidra.util.NumericUtilities;
|
import ghidra.util.NumericUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,6 +58,120 @@ import ghidra.util.NumericUtilities;
|
||||||
public enum DebuggerPcodeUtils {
|
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
|
* Get a p-code executor state for the given coordinates
|
||||||
*
|
*
|
||||||
|
@ -76,7 +203,8 @@ public enum DebuggerPcodeUtils {
|
||||||
return shared;
|
return shared;
|
||||||
}
|
}
|
||||||
PcodeExecutorState<byte[]> local = new RWTargetRegistersPcodeExecutorState(
|
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) {
|
return new ThreadPcodeExecutorState<>(shared, local) {
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
@ -319,7 +447,8 @@ public enum DebuggerPcodeUtils {
|
||||||
bytes.binaryOp(opcode, sizeout, sizein1, in1.bytes.bytes, sizein2,
|
bytes.binaryOp(opcode, sizeout, sizein1, in1.bytes.bytes, sizein2,
|
||||||
in2.bytes.bytes)),
|
in2.bytes.bytes)),
|
||||||
STATE.binaryOp(opcode, sizeout, sizein1, in1.state, sizein2, in2.state),
|
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));
|
READS.binaryOp(opcode, sizeout, sizein1, in1.reads, sizein2, in2.reads));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,7 +624,8 @@ public enum DebuggerPcodeUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
Reason reason) {
|
||||||
return piece.getVar(space, offset.bytes.bytes, size, quantize, reason);
|
return piece.getVar(space, offset.bytes.bytes, size, quantize, reason);
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,7 +135,7 @@ public class DebuggerListingPluginScreenShots extends GhidraScreenShotGenerator
|
||||||
|
|
||||||
performAction(listingProvider.actionGoTo, false);
|
performAction(listingProvider.actionGoTo, false);
|
||||||
DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class);
|
DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||||
dialog.setExpression("RAX");
|
dialog.setOffset("RAX");
|
||||||
|
|
||||||
captureDialog(dialog);
|
captureDialog(dialog);
|
||||||
}
|
}
|
||||||
|
|
|
@ -844,23 +844,31 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertTrue(listingProvider.actionGoTo.isEnabled());
|
assertTrue(listingProvider.actionGoTo.isEnabled());
|
||||||
|
|
||||||
performAction(listingProvider.actionGoTo, false);
|
performAction(listingProvider.actionGoTo, false);
|
||||||
DebuggerGoToDialog dialog1 = waitForDialogComponent(DebuggerGoToDialog.class);
|
DebuggerGoToDialog dialog1 = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
dialog1.setExpression("r0");
|
dialog1.setOffset("00400123");
|
||||||
dialog1.okCallback();
|
dialog1.okCallback();
|
||||||
});
|
});
|
||||||
|
|
||||||
waitForPass(
|
waitForPass(
|
||||||
() -> assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress()));
|
() -> assertEquals(tb.addr(0x00400123), listingProvider.getLocation().getAddress()));
|
||||||
|
|
||||||
performAction(listingProvider.actionGoTo, false);
|
performAction(listingProvider.actionGoTo, false);
|
||||||
DebuggerGoToDialog dialog2 = waitForDialogComponent(DebuggerGoToDialog.class);
|
DebuggerGoToDialog dialog2 = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
dialog2.setExpression("*:4 r0");
|
dialog2.setOffset("r0");
|
||||||
dialog2.okCallback();
|
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(
|
waitForPass(
|
||||||
() -> assertEquals(tb.addr(0x00404321), listingProvider.getLocation().getAddress()));
|
() -> assertEquals(tb.addr(0x00404321), listingProvider.getLocation().getAddress()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -629,7 +629,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
performAction(memBytesProvider.actionGoTo, false);
|
performAction(memBytesProvider.actionGoTo, false);
|
||||||
DebuggerGoToDialog dialog1 = waitForDialogComponent(DebuggerGoToDialog.class);
|
DebuggerGoToDialog dialog1 = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
dialog1.setExpression("r0");
|
dialog1.setOffset("r0");
|
||||||
dialog1.okCallback();
|
dialog1.okCallback();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -642,7 +642,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
performAction(memBytesProvider.actionGoTo, false);
|
performAction(memBytesProvider.actionGoTo, false);
|
||||||
DebuggerGoToDialog dialog2 = waitForDialogComponent(DebuggerGoToDialog.class);
|
DebuggerGoToDialog dialog2 = waitForDialogComponent(DebuggerGoToDialog.class);
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
dialog2.setExpression("*:4 r0");
|
dialog2.setOffset("*:4 r0");
|
||||||
dialog2.okCallback();
|
dialog2.okCallback();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -16,11 +16,12 @@
|
||||||
package ghidra.pcode.exec;
|
package ghidra.pcode.exec;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Map;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import ghidra.app.plugin.processors.sleigh.*;
|
import ghidra.app.plugin.processors.sleigh.*;
|
||||||
import ghidra.app.plugin.processors.sleigh.template.ConstructTpl;
|
import ghidra.app.plugin.processors.sleigh.template.ConstructTpl;
|
||||||
|
import ghidra.pcode.utils.MessageFormattingUtils;
|
||||||
import ghidra.pcodeCPort.pcoderaw.VarnodeData;
|
import ghidra.pcodeCPort.pcoderaw.VarnodeData;
|
||||||
import ghidra.pcodeCPort.sleighbase.SleighBase;
|
import ghidra.pcodeCPort.sleighbase.SleighBase;
|
||||||
import ghidra.pcodeCPort.slghsymbol.*;
|
import ghidra.pcodeCPort.slghsymbol.*;
|
||||||
|
@ -47,6 +48,94 @@ public enum SleighProgramCompiler {
|
||||||
private static final String EXPRESSION_SOURCE_NAME = "expression";
|
private static final String EXPRESSION_SOURCE_NAME = "expression";
|
||||||
public static final String NIL_SYMBOL_NAME = "__nil";
|
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
|
* Create a p-code parser for the given language
|
||||||
*
|
*
|
||||||
|
@ -54,7 +143,7 @@ public enum SleighProgramCompiler {
|
||||||
* @return a parser
|
* @return a parser
|
||||||
*/
|
*/
|
||||||
public static PcodeParser createParser(SleighLanguage language) {
|
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,
|
public static ConstructTpl compileTemplate(Language language, PcodeParser parser,
|
||||||
String sourceName, String source) {
|
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>
|
* <p>
|
||||||
* This is suitable for modifying program state using Sleigh statements. Most likely, in
|
* 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
|
* 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.
|
* 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 language the language of the target p-code machine
|
||||||
* @param sourceName a diagnostic name for the Sleigh source
|
* @param sourceName a diagnostic name for the Sleigh source
|
||||||
* @param source the Sleigh source
|
* @param source the Sleigh source
|
||||||
* @param library the userop library or stub library for binding userop symbols
|
* @param library the userop library or stub library for binding userop symbols
|
||||||
* @return the compiled p-code program
|
* @return the compiled p-code program
|
||||||
*/
|
*/
|
||||||
public static PcodeProgram compileProgram(SleighLanguage language, String sourceName,
|
public static PcodeProgram compileProgram(PcodeParser parser, SleighLanguage language,
|
||||||
String source, PcodeUseropLibrary<?> library) {
|
String sourceName, String source, PcodeUseropLibrary<?> library) {
|
||||||
PcodeParser parser = createParser(language);
|
|
||||||
Map<Integer, UserOpSymbol> symbols = library.getSymbols(language);
|
Map<Integer, UserOpSymbol> symbols = library.getSymbols(language);
|
||||||
addParserSymbols(parser, symbols);
|
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>
|
* <p>
|
||||||
* TODO: Currently, expressions cannot be compiled for a user-supplied userop library. The
|
* 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
|
* @return a p-code program whose {@link PcodeExpression#evaluate(PcodeExecutor)} method will
|
||||||
* evaluate the expression on the given executor and its state.
|
* evaluate the expression on the given executor and its state.
|
||||||
*/
|
*/
|
||||||
public static PcodeExpression compileExpression(SleighLanguage language, String expression) {
|
public static PcodeExpression compileExpression(PcodeParser parser, SleighLanguage language,
|
||||||
PcodeParser parser = createParser(language);
|
String expression) {
|
||||||
Map<Integer, UserOpSymbol> symbols = PcodeExpression.CAPTURING.getSymbols(language);
|
Map<Integer, UserOpSymbol> symbols = PcodeExpression.CAPTURING.getSymbols(language);
|
||||||
addParserSymbols(parser, symbols);
|
addParserSymbols(parser, symbols);
|
||||||
|
|
||||||
|
@ -208,6 +308,15 @@ public enum SleighProgramCompiler {
|
||||||
return constructProgram(PcodeExpression::new, language, template, symbols);
|
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
|
* 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
|
* 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)
|
* 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;
|
package ghidra.pcode.exec;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
import org.antlr.runtime.RecognitionException;
|
import org.antlr.runtime.RecognitionException;
|
||||||
import org.antlr.runtime.tree.Tree;
|
import org.antlr.runtime.tree.Tree;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ghidra.pcode.exec.SleighUtils.AddressOf;
|
||||||
import ghidra.pcode.exec.SleighUtils.SleighParseError;
|
import ghidra.pcode.exec.SleighUtils.SleighParseError;
|
||||||
|
|
||||||
public class SleighUtilsTest {
|
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
|
@Test
|
||||||
public void testRecoverConditionEqDec() {
|
public void testRecoverConditionEqDec() {
|
||||||
assertEquals("RAX == 0",
|
assertEquals("RAX == 0",
|
||||||
|
|
|
@ -155,7 +155,7 @@ public class PcodeParser extends PcodeCompile {
|
||||||
if (sym != null) {
|
if (sym != null) {
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
return PcodeParser.this.sleigh.findSymbol(nm);
|
return sleigh.findSymbol(nm);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SleighBase getSleigh() {
|
public SleighBase getSleigh() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue