mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-10-03 00:44:52 +00:00
GP-2727: Fix module patern for gdb-11 and later
This commit is contained in:
parent
89f5e5d4e9
commit
83193d4aff
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue