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.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<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
*
@ -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<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
* item instances.
*/
class AssemblyAutocompleter extends TextFieldAutocompleter<AssemblyCompletion> {
class AssemblyAutocompleter extends TextFieldAutocompleter<AssemblyCompletion>
implements AutocompletionListener<AssemblyCompletion> {
public AssemblyAutocompleter(AutocompletionModel<AssemblyCompletion> 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<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
*
* @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"));
}

View file

@ -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);
}

View file

@ -22,11 +22,25 @@ package docking.widgets.autocomplete;
* @see TextFieldAutocompleter
*/
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.
*
* <p>
* 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<T> e);

View file

@ -156,6 +156,7 @@ public class TextFieldAutocompleter<T> {
list.setCellRenderer(buildListCellRenderer());
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.addMouseListener(listener);
list.addListSelectionListener(listener);
content.add(scrollPane);
@ -563,7 +564,7 @@ public class TextFieldAutocompleter<T> {
* @param ev 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) {
if (ev.isConsumed()) {
break;
@ -580,7 +581,7 @@ public class TextFieldAutocompleter<T> {
}
AutocompletionEvent<T> ev = new AutocompletionEvent<>(sel, focus);
if (!fireAutocompletionListeners(ev)) {
if (!fireCompletionActivated(ev)) {
return;
}
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
*
@ -887,8 +907,8 @@ public class TextFieldAutocompleter<T> {
/**
* 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<T> {
public void mouseExited(MouseEvent e) {
// 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.
*/
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.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<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.
*

View file

@ -104,6 +104,16 @@ public abstract class AbstractSleighAssembler<RP extends AssemblyResolvedPattern
protected abstract AbstractAssemblyTreeResolver<RP> 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<RP extends AssemblyResolvedPattern
@Override
public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at) {
AssemblyPatternBlock ctx = getContextAt(at);
// ctx.fillMask()?
return resolveTree(parse, at, ctx);
}