Merge remote-tracking branch 'origin/patch'

Conflicts:
	Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java
This commit is contained in:
Ryan Kurtz 2023-05-22 06:50:32 -04:00
commit ce8fadf7a8
16 changed files with 163 additions and 112 deletions

View file

@ -117,14 +117,16 @@ public interface GdbInferior extends GdbConsoleOperations, GdbMemoryOperations {
* List GDB's modules in this inferior (process, thread group) * List GDB's modules in this inferior (process, thread group)
* *
* <p> * <p>
* This is equivalent to the CLI command: {@code maintenance info sections ALLOBJ}. This command * This is equivalent to the CLI command: {@code maintenance info sections -all-objects}. This
* is more thorough than {@code info shared} as it contains the executable module, shared * command is more thorough than {@code info shared} as it contains the executable module,
* libraries, system-supplied objects, and enumerates all sections thereof, not just * shared libraries, system-supplied objects, and enumerates all sections thereof, not just
* {@code .text}. * {@code .text}. By default, the manager will only refresh this list on the first call or the
* next call since a module-loaded event. Otherwise, it will just return its cached list.
* *
* @param refresh force the manager to refresh its modules and sections lists
* @return a future that completes with a map of module names to module handles * @return a future that completes with a map of module names to module handles
*/ */
CompletableFuture<Map<String, GdbModule>> listModules(); CompletableFuture<Map<String, GdbModule>> listModules(boolean refresh);
/** /**
* Enumerate the memory mappings known to the manager to belong to this inferior's process * Enumerate the memory mappings known to the manager to belong to this inferior's process

View file

@ -23,18 +23,13 @@ import agent.gdb.manager.impl.GdbMinimalSymbol;
public interface GdbModule { public interface GdbModule {
String getName(); String getName();
CompletableFuture<Long> computeBase(); Long getBase();
CompletableFuture<Long> computeMax(); Long getMax();
Long getKnownBase(); CompletableFuture<Map<String, GdbModuleSection>> listSections(boolean refresh);
Long getKnownMax();
CompletableFuture<Map<String, GdbModuleSection>> listSections();
Map<String, GdbModuleSection> getKnownSections(); Map<String, GdbModuleSection> getKnownSections();
CompletableFuture<Map<String, GdbMinimalSymbol>> listMinimalSymbols(); CompletableFuture<Map<String, GdbMinimalSymbol>> listMinimalSymbols();
} }

View file

@ -19,7 +19,6 @@ import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.*; import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -30,6 +29,7 @@ import agent.gdb.manager.GdbManager.StepCmd;
import agent.gdb.manager.impl.cmd.*; import agent.gdb.manager.impl.cmd.*;
import agent.gdb.manager.impl.cmd.GdbConsoleExecCommand.CompletesWithRunning; import agent.gdb.manager.impl.cmd.GdbConsoleExecCommand.CompletesWithRunning;
import generic.ULongSpan.ULongSpanSet; import generic.ULongSpan.ULongSpanSet;
import ghidra.async.AsyncLazyValue;
import ghidra.lifecycle.Internal; import ghidra.lifecycle.Internal;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -69,6 +69,8 @@ public class GdbInferiorImpl implements GdbInferior {
private final Map<String, GdbModuleImpl> modules = new LinkedHashMap<>(); private final Map<String, GdbModuleImpl> modules = new LinkedHashMap<>();
private final Map<String, GdbModule> unmodifiableModules = Collections.unmodifiableMap(modules); private final Map<String, GdbModule> unmodifiableModules = Collections.unmodifiableMap(modules);
protected final AsyncLazyValue<Map<String, GdbModule>> listModules =
new AsyncLazyValue<>(this::doListModules);
private final NavigableMap<BigInteger, GdbMemoryMapping> mappings = new TreeMap<>(); private final NavigableMap<BigInteger, GdbMemoryMapping> mappings = new TreeMap<>();
private final NavigableMap<BigInteger, GdbMemoryMapping> unmodifiableMappings = private final NavigableMap<BigInteger, GdbMemoryMapping> unmodifiableMappings =
@ -235,20 +237,29 @@ public class GdbInferiorImpl implements GdbInferior {
} }
@Override @Override
public CompletableFuture<Map<String, GdbModule>> listModules() { public CompletableFuture<Map<String, GdbModule>> listModules(boolean refresh) {
return manager.execMaintInfoSectionsAllObjects(this).thenApply(lines -> { if (refresh) {
return parseModuleNames(lines); if (listModules.isBusy()) {
}); manager.logInfo("Refresh requested while busy. Keeping cache.");
}
else {
manager.logInfo("Refresh requested. Forgetting module cache.");
listModules.forget();
}
}
manager.logInfo("Modules requested");
return listModules.request();
} }
protected CompletableFuture<Void> doLoadSections() { protected CompletableFuture<Map<String, GdbModule>> doListModules() {
return manager.execMaintInfoSectionsAllObjects(this).thenAccept(lines -> { return manager.execMaintInfoSectionsAllObjects(this).thenApply(lines -> {
parseAndUpdateAllModuleSections(lines); parseAndUpdateAllModuleSections(lines);
return unmodifiableModules;
}); });
} }
protected GdbModuleImpl resyncCreateModule(String name) { protected GdbModuleImpl resyncCreateModule(String name) {
Msg.warn(this, "Resync: Missed loaded module/library: " + name); //Msg.warn(this, "Resync: Missed loaded module/library: " + name);
//manager.listenersInferior.fire.libraryLoaded(this, name, Causes.UNCLAIMED); //manager.listenersInferior.fire.libraryLoaded(this, name, Causes.UNCLAIMED);
return createModule(name); return createModule(name);
} }
@ -258,7 +269,8 @@ public class GdbInferiorImpl implements GdbInferior {
} }
protected void libraryLoaded(String name) { protected void libraryLoaded(String name) {
modules.computeIfAbsent(name, this::createModule); manager.logInfo("Module loaded: " + name + ". Forgeting module cache.");
listModules.forget();
} }
protected void libraryUnloaded(String name) { protected void libraryUnloaded(String name) {
@ -266,15 +278,11 @@ public class GdbInferiorImpl implements GdbInferior {
} }
protected void resyncRetainModules(Set<String> names) { protected void resyncRetainModules(Set<String> names) {
for (Iterator<Entry<String, GdbModuleImpl>> mit = modules.entrySet().iterator(); mit /**
.hasNext();) { * NOTE: We used to fire libraryUnloaded on removes detected during resync. For that, we
Entry<String, GdbModuleImpl> ent = mit.next(); * used an iterator. Without a listener callback, we have simplified.
if (!names.contains(ent.getKey())) { */
Msg.warn(this, "Resync: Missed unloaded module/library: " + ent); modules.keySet().retainAll(names);
/*manager.listenersInferior.fire.libraryUnloaded(this, ent.getKey(),
Causes.UNCLAIMED);*/
}
}
} }
protected String nameFromLine(String line) { protected String nameFromLine(String line) {
@ -290,45 +298,28 @@ public class GdbInferiorImpl implements GdbInferior {
} }
protected void parseAndUpdateAllModuleSections(String[] lines) { protected void parseAndUpdateAllModuleSections(String[] lines) {
Set<String> namesSeen = new HashSet<>(); Set<String> modNamesSeen = new HashSet<>();
Set<String> secNamesSeen = new HashSet<>();
GdbModuleImpl curModule = null; GdbModuleImpl curModule = null;
for (String line : lines) { for (String line : lines) {
String name = nameFromLine(line); String name = nameFromLine(line);
if (name != null) { if (name != null) {
if (curModule != null) { if (curModule != null) {
curModule.loadSections.provide().complete(null); curModule.resyncRetainSections(secNamesSeen);
secNamesSeen.clear();
} }
namesSeen.add(name); modNamesSeen.add(name);
curModule = modules.computeIfAbsent(name, this::resyncCreateModule); curModule = modules.computeIfAbsent(name, this::resyncCreateModule);
// NOTE: This will usurp the module's lazy loader, but we're about to
// provide it anyway
if (curModule.loadSections.isDone()) {
curModule = null;
}
continue;
} }
if (curModule == null) { else if (curModule != null) {
continue; curModule.processSectionLine(line, secNamesSeen);
} }
curModule.processSectionLine(line);
} }
if (curModule != null) { if (curModule != null) {
curModule.loadSections.provide().complete(null); curModule.resyncRetainSections(secNamesSeen);
// No need to clear secNamesSeen
} }
resyncRetainModules(namesSeen); resyncRetainModules(modNamesSeen);
}
protected Map<String, GdbModule> parseModuleNames(String[] lines) {
Set<String> namesSeen = new HashSet<>();
for (String line : lines) {
String name = nameFromLine(line);
if (name != null) {
namesSeen.add(name);
modules.computeIfAbsent(name, this::resyncCreateModule);
}
}
resyncRetainModules(namesSeen);
return unmodifiableModules;
} }
@Override @Override

View file

@ -1781,6 +1781,10 @@ public class GdbManagerImpl implements GdbManager {
return runningInterpreter; return runningInterpreter;
} }
private boolean isProbablyValid(String out) {
return out.contains("->0x");
}
private CompletableFuture<Map.Entry<String, String[]>> nextMaintInfoSections( private CompletableFuture<Map.Entry<String, String[]>> nextMaintInfoSections(
GdbInferiorImpl inferior, String cmds[], List<String[]> results) { GdbInferiorImpl inferior, String cmds[], List<String[]> results) {
if (results.size() == cmds.length) { if (results.size() == cmds.length) {
@ -1795,7 +1799,7 @@ public class GdbManagerImpl implements GdbManager {
String cmd = cmds[results.size()]; String cmd = cmds[results.size()];
return inferior.consoleCapture(cmd, CompletesWithRunning.CANNOT).thenCompose(out -> { return inferior.consoleCapture(cmd, CompletesWithRunning.CANNOT).thenCompose(out -> {
String[] lines = out.split("\n"); String[] lines = out.split("\n");
if (lines.length >= 10) { if (isProbablyValid(out)) {
return CompletableFuture.completedFuture(Map.entry(cmd, lines)); return CompletableFuture.completedFuture(Map.entry(cmd, lines));
} }
results.add(lines); results.add(lines);
@ -1824,7 +1828,7 @@ public class GdbManagerImpl implements GdbManager {
inferior.consoleCapture(maintInfoSectionsCmd, CompletesWithRunning.CANNOT); inferior.consoleCapture(maintInfoSectionsCmd, CompletesWithRunning.CANNOT);
return futureOut.thenCompose(out -> { return futureOut.thenCompose(out -> {
String[] lines = out.split("\n"); String[] lines = out.split("\n");
if (lines.length >= 10) { if (isProbablyValid(out)) {
return CompletableFuture.completedFuture(lines); return CompletableFuture.completedFuture(lines);
} }
CompletableFuture<Entry<String, String[]>> futureBest = nextMaintInfoSections(inferior, CompletableFuture<Entry<String, String[]>> futureBest = nextMaintInfoSections(inferior,
@ -1889,4 +1893,10 @@ public class GdbManagerImpl implements GdbManager {
} }
return null; return null;
} }
public void logInfo(String string) {
if (LOG_IO) {
DBG_LOG.println("INFO: " + string);
}
}
} }

View file

@ -89,7 +89,6 @@ public class GdbModuleImpl implements GdbModule {
protected final Map<String, GdbModuleSectionImpl> sections = new LinkedHashMap<>(); protected final Map<String, GdbModuleSectionImpl> sections = new LinkedHashMap<>();
protected final Map<String, GdbModuleSection> unmodifiableSections = protected final Map<String, GdbModuleSection> unmodifiableSections =
Collections.unmodifiableMap(sections); Collections.unmodifiableMap(sections);
protected final AsyncLazyValue<Void> loadSections = new AsyncLazyValue<>(this::doLoadSections);
protected final AsyncLazyValue<Map<String, GdbMinimalSymbol>> minimalSymbols = protected final AsyncLazyValue<Map<String, GdbMinimalSymbol>> minimalSymbols =
new AsyncLazyValue<>(this::doGetMinimalSymbols); new AsyncLazyValue<>(this::doGetMinimalSymbols);
@ -104,36 +103,19 @@ public class GdbModuleImpl implements GdbModule {
return name; return name;
} }
protected CompletableFuture<Void> doLoadSections() {
return inferior.doLoadSections();
}
@Override @Override
public CompletableFuture<Long> computeBase() { public Long getBase() {
return loadSections.request().thenApply(__ -> base);
}
@Override
public CompletableFuture<Long> computeMax() {
return loadSections.request().thenApply(__ -> max);
}
@Override
public Long getKnownBase() {
return base; return base;
} }
@Override @Override
public Long getKnownMax() { public Long getMax() {
return max; return max;
} }
@Override @Override
public CompletableFuture<Map<String, GdbModuleSection>> listSections() { public CompletableFuture<Map<String, GdbModuleSection>> listSections(boolean refresh) {
if (sections.isEmpty() && loadSections.isDone()) { return inferior.listModules(refresh).thenApply(__ -> unmodifiableSections);
loadSections.forget();
}
return loadSections.request().thenApply(__ -> unmodifiableSections);
} }
@Override @Override
@ -168,7 +150,7 @@ public class GdbModuleImpl implements GdbModule {
return minimalSymbols.request(); return minimalSymbols.request();
} }
protected void processSectionLine(String line) { protected void processSectionLine(String line, Set<String> namesSeen) {
Matcher matcher = inferior.manager.matchSectionLine(line); Matcher matcher = inferior.manager.matchSectionLine(line);
if (matcher != null) { if (matcher != null) {
try { try {
@ -177,6 +159,7 @@ public class GdbModuleImpl implements GdbModule {
long offset = Long.parseLong(matcher.group("offset"), 16); long offset = Long.parseLong(matcher.group("offset"), 16);
String sectionName = matcher.group("name"); String sectionName = matcher.group("name");
namesSeen.add(sectionName);
List<String> attrs = new ArrayList<>(); List<String> attrs = new ArrayList<>();
for (String a : matcher.group("attrs").split("\\s+")) { for (String a : matcher.group("attrs").split("\\s+")) {
if (a.length() != 0) { if (a.length() != 0) {
@ -191,7 +174,6 @@ public class GdbModuleImpl implements GdbModule {
if (sections.put(sectionName, if (sections.put(sectionName,
new GdbModuleSectionImpl(sectionName, vmaStart, vmaEnd, offset, new GdbModuleSectionImpl(sectionName, vmaStart, vmaEnd, offset,
attrs)) != null) { attrs)) != null) {
Msg.warn(this, "Duplicate section name: " + line);
} }
} }
catch (NumberFormatException e) { catch (NumberFormatException e) {
@ -199,4 +181,16 @@ public class GdbModuleImpl implements GdbModule {
} }
} }
} }
protected void resyncRetainSections(Set<String> names) {
/**
* The need for this seems dubious. If it's the same module, why would its sections ever
* change? However, in theory, a module could be unloaded, replaced on disk, and reloaded.
* If all those happen between two queries, then yes, it'll seem as though an existing
* module's sections changed.
*
* If we ever need a callback, we'll have to use an iterator-based implementation.
*/
sections.keySet().retainAll(names);
}
} }

View file

@ -111,8 +111,8 @@ public class GdbModelTargetModule extends
} }
protected AddressRange doGetRange() { protected AddressRange doGetRange() {
Long base = module.getKnownBase(); Long base = module.getBase();
Long max = module.getKnownMax(); Long max = module.getMax();
max = max == null ? base : (Long) (max - 1); // GDB gives end+1 max = max == null ? base : (Long) (max - 1); // GDB gives end+1
if (base == null) { if (base == null) {
Address addr = impl.space.getMinAddress(); Address addr = impl.space.getMinAddress();

View file

@ -101,12 +101,12 @@ public class GdbModelTargetModuleContainer
@Override @Override
protected CompletableFuture<Void> requestElements(RefreshBehavior refresh) { protected CompletableFuture<Void> requestElements(RefreshBehavior refresh) {
// Ignore 'refresh' because inferior.getKnownModules may exclude executable // listModules is now cached by the manager
return doRefresh(); return doRefresh(refresh.isRefresh(elements.keySet()));
} }
protected CompletableFuture<Void> doRefresh() { protected CompletableFuture<Void> doRefresh(boolean force) {
return inferior.listModules().thenCompose(byName -> { return inferior.listModules(force).thenCompose(byName -> {
for (String modName : inferior.getKnownModules().keySet()) { for (String modName : inferior.getKnownModules().keySet()) {
if (!byName.keySet().contains(modName)) { if (!byName.keySet().contains(modName)) {
impl.deleteModelObject(byName.get(modName)); impl.deleteModelObject(byName.get(modName));
@ -129,7 +129,7 @@ public class GdbModelTargetModuleContainer
} }
protected CompletableFuture<?> refreshInternal() { protected CompletableFuture<?> refreshInternal() {
return doRefresh().exceptionally(ex -> { return doRefresh(false).exceptionally(ex -> {
impl.reportError(this, "Problem refreshing inferior's modules", ex); impl.reportError(this, "Problem refreshing inferior's modules", ex);
return null; return null;
}); });

View file

@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import agent.gdb.manager.GdbModuleSection; import agent.gdb.manager.GdbModuleSection;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerObjectModel.RefreshBehavior; import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
import ghidra.dbg.agent.DefaultTargetObject; import ghidra.dbg.agent.DefaultTargetObject;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
@ -62,9 +63,10 @@ public class GdbModelTargetSectionContainer
@Override @Override
public CompletableFuture<Void> requestElements(RefreshBehavior refresh) { public CompletableFuture<Void> requestElements(RefreshBehavior refresh) {
// getKnownSections is not guaranteed to be populated if (!refresh.isRefresh(elements.keySet())) {
// listSections is cached by manager, so just use it always return AsyncUtils.NIL;
return module.module.listSections().thenAccept(this::updateUsingSections); }
return module.module.listSections(true).thenAccept(this::updateUsingSections);
} }
protected synchronized GdbModelTargetSection getTargetSection(GdbModuleSection section) { protected synchronized GdbModelTargetSection getTargetSection(GdbModuleSection section) {

View file

@ -167,10 +167,10 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
waitOn(startManager(mgr)); waitOn(startManager(mgr));
GdbInferior inferior = mgr.currentInferior(); GdbInferior inferior = mgr.currentInferior();
waitOn(inferior.fileExecAndSymbols("/usr/bin/echo")); waitOn(inferior.fileExecAndSymbols("/usr/bin/echo"));
Map<String, GdbModule> modules = waitOn(inferior.listModules()); Map<String, GdbModule> modules = waitOn(inferior.listModules(false));
GdbModule modEcho = modules.get("/usr/bin/echo"); GdbModule modEcho = modules.get("/usr/bin/echo");
assertNotNull(modEcho); assertNotNull(modEcho);
Map<String, GdbModuleSection> sectionsEcho = waitOn(modEcho.listSections()); Map<String, GdbModuleSection> sectionsEcho = waitOn(modEcho.listSections(false));
GdbModuleSection secEchoText = sectionsEcho.get(".text"); GdbModuleSection secEchoText = sectionsEcho.get(".text");
assertNotNull(secEchoText); assertNotNull(secEchoText);
} }

View file

@ -133,7 +133,9 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring; private final AutoService.Wiring autoServiceWiring;
@AutoOptionDefined(name = "Default Extended Step", description = "The default string for the extended step command") @AutoOptionDefined(
name = "Default Extended Step",
description = "The default string for the extended step command")
String extendedStep = ""; String extendedStep = "";
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -491,7 +493,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
public void addTable(ObjectContainer container) { public void addTable(ObjectContainer container) {
AtomicReference<ObjectContainer> update = new AtomicReference<>(); AtomicReference<ObjectContainer> update = new AtomicReference<>();
AsyncUtils.sequence(TypeSpec.cls(ObjectContainer.class)).then(seq -> { AsyncUtils.sequence(TypeSpec.cls(ObjectContainer.class)).then(seq -> {
container.getOffspring().handle(seq::next); container.getOffspring(RefreshBehavior.REFRESH_WHEN_ABSENT).handle(seq::next);
}, update).then(seq -> { }, update).then(seq -> {
try { try {
ObjectContainer oc = update.get(); ObjectContainer oc = update.get();
@ -1242,9 +1244,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
public void performRefresh(ActionContext context) { public void performRefresh(ActionContext context) {
TargetObject current = getObjectFromContext(context); TargetObject current = getObjectFromContext(context);
if (current != null) { if (current != null) {
current.resync(RefreshBehavior.REFRESH_ALWAYS, RefreshBehavior.REFRESH_ALWAYS);
refresh(current.getName()); refresh(current.getName());
} }
else { else {
TargetObject modelRoot = getModel().getModelRoot();
if (modelRoot != null) {
modelRoot.resync(RefreshBehavior.REFRESH_ALWAYS, RefreshBehavior.REFRESH_ALWAYS);
}
refresh(); refresh();
} }
} }

View file

@ -153,11 +153,11 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
* p.update * p.update
*/ */
public CompletableFuture<ObjectContainer> getOffspring() { public CompletableFuture<ObjectContainer> getOffspring(RefreshBehavior refresh) {
if (targetObject == null) { if (targetObject == null) {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
return targetObject.resync(RefreshBehavior.REFRESH_ALWAYS, RefreshBehavior.REFRESH_ALWAYS).thenApplyAsync(__ -> { return targetObject.resync(refresh, refresh).thenApplyAsync(__ -> {
rebuildContainers(targetObject.getCachedElements(), targetObject.getCachedAttributes()); rebuildContainers(targetObject.getCachedElements(), targetObject.getCachedAttributes());
propagateProvider(provider); propagateProvider(provider);
return this; return this;

View file

@ -24,6 +24,7 @@ import docking.widgets.tree.*;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider; import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider;
import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer; import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -77,11 +78,13 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode
} }
@Override @Override
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException { public List<GTreeNode> generateChildren(TaskMonitor monitor)
throws CancelledException {
if (!container.isImmutable() || isInProgress()) { if (!container.isImmutable() || isInProgress()) {
try { try {
CompletableFuture<ObjectContainer> cf = container.getOffspring(); CompletableFuture<ObjectContainer> cf =
container.getOffspring(RefreshBehavior.REFRESH_WHEN_ABSENT);
if (cf != null) { if (cf != null) {
// NB: We're allowed to do this because we're guaranteed to be // NB: We're allowed to do this because we're guaranteed to be
// in our own thread by the GTreeSlowLoadingNode // in our own thread by the GTreeSlowLoadingNode

View file

@ -89,7 +89,7 @@ public class DefaultModuleRecorder implements ManagedModuleRecorder {
return traceModule.addSection(path, section.getIndex(), traceRange); return traceModule.addSection(path, section.getIndex(), traceRange);
} }
catch (DuplicateNameException e) { catch (DuplicateNameException e) {
Msg.warn(this, path + " already recorded"); // Msg.warn(this, path + " already recorded");
return moduleManager.getLoadedSectionByPath(snap, path); return moduleManager.getLoadedSectionByPath(snap, path);
} }
} }
@ -123,6 +123,27 @@ public class DefaultModuleRecorder implements ManagedModuleRecorder {
} }
} }
public void moduleChanged(TargetModule module, AddressRange traceRng) {
long snap = recorder.getSnap();
String path = module.getJoinedPath(".");
recorder.parTx.execute("Module " + path + " range updated", () -> {
doModuleChanged(snap, path, traceRng);
}, path);
}
protected void doModuleChanged(long snap, String path, AddressRange traceRng) {
TraceModule traceModule = moduleManager.getLoadedModuleByPath(snap, path);
if (traceModule == null) {
Msg.warn(this, "changed " + path + " is not in the trace");
return;
}
/**
* Yes, this will modify the module's previous history, which technically could be
* incorrect. The occasion should be rare, and the OBTR will handle it correctly.
*/
traceModule.setRange(traceRng);
}
@Override @Override
public void removeProcessModule(TargetModule module) { public void removeProcessModule(TargetModule module) {
long snap = recorder.getSnap(); long snap = recorder.getSnap();

View file

@ -118,6 +118,7 @@ public class TraceObjectManager {
putAttributesHandler(TargetBreakpointLocation.class, putAttributesHandler(TargetBreakpointLocation.class,
this::attributesChangedBreakpointLocation); this::attributesChangedBreakpointLocation);
putAttributesHandler(TargetMemoryRegion.class, this::attributesChangedMemoryRegion); putAttributesHandler(TargetMemoryRegion.class, this::attributesChangedMemoryRegion);
putAttributesHandler(TargetModule.class, this::attributesChangedModule);
putAttributesHandler(TargetRegister.class, this::attributesChangedRegister); putAttributesHandler(TargetRegister.class, this::attributesChangedRegister);
putAttributesHandler(TargetStackFrame.class, this::attributesChangedStackFrame); putAttributesHandler(TargetStackFrame.class, this::attributesChangedStackFrame);
putAttributesHandler(TargetThread.class, this::attributesChangedThread); putAttributesHandler(TargetThread.class, this::attributesChangedThread);
@ -147,11 +148,12 @@ public class TraceObjectManager {
}); });
} }
@SuppressWarnings("unchecked")
private <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putHandler( private <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putHandler(
Class<?> key, BiConsumer<TargetObject, Map<String, ?>> handler, Class<?> key, BiConsumer<U, Map<String, ?>> handler,
LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMap) { LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMap) {
return handlerMap.put(key, (u, v) -> { return handlerMap.put(key, (u, v) -> {
handler.accept(u, v); handler.accept((U) u, v);
return null; return null;
}); });
} }
@ -172,7 +174,7 @@ public class TraceObjectManager {
} }
public <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putAttributesHandler( public <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putAttributesHandler(
Class<?> key, BiConsumer<TargetObject, Map<String, ?>> handler) { Class<U> key, BiConsumer<U, Map<String, ?>> handler) {
return putHandler(key, handler, handlerMapAttributes); return putHandler(key, handler, handlerMapAttributes);
} }
@ -509,6 +511,13 @@ public class TraceObjectManager {
} }
} }
public void attributesChangedModule(TargetModule module, Map<String, ?> added) {
if (added.containsKey(TargetModule.RANGE_ATTRIBUTE_NAME)) {
AddressRange traceRng = recorder.getMemoryMapper().targetToTrace(module.getRange());
recorder.moduleRecorder.moduleChanged(module, traceRng);
}
}
public void attributesChangedRegister(TargetObject parent, Map<String, ?> added) { public void attributesChangedRegister(TargetObject parent, Map<String, ?> added) {
if (added.containsKey(TargetRegister.CONTAINER_ATTRIBUTE_NAME)) { if (added.containsKey(TargetRegister.CONTAINER_ATTRIBUTE_NAME)) {
TargetRegister register = (TargetRegister) parent; TargetRegister register = (TargetRegister) parent;

View file

@ -132,6 +132,7 @@ public class AsyncLazyValue<T> {
/** /**
* Check if the value has been requested, but not yet completed * Check if the value has been requested, but not yet completed
* *
* <p>
* This will also return true if something is providing the value out of band. * This will also return true if something is providing the value out of band.
* *
* @return true if {@link #request()} or {@link #provide()} has been called, but not completed * @return true if {@link #request()} or {@link #provide()} has been called, but not completed

View file

@ -21,7 +21,6 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.function.Predicate; import java.util.function.Predicate;
import ghidra.async.*; import ghidra.async.*;
import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
import ghidra.dbg.error.*; import ghidra.dbg.error.*;
import ghidra.dbg.target.TargetMemory; import ghidra.dbg.target.TargetMemory;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
@ -65,13 +64,30 @@ import ghidra.util.Msg;
* risk deadlocking Ghidra's UI. * risk deadlocking Ghidra's UI.
*/ */
public interface DebuggerObjectModel { public interface DebuggerObjectModel {
public static enum RefreshBehavior { public static enum RefreshBehavior {
REFRESH_ALWAYS, REFRESH_ALWAYS {
REFRESH_NEVER, @Override
REFRESH_WHEN_ABSENT public boolean isRefresh(Collection<?> col) {
return true;
}
},
REFRESH_NEVER {
@Override
public boolean isRefresh(Collection<?> col) {
return false;
}
},
REFRESH_WHEN_ABSENT {
@Override
public boolean isRefresh(Collection<?> col) {
return col.isEmpty();
}
};
public abstract boolean isRefresh(Collection<?> col);
} }
public static final TypeSpec<Map<String, ? extends TargetObject>> ELEMENT_MAP_TYPE = public static final TypeSpec<Map<String, ? extends TargetObject>> ELEMENT_MAP_TYPE =
TypeSpec.auto(); TypeSpec.auto();
public static final TypeSpec<Map<String, ?>> ATTRIBUTE_MAP_TYPE = TypeSpec.auto(); public static final TypeSpec<Map<String, ?>> ATTRIBUTE_MAP_TYPE = TypeSpec.auto();