mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-890: Added dialog for adding mappings and fixing various db/ui issues re/ length.
This commit is contained in:
parent
bd2ac52c9b
commit
e6fd14f88c
13 changed files with 967 additions and 111 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,35 +15,30 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.modules;
|
||||
|
||||
import ghidra.app.events.ProgramLocationPluginEvent;
|
||||
import ghidra.app.events.ProgramSelectionPluginEvent;
|
||||
import ghidra.app.events.ProgramActivatedPluginEvent;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
||||
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.DebuggerTraceManagerService;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
|
||||
@PluginInfo( //
|
||||
shortDescription = "Debugger static mapping manager", //
|
||||
description = "GUI to manage static mappings", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger static mapping manager",
|
||||
description = "GUI to manage static mappings",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.RELEASED,
|
||||
eventsConsumed = {
|
||||
TraceActivatedPluginEvent.class, //
|
||||
ProgramLocationPluginEvent.class, //
|
||||
ProgramSelectionPluginEvent.class, //
|
||||
TraceLocationPluginEvent.class, //
|
||||
TraceSelectionPluginEvent.class //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
DebuggerStaticMappingService.class, //
|
||||
DebuggerTraceManagerService.class, //
|
||||
} //
|
||||
)
|
||||
TraceActivatedPluginEvent.class,
|
||||
ProgramActivatedPluginEvent.class,
|
||||
},
|
||||
servicesRequired = {
|
||||
DebuggerStaticMappingService.class,
|
||||
DebuggerTraceManagerService.class,
|
||||
})
|
||||
public class DebuggerStaticMappingPlugin extends AbstractDebuggerPlugin {
|
||||
protected DebuggerStaticMappingProvider provider;
|
||||
|
||||
|
@ -70,17 +65,9 @@ public class DebuggerStaticMappingPlugin extends AbstractDebuggerPlugin {
|
|||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
provider.setTrace(ev.getActiveCoordinates().getTrace());
|
||||
}
|
||||
if (event instanceof ProgramLocationPluginEvent) {
|
||||
provider.contextChanged();
|
||||
}
|
||||
if (event instanceof ProgramSelectionPluginEvent) {
|
||||
provider.contextChanged();
|
||||
}
|
||||
if (event instanceof TraceLocationPluginEvent) {
|
||||
provider.contextChanged();
|
||||
}
|
||||
if (event instanceof TraceSelectionPluginEvent) {
|
||||
provider.contextChanged();
|
||||
if (event instanceof ProgramActivatedPluginEvent) {
|
||||
ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
|
||||
provider.setProgram(ev.getActiveProgram());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.modules;
|
|||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
@ -41,15 +42,18 @@ import ghidra.framework.model.DomainObject;
|
|||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.Trace;
|
||||
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.util.MathUtilities;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
||||
|
@ -60,7 +64,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
|
|||
DYNAMIC_ADDRESS("Dynamic Address", Address.class, StaticMappingRow::getTraceAddress),
|
||||
STATIC_URL("Static Program", URL.class, StaticMappingRow::getStaticProgramURL),
|
||||
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),
|
||||
LIFESPAN("Lifespan", Range.class, StaticMappingRow::getLifespan);
|
||||
|
||||
|
@ -113,6 +117,8 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
|
|||
|
||||
private final DebuggerStaticMappingPlugin plugin;
|
||||
|
||||
private final DebuggerAddMappingDialog addMappingDialog;
|
||||
|
||||
@AutoServiceConsumed
|
||||
private DebuggerStaticMappingService mappingService;
|
||||
// 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);
|
||||
this.plugin = plugin;
|
||||
|
||||
this.addMappingDialog = new DebuggerAddMappingDialog();
|
||||
this.autoWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
|
||||
setIcon(DebuggerResources.ICON_PROVIDER_MAPPINGS);
|
||||
|
@ -157,6 +164,11 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
|
|||
createActions();
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private void setMappingService(DebuggerStaticMappingService mappingService) {
|
||||
addMappingDialog.setMappingService(mappingService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalAction(DockingActionIf action) {
|
||||
super.addLocalAction(action);
|
||||
|
@ -215,7 +227,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
|
|||
statAddrCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
TableColumn lengthCol = columnModel.getColumn(StaticMappingTableColumns.LENGTH.ordinal());
|
||||
// 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());
|
||||
shiftCol.setCellRenderer(CustomToStringCellRenderer.MONO_LONG_HEX);
|
||||
}
|
||||
|
@ -223,7 +235,6 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
|
|||
protected void createActions() {
|
||||
actionAdd = AddAction.builder(plugin)
|
||||
.description("Add Mapping from Listing Selections")
|
||||
.enabledWhen(this::haveMappableSelections)
|
||||
.onAction(this::activatedAdd)
|
||||
.buildAndInstallLocal(this);
|
||||
actionRemove = RemoveAction.builder(plugin)
|
||||
|
@ -240,38 +251,10 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
|
|||
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) {
|
||||
// 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) {
|
||||
return;
|
||||
}
|
||||
|
@ -303,17 +286,13 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
|
|||
Address progStart = progLen != 0 ? progSel.getMinAddress() : progLoc.getAddress();
|
||||
Address traceStart = traceLen != 0 ? traceSel.getMinAddress() : traceLoc.getAddress();
|
||||
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 =
|
||||
UndoableTransaction.start(currentTrace, "Add Static Mapping", false)) {
|
||||
mappingService.addMapping(from, to, length, true);
|
||||
tid.commit();
|
||||
try {
|
||||
addMappingDialog.setValues(progLoc.getProgram(), currentTrace, progStart, traceStart,
|
||||
length, Range.atLeast(view.getSnap()));
|
||||
}
|
||||
catch (TraceConflictedMappingException e) {
|
||||
throw new AssertionError(e); // I said truncateExisting
|
||||
catch (AddressOverflowException e) {
|
||||
Msg.showError(this, null, "Add Mapping", "Error populating dialog");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -387,5 +366,11 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
|
|||
currentTrace = trace;
|
||||
addNewListeners();
|
||||
loadMappings();
|
||||
|
||||
addMappingDialog.setTrace(trace);
|
||||
}
|
||||
|
||||
public void setProgram(Program program) {
|
||||
addMappingDialog.setProgram(program);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.modules;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.URL;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
@ -24,6 +25,7 @@ import ghidra.trace.model.Trace;
|
|||
import ghidra.trace.model.modules.TraceStaticMapping;
|
||||
|
||||
public class StaticMappingRow {
|
||||
private static final BigInteger BIT64 = BigInteger.ONE.shiftLeft(64);
|
||||
private final TraceStaticMapping mapping;
|
||||
|
||||
public StaticMappingRow(TraceStaticMapping mapping) {
|
||||
|
@ -54,6 +56,19 @@ public class StaticMappingRow {
|
|||
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() {
|
||||
return mapping.getShift();
|
||||
}
|
||||
|
|
|
@ -940,31 +940,36 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
|||
if (toURL == null) {
|
||||
noProject();
|
||||
}
|
||||
try {
|
||||
Address start = from.getAddress();
|
||||
Address end = start.addNoWrap(length - 1);
|
||||
// Also check end in the destination
|
||||
Address fromAddress = from.getAddress();
|
||||
Address toAddress = to.getByteAddress();
|
||||
toAddress.addNoWrap(length - 1); // Anticipate possible AddressOverflow
|
||||
AddressRangeImpl range = new AddressRangeImpl(start, end);
|
||||
long maxFromLengthMinus1 =
|
||||
fromAddress.getAddressSpace().getMaxAddress().subtract(fromAddress);
|
||||
long maxToLengthMinus1 =
|
||||
toAddress.getAddressSpace().getMaxAddress().subtract(toAddress);
|
||||
if (Long.compareUnsigned(length - 1, maxFromLengthMinus1) > 0) {
|
||||
throw new IllegalArgumentException("Length would cause address overflow in trace");
|
||||
}
|
||||
if (Long.compareUnsigned(length - 1, maxToLengthMinus1) > 0) {
|
||||
throw new IllegalArgumentException("Length would cause address overflow in program");
|
||||
}
|
||||
Address end = fromAddress.addWrap(length - 1);
|
||||
// Also check end in the destination
|
||||
AddressRangeImpl range = new AddressRangeImpl(fromAddress, end);
|
||||
Range<Long> fromLifespan = from.getLifespan();
|
||||
if (truncateExisting) {
|
||||
long truncEnd = DBTraceUtils.lowerEndpoint(from.getLifespan()) - 1;
|
||||
long truncEnd = DBTraceUtils.lowerEndpoint(fromLifespan) - 1;
|
||||
for (TraceStaticMapping existing : List
|
||||
.copyOf(manager.findAllOverlapping(range, from.getLifespan()))) {
|
||||
.copyOf(manager.findAllOverlapping(range, fromLifespan))) {
|
||||
existing.delete();
|
||||
if (Long.compareUnsigned(existing.getStartSnap(), truncEnd) < 0) {
|
||||
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
|
||||
|
|
|
@ -535,7 +535,7 @@ public interface DebuggerStaticMappingService {
|
|||
*
|
||||
* @param from the source trace location, including lifespan
|
||||
* @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
|
||||
* @throws TraceConflictedMappingException if a conflicting mapping overlaps the source and
|
||||
* {@code truncateExisting} is false.
|
||||
|
|
|
@ -106,7 +106,7 @@ public class DebuggerStaticMappingProviderTest extends AbstractGhidraHeadedDebug
|
|||
|
||||
@Test
|
||||
public void testAddAction() throws Exception {
|
||||
assertFalse(mappingsProvider.actionAdd.isEnabled());
|
||||
assertTrue(mappingsProvider.actionAdd.isEnabled());
|
||||
|
||||
createProgramFromTrace(tb.trace);
|
||||
intoProject(tb.trace);
|
||||
|
@ -135,16 +135,18 @@ public class DebuggerStaticMappingProviderTest extends AbstractGhidraHeadedDebug
|
|||
programManager.openProgram(program);
|
||||
waitForSwing();
|
||||
|
||||
assertFalse(mappingsProvider.actionAdd.isEnabled());
|
||||
|
||||
ProgramSelection traceSel =
|
||||
new ProgramSelection(tb.addr(0xdeadbeefL), tb.addr(0xdeadbeefL + 0x0f));
|
||||
listingPlugin.getProvider().setSelection(traceSel);
|
||||
codeViewerPlugin.goTo(new ProgramLocation(program, addr(program, 0xc0de1234L)), true);
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(mappingsProvider.actionAdd.isEnabled());
|
||||
performAction(mappingsProvider.actionAdd, true);
|
||||
performAction(mappingsProvider.actionAdd, false);
|
||||
|
||||
DebuggerAddMappingDialog dialog = waitForDialogComponent(DebuggerAddMappingDialog.class);
|
||||
dialog.applyCallback();
|
||||
dialog.close();
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
TraceStaticMapping entry = Unique.assertOne(manager.getAllEntries());
|
||||
assertEquals(Range.atLeast(0L), entry.getLifespan());
|
||||
|
|
|
@ -76,7 +76,7 @@ public class DBTraceStaticMappingManager implements TraceStaticMappingManager, D
|
|||
public DBTraceStaticMapping add(AddressRange range, Range<Long> lifespan, URL toProgramURL,
|
||||
String toAddress)
|
||||
throws TraceConflictedMappingException {
|
||||
if (lifespan.lowerBoundType() != BoundType.CLOSED) {
|
||||
if (lifespan.hasLowerBound() && lifespan.lowerBoundType() != BoundType.CLOSED) {
|
||||
throw new IllegalArgumentException("Lower bound must be closed");
|
||||
}
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
|
|
|
@ -63,7 +63,7 @@ public interface TraceStaticMapping {
|
|||
/**
|
||||
* 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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -32,6 +32,14 @@ public class CustomToStringCellRenderer<T> extends AbstractGColumnRenderer<T> {
|
|||
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 =
|
||||
new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Object.class,
|
||||
(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);
|
||||
public static final CustomToStringCellRenderer<Long> MONO_LONG_HEX =
|
||||
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 =
|
||||
new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Long.class,
|
||||
(v, s) -> v == null ? "<null>" : "0x" + Long.toUnsignedString(v, 16), false);
|
||||
public static final CustomToStringCellRenderer<BigInteger> MONO_BIG_HEX =
|
||||
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 Class<T> cls;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue