diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerStaticMappingService.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerStaticMappingService.java index a51084c001..a02b83d021 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerStaticMappingService.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerStaticMappingService.java @@ -23,7 +23,8 @@ import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry; import ghidra.debug.api.modules.RegionMapProposal.RegionMapEntry; import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry; import ghidra.framework.model.DomainFile; -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; @@ -50,151 +51,6 @@ import ghidra.util.task.TaskMonitor; */ public interface DebuggerStaticMappingService { - /** - * A pair for describing sets of mapped addresses - * - *

- * Note, the natural order is by the destination address. - */ - public class MappedAddressRange implements Comparable { - - private final AddressRange srcRange; - private final AddressRange dstRange; - private final int hashCode; - private final long shift; - - public MappedAddressRange(AddressRange srcRange, AddressRange dstRange) { - this.srcRange = srcRange; - this.dstRange = dstRange; - this.hashCode = Objects.hash(dstRange, srcRange); - this.shift = dstRange.getMinAddress().getOffset() - - srcRange.getMinAddress().getOffset(); - } - - @Override - public String toString() { - return ""; - } - - /** - * Get the shift from the source address range to this address range - * - *

- * The meaning depends on what returned this view. If this view is the "static" range, then - * this shift describes what was added to the offset of the "dynamic" address to get a - * particular address in the "static" range. - * - * @return the shift - */ - public long getShift() { - return shift; - } - - /** - * Map an address in the source range to the corresponding address in the destination range - * - * @param saddr the source address (not validated) - * @return the destination address - */ - public Address mapSourceToDestination(Address saddr) { - return dstRange.getAddressSpace().getAddress(saddr.getOffset() + shift); - } - - /** - * Map an address in the destination range to the corresponding address in the source range - * - * @param daddr the destination address (not validated) - * @return the source address - */ - public Address mapDestinationToSource(Address daddr) { - return srcRange.getAddressSpace().getAddress(daddr.getOffset() - shift); - } - - /** - * Map a sub-range of the source to the corresponding sub-range of the destination - * - * @param srng the source sub-range - * @return the destination sub-range - */ - public AddressRange mapSourceToDestination(AddressRange srng) { - try { - return new AddressRangeImpl(mapSourceToDestination(srng.getMinAddress()), - srng.getLength()); - } - catch (AddressOverflowException e) { - throw new IllegalArgumentException(e); - } - } - - /** - * Map a sub-range of the destination to the corresponding sub-range of the source - * - * @param drng the destination sub-range - * @return the source sub-range - */ - public AddressRange mapDestinationToSource(AddressRange drng) { - try { - return new AddressRangeImpl(mapDestinationToSource(drng.getMinAddress()), - drng.getLength()); - } - catch (AddressOverflowException e) { - throw new IllegalArgumentException(e); - } - } - - /** - * Get the source address range - * - * @return the address range - */ - public AddressRange getSourceAddressRange() { - return srcRange; - } - - /** - * Get the destination address range - * - * @return the address range - */ - public AddressRange getDestinationAddressRange() { - return dstRange; - } - - @Override - public int compareTo(MappedAddressRange that) { - int c; - c = this.dstRange.compareTo(that.dstRange); - if (c != 0) { - return c; - } - c = this.srcRange.compareTo(that.srcRange); - if (c != 0) { - return c; - } - return 0; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof MappedAddressRange)) { - return false; - } - MappedAddressRange that = (MappedAddressRange) obj; - if (!this.dstRange.equals(that.dstRange)) { - return false; - } - if (!this.srcRange.equals(that.srcRange)) { - return false; - } - return true; - } - - @Override - public int hashCode() { - return hashCode; - } - } - /** * Add a static mapping (relocation) from the given trace to the given program * diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/modules/MappedAddressRange.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/modules/MappedAddressRange.java new file mode 100644 index 0000000000..c57f732ee9 --- /dev/null +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/modules/MappedAddressRange.java @@ -0,0 +1,165 @@ +/* ### + * 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.debug.api.modules; + +import java.util.Objects; + +import ghidra.program.model.address.*; + +/** + * A pair for describing sets of mapped addresses + * + *

+ * Note, the natural order is by the destination address. + */ +public class MappedAddressRange implements Comparable { + + private final AddressRange srcRange; + private final AddressRange dstRange; + private final int hashCode; + private final long shift; + + public MappedAddressRange(AddressRange srcRange, AddressRange dstRange) { + this.srcRange = srcRange; + this.dstRange = dstRange; + this.hashCode = Objects.hash(dstRange, srcRange); + this.shift = dstRange.getMinAddress().getOffset() - + srcRange.getMinAddress().getOffset(); + } + + @Override + public String toString() { + return ""; + } + + /** + * Get the shift from the source address range to this address range + * + *

+ * The meaning depends on what returned this view. If this view is the "static" range, then + * this shift describes what was added to the offset of the "dynamic" address to get a + * particular address in the "static" range. + * + * @return the shift + */ + public long getShift() { + return shift; + } + + /** + * Map an address in the source range to the corresponding address in the destination range + * + * @param saddr the source address (not validated) + * @return the destination address + */ + public Address mapSourceToDestination(Address saddr) { + return dstRange.getAddressSpace().getAddress(saddr.getOffset() + shift); + } + + /** + * Map an address in the destination range to the corresponding address in the source range + * + * @param daddr the destination address (not validated) + * @return the source address + */ + public Address mapDestinationToSource(Address daddr) { + return srcRange.getAddressSpace().getAddress(daddr.getOffset() - shift); + } + + /** + * Map a sub-range of the source to the corresponding sub-range of the destination + * + * @param srng the source sub-range + * @return the destination sub-range + */ + public AddressRange mapSourceToDestination(AddressRange srng) { + try { + return new AddressRangeImpl(mapSourceToDestination(srng.getMinAddress()), + srng.getLength()); + } + catch (AddressOverflowException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Map a sub-range of the destination to the corresponding sub-range of the source + * + * @param drng the destination sub-range + * @return the source sub-range + */ + public AddressRange mapDestinationToSource(AddressRange drng) { + try { + return new AddressRangeImpl(mapDestinationToSource(drng.getMinAddress()), + drng.getLength()); + } + catch (AddressOverflowException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get the source address range + * + * @return the address range + */ + public AddressRange getSourceAddressRange() { + return srcRange; + } + + /** + * Get the destination address range + * + * @return the address range + */ + public AddressRange getDestinationAddressRange() { + return dstRange; + } + + @Override + public int compareTo(MappedAddressRange that) { + int c; + c = this.dstRange.compareTo(that.dstRange); + if (c != 0) { + return c; + } + c = this.srcRange.compareTo(that.srcRange); + if (c != 0) { + return c; + } + return 0; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof MappedAddressRange)) { + return false; + } + MappedAddressRange that = (MappedAddressRange) obj; + if (!this.dstRange.equals(that.dstRange)) { + return false; + } + if (!this.srcRange.equals(that.srcRange)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return hashCode; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java index 2787694d29..6c91edbecf 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java @@ -32,7 +32,7 @@ import docking.widgets.table.*; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.copying.DebuggerCopyPlan.Copier; import ghidra.app.services.*; -import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.debug.api.modules.MappedAddressRange; import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.PluginTool; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java index 9ce860c2a4..e2d1ade782 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java @@ -20,8 +20,8 @@ import java.util.concurrent.CompletableFuture; import ghidra.app.plugin.core.debug.utils.AbstractMappedMemoryBytesVisitor; import ghidra.app.services.DebuggerStaticMappingService; -import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; import ghidra.debug.api.emulation.PcodeDebuggerMemoryAccess; +import ghidra.debug.api.modules.MappedAddressRange; import ghidra.debug.api.target.Target; import ghidra.framework.plugintool.ServiceProvider; import ghidra.pcode.exec.PcodeExecutorStatePiece; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingContext.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingContext.java new file mode 100644 index 0000000000..3507c43778 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingContext.java @@ -0,0 +1,343 @@ +/* ### + * 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.Executor; +import java.util.stream.Collectors; + +import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils; +import ghidra.async.AsyncUtils; +import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener; +import ghidra.debug.api.modules.MappedAddressRange; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.*; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.util.Msg; +import ghidra.util.datastruct.ListenerSet; + +public class DebuggerStaticMappingContext { + + record ChangeCollector(DebuggerStaticMappingContext ctx, Set traces, + Set programs) implements AutoCloseable { + + static Set subtract(Set a, Set b) { + Set result = new HashSet<>(a); + result.removeAll(b); + return result; + } + + public ChangeCollector(DebuggerStaticMappingContext ctx) { + this(ctx, 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 void close() { + ctx.changeListeners.getProxy().mappingsChanged(traces, programs); + } + } + + final Map traceInfoByTrace = new HashMap<>(); + final Map programInfoByProgram = new HashMap<>(); + final Map programInfoByUrl = new HashMap<>(); + + final Object lock = new Object(); + + final Executor executor; + private final ListenerSet changeListeners = + new ListenerSet<>(DebuggerStaticMappingChangeListener.class, true); + + public DebuggerStaticMappingContext() { + this(AsyncUtils.DIRECT_EXECUTOR); + } + + public DebuggerStaticMappingContext(Executor executor) { + this.executor = executor; + } + + public void addChangeListener(DebuggerStaticMappingChangeListener l) { + changeListeners.add(l); + } + + public void removeChangeListener(DebuggerStaticMappingChangeListener l) { + changeListeners.remove(l); + } + + public ChangeCollector collectChanges() { + return new ChangeCollector(this); + } + + public void addProgram(ChangeCollector cc, Program program) { + synchronized (lock) { + processAddedProgram(cc, program); + } + } + + public void removeProgram(ChangeCollector cc, Program program) { + synchronized (lock) { + processRemovedProgramInfo(cc, requireTrackedInfo(program)); + } + } + + public void setPrograms(ChangeCollector cc, Set programs) { + synchronized (lock) { + Set removed = programInfoByProgram.values() + .stream() + .filter(i -> !programs.contains(i.program) || !i.urlMatches()) + .collect(Collectors.toSet()); + processRemovedProgramInfos(cc, removed); + Set added = ChangeCollector.subtract(programs, programInfoByProgram.keySet()); + processAddedPrograms(cc, added); + } + } + + public void addTrace(ChangeCollector cc, Trace trace) { + synchronized (lock) { + processAddedTrace(cc, trace); + } + } + + public void removeTrace(ChangeCollector cc, Trace trace) { + synchronized (lock) { + processRemovedTrace(cc, trace); + } + } + + public void setTraces(ChangeCollector cc, Set traces) { + synchronized (lock) { + Set oldTraces = traceInfoByTrace.keySet(); + + Set removed = ChangeCollector.subtract(oldTraces, traces); + Set added = ChangeCollector.subtract(traces, oldTraces); + + processRemovedTraces(cc, removed); + processAddedTraces(cc, added); + } + } + + protected T noTraceInfo() { + Msg.debug(this, "The given trace is not open in this tool " + + "(or the service hasn't received and processed the open-trace event, yet)"); + return null; + } + + protected T noProgramInfo() { + Msg.debug(this, "The given program is not open in this tool " + + "(or the service hasn't received and processed the open-program event, yet)"); + return null; + } + + protected T noProject() { + return DebuggerStaticMappingUtils.noProject(this); + } + + 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); + } + + 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); + } + + 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); + } + + protected InfoPerTrace requireTrackedInfo(Trace trace) { + InfoPerTrace info = traceInfoByTrace.get(trace); + if (info == null) { + return noTraceInfo(); + } + return info; + } + + protected InfoPerProgram requireTrackedInfo(Program program) { + InfoPerProgram info = programInfoByProgram.get(program); + if (info == null) { + return noProgramInfo(); + } + return info; + } + + public Set getOpenMappedProgramsAtSnap(Trace trace, long snap) { + synchronized (lock) { + InfoPerTrace info = requireTrackedInfo(trace); + if (info == null) { + return null; + } + return info.getOpenMappedProgramsAtSnap(snap); + } + } + + public ProgramLocation getOpenMappedLocation(TraceLocation loc) { + synchronized (lock) { + InfoPerTrace info = requireTrackedInfo(loc.getTrace()); + if (info == null) { + return null; + } + return info.getOpenMappedProgramLocation(loc.getAddress(), loc.getLifespan()); + } + } + + protected long getNonScratchSnap(TraceProgramView view) { + return view.getViewport().getTop(s -> s >= 0 ? s : null); + } + + public ProgramLocation getStaticLocationFromDynamic(ProgramLocation loc) { + synchronized (lock) { + loc = ProgramLocationUtils.fixLocation(loc, true); + TraceProgramView view = (TraceProgramView) loc.getProgram(); + Trace trace = view.getTrace(); + TraceLocation tloc = new DefaultTraceLocation(trace, null, + Lifespan.at(getNonScratchSnap(view)), loc.getByteAddress()); + ProgramLocation mapped = getOpenMappedLocation(tloc); + if (mapped == null) { + return null; + } + return ProgramLocationUtils.replaceAddress(loc, mapped.getProgram(), + mapped.getByteAddress()); + } + } + + public Set getOpenMappedLocations(ProgramLocation loc) { + synchronized (lock) { + InfoPerProgram info = requireTrackedInfo(loc.getProgram()); + if (info == null) { + return null; + } + return info.getOpenMappedTraceLocations(loc.getByteAddress()); + } + } + + public TraceLocation getOpenMappedLocation(Trace trace, ProgramLocation loc, long snap) { + synchronized (lock) { + InfoPerProgram info = requireTrackedInfo(loc.getProgram()); + if (info == null) { + return null; + } + return info.getOpenMappedTraceLocation(trace, loc.getByteAddress(), snap); + } + } + + public ProgramLocation getDynamicLocationFromStatic(TraceProgramView view, + ProgramLocation loc) { + synchronized (lock) { + TraceLocation tloc = + getOpenMappedLocation(view.getTrace(), loc, getNonScratchSnap(view)); + if (tloc == null) { + return null; + } + return ProgramLocationUtils.replaceAddress(loc, view, tloc.getAddress()); + } + } + + public Map> getOpenMappedViews(Trace trace, + AddressSetView set, long snap) { + synchronized (lock) { + InfoPerTrace info = requireTrackedInfo(trace); + if (info == null) { + return Map.of(); + } + return info.getOpenMappedViews(set, Lifespan.at(snap)); + } + } + + public Map> getOpenMappedViews(Program program, + AddressSetView set) { + synchronized (lock) { + InfoPerProgram info = requireTrackedInfo(program); + if (info == null) { + return Map.of(); + } + return info.getOpenMappedViews(set); + } + } + + public Set getMappedProgramUrlsInView(Trace trace, AddressSetView set, long snap) { + synchronized (lock) { + InfoPerTrace info = requireTrackedInfo(trace); + if (info == null) { + return null; + } + return info.getMappedProgramUrlsInView(set, Lifespan.at(snap)); + } + } +} 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 758fee32b0..54340411c8 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 @@ -29,8 +29,8 @@ import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingContext.ChangeCollector; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingProposals.*; -import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils; import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; import ghidra.app.services.*; import ghidra.debug.api.modules.*; @@ -51,7 +51,6 @@ import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.modules.*; import ghidra.trace.model.program.TraceProgramView; import ghidra.util.Msg; -import ghidra.util.datastruct.ListenerSet; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -76,39 +75,6 @@ import ghidra.util.task.TaskMonitor; public class DebuggerStaticMappingServicePlugin extends Plugin implements DebuggerStaticMappingService, DomainFolderChangeListener { - record ChangeCollector(DebuggerStaticMappingServicePlugin plugin, Set traces, - Set programs) implements AutoCloseable { - - static Set subtract(Set a, Set b) { - Set result = new HashSet<>(a); - result.removeAll(b); - return result; - } - - 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 void close() { - plugin.changeListeners.getProxy().mappingsChanged(traces, programs); - } - } - - final Map traceInfoByTrace = new HashMap<>(); - final Map programInfoByProgram = new HashMap<>(); - final Map programInfoByUrl = new HashMap<>(); - @AutoServiceConsumed private DebuggerTraceManagerService traceManager; @AutoServiceConsumed @@ -116,11 +82,8 @@ public class DebuggerStaticMappingServicePlugin extends Plugin @SuppressWarnings("unused") private final AutoService.Wiring autoWiring; - final Object lock = new Object(); - - final ExecutorService executor = Executors.newSingleThreadExecutor(); - private final ListenerSet changeListeners = - new ListenerSet<>(DebuggerStaticMappingChangeListener.class, true); + private final ExecutorService executor = Executors.newSingleThreadExecutor(); + private final DebuggerStaticMappingContext context; private final ProgramModuleIndexer programModuleIndexer; private final ModuleMapProposalGenerator moduleMapProposalGenerator; @@ -128,6 +91,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin public DebuggerStaticMappingServicePlugin(PluginTool tool) { super(tool); this.autoWiring = AutoService.wireServicesProvidedAndConsumed(this); + this.context = new DebuggerStaticMappingContext(executor); this.programModuleIndexer = new ProgramModuleIndexer(tool); this.moduleMapProposalGenerator = new ModuleMapProposalGenerator(programModuleIndexer); } @@ -141,28 +105,12 @@ public class DebuggerStaticMappingServicePlugin extends Plugin @Override public void addChangeListener(DebuggerStaticMappingChangeListener l) { - changeListeners.add(l); + context.addChangeListener(l); } @Override public void removeChangeListener(DebuggerStaticMappingChangeListener l) { - 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); + context.removeChangeListener(l); } @Override @@ -171,99 +119,6 @@ public class DebuggerStaticMappingServicePlugin extends Plugin }, 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) { @@ -284,16 +139,35 @@ public class DebuggerStaticMappingServicePlugin extends Plugin public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) { // 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 + // LATER: Could probably cut out a bit of the kruft, but this should do if (object instanceof Program program) { - synchronized (lock) { - if (programInfoByProgram.containsKey(program)) { + synchronized (context.lock) { + if (context.programInfoByProgram.containsKey(program)) { CompletableFuture.runAsync(this::programsChanged, executor); } } } } + void programsChanged() { + try (ChangeCollector cc = context.collectChanges()) { + Set curProgs = Stream.of(programManager.getAllOpenPrograms()) + .filter(p -> !p.isClosed()) // Double-check + .collect(Collectors.toSet()); + context.setPrograms(cc, curProgs); + } + } + + private void tracesChanged() { + try (ChangeCollector cc = context.collectChanges()) { + Set curTraces = traceManager.getOpenTraces() + .stream() + .filter(t -> !t.isClosed()) // Double-check + .collect(Collectors.toSet()); + context.setTraces(cc, curTraces); + } + } + @Override public void addMapping(TraceLocation from, ProgramLocation to, long length, boolean truncateExisting) throws TraceConflictedMappingException { @@ -380,151 +254,10 @@ public class DebuggerStaticMappingServicePlugin extends Plugin addMappings(entries, monitor, truncateExisting, "Add regions mappings"); } - protected T noTraceInfo() { - Msg.debug(this, "The given trace is not open in this tool " + - "(or the service hasn't received and processed the open-trace event, yet)"); - return null; - } - - protected T noProgramInfo() { - Msg.debug(this, "The given program is not open in this tool " + - "(or the service hasn't received and processed the open-program event, yet)"); - return null; - } - - protected T noProject() { - return DebuggerStaticMappingUtils.noProject(this); - } - - protected InfoPerTrace requireTrackedInfo(Trace trace) { - InfoPerTrace info = traceInfoByTrace.get(trace); - if (info == null) { - return noTraceInfo(); - } - return info; - } - - protected InfoPerProgram requireTrackedInfo(Program program) { - InfoPerProgram info = programInfoByProgram.get(program); - if (info == null) { - return noProgramInfo(); - } - return info; - } - - @Override - public Set getOpenMappedProgramsAtSnap(Trace trace, long snap) { - synchronized (lock) { - InfoPerTrace info = requireTrackedInfo(trace); - if (info == null) { - return null; - } - return info.getOpenMappedProgramsAtSnap(snap); - } - } - - @Override - public ProgramLocation getOpenMappedLocation(TraceLocation loc) { - synchronized (lock) { - InfoPerTrace info = requireTrackedInfo(loc.getTrace()); - if (info == null) { - return null; - } - return info.getOpenMappedProgramLocation(loc.getAddress(), loc.getLifespan()); - } - } - - protected long getNonScratchSnap(TraceProgramView view) { - return view.getViewport().getTop(s -> s >= 0 ? s : null); - } - - @Override - public ProgramLocation getStaticLocationFromDynamic(ProgramLocation loc) { - synchronized (lock) { - loc = ProgramLocationUtils.fixLocation(loc, true); - TraceProgramView view = (TraceProgramView) loc.getProgram(); - Trace trace = view.getTrace(); - TraceLocation tloc = new DefaultTraceLocation(trace, null, - Lifespan.at(getNonScratchSnap(view)), loc.getByteAddress()); - ProgramLocation mapped = getOpenMappedLocation(tloc); - if (mapped == null) { - return null; - } - return ProgramLocationUtils.replaceAddress(loc, mapped.getProgram(), - mapped.getByteAddress()); - } - } - - @Override - public Set getOpenMappedLocations(ProgramLocation loc) { - synchronized (lock) { - InfoPerProgram info = requireTrackedInfo(loc.getProgram()); - if (info == null) { - return null; - } - return info.getOpenMappedTraceLocations(loc.getByteAddress()); - } - } - - @Override - public TraceLocation getOpenMappedLocation(Trace trace, ProgramLocation loc, long snap) { - synchronized (lock) { - InfoPerProgram info = requireTrackedInfo(loc.getProgram()); - if (info == null) { - return null; - } - return info.getOpenMappedTraceLocation(trace, loc.getByteAddress(), snap); - } - } - - @Override - public ProgramLocation getDynamicLocationFromStatic(TraceProgramView view, - ProgramLocation loc) { - synchronized (lock) { - TraceLocation tloc = - getOpenMappedLocation(view.getTrace(), loc, getNonScratchSnap(view)); - if (tloc == null) { - return null; - } - return ProgramLocationUtils.replaceAddress(loc, view, tloc.getAddress()); - } - } - - @Override - public Map> getOpenMappedViews(Trace trace, - AddressSetView set, long snap) { - synchronized (lock) { - InfoPerTrace info = requireTrackedInfo(trace); - if (info == null) { - return Map.of(); - } - return info.getOpenMappedViews(set, Lifespan.at(snap)); - } - } - - @Override - public Map> getOpenMappedViews(Program program, - AddressSetView set) { - synchronized (lock) { - InfoPerProgram info = requireTrackedInfo(program); - if (info == null) { - return Map.of(); - } - return info.getOpenMappedViews(set); - } - } - @Override public Set openMappedProgramsInView(Trace trace, AddressSetView set, long snap, Set failures) { - Set urls; - synchronized (lock) { - InfoPerTrace info = requireTrackedInfo(trace); - if (info == null) { - return null; - } - urls = info.getMappedProgramUrlsInView(set, Lifespan.at(snap)); - } + Set urls = context.getMappedProgramUrlsInView(trace, set, snap); Set result = new HashSet<>(); for (URL url : urls) { try { @@ -545,6 +278,49 @@ public class DebuggerStaticMappingServicePlugin extends Plugin return result; } + @Override + public Set getOpenMappedProgramsAtSnap(Trace trace, long snap) { + return context.getOpenMappedProgramsAtSnap(trace, snap); + } + + @Override + public ProgramLocation getOpenMappedLocation(TraceLocation loc) { + return context.getOpenMappedLocation(loc); + } + + @Override + public ProgramLocation getStaticLocationFromDynamic(ProgramLocation loc) { + return context.getStaticLocationFromDynamic(loc); + } + + @Override + public Set getOpenMappedLocations(ProgramLocation loc) { + return context.getOpenMappedLocations(loc); + } + + @Override + public TraceLocation getOpenMappedLocation(Trace trace, ProgramLocation loc, long snap) { + return context.getOpenMappedLocation(trace, loc, snap); + } + + @Override + public ProgramLocation getDynamicLocationFromStatic(TraceProgramView view, + ProgramLocation loc) { + return context.getDynamicLocationFromStatic(view, loc); + } + + @Override + public Map> getOpenMappedViews(Trace trace, + AddressSetView set, long snap) { + return context.getOpenMappedViews(trace, set, snap); + } + + @Override + public Map> getOpenMappedViews(Program program, + AddressSetView set) { + return context.getOpenMappedViews(program, set); + } + protected Collection orderCurrentFirst( Collection programs) { if (programManager == null) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DynamicStaticSynchronizationPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DynamicStaticSynchronizationPlugin.java index 4a40d1132f..ba5a5b7350 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DynamicStaticSynchronizationPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DynamicStaticSynchronizationPlugin.java @@ -35,7 +35,6 @@ import ghidra.app.plugin.core.debug.event.*; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; import ghidra.app.services.*; -import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; import ghidra.debug.api.modules.*; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.model.DomainFile; 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 index 330f2d9cf5..c7995d5b47 100644 --- 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 @@ -19,9 +19,9 @@ 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.service.modules.DebuggerStaticMappingContext.ChangeCollector; import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; -import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.debug.api.modules.MappedAddressRange; import ghidra.framework.model.*; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; @@ -52,14 +52,14 @@ class InfoPerProgram implements DomainObjectListener { } } - private final DebuggerStaticMappingServicePlugin plugin; + private final DebuggerStaticMappingContext ctx; final Program program; final NavMultiMap inboundByStaticAddress = new NavMultiMap<>(); final URL url; - InfoPerProgram(DebuggerStaticMappingServicePlugin plugin, Program program) { - this.plugin = plugin; + InfoPerProgram(DebuggerStaticMappingContext ctx, Program program) { + this.ctx = ctx; this.program = program; this.url = ProgramURLUtils.getUrlFromProgram(program); @@ -70,7 +70,12 @@ class InfoPerProgram implements DomainObjectListener { public void domainObjectChanged(DomainObjectChangedEvent ev) { if (ev.contains(DomainObjectEvent.FILE_CHANGED) || ev.contains(DomainObjectEvent.RENAMED)) { if (!urlMatches()) { - CompletableFuture.runAsync(plugin::programsChanged, plugin.executor); + CompletableFuture.runAsync(() -> { + try (ChangeCollector cc = ctx.collectChanges()) { + ctx.processRemovedProgramInfo(cc, this); + ctx.processAddedProgram(cc, program); + } + }, ctx.executor); } } } @@ -95,7 +100,7 @@ class InfoPerProgram implements DomainObjectListener { if (url == null) { return; } - for (InfoPerTrace info : plugin.traceInfoByTrace.values()) { + for (InfoPerTrace info : ctx.traceInfoByTrace.values()) { info.clearEntriesForProgram(cc, this); } } @@ -104,7 +109,7 @@ class InfoPerProgram implements DomainObjectListener { if (url == null) { return; } - for (InfoPerTrace info : plugin.traceInfoByTrace.values()) { + for (InfoPerTrace info : ctx.traceInfoByTrace.values()) { info.fillEntriesForProgram(cc, this); } } 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 index 7f3378d2e9..84bb5f7d32 100644 --- 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 @@ -24,8 +24,8 @@ 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.app.plugin.core.debug.service.modules.DebuggerStaticMappingContext.ChangeCollector; +import ghidra.debug.api.modules.MappedAddressRange; import ghidra.framework.model.DomainObjectChangedEvent; import ghidra.framework.model.DomainObjectEvent; import ghidra.program.model.address.*; @@ -37,7 +37,7 @@ import ghidra.trace.util.TraceEvents; import ghidra.util.Msg; class InfoPerTrace extends TraceDomainObjectListener { - private final DebuggerStaticMappingServicePlugin plugin; + private final DebuggerStaticMappingContext ctx; final Trace trace; final Map outboundByEntry = new HashMap<>(); @@ -47,8 +47,8 @@ class InfoPerTrace extends TraceDomainObjectListener { private volatile boolean needsResync = false; - InfoPerTrace(DebuggerStaticMappingServicePlugin plugin, Trace trace) { - this.plugin = plugin; + InfoPerTrace(DebuggerStaticMappingContext ctx, Trace trace) { + this.ctx = ctx; this.trace = trace; listenForUntyped(DomainObjectEvent.RESTORED, e -> objectRestored()); @@ -63,7 +63,7 @@ class InfoPerTrace extends TraceDomainObjectListener { // Now do the actual processing if (needsResync) { needsResync = false; - CompletableFuture.runAsync(this::resyncEntries, plugin.executor); + CompletableFuture.runAsync(this::resyncEntries, ctx.executor); } } @@ -84,9 +84,9 @@ class InfoPerTrace extends TraceDomainObjectListener { } private void resyncEntries() { - try (ChangeCollector cc = new ChangeCollector(plugin)) { + try (ChangeCollector cc = new ChangeCollector(ctx)) { // Invoke change callbacks without the lock! (try must surround sync) - synchronized (plugin.lock) { + synchronized (ctx.lock) { resyncEntries(cc); } } @@ -124,7 +124,7 @@ class InfoPerTrace extends TraceDomainObjectListener { } outboundByRange.remove(me.getTraceAddressSnapRange()); outboundByStaticUrl.removeMapping(me.getStaticProgramUrl(), me); - plugin.checkAndClearProgram(cc, me); + ctx.checkAndClearProgram(cc, me); } private void processAddedEntries(ChangeCollector cc, Set added) { @@ -138,7 +138,7 @@ class InfoPerTrace extends TraceDomainObjectListener { outboundByEntry.put(entry, me); outboundByRange.put(me.getTraceAddressSnapRange(), me); outboundByStaticUrl.put(me.getStaticProgramUrl(), me); - plugin.checkAndFillProgram(cc, me); + ctx.checkAndFillProgram(cc, me); } void clearEntriesForProgram(ChangeCollector cc, InfoPerProgram progInfo) { 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 index d551c0b7cb..b9d4d537c4 100644 --- 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 @@ -18,7 +18,7 @@ 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.app.plugin.core.debug.service.modules.DebuggerStaticMappingContext.ChangeCollector; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java index 962b42280a..1fa5fcf2d6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java @@ -24,8 +24,8 @@ import docking.ActionContext; import ghidra.app.context.NavigatableActionContext; import ghidra.app.nav.Navigatable; import ghidra.app.services.*; -import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; import ghidra.async.AsyncUtils; +import ghidra.debug.api.modules.MappedAddressRange; import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.PluginTool; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/AbstractMappedMemoryBytesVisitor.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/AbstractMappedMemoryBytesVisitor.java index 137780eef9..f31b5345bc 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/AbstractMappedMemoryBytesVisitor.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/AbstractMappedMemoryBytesVisitor.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. @@ -20,7 +20,7 @@ import java.util.Map.Entry; import java.util.Objects; import ghidra.app.services.DebuggerStaticMappingService; -import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.debug.api.modules.MappedAddressRange; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.*; 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 319205bcfa..7eef9ad20b 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 @@ -27,7 +27,7 @@ import db.Transaction; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesProviderTest; import ghidra.app.services.DebuggerStaticMappingService; -import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.debug.api.modules.MappedAddressRange; import ghidra.framework.model.DomainFile; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/async/AsyncUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/async/AsyncUtils.java index c75d358404..d3b169d6f1 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/async/AsyncUtils.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/async/AsyncUtils.java @@ -29,6 +29,12 @@ public interface AsyncUtils { ExecutorService FRAMEWORK_EXECUTOR = Executors.newWorkStealingPool(); ExecutorService SWING_EXECUTOR = SwingExecutorService.LATER; + Executor DIRECT_EXECUTOR = new Executor() { + @Override + public void execute(Runnable command) { + command.run(); + } + }; // NB. This was a bad idea, because CFs may maintain refs to dependents. //CompletableFuture NIL = CompletableFuture.completedFuture(null);