GP-890: Added dialog for adding mappings and fixing various db/ui issues re/ length.

This commit is contained in:
Dan 2021-04-28 15:43:27 -04:00
parent bd2ac52c9b
commit e6fd14f88c
13 changed files with 967 additions and 111 deletions

View file

@ -0,0 +1,378 @@
/* ###
* 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.modules;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.*;
import java.math.BigInteger;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import com.google.common.collect.Range;
import docking.DialogComponentProvider;
import docking.widgets.model.GAddressRangeField;
import docking.widgets.model.GLifespanField;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.modules.TraceConflictedMappingException;
import ghidra.util.MathUtilities;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.layout.PairLayout;
public class DebuggerAddMappingDialog extends DialogComponentProvider {
private static final String HEX_BIT64 = "0x" + BigInteger.ONE.shiftLeft(64).toString(16);
private DebuggerStaticMappingService mappingService;
private Program program;
private Trace trace;
private final JLabel labelProg = new JLabel();
private final GAddressRangeField fieldProgRange = new GAddressRangeField();
private final JLabel labelTrace = new JLabel();
private final GAddressRangeField fieldTraceRange = new GAddressRangeField();
private final JTextField fieldLength = new JTextField();
private final GLifespanField fieldSpan = new GLifespanField();
public DebuggerAddMappingDialog() {
super("Add Static Mapping", false, false, true, false);
populateComponents();
}
protected static void rigFocusAndEnter(Component c, Runnable runnable) {
c.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
runnable.run();
}
});
c.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
runnable.run();
}
}
});
}
protected void populateComponents() {
JPanel panel = new JPanel(new PairLayout(5, 5));
panel.setBorder(new EmptyBorder(10, 10, 10, 10));
panel.add(new JLabel("Program: "));
panel.add(labelProg);
panel.add(new JLabel("Static Range: "));
panel.add(fieldProgRange);
panel.add(new JLabel("Trace: "));
panel.add(labelTrace);
panel.add(new JLabel("Dynamic Range: "));
panel.add(fieldTraceRange);
panel.add(new JLabel("Length: "));
fieldLength.setFont(Font.decode("monospaced"));
panel.add(fieldLength);
panel.add(new JLabel("Lifespan: "));
panel.add(fieldSpan);
rigFocusAndEnter(fieldProgRange, this::progRangeChanged);
rigFocusAndEnter(fieldTraceRange, this::traceRangeChanged);
rigFocusAndEnter(fieldLength, this::lengthChanged);
rigFocusAndEnter(fieldSpan, this::spanChanged);
addWorkPanel(panel);
addApplyButton();
addDismissButton();
setDefaultButton(null);
}
public void setMappingService(DebuggerStaticMappingService mappingService) {
this.mappingService = mappingService;
}
protected static void revalidateByLength(GAddressRangeField adjusted,
GAddressRangeField other) {
AddressRange adjRange = adjusted.getRange();
if (adjRange == null) {
return;
}
long lengthMinus1 = adjRange.getMaxAddress().subtract(adjRange.getMinAddress());
AddressRange otherRange = other.getRange();
if (otherRange == null) {
return;
}
long maxLengthMinus1 =
otherRange.getAddressSpace().getMaxAddress().subtract(otherRange.getMinAddress());
if (Long.compareUnsigned(lengthMinus1, maxLengthMinus1) > 0) {
adjusted.setRange(range(adjRange.getMinAddress(), maxLengthMinus1));
}
}
protected void revalidateProgRange() {
/**
* Not sure there's much to do here wrt/ the program. Might be nice to warn (but not
* prohibit) when range is not in memory. NB: A range spanning all blocks will likely
* contain some dead space.
*/
revalidateByLength(fieldProgRange, fieldTraceRange);
}
protected void revalidateTraceRange() {
// Similarly nothing to really do wrt/ the trace.
revalidateByLength(fieldTraceRange, fieldProgRange);
}
protected static long lengthMin(long a, long b) {
if (a == 0) {
return b;
}
if (b == 0) {
return a;
}
return MathUtilities.unsignedMin(a, b);
}
protected static long revalidateLengthByRange(AddressRange range, long length) {
long maxLength =
range.getAddressSpace().getMaxAddress().subtract(range.getMinAddress()) + 1;
return lengthMin(length, maxLength);
}
protected void setFieldLength(long length) {
if (length == 0) {
fieldLength.setText(HEX_BIT64);
}
else {
fieldLength.setText("0x" + Long.toHexString(length));
}
}
public long getLength() {
return parseLength(fieldLength.getText(), 1);
}
protected void revalidateLength() {
long length;
if (fieldLength.getText().trim().startsWith("-")) {
length = 1;
}
else {
length = getLength();
}
length = revalidateLengthByRange(fieldProgRange.getRange(), length);
length = revalidateLengthByRange(fieldTraceRange.getRange(), length);
setFieldLength(length);
}
protected void revalidateSpan() {
// Yeah, nothing to do
}
protected static AddressRange range(Address min, long lengthMinus1) {
return new AddressRangeImpl(min, min.addWrap(lengthMinus1));
}
protected void adjustLengthToProgRange() {
long length = fieldProgRange.getRange().getLength();
setFieldLength(length);
}
protected void adjustLengthToTraceRange() {
long length = fieldTraceRange.getRange().getLength();
setFieldLength(length);
}
protected void adjustRangeToLength(GAddressRangeField field) {
AddressRange range = field.getRange();
if (range == null) {
return;
}
Address min = range.getMinAddress();
field.setRange(range(min, getLength() - 1));
}
protected void adjustProgRangeToLength() {
adjustRangeToLength(fieldProgRange);
}
protected void adjustTraceRangeToLength() {
adjustRangeToLength(fieldTraceRange);
}
protected void progRangeChanged() {
revalidateProgRange();
adjustLengthToProgRange();
adjustTraceRangeToLength();
}
protected void traceRangeChanged() {
revalidateTraceRange();
adjustLengthToTraceRange();
adjustProgRangeToLength();
}
protected void lengthChanged() {
revalidateLength();
adjustProgRangeToLength();
adjustTraceRangeToLength();
}
protected void spanChanged() {
revalidateSpan();
}
/**
* Parses a value from 1 to 1<<64. Any value outside the range is "clipped" into the range.
*
* <p>
* Note that a returned value of 0 indicates 2 to the power 64, which is just 1 too high to fit
* into a 64-bit long.
*
* @param text the text to parse
* @param defaultVal the default value should parsing fail altogether
* @return the length, where 0 indicates {@code 1 << 64}.
*/
protected static long parseLength(String text, long defaultVal) {
text = text.trim();
String post;
int radix;
if (text.startsWith("-")) {
return 0;
}
if (text.startsWith("0x")) {
post = text.substring(2);
radix = 16;
}
else {
post = text;
radix = 10;
}
BigInteger bi;
try {
bi = new BigInteger(post, radix);
}
catch (NumberFormatException e) {
return defaultVal;
}
if (bi.equals(BigInteger.ZERO)) {
return 1;
}
if (bi.bitLength() > 64) {
return 0; // indicates 2**64, the max length
}
return bi.longValue(); // Do not use exact. It checks bitLength again, and considers sign.
}
@Override
protected void applyCallback() {
TraceLocation from = new DefaultTraceLocation(trace, null, fieldSpan.getLifespan(),
fieldTraceRange.getRange().getMinAddress());
ProgramLocation to = new ProgramLocation(program,
fieldProgRange.getRange().getMinAddress());
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Add Static Mapping", false)) {
mappingService.addMapping(from, to, getLength(), true);
tid.commit();
}
catch (TraceConflictedMappingException e) {
throw new AssertionError(e); // I said truncateExisting
}
}
/**
* Set the values of the fields
*
* @param program the program
* @param trace the trace
* @param progStart the starting static address
* @param traceStart the starting dynamic address
* @param length the length (0 indicates the entire 64-bit range)
* @param lifespan the lifespan
* @throws AddressOverflowException if the length is too large for either space
*/
public void setValues(Program program, Trace trace, Address progStart,
Address traceStart, long length, Range<Long> lifespan) throws AddressOverflowException {
// NB. This dialog will not validate these. The caller is responsible.
this.program = program;
this.trace = trace;
this.fieldProgRange.setAddressFactory(program.getAddressFactory());
this.fieldProgRange.setRange(range(progStart, length - 1));
this.fieldTraceRange.setAddressFactory(trace.getBaseAddressFactory());
this.fieldTraceRange.setRange(range(traceStart, length - 1));
setFieldLength(length);
this.fieldSpan.setLifespan(lifespan);
}
protected void setEnabled(boolean enabled) {
applyButton.setEnabled(enabled);
fieldProgRange.setEnabled(enabled);
fieldTraceRange.setEnabled(enabled);
fieldLength.setEnabled(enabled);
fieldSpan.setEnabled(enabled);
}
public void setTrace(Trace trace) {
this.trace = trace;
if (trace != null) {
labelTrace.setText(trace.getName());
fieldTraceRange.setAddressFactory(trace.getBaseAddressFactory());
}
else {
labelTrace.setText("[No Trace]");
fieldTraceRange.setAddressFactory(null);
}
if (program != null && trace != null) {
traceRangeChanged();
setEnabled(true);
}
else {
setEnabled(false);
}
}
public void setProgram(Program program) {
this.program = program;
if (program != null) {
labelProg.setText(program.getName());
fieldProgRange.setAddressFactory(program.getAddressFactory());
}
else {
labelProg.setText("[No Program]");
fieldProgRange.setAddressFactory(null);
}
if (program != null && trace != null) {
progRangeChanged();
setEnabled(true);
}
else {
setEnabled(false);
}
}
}

View file

@ -15,35 +15,30 @@
*/ */
package ghidra.app.plugin.core.debug.gui.modules; package ghidra.app.plugin.core.debug.gui.modules;
import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.events.ProgramActivatedPluginEvent;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin; import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*; import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo( // @PluginInfo(
shortDescription = "Debugger static mapping manager", // shortDescription = "Debugger static mapping manager",
description = "GUI to manage static mappings", // description = "GUI to manage static mappings",
category = PluginCategoryNames.DEBUGGER, // category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME, // packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED, // status = PluginStatus.RELEASED,
eventsConsumed = { eventsConsumed = {
TraceActivatedPluginEvent.class, // TraceActivatedPluginEvent.class,
ProgramLocationPluginEvent.class, // ProgramActivatedPluginEvent.class,
ProgramSelectionPluginEvent.class, // },
TraceLocationPluginEvent.class, // servicesRequired = {
TraceSelectionPluginEvent.class // DebuggerStaticMappingService.class,
}, // DebuggerTraceManagerService.class,
servicesRequired = { // })
DebuggerStaticMappingService.class, //
DebuggerTraceManagerService.class, //
} //
)
public class DebuggerStaticMappingPlugin extends AbstractDebuggerPlugin { public class DebuggerStaticMappingPlugin extends AbstractDebuggerPlugin {
protected DebuggerStaticMappingProvider provider; protected DebuggerStaticMappingProvider provider;
@ -70,17 +65,9 @@ public class DebuggerStaticMappingPlugin extends AbstractDebuggerPlugin {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event; TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
provider.setTrace(ev.getActiveCoordinates().getTrace()); provider.setTrace(ev.getActiveCoordinates().getTrace());
} }
if (event instanceof ProgramLocationPluginEvent) { if (event instanceof ProgramActivatedPluginEvent) {
provider.contextChanged(); ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
} provider.setProgram(ev.getActiveProgram());
if (event instanceof ProgramSelectionPluginEvent) {
provider.contextChanged();
}
if (event instanceof TraceLocationPluginEvent) {
provider.contextChanged();
}
if (event instanceof TraceSelectionPluginEvent) {
provider.contextChanged();
} }
} }
} }

View file

@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.modules;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.math.BigInteger;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
@ -41,15 +42,18 @@ import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.Address; import ghidra.program.model.address.*;
import ghidra.program.model.address.AddressRange; import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.*; import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceStaticMappingChangeType; import ghidra.trace.model.Trace.TraceStaticMappingChangeType;
import ghidra.trace.model.modules.*; import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.modules.TraceStaticMapping;
import ghidra.trace.model.modules.TraceStaticMappingManager;
import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.MathUtilities; import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction; import ghidra.util.database.UndoableTransaction;
import ghidra.util.table.GhidraTableFilterPanel; import ghidra.util.table.GhidraTableFilterPanel;
@ -60,7 +64,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
DYNAMIC_ADDRESS("Dynamic Address", Address.class, StaticMappingRow::getTraceAddress), DYNAMIC_ADDRESS("Dynamic Address", Address.class, StaticMappingRow::getTraceAddress),
STATIC_URL("Static Program", URL.class, StaticMappingRow::getStaticProgramURL), STATIC_URL("Static Program", URL.class, StaticMappingRow::getStaticProgramURL),
STATIC_ADDRESS("Static Address", String.class, StaticMappingRow::getStaticAddress), STATIC_ADDRESS("Static Address", String.class, StaticMappingRow::getStaticAddress),
LENGTH("Length", Long.class, StaticMappingRow::getLength), LENGTH("Length", BigInteger.class, StaticMappingRow::getBigLength),
SHIFT("Shift", Long.class, StaticMappingRow::getShift), SHIFT("Shift", Long.class, StaticMappingRow::getShift),
LIFESPAN("Lifespan", Range.class, StaticMappingRow::getLifespan); LIFESPAN("Lifespan", Range.class, StaticMappingRow::getLifespan);
@ -113,6 +117,8 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
private final DebuggerStaticMappingPlugin plugin; private final DebuggerStaticMappingPlugin plugin;
private final DebuggerAddMappingDialog addMappingDialog;
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerStaticMappingService mappingService; private DebuggerStaticMappingService mappingService;
// TODO: Use events to track selections? This can only work with the main listings. // TODO: Use events to track selections? This can only work with the main listings.
@ -146,6 +152,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MAPPINGS, plugin.getName(), null); super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MAPPINGS, plugin.getName(), null);
this.plugin = plugin; this.plugin = plugin;
this.addMappingDialog = new DebuggerAddMappingDialog();
this.autoWiring = AutoService.wireServicesConsumed(plugin, this); this.autoWiring = AutoService.wireServicesConsumed(plugin, this);
setIcon(DebuggerResources.ICON_PROVIDER_MAPPINGS); setIcon(DebuggerResources.ICON_PROVIDER_MAPPINGS);
@ -157,6 +164,11 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
createActions(); createActions();
} }
@AutoServiceConsumed
private void setMappingService(DebuggerStaticMappingService mappingService) {
addMappingDialog.setMappingService(mappingService);
}
@Override @Override
public void addLocalAction(DockingActionIf action) { public void addLocalAction(DockingActionIf action) {
super.addLocalAction(action); super.addLocalAction(action);
@ -215,7 +227,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
statAddrCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT); statAddrCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn lengthCol = columnModel.getColumn(StaticMappingTableColumns.LENGTH.ordinal()); TableColumn lengthCol = columnModel.getColumn(StaticMappingTableColumns.LENGTH.ordinal());
// TODO: Get user column settings working. Still, should default to Hex // TODO: Get user column settings working. Still, should default to Hex
lengthCol.setCellRenderer(CustomToStringCellRenderer.MONO_LONG_HEX); lengthCol.setCellRenderer(CustomToStringCellRenderer.MONO_BIG_HEX);
TableColumn shiftCol = columnModel.getColumn(StaticMappingTableColumns.SHIFT.ordinal()); TableColumn shiftCol = columnModel.getColumn(StaticMappingTableColumns.SHIFT.ordinal());
shiftCol.setCellRenderer(CustomToStringCellRenderer.MONO_LONG_HEX); shiftCol.setCellRenderer(CustomToStringCellRenderer.MONO_LONG_HEX);
} }
@ -223,7 +235,6 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
protected void createActions() { protected void createActions() {
actionAdd = AddAction.builder(plugin) actionAdd = AddAction.builder(plugin)
.description("Add Mapping from Listing Selections") .description("Add Mapping from Listing Selections")
.enabledWhen(this::haveMappableSelections)
.onAction(this::activatedAdd) .onAction(this::activatedAdd)
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
actionRemove = RemoveAction.builder(plugin) actionRemove = RemoveAction.builder(plugin)
@ -240,38 +251,10 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
contextChanged(); contextChanged();
} }
private boolean haveMappableSelections(ActionContext ignore) {
// TODO: Use events to track selections/locations
if (codeViewerService == null || listingService == null) {
return false;
}
ProgramLocation progLoc = codeViewerService.getCurrentLocation();
ProgramLocation traceLoc = listingService.getCurrentLocation();
if (progLoc == null || traceLoc == null) {
return false;
}
ProgramSelection progSel = codeViewerService.getCurrentSelection();
ProgramSelection traceSel = listingService.getCurrentSelection();
if (progSel != null && progSel.getNumAddressRanges() > 1) {
return false;
}
if (traceSel != null && traceSel.getNumAddressRanges() > 1) {
return false;
}
long progLen = progSel == null ? 0 : progSel.getNumAddresses();
long traceLen = traceSel == null ? 0 : traceSel.getNumAddresses();
if (progLen == 0 && traceLen == 0) {
return false;
}
return true;
}
private void activatedAdd(ActionContext ignore) { private void activatedAdd(ActionContext ignore) {
// TODO: Use events to track selections/locations tool.showDialog(addMappingDialog);
// Try to populate the dialog based on selection, if applicable
if (codeViewerService == null || listingService == null) { if (codeViewerService == null || listingService == null) {
return; return;
} }
@ -303,17 +286,13 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
Address progStart = progLen != 0 ? progSel.getMinAddress() : progLoc.getAddress(); Address progStart = progLen != 0 ? progSel.getMinAddress() : progLoc.getAddress();
Address traceStart = traceLen != 0 ? traceSel.getMinAddress() : traceLoc.getAddress(); Address traceStart = traceLen != 0 ? traceSel.getMinAddress() : traceLoc.getAddress();
TraceProgramView view = (TraceProgramView) traceLoc.getProgram(); TraceProgramView view = (TraceProgramView) traceLoc.getProgram();
TraceLocation from =
new DefaultTraceLocation(currentTrace, null, Range.atLeast(view.getSnap()), traceStart);
ProgramLocation to = new ProgramLocation(progLoc.getProgram(), progStart);
try (UndoableTransaction tid = try {
UndoableTransaction.start(currentTrace, "Add Static Mapping", false)) { addMappingDialog.setValues(progLoc.getProgram(), currentTrace, progStart, traceStart,
mappingService.addMapping(from, to, length, true); length, Range.atLeast(view.getSnap()));
tid.commit();
} }
catch (TraceConflictedMappingException e) { catch (AddressOverflowException e) {
throw new AssertionError(e); // I said truncateExisting Msg.showError(this, null, "Add Mapping", "Error populating dialog");
} }
} }
@ -387,5 +366,11 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
currentTrace = trace; currentTrace = trace;
addNewListeners(); addNewListeners();
loadMappings(); loadMappings();
addMappingDialog.setTrace(trace);
}
public void setProgram(Program program) {
addMappingDialog.setProgram(program);
} }
} }

View file

@ -15,6 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.gui.modules; package ghidra.app.plugin.core.debug.gui.modules;
import java.math.BigInteger;
import java.net.URL; import java.net.URL;
import com.google.common.collect.Range; import com.google.common.collect.Range;
@ -24,6 +25,7 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.modules.TraceStaticMapping; import ghidra.trace.model.modules.TraceStaticMapping;
public class StaticMappingRow { public class StaticMappingRow {
private static final BigInteger BIT64 = BigInteger.ONE.shiftLeft(64);
private final TraceStaticMapping mapping; private final TraceStaticMapping mapping;
public StaticMappingRow(TraceStaticMapping mapping) { public StaticMappingRow(TraceStaticMapping mapping) {
@ -54,6 +56,19 @@ public class StaticMappingRow {
return mapping.getLength(); return mapping.getLength();
} }
public BigInteger getBigLength() {
long length = mapping.getLength();
if (length == 0) {
return BIT64;
}
else if (length < 0) {
return BigInteger.valueOf(length).add(BIT64);
}
else {
return BigInteger.valueOf(length);
}
}
public long getShift() { public long getShift() {
return mapping.getShift(); return mapping.getShift();
} }

View file

@ -940,31 +940,36 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
if (toURL == null) { if (toURL == null) {
noProject(); noProject();
} }
try { Address fromAddress = from.getAddress();
Address start = from.getAddress(); Address toAddress = to.getByteAddress();
Address end = start.addNoWrap(length - 1); long maxFromLengthMinus1 =
// Also check end in the destination fromAddress.getAddressSpace().getMaxAddress().subtract(fromAddress);
Address toAddress = to.getByteAddress(); long maxToLengthMinus1 =
toAddress.addNoWrap(length - 1); // Anticipate possible AddressOverflow toAddress.getAddressSpace().getMaxAddress().subtract(toAddress);
AddressRangeImpl range = new AddressRangeImpl(start, end); if (Long.compareUnsigned(length - 1, maxFromLengthMinus1) > 0) {
if (truncateExisting) { throw new IllegalArgumentException("Length would cause address overflow in trace");
long truncEnd = DBTraceUtils.lowerEndpoint(from.getLifespan()) - 1; }
for (TraceStaticMapping existing : List if (Long.compareUnsigned(length - 1, maxToLengthMinus1) > 0) {
.copyOf(manager.findAllOverlapping(range, from.getLifespan()))) { throw new IllegalArgumentException("Length would cause address overflow in program");
existing.delete(); }
if (Long.compareUnsigned(existing.getStartSnap(), truncEnd) < 0) { Address end = fromAddress.addWrap(length - 1);
manager.add(existing.getTraceAddressRange(), // Also check end in the destination
Range.closed(existing.getStartSnap(), truncEnd), AddressRangeImpl range = new AddressRangeImpl(fromAddress, end);
existing.getStaticProgramURL(), existing.getStaticAddress()); Range<Long> fromLifespan = from.getLifespan();
} if (truncateExisting) {
long truncEnd = DBTraceUtils.lowerEndpoint(fromLifespan) - 1;
for (TraceStaticMapping existing : List
.copyOf(manager.findAllOverlapping(range, fromLifespan))) {
existing.delete();
if (fromLifespan.hasLowerBound() &&
Long.compare(existing.getStartSnap(), truncEnd) <= 0) {
manager.add(existing.getTraceAddressRange(),
Range.closed(existing.getStartSnap(), truncEnd),
existing.getStaticProgramURL(), existing.getStaticAddress());
} }
} }
manager.add(range, from.getLifespan(), toURL,
toAddress.toString(true));
}
catch (AddressOverflowException e) {
throw new IllegalArgumentException("Length would cause address overflow", e);
} }
manager.add(range, fromLifespan, toURL, toAddress.toString(true));
} }
@Override @Override

View file

@ -535,7 +535,7 @@ public interface DebuggerStaticMappingService {
* *
* @param from the source trace location, including lifespan * @param from the source trace location, including lifespan
* @param to the destination program location * @param to the destination program location
* @param length the length of the mapped region * @param length the length of the mapped region, where 0 indicates {@code 1 << 64}.
* @param truncateExisting true to delete or truncate the lifespan of overlapping entries * @param truncateExisting true to delete or truncate the lifespan of overlapping entries
* @throws TraceConflictedMappingException if a conflicting mapping overlaps the source and * @throws TraceConflictedMappingException if a conflicting mapping overlaps the source and
* {@code truncateExisting} is false. * {@code truncateExisting} is false.

View file

@ -106,7 +106,7 @@ public class DebuggerStaticMappingProviderTest extends AbstractGhidraHeadedDebug
@Test @Test
public void testAddAction() throws Exception { public void testAddAction() throws Exception {
assertFalse(mappingsProvider.actionAdd.isEnabled()); assertTrue(mappingsProvider.actionAdd.isEnabled());
createProgramFromTrace(tb.trace); createProgramFromTrace(tb.trace);
intoProject(tb.trace); intoProject(tb.trace);
@ -135,16 +135,18 @@ public class DebuggerStaticMappingProviderTest extends AbstractGhidraHeadedDebug
programManager.openProgram(program); programManager.openProgram(program);
waitForSwing(); waitForSwing();
assertFalse(mappingsProvider.actionAdd.isEnabled());
ProgramSelection traceSel = ProgramSelection traceSel =
new ProgramSelection(tb.addr(0xdeadbeefL), tb.addr(0xdeadbeefL + 0x0f)); new ProgramSelection(tb.addr(0xdeadbeefL), tb.addr(0xdeadbeefL + 0x0f));
listingPlugin.getProvider().setSelection(traceSel); listingPlugin.getProvider().setSelection(traceSel);
codeViewerPlugin.goTo(new ProgramLocation(program, addr(program, 0xc0de1234L)), true); codeViewerPlugin.goTo(new ProgramLocation(program, addr(program, 0xc0de1234L)), true);
waitForSwing(); waitForSwing();
assertTrue(mappingsProvider.actionAdd.isEnabled()); performAction(mappingsProvider.actionAdd, false);
performAction(mappingsProvider.actionAdd, true);
DebuggerAddMappingDialog dialog = waitForDialogComponent(DebuggerAddMappingDialog.class);
dialog.applyCallback();
dialog.close();
waitForDomainObject(tb.trace);
TraceStaticMapping entry = Unique.assertOne(manager.getAllEntries()); TraceStaticMapping entry = Unique.assertOne(manager.getAllEntries());
assertEquals(Range.atLeast(0L), entry.getLifespan()); assertEquals(Range.atLeast(0L), entry.getLifespan());

View file

@ -76,7 +76,7 @@ public class DBTraceStaticMappingManager implements TraceStaticMappingManager, D
public DBTraceStaticMapping add(AddressRange range, Range<Long> lifespan, URL toProgramURL, public DBTraceStaticMapping add(AddressRange range, Range<Long> lifespan, URL toProgramURL,
String toAddress) String toAddress)
throws TraceConflictedMappingException { throws TraceConflictedMappingException {
if (lifespan.lowerBoundType() != BoundType.CLOSED) { if (lifespan.hasLowerBound() && lifespan.lowerBoundType() != BoundType.CLOSED) {
throw new IllegalArgumentException("Lower bound must be closed"); throw new IllegalArgumentException("Lower bound must be closed");
} }
try (LockHold hold = LockHold.lock(lock.writeLock())) { try (LockHold hold = LockHold.lock(lock.writeLock())) {

View file

@ -63,7 +63,7 @@ public interface TraceStaticMapping {
/** /**
* Get the length of the mapping, i.e., the length of the range * Get the length of the mapping, i.e., the length of the range
* *
* @return the length * @return the length, where 0 indicates {@code 1 << 64}
*/ */
long getLength(); long getLength();

View file

@ -0,0 +1,209 @@
/* ###
* 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 docking.widgets.model;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.util.List;
import java.util.Objects;
import javax.swing.*;
import ghidra.program.model.address.*;
import ghidra.util.MathUtilities;
public class GAddressRangeField extends JPanel {
private static final Font MONOSPACED = Font.decode("monospaced");
private final JComboBox<String> fieldSpace = new JComboBox<>();
private final JTextField fieldMin = new JTextField("0");
private final JTextField fieldMax = new JTextField("0");
private final DefaultComboBoxModel<String> modelSpace = new DefaultComboBoxModel<>();
private AddressFactory factory;
public GAddressRangeField() {
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(new JLabel("["));
fieldSpace.setFont(MONOSPACED);
add(fieldSpace);
add(new JLabel(":"));
fieldMin.setFont(MONOSPACED);
add(fieldMin);
add(new JLabel(", "));
fieldMax.setFont(MONOSPACED);
add(fieldMax);
add(new JLabel("]"));
fieldSpace.setEditable(false);
fieldSpace.setModel(modelSpace);
fieldSpace.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
spaceFocusLost(e);
checkDispatchFocus(e);
}
});
fieldMin.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
minFocusLost(e);
checkDispatchFocus(e);
}
});
fieldMax.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
maxFocusLost(e);
checkDispatchFocus(e);
}
});
}
protected void checkDispatchFocus(FocusEvent e) {
Component opp = e.getOppositeComponent();
if (opp == null || !SwingUtilities.isDescendingFrom(opp, this)) {
dispatchEvent(e);
}
}
public void setAddressFactory(AddressFactory factory) {
this.factory = factory;
modelSpace.removeAllElements();
if (factory != null) {
for (AddressSpace space : factory.getAddressSpaces()) {
modelSpace.addElement(space.getName());
}
modelSpace.setSelectedItem(factory.getDefaultAddressSpace().getName());
revalidateMin();
revalidateMax();
adjustMaxToMin();
}
}
protected AddressSpace getSpace(boolean required) {
AddressSpace space = factory.getAddressSpace((String) fieldSpace.getSelectedItem());
if (required) {
return Objects.requireNonNull(space);
}
return space;
}
protected long parseLong(String text, long defaultVal) {
try {
return Long.parseUnsignedLong(text, 16);
}
catch (NumberFormatException ex) {
return defaultVal;
}
}
protected void revalidateMin() {
AddressSpace space = getSpace(true);
long spaceMin = space.getMinAddress().getOffset();
long min = MathUtilities.unsignedMax(parseLong(fieldMin.getText(), spaceMin), spaceMin);
fieldMin.setText(Long.toUnsignedString(min, 16));
}
protected void revalidateMax() {
AddressSpace space = getSpace(true);
long spaceMax = space.getMaxAddress().getOffset();
long max = MathUtilities.unsignedMin(parseLong(fieldMax.getText(), spaceMax), spaceMax);
fieldMax.setText(Long.toUnsignedString(max, 16));
}
protected void adjustMaxToMin() {
AddressSpace space = getSpace(true);
long spaceMin = space.getMinAddress().getOffset();
long min = parseLong(fieldMin.getText(), spaceMin);
long max = MathUtilities.unsignedMax(min, parseLong(fieldMax.getText(), min));
fieldMax.setText(Long.toUnsignedString(max, 16));
}
protected void adjustMinToMax() {
AddressSpace space = getSpace(true);
long spaceMax = space.getMaxAddress().getOffset();
long max = parseLong(fieldMax.getText(), spaceMax);
long min = MathUtilities.unsignedMin(max, parseLong(fieldMin.getText(), max));
fieldMin.setText(Long.toUnsignedString(min, 16));
}
protected void spaceFocusLost(FocusEvent e) {
if (factory == null) {
return;
}
revalidateMin();
revalidateMax();
adjustMaxToMin();
}
protected void minFocusLost(FocusEvent e) {
if (factory == null) {
return;
}
revalidateMin();
adjustMaxToMin();
}
protected void maxFocusLost(FocusEvent e) {
if (factory == null) {
return;
}
revalidateMax();
adjustMinToMax();
}
public void setRange(AddressRange range) {
if (factory == null) {
throw new IllegalStateException("Must set address factory first.");
}
if (!List.of(factory.getAddressSpaces()).contains(range.getAddressSpace())) {
throw new IllegalArgumentException(
"Given range's space must be in the factory's physical spaces");
}
fieldSpace.setSelectedItem(range.getAddressSpace().getName());
fieldMin.setText(Long.toUnsignedString(range.getMinAddress().getOffset(), 16));
fieldMax.setText(Long.toUnsignedString(range.getMaxAddress().getOffset(), 16));
}
public AddressRange getRange() {
String name = (String) fieldSpace.getSelectedItem();
if (name == null) {
return null;
}
AddressSpace space = Objects.requireNonNull(factory.getAddressSpace(name));
long min = Long.parseUnsignedLong(fieldMin.getText(), 16);
long max = Long.parseUnsignedLong(fieldMax.getText(), 16);
return new AddressRangeImpl(space.getAddress(min), space.getAddress(max));
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
fieldSpace.setEnabled(enabled);
fieldMin.setEnabled(enabled);
fieldMax.setEnabled(enabled);
}
}

View file

@ -0,0 +1,191 @@
/* ###
* 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 docking.widgets.model;
import java.awt.Component;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import javax.swing.*;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
public class GLifespanField extends JPanel {
private static final String NEG_INF = "-\u221e";
private static final String POS_INF = "+\u221e";
private final JLabel labelLower = new JLabel("[");
private final JComboBox<String> fieldMin = new JComboBox<>();
private final JComboBox<String> fieldMax = new JComboBox<>();
private final JLabel labelUpper = new JLabel("]");
private final DefaultComboBoxModel<String> modelMin = new DefaultComboBoxModel<>();
private final DefaultComboBoxModel<String> modelMax = new DefaultComboBoxModel<>();
public GLifespanField() {
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(labelLower);
add(fieldMin);
add(new JLabel("\u2025"));
add(fieldMax);
add(labelUpper);
modelMin.addElement(NEG_INF);
modelMax.addElement(POS_INF);
fieldMin.setEditable(true);
fieldMin.setModel(modelMin);
fieldMax.setEditable(true);
fieldMax.setModel(modelMax);
fieldMin.getEditor().getEditorComponent().addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
minFocusLost(e);
checkDispatchFocus(e);
}
});
fieldMax.getEditor().getEditorComponent().addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
maxFocusLost(e);
checkDispatchFocus(e);
}
});
}
protected void checkDispatchFocus(FocusEvent e) {
Component opp = e.getOppositeComponent();
if (opp == null || !SwingUtilities.isDescendingFrom(opp, this)) {
dispatchEvent(e);
}
}
protected long parseLong(String text, long defaultVal) {
try {
return Long.parseLong(text);
}
catch (NumberFormatException e) {
return defaultVal;
}
}
protected void revalidateMin() {
String value = (String) fieldMin.getSelectedItem();
if (NEG_INF.equals(value)) {
labelLower.setText("(");
}
else {
fieldMin.setSelectedItem(Long.toString(parseLong(value, 0)));
labelLower.setText("[");
}
}
protected void revalidateMax() {
String value = (String) fieldMax.getSelectedItem();
if (POS_INF.equals(value)) {
labelUpper.setText(")");
}
else {
fieldMax.setSelectedItem(Long.toString(parseLong(value, 0)));
labelUpper.setText("]");
}
}
protected void adjustMaxToMin() {
if (unbounded()) {
return;
}
long min = parseLong((String) fieldMin.getSelectedItem(), 0);
long max = Math.max(min, parseLong((String) fieldMax.getSelectedItem(), min));
fieldMax.setSelectedItem(Long.toString(max));
}
protected boolean unbounded() {
return NEG_INF.equals(fieldMin.getSelectedItem()) ||
POS_INF.equals(fieldMax.getSelectedItem());
}
protected void adjustMinToMax() {
if (unbounded()) {
return;
}
long max = parseLong((String) fieldMax.getSelectedItem(), 0);
long min = Math.min(max, parseLong((String) fieldMin.getSelectedItem(), max));
fieldMin.setSelectedItem(Long.toString(min));
}
protected void minFocusLost(FocusEvent e) {
revalidateMin();
adjustMaxToMin();
}
protected void maxFocusLost(FocusEvent e) {
revalidateMax();
adjustMinToMax();
}
public void setLifespan(Range<Long> lifespan) {
if (lifespan.hasLowerBound() && lifespan.lowerBoundType() == BoundType.OPEN ||
lifespan.hasUpperBound() && lifespan.upperBoundType() == BoundType.OPEN) {
throw new IllegalArgumentException("Lifespans must be closed or unbounded");
}
if (!lifespan.hasLowerBound()) {
fieldMin.setSelectedItem(NEG_INF);
}
else {
fieldMin.setSelectedItem(Long.toString(lifespan.lowerEndpoint()));
}
if (!lifespan.hasUpperBound()) {
fieldMax.setSelectedItem(POS_INF);
}
else {
fieldMax.setSelectedItem(Long.toString(lifespan.upperEndpoint()));
}
}
public Range<Long> getLifespan() {
String min = (String) fieldMin.getSelectedItem();
String max = (String) fieldMax.getSelectedItem();
if (NEG_INF.equals(min)) {
if (POS_INF.equals(max)) {
return Range.all();
}
else {
return Range.atMost(Long.parseLong(max));
}
}
else {
if (POS_INF.equals(max)) {
return Range.atLeast(Long.parseLong(min));
}
else {
return Range.closed(Long.parseLong(min), Long.parseLong(max));
}
}
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
fieldMin.setEnabled(enabled);
fieldMax.setEnabled(enabled);
}
}

View file

@ -32,6 +32,14 @@ public class CustomToStringCellRenderer<T> extends AbstractGColumnRenderer<T> {
DEFAULT, MONOSPACED, BOLD; DEFAULT, MONOSPACED, BOLD;
} }
public static String longToPrefixedHexString(long v) {
return v < 0 ? "-0x" + Long.toString(-v, 16) : "0x" + Long.toString(v, 16);
}
public static String bigIntToPrefixedHexString(BigInteger v) {
return v.signum() < 0 ? "-0x" + v.negate().toString(16) : "0x" + v.toString(16);
}
public static final CustomToStringCellRenderer<Object> MONO_OBJECT = public static final CustomToStringCellRenderer<Object> MONO_OBJECT =
new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Object.class, new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Object.class,
(v, s) -> v == null ? "<null>" : v.toString(), false); (v, s) -> v == null ? "<null>" : v.toString(), false);
@ -40,13 +48,13 @@ public class CustomToStringCellRenderer<T> extends AbstractGColumnRenderer<T> {
(v, s) -> v == null ? "<null>" : v, true); (v, s) -> v == null ? "<null>" : v, true);
public static final CustomToStringCellRenderer<Long> MONO_LONG_HEX = public static final CustomToStringCellRenderer<Long> MONO_LONG_HEX =
new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Long.class, new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Long.class,
(v, s) -> v == null ? "<null>" : "0x" + Long.toString(v, 16), false); (v, s) -> v == null ? "<null>" : longToPrefixedHexString(v), false);
public static final CustomToStringCellRenderer<Long> MONO_ULONG_HEX = public static final CustomToStringCellRenderer<Long> MONO_ULONG_HEX =
new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Long.class, new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Long.class,
(v, s) -> v == null ? "<null>" : "0x" + Long.toUnsignedString(v, 16), false); (v, s) -> v == null ? "<null>" : "0x" + Long.toUnsignedString(v, 16), false);
public static final CustomToStringCellRenderer<BigInteger> MONO_BIG_HEX = public static final CustomToStringCellRenderer<BigInteger> MONO_BIG_HEX =
new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, BigInteger.class, new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, BigInteger.class,
(v, s) -> v == null ? "<null>" : "0x" + v.toString(16), false); (v, s) -> v == null ? "<null>" : bigIntToPrefixedHexString(v), false);
private final CustomFont customFont; private final CustomFont customFont;
private final Class<T> cls; private final Class<T> cls;

View file

@ -0,0 +1,76 @@
/* ###
* 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 docking.widgets.model;
import static org.junit.Assume.assumeFalse;
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import org.junit.Before;
import org.junit.Test;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
public class DemoFieldsTest extends AbstractGhidraHeadedIntegrationTest {
@Before
public void checkNotBatch() {
assumeFalse(SystemUtilities.isInTestingBatchMode());
}
@Test
public void testDemoGLifespanField() throws InterruptedException {
GLifespanField spanField = new GLifespanField();
JFrame frame = new JFrame("Demo GLifespanField");
frame.setBounds(40, 40, 400, 90);
frame.setLayout(new BorderLayout());
frame.add(spanField);
frame.setVisible(true);
JButton button = new JButton("Print");
button.addActionListener(evt -> {
Msg.info(this, "Lifespan: " + spanField.getLifespan());
});
frame.add(button, BorderLayout.SOUTH);
Thread.sleep(1000000);
}
@Test
public void testDemoGAddressRangeField() throws InterruptedException {
GAddressRangeField rangeField = new GAddressRangeField();
rangeField.setAddressFactory(getSLEIGH_X86_64_LANGUAGE().getAddressFactory());
JFrame frame = new JFrame("Demo GAddressRangeField");
frame.setBounds(40, 40, 400, 90);
frame.setLayout(new BorderLayout());
frame.add(rangeField);
frame.setVisible(true);
JButton button = new JButton("Print");
button.addActionListener(evt -> {
Msg.info(this, "Range: " + rangeField.getRange());
});
frame.add(button, BorderLayout.SOUTH);
Thread.sleep(1000000);
}
}