diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblyDualTextField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblyDualTextField.java index fcda0299da..876ff130fc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblyDualTextField.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/AssemblyDualTextField.java @@ -17,7 +17,9 @@ package ghidra.app.plugin.core.assembler; import java.awt.*; import java.awt.event.*; +import java.math.BigInteger; import java.util.*; +import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicReference; import javax.swing.*; @@ -37,11 +39,14 @@ import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParseErrorResult; import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParseResult; import ghidra.app.plugin.assembler.sleigh.sem.*; import ghidra.app.plugin.processors.sleigh.*; +import ghidra.app.util.viewer.field.ListingColors; import ghidra.framework.Application; import ghidra.framework.ApplicationConfiguration; import ghidra.program.model.address.Address; -import ghidra.program.model.lang.LanguageID; -import ghidra.program.model.listing.Instruction; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.ByteMemBufferImpl; +import ghidra.util.Msg; import ghidra.util.NumericUtilities; /** @@ -62,11 +67,11 @@ import ghidra.util.NumericUtilities; */ public class AssemblyDualTextField { private static final String FONT_ID = "font.plugin.assembly.dual.text.field"; - private static Color FG_PREFERENCE_MOST = + private static final Color FG_PREFERENCE_MOST = new GColor("color.fg.plugin.assembler.completion.most"); - private static Color FG_PREFERENCE_MIDDLE = + private static final Color FG_PREFERENCE_MIDDLE = new GColor("color.fg.plugin.assembler.completion.middle"); - private static Color FG_PREFERENCE_LEAST = + private static final Color FG_PREFERENCE_LEAST = new GColor("color.fg.plugin.assembler.completion.least"); protected final TextFieldLinker linker = new TextFieldLinker(); @@ -175,6 +180,34 @@ public class AssemblyDualTextField { } } + static class ContextChanges implements DisassemblerContextAdapter { + private final RegisterValue contextIn; + private final Map contextsOut = new TreeMap<>(); + + public ContextChanges(RegisterValue contextIn) { + this.contextIn = contextIn; + } + + @Override + public RegisterValue getRegisterValue(Register register) { + if (register.getBaseRegister() == contextIn.getRegister()) { + return contextIn.getRegisterValue(register); + } + return null; + } + + @Override + public void setFutureRegisterValue(Address address, RegisterValue value) { + RegisterValue current = contextsOut.get(address); + RegisterValue combined = current == null ? value : current.combineValues(value); + contextsOut.put(address, combined); + } + + public void addFlow(ProgramContext progCtx, Address after) { + contextsOut.put(after, progCtx.getFlowValue(contextIn)); + } + } + /** * Represents an encoding for a complete assembly instruction * @@ -183,15 +216,50 @@ public class AssemblyDualTextField { * listener. */ static class AssemblyInstruction extends AssemblyCompletion { - private byte[] data; + private final byte[] data; + private final ContextChanges contextChanges; - public AssemblyInstruction(String text, byte[] data, int preference) { + public AssemblyInstruction(Program program, Language language, Address at, String text, + byte[] data, RegisterValue ctxVal, int preference) { // TODO?: Description to display constructor tree information super("", NumericUtilities.convertBytesToString(data, " "), preference == 10000 ? FG_PREFERENCE_MOST : preference == 5000 ? FG_PREFERENCE_MIDDLE : FG_PREFERENCE_LEAST, -preference); this.data = data; + this.contextChanges = new ContextChanges(ctxVal); + + try { + if (program != null) { + // Handle flow context first + contextChanges.addFlow(program.getProgramContext(), at.addWrap(data.length)); + // drop prototype, just want context changes (globalsets) + language.parse(new ByteMemBufferImpl(at, data, language.isBigEndian()), + contextChanges, false); + } + } + catch (InsufficientBytesException | UnknownInstructionException e) { + Msg.error(this, "Cannot disassembly just-assembled instruction?: " + + NumericUtilities.convertBytesToString(data)); + } + adjustOrderByContextChanges(program); + } + + private void adjustOrderByContextChanges(Program program) { + if (program == null) { + return; + } + ProgramContext ctx = program.getProgramContext(); + Register ctxReg = ctx.getBaseContextRegister(); + for (Entry ent : contextChanges.contextsOut.entrySet()) { + RegisterValue defVal = ctx.getDefaultDisassemblyContext(); + RegisterValue newVal = defVal.combineValues(ent.getValue()); + RegisterValue curVal = + defVal.combineValues(ctx.getRegisterValue(ctxReg, ent.getKey())); + BigInteger changed = + newVal.getUnsignedValueIgnoreMask().xor(curVal.getUnsignedValueIgnoreMask()); + order += changed.bitCount(); + } } /** @@ -257,7 +325,8 @@ public class AssemblyDualTextField { * the linked text boxes when retrieving the prefix. It also delegates the item styling to the * item instances. */ - class AssemblyAutocompleter extends TextFieldAutocompleter { + class AssemblyAutocompleter extends TextFieldAutocompleter + implements AutocompletionListener { public AssemblyAutocompleter(AutocompletionModel model) { super(model); } @@ -332,8 +401,11 @@ public class AssemblyDualTextField { private static final String CMD_EXHAUST = "Exhaust undefined bits"; private static final String CMD_ZERO = "Zero undefined bits"; + private JLabel hints; + @Override protected void addContent(JPanel content) { + JPanel panel = new JPanel(new BorderLayout()); Box controls = Box.createHorizontalBox(); Icon icon = new GIcon("icon.plugin.assembler.question"); EmptyBorderToggleButton button = new EmptyBorderToggleButton(icon); @@ -350,9 +422,95 @@ public class AssemblyDualTextField { }); button.setActionCommand(CMD_EXHAUST); controls.add(button); - content.add(controls, BorderLayout.SOUTH); + panel.add(controls, BorderLayout.SOUTH); + hints = new JLabel(); + panel.add(hints); + content.add(panel, BorderLayout.SOUTH); + + addAutocompletionListener(this); } + @Override + public void completionSelected(AutocompletionEvent ev) { + if (!(ev.getSelection() instanceof AssemblyInstruction ai)) { + hints.setText(""); + return; + } + + Program program = assembler.getProgram(); + if (program == null) { + hints.setText(""); + return; + } + + ProgramContext ctx = program.getProgramContext(); + Register ctxReg = ctx.getBaseContextRegister(); + StringBuilder sb = new StringBuilder(""" +
    + """.formatted(ListingColors.REGISTER.toHexString())); + boolean displayedAny = false; + for (Entry ent : ai.contextChanges.contextsOut.entrySet()) { + RegisterValue defVal = ctx.getDefaultDisassemblyContext(); + RegisterValue newVal = defVal.combineValues(ent.getValue()); + RegisterValue curVal = + defVal.combineValues(ctx.getRegisterValue(ctxReg, ent.getKey())); + + boolean displayedAddress = false; + for (Register sub : ctxReg.getChildRegisters()) { + BigInteger newSubVal = + newVal.getRegisterValue(sub).getUnsignedValueIgnoreMask(); + BigInteger curSubVal = + curVal.getRegisterValue(sub).getUnsignedValueIgnoreMask(); + if (Objects.equals(curSubVal, newSubVal)) { + continue; + } + if (!displayedAddress) { + sb.append(""" +
  • At %s
  • +
      + """.formatted(ent.getKey())); + displayedAddress = true; + } + sb.append(""" +
    • %s := 0x%s
    • + """.formatted(sub.getName(), newSubVal.toString(16))); + displayedAny = true; + } + if (displayedAddress) { + sb.append(""" +
    + """); + } + } + if (!displayedAny) { + hints.setText(""); + } + sb.append(""" +
+ """); + hints.setText(sb.toString()); + } + + @Override + public void completionActivated(AutocompletionEvent e) { + } } /** @@ -477,6 +635,7 @@ public class AssemblyDualTextField { /** * For single mode: Get the text field containing the full assembly text + * * @return the text field */ public JTextField getAssemblyField() { @@ -749,13 +908,17 @@ public class AssemblyDualTextField { } } } - // HACK (Sort of): circumvents the API to get full text. + + Program program = assembler.getProgram(); + Language language = assembler.getLanguage(); + Register ctxReg = language.getContextBaseRegister(); + RegisterValue ctxVal = new RegisterValue(ctxReg, ctx.toBigInteger(ctxReg.getNumBytes())); + // HACK (Sort of): Don't use text passed in. Get full text. String fullText = getText(); parses = assembler.parseLine(fullText); for (AssemblyParseResult parse : parses) { if (!parse.isError()) { - AssemblyResolutionResults sems = - assembler.resolveTree(parse, address, ctx); + AssemblyResolutionResults sems = assembler.resolveTree(parse, address, ctx); for (AssemblyResolution ar : sems) { if (ar.isError()) { //result.add(new AssemblyError("", ar.toString())); @@ -763,8 +926,9 @@ public class AssemblyDualTextField { } AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar; for (byte[] ins : rc.possibleInsVals(ctx)) { - result.add(new AssemblyInstruction(text, Arrays.copyOf(ins, ins.length), - computePreference(rc))); + AssemblyInstruction ai = new AssemblyInstruction(program, language, address, + text, Arrays.copyOf(ins, ins.length), ctxVal, computePreference(rc)); + result.add(ai); if (!exhaustUndefined) { break; } @@ -772,6 +936,7 @@ public class AssemblyDualTextField { } } } + if (result.isEmpty()) { result.add(new AssemblyError("", "Invalid instruction and/or prefix")); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchInstructionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchInstructionAction.java index 5f01183ef4..058c4e19d1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchInstructionAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/assembler/PatchInstructionAction.java @@ -218,8 +218,8 @@ public class PatchInstructionAction extends AbstractPatchAction { protected void prepare() { CodeUnit cu = getCodeUnit(); language = getLanguage(cu); - warnLanguage(); cache.get(language).get(null); + warnLanguage(); assembler = getAssembler(cu); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/AutocompletionListener.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/AutocompletionListener.java index 5f3452c2d0..864343825a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/AutocompletionListener.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/AutocompletionListener.java @@ -22,11 +22,25 @@ package docking.widgets.autocomplete; * @see TextFieldAutocompleter */ public interface AutocompletionListener { + + /** + * The user has selected a suggested item. + * + *

+ * This means the user has highlighted an item, but has not activated that item. + * + * @param ev the event describing the selection + */ + default public void completionSelected(AutocompletionEvent ev) { + } + /** * The user has activated a suggested item. * + *

* This means the user has explicitly activate the item, i.e., pressed enter on or clicked the * item. + * * @param e the event describing the activation */ public void completionActivated(AutocompletionEvent e); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java index 07132bf7d1..263641006e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/autocomplete/TextFieldAutocompleter.java @@ -156,6 +156,7 @@ public class TextFieldAutocompleter { list.setCellRenderer(buildListCellRenderer()); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.addMouseListener(listener); + list.addListSelectionListener(listener); content.add(scrollPane); @@ -563,7 +564,7 @@ public class TextFieldAutocompleter { * @param ev the event * @return true, if no listener cancelled the event */ - protected boolean fireAutocompletionListeners(AutocompletionEvent ev) { + protected boolean fireCompletionActivated(AutocompletionEvent ev) { for (AutocompletionListener l : autocompletionListeners) { if (ev.isConsumed()) { break; @@ -580,7 +581,7 @@ public class TextFieldAutocompleter { } AutocompletionEvent ev = new AutocompletionEvent<>(sel, focus); - if (!fireAutocompletionListeners(ev)) { + if (!fireCompletionActivated(ev)) { return; } try { @@ -592,6 +593,25 @@ public class TextFieldAutocompleter { } } + protected boolean fireCompletionSelected(AutocompletionEvent ev) { + for (AutocompletionListener l : autocompletionListeners) { + if (ev.isConsumed()) { + break; + } + l.completionSelected(ev); + } + return !ev.isCancelled(); + } + + private void completionSelected(T sel) { + if (focus == null) { + return; + } + + AutocompletionEvent ev = new AutocompletionEvent<>(sel, focus); + fireCompletionSelected(ev); + } + /** * Register the given auto-completion listener * @@ -887,8 +907,8 @@ public class TextFieldAutocompleter { /** * A listener to handle all the callbacks */ - protected class MyListener - implements FocusListener, KeyListener, DocumentListener, MouseListener, CaretListener { + protected class MyListener implements FocusListener, KeyListener, DocumentListener, + MouseListener, CaretListener, ListSelectionListener { @Override public void keyTyped(KeyEvent e) { @@ -1031,6 +1051,14 @@ public class TextFieldAutocompleter { public void mouseExited(MouseEvent e) { // Nothing } + + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) { + return; + } + completionSelected(list.getSelectedValue()); + } } /** diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/Assembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/Assembler.java index 9218d58c9b..e657eaaa6a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/Assembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/Assembler.java @@ -25,4 +25,5 @@ import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns; * language. */ public interface Assembler extends GenericAssembler { + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/GenericAssembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/GenericAssembler.java index af059a3c69..5296ee3f66 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/GenericAssembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/GenericAssembler.java @@ -21,11 +21,25 @@ import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParseResult; import ghidra.app.plugin.assembler.sleigh.sem.*; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressOverflowException; -import ghidra.program.model.listing.Instruction; -import ghidra.program.model.listing.InstructionIterator; +import ghidra.program.model.lang.Language; +import ghidra.program.model.listing.*; import ghidra.program.model.mem.MemoryAccessException; public interface GenericAssembler { + /** + * Get the language of this assembler + * + * @return the processor language + */ + public Language getLanguage(); + + /** + * If the assembler is bound to a program, get that program + * + * @return the program, or null + */ + public Program getProgram(); + /** * Assemble a sequence of instructions and place them at the given address. * diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/AbstractSleighAssembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/AbstractSleighAssembler.java index aace5508f4..8045d7a9e6 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/AbstractSleighAssembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/AbstractSleighAssembler.java @@ -104,6 +104,16 @@ public abstract class AbstractSleighAssembler newResolver(Address at, AssemblyParseBranch tree, AssemblyPatternBlock ctx); + @Override + public SleighLanguage getLanguage() { + return lang; + } + + @Override + public Program getProgram() { + return program; + } + @Override public Instruction patchProgram(AssemblyResolvedPatterns res, Address at) throws MemoryAccessException { @@ -193,6 +203,7 @@ public abstract class AbstractSleighAssembler