From 8b3f26e0daa09c7843a5ea619c5133c08f8bf083 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Fri, 14 Jan 2022 13:37:30 -0500 Subject: [PATCH] GP-1666: Fix module/section list with GDB 11. --- .../gdb/manager/impl/GdbInferiorImpl.java | 65 ++++++++++++++----- .../agent/gdb/manager/impl/GdbModuleImpl.java | 3 + .../manager/impl/AbstractGdbManagerTest.java | 41 +++++++++++- .../manager/impl/JoinedGdbManagerTest.java | 10 +-- .../impl/SpawnedCliGdbManagerTest.java | 10 +-- .../SpawnedMi2Gdb7Dot6Dot1ManagerTest.java | 16 ++--- .../SpawnedMi2GdbHomeLocalManagerTest.java | 49 ++++++++++++++ ...va => SpawnedMi2GdbSystemManagerTest.java} | 14 +--- 8 files changed, 153 insertions(+), 55 deletions(-) create mode 100644 Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2GdbHomeLocalManagerTest.java rename Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/{SpawnedMi2GdbManagerTest2.java => SpawnedMi2GdbSystemManagerTest.java} (71%) 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 221418e055..114b61c37d 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 @@ -32,6 +32,7 @@ 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; @@ -90,7 +91,7 @@ public class GdbInferiorImpl implements GdbInferior { this.pid = g.getPid(); this.exitCode = g.getExitCode(); this.executable = g.getExecutable(); - + // Because we're only called to resync, we should synth started, if needed if (oldPid == null && pid != null) { manager.fireInferiorStarted(this, Causes.UNCLAIMED, "resyncInferiorStarted"); @@ -232,10 +233,17 @@ public class GdbInferiorImpl implements GdbInferior { @Override public CompletableFuture> listModules() { // "nosections" is an unlikely section name. Goal is to exclude section lines. - // TODO: See how this behaves on other GDB versions. - return consoleCapture("maintenance info sections ALLOBJ nosections", - CompletesWithRunning.CANNOT) - .thenApply(this::parseModuleNames); + // TODO: Would be nice to save this switch, or better, choose at start based on version + CompletableFuture future = + consoleCapture("maintenance info sections ALLOBJ nosections", + CompletesWithRunning.CANNOT); + return future.thenCompose(output -> { + if (output.split("\n").length <= 1) { + return consoleCapture("maintenance info sections -all-objects nosections") + .thenApply(out2 -> parseModuleNames(out2, true)); + } + return CompletableFuture.completedFuture(parseModuleNames(output, false)); + }); } protected CompletableFuture loadSections() { @@ -243,8 +251,16 @@ public class GdbInferiorImpl implements GdbInferior { } protected CompletableFuture doLoadSections() { - return consoleCapture("maintenance info sections ALLOBJ", CompletesWithRunning.CANNOT) - .thenAccept(this::parseAndUpdateAllModuleSections); + 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; + }); } protected GdbModuleImpl resyncCreateModule(String name) { @@ -277,16 +293,36 @@ public class GdbInferiorImpl implements GdbInferior { } } - protected void parseAndUpdateAllModuleSections(String out) { + 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; + } + else { + Matcher nameMatcher = GdbModuleImpl.OBJECT_FILE_LINE_PATTERN.matcher(line); + if (!nameMatcher.matches()) { + return null; + } + return nameMatcher.group("name"); + } + } + + protected void parseAndUpdateAllModuleSections(String out, boolean v11) { Set namesSeen = new HashSet<>(); GdbModuleImpl curModule = null; for (String line : out.split("\n")) { - Matcher nameMatcher = GdbModuleImpl.OBJECT_FILE_LINE_PATTERN.matcher(line); - if (nameMatcher.matches()) { + String name = nameFromLine(line, v11); + if (name != null) { if (curModule != null) { curModule.loadSections.provide().complete(null); } - String name = nameMatcher.group("name"); namesSeen.add(name); curModule = modules.computeIfAbsent(name, this::resyncCreateModule); // NOTE: This will usurp the module's lazy loader, but we're about to @@ -307,12 +343,11 @@ public class GdbInferiorImpl implements GdbInferior { resyncRetainModules(namesSeen); } - protected Map parseModuleNames(String out) { + protected Map parseModuleNames(String out, boolean v11) { Set namesSeen = new HashSet<>(); for (String line : out.split("\n")) { - Matcher nameMatcher = GdbModuleImpl.OBJECT_FILE_LINE_PATTERN.matcher(line); - if (nameMatcher.matches()) { - String name = nameMatcher.group("name"); + String name = nameFromLine(line, v11); + 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/GdbModuleImpl.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/impl/GdbModuleImpl.java index 1a46e062f1..9a2be0635e 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 @@ -31,6 +31,9 @@ import ghidra.util.Msg; public class GdbModuleImpl implements GdbModule { protected static final Pattern OBJECT_FILE_LINE_PATTERN = Pattern.compile("\\s*Object file: (?.*)"); + protected static final Pattern V11_FILE_LINE_PATTERN = + Pattern.compile("\\s*(Object)|(Exec) file: `(?.*)', file type (?.*)"); + protected static final String GNU_DEBUGDATA_PREFIX = ".gnu_debugdata for "; // Pattern observed in GDB 8 (probably applies to previous, too) protected static final Pattern OBJECT_SECTION_LINE_PATTERN_V8 = Pattern.compile( diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/AbstractGdbManagerTest.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/AbstractGdbManagerTest.java index 9d05ca3c32..9266eccc1e 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/AbstractGdbManagerTest.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/AbstractGdbManagerTest.java @@ -18,7 +18,9 @@ package agent.gdb.manager.impl; import static ghidra.dbg.testutil.DummyProc.run; import static org.junit.Assert.*; import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; +import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; @@ -36,6 +38,7 @@ import agent.gdb.manager.*; import agent.gdb.manager.GdbManager.StepCmd; import agent.gdb.manager.breakpoint.GdbBreakpointInfo; import agent.gdb.pty.PtyFactory; +import agent.gdb.pty.linux.LinuxPtyFactory; import ghidra.async.AsyncReference; import ghidra.dbg.testutil.DummyProc; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; @@ -46,7 +49,22 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg protected static final long TIMEOUT_MILLISECONDS = SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE; - protected abstract PtyFactory getPtyFactory(); + protected File gdbBin; + + protected File findGdbBin() { + return new File(GdbManager.DEFAULT_GDB_CMD); + } + + @Before + public void findAndCheckGdb() { + gdbBin = findGdbBin(); + assumeTrue(gdbBin.exists()); + } + + protected PtyFactory getPtyFactory() { + // TODO: Choose by host OS + return new LinuxPtyFactory(); + } protected abstract CompletableFuture startManager(GdbManager manager); @@ -143,6 +161,21 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg } } + @Test + public void testListModulesAndSections() throws Throwable { + try (GdbManager mgr = GdbManager.newInstance(getPtyFactory())) { + waitOn(startManager(mgr)); + GdbInferior inferior = mgr.currentInferior(); + waitOn(inferior.fileExecAndSymbols("/usr/bin/echo")); + Map modules = waitOn(inferior.listModules()); + GdbModule modEcho = modules.get("/usr/bin/echo"); + assertNotNull(modEcho); + Map sectionsEcho = waitOn(modEcho.listSections()); + GdbModuleSection secEchoText = sectionsEcho.get(".text"); + assertNotNull(secEchoText); + } + } + @Test public void testInfoOs() throws Throwable { try (GdbManager mgr = GdbManager.newInstance(getPtyFactory())) { @@ -284,12 +317,16 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg } } + protected String getExpectedDefaultArgsVar() { + return null; + } + @Test public void testSetVarGetVar() throws Throwable { try (GdbManager mgr = GdbManager.newInstance(getPtyFactory())) { waitOn(startManager(mgr)); String val = waitOn(mgr.currentInferior().getVar("args")); - assertEquals(null, val); + assertEquals(getExpectedDefaultArgsVar(), val); waitOn(mgr.currentInferior().setVar("args", "test")); val = waitOn(mgr.currentInferior().getVar("args")); assertEquals("test", val); diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/JoinedGdbManagerTest.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/JoinedGdbManagerTest.java index 2530118336..afe8b05f27 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/JoinedGdbManagerTest.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/JoinedGdbManagerTest.java @@ -22,10 +22,8 @@ import java.util.concurrent.CompletableFuture; import org.junit.Ignore; import agent.gdb.manager.GdbManager; -import agent.gdb.pty.PtyFactory; import agent.gdb.pty.PtySession; import agent.gdb.pty.linux.LinuxPty; -import agent.gdb.pty.linux.LinuxPtyFactory; import ghidra.util.Msg; @Ignore("Need compatible GDB version for CI") @@ -50,12 +48,6 @@ public class JoinedGdbManagerTest extends AbstractGdbManagerTest { protected LinuxPty ptyUserGdb; protected PtySession gdb; - @Override - protected PtyFactory getPtyFactory() { - // TODO: Choose by host OS - return new LinuxPtyFactory(); - } - @Override protected CompletableFuture startManager(GdbManager manager) { try { @@ -64,7 +56,7 @@ public class JoinedGdbManagerTest extends AbstractGdbManagerTest { Msg.debug(this, "Starting GDB and invoking new-ui mi2 " + manager.getMi2PtyName()); gdb = ptyUserGdb.getChild() - .session(new String[] { GdbManager.DEFAULT_GDB_CMD }, Map.of()); + .session(new String[] { gdbBin.getAbsolutePath() }, Map.of()); new ReaderThread().start(); PrintWriter gdbCmd = new PrintWriter(ptyUserGdb.getParent().getOutputStream()); gdbCmd.println("new-ui mi2 " + manager.getMi2PtyName()); diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedCliGdbManagerTest.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedCliGdbManagerTest.java index 5fd1fe0401..5e8c43b73d 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedCliGdbManagerTest.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedCliGdbManagerTest.java @@ -21,25 +21,17 @@ import java.util.concurrent.CompletableFuture; import org.junit.Ignore; import agent.gdb.manager.GdbManager; -import agent.gdb.pty.PtyFactory; -import agent.gdb.pty.linux.LinuxPtyFactory; @Ignore("Need compatible GDB version for CI") public class SpawnedCliGdbManagerTest extends AbstractGdbManagerTest { @Override protected CompletableFuture startManager(GdbManager manager) { try { - manager.start(); + manager.start(gdbBin.getAbsolutePath()); return manager.runRC(); } catch (IOException e) { throw new AssertionError(e); } } - - @Override - protected PtyFactory getPtyFactory() { - // TODO: Choose by host OS - return new LinuxPtyFactory(); - } } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2Gdb7Dot6Dot1ManagerTest.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2Gdb7Dot6Dot1ManagerTest.java index 70eacd6bd4..27e287b3dc 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2Gdb7Dot6Dot1ManagerTest.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2Gdb7Dot6Dot1ManagerTest.java @@ -15,31 +15,29 @@ */ package agent.gdb.manager.impl; +import java.io.File; import java.io.IOException; import java.util.concurrent.CompletableFuture; import org.junit.Ignore; import agent.gdb.manager.GdbManager; -import agent.gdb.pty.PtyFactory; -import agent.gdb.pty.linux.LinuxPtyFactory; @Ignore("Need to install GDB 7.6.1 to the expected directory on CI") public class SpawnedMi2Gdb7Dot6Dot1ManagerTest extends AbstractGdbManagerTest { + @Override + protected File findGdbBin() { + return new File("/opt/gdb-7.6.1/bin/gdb"); + } + @Override protected CompletableFuture startManager(GdbManager manager) { try { - manager.start("/opt/gdb-7.6.1/bin/gdb", "-i", "mi2"); + manager.start(gdbBin.getAbsolutePath(), "-i", "mi2"); return manager.runRC(); } catch (IOException e) { throw new AssertionError(e); } } - - @Override - protected PtyFactory getPtyFactory() { - // TODO: Choose by host OS - return new LinuxPtyFactory(); - } } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2GdbHomeLocalManagerTest.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2GdbHomeLocalManagerTest.java new file mode 100644 index 0000000000..558cb63503 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2GdbHomeLocalManagerTest.java @@ -0,0 +1,49 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package agent.gdb.manager.impl; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +import org.junit.Ignore; + +import agent.gdb.manager.GdbManager; + +@Ignore("Does not exist on CI") +public class SpawnedMi2GdbHomeLocalManagerTest extends AbstractGdbManagerTest { + @Override + protected File findGdbBin() { + String home = System.getProperty("user.home"); + return new File(home, "local/bin/gdb"); + } + + @Override + protected CompletableFuture startManager(GdbManager manager) { + try { + manager.start(gdbBin.getAbsolutePath(), "-i", "mi2"); + return manager.runRC(); + } + catch (IOException e) { + throw new AssertionError(e); + } + } + + @Override + protected String getExpectedDefaultArgsVar() { + return ""; + } +} diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2GdbManagerTest2.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2GdbSystemManagerTest.java similarity index 71% rename from Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2GdbManagerTest2.java rename to Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2GdbSystemManagerTest.java index a62411aa28..b593e4b89c 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2GdbManagerTest2.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2GdbSystemManagerTest.java @@ -21,25 +21,17 @@ import java.util.concurrent.CompletableFuture; import org.junit.Ignore; import agent.gdb.manager.GdbManager; -import agent.gdb.pty.PtyFactory; -import agent.gdb.pty.linux.LinuxPtyFactory; -@Ignore("Need compatible GDB version for CI") -public class SpawnedMi2GdbManagerTest2 extends AbstractGdbManagerTest { +@Ignore("Need compatible version install on CI") +public class SpawnedMi2GdbSystemManagerTest extends AbstractGdbManagerTest { @Override protected CompletableFuture startManager(GdbManager manager) { try { - manager.start(GdbManager.DEFAULT_GDB_CMD, "-i", "mi2"); + manager.start(gdbBin.getAbsolutePath(), "-i", "mi2"); return manager.runRC(); } catch (IOException e) { throw new AssertionError(e); } } - - @Override - protected PtyFactory getPtyFactory() { - // TODO: Choose by host OS - return new LinuxPtyFactory(); - } }