diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html index 032f904f74..3fa2c7bdd5 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html @@ -134,10 +134,12 @@

Go To (G)

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.

+ an address, which can be expressed in simple notation or 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. 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.

@@ -147,6 +149,22 @@
+

Some examples:

+ + +

Auto-Sync Cursor with Static Listing

This action is always available, but only on the primary dynamic listing. It configures diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png index bdbc14fcca..bfc2dca48c 100644 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html index 47b1687768..94d5029bf5 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html @@ -78,11 +78,9 @@

Go To (G)

-

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.

+

This action is equivalent to the same action in the Dynamic Listing + window.

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html index fe19e146a1..6ad0c355f7 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html @@ -42,6 +42,10 @@ constant 0x7fff0004 is a known issue. Just use the target's pointer size in bytes — 8 in the example. +
  • *:4 my_global: Display 4 bytes starting at the label "my_global", e.g., to + read the int value there. The label may be in the trace or in a program mapped + to the trace. Ambiguities are resolved arbitrarily.
  • +
  • *:8 RSP: Display 8 bytes of [ram] starting at the offset given by register RSP, e.g., to read a long on the stack.
  • diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToDialog.java index ad93816bca..83907f0092 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToDialog.java @@ -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 = """ + + +

    + Enter an address or Sleigh expression. Press F1 for help and examples. +

    + + + """; private final DebuggerGoToTrait trait; private final DefaultComboBoxModel modelSpaces; - final JTextField textExpression; final JComboBox 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( - "Enter any sleigh expression to evaluate against the current thread.
    " + - "Note that constants and memory derefs must have a resolved size.
    " + - "Examples:
    " + - ""); - 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 future; + validateAndMarkup(); + if (!isValid) { + return; + } + + CompletableFuture 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("Address " + result.address() + " 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); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java index 4cdea1eed0..bcf2f20b80 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java @@ -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 goToSleigh(String spaceName, String expression) { + /** + * Go to the given address + * + *

    + * 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 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 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 goToSleigh(TracePlatform platform, AddressSpace space, + protected CompletableFuture goToSleigh(TracePlatform platform, AddressSpace space, PcodeExpression expression) { PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(tool, current); CompletableFuture 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); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java index e9fcef819f..843d49f8ed 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java @@ -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 } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/GoToInput.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/GoToInput.java new file mode 100644 index 0000000000..deede24f7d --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/GoToInput.java @@ -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); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LocationTracker.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LocationTracker.java index 6fa2bb3acc..b2750c8ca9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LocationTracker.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LocationTracker.java @@ -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

    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? diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneLocationTrackingSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneLocationTrackingSpec.java index f41121d0e8..443cb5896a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneLocationTrackingSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneLocationTrackingSpec.java @@ -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) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/PCByStackLocationTrackingSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/PCByStackLocationTrackingSpec.java index 996552df13..dee6cd3193 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/PCByStackLocationTrackingSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/PCByStackLocationTrackingSpec.java @@ -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) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/PCLocationTrackingSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/PCLocationTrackingSpec.java index c2b69303cb..511800da22 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/PCLocationTrackingSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/PCLocationTrackingSpec.java @@ -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) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/RegisterLocationTrackingSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/RegisterLocationTrackingSpec.java index 8af97f5b7c..7087147a73 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/RegisterLocationTrackingSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/RegisterLocationTrackingSpec.java @@ -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) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/WatchLocationTrackingSpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/WatchLocationTrackingSpec.java index 527b200a63..62afdfc624 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/WatchLocationTrackingSpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/WatchLocationTrackingSpec.java @@ -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) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/WatchLocationTrackingSpecFactory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/WatchLocationTrackingSpecFactory.java index c5f4c7ad50..874afb0b82 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/WatchLocationTrackingSpecFactory.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/WatchLocationTrackingSpecFactory.java @@ -38,6 +38,7 @@ public class WatchLocationTrackingSpecFactory implements LocationTrackingSpecFac } return watchesService.getWatches() .stream() + .filter(WatchLocationTrackingSpec::isTrackable) .map(WatchLocationTrackingSpec::fromWatch) .collect(Collectors.toList()); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java index 148a749354..516e2393cd 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java @@ -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); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java index 4774bb093a..8f20f1dc9a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java @@ -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)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java index 06184ea615..436536ff30 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java @@ -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 executor = provider.asyncWatchExecutor; PcodeExecutor 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; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java index b8948a7873..177857ed5f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java @@ -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 + * + *

    + * 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 + * + *

    + * 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 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); } diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPluginScreenShots.java index eaeda76b88..841be8a71c 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPluginScreenShots.java @@ -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); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index 0aed8791e5..4366be04a0 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -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())); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java index 724f7888ad..233d8f73d0 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java @@ -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(); }); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighProgramCompiler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighProgramCompiler.java index 54e80fe237..ae623db85a 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighProgramCompiler.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighProgramCompiler.java @@ -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 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 details; + + public DetailedSleighException(List details) { + super(PcodeLogEntry.formatList(details)); + this.details = List.copyOf(details); + } + + public List getDetails() { + return details; + } + } + + /** + * A p-code parser that provides programmatic access to error diagnostics. + */ + public static class ErrorCollectingPcodeParser extends PcodeParser { + private final List 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 * *

    * 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 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 * *

    * 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 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 * diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighUtils.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighUtils.java index 52332022fc..b0c0251289 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighUtils.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighUtils.java @@ -290,6 +290,39 @@ public enum SleighUtils { }); } + public static void matchDereference(Tree tree, Consumer onSpace, Consumer onSize, + Consumer 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) * diff --git a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/SleighProgramCompilerTest.java b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/SleighProgramCompilerTest.java new file mode 100644 index 0000000000..a0c9b6124d --- /dev/null +++ b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/SleighProgramCompilerTest.java @@ -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 rfail(String message) { + fail(message); + throw new AssertionError(); + } + + protected E expect(Class cls, ExceptionalCallback 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()); + } +} diff --git a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/SleighUtilsTest.java b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/SleighUtilsTest.java index 38c90810ef..43fb5a60cd 100644 --- a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/SleighUtilsTest.java +++ b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/SleighUtilsTest.java @@ -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", diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PcodeParser.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PcodeParser.java index 8e64c318a8..19d8622dd6 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PcodeParser.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PcodeParser.java @@ -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() {