diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java index 5699a01209..31c2100629 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/TraceBreakpointSet.java @@ -4,9 +4,9 @@ * 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. @@ -65,7 +65,9 @@ class TraceBreakpointSet { @Override public String toString() { - return String.format("", address, trace.getName(), breakpoints); + synchronized (breakpoints) { + return String.format("", address, trace.getName(), breakpoints); + } } /** @@ -126,22 +128,24 @@ class TraceBreakpointSet { */ public TraceMode computeMode() { TraceMode mode = TraceMode.NONE; - if (getControlMode().useEmulatedBreakpoints()) { + synchronized (breakpoints) { + if (getControlMode().useEmulatedBreakpoints()) { + for (IDHashed bpt : breakpoints) { + mode = mode.combine(computeEmuMode(bpt.obj)); + if (mode == TraceMode.MISSING) { + return mode; + } + } + return mode; + } for (IDHashed bpt : breakpoints) { - mode = mode.combine(computeEmuMode(bpt.obj)); + mode = mode.combine(computeTargetMode(bpt.obj)); if (mode == TraceMode.MISSING) { return mode; } } return mode; } - for (IDHashed bpt : breakpoints) { - mode = mode.combine(computeTargetMode(bpt.obj)); - if (mode == TraceMode.MISSING) { - return mode; - } - } - return mode; } /** @@ -188,14 +192,16 @@ class TraceBreakpointSet { */ public String computeSleigh() { String sleigh = null; - for (IDHashed bpt : breakpoints) { - String s = bpt.obj.getEmuSleigh(); - if (sleigh != null && !sleigh.equals(s)) { - return null; + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + String s = bpt.obj.getEmuSleigh(); + if (sleigh != null && !sleigh.equals(s)) { + return null; + } + sleigh = s; } - sleigh = s; + return sleigh; } - return sleigh; } /** @@ -206,8 +212,10 @@ class TraceBreakpointSet { public void setEmuSleigh(String emuSleigh) { this.emuSleigh = emuSleigh; try (Transaction tx = trace.openTransaction("Set breakpoint Sleigh")) { - for (IDHashed bpt : breakpoints) { - bpt.obj.setEmuSleigh(emuSleigh); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + bpt.obj.setEmuSleigh(emuSleigh); + } } } } @@ -218,7 +226,9 @@ class TraceBreakpointSet { * @return true if empty, false otherwise */ public boolean isEmpty() { - return breakpoints.isEmpty(); + synchronized (breakpoints) { + return breakpoints.isEmpty(); + } } /** @@ -227,7 +237,9 @@ class TraceBreakpointSet { * @return the breakpoints */ public Set getBreakpoints() { - return breakpoints.stream().map(e -> e.obj).collect(Collectors.toUnmodifiableSet()); + synchronized (breakpoints) { + return breakpoints.stream().map(e -> e.obj).collect(Collectors.toUnmodifiableSet()); + } } /** @@ -246,7 +258,9 @@ class TraceBreakpointSet { bpt.setEmuSleigh(emuSleigh); } } - return breakpoints.add(new IDHashed<>(bpt)); + synchronized (breakpoints) { + return breakpoints.add(new IDHashed<>(bpt)); + } } /** @@ -275,7 +289,9 @@ class TraceBreakpointSet { * @return true if the set actually changes as a result */ public boolean remove(TraceBreakpoint bpt) { - return breakpoints.remove(new IDHashed<>(bpt)); + synchronized (breakpoints) { + return breakpoints.remove(new IDHashed<>(bpt)); + } } /** @@ -303,7 +319,7 @@ class TraceBreakpointSet { public void planEnable(BreakpointActionSet actions, long length, Collection kinds) { long snap = getSnap(); - if (breakpoints.isEmpty()) { + if (isEmpty()) { if (target == null || getControlMode().useEmulatedBreakpoints()) { planPlaceEmu(actions, snap, length, kinds); } @@ -339,14 +355,18 @@ class TraceBreakpointSet { } private void planEnableTarget(BreakpointActionSet actions) { - for (IDHashed bpt : breakpoints) { - actions.planEnableTarget(target, bpt.obj); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + actions.planEnableTarget(target, bpt.obj); + } } } private void planEnableEmu(BreakpointActionSet actions) { - for (IDHashed bpt : breakpoints) { - actions.planEnableEmu(bpt.obj); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + actions.planEnableEmu(bpt.obj); + } } } @@ -369,14 +389,18 @@ class TraceBreakpointSet { private void planDisableTarget(BreakpointActionSet actions, long length, Collection kinds) { - for (IDHashed bpt : breakpoints) { - actions.planDisableTarget(target, bpt.obj); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + actions.planDisableTarget(target, bpt.obj); + } } } private void planDisableEmu(BreakpointActionSet actions) { - for (IDHashed bpt : breakpoints) { - actions.planDisableEmu(bpt.obj); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + actions.planDisableEmu(bpt.obj); + } } } @@ -399,14 +423,18 @@ class TraceBreakpointSet { private void planDeleteTarget(BreakpointActionSet actions, long length, Set kinds) { - for (IDHashed bpt : breakpoints) { - actions.planDeleteTarget(target, bpt.obj); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + actions.planDeleteTarget(target, bpt.obj); + } } } private void planDeleteEmu(BreakpointActionSet actions) { - for (IDHashed bpt : breakpoints) { - actions.planDeleteEmu(bpt.obj); + synchronized (breakpoints) { + for (IDHashed bpt : breakpoints) { + actions.planDeleteEmu(bpt.obj); + } } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java index 149cc31cd7..04b76e5db0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java @@ -18,11 +18,9 @@ package ghidra.app.plugin.core.debug.service.modules; import java.io.FileNotFoundException; import java.net.URL; import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.*; import java.util.stream.Collectors; - -import org.apache.commons.lang3.ArrayUtils; +import java.util.stream.Stream; import db.Transaction; import ghidra.app.events.ProgramClosedPluginEvent; @@ -35,8 +33,6 @@ import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingProposa import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils; import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; import ghidra.app.services.*; -import ghidra.async.AsyncDebouncer; -import ghidra.async.AsyncTimer; import ghidra.debug.api.modules.*; import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry; import ghidra.debug.api.modules.RegionMapProposal.RegionMapEntry; @@ -45,9 +41,8 @@ import ghidra.framework.model.*; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; -import ghidra.generic.util.datastruct.TreeValueSortedMap; -import ghidra.generic.util.datastruct.ValueSortedMap; -import ghidra.program.model.address.*; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.address.AddressSpace; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryBlock; import ghidra.program.util.ProgramLocation; @@ -55,581 +50,64 @@ import ghidra.trace.model.*; import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.modules.*; import ghidra.trace.model.program.TraceProgramView; -import ghidra.trace.util.TraceEvents; import ghidra.util.Msg; import ghidra.util.datastruct.ListenerSet; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -//@formatter:off @PluginInfo( shortDescription = "Debugger static mapping manager", description = "Track and manage static mappings (program-trace relocations)", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, - eventsConsumed = { ProgramOpenedPluginEvent.class, ProgramClosedPluginEvent.class, - TraceOpenedPluginEvent.class, TraceClosedPluginEvent.class, }, - servicesRequired = { ProgramManager.class, DebuggerTraceManagerService.class, }, - servicesProvided = { DebuggerStaticMappingService.class, }) -//@formatter:on + eventsConsumed = { + ProgramOpenedPluginEvent.class, + ProgramClosedPluginEvent.class, + TraceOpenedPluginEvent.class, + TraceClosedPluginEvent.class, }, + servicesRequired = { + ProgramManager.class, + DebuggerTraceManagerService.class, + }, + servicesProvided = { + DebuggerStaticMappingService.class, + }) public class DebuggerStaticMappingServicePlugin extends Plugin implements DebuggerStaticMappingService, DomainFolderChangeListener { - protected class MappingEntry { - private final TraceStaticMapping mapping; + record ChangeCollector(DebuggerStaticMappingServicePlugin plugin, Set traces, + Set programs) implements AutoCloseable { - private Program program; - private AddressRange staticRange; + static Set subtract(Set a, Set b) { + Set result = new HashSet<>(a); + result.removeAll(b); + return result; + } - public MappingEntry(TraceStaticMapping mapping) { - this.mapping = mapping; + public ChangeCollector(DebuggerStaticMappingServicePlugin plugin) { + this(plugin, new HashSet<>(), new HashSet<>()); + } + + public void traceAffected(Trace trace) { + this.traces.add(trace); + } + + public void programAffected(Program program) { + if (program != null) { + this.programs.add(program); + } } @Override - public boolean equals(Object o) { - if (!(o instanceof MappingEntry that)) { - return false; - } - // Yes, use identity, since it should be the same trace db records - if (this.mapping != that.mapping) { - return false; - } - if (this.program != that.program) { - return false; - } - if (!Objects.equals(this.staticRange, that.staticRange)) { - return false; - } - return true; - } - - public Trace getTrace() { - return mapping.getTrace(); - } - - public Address addOrMax(Address start, long length) { - Address result = start.addWrapSpace(length); - if (result.compareTo(start) < 0) { - Msg.warn(this, "Mapping entry caused overflow in static address space"); - return start.getAddressSpace().getMaxAddress(); - } - return result; - } - - public boolean programOpened(Program opened) { - if (mapping.getStaticProgramURL().equals(ProgramURLUtils.getUrlFromProgram(opened))) { - this.program = opened; - Address minAddr = opened.getAddressFactory().getAddress(mapping.getStaticAddress()); - Address maxAddr = addOrMax(minAddr, mapping.getLength() - 1); - this.staticRange = new AddressRangeImpl(minAddr, maxAddr); - return true; - } - return false; - } - - public boolean programClosed(Program closed) { - if (this.program == closed) { - this.program = null; - this.staticRange = null; - return true; - } - return false; - } - - public Address getTraceAddress() { - return mapping.getMinTraceAddress(); - } - - public Address getStaticAddress() { - if (staticRange == null) { - return null; - } - return staticRange.getMinAddress(); - } - - public TraceSpan getTraceSpan() { - return new DefaultTraceSpan(mapping.getTrace(), mapping.getLifespan()); - } - - public TraceAddressSnapRange getTraceAddressSnapRange() { - // NOTE: No need to capture shape since static mappings are immutable - return new ImmutableTraceAddressSnapRange(mapping.getTraceAddressRange(), - mapping.getLifespan()); - } - - public boolean isInTraceRange(Address address, Long snap) { - return mapping.getTraceAddressRange().contains(address) && - (snap == null || mapping.getLifespan().contains(snap)); - } - - public boolean isInTraceRange(AddressRange rng, Long snap) { - return mapping.getTraceAddressRange().intersects(rng) && - (snap == null || mapping.getLifespan().contains(snap)); - } - - public boolean isInTraceLifespan(long snap) { - return mapping.getLifespan().contains(snap); - } - - public boolean isInProgramRange(Address address) { - if (staticRange == null) { - return false; - } - return staticRange.contains(address); - } - - public boolean isInProgramRange(AddressRange rng) { - if (staticRange == null) { - return false; - } - return staticRange.intersects(rng); - } - - protected Address mapTraceAddressToProgram(Address address) { - assert isInTraceRange(address, null); - long offset = address.subtract(mapping.getMinTraceAddress()); - return staticRange.getMinAddress().addWrapSpace(offset); - } - - public ProgramLocation mapTraceAddressToProgramLocation(Address address) { - if (program == null) { - throw new IllegalStateException("Static program is not opened"); - } - return new ProgramLocation(program, mapTraceAddressToProgram(address)); - } - - public AddressRange mapTraceRangeToProgram(AddressRange rng) { - assert isInTraceRange(rng, null); - AddressRange part = rng.intersect(mapping.getTraceAddressRange()); - Address min = mapTraceAddressToProgram(part.getMinAddress()); - Address max = mapTraceAddressToProgram(part.getMaxAddress()); - return new AddressRangeImpl(min, max); - } - - protected Address mapProgramAddressToTrace(Address address) { - assert isInProgramRange(address); - long offset = address.subtract(staticRange.getMinAddress()); - return mapping.getMinTraceAddress().addWrapSpace(offset); - } - - protected TraceLocation mapProgramAddressToTraceLocation(Address address) { - return new DefaultTraceLocation(mapping.getTrace(), null, mapping.getLifespan(), - mapProgramAddressToTrace(address)); - } - - public AddressRange mapProgramRangeToTrace(AddressRange rng) { - assert (rng.intersects(staticRange)); - AddressRange part = rng.intersect(staticRange); - Address min = mapProgramAddressToTrace(part.getMinAddress()); - Address max = mapProgramAddressToTrace(part.getMaxAddress()); - return new AddressRangeImpl(min, max); - } - - public boolean isStaticProgramOpen() { - return program != null; - } - - public URL getStaticProgramURL() { - return mapping.getStaticProgramURL(); + public void close() { + plugin.changeListeners.getProxy().mappingsChanged(traces, programs); } } - protected class InfoPerTrace extends TraceDomainObjectListener { - private Trace trace; - private Map outbound = new HashMap<>(); - - public InfoPerTrace(Trace trace) { - this.trace = trace; - - listenForUntyped(DomainObjectEvent.RESTORED, e -> objectRestored()); - listenFor(TraceEvents.MAPPING_ADDED, this::staticMappingAdded); - listenFor(TraceEvents.MAPPING_DELETED, this::staticMappingDeleted); - - trace.addListener(this); - - loadOutboundEntries(); - } - - private void objectRestored() { - synchronized (lock) { - Set fromClosure = collectAffectedByTrace(trace); - var old = Map.copyOf(outbound); - clearOutboundEntries(); - loadOutboundEntries(); // Also places corresponding inbound entries - if (!old.equals(outbound)) { - if (!fromClosure.isEmpty()) { - traceAffected(trace); - } - programsAffected(fromClosure); - doAffectedByTraceOpened(trace); - } - } - } - - private void staticMappingAdded(TraceStaticMapping mapping) { - // Msg.debug(this, "Trace Mapping added: " + mapping); - synchronized (lock) { - MappingEntry me = new MappingEntry(mapping); - putOutboundAndInboundEntries(me); - if (me.program != null) { - traceAffected(trace); - programAffected(me.program); - } - } - } - - private void staticMappingDeleted(TraceStaticMapping mapping) { - synchronized (lock) { - MappingEntry me = - outbound.get(new ImmutableTraceAddressSnapRange(mapping.getTraceAddressRange(), - mapping.getLifespan())); - if (me == null) { - Msg.warn(this, "It appears I lost track of something that just got removed"); - return; - } - Program program = me.program; - removeOutboundAndInboundEntries(me); - if (program != null) { - traceAffected(trace); - programAffected(program); - } - } - } - - public void dispose() { - trace.removeListener(this); - } - - protected void putOutboundAndInboundEntries(MappingEntry me) { - outbound.put(me.getTraceAddressSnapRange(), me); - - InfoPerProgram destInfo = trackedProgramInfo.get(me.getStaticProgramURL()); - if (destInfo == null) { - return; // Not opened - } - me.programOpened(destInfo.program); - destInfo.inbound.put(me, me.getStaticAddress()); - } - - protected void removeInboundEntryFor(MappingEntry me) { - InfoPerProgram destInfo = trackedProgramInfo.get(me.getStaticProgramURL()); - if (destInfo == null) { - return; // Not opened - } - destInfo.inbound.remove(me); - } - - protected void removeOutboundAndInboundEntries(MappingEntry me) { - outbound.remove(me.getTraceAddressSnapRange()); - removeInboundEntryFor(me); - } - - protected void loadOutboundEntries() { - TraceStaticMappingManager manager = trace.getStaticMappingManager(); - for (TraceStaticMapping mapping : manager.getAllEntries()) { - putOutboundAndInboundEntries(new MappingEntry(mapping)); - } - } - - protected void clearOutboundEntries() { - for (MappingEntry me : outbound.values()) { - removeInboundEntryFor(me); - } - outbound.clear(); - } - - public boolean programOpened(Program other, InfoPerProgram otherInfo) { - boolean result = false; - for (MappingEntry me : outbound.values()) { - if (me.programOpened(other)) { - otherInfo.inbound.put(me, me.getStaticAddress()); - result = true; - } - } - return result; - } - - public boolean programClosed(Program other) { - boolean result = false; - for (MappingEntry me : outbound.values()) { - result |= me.programClosed(other); - } - return result; - } - - public Set getOpenMappedProgramsAtSnap(long snap) { - Set result = new HashSet<>(); - Set toClean = new HashSet<>(); - for (Entry out : outbound.entrySet()) { - MappingEntry me = out.getValue(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - if (!me.isStaticProgramOpen()) { - continue; - } - if (!out.getKey().getLifespan().contains(snap)) { - continue; - } - result.add(me.program); - } - outbound.values().removeAll(toClean); - mappingsAffected(toClean); - return result; - } - - public ProgramLocation getOpenMappedLocations(Address address, Lifespan span) { - TraceAddressSnapRange at = new ImmutableTraceAddressSnapRange(address, span); - Set toClean = new HashSet<>(); - for (Entry out : outbound.entrySet()) { - if (out.getKey().intersects(at)) { - MappingEntry me = out.getValue(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - if (me.isStaticProgramOpen()) { - outbound.values().removeAll(toClean); - mappingsAffected(toClean); - return me.mapTraceAddressToProgramLocation(address); - } - } - } - outbound.values().removeAll(toClean); - mappingsAffected(toClean); - return null; - } - - protected void collectOpenMappedPrograms(AddressRange rng, Lifespan span, - Map> result) { - TraceAddressSnapRange tatr = new ImmutableTraceAddressSnapRange(rng, span); - Set toClean = new HashSet<>(); - for (Entry out : outbound.entrySet()) { - MappingEntry me = out.getValue(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - if (me.program == null) { - continue; - } - if (!out.getKey().intersects(tatr)) { - continue; - } - AddressRange srcRng = out.getKey().getRange().intersect(rng); - AddressRange dstRng = me.mapTraceRangeToProgram(rng); - result.computeIfAbsent(me.program, p -> new TreeSet<>()) - .add(new MappedAddressRange(srcRng, dstRng)); - } - outbound.values().removeAll(toClean); - mappingsAffected(toClean); - } - - public Map> getOpenMappedViews(AddressSetView set, - Lifespan span) { - Map> result = new HashMap<>(); - for (AddressRange rng : set) { - collectOpenMappedPrograms(rng, span, result); - } - return Collections.unmodifiableMap(result); - } - - protected void collectMappedProgramURLsInView(AddressRange rng, Lifespan span, - Set result) { - Set toClean = new HashSet<>(); - TraceAddressSnapRange tatr = new ImmutableTraceAddressSnapRange(rng, span); - for (Entry out : outbound.entrySet()) { - MappingEntry me = out.getValue(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - if (!out.getKey().intersects(tatr)) { - continue; - } - result.add(me.getStaticProgramURL()); - } - outbound.values().removeAll(toClean); - mappingsAffected(toClean); - } - - public Set getMappedProgramURLsInView(AddressSetView set, Lifespan span) { - Set result = new HashSet<>(); - for (AddressRange rng : set) { - collectMappedProgramURLsInView(rng, span, result); - } - return Collections.unmodifiableSet(result); - } - } - - protected class InfoPerProgram implements DomainObjectListener { - private Program program; - - private ValueSortedMap inbound = - TreeValueSortedMap.createWithNaturalOrder(); - - public InfoPerProgram(Program program) { - this.program = program; - program.addListener(this); - loadInboundEntries(); - } - - @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (ev.contains(DomainObjectEvent.FILE_CHANGED)) { - // TODO: This seems like overkill - programClosed(program); - programOpened(program); - } - // TODO: Can I listen for when the program moves? - // TODO: Or when relevant blocks move? - } - - protected void loadInboundEntries() { - for (InfoPerTrace traceInfo : trackedTraceInfo.values()) { - for (MappingEntry out : traceInfo.outbound.values()) { - if (out.program == program) { - inbound.put(out, out.getStaticAddress()); - } - } - } - } - - public boolean isMappedInTrace(Trace trace) { - Set toClean = new HashSet<>(); - for (MappingEntry me : inbound.keySet()) { - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - if (Objects.equals(trace, me.getTrace())) { - inbound.keySet().removeAll(toClean); - mappingsAffected(toClean); - return true; - } - } - inbound.keySet().removeAll(toClean); - mappingsAffected(toClean); - return false; - } - - public boolean traceClosed(Trace trace) { - Set updates = new HashSet<>(); - for (Entry ent : inbound.entrySet()) { - MappingEntry me = ent.getKey(); - if (Objects.equals(trace, me.getTrace())) { - updates.add(me); - } - } - return inbound.keySet().removeAll(updates); - } - - public Set getOpenMappedTraceLocations(Address address) { - Set result = new HashSet<>(); - Set toClean = new HashSet<>(); - for (Entry inPreceding : inbound.headMapByValue(address, true) - .entrySet()) { - MappingEntry me = inPreceding.getKey(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - Address start = inPreceding.getValue(); - if (start == null) { - continue; - } - if (!me.isInProgramRange(address)) { - continue; - } - result.add(me.mapProgramAddressToTraceLocation(address)); - } - inbound.keySet().removeAll(toClean); - mappingsAffected(toClean); - return result; - } - - public TraceLocation getOpenMappedTraceLocation(Trace trace, Address address, long snap) { - // TODO: Map by trace? - Set toClean = new HashSet<>(); - for (Entry inPreceding : inbound.headMapByValue(address, true) - .entrySet()) { - MappingEntry me = inPreceding.getKey(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - Address start = inPreceding.getValue(); - if (start == null) { - continue; - } - if (me.getTrace() != trace) { - continue; - } - if (!me.isInProgramRange(address)) { - continue; - } - if (!me.isInTraceLifespan(snap)) { - continue; - } - inbound.keySet().removeAll(toClean); - mappingsAffected(toClean); - return me.mapProgramAddressToTraceLocation(address); - } - inbound.keySet().removeAll(toClean); - mappingsAffected(toClean); - return null; - } - - protected void collectOpenMappedViews(AddressRange rng, - Map> result) { - Set toClean = new HashSet<>(); - for (Entry inPreceeding : inbound - .headMapByValue(rng.getMaxAddress(), true) - .entrySet()) { - MappingEntry me = inPreceeding.getKey(); - if (me.mapping.isDeleted()) { - Msg.warn(this, "Encountered deleted mapping: " + me.mapping); - toClean.add(me); - continue; - } - Address start = inPreceeding.getValue(); - if (start == null) { - continue; - } - if (!me.isInProgramRange(rng)) { - continue; - } - - AddressRange srcRange = me.staticRange.intersect(rng); - AddressRange dstRange = me.mapProgramRangeToTrace(rng); - result.computeIfAbsent(me.getTraceSpan(), p -> new TreeSet<>()) - .add(new MappedAddressRange(srcRange, dstRange)); - } - inbound.keySet().removeAll(toClean); - mappingsAffected(toClean); - } - - public Map> getOpenMappedViews( - AddressSetView set) { - Map> result = new HashMap<>(); - for (AddressRange rng : set) { - collectOpenMappedViews(rng, result); - } - return Collections.unmodifiableMap(result); - } - } - - private final Map trackedTraceInfo = new HashMap<>(); - private final Map trackedProgramInfo = new HashMap<>(); + final Map traceInfoByTrace = new HashMap<>(); + final Map programInfoByProgram = new HashMap<>(); + final Map programInfoByUrl = new HashMap<>(); @AutoServiceConsumed private DebuggerTraceManagerService traceManager; @@ -638,14 +116,11 @@ public class DebuggerStaticMappingServicePlugin extends Plugin @SuppressWarnings("unused") private final AutoService.Wiring autoWiring; - private final Object lock = new Object(); + final Object lock = new Object(); - private final AsyncDebouncer changeDebouncer = - new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100); + final ExecutorService executor = Executors.newSingleThreadExecutor(); private final ListenerSet changeListeners = new ListenerSet<>(DebuggerStaticMappingChangeListener.class, true); - private Set affectedTraces = new HashSet<>(); - private Set affectedPrograms = new HashSet<>(); private final ProgramModuleIndexer programModuleIndexer; private final ModuleMapProposalGenerator moduleMapProposalGenerator; @@ -655,60 +130,15 @@ public class DebuggerStaticMappingServicePlugin extends Plugin this.autoWiring = AutoService.wireServicesProvidedAndConsumed(this); this.programModuleIndexer = new ProgramModuleIndexer(tool); this.moduleMapProposalGenerator = new ModuleMapProposalGenerator(programModuleIndexer); - - changeDebouncer.addListener(this::fireChangeListeners); - tool.getProject().getProjectData().addDomainFolderChangeListener(this); } @Override protected void dispose() { tool.getProject().getProjectData().removeDomainFolderChangeListener(this); + executor.close(); super.dispose(); } - private void fireChangeListeners(Void v) { - Set traces; - Set programs; - synchronized (affectedTraces) { - traces = Collections.unmodifiableSet(affectedTraces); - programs = Collections.unmodifiableSet(affectedPrograms); - affectedTraces = new HashSet<>(); - affectedPrograms = new HashSet<>(); - } - changeListeners.invoke().mappingsChanged(traces, programs); - } - - private void traceAffected(Trace trace) { - synchronized (affectedTraces) { - affectedTraces.add(trace); - changeDebouncer.contact(null); - } - } - - private void programAffected(Program program) { - synchronized (affectedTraces) { - affectedPrograms.add(program); - changeDebouncer.contact(null); - } - } - - private void programsAffected(Collection programs) { - synchronized (affectedTraces) { - affectedPrograms.addAll(programs); - changeDebouncer.contact(null); - } - } - - private void mappingsAffected(Collection entries) { - Set traces = entries.stream().map(e -> e.getTrace()).collect(Collectors.toSet()); - Set programs = entries.stream().map(e -> e.program).collect(Collectors.toSet()); - synchronized (affectedTraces) { - affectedTraces.addAll(traces); - affectedPrograms.addAll(programs); - changeDebouncer.contact(null); - } - } - @Override public void addChangeListener(DebuggerStaticMappingChangeListener l) { changeListeners.add(l); @@ -719,73 +149,134 @@ public class DebuggerStaticMappingServicePlugin extends Plugin changeListeners.remove(l); } + void checkAndClearProgram(ChangeCollector cc, MappingEntry me) { + InfoPerProgram info = programInfoByUrl.get(me.getStaticProgramUrl()); + if (info == null) { + return; + } + info.clearProgram(cc, me); + } + + void checkAndFillProgram(ChangeCollector cc, MappingEntry me) { + InfoPerProgram info = programInfoByUrl.get(me.getStaticProgramUrl()); + if (info == null) { + return; + } + info.fillProgram(cc, me); + } + @Override public CompletableFuture changesSettled() { - return changeDebouncer.stable(); + return CompletableFuture.runAsync(() -> { + }, executor); + } + + void programsChanged() { + try (ChangeCollector cc = new ChangeCollector(this)) { + // Invoke change callbacks without the lock! (try must surround sync) + synchronized (lock) { + programsChanged(cc); + } + } + } + + void programsChanged(ChangeCollector cc) { + Set curProgs = Stream.of(programManager.getAllOpenPrograms()) + .filter(p -> !p.isClosed()) // Double-check + .collect(Collectors.toSet()); + Set removed = programInfoByProgram.values() + .stream() + .filter(i -> !curProgs.contains(i.program) || !i.urlMatches()) + .collect(Collectors.toSet()); + processRemovedProgramInfos(cc, removed); + Set added = ChangeCollector.subtract(curProgs, programInfoByProgram.keySet()); + processAddedPrograms(cc, added); + } + + void processRemovedProgramInfos(ChangeCollector cc, Set removed) { + for (InfoPerProgram info : removed) { + processRemovedProgramInfo(cc, info); + } + } + + void processRemovedProgramInfo(ChangeCollector cc, InfoPerProgram info) { + programInfoByProgram.remove(info.program); + programInfoByUrl.remove(info.url); + info.clearEntries(cc); + } + + void processAddedPrograms(ChangeCollector cc, Set added) { + for (Program program : added) { + processAddedProgram(cc, program); + } + } + + void processAddedProgram(ChangeCollector cc, Program program) { + InfoPerProgram info = new InfoPerProgram(this, program); + programInfoByProgram.put(program, info); + programInfoByUrl.put(info.url, info); + info.fillEntries(cc); + } + + private void tracesChanged() { + try (ChangeCollector cc = new ChangeCollector(this)) { + // Invoke change callbacks without the lock! (try must surround sync) + synchronized (lock) { + tracesChanged(cc); + } + } + } + + void tracesChanged(ChangeCollector cc) { + Set curTraces = traceManager.getOpenTraces() + .stream() + .filter(t -> !t.isClosed()) // Double-check + .collect(Collectors.toSet()); + Set oldTraces = traceInfoByTrace.keySet(); + + Set removed = ChangeCollector.subtract(oldTraces, curTraces); + Set added = ChangeCollector.subtract(curTraces, oldTraces); + + processRemovedTraces(cc, removed); + processAddedTraces(cc, added); + } + + void processRemovedTraces(ChangeCollector cc, Set removed) { + for (Trace trace : removed) { + processRemovedTrace(cc, trace); + } + } + + void processRemovedTrace(ChangeCollector cc, Trace trace) { + InfoPerTrace info = traceInfoByTrace.remove(trace); + info.removeEntries(cc); + } + + void processAddedTraces(ChangeCollector cc, Set added) { + for (Trace trace : added) { + processAddedTrace(cc, trace); + } + } + + void processAddedTrace(ChangeCollector cc, Trace trace) { + InfoPerTrace info = new InfoPerTrace(this, trace); + traceInfoByTrace.put(trace, info); + info.resyncEntries(cc); } @Override public void processEvent(PluginEvent event) { if (event instanceof ProgramOpenedPluginEvent) { - ProgramOpenedPluginEvent openedEvt = (ProgramOpenedPluginEvent) event; - programOpened(openedEvt.getProgram()); + CompletableFuture.runAsync(this::programsChanged, executor); } else if (event instanceof ProgramClosedPluginEvent) { - ProgramClosedPluginEvent closedEvt = (ProgramClosedPluginEvent) event; - programClosed(closedEvt.getProgram()); + CompletableFuture.runAsync(this::programsChanged, executor); } else if (event instanceof TraceOpenedPluginEvent) { - TraceOpenedPluginEvent openedEvt = (TraceOpenedPluginEvent) event; - traceOpened(openedEvt.getTrace()); + CompletableFuture.runAsync(this::tracesChanged, executor); } else if (event instanceof TraceClosedPluginEvent) { - TraceClosedPluginEvent closedEvt = (TraceClosedPluginEvent) event; - traceClosed(closedEvt.getTrace()); - } - } - - private void programOpened(Program program) { - synchronized (lock) { - if (program instanceof TraceProgramView) { - return; // TODO: Allow this? - } - URL url = ProgramURLUtils.getUrlFromProgram(program); - if (url == null) { - // Not in a project. Nothing could refer to it anyway.... - // TODO: If the program is saved into a project, it could be.... - return; - } - InfoPerProgram newInfo = new InfoPerProgram(program); - InfoPerProgram mustBeNull = trackedProgramInfo.put(url, newInfo); - assert mustBeNull == null; - - for (InfoPerTrace info : trackedTraceInfo.values()) { - if (info.programOpened(program, newInfo)) { - programAffected(program); - traceAffected(info.trace); - } - } - } - } - - private void programClosed(Program program) { - synchronized (lock) { - if (program instanceof TraceProgramView) { - return; - } - // NB. The URL may have changed, so can't use that as key - for (Iterator it = trackedProgramInfo.values().iterator(); it - .hasNext();) { - InfoPerProgram info = it.next(); - if (info.program == program) { - it.remove(); - } - } - for (InfoPerTrace info : trackedTraceInfo.values()) { - if (info.programClosed(program)) { - traceAffected(info.trace); - } - } + CompletableFuture.runAsync(this::tracesChanged, executor); } } @@ -794,69 +285,15 @@ public class DebuggerStaticMappingServicePlugin extends Plugin // This get called when a domain object is saved into the active project // We essentially need to update the URL, which requires examining every entry // TODO: Could probably cut out a bit of the kruft, but this should do - if (object instanceof Program) { - Program program = (Program) object; + if (object instanceof Program program) { synchronized (lock) { - programClosed(program); - int i = ArrayUtils.indexOf(programManager.getAllOpenPrograms(), program); - if (i >= 0) { - programOpened(program); + if (programInfoByProgram.containsKey(program)) { + CompletableFuture.runAsync(this::programsChanged, executor); } } } } - private Set collectAffectedByTrace(Trace trace) { - Set set = new HashSet<>(); - for (InfoPerProgram info : trackedProgramInfo.values()) { - if (info.isMappedInTrace(trace)) { - set.add(info.program); - } - } - return set; - } - - private void doAffectedByTraceOpened(Trace trace) { - Set set = collectAffectedByTrace(trace); - if (!set.isEmpty()) { - traceAffected(trace); - programsAffected(set); - } - } - - private void traceOpened(Trace trace) { - synchronized (lock) { - if (trace.isClosed()) { - Msg.warn(this, "Got traceOpened for a close trace"); - return; - } - InfoPerTrace newInfo = new InfoPerTrace(trace); - InfoPerTrace mustBeNull = trackedTraceInfo.put(trace, newInfo); - assert mustBeNull == null; - doAffectedByTraceOpened(trace); - } - } - - private void doAffectedByTraceClosed(Trace trace) { - for (InfoPerProgram info : trackedProgramInfo.values()) { - if (info.traceClosed(trace)) { - programAffected(info.program); - } - } - } - - private void traceClosed(Trace trace) { - synchronized (lock) { - InfoPerTrace traceInfo = trackedTraceInfo.remove(trace); - if (traceInfo == null) { - Msg.warn(this, "Got traceClosed without/before traceOpened"); - return; - } - traceInfo.dispose(); - doAffectedByTraceClosed(trace); - } - } - @Override public void addMapping(TraceLocation from, ProgramLocation to, long length, boolean truncateExisting) throws TraceConflictedMappingException { @@ -960,7 +397,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } protected InfoPerTrace requireTrackedInfo(Trace trace) { - InfoPerTrace info = trackedTraceInfo.get(trace); + InfoPerTrace info = traceInfoByTrace.get(trace); if (info == null) { return noTraceInfo(); } @@ -968,11 +405,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } protected InfoPerProgram requireTrackedInfo(Program program) { - URL url = ProgramURLUtils.getUrlFromProgram(program); - if (url == null) { - return noProject(); - } - InfoPerProgram info = trackedProgramInfo.get(url); + InfoPerProgram info = programInfoByProgram.get(program); if (info == null) { return noProgramInfo(); } @@ -997,7 +430,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin if (info == null) { return null; } - return info.getOpenMappedLocations(loc.getAddress(), loc.getLifespan()); + return info.getOpenMappedProgramLocation(loc.getAddress(), loc.getLifespan()); } } @@ -1090,7 +523,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin if (info == null) { return null; } - urls = info.getMappedProgramURLsInView(set, Lifespan.at(snap)); + urls = info.getMappedProgramUrlsInView(set, Lifespan.at(snap)); } Set result = new HashSet<>(); for (URL url : urls) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerProgram.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerProgram.java new file mode 100644 index 0000000000..330f2d9cf5 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerProgram.java @@ -0,0 +1,180 @@ +/* ### + * 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.service.modules; + +import java.net.URL; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin.ChangeCollector; +import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; +import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.framework.model.*; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Program; +import ghidra.trace.model.*; +import ghidra.util.Msg; + +class InfoPerProgram implements DomainObjectListener { + + static class NavMultiMap { + private final TreeMap> map = new TreeMap<>(); + + public boolean put(K k, V v) { + return map.computeIfAbsent(k, __ -> new HashSet<>()).add(v); + } + + public boolean remove(K k, V v) { + Set set = map.get(k); + if (set == null) { + return false; + } + if (!set.remove(v)) { + return false; + } + if (set.isEmpty()) { + map.remove(k); + } + return true; + } + } + + private final DebuggerStaticMappingServicePlugin plugin; + final Program program; + final NavMultiMap inboundByStaticAddress = new NavMultiMap<>(); + + final URL url; + + InfoPerProgram(DebuggerStaticMappingServicePlugin plugin, Program program) { + this.plugin = plugin; + this.program = program; + this.url = ProgramURLUtils.getUrlFromProgram(program); + + program.addListener(this); + } + + @Override + public void domainObjectChanged(DomainObjectChangedEvent ev) { + if (ev.contains(DomainObjectEvent.FILE_CHANGED) || ev.contains(DomainObjectEvent.RENAMED)) { + if (!urlMatches()) { + CompletableFuture.runAsync(plugin::programsChanged, plugin.executor); + } + } + } + + boolean urlMatches() { + return Objects.equals(url, ProgramURLUtils.getUrlFromProgram(program)); + } + + void clearProgram(ChangeCollector cc, MappingEntry me) { + assert me.program == program; + inboundByStaticAddress.remove(me.getStaticAddress(), me); + me.clearProgram(cc, program); + } + + void fillProgram(ChangeCollector cc, MappingEntry me) { + assert me.getStaticProgramUrl().equals(ProgramURLUtils.getUrlFromProgram(program)); + me.fillProgram(cc, program); + inboundByStaticAddress.put(me.getStaticAddress(), me); + } + + void clearEntries(ChangeCollector cc) { + if (url == null) { + return; + } + for (InfoPerTrace info : plugin.traceInfoByTrace.values()) { + info.clearEntriesForProgram(cc, this); + } + } + + void fillEntries(ChangeCollector cc) { + if (url == null) { + return; + } + for (InfoPerTrace info : plugin.traceInfoByTrace.values()) { + info.fillEntriesForProgram(cc, this); + } + } + + Set getOpenMappedTraceLocations(Address address) { + Set result = new HashSet<>(); + for (Set set : inboundByStaticAddress.map.headMap(address, true).values()) { + for (MappingEntry me : set) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + if (!me.isInProgramRange(address)) { + continue; + } + result.add(me.mapProgramAddressToTraceLocation(address)); + } + } + return result; + } + + TraceLocation getOpenMappedTraceLocation(Trace trace, Address address, long snap) { + // TODO: Map by trace? + for (Set set : inboundByStaticAddress.map.headMap(address, true).values()) { + for (MappingEntry me : set) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + if (me.getTrace() != trace) { + continue; + } + if (!me.isInProgramRange(address)) { + continue; + } + if (!me.isInTraceLifespan(snap)) { + continue; + } + return me.mapProgramAddressToTraceLocation(address); + } + } + return null; + } + + private void collectOpenMappedViews(Map> result, + AddressRange rng) { + for (Set set : inboundByStaticAddress.map.headMap(rng.getMaxAddress(), true) + .values()) { + for (MappingEntry me : set) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + // NB. No lifespan to consider + if (!me.isInProgramRange(rng)) { + continue; + } + AddressRange srcRange = me.getStaticRange().intersect(rng); + AddressRange dstRange = me.mapProgramRangeToTrace(rng); + result.computeIfAbsent(me.getTraceSpan(), p -> new TreeSet<>()) + .add(new MappedAddressRange(srcRange, dstRange)); + } + } + } + + Map> getOpenMappedViews(AddressSetView set) { + Map> result = new HashMap<>(); + for (AddressRange rng : set) { + collectOpenMappedViews(result, rng); + } + return Collections.unmodifiableMap(result); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerTrace.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerTrace.java new file mode 100644 index 0000000000..7f3378d2e9 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/InfoPerTrace.java @@ -0,0 +1,254 @@ +/* ### + * 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.service.modules; + +import java.net.URL; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.HashSetValuedHashMap; + +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin.ChangeCollector; +import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.framework.model.DomainObjectChangedEvent; +import ghidra.framework.model.DomainObjectEvent; +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.TraceStaticMapping; +import ghidra.trace.util.TraceEvents; +import ghidra.util.Msg; + +class InfoPerTrace extends TraceDomainObjectListener { + private final DebuggerStaticMappingServicePlugin plugin; + final Trace trace; + + final Map outboundByEntry = new HashMap<>(); + final NavigableMap outboundByRange = + new TreeMap<>(Comparator.comparing(TraceAddressSnapRange::getX1)); + final MultiValuedMap outboundByStaticUrl = new HashSetValuedHashMap<>(); + + private volatile boolean needsResync = false; + + InfoPerTrace(DebuggerStaticMappingServicePlugin plugin, Trace trace) { + this.plugin = plugin; + this.trace = trace; + + listenForUntyped(DomainObjectEvent.RESTORED, e -> objectRestored()); + listenFor(TraceEvents.MAPPING_ADDED, this::staticMappingAdded); + listenFor(TraceEvents.MAPPING_DELETED, this::staticMappingDeleted); + trace.addListener(this); + } + + @Override + public void domainObjectChanged(DomainObjectChangedEvent ev) { + super.domainObjectChanged(ev); // Dispatch individual records + // Now do the actual processing + if (needsResync) { + needsResync = false; + CompletableFuture.runAsync(this::resyncEntries, plugin.executor); + } + } + + private void objectRestored() { + this.needsResync = true; + } + + private void staticMappingAdded(TraceStaticMapping mapping) { + this.needsResync = true; + } + + private void staticMappingDeleted(TraceStaticMapping mapping) { + this.needsResync = true; + } + + public void dispose() { + trace.removeListener(this); + } + + private void resyncEntries() { + try (ChangeCollector cc = new ChangeCollector(plugin)) { + // Invoke change callbacks without the lock! (try must surround sync) + synchronized (plugin.lock) { + resyncEntries(cc); + } + } + } + + void resyncEntries(ChangeCollector cc) { + Set oldEntries = outboundByEntry.keySet(); + Set curEntries = trace.getStaticMappingManager() + .getAllEntries() + .stream() + .filter(e -> !e.isDeleted()) // Double-check + .collect(Collectors.toSet()); + + Set removed = ChangeCollector.subtract(oldEntries, curEntries); + Set added = ChangeCollector.subtract(curEntries, oldEntries); + + processRemovedEntries(cc, removed); + processAddedEntries(cc, added); + } + + void removeEntries(ChangeCollector cc) { + processRemovedEntries(cc, Set.copyOf(outboundByEntry.keySet())); + } + + private void processRemovedEntries(ChangeCollector cc, Set removed) { + for (TraceStaticMapping entry : removed) { + processRemovedEntry(cc, entry); + } + } + + private void processRemovedEntry(ChangeCollector cc, TraceStaticMapping entry) { + MappingEntry me = outboundByEntry.remove(entry); + if (me == null) { + return; + } + outboundByRange.remove(me.getTraceAddressSnapRange()); + outboundByStaticUrl.removeMapping(me.getStaticProgramUrl(), me); + plugin.checkAndClearProgram(cc, me); + } + + private void processAddedEntries(ChangeCollector cc, Set added) { + for (TraceStaticMapping entry : added) { + processAddedEntry(cc, entry); + } + } + + private void processAddedEntry(ChangeCollector cc, TraceStaticMapping entry) { + MappingEntry me = new MappingEntry(entry); + outboundByEntry.put(entry, me); + outboundByRange.put(me.getTraceAddressSnapRange(), me); + outboundByStaticUrl.put(me.getStaticProgramUrl(), me); + plugin.checkAndFillProgram(cc, me); + } + + void clearEntriesForProgram(ChangeCollector cc, InfoPerProgram progInfo) { + for (MappingEntry me : outboundByStaticUrl.get(progInfo.url)) { + progInfo.clearProgram(cc, me); + } + } + + void fillEntriesForProgram(ChangeCollector cc, InfoPerProgram progInfo) { + for (MappingEntry me : outboundByStaticUrl.get(progInfo.url)) { + progInfo.fillProgram(cc, me); + } + } + + Set getOpenMappedProgramsAtSnap(long snap) { + Set result = new HashSet<>(); + for (Entry out : outboundByRange.entrySet()) { + MappingEntry me = out.getValue(); + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + if (!me.isStaticProgramOpen()) { + continue; + } + if (!out.getKey().getLifespan().contains(snap)) { + continue; + } + result.add(me.program); + } + return result; + } + + ProgramLocation getOpenMappedProgramLocation(Address address, Lifespan span) { + TraceAddressSnapRange tasr = new ImmutableTraceAddressSnapRange(address, span); + // max is tasr (single address) + for (MappingEntry me : outboundByRange.headMap(tasr, true).values()) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + if (!tasr.intersects(me.getTraceAddressSnapRange())) { + continue; + } + if (me.isStaticProgramOpen()) { + return me.mapTraceAddressToProgramLocation(address); + } + } + return null; + } + + private void collectOpenMappedViews(Map> result, + AddressRange rng, Lifespan span) { + TraceAddressSnapRange tasr = new ImmutableTraceAddressSnapRange(rng, span); + TraceAddressSnapRange max = new ImmutableTraceAddressSnapRange(rng.getMaxAddress(), span); + for (MappingEntry me : outboundByRange.headMap(max, true).values()) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + if (me.program == null) { + continue; + } + if (!tasr.intersects(me.getTraceAddressSnapRange())) { + continue; + } + AddressRange srcRng = me.getTraceRange().intersect(rng); + AddressRange dstRng = me.mapTraceRangeToProgram(rng); + result.computeIfAbsent(me.program, p -> new TreeSet<>()) + .add(new MappedAddressRange(srcRng, dstRng)); + } + } + + Map> getOpenMappedViews(AddressSetView set, + Lifespan span) { + /** + * NB. Cannot use the OverlappingObjectIterator here. Because of the snap dimension, objects + * may not be disjoint in the address dimension. + */ + Map> result = new HashMap<>(); + for (AddressRange rng : set) { + collectOpenMappedViews(result, rng, span); + } + return Collections.unmodifiableMap(result); + } + + private void collectMappedProgramUrlsInView(Set result, AddressRange rng, Lifespan span) { + TraceAddressSnapRange tasr = new ImmutableTraceAddressSnapRange(rng, span); + TraceAddressSnapRange max = new ImmutableTraceAddressSnapRange(rng.getMaxAddress(), span); + for (MappingEntry me : outboundByRange.headMap(max, true).values()) { + if (me.mapping.isDeleted()) { + Msg.warn(this, "Encountered deleted mapping: " + me.mapping); + continue; + } + if (!tasr.intersects(me.getTraceAddressSnapRange())) { + continue; + } + result.add(me.getStaticProgramUrl()); + } + } + + Set getMappedProgramUrlsInView(AddressSetView set, Lifespan span) { + /** + * NB. Cannot use the OverlappingObjectIterator here. Because of the snap dimension, objects + * may not be disjoint in the address dimension. + */ + Set result = new HashSet<>(); + for (AddressRange rng : set) { + collectMappedProgramUrlsInView(result, rng, span); + } + return Collections.unmodifiableSet(result); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/MappingEntry.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/MappingEntry.java new file mode 100644 index 0000000000..d551c0b7cb --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/MappingEntry.java @@ -0,0 +1,202 @@ +/* ### + * 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.service.modules; + +import java.net.URL; +import java.util.Objects; + +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin.ChangeCollector; +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.TraceStaticMapping; +import ghidra.util.Msg; + +class MappingEntry { + final TraceStaticMapping mapping; + final TraceAddressSnapRange tasr; + + Program program; + private AddressRange staticRange; + + public MappingEntry(TraceStaticMapping mapping) { + this.mapping = mapping; + // Yes, mapping range and lifespan are immutable + this.tasr = new ImmutableTraceAddressSnapRange(mapping.getTraceAddressRange(), + mapping.getLifespan()); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof MappingEntry that)) { + return false; + } + // Yes, use identity, since it should be the same trace db records + if (this.mapping != that.mapping) { + return false; + } + if (this.program != that.program) { + return false; + } + if (!Objects.equals(this.staticRange, that.staticRange)) { + return false; + } + return true; + } + + public Trace getTrace() { + return mapping.getTrace(); + } + + Address addrOrMin(Program program, String addr) { + AddressFactory factory = program.getAddressFactory(); + Address result = factory.getAddress(addr); + if (result == null) { + Msg.warn(this, "Mapping entry has invalid static address: " + addr); + result = factory.getDefaultAddressSpace().getMinAddress(); + } + return result; + } + + Address addrOrMax(Address start, long length) { + Address result = start.addWrapSpace(length); + if (result.compareTo(start) < 0) { + Msg.warn(this, "Mapping entry caused overflow in static address space"); + return start.getAddressSpace().getMaxAddress(); + } + return result; + } + + void clearProgram(ChangeCollector cc, Program program) { + this.program = null; + this.staticRange = null; + cc.traceAffected(getTrace()); + cc.programAffected(program); + } + + void fillProgram(ChangeCollector cc, Program program) { + this.program = program; + Address minAddr = addrOrMin(program, mapping.getStaticAddress()); + Address maxAddr = addrOrMax(minAddr, mapping.getLength() - 1); + this.staticRange = new AddressRangeImpl(minAddr, maxAddr); + cc.traceAffected(getTrace()); + cc.programAffected(program); + } + + public AddressRange getTraceRange() { + return mapping.getTraceAddressRange(); + } + + public Address getTraceAddress() { + return mapping.getMinTraceAddress(); + } + + public AddressRange getStaticRange() { + return staticRange; + } + + public Address getStaticAddress() { + if (staticRange == null) { + return null; + } + return staticRange.getMinAddress(); + } + + public TraceSpan getTraceSpan() { + return new DefaultTraceSpan(mapping.getTrace(), mapping.getLifespan()); + } + + public TraceAddressSnapRange getTraceAddressSnapRange() { + return tasr; + } + + public boolean isInTraceRange(Address address, Long snap) { + return mapping.getTraceAddressRange().contains(address) && + (snap == null || mapping.getLifespan().contains(snap)); + } + + public boolean isInTraceRange(AddressRange rng, Long snap) { + return mapping.getTraceAddressRange().intersects(rng) && + (snap == null || mapping.getLifespan().contains(snap)); + } + + public boolean isInTraceLifespan(long snap) { + return mapping.getLifespan().contains(snap); + } + + public boolean isInProgramRange(Address address) { + if (staticRange == null) { + return false; + } + return staticRange.contains(address); + } + + public boolean isInProgramRange(AddressRange rng) { + if (staticRange == null) { + return false; + } + return staticRange.intersects(rng); + } + + protected Address mapTraceAddressToProgram(Address address) { + assert isInTraceRange(address, null); + long offset = address.subtract(mapping.getMinTraceAddress()); + return staticRange.getMinAddress().addWrapSpace(offset); + } + + public ProgramLocation mapTraceAddressToProgramLocation(Address address) { + if (program == null) { + throw new IllegalStateException("Static program is not opened"); + } + return new ProgramLocation(program, mapTraceAddressToProgram(address)); + } + + public AddressRange mapTraceRangeToProgram(AddressRange rng) { + assert isInTraceRange(rng, null); + AddressRange part = rng.intersect(mapping.getTraceAddressRange()); + Address min = mapTraceAddressToProgram(part.getMinAddress()); + Address max = mapTraceAddressToProgram(part.getMaxAddress()); + return new AddressRangeImpl(min, max); + } + + protected Address mapProgramAddressToTrace(Address address) { + assert isInProgramRange(address); + long offset = address.subtract(staticRange.getMinAddress()); + return mapping.getMinTraceAddress().addWrapSpace(offset); + } + + protected TraceLocation mapProgramAddressToTraceLocation(Address address) { + return new DefaultTraceLocation(mapping.getTrace(), null, mapping.getLifespan(), + mapProgramAddressToTrace(address)); + } + + public AddressRange mapProgramRangeToTrace(AddressRange rng) { + assert (rng.intersects(staticRange)); + AddressRange part = rng.intersect(staticRange); + Address min = mapProgramAddressToTrace(part.getMinAddress()); + Address max = mapProgramAddressToTrace(part.getMaxAddress()); + return new AddressRangeImpl(min, max); + } + + public boolean isStaticProgramOpen() { + return program != null; + } + + public URL getStaticProgramUrl() { + return mapping.getStaticProgramURL(); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index 95c2ebefcc..821cd14e34 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -4,9 +4,9 @@ * 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. @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.core.debug.service.tracemgr; -import static ghidra.framework.main.DataTreeDialogType.*; +import static ghidra.framework.main.DataTreeDialogType.OPEN; import java.io.IOException; import java.lang.invoke.MethodHandles; @@ -656,7 +656,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin @Override public synchronized Collection getOpenTraces() { - return Set.copyOf(tracesView); + synchronized (listenersByTrace) { + return Set.copyOf(tracesView); + } } @Override @@ -998,8 +1000,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin protected void doCloseTraces(Collection traces, Collection targets) { for (Trace t : traces) { if (t.getConsumerList().contains(this)) { - firePluginEvent(new TraceClosedPluginEvent(getName(), t)); doTraceClosed(t); + firePluginEvent(new TraceClosedPluginEvent(getName(), t)); } } TargetActionTask.executeTask(tool, new DisconnectTask(tool, targets)); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java index e1641374c8..c4ca0f3b7a 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerTest.java @@ -4,9 +4,9 @@ * 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. @@ -614,8 +614,7 @@ public abstract class AbstractGhidraHeadedDebuggerTest @BeforeClass public static void beforeClass() { - - // Note: we may decided to move this up to a framework-level base test class + // Note: we decided to move this up to a framework-level base test class TestDataStructureErrorHandlerInstaller.installConcurrentExceptionErrorHandler(); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java index 8bad648977..15f288504c 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServiceTest.java @@ -4,9 +4,9 @@ * 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. @@ -95,6 +95,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg try (ToyDBTraceBuilder r = new ToyDBTraceBuilder(saved)) { assertNotSame(tb.trace, r.trace); traceManager.openTrace(r.trace); + waitForDomainObject(r.trace); return r.trace; } } @@ -240,10 +241,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceMissWayBefore() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceMissWayBefore() throws Throwable { addMapping(); copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00000bad))); @@ -251,10 +253,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceMissJustBefore() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceMissJustBefore() throws Throwable { addMapping(); copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x001fffff))); @@ -262,10 +265,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceHitAtStart() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceHitAtStart() throws Throwable { addMapping(); Trace copy = copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200000))); @@ -281,10 +285,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceHitInMiddle() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceHitInMiddle() throws Throwable { addMapping(); Trace copy = copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200833))); @@ -298,10 +303,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceHitAtEnd() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceHitAtEnd() throws Throwable { addMapping(); Trace copy = copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200fff))); @@ -315,10 +321,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceMissJustAfter() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceMissJustAfter() throws Throwable { addMapping(); copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00201000))); @@ -326,10 +333,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCopyAndTranslateStaticToTraceMissWayAfter() throws Exception { + public void testAddMappingThenCopyAndTranslateStaticToTraceMissWayAfter() throws Throwable { addMapping(); copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Set locations = mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0xbadbadbadL))); @@ -377,10 +385,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenTranslateStaticViewToTraceEmpty() throws Exception { + public void testAddMappingThenTranslateStaticViewToTraceEmpty() throws Throwable { addMapping(); copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); Map> views = mappingService.getOpenMappedViews(program, new AddressSet()); @@ -388,10 +397,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenTranslateStaticViewToTraceReplete() throws Exception { + public void testAddMappingThenTranslateStaticViewToTraceReplete() throws Throwable { addMapping(); Trace copy = copyTrace(); add2ndMapping(); + waitOn(mappingService.changesSettled()); AddressSet set = new AddressSet(); // Before @@ -438,10 +448,12 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCloseStaticAndOpenMappedMissWayBefore() throws Exception { + public void testAddMappingThenCloseStaticAndOpenMappedMissWayBefore() throws Throwable { // NOTE: Does not make sense to test program->trace, as program has no mapping records addMapping(); programManager.closeProgram(program, true); + waitForSwing(); + waitOn(mappingService.changesSettled()); AddressSet addrSet = new AddressSet(dynSpace.getAddress(0x00000bad)); Set programSet = @@ -451,9 +463,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCloseStaticAndOpenMappedHitInMiddle() throws Exception { + public void testAddMappingThenCloseStaticAndOpenMappedHitInMiddle() throws Throwable { addMapping(); programManager.closeProgram(program, true); + waitForSwing(); + waitOn(mappingService.changesSettled()); AddressSet addrSet = new AddressSet(dynSpace.getAddress(0x00100c0d)); Set programSet = @@ -465,9 +479,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testAddMappingThenCloseStaticAndOpenMappedMissWayAfter() throws Exception { + public void testAddMappingThenCloseStaticAndOpenMappedMissWayAfter() throws Throwable { addMapping(); programManager.closeProgram(program, true); + waitForSwing(); + waitOn(mappingService.changesSettled()); AddressSet addrSet = new AddressSet(dynSpace.getAddress(0xbadbadbadL)); Set programSet = @@ -478,14 +494,17 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg @Test public void testAddMappingThenCloseStaticAndTranslateTraceToStaticHitInMiddle() - throws Exception { + throws Throwable { addMapping(); + waitOn(mappingService.changesSettled()); // pre-check assertNotNull(mappingService.getOpenMappedLocation( new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), dynSpace.getAddress(0x00100c0d)))); programManager.closeProgram(program, true); + waitForSwing(); + waitOn(mappingService.changesSettled()); assertNull(mappingService.getOpenMappedLocation( new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), @@ -494,14 +513,16 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg @Test public void testAddMappingThenCloseTraceAndTranslateStaticToTraceHitInMiddle() - throws Exception { + throws Throwable { addMapping(); + waitOn(mappingService.changesSettled()); // pre-check assertEquals(1, mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).size()); traceManager.closeTrace(tb.trace); waitForSwing(); + waitOn(mappingService.changesSettled()); assertTrue(mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).isEmpty()); @@ -509,9 +530,11 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg @Test public void testAddMappingThenCloseAndReopenStaticAndTranslateTraceToStaticHitInMiddle() - throws Exception { + throws Throwable { addMapping(); programManager.closeProgram(program, true); + waitForSwing(); + waitOn(mappingService.changesSettled()); // pre-check assertNull(mappingService.getOpenMappedLocation( new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), @@ -519,6 +542,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg programManager.openProgram(program); waitForProgram(program); + waitOn(mappingService.changesSettled()); assertNotNull(mappingService.getOpenMappedLocation( new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), @@ -527,17 +551,19 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg @Test public void testAddMappingThenCloseAndReopenTraceAndTranslateStaticToTraceHitInMiddle() - throws Exception { + throws Throwable { addMapping(); traceManager.closeTrace(tb.trace); waitForSwing(); + waitOn(mappingService.changesSettled()); // pre-check assertTrue(mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).isEmpty()); traceManager.openTrace(tb.trace); - waitForSwing(); + waitForDomainObject(tb.trace); + waitOn(mappingService.changesSettled()); assertEquals(1, mappingService.getOpenMappedLocations( new ProgramLocation(program, stSpace.getAddress(0x00200c0d))).size()); @@ -545,7 +571,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg @Test public void testAddMappingThenRemoveButAbortThenTranslateTraceToStaticHitInMiddle() - throws Exception { + throws Throwable { addMapping(); TraceLocation goodLoc = new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), @@ -553,19 +579,23 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg try (Transaction tx = tb.startTransaction()) { mappingManager.findContaining(dynSpace.getAddress(0x00100000), 0).delete(); waitForDomainObject(tb.trace); + waitOn(mappingService.changesSettled()); // pre-check assertNull(mappingService.getOpenMappedLocation(goodLoc)); tx.abort(); } waitForDomainObject(tb.trace); + waitOn(mappingService.changesSettled()); assertNotNull(mappingService.getOpenMappedLocation(goodLoc)); } @Test public void testAddCorrelationRemoveButUndoThenRequestMappingDynamicToStaticWithin() - throws Exception { + throws Throwable { addMapping(); + waitOn(mappingService.changesSettled()); + TraceLocation goodLoc = new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), dynSpace.getAddress(0x00100c0d)); @@ -577,11 +607,13 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg mappingManager.findContaining(dynSpace.getAddress(0x00100000), 0).delete(); } waitForDomainObject(tb.trace); + waitOn(mappingService.changesSettled()); // pre-check assertNull(mappingService.getOpenMappedLocation(goodLoc)); undo(tb.trace, true); + waitOn(mappingService.changesSettled()); assertNotNull(mappingService.getOpenMappedLocation(goodLoc)); } @@ -619,7 +651,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg } @Test - public void testMapFullSpace() throws Exception { + public void testMapFullSpace() throws Throwable { try (Transaction tx = tb.startTransaction()) { TraceLocation traceLoc = new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), tb.addr(0)); @@ -627,7 +659,10 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg // NB. 0 indicates 1 << 64 mappingService.addMapping(traceLoc, progLoc, 0, true); } - waitForPass(() -> assertMapsTwoWay(0L, 0L)); + waitForSwing(); + waitOn(mappingService.changesSettled()); + + assertMapsTwoWay(0L, 0L); assertMapsTwoWay(-1L, -1L); assertMapsTwoWay(Long.MAX_VALUE, Long.MAX_VALUE); assertMapsTwoWay(Long.MIN_VALUE, Long.MIN_VALUE); 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 e7bf8120ff..e5a79b4e9c 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 @@ -4,9 +4,9 @@ * 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. @@ -33,7 +33,7 @@ import ghidra.util.database.*; import ghidra.util.database.annot.*; /** - * The implementation of a stack mapping, directly via a database object + * The implementation of a static mapping, directly via a database object * *

* Version history: diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/OverlappingObjectIterator.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/OverlappingObjectIterator.java index b242035826..0529d29475 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/OverlappingObjectIterator.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/OverlappingObjectIterator.java @@ -4,9 +4,9 @@ * 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. @@ -26,7 +26,30 @@ import ghidra.program.model.listing.CodeUnit; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.util.AbstractPeekableIterator; +/** + * An iterator of overlapping objects return from two given iterators. + * + *

+ * The given iterators, named left and right, must return objects each having a range attribute. + * Each iterator must return objects having disjoint ranges, i.e., no two objects from the + * same iterator may intersect. Each iterator must also return the objects sorted by min + * address. This iterator will then discover every case where an object from the left iterator + * overlaps an object from the right iterator, and return a pair for each such instance, in order of + * min address. + * + *

+ * WARNING: To avoid heap pollution, this iterator re-uses the same {@link Pair} on each call + * to {@link #next()}. If you need to save an overlapping pair, you must copy it. + * + * @param the type of objects returned by the left iterator + * @param the type of objects returned by the right iterator + */ public class OverlappingObjectIterator extends AbstractPeekableIterator> { + /** + * A means of obtaining the range attribute from each object + * + * @param the type of objects returned by an iterator + */ public interface Ranger { Address getMinAddress(T t); diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointMarkerPluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointMarkerPluginTest.java index 19677ae117..fc76747e59 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointMarkerPluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/AbstractDebuggerBreakpointMarkerPluginTest.java @@ -4,9 +4,9 @@ * 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. @@ -483,7 +483,7 @@ public abstract class AbstractDebuggerBreakpointMarkerPluginTest addMappedBreakpointOpenAndWait(); // wasteful, but whatever for (LogicalBreakpoint lb : List.copyOf(breakpointService.getAllBreakpoints())) { TraceBreakpoint brk = Unique.assertOne(lb.getTraceBreakpoints(tb.trace)); - lb.delete(); + waitOn(lb.delete()); handleDeleteBreakpointInvocation(brk); } waitForPass(() -> assertEquals(0, breakpointService.getAllBreakpoints().size())); @@ -515,7 +515,7 @@ public abstract class AbstractDebuggerBreakpointMarkerPluginTest addMappedBreakpointOpenAndWait(); // wasteful, but whatever for (LogicalBreakpoint lb : List.copyOf(breakpointService.getAllBreakpoints())) { TraceBreakpoint brk = Unique.assertOne(lb.getTraceBreakpoints(tb.trace)); - lb.delete(); + waitOn(lb.delete()); handleDeleteBreakpointInvocation(brk); } waitForPass(() -> assertEquals(0, breakpointService.getAllBreakpoints().size())); diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/AbstractDebuggerLogicalBreakpointServiceTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/AbstractDebuggerLogicalBreakpointServiceTest.java index 9a54aec1ed..c980c80a7c 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/AbstractDebuggerLogicalBreakpointServiceTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/AbstractDebuggerLogicalBreakpointServiceTest.java @@ -4,9 +4,9 @@ * 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. @@ -46,8 +46,6 @@ import ghidra.trace.model.memory.TraceMemoryFlag; import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.util.Msg; import ghidra.util.SystemUtilities; -import utility.function.ExceptionalCallback; -import utility.function.ExceptionalSupplier; public abstract class AbstractDebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDebuggerIntegrationTest { @@ -102,22 +100,6 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest } } - protected U expectMappingChange(ExceptionalSupplier supplier) - throws Throwable { - mappingChangeListener.ar.set(false, null); - U result = supplier.get(); - waitOn(mappingChangeListener.ar.waitValue(true)); - return result; - } - - protected void expectMappingChange(ExceptionalCallback runnable) - throws Throwable { - expectMappingChange(() -> { - runnable.call(); - return null; - }); - } - protected DebuggerStaticMappingService mappingService; protected DebuggerLogicalBreakpointService breakpointService; @@ -134,8 +116,6 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest protected NoDuplicatesBreakpointsChangeListener changeListener = new NoDuplicatesBreakpointsChangeListener(); - protected ForTimingMappingChangeListener mappingChangeListener = - new ForTimingMappingChangeListener(); protected abstract void createTarget1() throws Throwable; @@ -169,10 +149,12 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest protected abstract TraceBreakpoint findLoc(Set locs, int index); - protected abstract void handleToggleBreakpointInvocation(TraceBreakpoint expectedBreakpoint, + protected abstract void handleToggleBreakpointInvocation(T target, + TraceBreakpoint expectedBreakpoint, boolean expectedEnabled) throws Throwable; - protected abstract void handleDeleteBreakpointInvocation(TraceBreakpoint expectedBreakpoint) + protected abstract void handleDeleteBreakpointInvocation(T target, + TraceBreakpoint expectedBreakpoint) throws Throwable; @Before @@ -182,7 +164,6 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest mappingService = tool.getService(DebuggerStaticMappingService.class); breakpointService.addChangeListener(changeListener); - mappingService.addChangeListener(mappingChangeListener); } @After @@ -269,6 +250,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest .createInitializedBlock(".text", addr(p, 0x00400000), 0x1000, (byte) 0, monitor, false); } + waitForDomainObject(p); } protected void addProgramBreakpoints(Program p) throws Throwable { @@ -280,6 +262,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest .setBookmark(addr(p, 0x00400321), LogicalBreakpoint.DISABLED_BOOKMARK_TYPE, "SW_EXECUTE;1", ""); } + waitForDomainObject(p); } protected void refetchProgramBreakpoints(Program p) throws Throwable { @@ -297,6 +280,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest p.getBookmarkManager().removeBookmark(enBm); p.getBookmarkManager().removeBookmark(disBm); } + waitForDomainObject(p); } protected void assertLogicalBreakpointForLoneAccessBreakpoint(Trace trace) { @@ -546,8 +530,8 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @@ -564,9 +548,9 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); addProgramBreakpoints(program); - waitForSwing(); assertLogicalBreakpointsForMappedBookmarks(trace); } @@ -584,12 +568,12 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); addProgramBreakpoints(program); - waitForSwing(); changeListener.assertAgreesWithService(); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointsForMappedBookmarks(trace); } @@ -606,12 +590,12 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); + addTextMapping(target1, text, program); addTargetSoftwareBreakpoint(target1, text); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); - }); + assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); } @Test @@ -627,14 +611,14 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); addTargetSoftwareBreakpoint(target1, text); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); - }); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); changeListener.assertAgreesWithService(); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); } @@ -660,8 +644,9 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest // NOTE: Extraneous mappings-changed events can cause timing issues here. // TODO: Better testing for static mapping listener events? MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointsForMappedBookmarks(trace); } @@ -680,12 +665,15 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest MR text = addTargetTextRegion(target1); addTextMapping(target1, text, program); addProgramBreakpoints(program); - waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); - expectMappingChange(() -> programManager.openProgram(program)); - waitForSwing(); + programManager.openProgram(program); + waitForDomainObject(program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointsForMappedBookmarks(trace); } @@ -704,13 +692,17 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest MR text = addTargetTextRegion(target1); addTextMapping(target1, text, program); addTargetSoftwareBreakpoint(target1, text); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1)); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); - expectMappingChange(() -> programManager.openProgram(program)); - waitForSwing(); + programManager.openProgram(program); + waitForDomainObject(program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> assertLogicalBreakpointForMappedSoftwareBreakpoint(trace)); + assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); } @Test @@ -722,12 +714,16 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest createProgramFromTrace(trace); intoProject(program); programManager.openProgram(program); - waitForSwing(); + waitForDomainObject(program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); programManager.closeProgram(program, true); waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @@ -741,13 +737,17 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest createProgramFromTrace(trace); intoProject(program); programManager.openProgram(program); - waitForSwing(); + waitForDomainObject(program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); traceManager.closeTrace(trace); terminateTarget(target1); waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @@ -766,13 +766,14 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); addProgramBreakpoints(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointsForMappedBookmarks(trace); removeProgramBreakpoints(program); - waitForSwing(); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @@ -791,13 +792,15 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); addProgramBreakpoints(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointsForMappedBookmarks(trace); - expectMappingChange(() -> removeTextMapping(target1, program)); - waitForSwing(); + removeTextMapping(target1, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointsForUnmappedBookmarks(); assertTrue(breakpointService.getBreakpoints(trace).isEmpty()); @@ -818,19 +821,19 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest programManager.openProgram(program); addProgramTextBlock(program); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); assertServiceAgreesWithOpenProgramsAndTraces(); removeTargetSoftwareBreakpoint(target1); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - // NB. The bookmark remains - LogicalBreakpoint one = Unique.assertOne(breakpointService.getAllBreakpoints()); - assertTrue(one.getTraceBreakpoints().isEmpty()); - }); + // NB. The bookmark remains + LogicalBreakpoint one = Unique.assertOne(breakpointService.getAllBreakpoints()); + assertTrue(one.getTraceBreakpoints().isEmpty()); } @Test @@ -848,14 +851,16 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest programManager.openProgram(program); addProgramTextBlock(program); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); assertServiceAgreesWithOpenProgramsAndTraces(); - expectMappingChange(() -> removeTextMapping(target1, program)); - waitForSwing(); + removeTextMapping(target1, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // NB. Bookmark remains assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 2); @@ -881,23 +886,19 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addTextMapping(target1, text1, program); addTextMapping(target3, text3, program); - waitForSwing(); - waitForPass(() -> { - assertEquals(2, - mappingService - .getOpenMappedLocations( - new ProgramLocation(program, addr(program, 0x00400123))) - .size()); - }); - waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + + assertEquals(2, mappingService + .getOpenMappedLocations(new ProgramLocation(program, addr(program, 0x00400123))) + .size()); addProgramBreakpoints(program); addTargetSoftwareBreakpoint(target1, text1); addTargetSoftwareBreakpoint(target3, text3); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - }); + assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); } @Test @@ -920,26 +921,24 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addTextMapping(target1, text1, program); addTextMapping(target3, text3, program); - waitForSwing(); - waitForPass(() -> { - assertEquals(2, - mappingService - .getOpenMappedLocations( - new ProgramLocation(program, addr(program, 0x00400123))) - .size()); - }); - waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + + assertEquals(2, mappingService + .getOpenMappedLocations(new ProgramLocation(program, addr(program, 0x00400123))) + .size()); addProgramBreakpoints(program); addTargetSoftwareBreakpoint(target1, text1); addTargetSoftwareBreakpoint(target3, text3); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - }); + assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - expectMappingChange(() -> programManager.closeProgram(program, true)); + programManager.closeProgram(program, true); waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); assertLogicalBreakpointForLoneSoftwareBreakpoint(trace1, 0x55550123, 2); assertLogicalBreakpointForLoneSoftwareBreakpoint(trace3, 0x55551123, 2); @@ -965,38 +964,34 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addTextMapping(target1, text1, program); addTextMapping(target3, text3, program); - waitForSwing(); - waitForPass(() -> { - assertEquals(2, - mappingService - .getOpenMappedLocations( - new ProgramLocation(program, addr(program, 0x00400123))) - .size()); - }); - waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + + assertEquals(2, mappingService + .getOpenMappedLocations(new ProgramLocation(program, addr(program, 0x00400123))) + .size()); addProgramBreakpoints(program); addTargetSoftwareBreakpoint(target1, text1); addTargetSoftwareBreakpoint(target3, text3); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - }); + assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - expectMappingChange(() -> programManager.closeProgram(program, true)); + programManager.closeProgram(program, true); waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace1, 0x55550123, 2); - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace3, 0x55551123, 2); - }); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace1, 0x55550123, 2); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace3, 0x55551123, 2); - expectMappingChange(() -> programManager.openProgram(program)); - waitForSwing(); + programManager.openProgram(program); + waitForDomainObject(program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - }); + assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); } @Test @@ -1019,36 +1014,34 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addTextMapping(target1, text1, program); addTextMapping(target3, text3, program); - waitForSwing(); - waitForPass(() -> { - assertEquals(2, - mappingService - .getOpenMappedLocations( - new ProgramLocation(program, addr(program, 0x00400123))) - .size()); - }); - waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + + assertEquals(2, mappingService + .getOpenMappedLocations(new ProgramLocation(program, addr(program, 0x00400123))) + .size()); addProgramBreakpoints(program); addTargetSoftwareBreakpoint(target1, text1); addTargetSoftwareBreakpoint(target3, text3); - waitForSwing(); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); - }); + assertLogicalBreakpointForMappedBookmarkAnd2TraceBreakpoints(trace1, trace3); waitForLock(getTrace(target3)); - expectMappingChange(() -> { - // If I don't close the trace here, the test will fail. - terminateTarget(target3); - // NB. Auto-close on stop is the default - //traceManager.closeTrace(trace3); - }); + /** + * NB. Relying on terminate to auto-close is causing some timing issue I don't yet + * understand. So, I close it in the test code. + */ + terminateTarget(target3); + traceManager.closeTrace(trace3); + waitForPass(() -> !traceManager.getOpenTraces().contains(trace3)); waitForSwing(); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // NB. Auto-close is possibly delayed because of auto-save - waitForPass(() -> assertLogicalBreakpointForMappedBookmarkAnd1TraceBreakpoint(trace1)); + assertLogicalBreakpointForMappedBookmarkAnd1TraceBreakpoint(trace1); } @Test // Mappings are not write-behind cached @@ -1064,10 +1057,10 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); addTargetSoftwareBreakpoint(target1, text); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); - }); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); /** * NB. The target could still be mid transaction. If we open this transaction too soon, then * the breakpoint gets aborted as well. @@ -1077,19 +1070,21 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest changeListener.assertAgreesWithService(); try (Transaction tx = trace.openTransaction("Will abort")) { - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // Sanity assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); - expectMappingChange(() -> tx.abort()); + tx.abort(); } + waitForDomainObject(trace); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - // NB. The bookmark is left over, so total increases - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 2); - }); + // NB. The bookmark is left over, so total increases + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 2); } // @Test // Not gonna with write-behind cache @@ -1108,15 +1103,18 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest try (Transaction tx = trace.openTransaction("Will abort")) { addTargetSoftwareBreakpoint(target1, text); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // Sanity assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); - expectMappingChange(() -> tx.abort()); + tx.abort(); } - waitForDomainObject(trace); // Duplicative, but for form's sake.... + waitForDomainObject(trace); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // Left over, because it was bookmarked automatically in program // Still, there should be no trace breakpoint in it @@ -1136,18 +1134,19 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); try (Transaction tx = program.openTransaction("Will abort")) { addProgramBreakpoints(program); - waitForDomainObject(program); // Sanity assertLogicalBreakpointsForMappedBookmarks(trace); tx.abort(); } waitForDomainObject(program); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @@ -1167,18 +1166,16 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest try (Transaction tx = trace.openTransaction("Will undo")) { addTargetSoftwareBreakpoint(target1, text); - expectMappingChange(() -> addTextMapping(target1, text, program)); + addTextMapping(target1, text, program); } - waitForDomainObject(trace); - + waitForDomainObject(program); waitOn(mappingService.changesSettled()); waitOn(breakpointService.changesSettled()); // Sanity assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); - expectMappingChange(() -> undo(trace)); - + undo(trace); waitOn(mappingService.changesSettled()); waitOn(breakpointService.changesSettled()); @@ -1186,10 +1183,12 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest LogicalBreakpoint one = Unique.assertOne(breakpointService.getAllBreakpoints()); assertTrue(one.getTraceBreakpoints().isEmpty()); - expectMappingChange(() -> redo(trace)); + redo(trace); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // Mapping, breakpoint may be processed in whatever order - waitForPass(() -> assertLogicalBreakpointForMappedSoftwareBreakpoint(trace)); + assertLogicalBreakpointForMappedSoftwareBreakpoint(trace); } @Test @@ -1204,22 +1203,27 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest addProgramTextBlock(program); MR text = addTargetTextRegion(target1); - expectMappingChange(() -> addTextMapping(target1, text, program)); - waitForSwing(); + addTextMapping(target1, text, program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); try (Transaction tx = program.openTransaction("Will undo")) { addProgramBreakpoints(program); } waitForDomainObject(program); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); // Sanity assertLogicalBreakpointsForMappedBookmarks(trace); undo(program); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); redo(program); + waitOn(breakpointService.changesSettled()); refetchProgramBreakpoints(program); assertLogicalBreakpointsForMappedBookmarks(trace); @@ -1234,18 +1238,20 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest MR text = addTargetTextRegion(target1); addTargetSoftwareBreakpoint(target1, text); - waitForPass(() -> { - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); - }); + waitOn(breakpointService.changesSettled()); + + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints()); CompletableFuture disable = lb.disable(); - handleToggleBreakpointInvocation(Unique.assertOne(lb.getTraceBreakpoints(trace)), false); + handleToggleBreakpointInvocation(target1, Unique.assertOne(lb.getTraceBreakpoints(trace)), + false); waitOn(disable); - waitForPass(() -> { - assertEquals(State.INCONSISTENT_DISABLED, lb.computeState()); - }); + waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); + + assertEquals(State.INCONSISTENT_DISABLED, lb.computeState()); // Simulate a step, which should also cause snap advance in target long oldSnap = getSnap(target1); @@ -1256,11 +1262,13 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest }); CompletableFuture enable = lb.enable(); - handleToggleBreakpointInvocation(Unique.assertOne(lb.getTraceBreakpoints(trace)), true); + handleToggleBreakpointInvocation(target1, Unique.assertOne(lb.getTraceBreakpoints(trace)), + true); waitOn(enable); - waitForPass(() -> { - assertEquals(State.INCONSISTENT_ENABLED, lb.computeState()); - }); + waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); + + assertEquals(State.INCONSISTENT_ENABLED, lb.computeState()); } @Test @@ -1270,22 +1278,20 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest traceManager.openTrace(trace); MR text = addTargetTextRegion(target1); - addTargetSoftwareBreakpoint(target1, text); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); - }); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints()); CompletableFuture delete = lb.delete(); - handleDeleteBreakpointInvocation(Unique.assertOne(lb.getTraceBreakpoints(trace))); + handleDeleteBreakpointInvocation(target1, Unique.assertOne(lb.getTraceBreakpoints(trace))); waitOn(delete); + waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertTrue(breakpointService.getAllBreakpoints().isEmpty()); - }); + assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } @Test @@ -1295,11 +1301,10 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest traceManager.openTrace(trace); MR text = addTargetTextRegion(target1); - addTargetSoftwareBreakpoint(target1, text); - waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1)); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints()); @@ -1307,12 +1312,12 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest simulateTargetStep(target1); CompletableFuture delete = lb.delete(); - handleDeleteBreakpointInvocation(Unique.assertOne(lb.getTraceBreakpoints(trace))); + handleDeleteBreakpointInvocation(target1, Unique.assertOne(lb.getTraceBreakpoints(trace))); waitOn(delete); + waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertEquals(0, breakpointService.getAllBreakpoints().size()); - }); + assertEquals(0, breakpointService.getAllBreakpoints().size()); } @Test @@ -1324,14 +1329,14 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest MR text = addTargetTextRegion(target1); addTargetSoftwareBreakpoint(target1, text); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); - }); + assertLogicalBreakpointForLoneSoftwareBreakpoint(trace, 1); // NOTE: Still recording in the background traceManager.closeTraceNoConfirm(trace); waitForSwing(); + waitOn(breakpointService.changesSettled()); assertEquals(0, breakpointService.getAllBreakpoints().size()); } @@ -1350,39 +1355,36 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest MR text = addTargetTextRegion(target1); addTextMapping(target1, text, program); - waitForSwing(); addProgramBreakpoints(program); addTargetSoftwareBreakpoint(target1, text); addTargetSoftwareBreakpoint(target1, text); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertEquals(2, breakpointService.getAllBreakpoints().size()); + assertEquals(2, breakpointService.getAllBreakpoints().size()); - LogicalBreakpoint lb = Unique.assertOne( - breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); - assertEquals(program, lb.getProgram()); - assertEquals(Set.of(trace), lb.getMappedTraces()); + LogicalBreakpoint lb = Unique.assertOne( + breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); + assertEquals(program, lb.getProgram()); + assertEquals(Set.of(trace), lb.getMappedTraces()); - assertEquals(2, lb.getTraceBreakpoints().size()); - }); + assertEquals(2, lb.getTraceBreakpoints().size()); - LogicalBreakpoint lb = Unique - .assertOne(breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); Set locs = lb.getTraceBreakpoints(); TraceBreakpoint bpt0 = findLoc(locs, 0); TraceBreakpoint bpt1 = findLoc(locs, 1); CompletableFuture disable = breakpointService.disableLocs(Set.of(bpt0)); - handleToggleBreakpointInvocation(bpt0, false); + handleToggleBreakpointInvocation(target1, bpt0, false); waitOn(disable); + waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertEquals(State.INCONSISTENT_ENABLED, lb.computeState()); - assertEquals(State.INCONSISTENT_MIXED, lb.computeStateForTrace(trace)); - assertEquals(State.INCONSISTENT_DISABLED, lb.computeStateForLocation(bpt0)); - assertEquals(State.ENABLED, lb.computeStateForLocation(bpt1)); - }); + assertEquals(State.INCONSISTENT_ENABLED, lb.computeState()); + assertEquals(State.INCONSISTENT_MIXED, lb.computeStateForTrace(trace)); + assertEquals(State.INCONSISTENT_DISABLED, lb.computeStateForLocation(bpt0)); + assertEquals(State.ENABLED, lb.computeStateForLocation(bpt1)); } @Test @@ -1399,29 +1401,26 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest MR text = addTargetTextRegion(target1); addTextMapping(target1, text, program); - waitForSwing(); addProgramBreakpoints(program); addTargetSoftwareBreakpoint(target1, text); addTargetAccessBreakpoint(target1, text); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); - waitForPass(() -> { - assertEquals(3, breakpointService.getAllBreakpoints().size()); + assertEquals(3, breakpointService.getAllBreakpoints().size()); - Set lbs = - breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)); - assertEquals(2, lbs.size()); - lbs.stream() - .filter(l -> l.getKinds().contains(TraceBreakpointKind.SW_EXECUTE)) - .findAny() - .orElseThrow(); - lbs.stream() - .filter(l -> l.getKinds().contains(TraceBreakpointKind.READ)) - .findAny() - .orElseThrow(); - }); Set lbs = breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)); + assertEquals(2, lbs.size()); + lbs.stream() + .filter(l -> l.getKinds().contains(TraceBreakpointKind.SW_EXECUTE)) + .findAny() + .orElseThrow(); + lbs.stream() + .filter(l -> l.getKinds().contains(TraceBreakpointKind.READ)) + .findAny() + .orElseThrow(); LogicalBreakpoint lbEx = lbs.stream() .filter(l -> l.getKinds().contains(TraceBreakpointKind.SW_EXECUTE)) .findAny() @@ -1431,12 +1430,14 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest .findAny() .orElseThrow(); CompletableFuture disable = lbEx.disable(); - handleToggleBreakpointInvocation(Unique.assertOne(lbEx.getTraceBreakpoints(trace)), false); + handleToggleBreakpointInvocation(target1, Unique.assertOne(lbEx.getTraceBreakpoints(trace)), + false); waitOn(disable); + waitForDomainObject(trace); + waitOn(breakpointService.changesSettled()); // TODO: This is more a test for the marker plugin, no? - waitForPass( - () -> assertEquals(State.MIXED, lbEx.computeState().sameAdddress(lbRw.computeState()))); + assertEquals(State.MIXED, lbEx.computeState().sameAdddress(lbRw.computeState())); } protected void addTextMappingDead(Program p, ToyDBTraceBuilder tb) throws Throwable { @@ -1451,6 +1452,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest textRegion.getMinAddress()), new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, false); } + waitForDomainObject(tb.trace); } protected void addEnabledProgramBreakpointWithSleigh(Program p) { @@ -1459,6 +1461,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest .setBookmark(addr(p, 0x00400123), LogicalBreakpoint.ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1", "{sleigh: 'r0=0xbeef;'}"); } + waitForDomainObject(p); } @Test @@ -1471,16 +1474,18 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest programManager.openProgram(program); addTextMappingDead(program, tb); - waitForSwing(); addEnabledProgramBreakpointWithSleigh(program); - LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne( - breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)))); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + LogicalBreakpoint lb = Unique.assertAtMostOne( + breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); assertEquals("r0=0xbeef;", lb.getEmuSleigh()); waitOn(lb.enable()); - waitForSwing(); + waitForDomainObject(program); + waitOn(breakpointService.changesSettled()); TraceBreakpoint bpt = Unique.assertOne( tb.trace.getBreakpointManager().getBreakpointsAt(0, tb.addr(0x55550123))); @@ -1497,21 +1502,22 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest programManager.openProgram(program); addEnabledProgramBreakpointWithSleigh(program); - LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne( - breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)))); + waitOn(breakpointService.changesSettled()); + LogicalBreakpoint lb = Unique.assertAtMostOne( + breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); assertEquals("r0=0xbeef;", lb.getEmuSleigh()); addTextMappingDead(program, tb); - lb = waitForPass(() -> { - LogicalBreakpoint newLb = Unique.assertOne( - breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); - assertTrue(newLb.getMappedTraces().contains(tb.trace)); - return newLb; - }); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + lb = Unique.assertOne( + breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); + assertTrue(lb.getMappedTraces().contains(tb.trace)); waitOn(lb.enable()); - waitForSwing(); + waitForDomainObject(program); + waitOn(breakpointService.changesSettled()); TraceBreakpoint bpt = Unique.assertOne( tb.trace.getBreakpointManager().getBreakpointsAt(0, tb.addr(0x55550123))); @@ -1541,21 +1547,25 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest false /* emuEnabled defaults to true */, ""); bpt.setEmuSleigh("r0=0xbeef;"); } - LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne( - breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x55550123)))); + waitForDomainObject(tb.trace); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + LogicalBreakpoint lb = Unique.assertAtMostOne( + breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x55550123))); assertEquals("r0=0xbeef;", lb.getEmuSleigh()); addTextMappingDead(program, tb); - lb = waitForPass(() -> { - LogicalBreakpoint newLb = Unique.assertOne( - breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); - assertTrue(newLb.getMappedTraces().contains(tb.trace)); - return newLb; - }); + waitOn(mappingService.changesSettled()); + waitOn(breakpointService.changesSettled()); + + lb = Unique + .assertOne(breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))); + assertTrue(lb.getMappedTraces().contains(tb.trace)); lb.enableForProgram(); - waitForSwing(); + waitForDomainObject(tb.trace); + waitOn(breakpointService.changesSettled()); assertEquals("{\"sleigh\":\"r0\\u003d0xbeef;\"}", lb.getProgramBookmark().getComment()); } @@ -1566,6 +1576,8 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest * *

* With the addition of the write-behind cache, this test is no longer sane. + * + * @throws Throwable who doesn't? */ // @Test public void testAbortAddBreakpointSetSleigh() throws Throwable { @@ -1595,6 +1607,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest tid.abort(); } waitForDomainObject(tb.trace); + waitOn(breakpointService.changesSettled()); assertTrue(breakpointService.getAllBreakpoints().isEmpty()); } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerObjectRecorderLogicalBreakpointServiceTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerObjectRecorderLogicalBreakpointServiceTest.java index 981859ef71..6684bb0235 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerObjectRecorderLogicalBreakpointServiceTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerObjectRecorderLogicalBreakpointServiceTest.java @@ -4,9 +4,9 @@ * 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. @@ -17,6 +17,8 @@ package ghidra.app.plugin.core.debug.service.breakpoint; import java.io.IOException; +import org.junit.Ignore; + import db.Transaction; import ghidra.dbg.target.schema.SchemaContext; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; @@ -24,6 +26,7 @@ import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.trace.model.Trace; import ghidra.trace.model.target.TraceObjectKeyPath; +@Ignore("Deprecated") public class DebuggerObjectRecorderLogicalBreakpointServiceTest extends DebuggerRecorderLogicalBreakpointServiceTest { diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRecorderLogicalBreakpointServiceTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRecorderLogicalBreakpointServiceTest.java index 6685d6efff..a3e2820e5a 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRecorderLogicalBreakpointServiceTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRecorderLogicalBreakpointServiceTest.java @@ -4,9 +4,9 @@ * 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. @@ -19,9 +19,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.util.Set; -import java.util.concurrent.TimeUnit; import org.junit.Before; +import org.junit.Ignore; import db.Transaction; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; @@ -39,6 +39,7 @@ import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.modules.TraceStaticMapping; +@Ignore("Deprecated") public class DebuggerRecorderLogicalBreakpointServiceTest extends AbstractDebuggerLogicalBreakpointServiceTest { @@ -130,6 +131,7 @@ public class DebuggerRecorderLogicalBreakpointServiceTest extends new ProgramLocation(p, addr(p, 0x00400000)), 0x1000, false); } + waitForDomainObject(t); } @Override @@ -140,6 +142,7 @@ public class DebuggerRecorderLogicalBreakpointServiceTest extends t.getStaticMappingManager().findContaining(addr(t, 0x55550000), r.getSnap()); mapping.delete(); } + waitForDomainObject(t); } @Override @@ -168,7 +171,7 @@ public class DebuggerRecorderLogicalBreakpointServiceTest extends @Override protected void removeTargetSoftwareBreakpoint(TraceRecorder r) throws Throwable { TargetBreakpointSpecContainer cont = getBreakpointContainer(r); - cont.fetchElements().thenAccept(elements -> { + waitOn(cont.fetchElements().thenCompose(elements -> { for (TargetObject obj : elements.values()) { if (!(obj instanceof TargetBreakpointSpec) || !(obj instanceof TargetDeletable)) { @@ -179,11 +182,12 @@ public class DebuggerRecorderLogicalBreakpointServiceTest extends continue; } TargetDeletable del = (TargetDeletable) obj; - del.delete(); - return; + return del.delete(); } fail("No deletable software breakpoint spec found"); - }).get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + throw new AssertionError(); + })); + waitRecorder(r); } @Override @@ -200,14 +204,16 @@ public class DebuggerRecorderLogicalBreakpointServiceTest extends } @Override - protected void handleToggleBreakpointInvocation(TraceBreakpoint expectedBreakpoint, - boolean expectedEnabled) throws Throwable { + protected void handleToggleBreakpointInvocation(TraceRecorder target, + TraceBreakpoint expectedBreakpoint, boolean expectedEnabled) throws Throwable { // Logic is in the Test model + waitRecorder(target); } @Override - protected void handleDeleteBreakpointInvocation(TraceBreakpoint expectedBreakpoint) - throws Throwable { + protected void handleDeleteBreakpointInvocation(TraceRecorder target, + TraceBreakpoint expectedBreakpoint) throws Throwable { // Logic is in the Test model + waitRecorder(target); } } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRmiLogicalBreakpointServiceTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRmiLogicalBreakpointServiceTest.java index 0ec8b8c5b6..56ec694d37 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRmiLogicalBreakpointServiceTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerRmiLogicalBreakpointServiceTest.java @@ -4,9 +4,9 @@ * 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. @@ -53,6 +53,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION); tb.createObjectsProcessAndThreads(); } + waitForDomainObject(tb.trace); target1 = rmiCx.publishTarget(tool, tb.trace); } @@ -64,6 +65,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends tb3.trace.getObjectManager().createRootObject(SCHEMA_SESSION); tb3.createObjectsProcessAndThreads(); } + waitForDomainObject(tb3.trace); target3 = rmiCx.publishTarget(tool, tb3.trace); } @@ -79,6 +81,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends try (Transaction tx = trace.openTransaction("Simulate step")) { snapshot = trace.getTimeManager().createSnapshot("Simulated step"); } + waitForDomainObject(trace); rmiCx.setLastSnapshot(trace, snapshot.getKey()); } @@ -96,12 +99,15 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends protected TraceObjectMemoryRegion addTargetTextRegion(TraceRmiTarget target, long offset) throws Throwable { Trace trace = target.getTrace(); + TraceObjectMemoryRegion result; try (Transaction tx = trace.openTransaction("Add .text")) { - return Objects.requireNonNull( + result = Objects.requireNonNull( addMemoryRegion(trace.getObjectManager(), Lifespan.nowOn(target.getSnap()), tb.range(offset, offset + 0x0fff), "bin:.text", "rx") .queryInterface(TraceObjectMemoryRegion.class)); } + waitForDomainObject(trace); + return result; } @Override @@ -113,12 +119,15 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends protected TraceObjectMemoryRegion addTargetDataRegion(TraceRmiTarget target) throws Throwable { Trace trace = target.getTrace(); long offset = 0x56550000; + TraceObjectMemoryRegion result; try (Transaction tx = trace.openTransaction("Add .data")) { - return Objects.requireNonNull( + result = Objects.requireNonNull( addMemoryRegion(trace.getObjectManager(), Lifespan.nowOn(target.getSnap()), tb.range(offset, offset + 0x0fff), "bin:.data", "rw") .queryInterface(TraceObjectMemoryRegion.class)); } + waitForDomainObject(trace); + return result; } @Override @@ -131,6 +140,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends new ProgramLocation(program, addr(program, 0x00400000)), 0x1000, false); } + waitForDomainObject(trace); } @Override @@ -141,6 +151,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends t.getStaticMappingManager().findContaining(addr(t, 0x55550000), target.getSnap()); mapping.delete(); } + waitForDomainObject(t); } @Override @@ -188,6 +199,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends spec.getObject().remove(nowOn); } } + waitForDomainObject(trace); } @Override @@ -204,8 +216,8 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends } @Override - protected void handleToggleBreakpointInvocation(TraceBreakpoint expectedBreakpoint, - boolean expectedEn) throws Throwable { + protected void handleToggleBreakpointInvocation(TraceRmiTarget target, + TraceBreakpoint expectedBreakpoint, boolean expectedEn) throws Throwable { if (!(expectedBreakpoint instanceof TraceObjectBreakpointLocation loc)) { throw new AssertionError("Unexpected trace breakpoint type: " + expectedBreakpoint); } @@ -213,6 +225,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends try (Transaction tx = tb.startTransaction()) { loc.setEnabled(Lifespan.nowOn(0), expectedEn); } + waitForDomainObject(tb.trace); rmiMethodToggleBreak.result(null); assertEquals(Map.ofEntries( Map.entry("breakpoint", loc.getSpecification().getObject()), @@ -220,8 +233,8 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends } @Override - protected void handleDeleteBreakpointInvocation(TraceBreakpoint expectedBreakpoint) - throws Throwable { + protected void handleDeleteBreakpointInvocation(TraceRmiTarget target, + TraceBreakpoint expectedBreakpoint) throws Throwable { if (!(expectedBreakpoint instanceof TraceObjectBreakpointLocation loc)) { throw new AssertionError("Unexpected trace breakpoint type: " + expectedBreakpoint); } @@ -229,6 +242,7 @@ public class DebuggerRmiLogicalBreakpointServiceTest extends try (Transaction tx = tb.startTransaction()) { loc.getObject().remove(Lifespan.nowOn(0)); } + waitForDomainObject(tb.trace); rmiMethodDeleteBreak.result(null); assertEquals(Map.ofEntries( Map.entry("breakpoint", loc.getSpecification().getObject())), args);