diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/gadp/GdbLocalDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/gadp/GdbLocalDebuggerModelFactory.java index 851027f239..34f2d4be01 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/gadp/GdbLocalDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/gadp/GdbLocalDebuggerModelFactory.java @@ -22,6 +22,7 @@ import java.util.List; import agent.gdb.manager.GdbManager; import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; +import ghidra.dbg.util.ShellUtils; import ghidra.util.classfinder.ExtensionPointProperties; @FactoryDescription( // @@ -31,8 +32,12 @@ import ghidra.util.classfinder.ExtensionPointProperties; @ExtensionPointProperties(priority = 100) public class GdbLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory { public static boolean checkGdbPresent(String gdbCmd) { + List args = ShellUtils.parseArgs(gdbCmd); + if (args.isEmpty()) { + return false; + } try { - ProcessBuilder builder = new ProcessBuilder(gdbCmd, "--version"); + ProcessBuilder builder = new ProcessBuilder(args.get(0), "--version"); builder.redirectError(Redirect.INHERIT); builder.redirectOutput(Redirect.INHERIT); @SuppressWarnings("unused") @@ -92,13 +97,17 @@ public class GdbLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModel @Override protected void completeCommandLine(List cmd) { + List gdbCmdLine = ShellUtils.parseArgs(gdbCmd); cmd.add(GdbGadpServer.class.getCanonicalName()); - // TODO: Option for additional GDB command-line parameters + if (!existing && gdbCmdLine.size() >= 2) { + cmd.addAll(gdbCmdLine.subList(1, gdbCmdLine.size())); + } cmd.add("--gadp-args"); cmd.addAll(List.of("-H", host)); cmd.addAll(List.of("-p", Integer.toString(port))); // Available ephemeral port - if (!existing) { - cmd.addAll(List.of("-g", gdbCmd)); + if (!existing && gdbCmdLine.size() >= 1) { + cmd.add("-g"); + cmd.add(gdbCmdLine.get(0)); } else { cmd.add("-x"); diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/GdbManager.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/GdbManager.java index 6c8526f8d6..32f793fdba 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/GdbManager.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/GdbManager.java @@ -72,6 +72,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Just a vanilla demo of the manager * + *

* This presents the usual GDB CLI, using GDB/MI on the back end. The manager is keeps track of * events; however, in this vanilla front end, nothing consumes them. This also provides a quick * test to ensure the console loop operates correctly, or at least closely enough to actual GDB. @@ -100,8 +101,6 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { * @return the manager */ public static GdbManager newInstance() { - // TODO: Add parameter and test both? - // TODO: Eventually the 'true' variant will be deprecated return new GdbManagerImpl(); } @@ -143,6 +142,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Execute a console loop in this thread * + *

* Note this does not follow the asynchronous pattern. * * @throws IOException if an I/O error occurs @@ -157,6 +157,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Check if GDB is alive * + *

* Note this is not about the state of inferiors in GDB. If the GDB controlling process is * alive, GDB is alive. * @@ -197,6 +198,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Add a listener for target output * + *

* Note: depending on the target, its output may not be communicated via this listener. Local * targets, e.g., tend to just print output to GDB's controlling TTY. See * {@link GdbInferior#setTty(String)} for a means to more reliably interact with a target's @@ -231,6 +233,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Get a thread by its GDB-assigned ID * + *

* GDB numbers its threads using a global counter. These IDs are unrelated to the OS-assigned * TID. This method can retrieve a thread by its ID no matter which inferior it belongs to. * @@ -242,6 +245,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Get an inferior by its GDB-assigned ID * + *

* GDB numbers inferiors incrementally. All inferiors and created and destroyed by the user. See * {@link #addInferior()}. * @@ -261,6 +265,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Get all inferiors known to the manager * + *

* This does not ask GDB to list its inferiors. Rather it returns a read-only view of the * manager's understanding of the current inferiors based on its tracking of GDB events. * @@ -271,6 +276,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Get all threads known to the manager * + *

* This does not ask GDB to lists its known threads. Rather it returns a read-only view of the * manager's understanding of the current threads based on its tracking of GDB events. * @@ -281,6 +287,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Get all breakpoints known to the manager * + *

* This does not ask GDB to list its breakpoints. Rather it returns a read-only view of the * manager's understanding of the current breakpoints based on its tracking of GDB events. * @@ -291,6 +298,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Send an interrupt to GDB regardless of other queued commands * + *

* This may be useful if the manager's command queue is stalled because an inferior is running. * * @throws IOException if an I/O error occurs @@ -301,6 +309,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Get the state of the GDB session * + *

* In all-stop mode, if any thread is running, GDB is said to be in the running state and is * unable to process commands. Otherwise, if all threads are stopped, then GDB is said to be in * the stopped state and can accept and process commands. This manager has not been tested in @@ -321,6 +330,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Wait for GDB to present a prompt * + *

* This waits for a prompt from GDB unless the last line printed is already a prompt. This is * generally not necessary following normal commands, but may be necessary after interrupting a * running inferior, or after waiting for an inferior to reach a stopped state. @@ -332,6 +342,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * A dummy command which claims as cause a stopped event and waits for the next prompt * + *

* This is used to squelch normal processing of a stopped event until the next prompt * * @return a future which completes when the "command" has finished execution @@ -341,6 +352,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Add an inferior * + *

* This is equivalent to the CLI command: {@code add-inferior}. * * @return a future which completes with the handle to the new inferior @@ -350,8 +362,10 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Remove an inferior * + *

* This is equivalent to the CLI command: {@code remove-inferior}. * + *

* Note that unlike the CLI, it is possible to remove the current inferior, in which case, the * lowest-id inferior is selected. Like the CLI, it is not possible to remove an active inferior * or the last inferior. @@ -364,6 +378,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Execute an arbitrary CLI command, printing output to the CLI console * + *

* Note: to ensure a certain thread or inferior has focus for a console command, see * {@link GdbThread#console(String)} and {@link GdbInferior#console(String)}. * @@ -375,6 +390,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Execute an arbitrary CLI command, capturing its console output * + *

* The output will not be printed to the CLI console. To ensure a certain thread or inferior has * focus for a console command, see {@link GdbThread#consoleCapture(String)} and * {@link GdbInferior#consoleCapture(String)}. @@ -387,10 +403,12 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Interrupt the GDB session * + *

* This is equivalent to typing Ctrl-C in the CLI. This typically results in the target being * interrupted, either because GDB and the target have the same controlling TTY, or because GDB * will "forward" the interrupt to the target. * + *

* For whatever reason, interrupting the session does not always reliably interrupt the target. * The manager will send Ctrl-C to the pseudo-terminal up to three times, waiting about 10ms * between each, until GDB issues a stopped event and presents a new prompt. @@ -402,6 +420,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * List GDB's inferiors * + *

* This is equivalent to the CLI command: {@code inferiors}. * * @return a future that completes with a map of inferior IDs to inferior handles @@ -411,6 +430,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * List information for all breakpoints * + *

* This is equivalent to the CLI command {@code info break}. * * @return a future that completes with a list of information for all breakpoints @@ -420,6 +440,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Disable the given breakpoints * + *

* This is equivalent to the CLI command {@code disable breakpoint [NUMBER]}. * * @param numbers the GDB-assigned breakpoint numbers @@ -430,6 +451,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Enable the given breakpoints * + *

* This is equivalent to the CLI command {@code enable breakpoint [NUMBER]}. * * @param numbers the GDB-assigned breakpoint numbers @@ -440,6 +462,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Delete a breakpoint * + *

* This is equivalent to the CLI command {@code delete breakpoint [NUMBER]}. * * @param numbers the GDB-assigned breakpoint numbers @@ -457,6 +480,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions { /** * Gather information about the host OS * + *

* This is equivalent to the CLI command: {@code info os [TYPE]}. * * @param type the type of OS information to gather 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 84202a2a88..5fd9d018f9 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 @@ -27,7 +27,6 @@ import org.python.core.PyDictionary; import org.python.util.InteractiveConsole; import agent.gdb.ffi.linux.Pty; -import agent.gdb.ffi.linux.PtyMaster; import agent.gdb.manager.*; import agent.gdb.manager.GdbCause.Causes; import agent.gdb.manager.breakpoint.GdbBreakpointInfo; @@ -87,6 +86,60 @@ public class GdbManagerImpl implements GdbManager { public static final int INTERRUPT_MAX_RETRIES = 3; public static final int INTERRUPT_RETRY_PERIOD_MILLIS = 100; + class PtyThread extends Thread { + final Pty pty; + final BufferedReader reader; + final Channel channel; + + Interpreter interpreter; + PrintWriter writer; + CompletableFuture hasWriter; + + PtyThread(Pty pty, Channel channel, Interpreter interpreter) { + this.pty = pty; + this.channel = channel; + this.reader = + new BufferedReader(new InputStreamReader(pty.getMaster().getInputStream())); + this.interpreter = interpreter; + hasWriter = new CompletableFuture<>(); + } + + @Override + public void run() { + try { + String line; + while (isAlive() && null != (line = reader.readLine())) { + String l = line; + if (interpreter == null) { + if (l.startsWith("=") || l.startsWith("~")) { + interpreter = Interpreter.MI2; + } + else { + interpreter = Interpreter.CLI; + } + } + if (writer == null) { + writer = new PrintWriter(pty.getMaster().getOutputStream()); + hasWriter.complete(null); + } + //Msg.debug(this, channel + ": " + line); + submit(() -> { + if (LOG_IO) { + DBG_LOG.println("<" + interpreter + ": " + l); + DBG_LOG.flush(); + } + processLine(l, channel, interpreter); + }); + } + } + catch (Throwable e) { + terminate(); + Msg.debug(this, channel + "," + interpreter + " reader exiting because " + e); + //throw new AssertionError(e); + } + } + } + private final AsyncReference state = new AsyncReference<>(GdbState.NOT_STARTED); // A copy of state, which is updated on the eventThread. @@ -98,15 +151,12 @@ public class GdbManagerImpl implements GdbManager { private final HandlerMap, Void, Void> handlerMap = new HandlerMap<>(); private final AtomicBoolean exited = new AtomicBoolean(false); - private Pty cliPty; - private Pty mi2Pty; private Process gdb; private Thread gdbWaiter; - private Thread cliReader; - private Thread mi2Reader; - private PrintWriter cliWriter; - private PrintWriter mi2Writer; + private PtyThread iniThread; + private PtyThread cliThread; + private PtyThread mi2Thread; private final AsyncLock cmdLock = new AsyncLock(); private final AtomicReference cmdLockHold = new AtomicReference<>(null); @@ -500,41 +550,50 @@ public class GdbManagerImpl implements GdbManager { state.set(GdbState.STARTING, Causes.UNCLAIMED); executor = Executors.newSingleThreadExecutor(); - mi2Pty = Pty.openpty(); if (gdbCmd != null) { - cliPty = Pty.openpty(); + iniThread = new PtyThread(Pty.openpty(), Channel.STDOUT, null); + gdb = iniThread.pty.getSlave().session(fullargs.toArray(new String[] {}), null); + iniThread.start(); try { - gdb = cliPty.getSlave().session(fullargs.toArray(new String[] {}), null); + iniThread.hasWriter.get(10, TimeUnit.SECONDS); } - catch (IOException e) { - // TODO: Seems I should declare this, but it makes client code ugly :( - throw new RuntimeException(e); + catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new IOException("Could not detect GDB's interpreter mode"); } + switch (iniThread.interpreter) { + case CLI: + cliThread = iniThread; + cliThread.setName("GDB Read CLI"); - PtyMaster cliMaster = cliPty.getMaster(); - cliReader = new Thread( - () -> readStream(cliMaster.getInputStream(), Channel.STDOUT, Interpreter.CLI), - "GDB Read CLI"); - cliReader.start(); - cliWriter = new PrintWriter(cliMaster.getOutputStream()); + mi2Thread = new PtyThread(Pty.openpty(), Channel.STDOUT, Interpreter.MI2); + mi2Thread.setName("GDB Read MI2"); + mi2Thread.start(); + cliThread.writer.println("new-ui mi2 " + mi2Thread.pty.getSlave().getFile()); + cliThread.writer.flush(); + try { + mi2Thread.hasWriter.get(2, TimeUnit.SECONDS); + } + catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new IOException( + "Could not obtain GDB/MI2 interpreter. Try " + gdbCmd + " -i mi2"); + } + break; + case MI2: + mi2Thread = iniThread; + mi2Thread.setName("GDB Read MI2"); + break; + } gdbWaiter = new Thread(this::waitGdbExit, "GDB WaitExit"); gdbWaiter.start(); - - cliWriter.println("new-ui mi2 " + mi2Pty.getSlave().getFile()); - cliWriter.flush(); } else { - System.out.println( - "Agent is waiting for GDB/MI v2 interpreter at " + mi2Pty.getSlave().getFile()); + mi2Thread = new PtyThread(Pty.openpty(), Channel.STDOUT, Interpreter.MI2); + mi2Thread.setName("GDB Read MI2"); + Msg.info(this, "Agent is waiting for GDB/MI v2 interpreter at " + + mi2Thread.pty.getSlave().getFile()); + mi2Thread.start(); } - - PtyMaster mi2Master = mi2Pty.getMaster(); - mi2Reader = new Thread( - () -> readStream(mi2Master.getInputStream(), Channel.STDOUT, Interpreter.MI2), - "GDB Read MI2"); - mi2Reader.start(); - mi2Writer = new PrintWriter(mi2Master.getOutputStream()); } @Override @@ -597,22 +656,24 @@ public class GdbManagerImpl implements GdbManager { checkStarted(); exited.set(true); executor.shutdownNow(); - if (cliPty != null) { + if (gdbWaiter != null) { gdbWaiter.interrupt(); - cliReader.interrupt(); - } - mi2Reader.interrupt(); - if (gdb != null) { - gdb.destroyForcibly(); } try { - if (cliPty != null) { - cliPty.close(); + if (cliThread != null) { + cliThread.interrupt(); + cliThread.pty.close(); + } + if (mi2Thread != null) { + mi2Thread.interrupt(); + mi2Thread.pty.close(); } - mi2Pty.close(); } catch (IOException e) { - throw new AssertionError(e); + Msg.error(this, "Problem closing PTYs to GDB."); + } + if (gdb != null) { + gdb.destroyForcibly(); } cmdLock.dispose("GDB is terminating"); state.dispose("GDB is terminating"); @@ -679,9 +740,9 @@ public class GdbManagerImpl implements GdbManager { protected PrintWriter getWriter(Interpreter interpreter) { switch (interpreter) { case CLI: - return cliWriter; + return cliThread == null ? null : cliThread.writer; case MI2: - return mi2Writer; + return mi2Thread == null ? null : mi2Thread.writer; default: throw new AssertionError(); } @@ -790,7 +851,7 @@ public class GdbManagerImpl implements GdbManager { */ protected void processStdOut(GdbConsoleOutputEvent evt, Void v) { String out = evt.getOutput(); - System.out.print(out); + //System.out.print(out); if (!evt.isStolen()) { listenersConsoleOutput.fire.output(Channel.STDOUT, out); } @@ -820,7 +881,7 @@ public class GdbManagerImpl implements GdbManager { */ protected void processStdErr(GdbDebugOutputEvent evt, Void v) { String out = evt.getOutput(); - System.err.print(out); + //System.err.print(out); if (!evt.isStolen()) { listenersConsoleOutput.fire.output(Channel.STDERR, out); } @@ -1379,14 +1440,16 @@ public class GdbManagerImpl implements GdbManager { public void sendInterruptNow() throws IOException { checkStarted(); Msg.info(this, "Interrupting"); - if (hasCli()) { - OutputStream os = cliPty.getMaster().getOutputStream(); + if (cliThread != null) { + OutputStream os = cliThread.pty.getMaster().getOutputStream(); + os.write(3); + os.flush(); + } + if (mi2Thread != null) { + OutputStream os = mi2Thread.pty.getMaster().getOutputStream(); os.write(3); os.flush(); } - OutputStream os = mi2Pty.getMaster().getOutputStream(); - os.write(3); - os.flush(); } @Override @@ -1488,11 +1551,11 @@ public class GdbManagerImpl implements GdbManager { @Override public String getMi2PtyName() { - return mi2Pty.getSlave().getFile().getAbsolutePath(); + return mi2Thread.pty.getSlave().getFile().getAbsolutePath(); } public boolean hasCli() { - return cliWriter != null; + return cliThread != null && cliThread.pty != null; } public Interpreter getRunningInterpreter() { diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelImpl.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelImpl.java index 653387aca7..c6ae92f553 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelImpl.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelImpl.java @@ -23,6 +23,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import agent.gdb.manager.*; import agent.gdb.manager.impl.cmd.GdbCommandError; import ghidra.async.AsyncUtils; +import ghidra.dbg.DebuggerModelClosedReason; import ghidra.dbg.agent.AbstractDebuggerObjectModel; import ghidra.dbg.error.DebuggerUserException; import ghidra.dbg.target.TargetAccessConditioned.TargetAccessibility; @@ -74,6 +75,7 @@ public class GdbModelImpl extends AbstractDebuggerObjectModel { } // TODO: Place make this a model method? + @Override public AddressFactory getAddressFactory() { return addressFactory; } @@ -122,6 +124,7 @@ public class GdbModelImpl extends AbstractDebuggerObjectModel { } public void terminate() throws IOException { + listeners.fire.modelClosed(DebuggerModelClosedReason.NORMAL); session.invalidateSubtree("GDB is terminating"); gdb.terminate(); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedGdbManagerTest.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedCliGdbManagerTest.java similarity index 92% rename from Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedGdbManagerTest.java rename to Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedCliGdbManagerTest.java index a4d2571ba5..90f59b32f0 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedGdbManagerTest.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedCliGdbManagerTest.java @@ -20,7 +20,7 @@ import java.util.concurrent.CompletableFuture; import agent.gdb.manager.GdbManager; -public class SpawnedGdbManagerTest extends AbstractGdbManagerTest { +public class SpawnedCliGdbManagerTest extends AbstractGdbManagerTest { @Override protected CompletableFuture startManager(GdbManager manager) { try { 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 new file mode 100644 index 0000000000..7302aa5101 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2Gdb7Dot6Dot1ManagerTest.java @@ -0,0 +1,34 @@ +/* ### + * 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.IOException; +import java.util.concurrent.CompletableFuture; + +import agent.gdb.manager.GdbManager; + +public class SpawnedMi2Gdb7Dot6Dot1ManagerTest extends AbstractGdbManagerTest { + @Override + protected CompletableFuture startManager(GdbManager manager) { + try { + manager.start("/opt/gdb-7.6.1/bin/gdb", "-i", "mi2"); + return manager.runRC(); + } + catch (IOException e) { + throw new AssertionError(e); + } + } +} 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/SpawnedMi2GdbManagerTest2.java new file mode 100644 index 0000000000..e4f82cd30e --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/manager/impl/SpawnedMi2GdbManagerTest2.java @@ -0,0 +1,34 @@ +/* ### + * 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.IOException; +import java.util.concurrent.CompletableFuture; + +import agent.gdb.manager.GdbManager; + +public class SpawnedMi2GdbManagerTest2 extends AbstractGdbManagerTest { + @Override + protected CompletableFuture startManager(GdbManager manager) { + try { + manager.start(GdbManager.DEFAULT_GDB_CMD, "-i", "mi2"); + return manager.runRC(); + } + catch (IOException e) { + throw new AssertionError(e); + } + } +} diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/server/AbstractGadpServer.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/server/AbstractGadpServer.java index 726258428b..90c34f44d0 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/server/AbstractGadpServer.java +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/server/AbstractGadpServer.java @@ -20,14 +20,15 @@ import java.net.SocketAddress; import java.nio.channels.AsynchronousSocketChannel; import ghidra.comm.service.AbstractAsyncServer; -import ghidra.dbg.DebuggerObjectModel; +import ghidra.dbg.*; import ghidra.dbg.gadp.error.GadpErrorException; import ghidra.dbg.gadp.protocol.Gadp; import ghidra.dbg.gadp.protocol.Gadp.ErrorCode; import ghidra.program.model.address.*; public abstract class AbstractGadpServer - extends AbstractAsyncServer { + extends AbstractAsyncServer + implements DebuggerModelListener { public static final String LISTENING_ON = "GADP Server listening on "; protected final DebuggerObjectModel model; @@ -36,6 +37,8 @@ public abstract class AbstractGadpServer super(addr); this.model = model; System.out.println(LISTENING_ON + getLocalAddress()); + + model.addModelListener(this); } public DebuggerObjectModel getModel() { @@ -63,4 +66,10 @@ public abstract class AbstractGadpServer // Note, +1 accounted for in how Ghidra AddressRanges work (inclusive of end) return new AddressRangeImpl(min, min.add(Integer.toUnsignedLong(range.getExtend()))); } + + @Override + public void modelClosed(DebuggerModelClosedReason reason) { + System.err.println("Model closed: " + reason); + System.exit(0); + } } diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerModelClosedReason.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerModelClosedReason.java index 5b020bef99..5955d58352 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerModelClosedReason.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerModelClosedReason.java @@ -19,7 +19,7 @@ package ghidra.dbg; * A reason given for a closed connection */ public interface DebuggerModelClosedReason { - DebuggerModelClosedReason NORMAL = DebuggerNormalModelClosedReason.INSTANCE; + DebuggerModelClosedReason NORMAL = DebuggerNormalModelClosedReason.NORMAL; static DebuggerModelClosedReason normal() { return NORMAL; diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerNormalModelClosedReason.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerNormalModelClosedReason.java index 6c1a98c908..b8b3391f0d 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerNormalModelClosedReason.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerNormalModelClosedReason.java @@ -16,7 +16,7 @@ package ghidra.dbg; enum DebuggerNormalModelClosedReason implements DebuggerModelClosedReason { - INSTANCE; + NORMAL; @Override public boolean hasException() {