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;
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 end = start.addNoWrap(length - 1);
|
|
||||||
// Also check end in the destination
|
|
||||||
Address toAddress = to.getByteAddress();
|
Address toAddress = to.getByteAddress();
|
||||||
toAddress.addNoWrap(length - 1); // Anticipate possible AddressOverflow
|
long maxFromLengthMinus1 =
|
||||||
AddressRangeImpl range = new AddressRangeImpl(start, end);
|
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) {
|
if (truncateExisting) {
|
||||||
long truncEnd = DBTraceUtils.lowerEndpoint(from.getLifespan()) - 1;
|
long truncEnd = DBTraceUtils.lowerEndpoint(fromLifespan) - 1;
|
||||||
for (TraceStaticMapping existing : List
|
for (TraceStaticMapping existing : List
|
||||||
.copyOf(manager.findAllOverlapping(range, from.getLifespan()))) {
|
.copyOf(manager.findAllOverlapping(range, fromLifespan))) {
|
||||||
existing.delete();
|
existing.delete();
|
||||||
if (Long.compareUnsigned(existing.getStartSnap(), truncEnd) < 0) {
|
if (fromLifespan.hasLowerBound() &&
|
||||||
|
Long.compare(existing.getStartSnap(), truncEnd) <= 0) {
|
||||||
manager.add(existing.getTraceAddressRange(),
|
manager.add(existing.getTraceAddressRange(),
|
||||||
Range.closed(existing.getStartSnap(), truncEnd),
|
Range.closed(existing.getStartSnap(), truncEnd),
|
||||||
existing.getStaticProgramURL(), existing.getStaticAddress());
|
existing.getStaticProgramURL(), existing.getStaticAddress());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
manager.add(range, from.getLifespan(), toURL,
|
manager.add(range, fromLifespan, toURL, toAddress.toString(true));
|
||||||
toAddress.toString(true));
|
|
||||||
}
|
|
||||||
catch (AddressOverflowException e) {
|
|
||||||
throw new IllegalArgumentException("Length would cause address overflow", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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())) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
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;
|
||||||
|
|
|
@ -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