From 4f444f9db3fd2677df5295a90a830ab53ba43da2 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Tue, 23 Nov 2021 10:56:50 -0500 Subject: [PATCH] GP-1225: Fixing display of userops in toString and p-code stepper --- .../pcode/DebuggerPcodeStepperProvider.java | 8 +- .../core/debug/gui/pcode/OpPcodeRow.java | 9 +- .../services/DebuggerEmulationService.java | 6 +- .../DebuggerPcodeStepperProviderTest.java | 122 ++++++++++++++++++ .../ghidra/pcode/emu/DefaultPcodeThread.java | 2 +- .../java/ghidra/pcode/exec/PcodeFrame.java | 7 +- .../java/ghidra/pcode/exec/PcodeProgram.java | 13 +- .../src/main/java/ghidra/util/ColorUtils.java | 7 +- .../assembler/sleigh/SleighAssembler.java | 5 +- 9 files changed, 163 insertions(+), 16 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProviderTest.java diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java index 66dc66933b..b024e7f742 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java @@ -215,7 +215,10 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { boolean isCurrent = counter == data.getRowModelIndex(); if (data.isSelected()) { if (isCurrent) { - setBackground(ColorUtils.blend(counterColor, cursorColor, 0.5f)); + Color blend = ColorUtils.blend(counterColor, cursorColor, 0.5f); + if (blend != null) { + setBackground(blend); + } } // else background is already set. Leave it alone } @@ -574,7 +577,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { int index = frame.index(); List toAdd = frame.getCode() .stream() - .map(op -> new OpPcodeRow(language, op, index == op.getSeqnum().getTime())) + .map(op -> new OpPcodeRow(language, op, index == op.getSeqnum().getTime(), + frame.getUseropNames())) .collect(Collectors.toCollection(ArrayList::new)); if (frame.isBranch()) { counter = toAdd.size(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/OpPcodeRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/OpPcodeRow.java index 81a3165165..b84ca78455 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/OpPcodeRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/OpPcodeRow.java @@ -15,6 +15,8 @@ */ package ghidra.app.plugin.core.debug.gui.pcode; +import java.util.Map; + import ghidra.pcode.exec.PcodeProgram; import ghidra.program.model.lang.Language; import ghidra.program.model.pcode.PcodeOp; @@ -23,11 +25,14 @@ public class OpPcodeRow implements PcodeRow { protected final Language language; protected final PcodeOp op; protected final boolean isNext; + protected final Map useropNames; - public OpPcodeRow(Language language, PcodeOp op, boolean isNext) { + public OpPcodeRow(Language language, PcodeOp op, boolean isNext, + Map useropNames) { this.language = language; this.op = op; this.isNext = isNext; + this.useropNames = useropNames; } @Override @@ -37,7 +42,7 @@ public class OpPcodeRow implements PcodeRow { @Override public String getCode() { - return "" + PcodeProgram.opToString(language, op, true) + ""; + return "" + PcodeProgram.opToString(language, op, true, useropNames) + ""; } public boolean isNext() { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java index 64ead7e305..a2905ab828 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java @@ -56,7 +56,7 @@ public interface DebuggerEmulationService { * *

* This is the preferred means of performing emulation. Because the underlying emulator may - * request blocking read of a target, it is important that + * request a blocking read from a target, it is important that * {@link #emulate(Trace, TraceSchedule, TaskMonitor)} is never called by the Swing * thread. * @@ -72,7 +72,9 @@ public interface DebuggerEmulationService { * *

* To guarantee the emulator is present, call {@link #backgroundEmulate(Trace, TraceSchedule)} - * first. WARNING: This emulator belongs to this service. Stepping it, or otherwise manipulating + * first. + *

+ * WARNING: This emulator belongs to this service. Stepping it, or otherwise manipulating * it without the service's knowledge can lead to unintended consequences. * * @param trace the trace containing the initial state diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProviderTest.java new file mode 100644 index 0000000000..dc995b1f92 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProviderTest.java @@ -0,0 +1,122 @@ +/* ### + * 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.pcode; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.Range; + +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.service.emulation.DebuggerTracePcodeEmulator; +import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; +import ghidra.app.services.DebuggerEmulationService; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.PcodeExecutor; +import ghidra.pcode.exec.trace.TraceSleighUtils; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Instruction; +import ghidra.program.model.listing.InstructionIterator; +import ghidra.trace.model.memory.TraceMemoryFlag; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.TraceSchedule; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebuggerGUITest { + + protected DebuggerTraceManagerService traceManager; + protected DebuggerPcodeStepperPlugin pcodePlugin; + protected DebuggerListingPlugin listingPlugin; + + protected DebuggerPcodeStepperProvider pcodeProvider; + protected DebuggerEmulationService emuService; + + @Before + public void setUpPcodeStepperProviderTest() throws Exception { + traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class); + pcodePlugin = addPlugin(tool, DebuggerPcodeStepperPlugin.class); + listingPlugin = addPlugin(tool, DebuggerListingPlugin.class); // For colors + emuService = tool.getService(DebuggerEmulationService.class); + + pcodeProvider = waitForComponentProvider(DebuggerPcodeStepperProvider.class); + + createTrace(); + } + + @Test + public void testCustomUseropDisplay() throws Exception { + Address start = tb.addr(0x00400000); + TraceThread thread; + InstructionIterator iit; + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getMemoryManager() + .addRegion("echo:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + + thread = tb.getOrAddThread("1", 0); + + PcodeExecutor init = TraceSleighUtils.buildByteExecutor(tb.trace, 0, thread, 0); + init.executeLine("pc = 0x00400000"); + + Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); + iit = asm.assemble(start, + "imm r0, #1234", + "imm r1, #2045"); // 11 bits unsigned + + } + Instruction imm1234 = iit.next(); + Instruction imm2045 = iit.next(); + + TraceSchedule schedule1 = TraceSchedule.parse("0:.t0-1"); + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + traceManager.activateTime(schedule1); + + DebuggerTracePcodeEmulator emu = + waitForValue(() -> emuService.getCachedEmulator(tb.trace, schedule1)); + assertNotNull(emu); + + // P-code step to decode already done. One for each op. One to retire. + TraceSchedule schedule2 = + schedule1.steppedPcodeForward(thread, imm1234.getPcode().length + 1); + PcodeThread et = emu.getThread(thread.getPath(), false); + traceManager.activateTime(schedule2); + waitForPass(() -> assertNull(et.getFrame())); + + /** + * NB. at the moment, there is no API to customize the service's emulator. In the meantime, + * the vanilla PcodeThread does inject a custom library for breakpoints, so we'll use that + * as our "custom userop" test case. It might also be nice if the emulator service placed + * breakpoints, no? + */ + emu.addBreakpoint(imm2045.getAddress(), "1:1"); + + // Just one to decode is necessary + TraceSchedule schedule3 = schedule2.steppedPcodeForward(thread, 1); + traceManager.activateTime(schedule3); + waitForPass(() -> assertEquals(schedule3, pcodeProvider.current.getTime())); + + assertTrue(pcodeProvider.pcodeTableModel.getModelData() + .stream() + .anyMatch(r -> r.getCode().contains("emu_swi"))); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java index c8f131fcd3..1fc901ca6d 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java @@ -258,7 +258,7 @@ public class DefaultPcodeThread implements PcodeThread { } protected void beginInstructionOrInject() { - PcodeProgram inj = injects.get(counter); + PcodeProgram inj = getInject(counter); if (inj != null) { instruction = null; frame = executor.begin(inj); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeFrame.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeFrame.java index dfac2d22f7..3a13ec2119 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeFrame.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeFrame.java @@ -69,7 +69,8 @@ public class PcodeFrame { sb.append(" "); } PcodeOp op = code.get(i); - sb.append(op.getSeqnum() + ": " + PcodeProgram.opToString(language, op, false)); + sb.append( + op.getSeqnum() + ": " + PcodeProgram.opToString(language, op, false, useropNames)); } if (index == code.size()) { sb.append("\n *> fall-through"); @@ -98,6 +99,10 @@ public class PcodeFrame { return useropNames.get(userop); } + public Map getUseropNames() { + return useropNames; + } + public boolean isFallThrough() { return index == code.size(); } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java index d6c95bd9a7..f949b86eb8 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java @@ -102,11 +102,12 @@ public class PcodeProgram { } } - public static String useropToString(Language language, Varnode vn, boolean markup) { + public static String useropToString(Language language, Varnode vn, boolean markup, + Map useropNames) { if (!vn.isConstant()) { throw new IllegalArgumentException("userop index must be a constant varnode"); } - String display = "\"" + language.getUserDefinedOpName((int) vn.getOffset()) + "\""; + String display = "\"" + useropNames.get((int) vn.getOffset()) + "\""; if (markup) { return htmlSpan("userop", display); } @@ -124,7 +125,8 @@ public class PcodeProgram { } } - public static String opToString(Language language, PcodeOp op, boolean markup) { + public static String opToString(Language language, PcodeOp op, boolean markup, + Map useropNames) { StringBuilder sb = new StringBuilder(); Varnode output = op.getOutput(); if (output != null) { @@ -146,7 +148,7 @@ public class PcodeProgram { } else if (isUserop) { sb.append(' '); - sb.append(useropToString(language, op.getInput(0), markup)); + sb.append(useropToString(language, op.getInput(0), markup, useropNames)); i = 1; } else { @@ -208,7 +210,8 @@ public class PcodeProgram { public String toString() { StringBuilder sb = new StringBuilder("<" + getHead() + ":"); for (PcodeOp op : code) { - sb.append("\n " + op.getSeqnum() + ": " + opToString(language, op, false)); + sb.append( + "\n " + op.getSeqnum() + ": " + opToString(language, op, false, useropNames)); } sb.append("\n>"); return sb.toString(); diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java index 2158781a1b..3e5dc984ab 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/ColorUtils.java @@ -168,7 +168,12 @@ public class ColorUtils { * @return the new color */ public static Color blend(Color c1, Color c2, float ratio) { - + if (c1 == null) { + return c2; + } + if (c2 == null) { + return c1; + } float rgb1[] = new float[3]; float rgb2[] = new float[3]; c1.getColorComponents(rgb1); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssembler.java index c2b2c32487..f251a63610 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssembler.java @@ -113,8 +113,9 @@ public class SleighAssembler implements Assembler { Address end = at.add(insbytes.length - 1); listing.clearCodeUnits(at, end, false); memory.setBytes(at, insbytes); - dis.disassemble(at, new AddressSet(at)); - return listing.getInstructions(new AddressSet(at, end), true); + AddressSet set = new AddressSet(at, end); + dis.disassemble(at, set); + return listing.getInstructions(set, true); } @Override