Merge remote-tracking branch 'origin/GP-1883_Dan_pcodeStepperLabels--SQUASHED'

This commit is contained in:
Ryan Kurtz
2022-04-18 00:45:41 -04:00
7 changed files with 285 additions and 54 deletions
@@ -27,10 +27,15 @@ public class BranchPcodeRow implements PcodeRow {
}
@Override
public Integer getSequence() {
public int getSequence() {
return sequence;
}
@Override
public String getLabel() {
return "";
}
@Override
public String getCode() {
return "(branched from " + fromSeq + ")";
@@ -107,6 +107,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
protected enum PcodeTableColumns implements EnumeratedTableColumn<PcodeTableColumns, PcodeRow> {
SEQUENCE("Sequence", Integer.class, PcodeRow::getSequence),
LABEL("Label", String.class, PcodeRow::getLabel),
CODE("Code", String.class, PcodeRow::getCode);
private final String header;
@@ -331,90 +332,122 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
class ToPcodeRowsAppender extends AbstractAppender<List<PcodeRow>> {
private final List<PcodeRow> rows = new ArrayList<>();
private final PcodeFrame frame;
private StringBuilder html;
private boolean hasLabel;
private StringBuilder labelHtml;
private StringBuilder codeHtml;
private PcodeOp op;
private boolean isNext;
public ToPcodeRowsAppender(Language language, boolean indent, PcodeFrame frame) {
super(language, indent);
public ToPcodeRowsAppender(Language language, PcodeFrame frame) {
super(language, false);
this.frame = frame;
}
void startRow(PcodeOp op, boolean isNext) {
if (hasLabel && this.op == null) {
// Just continue formatting the current label-only row
}
else {
// Reset and actually start a new row
labelHtml = new StringBuilder("<html>");
hasLabel = false;
codeHtml = new StringBuilder("<html>");
}
this.op = op;
this.isNext = isNext;
html = new StringBuilder("<html>");
}
void endRow() {
html.append("</html>");
rows.add(new OpPcodeRow(language, op, isNext, html.toString()));
if (hasLabel && op == null) {
// Don't end, just wait for the code
}
else {
// Actually append the row
labelHtml.append("</html>");
codeHtml.append("</html>");
rows.add(new OpPcodeRow(language, op, isNext, labelHtml.toString(),
codeHtml.toString()));
hasLabel = false;
op = null;
}
}
@Override
public void appendAddressWordOffcut(long wordOffset, long offcut) {
html.append(htmlSpan(SPAN_ADDRESS, stringifyWordOffcut(wordOffset, offcut)));
codeHtml.append(htmlSpan(SPAN_ADDRESS, stringifyWordOffcut(wordOffset, offcut)));
}
@Override
public void appendCharacter(char c) {
if (c == '=') {
html.append(htmlSpan(SPAN_SEPARATOR, " = "));
codeHtml.append("&nbsp;");
codeHtml.append(htmlSpan(SPAN_SEPARATOR, "="));
codeHtml.append("&nbsp;");
}
else if (c == ' ') {
codeHtml.append("&nbsp;");
}
else {
html.append(htmlSpan(SPAN_SEPARATOR, Character.toString(c)));
codeHtml.append(htmlSpan(SPAN_SEPARATOR, Character.toString(c)));
}
}
@Override
public void appendIndent() {
html.append("&nbsp;&nbsp;");
}
@Override
public void appendLabel(String label) {
html.append(htmlSpan(SPAN_LOCAL, label));
codeHtml.append(htmlSpan(SPAN_LOCAL, label));
}
@Override
public void appendLineLabel(long label) {
hasLabel = true;
labelHtml.append(htmlSpan(SPAN_LINE_LABEL, stringifyLineLabel(label)));
}
@Override
public void appendLineLabelRef(long label) {
html.append(htmlSpan(SPAN_LINE_LABEL, stringifyLineLabel(label)));
codeHtml.append(htmlSpan(SPAN_LINE_LABEL, stringifyLineLabel(label)));
}
@Override
public void appendMnemonic(int opcode) {
String style = opcode == PcodeOp.UNIMPLEMENTED ? SPAN_UNIMPL : SPAN_MNEMONIC;
html.append(htmlSpan(style, stringifyOpMnemonic(opcode)));
codeHtml.append(htmlSpan(style, stringifyOpMnemonic(opcode)));
}
@Override
public void appendRawVarnode(AddressSpace space, long offset, long size) {
html.append(htmlSpan(SPAN_RAW, stringifyRawVarnode(space, offset, size)));
codeHtml.append(htmlSpan(SPAN_RAW, stringifyRawVarnode(space, offset, size)));
}
@Override
public void appendRegister(Register register) {
html.append(htmlSpan(SPAN_REGISTER, stringifyRegister(register)));
codeHtml.append(htmlSpan(SPAN_REGISTER, stringifyRegister(register)));
}
@Override
public void appendScalar(long value) {
html.append(htmlSpan(SPAN_SCALAR, stringifyScalarValue(value)));
codeHtml.append(htmlSpan(SPAN_SCALAR, stringifyScalarValue(value)));
}
@Override
public void appendSpace(AddressSpace space) {
html.append(htmlSpan(SPAN_SPACE, stringifySpace(space)));
codeHtml.append(htmlSpan(SPAN_SPACE, stringifySpace(space)));
}
@Override
public void appendUnique(long offset) {
html.append(htmlSpan(SPAN_LOCAL, stringifyUnique(offset)));
codeHtml.append(htmlSpan(SPAN_LOCAL, stringifyUnique(offset)));
}
@Override
public void appendUserop(int id) {
html.append(htmlSpan(SPAN_USEROP, stringifyUserop(language, id)));
codeHtml.append(htmlSpan(SPAN_USEROP, stringifyUserop(language, id)));
}
@Override
@@ -428,6 +461,15 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
@Override
public List<PcodeRow> finish() {
String label;
if (hasLabel) {
labelHtml.append("</html>");
label = labelHtml.toString();
}
else {
label = "";
}
rows.add(new FallthroughPcodeRow(frame.getCode().size(), frame.isFallThrough(), label));
return rows;
}
}
@@ -451,7 +493,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
@Override
protected ToPcodeRowsAppender createAppender(Language language, boolean indent) {
return new ToPcodeRowsAppender(language, indent, frame);
return new ToPcodeRowsAppender(language, frame);
}
@Override
@@ -541,6 +583,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
PcodeTableModel pcodeTableModel = new PcodeTableModel();
JLabel instructionLabel;
// No filter panel on p-code
PcodeCellRenderer codeColRenderer;
DockingAction actionStepBackward;
DockingAction actionStepForward;
@@ -696,16 +739,31 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
pcodeTableModel.fireTableDataChanged();
}
protected int computeSeqColWidth(JLabel renderer) {
protected int measureColWidth(JLabel renderer, String sample) {
Font font = renderer.getFont();
Insets insets = renderer.getBorder().getBorderInsets(renderer);
return (int) font.getStringBounds("00", METRIC_FRC).getWidth() + insets.left + insets.right;
return (int) font.getStringBounds(sample, METRIC_FRC).getWidth() + insets.left +
insets.right;
}
protected int measureWidthHtml(JLabel renderer, String sampleHtml) {
String sampleText = HTMLUtilities.fromHTML(sampleHtml);
return measureColWidth(renderer, sampleText);
}
protected void buildMainPanel() {
JPanel pcodePanel = new JPanel(new BorderLayout());
// An intervening panel to interrupt swings table-viewport nonsense
// This will allow the viewport to properly fit the table's contents
JPanel pcodeTablePanel = new JPanel(new BorderLayout());
pcodeTable = new GhidraTable(pcodeTableModel);
pcodePanel.add(new JScrollPane(pcodeTable));
pcodeTablePanel.add(pcodeTable, BorderLayout.CENTER);
JScrollPane pcodeScrollPane = new JScrollPane(pcodeTablePanel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
JPanel pcodePanel = new JPanel(new BorderLayout());
pcodePanel.add(pcodeScrollPane, BorderLayout.CENTER);
instructionLabel = new JLabel();
pcodePanel.add(instructionLabel, BorderLayout.NORTH);
mainPanel.setLeftComponent(pcodePanel);
@@ -732,12 +790,17 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
TableColumn seqCol = pcodeColModel.getColumn(PcodeTableColumns.SEQUENCE.ordinal());
CounterBackgroundCellRenderer seqColRenderer = new CounterBackgroundCellRenderer();
seqCol.setCellRenderer(seqColRenderer);
int seqColWidth = computeSeqColWidth(seqColRenderer);
int seqColWidth = measureColWidth(seqColRenderer, "00");
seqCol.setMinWidth(seqColWidth);
seqCol.setMaxWidth(seqColWidth);
TableColumn labelCol = pcodeColModel.getColumn(PcodeTableColumns.LABEL.ordinal());
codeColRenderer = new PcodeCellRenderer();
labelCol.setCellRenderer(codeColRenderer);
int labelColWidth = measureColWidth(codeColRenderer, "<00>");
labelCol.setMinWidth(labelColWidth);
labelCol.setMaxWidth(labelColWidth);
TableColumn codeCol = pcodeColModel.getColumn(PcodeTableColumns.CODE.ordinal());
codeCol.setCellRenderer(new PcodeCellRenderer());
//codeCol.setPreferredWidth(75);
codeCol.setCellRenderer(codeColRenderer);
TableColumnModel uniqueColModel = uniqueTable.getColumnModel();
TableColumn refCol = uniqueColModel.getColumn(UniqueTableColumns.REF.ordinal());
@@ -811,9 +874,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
}
protected void populateSingleton(PcodeRow row) {
pcodeTableModel.clear();
pcodeTableModel.add(row);
uniqueTableModel.clear();
}
protected void populateFromFrame(PcodeFrame frame, PcodeExecutorState<byte[]> state) {
@@ -821,22 +882,37 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
populateUnique(frame, state);
}
protected int computeCodeColWidth(List<PcodeRow> rows) {
return rows.stream()
.map(r -> measureWidthHtml(codeColRenderer, r.getCode()))
.reduce(0, Integer::max);
}
protected void adjustCodeColWidth(List<PcodeRow> rows) {
TableColumn codeCol =
pcodeTable.getColumnModel().getColumn(PcodeTableColumns.CODE.ordinal());
int width = computeCodeColWidth(rows);
codeCol.setMinWidth(width);
codeCol.setPreferredWidth(width);
}
protected void populatePcode(PcodeFrame frame) {
Language language = current.getTrace().getBaseLanguage();
PcodeRowHtmlFormatter formatter = new PcodeRowHtmlFormatter(language, frame);
List<PcodeRow> toAdd = formatter.getRows();
int sel = formatter.nextRowIndex;
if (frame.isBranch()) {
toAdd.add(new BranchPcodeRow(frame.getCode().size(), frame.getBranched()));
sel = frame.getCode().size() + 1;
toAdd.add(new BranchPcodeRow(sel, frame.getBranched()));
}
else if (frame.isFallThrough()) {
toAdd.add(new FallthroughPcodeRow(frame.getCode().size()));
sel = frame.getCode().size();
}
pcodeTableModel.clear();
adjustCodeColWidth(toAdd);
pcodeTableModel.addAll(toAdd);
pcodeTable.getSelectionModel()
.setSelectionInterval(formatter.nextRowIndex, formatter.nextRowIndex);
pcodeTable.getSelectionModel().setSelectionInterval(sel, sel);
pcodeTable.scrollToSelectedRow();
}
@@ -862,37 +938,47 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
uniques.stream()
.map(u -> new UniqueRow(this, language, state, u))
.collect(Collectors.toList());
uniqueTableModel.clear();
uniqueTableModel.addAll(toAdd);
}
protected void clear() {
pcodeTableModel.clear();
uniqueTableModel.clear();
}
protected void doLoadPcodeFrame() {
if (instructionLabel != null) {
instructionLabel.setText("(no instruction)");
}
if (emulationService == null) {
clear();
return;
}
DebuggerCoordinates current = this.current; // Volatile, also after background
Trace trace = current.getTrace();
if (trace == null) {
clear();
return;
}
if (current.getThread() == null) {
clear();
populateSingleton(EnumPcodeRow.NO_THREAD);
return;
}
TraceSchedule time = current.getTime();
if (time.pTickCount() == 0) {
clear();
populateSingleton(EnumPcodeRow.DECODE);
return;
}
DebuggerTracePcodeEmulator emu = emulationService.getCachedEmulator(trace, time);
if (emu != null) {
clear();
doLoadPcodeFrameFromEmulator(emu);
return;
}
emulationService.backgroundEmulate(trace, time).thenAcceptAsync(__ -> {
clear();
if (current != this.current) {
return;
}
@@ -27,10 +27,15 @@ public enum EnumPcodeRow implements PcodeRow {
}
@Override
public Integer getSequence() {
public int getSequence() {
return 0;
}
@Override
public String getLabel() {
return "";
}
@Override
public String getCode() {
return message;
@@ -19,16 +19,25 @@ import ghidra.program.model.pcode.PcodeOp;
public class FallthroughPcodeRow implements PcodeRow {
private final int sequence;
private final boolean isNext;
private final String label;
public FallthroughPcodeRow(int sequence) {
public FallthroughPcodeRow(int sequence, boolean isNext, String label) {
this.sequence = sequence;
this.isNext = isNext;
this.label = label;
}
@Override
public Integer getSequence() {
public int getSequence() {
return sequence;
}
@Override
public String getLabel() {
return label;
}
@Override
public String getCode() {
return "(fall-through)";
@@ -36,7 +45,7 @@ public class FallthroughPcodeRow implements PcodeRow {
@Override
public boolean isNext() {
return true;
return isNext;
}
@Override
@@ -15,6 +15,8 @@
*/
package ghidra.app.plugin.core.debug.gui.pcode;
import java.util.Objects;
import ghidra.program.model.lang.Language;
import ghidra.program.model.pcode.PcodeOp;
@@ -22,23 +24,27 @@ public class OpPcodeRow implements PcodeRow {
protected final Language language;
protected final PcodeOp op;
protected final boolean isNext;
protected final String label;
protected final String code;
public OpPcodeRow(Language language, PcodeOp op, boolean isNext, String code) {
public OpPcodeRow(Language language, PcodeOp op, boolean isNext, String label, String code) {
this.language = language;
this.op = op;
this.op = Objects.requireNonNull(op);
this.isNext = isNext;
this.label = label;
this.code = code;
}
@Override
public Integer getSequence() {
if (op == null) {
return null;
}
public int getSequence() {
return op.getSeqnum().getTime();
}
@Override
public String getLabel() {
return label;
}
@Override
public String getCode() {
return code;
@@ -18,7 +18,9 @@ package ghidra.app.plugin.core.debug.gui.pcode;
import ghidra.program.model.pcode.PcodeOp;
public interface PcodeRow {
Integer getSequence();
int getSequence();
String getLabel();
String getCode();
@@ -17,6 +17,8 @@ package ghidra.app.plugin.core.debug.gui.pcode;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
@@ -26,12 +28,14 @@ import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.pcode.DebuggerPcodeStepperProvider.PcodeRowHtmlFormatter;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.DebuggerEmulationService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.PcodeExecutor;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.trace.TraceSleighUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Instruction;
@@ -50,6 +54,11 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg
protected DebuggerPcodeStepperProvider pcodeProvider;
protected DebuggerEmulationService emuService;
private Address start;
private TraceThread thread;
private Instruction imm1234;
private Instruction imm2045;
@Before
public void setUpPcodeStepperProviderTest() throws Exception {
traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
@@ -62,10 +71,8 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg
createTrace();
}
@Test
public void testCustomUseropDisplay() throws Exception {
Address start = tb.addr(0x00400000);
TraceThread thread;
protected void populateTrace() throws Exception {
start = tb.addr(0x00400000);
InstructionIterator iit;
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getMemoryManager()
@@ -83,8 +90,45 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg
"imm r1, #2045"); // 11 bits unsigned
}
Instruction imm1234 = iit.next();
Instruction imm2045 = iit.next();
imm1234 = iit.next();
imm2045 = iit.next();
}
protected void assertEmpty() {
assertTrue(pcodeProvider.pcodeTableModel.getModelData().isEmpty());
assertTrue(pcodeProvider.uniqueTableModel.getModelData().isEmpty());
}
protected void assertPopulated() {
assertFalse(pcodeProvider.pcodeTableModel.getModelData().isEmpty());
// NB. I don't know what uniques, if any, are involved
}
@Test
public void testEmpty() throws Exception {
assertEmpty();
}
@Test
public void testCloseCurrentTraceEmpty() throws Exception {
populateTrace();
TraceSchedule schedule1 = TraceSchedule.parse("0:.t0-1");
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
assertEmpty();
traceManager.activateTime(schedule1);
waitForPass(() -> assertEquals(schedule1, pcodeProvider.current.getTime()));
waitForPass(() -> assertPopulated());
traceManager.closeTrace(tb.trace);
waitForPass(() -> assertEmpty());
}
@Test
public void testCustomUseropDisplay() throws Exception {
populateTrace();
TraceSchedule schedule1 = TraceSchedule.parse("0:.t0-1");
traceManager.openTrace(tb.trace);
@@ -121,4 +165,78 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg
.stream()
.anyMatch(r -> r.getCode().contains("emu_swi"))));
}
protected List<PcodeRow> format(List<String> sleigh) {
SleighLanguage language = (SleighLanguage) getToyBE64Language();
PcodeProgram prog = SleighProgramCompiler.compileProgram(language, "test", sleigh,
SleighUseropLibrary.nil());
PcodeExecutor<byte[]> executor =
new PcodeExecutor<>(language, PcodeArithmetic.BYTES_BE, null);
PcodeFrame frame = executor.begin(prog);
PcodeRowHtmlFormatter formatter = pcodeProvider.new PcodeRowHtmlFormatter(language, frame);
return formatter.getRows();
}
@Test
public void testPcodeFormatterSimple() {
List<PcodeRow> rows = format(List.of("r0 = 1;"));
assertEquals(2, rows.size());
assertEquals("<html></html>", rows.get(0).getLabel());
assertEquals(FallthroughPcodeRow.class, rows.get(1).getClass());
}
@Test
public void testPcodeFormatterStartsLabel() {
List<PcodeRow> rows = format(List.of(
"<L0> r0 = 1;",
"goto <L0>;"));
assertEquals(3, rows.size());
assertEquals("<html><span class=\"lab\">&lt;0&gt;</span></html>", rows.get(0).getLabel());
assertEquals("<html></html>", rows.get(1).getLabel());
assertEquals(FallthroughPcodeRow.class, rows.get(2).getClass());
}
@Test
public void testPcodeFormatterMiddleLabel() {
List<PcodeRow> rows = format(List.of(
"if 1:1 goto <SKIP>;",
"r0 = 1;",
"<SKIP> r1 = 2;"));
assertEquals(4, rows.size());
assertEquals("<html></html>", rows.get(0).getLabel());
assertEquals("<html></html>", rows.get(1).getLabel());
assertEquals("<html><span class=\"lab\">&lt;0&gt;</span></html>", rows.get(2).getLabel());
assertEquals(FallthroughPcodeRow.class, rows.get(3).getClass());
}
@Test
public void testPcodeFormatterFallthroughLabel() {
List<PcodeRow> rows = format(List.of(
"if 1:1 goto <SKIP>;",
"r0 = 1;",
"<SKIP>"));
assertEquals(3, rows.size());
assertEquals("<html></html>", rows.get(0).getLabel());
assertEquals("<html></html>", rows.get(1).getLabel());
assertEquals("<html><span class=\"lab\">&lt;0&gt;</span></html>", rows.get(2).getLabel());
assertEquals(FallthroughPcodeRow.class, rows.get(2).getClass());
}
@Test
public void testPcodeFormatterManyLabel() {
List<PcodeRow> rows = format(List.of(
"<L0> goto <L1>;",
"<L1> goto <L2>;",
"<L2> goto <L3>;",
"goto <L0>;",
"<L3>"));
assertEquals(5, rows.size());
// NB. templates number labels in order of appearance in BRANCHes
assertEquals("<html><span class=\"lab\">&lt;3&gt;</span></html>", rows.get(0).getLabel());
assertEquals("<html><span class=\"lab\">&lt;0&gt;</span></html>", rows.get(1).getLabel());
assertEquals("<html><span class=\"lab\">&lt;1&gt;</span></html>", rows.get(2).getLabel());
assertEquals("<html></html>", rows.get(3).getLabel());
assertEquals("<html><span class=\"lab\">&lt;2&gt;</span></html>", rows.get(4).getLabel());
assertEquals(FallthroughPcodeRow.class, rows.get(4).getClass());
}
}