From eaf8367ba142430b84ad083a0872d1ec95cda0a8 Mon Sep 17 00:00:00 2001 From: Ricardo Sawir <37329575+sawirricardo@users.noreply.github.com> Date: Tue, 23 Jun 2026 10:48:14 +0700 Subject: [PATCH] Fix debugger module indexing startup hang --- .../DebuggerStaticMappingServicePlugin.java | 7 +- .../service/modules/ProgramModuleIndexer.java | 139 ++++++++++++++---- 2 files changed, 119 insertions(+), 27 deletions(-) diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java index 2719a6595a..d85d1efd7f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java @@ -100,13 +100,14 @@ public class DebuggerStaticMappingServicePlugin extends Plugin super(tool); this.autoWiring = AutoService.wireServicesProvidedAndConsumed(this); this.context = new DebuggerStaticMappingContext(executor); - this.programModuleIndexer = new ProgramModuleIndexer(tool); + this.programModuleIndexer = new ProgramModuleIndexer(tool, this::executeTask); this.moduleMapProposalGenerator = new ModuleMapProposalGenerator(programModuleIndexer); } @Override protected void dispose() { tool.getProject().getProjectData().removeDomainFolderChangeListener(this); + programModuleIndexer.dispose(); executor.close(); super.dispose(); } @@ -128,6 +129,8 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } private void checkTraceMapping(Trace trace, TaskMonitor monitor) throws CancelledException { + programModuleIndexer.waitForInitialIndex(monitor); + DebuggerAutoMappingService autoMappingService = tool.getService(DebuggerAutoMappingService.class); if (autoMappingService == null || @@ -188,6 +191,8 @@ public class DebuggerStaticMappingServicePlugin extends Plugin private void checkAllTraceMappingsForProgram(Program program, TaskMonitor monitor) throws CancelledException { + programModuleIndexer.waitForInitialIndex(monitor); + DebuggerAutoMappingService autoMappingService = tool.getService(DebuggerAutoMappingService.class); if (autoMappingService == null || diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java index ea420ea1d8..d1117f5048 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java @@ -17,6 +17,8 @@ package ghidra.app.plugin.core.debug.service.modules; import java.io.File; import java.util.*; +import java.util.concurrent.*; +import java.util.function.Consumer; import java.util.stream.Collectors; import com.google.gson.Gson; @@ -31,6 +33,11 @@ import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; import ghidra.trace.model.Lifespan; import ghidra.trace.model.modules.TraceModule; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.CancelledListener; +import ghidra.util.task.Task; +import ghidra.util.task.TaskMonitor; // TODO: Consider making this a front-end plugin? public class ProgramModuleIndexer implements DomainFolderChangeListener { @@ -177,29 +184,103 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener { private final Project project; private final ProjectData projectData; private volatile boolean disposed; + private final CompletableFuture initialIndexFuture = new CompletableFuture<>(); private final Map openedForUpdate = new HashMap<>(); + private final Object indexLock = new Object(); private final ModuleIndex index = new ModuleIndex(); public ProgramModuleIndexer(PluginTool tool) { + this(tool, tool::execute); + } + + public ProgramModuleIndexer(PluginTool tool, Consumer taskExecutor) { this.project = tool.getProject(); this.projectData = tool.getProject().getProjectData(); this.projectData.addDomainFolderChangeListener(this); - indexFolder(projectData.getRootFolder()); + startInitialIndex(taskExecutor); } void dispose() { disposed = true; projectData.removeDomainFolderChangeListener(this); + initialIndexFuture.cancel(false); + synchronized (openedForUpdate) { + for (ModuleChangeListener listener : openedForUpdate.values()) { + listener.dispose(); + } + openedForUpdate.clear(); + } } - protected void indexFolder(DomainFolder folder) { - for (DomainFile file : folder.getFiles()) { - addToIndex(file); + protected void startInitialIndex(Consumer taskExecutor) { + taskExecutor.accept(new Task("Index Program Modules", true, true, false) { + @Override + public void run(TaskMonitor monitor) throws CancelledException { + try { + indexProject(monitor); + initialIndexFuture.complete(null); + } + catch (CancelledException e) { + initialIndexFuture.cancel(false); + throw e; + } + catch (Throwable e) { + initialIndexFuture.completeExceptionally(e); + Msg.error(this, "Failed to index program modules", e); + } + } + }); + } + + protected void indexProject(TaskMonitor monitor) throws CancelledException { + int fileCount = projectData.getFileCount(); + if (fileCount < 0) { + monitor.setIndeterminate(true); } - for (DomainFolder sub : folder.getFolders()) { - indexFolder(sub); + else { + monitor.initialize(fileCount); + } + + for (DomainFile file : ProjectDataUtils.descendantFiles(projectData.getRootFolder())) { + if (disposed) { + throw new CancelledException(); + } + monitor.checkCancelled(); + monitor.setMessage("Indexing " + file.getPathname()); + addToIndex(file); + monitor.incrementProgress(1); + } + } + + public void waitForInitialIndex(TaskMonitor monitor) throws CancelledException { + monitor = TaskMonitor.dummyIfNull(monitor); + monitor.setMessage("Waiting for program module index"); + CompletableFuture cancelled = new CompletableFuture<>(); + CancelledListener listener = () -> cancelled.cancel(false); + monitor.addCancelledListener(listener); + try { + monitor.checkCancelled(); + CompletableFuture.anyOf(cancelled, initialIndexFuture).get(); + initialIndexFuture.get(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new CancelledException(); + } + catch (CancellationException e) { + throw new CancelledException(); + } + catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof CancellationException) { + throw new CancelledException(); + } + Msg.error(this, "Failed to index program modules", cause); + } + finally { + monitor.removeCancelledListener(listener); } } @@ -237,28 +318,32 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener { } String exeName = exePath == null ? null : new File(exePath).getName(); - for (String modPath : getModulePaths(metadata)) { - String modName = new File(modPath).getName(); - if (!modPath.equals(modName)) { - index.addEntry(modPath, dfID, NameSource.MODULE_PATH); + synchronized (indexLock) { + for (String modPath : getModulePaths(metadata)) { + String modName = new File(modPath).getName(); + if (!modPath.equals(modName)) { + index.addEntry(modPath, dfID, NameSource.MODULE_PATH); + } + index.addEntry(modName, dfID, NameSource.MODULE_NAME); } - index.addEntry(modName, dfID, NameSource.MODULE_NAME); - } - index.addEntry(dfName, dfID, NameSource.DOMAIN_FILE_NAME); - if (progName != null) { - index.addEntry(progName, dfID, NameSource.DOMAIN_FILE_NAME); - } - if (exeName != null) { - if (!exePath.equals(exeName)) { - index.addEntry(exePath, dfID, NameSource.PROGRAM_EXECUTABLE_PATH); + index.addEntry(dfName, dfID, NameSource.DOMAIN_FILE_NAME); + if (progName != null) { + index.addEntry(progName, dfID, NameSource.DOMAIN_FILE_NAME); + } + if (exeName != null) { + if (!exePath.equals(exeName)) { + index.addEntry(exePath, dfID, NameSource.PROGRAM_EXECUTABLE_PATH); + } + index.addEntry(exeName, dfID, NameSource.PROGRAM_EXECUTABLE_NAME); } - index.addEntry(exeName, dfID, NameSource.PROGRAM_EXECUTABLE_NAME); } } protected void removeFromIndex(String fileID) { - index.removeFile(fileID); + synchronized (indexLock) { + index.removeFile(fileID); + } } protected void refreshIndex(DomainFile file) { @@ -396,13 +481,15 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener { public List getBestEntries(TraceModule module, long snap) { String modulePathName = module.getName(snap).toLowerCase(); - List entries = new ArrayList<>(index.getByName(modulePathName)); - if (!entries.isEmpty()) { + synchronized (indexLock) { + List entries = new ArrayList<>(index.getByName(modulePathName)); + if (!entries.isEmpty()) { + return entries; + } + String moduleFileName = new File(modulePathName).getName(); + entries.addAll(index.getByName(moduleFileName)); return entries; } - String moduleFileName = new File(modulePathName).getName(); - entries.addAll(index.getByName(moduleFileName)); - return entries; } public DomainFile getBestMatch(AddressSpace space, TraceModule module, long snap,