diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbInferiorImpl.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbInferiorImpl.java index c9460a7852..a5ccc3798f 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbInferiorImpl.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbInferiorImpl.java @@ -31,8 +31,6 @@ import agent.gdb.manager.GdbCause.Causes; import agent.gdb.manager.GdbManager.StepCmd; import agent.gdb.manager.impl.cmd.*; import agent.gdb.manager.impl.cmd.GdbConsoleExecCommand.CompletesWithRunning; -import ghidra.async.AsyncLazyValue; -import ghidra.async.AsyncUtils; import ghidra.lifecycle.Internal; import ghidra.util.Msg; @@ -57,7 +55,7 @@ public class GdbInferiorImpl implements GdbInferior { "(?[rwsxp\\-]+)\\s+" + "(?\\S*)\\s*"); - private final GdbManagerImpl manager; + protected final GdbManagerImpl manager; private final int id; private Long pid; // Not always present @@ -73,9 +71,6 @@ public class GdbInferiorImpl implements GdbInferior { private final Map modules = new LinkedHashMap<>(); private final Map unmodifiableModules = Collections.unmodifiableMap(modules); - // Because asking GDB to list sections lists those of all modules - protected final AsyncLazyValue loadSections = new AsyncLazyValue<>(this::doLoadSections); - private final NavigableMap mappings = new TreeMap<>(); private final NavigableMap unmodifiableMappings = Collections.unmodifiableNavigableMap(mappings); @@ -242,34 +237,14 @@ public class GdbInferiorImpl implements GdbInferior { @Override public CompletableFuture> listModules() { - // "nosections" is an unlikely section name. Goal is to exclude section lines. - // TODO: Would be nice to save this switch, or better, choose at start based on version - CompletableFuture future = - consoleCapture("maintenance info sections ALLOBJ", - CompletesWithRunning.CANNOT); - return future.thenCompose(output -> { - if (output.split("\n").length <= 1) { - return consoleCapture("maintenance info sections -all-objects") - .thenApply(out2 -> parseModuleNames(out2, true)); - } - return CompletableFuture.completedFuture(parseModuleNames(output, false)); + return manager.execMaintInfoSectionsAllObjects(this).thenApply(lines -> { + return parseModuleNames(lines); }); } - protected CompletableFuture loadSections() { - return loadSections.request(); - } - protected CompletableFuture doLoadSections() { - CompletableFuture future = - consoleCapture("maintenance info sections ALLOBJ", CompletesWithRunning.CANNOT); - return future.thenCompose(output -> { - if (output.split("\n").length <= 1) { - return consoleCapture("maintenance info sections -all-objects") - .thenAccept(out2 -> parseAndUpdateAllModuleSections(out2, true)); - } - parseAndUpdateAllModuleSections(output, false); - return AsyncUtils.NIL; + return manager.execMaintInfoSectionsAllObjects(this).thenAccept(lines -> { + parseAndUpdateAllModuleSections(lines); }); } @@ -303,32 +278,23 @@ public class GdbInferiorImpl implements GdbInferior { } } - protected String nameFromLine(String line, boolean v11) { - if (v11) { - Matcher nameMatcher = GdbModuleImpl.V11_FILE_LINE_PATTERN.matcher(line); - if (!nameMatcher.matches()) { - return null; - } - String name = nameMatcher.group("name"); - if (name.startsWith(GdbModuleImpl.GNU_DEBUGDATA_PREFIX)) { - return null; - } - return name; + protected String nameFromLine(String line) { + Matcher nameMatcher = manager.matchFileLine(line); + if (nameMatcher == null) { + return null; } - else { - Matcher nameMatcher = GdbModuleImpl.OBJECT_FILE_LINE_PATTERN.matcher(line); - if (!nameMatcher.matches()) { - return null; - } - return nameMatcher.group("name"); + String name = nameMatcher.group("name"); + if (name.startsWith(GdbModuleImpl.GNU_DEBUGDATA_PREFIX)) { + return null; } + return name; } - protected void parseAndUpdateAllModuleSections(String out, boolean v11) { + protected void parseAndUpdateAllModuleSections(String[] lines) { Set namesSeen = new HashSet<>(); GdbModuleImpl curModule = null; - for (String line : out.split("\n")) { - String name = nameFromLine(line, v11); + for (String line : lines) { + String name = nameFromLine(line); if (name != null) { if (curModule != null) { curModule.loadSections.provide().complete(null); @@ -353,10 +319,10 @@ public class GdbInferiorImpl implements GdbInferior { resyncRetainModules(namesSeen); } - protected Map parseModuleNames(String out, boolean v11) { + protected Map parseModuleNames(String[] lines) { Set namesSeen = new HashSet<>(); - for (String line : out.split("\n")) { - String name = nameFromLine(line, v11); + for (String line : lines) { + String name = nameFromLine(line); if (name != null) { namesSeen.add(name); modules.computeIfAbsent(name, this::resyncCreateModule); diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbManagerImpl.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbManagerImpl.java index 9e4584f5d3..e98dcb79e9 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbManagerImpl.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbManagerImpl.java @@ -18,9 +18,12 @@ package agent.gdb.manager.impl; import java.io.*; import java.text.MessageFormat; import java.util.*; +import java.util.Map.Entry; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.swing.JDialog; import javax.swing.JOptionPane; @@ -74,6 +77,10 @@ public class GdbManagerImpl implements GdbManager { private static final String GDB_IS_TERMINATING = "GDB is terminating"; public static final int MAX_CMD_LEN = 4094; // Account for longest possible line end + private String maintInfoSectionsCmd = GdbModuleImpl.MAINT_INFO_SECTIONS_CMD_V11; + private Pattern fileLinePattern = GdbModuleImpl.OBJECT_FILE_LINE_PATTERN_V11; + private Pattern sectionLinePattern = GdbModuleImpl.OBJECT_SECTION_LINE_PATTERN_V10; + private static final String PTY_DIALOG_MESSAGE_PATTERN = "

Please enter:

" + "
new-ui mi2 {0}
" + "" + @@ -1773,4 +1780,113 @@ public class GdbManagerImpl implements GdbManager { public Interpreter getRunningInterpreter() { return runningInterpreter; } + + private CompletableFuture> nextMaintInfoSections( + GdbInferiorImpl inferior, String cmds[], List results) { + if (results.size() == cmds.length) { + int best = 0; + for (int i = 0; i < cmds.length; i++) { + if (results.get(i).length > results.get(best).length) { + best = i; + } + } + return CompletableFuture.completedFuture(Map.entry(cmds[best], results.get(best))); + } + String cmd = cmds[results.size()]; + return inferior.consoleCapture(cmd, CompletesWithRunning.CANNOT).thenCompose(out -> { + String[] lines = out.split("\n"); + if (lines.length >= 10) { + return CompletableFuture.completedFuture(Map.entry(cmd, lines)); + } + results.add(lines); + return nextMaintInfoSections(inferior, cmds, results); + }); + } + + /** + * Execute "maintenance info sections" for all objects, starting with the syntax that last + * worked best + * + *

+ * If any syntax yields at least 10 lines of output, then it is taken immediately, and the "last + * best" syntax is updated. In most cases, the first execution is the only time this will need + * to try more than once, since the underlying version should not change during the manager's + * lifetime. If none give more than 10, then the one which yielded the most lines is selected. + * This could happen in vacuous cases, e.g., if modules are requested without a target file. + * + * @param inferior the inferior for context when executing the command + * @return a future which completes with the list of lines + */ + protected CompletableFuture execMaintInfoSectionsAllObjects( + GdbInferiorImpl inferior) { + // TODO: Would be nice to choose based on version + CompletableFuture futureOut = + inferior.consoleCapture(maintInfoSectionsCmd, CompletesWithRunning.CANNOT); + return futureOut.thenCompose(out -> { + String[] lines = out.split("\n"); + if (lines.length >= 10) { + return CompletableFuture.completedFuture(lines); + } + CompletableFuture> futureBest = nextMaintInfoSections(inferior, + GdbModuleImpl.MAINT_INFO_SECTIONS_CMDS, new ArrayList<>()); + return futureBest.thenApply(best -> { + maintInfoSectionsCmd = best.getKey(); + return best.getValue(); + }); + }); + } + + /** + * Match a module file line, starting with the last working pattern + * + *

+ * In most cases, only the first attempt to parse causes an update to the "last working" + * pattern, since the underlying GDB version should not change during the lifetime of the + * manager. + * + * @param line the line to parse + * @return the matcher or null + */ + protected Matcher matchFileLine(String line) { + Matcher matcher; + matcher = fileLinePattern.matcher(line); + if (matcher.matches()) { + return matcher; + } + for (Pattern pattern : GdbModuleImpl.OBJECT_FILE_LINE_PATTERNS) { + matcher = pattern.matcher(line); + if (matcher.matches()) { + fileLinePattern = pattern; + return matcher; + } + } + return null; + } + + /** + * Match a module section line, starting with the last working pattern + * + *

+ * In most cases, only the first attempt to parse causes an update to the "last working" + * pattern, since the underlying GDB version should not change during the lifetime of the + * manager. + * + * @param line the line to parse + * @return the matcher or null + */ + protected Matcher matchSectionLine(String line) { + Matcher matcher; + matcher = sectionLinePattern.matcher(line); + if (matcher.matches()) { + return matcher; + } + for (Pattern pattern : GdbModuleImpl.OBJECT_SECTION_LINE_PATTERNS) { + matcher = pattern.matcher(line); + if (matcher.matches()) { + sectionLinePattern = pattern; + return matcher; + } + } + return null; + } } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbModuleImpl.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbModuleImpl.java index 9a2be0635e..ccce5162a7 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbModuleImpl.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbModuleImpl.java @@ -24,15 +24,29 @@ import agent.gdb.manager.GdbModule; import agent.gdb.manager.GdbModuleSection; import agent.gdb.manager.impl.cmd.GdbConsoleExecCommand.CompletesWithRunning; import ghidra.async.AsyncLazyValue; -import ghidra.async.AsyncUtils; import ghidra.util.MathUtilities; import ghidra.util.Msg; public class GdbModuleImpl implements GdbModule { - protected static final Pattern OBJECT_FILE_LINE_PATTERN = + protected static final String MAINT_INFO_SECTIONS_CMD_V8 = + "maintenance info sections ALLOBJ"; + protected static final String MAINT_INFO_SECTIONS_CMD_V11 = + "maintenance info sections -all-objects"; + protected static final String[] MAINT_INFO_SECTIONS_CMDS = new String[] { + MAINT_INFO_SECTIONS_CMD_V11, + MAINT_INFO_SECTIONS_CMD_V8, + }; + + protected static final Pattern OBJECT_FILE_LINE_PATTERN_V8 = Pattern.compile("\\s*Object file: (?.*)"); - protected static final Pattern V11_FILE_LINE_PATTERN = - Pattern.compile("\\s*(Object)|(Exec) file: `(?.*)', file type (?.*)"); + protected static final Pattern OBJECT_FILE_LINE_PATTERN_V11 = + Pattern.compile("\\s*((Object)|(Exec)) file: `(?.*)', file type (?.*)"); + + protected static final Pattern[] OBJECT_FILE_LINE_PATTERNS = new Pattern[] { + OBJECT_FILE_LINE_PATTERN_V11, + OBJECT_FILE_LINE_PATTERN_V8, + }; + protected static final String GNU_DEBUGDATA_PREFIX = ".gnu_debugdata for "; // Pattern observed in GDB 8 (probably applies to previous, too) @@ -54,6 +68,11 @@ public class GdbModuleImpl implements GdbModule { "(?\\S+)\\s+" + // "(?.*)"); + protected static final Pattern[] OBJECT_SECTION_LINE_PATTERNS = new Pattern[] { + OBJECT_SECTION_LINE_PATTERN_V10, + OBJECT_SECTION_LINE_PATTERN_V8, + }; + protected static final Pattern MSYMBOL_LINE_PATTERN = Pattern.compile( "\\s*" + // "\\[\\s*(?\\d+)\\]\\s+" + // @@ -67,8 +86,6 @@ public class GdbModuleImpl implements GdbModule { protected Long base = null; protected Long max = null; - protected Pattern sectionLinePattern = OBJECT_SECTION_LINE_PATTERN_V10; - protected final Map sections = new LinkedHashMap<>(); protected final Map unmodifiableSections = Collections.unmodifiableMap(sections); @@ -88,24 +105,7 @@ public class GdbModuleImpl implements GdbModule { } protected CompletableFuture doLoadSections() { - return inferior.loadSections().thenCompose(__ -> { - if (!loadSections.isDone()) { - /** - * The inferior's load sections should have provided the value out of band before it - * is completed from the request that got us invoked. If it didn't it's because the - * response to the load in progress did not include this module. We should only have - * to force it at most once more. - */ - inferior.loadSections.forget(); - return inferior.loadSections(); - } - return AsyncUtils.NIL; - }).thenAccept(__ -> { - if (!loadSections.isDone()) { - Msg.warn(this, - "Module's sections still not known: " + name + ". Probably got unloaded."); - } - }); + return inferior.doLoadSections(); } @Override @@ -130,6 +130,9 @@ public class GdbModuleImpl implements GdbModule { @Override public CompletableFuture> listSections() { + if (sections.isEmpty() && loadSections.isDone()) { + loadSections.forget(); + } return loadSections.request().thenApply(__ -> unmodifiableSections); } @@ -165,33 +168,9 @@ public class GdbModuleImpl implements GdbModule { return minimalSymbols.request(); } - protected Matcher matchSectionLine(Pattern pattern, String line) { - Matcher matcher = pattern.matcher(line); - if (matcher.matches()) { - sectionLinePattern = pattern; - } - return matcher; - } - - protected Matcher matchSectionLine(String line) { - Matcher matcher = sectionLinePattern.matcher(line); - if (matcher.matches()) { - return matcher; - } - matcher = matchSectionLine(OBJECT_SECTION_LINE_PATTERN_V10, line); - if (matcher.matches()) { - return matcher; - } - matcher = matchSectionLine(OBJECT_SECTION_LINE_PATTERN_V8, line); - if (matcher.matches()) { - return matcher; - } - return matcher; - } - protected void processSectionLine(String line) { - Matcher matcher = matchSectionLine(line); - if (matcher.matches()) { + Matcher matcher = inferior.manager.matchSectionLine(line); + if (matcher != null) { try { long vmaStart = Long.parseLong(matcher.group("vmaS"), 16); long vmaEnd = Long.parseLong(matcher.group("vmaE"), 16);