Merge remote-tracking branch 'origin/GP-3993_Dan_asmContextHint--SQUASHED'

This commit is contained in:
Ryan Kurtz 2024-04-03 13:04:07 -04:00
commit baaadc2143
7 changed files with 254 additions and 21 deletions

View file

@ -17,7 +17,9 @@ package ghidra.app.plugin.core.assembler;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.math.BigInteger;
import java.util.*; import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.swing.*; 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.parse.AssemblyParseResult;
import ghidra.app.plugin.assembler.sleigh.sem.*; import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.processors.sleigh.*; import ghidra.app.plugin.processors.sleigh.*;
import ghidra.app.util.viewer.field.ListingColors;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration; import ghidra.framework.ApplicationConfiguration;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction; import ghidra.program.model.listing.*;
import ghidra.program.model.mem.ByteMemBufferImpl;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities; import ghidra.util.NumericUtilities;
/** /**
@ -62,11 +67,11 @@ import ghidra.util.NumericUtilities;
*/ */
public class AssemblyDualTextField { public class AssemblyDualTextField {
private static final String FONT_ID = "font.plugin.assembly.dual.text.field"; 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"); 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"); 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"); new GColor("color.fg.plugin.assembler.completion.least");
protected final TextFieldLinker linker = new TextFieldLinker(); 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<Address, RegisterValue> 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 * Represents an encoding for a complete assembly instruction
* *
@ -183,15 +216,50 @@ public class AssemblyDualTextField {
* listener. * listener.
*/ */
static class AssemblyInstruction extends AssemblyCompletion { 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 // TODO?: Description to display constructor tree information
super("", NumericUtilities.convertBytesToString(data, " "), super("", NumericUtilities.convertBytesToString(data, " "),
preference == 10000 ? FG_PREFERENCE_MOST preference == 10000 ? FG_PREFERENCE_MOST
: preference == 5000 ? FG_PREFERENCE_MIDDLE : FG_PREFERENCE_LEAST, : preference == 5000 ? FG_PREFERENCE_MIDDLE : FG_PREFERENCE_LEAST,
-preference); -preference);
this.data = data; 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<Address, RegisterValue> 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 * the linked text boxes when retrieving the prefix. It also delegates the item styling to the
* item instances. * item instances.
*/ */
class AssemblyAutocompleter extends TextFieldAutocompleter<AssemblyCompletion> { class AssemblyAutocompleter extends TextFieldAutocompleter<AssemblyCompletion>
implements AutocompletionListener<AssemblyCompletion> {
public AssemblyAutocompleter(AutocompletionModel<AssemblyCompletion> model) { public AssemblyAutocompleter(AutocompletionModel<AssemblyCompletion> model) {
super(model); super(model);
} }
@ -332,8 +401,11 @@ public class AssemblyDualTextField {
private static final String CMD_EXHAUST = "Exhaust undefined bits"; private static final String CMD_EXHAUST = "Exhaust undefined bits";
private static final String CMD_ZERO = "Zero undefined bits"; private static final String CMD_ZERO = "Zero undefined bits";
private JLabel hints;
@Override @Override
protected void addContent(JPanel content) { protected void addContent(JPanel content) {
JPanel panel = new JPanel(new BorderLayout());
Box controls = Box.createHorizontalBox(); Box controls = Box.createHorizontalBox();
Icon icon = new GIcon("icon.plugin.assembler.question"); Icon icon = new GIcon("icon.plugin.assembler.question");
EmptyBorderToggleButton button = new EmptyBorderToggleButton(icon); EmptyBorderToggleButton button = new EmptyBorderToggleButton(icon);
@ -350,9 +422,95 @@ public class AssemblyDualTextField {
}); });
button.setActionCommand(CMD_EXHAUST); button.setActionCommand(CMD_EXHAUST);
controls.add(button); 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<AssemblyCompletion> 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("""
<html><style>
ul.addresses {
margin: 0;
padding: 0;
}
ul.addresses > li {
margin: 0;
padding: 0;
list-style-type: none;
}
ul.context {
font-family: monospaced;
margin: 0 0 0 20px;
}
span.addr {
font-family: monospaced;
}
</style><body width="300px"><ul class="addresses">
""".formatted(ListingColors.REGISTER.toHexString()));
boolean displayedAny = false;
for (Entry<Address, RegisterValue> 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("""
<li>At <span class="addr">%s</span></li>
<ul class="context">
""".formatted(ent.getKey()));
displayedAddress = true;
}
sb.append("""
<li>%s := 0x%s</li>
""".formatted(sub.getName(), newSubVal.toString(16)));
displayedAny = true;
}
if (displayedAddress) {
sb.append("""
</ul>
""");
}
}
if (!displayedAny) {
hints.setText("");
}
sb.append("""
</ul></body></html>
""");
hints.setText(sb.toString());
}
@Override
public void completionActivated(AutocompletionEvent<AssemblyCompletion> e) {
}
} }
/** /**
@ -477,6 +635,7 @@ public class AssemblyDualTextField {
/** /**
* For single mode: Get the text field containing the full assembly text * For single mode: Get the text field containing the full assembly text
*
* @return the text field * @return the text field
*/ */
public JTextField getAssemblyField() { 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(); String fullText = getText();
parses = assembler.parseLine(fullText); parses = assembler.parseLine(fullText);
for (AssemblyParseResult parse : parses) { for (AssemblyParseResult parse : parses) {
if (!parse.isError()) { if (!parse.isError()) {
AssemblyResolutionResults sems = AssemblyResolutionResults sems = assembler.resolveTree(parse, address, ctx);
assembler.resolveTree(parse, address, ctx);
for (AssemblyResolution ar : sems) { for (AssemblyResolution ar : sems) {
if (ar.isError()) { if (ar.isError()) {
//result.add(new AssemblyError("", ar.toString())); //result.add(new AssemblyError("", ar.toString()));
@ -763,8 +926,9 @@ public class AssemblyDualTextField {
} }
AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar; AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar;
for (byte[] ins : rc.possibleInsVals(ctx)) { for (byte[] ins : rc.possibleInsVals(ctx)) {
result.add(new AssemblyInstruction(text, Arrays.copyOf(ins, ins.length), AssemblyInstruction ai = new AssemblyInstruction(program, language, address,
computePreference(rc))); text, Arrays.copyOf(ins, ins.length), ctxVal, computePreference(rc));
result.add(ai);
if (!exhaustUndefined) { if (!exhaustUndefined) {
break; break;
} }
@ -772,6 +936,7 @@ public class AssemblyDualTextField {
} }
} }
} }
if (result.isEmpty()) { if (result.isEmpty()) {
result.add(new AssemblyError("", "Invalid instruction and/or prefix")); result.add(new AssemblyError("", "Invalid instruction and/or prefix"));
} }

View file

@ -218,8 +218,8 @@ public class PatchInstructionAction extends AbstractPatchAction {
protected void prepare() { protected void prepare() {
CodeUnit cu = getCodeUnit(); CodeUnit cu = getCodeUnit();
language = getLanguage(cu); language = getLanguage(cu);
warnLanguage();
cache.get(language).get(null); cache.get(language).get(null);
warnLanguage();
assembler = getAssembler(cu); assembler = getAssembler(cu);
} }

View file

@ -22,11 +22,25 @@ package docking.widgets.autocomplete;
* @see TextFieldAutocompleter * @see TextFieldAutocompleter
*/ */
public interface AutocompletionListener<T> { public interface AutocompletionListener<T> {
/**
* The user has selected a suggested item.
*
* <p>
* This means the user has highlighted an item, but has <em>not</em> activated that item.
*
* @param ev the event describing the selection
*/
default public void completionSelected(AutocompletionEvent<T> ev) {
}
/** /**
* The user has activated a suggested item. * The user has activated a suggested item.
* *
* <p>
* This means the user has explicitly activate the item, i.e., pressed enter on or clicked the * This means the user has explicitly activate the item, i.e., pressed enter on or clicked the
* item. * item.
*
* @param e the event describing the activation * @param e the event describing the activation
*/ */
public void completionActivated(AutocompletionEvent<T> e); public void completionActivated(AutocompletionEvent<T> e);

View file

@ -156,6 +156,7 @@ public class TextFieldAutocompleter<T> {
list.setCellRenderer(buildListCellRenderer()); list.setCellRenderer(buildListCellRenderer());
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.addMouseListener(listener); list.addMouseListener(listener);
list.addListSelectionListener(listener);
content.add(scrollPane); content.add(scrollPane);
@ -563,7 +564,7 @@ public class TextFieldAutocompleter<T> {
* @param ev the event * @param ev the event
* @return true, if no listener cancelled the event * @return true, if no listener cancelled the event
*/ */
protected boolean fireAutocompletionListeners(AutocompletionEvent<T> ev) { protected boolean fireCompletionActivated(AutocompletionEvent<T> ev) {
for (AutocompletionListener<T> l : autocompletionListeners) { for (AutocompletionListener<T> l : autocompletionListeners) {
if (ev.isConsumed()) { if (ev.isConsumed()) {
break; break;
@ -580,7 +581,7 @@ public class TextFieldAutocompleter<T> {
} }
AutocompletionEvent<T> ev = new AutocompletionEvent<>(sel, focus); AutocompletionEvent<T> ev = new AutocompletionEvent<>(sel, focus);
if (!fireAutocompletionListeners(ev)) { if (!fireCompletionActivated(ev)) {
return; return;
} }
try { try {
@ -592,6 +593,25 @@ public class TextFieldAutocompleter<T> {
} }
} }
protected boolean fireCompletionSelected(AutocompletionEvent<T> ev) {
for (AutocompletionListener<T> l : autocompletionListeners) {
if (ev.isConsumed()) {
break;
}
l.completionSelected(ev);
}
return !ev.isCancelled();
}
private void completionSelected(T sel) {
if (focus == null) {
return;
}
AutocompletionEvent<T> ev = new AutocompletionEvent<>(sel, focus);
fireCompletionSelected(ev);
}
/** /**
* Register the given auto-completion listener * Register the given auto-completion listener
* *
@ -887,8 +907,8 @@ public class TextFieldAutocompleter<T> {
/** /**
* A listener to handle all the callbacks * A listener to handle all the callbacks
*/ */
protected class MyListener protected class MyListener implements FocusListener, KeyListener, DocumentListener,
implements FocusListener, KeyListener, DocumentListener, MouseListener, CaretListener { MouseListener, CaretListener, ListSelectionListener {
@Override @Override
public void keyTyped(KeyEvent e) { public void keyTyped(KeyEvent e) {
@ -1031,6 +1051,14 @@ public class TextFieldAutocompleter<T> {
public void mouseExited(MouseEvent e) { public void mouseExited(MouseEvent e) {
// Nothing // Nothing
} }
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
completionSelected(list.getSelectedValue());
}
} }
/** /**

View file

@ -25,4 +25,5 @@ import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
* language. * language.
*/ */
public interface Assembler extends GenericAssembler<AssemblyResolvedPatterns> { public interface Assembler extends GenericAssembler<AssemblyResolvedPatterns> {
} }

View file

@ -21,11 +21,25 @@ import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParseResult;
import ghidra.app.plugin.assembler.sleigh.sem.*; import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException; import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.listing.Instruction; import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.InstructionIterator; import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryAccessException;
public interface GenericAssembler<RP extends AssemblyResolvedPatterns> { public interface GenericAssembler<RP extends AssemblyResolvedPatterns> {
/**
* 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. * Assemble a sequence of instructions and place them at the given address.
* *

View file

@ -104,6 +104,16 @@ public abstract class AbstractSleighAssembler<RP extends AssemblyResolvedPattern
protected abstract AbstractAssemblyTreeResolver<RP> newResolver(Address at, protected abstract AbstractAssemblyTreeResolver<RP> newResolver(Address at,
AssemblyParseBranch tree, AssemblyPatternBlock ctx); AssemblyParseBranch tree, AssemblyPatternBlock ctx);
@Override
public SleighLanguage getLanguage() {
return lang;
}
@Override
public Program getProgram() {
return program;
}
@Override @Override
public Instruction patchProgram(AssemblyResolvedPatterns res, Address at) public Instruction patchProgram(AssemblyResolvedPatterns res, Address at)
throws MemoryAccessException { throws MemoryAccessException {
@ -193,6 +203,7 @@ public abstract class AbstractSleighAssembler<RP extends AssemblyResolvedPattern
@Override @Override
public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at) { public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at) {
AssemblyPatternBlock ctx = getContextAt(at); AssemblyPatternBlock ctx = getContextAt(at);
// ctx.fillMask()?
return resolveTree(parse, at, ctx); return resolveTree(parse, at, ctx);
} }