mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-2814: Fix translation of address operands for guest instructions.
This commit is contained in:
parent
346eef3727
commit
ef6fb310bb
8 changed files with 201 additions and 25 deletions
|
@ -17,6 +17,9 @@ package ghidra.app.plugin.core.debug.gui.listing;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
|
@ -29,10 +32,14 @@ import org.junit.experimental.categories.Category;
|
|||
import docking.menu.ActionState;
|
||||
import docking.menu.MultiStateDockingAction;
|
||||
import docking.widgets.EventTrigger;
|
||||
import docking.widgets.fieldpanel.FieldPanel;
|
||||
import generic.test.category.NightlyCategory;
|
||||
import generic.test.rule.Repeated;
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
import ghidra.app.plugin.assembler.*;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
||||
import ghidra.app.plugin.core.codebrowser.hover.ReferenceListingHoverPlugin;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
|
@ -44,6 +51,7 @@ import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
|
|||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
|
||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||
import ghidra.async.SwingExecutorService;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.lifecycle.Unfinished;
|
||||
|
@ -51,12 +59,13 @@ import ghidra.plugin.importer.ImporterPlugin;
|
|||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.program.model.listing.Instruction;
|
||||
import ghidra.program.util.*;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.database.memory.DBTraceMemoryManager;
|
||||
import ghidra.trace.database.stack.DBTraceStackManager;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.guest.TraceGuestPlatform;
|
||||
import ghidra.trace.model.memory.*;
|
||||
import ghidra.trace.model.modules.TraceModule;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
|
@ -1615,4 +1624,117 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
// TODO: Test this independent of this particular action?
|
||||
assertNull(consolePlugin.getLogRow(ctx));
|
||||
}
|
||||
|
||||
protected Instruction placeGuestInstruction(int guestRangeLength) throws Throwable {
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.trace.getMemoryManager()
|
||||
.addRegion("Memory[.text]", Lifespan.nowOn(0), tb.range(0x00400000, 0x0040ffff),
|
||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||
TraceGuestPlatform toy = tb.trace.getPlatformManager()
|
||||
.addGuestPlatform(getToyBE64Language().getDefaultCompilerSpec());
|
||||
Address hostEntry = tb.addr(0x00400000);
|
||||
Address guestEntry = tb.addr(toy, 0x00000000);
|
||||
toy.addMappedRange(hostEntry, guestEntry, guestRangeLength);
|
||||
|
||||
Assembler asm = Assemblers.getAssembler(toy.getLanguage());
|
||||
AssemblyBuffer buf = new AssemblyBuffer(asm, guestEntry);
|
||||
buf.assemble("call 0x123");
|
||||
Instruction callInstr =
|
||||
tb.addInstruction(0, hostEntry, toy, ByteBuffer.wrap(buf.getBytes()));
|
||||
|
||||
return callInstr;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuestInstructionNavigation() throws Throwable {
|
||||
createAndOpenTrace("DATA:BE:64:default");
|
||||
|
||||
Instruction callInstr = placeGuestInstruction(0x1000);
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals("call 0x00400123", callInstr.toString());
|
||||
|
||||
listingPlugin.goTo(new OperandFieldLocation(tb.trace.getProgramView(), tb.addr(0x00400000),
|
||||
null, null, null, 0, 0));
|
||||
waitForPass(() -> assertEquals(tb.addr(0x00400000), listingPlugin.getCurrentAddress()));
|
||||
|
||||
click(listingPlugin, 2);
|
||||
waitForPass(() -> assertEquals(tb.addr(0x00400123), listingPlugin.getCurrentAddress()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuestInstructionNavigationUnmapped() throws Throwable {
|
||||
createAndOpenTrace("DATA:BE:64:default");
|
||||
|
||||
Instruction callInstr = placeGuestInstruction(0x100);
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals("call guest:ram:00000123", callInstr.toString());
|
||||
|
||||
listingPlugin.goTo(new OperandFieldLocation(tb.trace.getProgramView(), tb.addr(0x00400000),
|
||||
null, null, null, 0, 0));
|
||||
waitForPass(() -> assertEquals(tb.addr(0x00400000), listingPlugin.getCurrentAddress()));
|
||||
|
||||
click(listingPlugin, 2); // It should not move, nor crash
|
||||
waitForPass(() -> assertEquals(tb.addr(0x00400000), listingPlugin.getCurrentAddress()));
|
||||
}
|
||||
|
||||
private void triggerPopup(Point cursorPoint, Component eventSource) {
|
||||
moveMouse(eventSource, cursorPoint.x, cursorPoint.y);
|
||||
clickMouse(eventSource, MouseEvent.BUTTON1, cursorPoint.x, cursorPoint.y, 1, 0);
|
||||
moveMouse(eventSource, cursorPoint.x + 5, cursorPoint.y);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuestInstructionHover() throws Throwable {
|
||||
ReferenceListingHoverPlugin hoverPlugin =
|
||||
addPlugin(tool, ReferenceListingHoverPlugin.class);
|
||||
ListingPanel listingPanel = listingProvider.getListingPanel();
|
||||
FieldPanel fieldPanel = listingPanel.getFieldPanel();
|
||||
|
||||
createAndOpenTrace("DATA:BE:64:default");
|
||||
|
||||
Instruction callInstr = placeGuestInstruction(0x1000);
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals("call 0x00400123", callInstr.toString());
|
||||
|
||||
listingPlugin.goTo(new OperandFieldLocation(tb.trace.getProgramView(), tb.addr(0x00400000),
|
||||
null, null, null, 0, 0));
|
||||
waitForPass(() -> assertEquals(tb.addr(0x00400000), listingPlugin.getCurrentAddress()));
|
||||
Point p = fieldPanel.getCursorPoint();
|
||||
triggerPopup(p, fieldPanel);
|
||||
waitForPass(() -> assertTrue(listingPanel.isHoverShowing()));
|
||||
|
||||
ListingPanel popupPanel = hoverPlugin.getReferenceHoverService().getPanel();
|
||||
assertEquals(tb.addr(0x00400123), popupPanel.getProgramLocation().getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuestInstructionHoverUnmapped() throws Throwable {
|
||||
addPlugin(tool, ReferenceListingHoverPlugin.class);
|
||||
ListingPanel listingPanel = listingProvider.getListingPanel();
|
||||
FieldPanel fieldPanel = listingPanel.getFieldPanel();
|
||||
|
||||
createAndOpenTrace("DATA:BE:64:default");
|
||||
|
||||
Instruction callInstr = placeGuestInstruction(0x100);
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals("call guest:ram:00000123", callInstr.toString());
|
||||
|
||||
listingPlugin.goTo(new OperandFieldLocation(tb.trace.getProgramView(), tb.addr(0x00400000),
|
||||
null, null, null, 0, 0));
|
||||
waitForPass(() -> assertEquals(tb.addr(0x00400000), listingPlugin.getCurrentAddress()));
|
||||
Point p = fieldPanel.getCursorPoint();
|
||||
triggerPopup(p, fieldPanel);
|
||||
listingPlugin.updateNow();
|
||||
waitForSwing();
|
||||
assertFalse(listingPanel.isHoverShowing());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.trace.database.listing;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
import db.DBRecord;
|
||||
|
@ -24,8 +25,7 @@ import ghidra.program.model.address.*;
|
|||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.ContextChangeException;
|
||||
import ghidra.program.model.listing.FlowOverride;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.program.model.mem.*;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
|
@ -106,11 +106,32 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
|
|||
@Override
|
||||
public ParserContext getParserContext(Address instructionAddress)
|
||||
throws UnknownContextException, MemoryAccessException {
|
||||
// TODO: Does the given address need mapping?
|
||||
return DBTraceInstruction.this.getParserContext(instructionAddress);
|
||||
}
|
||||
}
|
||||
|
||||
protected class GuestMemBuffer implements MemBufferAdapter {
|
||||
@Override
|
||||
public Address getAddress() {
|
||||
return platform.mapHostToGuest(getX1());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Memory getMemory() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBigEndian() {
|
||||
return platform.getLanguage().isBigEndian();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(ByteBuffer buffer, int addressOffset) {
|
||||
return DBTraceInstruction.this.getBytes(buffer, addressOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@DBAnnotatedField(column = PLATFORM_COLUMN_NAME)
|
||||
private int platformKey;
|
||||
@DBAnnotatedField(column = PROTOTYPE_COLUMN_NAME)
|
||||
|
@ -126,6 +147,7 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
|
|||
protected ParserContext parserContext;
|
||||
protected InternalTracePlatform platform;
|
||||
protected InstructionContext instructionContext;
|
||||
protected MemBuffer memBuffer;
|
||||
|
||||
/**
|
||||
* Construct an instruction unit
|
||||
|
@ -150,9 +172,11 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
|
|||
this.platform = platform;
|
||||
if (platform.isHost()) {
|
||||
instructionContext = this;
|
||||
memBuffer = this;
|
||||
}
|
||||
else {
|
||||
instructionContext = new GuestInstructionContext();
|
||||
memBuffer = new GuestMemBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -708,12 +732,13 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
|
|||
|
||||
@Override
|
||||
public MemBuffer getMemBuffer() {
|
||||
return this;
|
||||
return memBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParserContext getParserContext() throws MemoryAccessException {
|
||||
return parserContext == null ? parserContext = prototype.getParserContext(this, this)
|
||||
return parserContext == null
|
||||
? parserContext = prototype.getParserContext(getMemBuffer(), getProcessorContext())
|
||||
: parserContext;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,16 +16,18 @@
|
|||
package ghidra.trace.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.Instruction;
|
||||
import ghidra.program.model.listing.InstructionPcodeOverride;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
import ghidra.program.model.scalar.Scalar;
|
||||
import ghidra.program.model.symbol.RefType;
|
||||
import ghidra.trace.model.guest.TracePlatform;
|
||||
import ghidra.trace.model.listing.TraceInstruction;
|
||||
|
||||
public interface InstructionAdapterFromPrototype extends Instruction {
|
||||
public interface InstructionAdapterFromPrototype extends TraceInstruction {
|
||||
default String getFullString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getMnemonicString());
|
||||
|
@ -68,7 +70,7 @@ public interface InstructionAdapterFromPrototype extends Instruction {
|
|||
InstructionContext context = getInstructionContext();
|
||||
int opType = prototype.getOpType(opIndex, context);
|
||||
if (OperandType.isAddress(opType)) {
|
||||
return prototype.getAddress(opIndex, context);
|
||||
return getPlatform().mapGuestToHost(prototype.getAddress(opIndex, context));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -106,11 +108,13 @@ public interface InstructionAdapterFromPrototype extends Instruction {
|
|||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Object opElem : opList) {
|
||||
if (opElem instanceof Address) {
|
||||
Address opAddr = (Address) opElem;
|
||||
if (opElem instanceof Address opAddr) {
|
||||
sb.append("0x");
|
||||
sb.append(opAddr.toString(false));
|
||||
}
|
||||
else if (opElem == null) {
|
||||
sb.append("<null>");
|
||||
}
|
||||
else {
|
||||
sb.append(opElem.toString());
|
||||
}
|
||||
|
@ -120,7 +124,23 @@ public interface InstructionAdapterFromPrototype extends Instruction {
|
|||
|
||||
@Override
|
||||
default List<Object> getDefaultOperandRepresentationList(int opIndex) {
|
||||
return getPrototype().getOpRepresentationList(opIndex, getInstructionContext());
|
||||
// TODO: Cache this in the instruction?
|
||||
List<Object> list =
|
||||
getPrototype().getOpRepresentationList(opIndex, getInstructionContext());
|
||||
TracePlatform platform = getPlatform();
|
||||
if (platform.isHost()) {
|
||||
return list;
|
||||
}
|
||||
return list.stream().map(obj -> {
|
||||
if (obj instanceof Address addr) {
|
||||
Address hostAddr = platform.mapGuestToHost(addr);
|
||||
if (hostAddr == null) {
|
||||
return "guest:" + addr.toString(true);
|
||||
}
|
||||
return hostAddr;
|
||||
}
|
||||
return obj;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -41,7 +41,7 @@ import ghidra.framework.plugintool.util.PluginStatus;
|
|||
//@formatter:on
|
||||
public class ReferenceListingHoverPlugin extends Plugin {
|
||||
|
||||
private ReferenceListingHover referenceHoverService;
|
||||
private final ReferenceListingHover referenceHoverService;
|
||||
|
||||
public ReferenceListingHoverPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
|
@ -66,4 +66,9 @@ public class ReferenceListingHoverPlugin extends Plugin {
|
|||
public void dispose() {
|
||||
referenceHoverService.dispose();
|
||||
}
|
||||
|
||||
/* testing */
|
||||
public ReferenceListingHover getReferenceHoverService() {
|
||||
return referenceHoverService;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import ghidra.GhidraOptions;
|
|||
import ghidra.app.plugin.core.hover.AbstractScalarOperandHover;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.lang.InstructionPrototype;
|
||||
import ghidra.program.model.listing.Instruction;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.scalar.Scalar;
|
||||
|
@ -96,9 +95,7 @@ public class ScalarOperandListingHover extends AbstractScalarOperandHover
|
|||
return operands[0];
|
||||
}
|
||||
|
||||
InstructionPrototype prototype = instruction.getPrototype();
|
||||
List<Object> list =
|
||||
prototype.getOpRepresentationList(opIndex, instruction.getInstructionContext());
|
||||
List<Object> list = instruction.getDefaultOperandRepresentationList(opIndex);
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -66,6 +66,16 @@ public abstract class AbstractReferenceHover extends AbstractConfigurableHover {
|
|||
initialize();
|
||||
}
|
||||
|
||||
/* testing */
|
||||
public ListingPanel getPanel() {
|
||||
return panel;
|
||||
}
|
||||
|
||||
/* testing */
|
||||
public JToolTip getToolTip() {
|
||||
return toolTip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
|
|
|
@ -21,7 +21,6 @@ import ghidra.app.util.NamespaceUtils;
|
|||
import ghidra.app.util.viewer.field.CommentUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.lang.InstructionPrototype;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.listing.CodeUnitFormatOptions.ShowBlockName;
|
||||
import ghidra.program.model.listing.CodeUnitFormatOptions.ShowNamespace;
|
||||
|
@ -212,7 +211,6 @@ public class CodeUnitFormat {
|
|||
|
||||
Program program = cu.getProgram();
|
||||
Instruction instr = (Instruction) cu;
|
||||
InstructionPrototype proto = instr.getPrototype();
|
||||
|
||||
if (!program.getLanguage().supportsPcode()) {
|
||||
// Formatted mark-up only supported for languages which support PCode
|
||||
|
@ -220,8 +218,7 @@ public class CodeUnitFormat {
|
|||
}
|
||||
|
||||
// Get raw representation list and map of registers contained within it
|
||||
ArrayList<Object> representationList =
|
||||
proto.getOpRepresentationList(opIndex, instr.getInstructionContext());
|
||||
List<Object> representationList = instr.getDefaultOperandRepresentationList(opIndex);
|
||||
if (representationList == null) {
|
||||
return new OperandRepresentationList("<BAD-Instruction>");
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import docking.DialogComponentProvider;
|
|||
import docking.action.DockingActionIf;
|
||||
import docking.widgets.fieldpanel.FieldPanel;
|
||||
import ghidra.GhidraTestApplicationLayout;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||
import ghidra.app.plugin.core.codebrowser.AbstractCodeBrowserPlugin;
|
||||
import ghidra.framework.ApplicationConfiguration;
|
||||
import ghidra.framework.GhidraApplicationConfiguration;
|
||||
import ghidra.framework.model.*;
|
||||
|
@ -192,11 +192,11 @@ public abstract class AbstractGhidraHeadedIntegrationTest
|
|||
* @param codeBrowser the CodeBrowserPlugin
|
||||
* @param clickCount the click count
|
||||
*/
|
||||
public void click(CodeBrowserPlugin codeBrowser, int clickCount) {
|
||||
public void click(AbstractCodeBrowserPlugin<?> codeBrowser, int clickCount) {
|
||||
click(codeBrowser, clickCount, true);
|
||||
}
|
||||
|
||||
public void click(CodeBrowserPlugin codeBrowser, int clickCount, boolean wait) {
|
||||
public void click(AbstractCodeBrowserPlugin<?> codeBrowser, int clickCount, boolean wait) {
|
||||
|
||||
// make sure that the code browser is ready to go--sometimes it is not, due to timing
|
||||
// during the testing process, like when the tool is first loaded.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue