GP-2727: Fix module patern for gdb-11 and later

This commit is contained in:
Dan 2022-10-19 14:50:50 -04:00
parent 89f5e5d4e9
commit 83193d4aff
3 changed files with 164 additions and 103 deletions

View file

@ -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 {
"(?<flags>[rwsxp\\-]+)\\s+" +
"(?<file>\\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<String, GdbModuleImpl> modules = new LinkedHashMap<>();
private final Map<String, GdbModule> unmodifiableModules = Collections.unmodifiableMap(modules);
// Because asking GDB to list sections lists those of all modules
protected final AsyncLazyValue<Void> loadSections = new AsyncLazyValue<>(this::doLoadSections);
private final NavigableMap<BigInteger, GdbMemoryMapping> mappings = new TreeMap<>();
private final NavigableMap<BigInteger, GdbMemoryMapping> unmodifiableMappings =
Collections.unmodifiableNavigableMap(mappings);
@ -242,34 +237,14 @@ public class GdbInferiorImpl implements GdbInferior {
@Override
public CompletableFuture<Map<String, GdbModule>> 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<String> 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<Void> loadSections() {
return loadSections.request();
}
protected CompletableFuture<Void> doLoadSections() {
CompletableFuture<String> 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<String> 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<String, GdbModule> parseModuleNames(String out, boolean v11) {
protected Map<String, GdbModule> parseModuleNames(String[] lines) {
Set<String> 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);

View file

@ -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 =
"<html><p>Please enter:</p>" +
"<pre>new-ui mi2 <b>{0}</b></pre>" + "" +
@ -1773,4 +1780,113 @@ public class GdbManagerImpl implements GdbManager {
public Interpreter getRunningInterpreter() {
return runningInterpreter;
}
private CompletableFuture<Map.Entry<String, String[]>> nextMaintInfoSections(
GdbInferiorImpl inferior, String cmds[], List<String[]> 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
*
* <p>
* 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<String[]> execMaintInfoSectionsAllObjects(
GdbInferiorImpl inferior) {
// TODO: Would be nice to choose based on version
CompletableFuture<String> futureOut =
inferior.consoleCapture(maintInfoSectionsCmd, CompletesWithRunning.CANNOT);
return futureOut.thenCompose(out -> {
String[] lines = out.split("\n");
if (lines.length >= 10) {
return CompletableFuture.completedFuture(lines);
}
CompletableFuture<Entry<String, String[]>> 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
*
* <p>
* 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
*
* <p>
* 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;
}
}

View file

@ -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: (?<name>.*)");
protected static final Pattern V11_FILE_LINE_PATTERN =
Pattern.compile("\\s*(Object)|(Exec) file: `(?<name>.*)', file type (?<type>.*)");
protected static final Pattern OBJECT_FILE_LINE_PATTERN_V11 =
Pattern.compile("\\s*((Object)|(Exec)) file: `(?<name>.*)', file type (?<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 {
"(?<name>\\S+)\\s+" + //
"(?<attrs>.*)");
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*(?<idx>\\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<String, GdbModuleSectionImpl> sections = new LinkedHashMap<>();
protected final Map<String, GdbModuleSection> unmodifiableSections =
Collections.unmodifiableMap(sections);
@ -88,24 +105,7 @@ public class GdbModuleImpl implements GdbModule {
}
protected CompletableFuture<Void> 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<Map<String, GdbModuleSection>> 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);