mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-23 20:59:58 +00:00
GP-3448: Fix GDB module ranges. Cull unnecessary queries.
This commit is contained in:
parent
0cae3ab0f5
commit
bcf78937d9
@ -117,14 +117,16 @@ public interface GdbInferior extends GdbConsoleOperations, GdbMemoryOperations {
|
||||
* List GDB's modules in this inferior (process, thread group)
|
||||
*
|
||||
* <p>
|
||||
* This is equivalent to the CLI command: {@code maintenance info sections ALLOBJ}. This command
|
||||
* is more thorough than {@code info shared} as it contains the executable module, shared
|
||||
* libraries, system-supplied objects, and enumerates all sections thereof, not just
|
||||
* {@code .text}.
|
||||
* This is equivalent to the CLI command: {@code maintenance info sections -all-objects}. This
|
||||
* command is more thorough than {@code info shared} as it contains the executable module,
|
||||
* shared libraries, system-supplied objects, and enumerates all sections thereof, not just
|
||||
* {@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
|
||||
*/
|
||||
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
|
||||
|
@ -23,18 +23,13 @@ import agent.gdb.manager.impl.GdbMinimalSymbol;
|
||||
public interface GdbModule {
|
||||
String getName();
|
||||
|
||||
CompletableFuture<Long> computeBase();
|
||||
Long getBase();
|
||||
|
||||
CompletableFuture<Long> computeMax();
|
||||
Long getMax();
|
||||
|
||||
Long getKnownBase();
|
||||
|
||||
Long getKnownMax();
|
||||
|
||||
CompletableFuture<Map<String, GdbModuleSection>> listSections();
|
||||
CompletableFuture<Map<String, GdbModuleSection>> listSections(boolean refresh);
|
||||
|
||||
Map<String, GdbModuleSection> getKnownSections();
|
||||
|
||||
CompletableFuture<Map<String, GdbMinimalSymbol>> listMinimalSymbols();
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.regex.Matcher;
|
||||
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.GdbConsoleExecCommand.CompletesWithRunning;
|
||||
import generic.ULongSpan.ULongSpanSet;
|
||||
import ghidra.async.AsyncLazyValue;
|
||||
import ghidra.lifecycle.Internal;
|
||||
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, 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> unmodifiableMappings =
|
||||
@ -235,20 +237,29 @@ public class GdbInferiorImpl implements GdbInferior {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Map<String, GdbModule>> listModules() {
|
||||
return manager.execMaintInfoSectionsAllObjects(this).thenApply(lines -> {
|
||||
return parseModuleNames(lines);
|
||||
});
|
||||
public CompletableFuture<Map<String, GdbModule>> listModules(boolean refresh) {
|
||||
if (refresh) {
|
||||
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() {
|
||||
return manager.execMaintInfoSectionsAllObjects(this).thenAccept(lines -> {
|
||||
protected CompletableFuture<Map<String, GdbModule>> doListModules() {
|
||||
return manager.execMaintInfoSectionsAllObjects(this).thenApply(lines -> {
|
||||
parseAndUpdateAllModuleSections(lines);
|
||||
return unmodifiableModules;
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
return createModule(name);
|
||||
}
|
||||
@ -258,7 +269,8 @@ public class GdbInferiorImpl implements GdbInferior {
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -266,15 +278,11 @@ public class GdbInferiorImpl implements GdbInferior {
|
||||
}
|
||||
|
||||
protected void resyncRetainModules(Set<String> names) {
|
||||
for (Iterator<Entry<String, GdbModuleImpl>> mit = modules.entrySet().iterator(); mit
|
||||
.hasNext();) {
|
||||
Entry<String, GdbModuleImpl> ent = mit.next();
|
||||
if (!names.contains(ent.getKey())) {
|
||||
Msg.warn(this, "Resync: Missed unloaded module/library: " + ent);
|
||||
/*manager.listenersInferior.fire.libraryUnloaded(this, ent.getKey(),
|
||||
Causes.UNCLAIMED);*/
|
||||
}
|
||||
}
|
||||
/**
|
||||
* NOTE: We used to fire libraryUnloaded on removes detected during resync. For that, we
|
||||
* used an iterator. Without a listener callback, we have simplified.
|
||||
*/
|
||||
modules.keySet().retainAll(names);
|
||||
}
|
||||
|
||||
protected String nameFromLine(String line) {
|
||||
@ -290,45 +298,28 @@ public class GdbInferiorImpl implements GdbInferior {
|
||||
}
|
||||
|
||||
protected void parseAndUpdateAllModuleSections(String[] lines) {
|
||||
Set<String> namesSeen = new HashSet<>();
|
||||
Set<String> modNamesSeen = new HashSet<>();
|
||||
Set<String> secNamesSeen = new HashSet<>();
|
||||
GdbModuleImpl curModule = null;
|
||||
for (String line : lines) {
|
||||
String name = nameFromLine(line);
|
||||
if (name != 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);
|
||||
// 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) {
|
||||
continue;
|
||||
else if (curModule != null) {
|
||||
curModule.processSectionLine(line, secNamesSeen);
|
||||
}
|
||||
curModule.processSectionLine(line);
|
||||
}
|
||||
if (curModule != null) {
|
||||
curModule.loadSections.provide().complete(null);
|
||||
curModule.resyncRetainSections(secNamesSeen);
|
||||
// No need to clear secNamesSeen
|
||||
}
|
||||
resyncRetainModules(namesSeen);
|
||||
}
|
||||
|
||||
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;
|
||||
resyncRetainModules(modNamesSeen);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1781,6 +1781,10 @@ public class GdbManagerImpl implements GdbManager {
|
||||
return runningInterpreter;
|
||||
}
|
||||
|
||||
private boolean isProbablyValid(String out) {
|
||||
return out.contains("->0x");
|
||||
}
|
||||
|
||||
private CompletableFuture<Map.Entry<String, String[]>> nextMaintInfoSections(
|
||||
GdbInferiorImpl inferior, String cmds[], List<String[]> results) {
|
||||
if (results.size() == cmds.length) {
|
||||
@ -1795,7 +1799,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||
String cmd = cmds[results.size()];
|
||||
return inferior.consoleCapture(cmd, CompletesWithRunning.CANNOT).thenCompose(out -> {
|
||||
String[] lines = out.split("\n");
|
||||
if (lines.length >= 10) {
|
||||
if (isProbablyValid(out)) {
|
||||
return CompletableFuture.completedFuture(Map.entry(cmd, lines));
|
||||
}
|
||||
results.add(lines);
|
||||
@ -1824,7 +1828,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||
inferior.consoleCapture(maintInfoSectionsCmd, CompletesWithRunning.CANNOT);
|
||||
return futureOut.thenCompose(out -> {
|
||||
String[] lines = out.split("\n");
|
||||
if (lines.length >= 10) {
|
||||
if (isProbablyValid(out)) {
|
||||
return CompletableFuture.completedFuture(lines);
|
||||
}
|
||||
CompletableFuture<Entry<String, String[]>> futureBest = nextMaintInfoSections(inferior,
|
||||
@ -1889,4 +1893,10 @@ public class GdbManagerImpl implements GdbManager {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void logInfo(String string) {
|
||||
if (LOG_IO) {
|
||||
DBG_LOG.println("INFO: " + string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,6 @@ public class GdbModuleImpl implements GdbModule {
|
||||
protected final Map<String, GdbModuleSectionImpl> sections = new LinkedHashMap<>();
|
||||
protected final Map<String, GdbModuleSection> unmodifiableSections =
|
||||
Collections.unmodifiableMap(sections);
|
||||
protected final AsyncLazyValue<Void> loadSections = new AsyncLazyValue<>(this::doLoadSections);
|
||||
|
||||
protected final AsyncLazyValue<Map<String, GdbMinimalSymbol>> minimalSymbols =
|
||||
new AsyncLazyValue<>(this::doGetMinimalSymbols);
|
||||
@ -104,36 +103,19 @@ public class GdbModuleImpl implements GdbModule {
|
||||
return name;
|
||||
}
|
||||
|
||||
protected CompletableFuture<Void> doLoadSections() {
|
||||
return inferior.doLoadSections();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Long> computeBase() {
|
||||
return loadSections.request().thenApply(__ -> base);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Long> computeMax() {
|
||||
return loadSections.request().thenApply(__ -> max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getKnownBase() {
|
||||
public Long getBase() {
|
||||
return base;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getKnownMax() {
|
||||
public Long getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Map<String, GdbModuleSection>> listSections() {
|
||||
if (sections.isEmpty() && loadSections.isDone()) {
|
||||
loadSections.forget();
|
||||
}
|
||||
return loadSections.request().thenApply(__ -> unmodifiableSections);
|
||||
public CompletableFuture<Map<String, GdbModuleSection>> listSections(boolean refresh) {
|
||||
return inferior.listModules(refresh).thenApply(__ -> unmodifiableSections);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -168,7 +150,7 @@ public class GdbModuleImpl implements GdbModule {
|
||||
return minimalSymbols.request();
|
||||
}
|
||||
|
||||
protected void processSectionLine(String line) {
|
||||
protected void processSectionLine(String line, Set<String> namesSeen) {
|
||||
Matcher matcher = inferior.manager.matchSectionLine(line);
|
||||
if (matcher != null) {
|
||||
try {
|
||||
@ -177,6 +159,7 @@ public class GdbModuleImpl implements GdbModule {
|
||||
long offset = Long.parseLong(matcher.group("offset"), 16);
|
||||
|
||||
String sectionName = matcher.group("name");
|
||||
namesSeen.add(sectionName);
|
||||
List<String> attrs = new ArrayList<>();
|
||||
for (String a : matcher.group("attrs").split("\\s+")) {
|
||||
if (a.length() != 0) {
|
||||
@ -191,7 +174,6 @@ public class GdbModuleImpl implements GdbModule {
|
||||
if (sections.put(sectionName,
|
||||
new GdbModuleSectionImpl(sectionName, vmaStart, vmaEnd, offset,
|
||||
attrs)) != null) {
|
||||
Msg.warn(this, "Duplicate section name: " + line);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -111,8 +111,8 @@ public class GdbModelTargetModule extends
|
||||
}
|
||||
|
||||
protected AddressRange doGetRange() {
|
||||
Long base = module.getKnownBase();
|
||||
Long max = module.getKnownMax();
|
||||
Long base = module.getBase();
|
||||
Long max = module.getMax();
|
||||
max = max == null ? base : (Long) (max - 1); // GDB gives end+1
|
||||
if (base == null) {
|
||||
Address addr = impl.space.getMinAddress();
|
||||
|
@ -101,12 +101,12 @@ public class GdbModelTargetModuleContainer
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<Void> requestElements(RefreshBehavior refresh) {
|
||||
// Ignore 'refresh' because inferior.getKnownModules may exclude executable
|
||||
return doRefresh();
|
||||
// listModules is now cached by the manager
|
||||
return doRefresh(refresh.isRefresh(elements.keySet()));
|
||||
}
|
||||
|
||||
protected CompletableFuture<Void> doRefresh() {
|
||||
return inferior.listModules().thenCompose(byName -> {
|
||||
protected CompletableFuture<Void> doRefresh(boolean force) {
|
||||
return inferior.listModules(force).thenCompose(byName -> {
|
||||
for (String modName : inferior.getKnownModules().keySet()) {
|
||||
if (!byName.keySet().contains(modName)) {
|
||||
impl.deleteModelObject(byName.get(modName));
|
||||
@ -129,7 +129,7 @@ public class GdbModelTargetModuleContainer
|
||||
}
|
||||
|
||||
protected CompletableFuture<?> refreshInternal() {
|
||||
return doRefresh().exceptionally(ex -> {
|
||||
return doRefresh(false).exceptionally(ex -> {
|
||||
impl.reportError(this, "Problem refreshing inferior's modules", ex);
|
||||
return null;
|
||||
});
|
||||
|
@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import agent.gdb.manager.GdbModuleSection;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
@ -62,9 +63,10 @@ public class GdbModelTargetSectionContainer
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> requestElements(RefreshBehavior refresh) {
|
||||
// getKnownSections is not guaranteed to be populated
|
||||
// listSections is cached by manager, so just use it always
|
||||
return module.module.listSections().thenAccept(this::updateUsingSections);
|
||||
if (!refresh.isRefresh(elements.keySet())) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
return module.module.listSections(true).thenAccept(this::updateUsingSections);
|
||||
}
|
||||
|
||||
protected synchronized GdbModelTargetSection getTargetSection(GdbModuleSection section) {
|
||||
|
@ -167,10 +167,10 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
|
||||
waitOn(startManager(mgr));
|
||||
GdbInferior inferior = mgr.currentInferior();
|
||||
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");
|
||||
assertNotNull(modEcho);
|
||||
Map<String, GdbModuleSection> sectionsEcho = waitOn(modEcho.listSections());
|
||||
Map<String, GdbModuleSection> sectionsEcho = waitOn(modEcho.listSections(false));
|
||||
GdbModuleSection secEchoText = sectionsEcho.get(".text");
|
||||
assertNotNull(secEchoText);
|
||||
}
|
||||
|
@ -20,14 +20,8 @@ import java.awt.Color;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@ -42,10 +36,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.WindowPosition;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.MenuData;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import docking.action.*;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.action.builder.ToggleActionBuilder;
|
||||
import docking.widgets.OptionDialog;
|
||||
@ -56,107 +47,30 @@ import generic.theme.GColor;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractAttachAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractConsoleAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractDetachAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractInterruptAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractKillAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractLaunchAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractQuickLaunchAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractRecordAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractRefreshAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractResumeAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractSetBreakpointAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractStepFinishAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractStepIntoAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractStepLastAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractStepOverAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractToggleAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.DisplayAsGraphAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.DisplayAsTableAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.DisplayAsTreeAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.DisplayAsXMLAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.DisplayFilteredGraphAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.DisplayFilteredTableAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.DisplayFilteredTreeAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.DisplayFilteredXMLAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.DisplayMethodsAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.ExportAsFactsAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.ExportAsXMLAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.ImportFromFactsAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.ImportFromXMLAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.OpenWinDbgTraceAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.SetTimeoutAction;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerAttachDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerBreakpointDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DummyTargetObject;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.GenericDebuggerProgramLaunchOffer;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.ObjectAttributeColumn;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.ObjectAttributeRow;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.ObjectElementColumn;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.ObjectElementRow;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.ObjectEnumeratedColumnTableModel;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.ObjectNode;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.ObjectPane;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.ObjectTable;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.ObjectTree;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.actions.*;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.*;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.script.GhidraScriptLoadException;
|
||||
import ghidra.app.script.GhidraScriptProvider;
|
||||
import ghidra.app.script.GhidraScriptUtil;
|
||||
import ghidra.app.script.GhidraState;
|
||||
import ghidra.app.services.ConsoleService;
|
||||
import ghidra.app.services.DebuggerListingService;
|
||||
import ghidra.app.services.DebuggerModelService;
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.app.script.*;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
|
||||
import ghidra.app.services.GraphDisplayBroker;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.async.TypeSpec;
|
||||
import ghidra.dbg.AnnotatedDebuggerAttributeListener;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.async.*;
|
||||
import ghidra.dbg.*;
|
||||
import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
|
||||
import ghidra.dbg.error.DebuggerMemoryAccessException;
|
||||
import ghidra.dbg.target.TargetAccessConditioned;
|
||||
import ghidra.dbg.target.TargetAttachable;
|
||||
import ghidra.dbg.target.TargetAttacher;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer;
|
||||
import ghidra.dbg.target.TargetConfigurable;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetConsole.Channel;
|
||||
import ghidra.dbg.target.TargetDetachable;
|
||||
import ghidra.dbg.target.TargetExecutionStateful;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.TargetFocusScope;
|
||||
import ghidra.dbg.target.TargetInterpreter;
|
||||
import ghidra.dbg.target.TargetInterruptible;
|
||||
import ghidra.dbg.target.TargetKillable;
|
||||
import ghidra.dbg.target.TargetLauncher;
|
||||
import ghidra.dbg.target.TargetMethod;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetProcess;
|
||||
import ghidra.dbg.target.TargetResumable;
|
||||
import ghidra.dbg.target.TargetSteppable;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.dbg.target.TargetTogglable;
|
||||
import ghidra.dbg.util.DebuggerCallbackReorderer;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.framework.model.Project;
|
||||
import ghidra.framework.options.AutoOptions;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.options.annotation.AutoOptionDefined;
|
||||
import ghidra.framework.plugintool.AutoConfigState;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.program.model.address.Address;
|
||||
@ -166,9 +80,7 @@ import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.datastruct.PrivatelyQueuedListener;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
@ -222,7 +134,9 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||
@SuppressWarnings("unused")
|
||||
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 = "";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ -580,7 +494,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||
public void addTable(ObjectContainer container) {
|
||||
AtomicReference<ObjectContainer> update = new AtomicReference<>();
|
||||
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 -> {
|
||||
try {
|
||||
ObjectContainer oc = update.get();
|
||||
@ -1331,9 +1245,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
||||
public void performRefresh(ActionContext context) {
|
||||
TargetObject current = getObjectFromContext(context);
|
||||
if (current != null) {
|
||||
current.resync(RefreshBehavior.REFRESH_ALWAYS, RefreshBehavior.REFRESH_ALWAYS);
|
||||
refresh(current.getName());
|
||||
}
|
||||
else {
|
||||
TargetObject modelRoot = getModel().getModelRoot();
|
||||
if (modelRoot != null) {
|
||||
modelRoot.resync(RefreshBehavior.REFRESH_ALWAYS, RefreshBehavior.REFRESH_ALWAYS);
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
@ -153,11 +153,11 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
|
||||
* p.update
|
||||
*/
|
||||
|
||||
public CompletableFuture<ObjectContainer> getOffspring() {
|
||||
public CompletableFuture<ObjectContainer> getOffspring(RefreshBehavior refresh) {
|
||||
if (targetObject == 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());
|
||||
propagateProvider(provider);
|
||||
return this;
|
||||
|
@ -24,6 +24,7 @@ import docking.widgets.tree.*;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
|
||||
import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.util.Msg;
|
||||
@ -77,11 +78,13 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
|
||||
public List<GTreeNode> generateChildren(TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
if (!container.isImmutable() || isInProgress()) {
|
||||
try {
|
||||
CompletableFuture<ObjectContainer> cf = container.getOffspring();
|
||||
CompletableFuture<ObjectContainer> cf =
|
||||
container.getOffspring(RefreshBehavior.REFRESH_WHEN_ABSENT);
|
||||
if (cf != null) {
|
||||
// NB: We're allowed to do this because we're guaranteed to be
|
||||
// in our own thread by the GTreeSlowLoadingNode
|
||||
|
@ -89,7 +89,7 @@ public class DefaultModuleRecorder implements ManagedModuleRecorder {
|
||||
return traceModule.addSection(path, section.getIndex(), traceRange);
|
||||
}
|
||||
catch (DuplicateNameException e) {
|
||||
Msg.warn(this, path + " already recorded");
|
||||
// Msg.warn(this, path + " already recorded");
|
||||
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
|
||||
public void removeProcessModule(TargetModule module) {
|
||||
long snap = recorder.getSnap();
|
||||
|
@ -118,6 +118,7 @@ public class TraceObjectManager {
|
||||
putAttributesHandler(TargetBreakpointLocation.class,
|
||||
this::attributesChangedBreakpointLocation);
|
||||
putAttributesHandler(TargetMemoryRegion.class, this::attributesChangedMemoryRegion);
|
||||
putAttributesHandler(TargetModule.class, this::attributesChangedModule);
|
||||
putAttributesHandler(TargetRegister.class, this::attributesChangedRegister);
|
||||
putAttributesHandler(TargetStackFrame.class, this::attributesChangedStackFrame);
|
||||
putAttributesHandler(TargetThread.class, this::attributesChangedThread);
|
||||
@ -147,11 +148,12 @@ public class TraceObjectManager {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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) {
|
||||
return handlerMap.put(key, (u, v) -> {
|
||||
handler.accept(u, v);
|
||||
handler.accept((U) u, v);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -172,7 +174,7 @@ public class TraceObjectManager {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
if (added.containsKey(TargetRegister.CONTAINER_ATTRIBUTE_NAME)) {
|
||||
TargetRegister register = (TargetRegister) parent;
|
||||
|
@ -132,6 +132,7 @@ public class AsyncLazyValue<T> {
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return true if {@link #request()} or {@link #provide()} has been called, but not completed
|
||||
|
@ -21,7 +21,6 @@ import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import ghidra.async.*;
|
||||
import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
|
||||
import ghidra.dbg.error.*;
|
||||
import ghidra.dbg.target.TargetMemory;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
@ -65,13 +64,30 @@ import ghidra.util.Msg;
|
||||
* risk deadlocking Ghidra's UI.
|
||||
*/
|
||||
public interface DebuggerObjectModel {
|
||||
|
||||
|
||||
public static enum RefreshBehavior {
|
||||
REFRESH_ALWAYS,
|
||||
REFRESH_NEVER,
|
||||
REFRESH_WHEN_ABSENT
|
||||
REFRESH_ALWAYS {
|
||||
@Override
|
||||
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 =
|
||||
TypeSpec.auto();
|
||||
public static final TypeSpec<Map<String, ?>> ATTRIBUTE_MAP_TYPE = TypeSpec.auto();
|
||||
|
Loading…
Reference in New Issue
Block a user