Merge remote-tracking branch 'origin/GP-6680_Dan_separateMapperVsService--SQUASHED'

This commit is contained in:
Ryan Kurtz
2026-05-11 05:12:44 -04:00
14 changed files with 619 additions and 469 deletions
@@ -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
*
* <p>
* Note, the natural order is by the <em>destination</em> address.
*/
public class MappedAddressRange implements Comparable<MappedAddressRange> {
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 "<MappedRange " + srcRange + "::" + dstRange + ">";
}
/**
* Get the shift from the source address range to this address range
*
* <p>
* 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
*
@@ -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
*
* <p>
* Note, the natural order is by the <em>destination</em> address.
*/
public class MappedAddressRange implements Comparable<MappedAddressRange> {
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 "<MappedRange " + srcRange + "::" + dstRange + ">";
}
/**
* Get the shift from the source address range to this address range
*
* <p>
* 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;
}
}
@@ -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;
@@ -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;
@@ -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<Trace> traces,
Set<Program> programs) implements AutoCloseable {
static <T> Set<T> subtract(Set<T> a, Set<T> b) {
Set<T> 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<Trace, InfoPerTrace> traceInfoByTrace = new HashMap<>();
final Map<Program, InfoPerProgram> programInfoByProgram = new HashMap<>();
final Map<URL, InfoPerProgram> programInfoByUrl = new HashMap<>();
final Object lock = new Object();
final Executor executor;
private final ListenerSet<DebuggerStaticMappingChangeListener> 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<Program> programs) {
synchronized (lock) {
Set<InfoPerProgram> removed = programInfoByProgram.values()
.stream()
.filter(i -> !programs.contains(i.program) || !i.urlMatches())
.collect(Collectors.toSet());
processRemovedProgramInfos(cc, removed);
Set<Program> 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<Trace> traces) {
synchronized (lock) {
Set<Trace> oldTraces = traceInfoByTrace.keySet();
Set<Trace> removed = ChangeCollector.subtract(oldTraces, traces);
Set<Trace> added = ChangeCollector.subtract(traces, oldTraces);
processRemovedTraces(cc, removed);
processAddedTraces(cc, added);
}
}
protected <T> 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> 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> 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<InfoPerProgram> 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<Program> 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<Trace> 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<Trace> 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<Program> 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<TraceLocation> 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<Program, Collection<MappedAddressRange>> 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<TraceSpan, Collection<MappedAddressRange>> getOpenMappedViews(Program program,
AddressSetView set) {
synchronized (lock) {
InfoPerProgram info = requireTrackedInfo(program);
if (info == null) {
return Map.of();
}
return info.getOpenMappedViews(set);
}
}
public Set<URL> 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));
}
}
}
@@ -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<Trace> traces,
Set<Program> programs) implements AutoCloseable {
static <T> Set<T> subtract(Set<T> a, Set<T> b) {
Set<T> 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<Trace, InfoPerTrace> traceInfoByTrace = new HashMap<>();
final Map<Program, InfoPerProgram> programInfoByProgram = new HashMap<>();
final Map<URL, InfoPerProgram> 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<DebuggerStaticMappingChangeListener> 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<Program> curProgs = Stream.of(programManager.getAllOpenPrograms())
.filter(p -> !p.isClosed()) // Double-check
.collect(Collectors.toSet());
Set<InfoPerProgram> removed = programInfoByProgram.values()
.stream()
.filter(i -> !curProgs.contains(i.program) || !i.urlMatches())
.collect(Collectors.toSet());
processRemovedProgramInfos(cc, removed);
Set<Program> added = ChangeCollector.subtract(curProgs, programInfoByProgram.keySet());
processAddedPrograms(cc, added);
}
void processRemovedProgramInfos(ChangeCollector cc, Set<InfoPerProgram> 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<Program> 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<Trace> curTraces = traceManager.getOpenTraces()
.stream()
.filter(t -> !t.isClosed()) // Double-check
.collect(Collectors.toSet());
Set<Trace> oldTraces = traceInfoByTrace.keySet();
Set<Trace> removed = ChangeCollector.subtract(oldTraces, curTraces);
Set<Trace> added = ChangeCollector.subtract(curTraces, oldTraces);
processRemovedTraces(cc, removed);
processAddedTraces(cc, added);
}
void processRemovedTraces(ChangeCollector cc, Set<Trace> 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<Trace> 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<Program> 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<Trace> 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> 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> 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> 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<Program> 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<TraceLocation> 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<Program, Collection<MappedAddressRange>> 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<TraceSpan, Collection<MappedAddressRange>> getOpenMappedViews(Program program,
AddressSetView set) {
synchronized (lock) {
InfoPerProgram info = requireTrackedInfo(program);
if (info == null) {
return Map.of();
}
return info.getOpenMappedViews(set);
}
}
@Override
public Set<Program> openMappedProgramsInView(Trace trace, AddressSetView set, long snap,
Set<Exception> failures) {
Set<URL> urls;
synchronized (lock) {
InfoPerTrace info = requireTrackedInfo(trace);
if (info == null) {
return null;
}
urls = info.getMappedProgramUrlsInView(set, Lifespan.at(snap));
}
Set<URL> urls = context.getMappedProgramUrlsInView(trace, set, snap);
Set<Program> result = new HashSet<>();
for (URL url : urls) {
try {
@@ -545,6 +278,49 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
return result;
}
@Override
public Set<Program> 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<TraceLocation> 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<Program, Collection<MappedAddressRange>> getOpenMappedViews(Trace trace,
AddressSetView set, long snap) {
return context.getOpenMappedViews(trace, set, snap);
}
@Override
public Map<TraceSpan, Collection<MappedAddressRange>> getOpenMappedViews(Program program,
AddressSetView set) {
return context.getOpenMappedViews(program, set);
}
protected Collection<? extends Program> orderCurrentFirst(
Collection<? extends Program> programs) {
if (programManager == null) {
@@ -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;
@@ -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<Address, MappingEntry> 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);
}
}
@@ -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<TraceStaticMapping, MappingEntry> 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<TraceStaticMapping> 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) {
@@ -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;
@@ -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;
@@ -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.*;
@@ -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;
@@ -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<Void> NIL = CompletableFuture.completedFuture(null);