diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerAddMappingDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerAddMappingDialog.java index b7da3b86e4..355d496f63 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerAddMappingDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerAddMappingDialog.java @@ -34,6 +34,7 @@ import ghidra.program.util.ProgramLocation; import ghidra.trace.model.*; import ghidra.trace.model.modules.TraceConflictedMappingException; import ghidra.util.MathUtilities; +import ghidra.util.MessageType; import ghidra.util.layout.PairLayout; public class DebuggerAddMappingDialog extends ReusableDialogComponentProvider { @@ -52,7 +53,7 @@ public class DebuggerAddMappingDialog extends ReusableDialogComponentProvider { private final GSpanField fieldSpan = new GSpanField(); public DebuggerAddMappingDialog() { - super("Add Static Mapping", false, false, true, false); + super("Add Static Mapping", false, true, true, false); populateComponents(); } @@ -103,6 +104,8 @@ public class DebuggerAddMappingDialog extends ReusableDialogComponentProvider { rigFocusAndEnter(fieldLength, this::lengthChanged); rigFocusAndEnter(fieldSpan, this::spanChanged); + fieldSpan.setLifespan(Lifespan.nowOn(0)); + addWorkPanel(panel); addApplyButton(); @@ -287,6 +290,12 @@ public class DebuggerAddMappingDialog extends ReusableDialogComponentProvider { return bi.longValue(); // Do not use exact. It checks bitLength again, and considers sign. } + @Override + protected void dialogShown() { + super.dialogShown(); + setStatusText(""); + } + @Override protected void applyCallback() { TraceLocation from = new DefaultTraceLocation(trace, null, fieldSpan.getLifespan(), @@ -295,13 +304,20 @@ public class DebuggerAddMappingDialog extends ReusableDialogComponentProvider { fieldProgRange.getRange().getMinAddress()); try { - mappingService.addMapping(from, to, getLength(), true); + mappingService.addMapping(from, to, getLength(), false); + setStatusText(""); } catch (TraceConflictedMappingException e) { - throw new AssertionError(e); // I said truncateExisting + setStatusText(e.getMessage(), MessageType.ERROR); } } + @Override + protected void dismissCallback() { + setStatusText(""); + super.dismissCallback(); + } + /** * Set the values of the fields * diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java index d05e1cfdc9..de6fa68011 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java @@ -659,8 +659,13 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { if (currentProgram == null || current.getTrace() == null) { return; } - staticMappingService.addIdentityMapping(current.getTrace(), currentProgram, - Lifespan.nowOn(traceManager.getCurrentSnap()), true); + try { + staticMappingService.addIdentityMapping(current.getTrace(), currentProgram, + Lifespan.nowOn(traceManager.getCurrentSnap()), false); + } + catch (TraceConflictedMappingException e) { + Msg.showError(this, null, "Map Identically", e.getMessage()); + } } private void activatedMapManually(ActionContext ignored) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProvider.java index 119c9692f1..f6932a10c3 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProvider.java @@ -291,11 +291,11 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter : traceLen == 0 ? progLen : MathUtilities.unsignedMin(progLen, traceLen); Address progStart = progLen != 0 ? progSel.getMinAddress() : progLoc.getAddress(); Address traceStart = traceLen != 0 ? traceSel.getMinAddress() : traceLoc.getAddress(); - TraceProgramView view = (TraceProgramView) traceLoc.getProgram(); + long snap = traceManager.getCurrentSnap(); try { addMappingDialog.setValues(progLoc.getProgram(), currentTrace, progStart, traceStart, - length, Lifespan.nowOn(view.getSnap())); + length, Lifespan.nowOn(snap)); } catch (AddressOverflowException e) { Msg.showError(this, null, "Add Mapping", "Error populating dialog"); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java index cb0903a0c9..cf48ae10c1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils.Extrema; import ghidra.app.services.DebuggerEmulationService; import ghidra.dbg.target.*; import ghidra.dbg.target.schema.TargetObjectSchema; @@ -41,7 +42,8 @@ import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.thread.*; import ghidra.trace.model.time.TraceSnapshot; -import ghidra.util.*; +import ghidra.util.DifferenceAddressSetView; +import ghidra.util.NumericUtilities; import ghidra.util.database.UndoableTransaction; import ghidra.util.exception.DuplicateNameException; @@ -116,18 +118,6 @@ public enum ProgramEmulationUtils { return result; } - static class Extrema { - Address min = null; - Address max = null; - - void consider(AddressRange range) { - min = min == null ? range.getMinAddress() - : ComparatorMath.cmin(min, range.getMinAddress()); - max = max == null ? range.getMaxAddress() - : ComparatorMath.cmax(max, range.getMaxAddress()); - } - } - /** * Create regions for each block in a program, without relocation, and map the program in * @@ -149,10 +139,7 @@ public enum ProgramEmulationUtils { Map extremaBySpace = new HashMap<>(); try { for (MemoryBlock block : program.getMemory().getBlocks()) { - if (!block.isLoaded()) { - continue; - } - if (block.isOverlay()) { + if (!DebuggerStaticMappingUtils.isReal(block)) { continue; } AddressRange range = new AddressRangeImpl(block.getStart(), block.getEnd()); @@ -170,9 +157,8 @@ public enum ProgramEmulationUtils { for (Extrema extrema : extremaBySpace.values()) { DebuggerStaticMappingUtils.addMapping( new DefaultTraceLocation(trace, null, Lifespan.nowOn(snapshot.getKey()), - extrema.min), - new ProgramLocation(program, extrema.min), - extrema.max.subtract(extrema.min), false); + extrema.getMin()), + new ProgramLocation(program, extrema.getMin()), extrema.getLength(), false); } } catch (TraceOverlappedRegionException | DuplicateNameException diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java index a42b48fade..ed1080e2ca 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingUtils.java @@ -25,11 +25,13 @@ import ghidra.framework.model.ProjectData; import ghidra.program.model.address.*; import ghidra.program.model.listing.Library; import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.symbol.ExternalManager; import ghidra.program.util.ProgramLocation; import ghidra.trace.model.*; import ghidra.trace.model.modules.*; import ghidra.trace.model.program.TraceProgramView; +import ghidra.util.ComparatorMath; import ghidra.util.Msg; public enum DebuggerStaticMappingUtils { @@ -153,42 +155,72 @@ public enum DebuggerStaticMappingUtils { addMapping(fromLoc, toLoc, length, truncateExisting); } - public static void addIdentityMapping(Trace from, Program toProgram, Lifespan lifespan, - boolean truncateExisting) { - Map mins = new HashMap<>(); - Map maxs = new HashMap<>(); - for (AddressRange range : toProgram.getMemory().getAddressRanges()) { - mins.compute(range.getAddressSpace().getName(), (n, min) -> { - Address can = range.getMinAddress(); - if (min == null || can.compareTo(min) < 0) { - return can; - } - return min; - }); - maxs.compute(range.getAddressSpace().getName(), (n, max) -> { - Address can = range.getMaxAddress(); - if (max == null || can.compareTo(max) > 0) { - return can; - } - return max; - }); + public static class Extrema { + private Address min = null; + private Address max = null; + + public void consider(AddressRange range) { + min = min == null ? range.getMinAddress() + : ComparatorMath.cmin(min, range.getMinAddress()); + max = max == null ? range.getMaxAddress() + : ComparatorMath.cmax(max, range.getMaxAddress()); } - for (String name : mins.keySet()) { - AddressRange range = clippedRange(from, name, mins.get(name).getOffset(), - maxs.get(name).getOffset()); - if (range == null) { + + public Address getMin() { + return min; + } + + public Address getMax() { + return max; + } + + public long getLength() { + return max.subtract(min) + 1; + } + } + + public static boolean isReal(MemoryBlock block) { + return block.isLoaded() && !block.isOverlay() && !block.isExternalBlock(); + } + + public static void addIdentityMapping(Trace from, Program toProgram, Lifespan lifespan, + boolean truncateExisting) throws TraceConflictedMappingException { + AddressSet failures = new AddressSet(); + Set conflicts = new HashSet<>(); + Map extremaBySpace = new HashMap<>(); + for (MemoryBlock block : toProgram.getMemory().getBlocks()) { + if (!isReal(block)) { + continue; + } + AddressRange range = new AddressRangeImpl(block.getStart(), block.getEnd()); + extremaBySpace.computeIfAbsent(range.getAddressSpace(), s -> new Extrema()) + .consider(range); + } + + for (Extrema extrema : extremaBySpace.values()) { + AddressRange fromRange = + clippedRange(from, extrema.getMin().getAddressSpace().getName(), + extrema.getMin().getOffset(), extrema.getMax().getOffset()); + if (fromRange == null) { continue; } try { - addMapping(new DefaultTraceLocation(from, null, lifespan, range.getMinAddress()), - new ProgramLocation(toProgram, mins.get(name)), range.getLength(), + addMapping( + new DefaultTraceLocation(from, null, lifespan, fromRange.getMinAddress()), + new ProgramLocation(toProgram, extrema.getMin()), fromRange.getLength(), truncateExisting); } catch (TraceConflictedMappingException e) { + failures.add(fromRange); + conflicts.addAll(e.getConflicts()); Msg.error(DebuggerStaticMappingUtils.class, - "Could not add identity mapping " + range + ": " + e.getMessage()); + "Could not add identity mapping " + fromRange + ": " + e.getMessage()); } } + if (!failures.isEmpty()) { + throw new TraceConflictedMappingException("Conflicting mappings for " + failures, + conflicts); + } } protected static AddressRange clippedRange(Trace trace, String spaceName, long min, diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMapping.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMapping.java index 066621c0a4..e7bf8120ff 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMapping.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMapping.java @@ -112,6 +112,13 @@ public class DBTraceStaticMapping extends DBAnnotatedObject this.manager = manager; } + @Override + public String toString() { + return String.format( + "Mapping: dynamic=%s,program=%s,address=%s,length=0x%x,shift=0x%x,lifespan=%s", + traceAddress, staticProgramURL, staticAddress, length, shift, lifespan); + } + @Override protected void fresh(boolean created) throws IOException { if (created) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMappingManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMappingManager.java index 8870236512..5c00bc1715 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMappingManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceStaticMappingManager.java @@ -85,8 +85,9 @@ public class DBTraceStaticMappingManager implements TraceStaticMappingManager, D DBTraceStaticMapping conflict = findAnyConflicting(range, lifespan, toProgramURL, toAddress); if (conflict != null) { + // TODO: Find all conflicts? throw new TraceConflictedMappingException("Another mapping would conflict", - conflict); + Set.of(conflict)); } // TODO: A more sophisticated coverage check? // TODO: Better coalescing @@ -133,8 +134,7 @@ public class DBTraceStaticMappingManager implements TraceStaticMappingManager, D @Override public DBTraceStaticMapping findAnyConflicting(AddressRange range, Lifespan lifespan, - URL toProgramURL, - String toAddress) { + URL toProgramURL, String toAddress) { for (DBTraceStaticMapping mapping : mappingsByAddress.head(range.getMaxAddress(), true).descending().values()) { if (!mapping.conflictsWith(range, lifespan, toProgramURL, toAddress)) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceConflictedMappingException.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceConflictedMappingException.java index 159a2bc27b..383ece88c4 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceConflictedMappingException.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceConflictedMappingException.java @@ -15,15 +15,19 @@ */ package ghidra.trace.model.modules; -public class TraceConflictedMappingException extends Exception { - private final TraceStaticMapping conflict; +import java.util.Collection; +import java.util.Set; - public TraceConflictedMappingException(String message, TraceStaticMapping conflict) { - super(message); - this.conflict = conflict; +public class TraceConflictedMappingException extends RuntimeException { + private final Set conflicts; + + public TraceConflictedMappingException(String message, + Collection conflicts) { + super(message + ": " + conflicts); + this.conflicts = Set.copyOf(conflicts); } - public TraceStaticMapping getConflict() { - return conflict; + public Set getConflicts() { + return conflicts; } }