GP-909: Adjusting how GDB resumes/steps to assure interrupts work.

This commit is contained in:
Dan
2021-04-29 15:20:46 -04:00
parent c70496680d
commit b150c557fc
24 changed files with 210 additions and 144 deletions

View File

@@ -20,7 +20,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import agent.gdb.manager.GdbManager.ExecSuffix; import agent.gdb.manager.GdbManager.StepCmd;
import agent.gdb.manager.impl.GdbMemoryMapping; import agent.gdb.manager.impl.GdbMemoryMapping;
/** /**
@@ -263,7 +263,7 @@ public interface GdbInferior extends GdbMemoryOperations {
* *
* @return a future that completes once the inferior has stepped * @return a future that completes once the inferior has stepped
*/ */
CompletableFuture<Void> step(ExecSuffix suffix); CompletableFuture<Void> step(StepCmd suffix);
/** /**
* Evaluate an expression * Evaluate an expression

View File

@@ -40,35 +40,34 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
public static final String DEFAULT_GDB_CMD = "/usr/bin/gdb"; public static final String DEFAULT_GDB_CMD = "/usr/bin/gdb";
/** /**
* Possible values for {@link GdbThread#step(ExecSuffix)} * Possible values for {@link GdbThread#step(StepCmd)}
*/ */
public enum ExecSuffix { public enum StepCmd {
/** Equivalent to {@code finish} in the CLI */
FINISH("finish"), FINISH("finish"),
/** Equivalent to {@code next} in the CLI */
NEXT("next"), NEXT("next"),
/** Equivalent to {@code nexti} in the CLI */ NEXTI("nexti", "next-instruction"),
NEXT_INSTRUCTION("next-instruction"),
/** Equivalent to {@code return} in the CLI */
RETURN("return"), RETURN("return"),
/** Equivalent to {@code step} in the CLI */
STEP("step"), STEP("step"),
/** Equivalent to {@code stepi} in the CLI */ STEPI("stepi", "step-instruction"),
STEP_INSTRUCTION("step-instruction"),
/** Equivalent to {@code until} in the CLI */
UNTIL("until"), UNTIL("until"),
/** User-defined */ /** User-defined */
EXTENDED("until"),; EXTENDED("echo extended-step?", "???"),;
final String str; public final String mi2;
public final String cli;
ExecSuffix(String str) { StepCmd(String cli, String execSuffix) {
this.str = str; this.cli = cli;
this.mi2 = "-exec-" + execSuffix;
}
StepCmd(String cli) {
this(cli, cli);
} }
@Override @Override
public String toString() { public String toString() {
return str; return mi2;
} }
} }
@@ -305,12 +304,23 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
* *
* <p> * <p>
* This may be useful if the manager's command queue is stalled because an inferior is running. * This may be useful if the manager's command queue is stalled because an inferior is running.
* If this doesn't clear the stall, try {@link #cancelCurrentCommand()}.
* *
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs
* @throws InterruptedException * @throws InterruptedException
*/ */
void sendInterruptNow() throws IOException; void sendInterruptNow() throws IOException;
/**
* Cancel the current command
*
* <p>
* Occasionally, a command gets stalled up waiting for an event, which for other reasons, will
* no longer occur. This will free up the queue for other commands to (hopefully) be processed.
* If {@link #sendInterruptNow()} doesn't clear the stall, try this.
*/
void cancelCurrentCommand();
/** /**
* Get the state of the GDB session * Get the state of the GDB session
* *
@@ -521,5 +531,4 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
* @return the description * @return the description
*/ */
String getPtyDescription(); String getPtyDescription();
} }

View File

@@ -18,7 +18,7 @@ package agent.gdb.manager;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import agent.gdb.manager.GdbManager.ExecSuffix; import agent.gdb.manager.GdbManager.StepCmd;
import agent.gdb.manager.breakpoint.GdbBreakpointInsertions; import agent.gdb.manager.breakpoint.GdbBreakpointInsertions;
import agent.gdb.manager.impl.GdbThreadInfo; import agent.gdb.manager.impl.GdbThreadInfo;
@@ -121,7 +121,7 @@ public interface GdbThread
* @param suffix specifies how far to step, or on what conditions stepping ends. * @param suffix specifies how far to step, or on what conditions stepping ends.
* @return a future that completes once the thread is running * @return a future that completes once the thread is running
*/ */
CompletableFuture<Void> step(ExecSuffix suffix); CompletableFuture<Void> step(StepCmd suffix);
/** /**
* Detach from the entire process * Detach from the entire process

View File

@@ -27,7 +27,7 @@ import java.util.regex.Pattern;
import com.google.common.collect.RangeSet; import com.google.common.collect.RangeSet;
import agent.gdb.manager.*; import agent.gdb.manager.*;
import agent.gdb.manager.GdbManager.ExecSuffix; import agent.gdb.manager.GdbManager.StepCmd;
import agent.gdb.manager.impl.cmd.*; import agent.gdb.manager.impl.cmd.*;
import ghidra.async.AsyncLazyValue; import ghidra.async.AsyncLazyValue;
import ghidra.lifecycle.Internal; import ghidra.lifecycle.Internal;
@@ -394,7 +394,7 @@ public class GdbInferiorImpl implements GdbInferior {
} }
@Override @Override
public CompletableFuture<Void> step(ExecSuffix suffix) { public CompletableFuture<Void> step(StepCmd suffix) {
return execute(new GdbStepCommand(manager, null, suffix)); return execute(new GdbStepCommand(manager, null, suffix));
} }

View File

@@ -735,7 +735,9 @@ public class GdbManagerImpl implements GdbManager {
}).exceptionally((exc) -> { }).exceptionally((exc) -> {
pcmd.completeExceptionally(exc); pcmd.completeExceptionally(exc);
//Msg.debug(this, "ON_EXCEPTION: CURCMD = " + curCmd); //Msg.debug(this, "ON_EXCEPTION: CURCMD = " + curCmd);
curCmd = null; synchronized (this) {
curCmd = null;
}
//Msg.debug(this, "SET CURCMD = null"); //Msg.debug(this, "SET CURCMD = null");
//Msg.debug(this, "RELEASING cmdLock"); //Msg.debug(this, "RELEASING cmdLock");
Hold hold = cmdLockHold.getAndSet(null); Hold hold = cmdLockHold.getAndSet(null);
@@ -747,6 +749,23 @@ public class GdbManagerImpl implements GdbManager {
return pcmd; return pcmd;
} }
@Override
public void cancelCurrentCommand() {
GdbPendingCommand<?> curCmd;
synchronized (this) {
curCmd = this.curCmd;
this.curCmd = null;
}
if (curCmd != null) {
Msg.info(this, "Cancelling current command: " + curCmd);
curCmd.cancel(false);
}
Hold hold = cmdLockHold.getAndSet(null);
if (hold != null) {
hold.release();
}
}
protected PrintWriter getWriter(Interpreter interpreter) { protected PrintWriter getWriter(Interpreter interpreter) {
switch (interpreter) { switch (interpreter) {
case CLI: case CLI:
@@ -1481,7 +1500,7 @@ public class GdbManagerImpl implements GdbManager {
os.write(3); os.write(3);
os.flush(); os.flush();
} }
if (mi2Thread != null) { else if (mi2Thread != null) {
OutputStream os = mi2Thread.pty.getParent().getOutputStream(); OutputStream os = mi2Thread.pty.getParent().getOutputStream();
os.write(3); os.write(3);
os.flush(); os.flush();

View File

@@ -26,7 +26,7 @@ import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.RangeSet; import com.google.common.collect.RangeSet;
import agent.gdb.manager.*; import agent.gdb.manager.*;
import agent.gdb.manager.GdbManager.ExecSuffix; import agent.gdb.manager.GdbManager.StepCmd;
import agent.gdb.manager.breakpoint.GdbBreakpointInfo; import agent.gdb.manager.breakpoint.GdbBreakpointInfo;
import agent.gdb.manager.breakpoint.GdbBreakpointType; import agent.gdb.manager.breakpoint.GdbBreakpointType;
import agent.gdb.manager.impl.cmd.*; import agent.gdb.manager.impl.cmd.*;
@@ -249,7 +249,7 @@ public class GdbThreadImpl implements GdbThread {
} }
@Override @Override
public CompletableFuture<Void> step(ExecSuffix suffix) { public CompletableFuture<Void> step(StepCmd suffix) {
return execute(new GdbStepCommand(manager, id, suffix)); return execute(new GdbStepCommand(manager, id, suffix));
} }

View File

@@ -19,9 +19,9 @@ import agent.gdb.manager.GdbThread;
import agent.gdb.manager.evt.*; import agent.gdb.manager.evt.*;
import agent.gdb.manager.impl.*; import agent.gdb.manager.impl.*;
public abstract class AbstractGdbCommandExpectRunning extends AbstractGdbCommand<GdbThread> { public abstract class AbstractLaunchGdbCommand extends AbstractGdbCommand<GdbThread> {
protected AbstractGdbCommandExpectRunning(GdbManagerImpl manager) { protected AbstractLaunchGdbCommand(GdbManagerImpl manager) {
super(manager); super(manager);
} }

View File

@@ -16,62 +16,39 @@
package agent.gdb.manager.impl.cmd; package agent.gdb.manager.impl.cmd;
import agent.gdb.manager.GdbInferior; import agent.gdb.manager.GdbInferior;
import agent.gdb.manager.evt.*;
import agent.gdb.manager.impl.*; import agent.gdb.manager.impl.*;
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter; import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
/** /**
* Implementation of {@link GdbInferior#cont()} * Implementation of {@link GdbInferior#cont()}
*/ */
public class GdbContinueCommand extends AbstractGdbCommandWithThreadId<Void> { public class GdbContinueCommand extends AbstractGdbCommandWithThreadId<Void>
implements MixinResumeInCliGdbCommand {
public GdbContinueCommand(GdbManagerImpl manager, Integer threadId) { public GdbContinueCommand(GdbManagerImpl manager, Integer threadId) {
super(manager, threadId); super(manager, threadId);
} }
@Override @Override
public Interpreter getInterpreter() { public Interpreter getInterpreter() {
if (manager.hasCli()) { return getInterpreter(manager);
return Interpreter.CLI;
}
return Interpreter.MI2;
} }
@Override @Override
public String encode(String threadPart) { public String encode(String threadPart) {
switch (getInterpreter()) { if (getInterpreter() == Interpreter.CLI) {
case CLI: return "continue";
// The significance is the Pty, not so much the actual command
// Using MI2 simplifies event processing (no console output parsing)
return "interpreter-exec mi2 \"-exec-continue" + threadPart + "\"";
case MI2:
return "-exec-continue" + threadPart;
default:
throw new AssertionError();
} }
return "-exec-continue" + threadPart;
} }
@Override @Override
public boolean handle(GdbEvent<?> evt, GdbPendingCommand<?> pending) { public boolean handle(GdbEvent<?> evt, GdbPendingCommand<?> pending) {
evt = checkErrorViaCli(evt); evt = checkErrorViaCli(evt); // TODO: Deprecated, since that hack can crash GDB
if (evt instanceof GdbCommandRunningEvent) { return handleExpectingRunning(evt, pending);
pending.claim(evt);
return pending.hasAny(GdbRunningEvent.class);
}
else if (evt instanceof AbstractGdbCompletedCommandEvent) {
pending.claim(evt);
return true; // Not the expected Completed event
}
else if (evt instanceof GdbRunningEvent) {
// Event happens no matter which interpreter received the command
pending.claim(evt);
return pending.hasAny(GdbCommandRunningEvent.class);
}
return false;
} }
@Override @Override
public Void complete(GdbPendingCommand<?> pending) { public Void complete(GdbPendingCommand<?> pending) {
pending.checkCompletion(GdbCommandRunningEvent.class); return completeOnRunning(pending);
return null;
} }
} }

View File

@@ -22,7 +22,7 @@ import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
/** /**
* Implementation of {@link GdbInferior#run()} * Implementation of {@link GdbInferior#run()}
*/ */
public class GdbRunCommand extends AbstractGdbCommandExpectRunning { public class GdbRunCommand extends AbstractLaunchGdbCommand {
public GdbRunCommand(GdbManagerImpl manager) { public GdbRunCommand(GdbManagerImpl manager) {
super(manager); super(manager);
@@ -30,23 +30,11 @@ public class GdbRunCommand extends AbstractGdbCommandExpectRunning {
@Override @Override
public Interpreter getInterpreter() { public Interpreter getInterpreter() {
if (manager.hasCli()) {
return Interpreter.CLI;
}
return Interpreter.MI2; return Interpreter.MI2;
} }
@Override @Override
public String encode() { public String encode() {
switch (getInterpreter()) { return "-exec-run";
case CLI:
// The significance is the Pty, not so much the actual command
// Using MI2 simplifies event processing (no console output parsing)
return "interpreter-exec mi2 \"-exec-run\"";
case MI2:
return "-exec-run";
default:
throw new AssertionError();
}
} }
} }

View File

@@ -22,7 +22,7 @@ import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
/** /**
* Implementation of {@link GdbInferior#start()} * Implementation of {@link GdbInferior#start()}
*/ */
public class GdbStartCommand extends AbstractGdbCommandExpectRunning { public class GdbStartCommand extends AbstractLaunchGdbCommand {
public GdbStartCommand(GdbManagerImpl manager) { public GdbStartCommand(GdbManagerImpl manager) {
super(manager); super(manager);
@@ -35,6 +35,6 @@ public class GdbStartCommand extends AbstractGdbCommandExpectRunning {
@Override @Override
public String encode() { public String encode() {
return "-interpreter-exec console start"; return "-exec-run --start";
} }
} }

View File

@@ -22,7 +22,7 @@ import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
/** /**
* Implementation of {@link GdbInferior#starti()} * Implementation of {@link GdbInferior#starti()}
*/ */
public class GdbStartInstructionCommand extends AbstractGdbCommandExpectRunning { public class GdbStartInstructionCommand extends AbstractLaunchGdbCommand {
public GdbStartInstructionCommand(GdbManagerImpl manager) { public GdbStartInstructionCommand(GdbManagerImpl manager) {
super(manager); super(manager);

View File

@@ -15,67 +15,44 @@
*/ */
package agent.gdb.manager.impl.cmd; package agent.gdb.manager.impl.cmd;
import agent.gdb.manager.GdbManager.ExecSuffix; import agent.gdb.manager.GdbManager.StepCmd;
import agent.gdb.manager.GdbThread; import agent.gdb.manager.GdbThread;
import agent.gdb.manager.evt.*;
import agent.gdb.manager.impl.*; import agent.gdb.manager.impl.*;
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter; import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
/** /**
* Implementation of {@link GdbThread#stepInstruction()} * Implementation of {@link GdbThread#stepInstruction()}
*/ */
public class GdbStepCommand extends AbstractGdbCommandWithThreadId<Void> { public class GdbStepCommand extends AbstractGdbCommandWithThreadId<Void>
protected final ExecSuffix suffix; implements MixinResumeInCliGdbCommand {
protected final StepCmd cmd;
public GdbStepCommand(GdbManagerImpl manager, Integer threadId, ExecSuffix suffix) { public GdbStepCommand(GdbManagerImpl manager, Integer threadId, StepCmd cmd) {
super(manager, threadId); super(manager, threadId);
this.suffix = suffix; this.cmd = cmd;
} }
@Override @Override
public Interpreter getInterpreter() { public Interpreter getInterpreter() {
if (manager.hasCli()) { return getInterpreter(manager);
return Interpreter.CLI;
}
return Interpreter.MI2;
} }
@Override @Override
protected String encode(String threadPart) { protected String encode(String threadPart) {
String mi2Cmd = "-exec-" + suffix + threadPart; if (getInterpreter() == Interpreter.CLI) {
switch (getInterpreter()) { return cmd.cli;
case CLI:
// The significance is the Pty, not so much the actual command
// Using MI2 simplifies event processing (no console output parsing)
return "interpreter-exec mi2 \"" + mi2Cmd + "\"";
case MI2:
return mi2Cmd;
default:
throw new AssertionError();
} }
return cmd.mi2 + threadPart;
} }
@Override @Override
public boolean handle(GdbEvent<?> evt, GdbPendingCommand<?> pending) { public boolean handle(GdbEvent<?> evt, GdbPendingCommand<?> pending) {
evt = checkErrorViaCli(evt); evt = checkErrorViaCli(evt); // TODO: Deprecated, since that hack can crash GDB
if (evt instanceof GdbCommandRunningEvent) { return handleExpectingRunning(evt, pending);
pending.claim(evt);
return pending.hasAny(GdbRunningEvent.class);
}
else if (evt instanceof AbstractGdbCompletedCommandEvent) {
pending.claim(evt);
return true; // Not the expected Completed event
}
else if (evt instanceof GdbRunningEvent) {
pending.claim(evt);
return pending.hasAny(GdbCommandRunningEvent.class);
}
return false;
} }
@Override @Override
public Void complete(GdbPendingCommand<?> pending) { public Void complete(GdbPendingCommand<?> pending) {
pending.checkCompletion(GdbCommandRunningEvent.class); return completeOnRunning(pending);
return null;
} }
} }

View File

@@ -0,0 +1,62 @@
/* ###
* 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.cmd;
import agent.gdb.manager.evt.*;
import agent.gdb.manager.impl.*;
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
/**
* A marker and mixin for dealing with commands where resuming in a secondary (mi2) ui seems
* problematic.
*
* <p>
* I'm not sure if it's a bug in GDB or Linux, or what, but if I attach to a target that doesn't
* have a tty then resume (i.e., continue or step) from the mi2 interpreter, it seems I cannot use
* "interrupt" from the primary (console) interpreter. So, for these resumes, I need to issue the
* command from the console, allowing ^C to work.
*/
public interface MixinResumeInCliGdbCommand extends GdbCommand<Void> {
default Interpreter getInterpreter(GdbManagerImpl manager) {
if (manager.hasCli()) {
return Interpreter.CLI;
}
return Interpreter.MI2;
}
default boolean handleExpectingRunning(GdbEvent<?> evt, GdbPendingCommand<?> pending) {
if (evt instanceof GdbCommandRunningEvent) {
pending.claim(evt);
return pending.hasAny(GdbRunningEvent.class);
}
else if (evt instanceof AbstractGdbCompletedCommandEvent) {
pending.claim(evt);
return true; // Not the expected Completed event
}
else if (evt instanceof GdbRunningEvent) {
// Event happens no matter which interpreter received the command
pending.claim(evt);
return pending.hasAny(GdbCommandRunningEvent.class);
}
return false;
}
default Void completeOnRunning(GdbPendingCommand<?> pending) {
pending.checkCompletion(GdbCommandRunningEvent.class);
return null;
}
}

View File

@@ -19,7 +19,7 @@ import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import agent.gdb.manager.*; import agent.gdb.manager.*;
import agent.gdb.manager.GdbManager.ExecSuffix; import agent.gdb.manager.GdbManager.StepCmd;
import agent.gdb.manager.impl.cmd.GdbStateChangeRecord; import agent.gdb.manager.impl.cmd.GdbStateChangeRecord;
import agent.gdb.manager.reason.*; import agent.gdb.manager.reason.*;
import ghidra.async.AsyncFence; import ghidra.async.AsyncFence;
@@ -160,24 +160,24 @@ public class GdbModelTargetInferior
return impl.gateFuture(inferior.cont()); return impl.gateFuture(inferior.cont());
} }
protected ExecSuffix convertToGdb(TargetStepKind kind) { protected StepCmd convertToGdb(TargetStepKind kind) {
switch (kind) { switch (kind) {
case FINISH: case FINISH:
return ExecSuffix.FINISH; return StepCmd.FINISH;
case INTO: case INTO:
return ExecSuffix.STEP_INSTRUCTION; return StepCmd.STEPI;
case LINE: case LINE:
return ExecSuffix.STEP; return StepCmd.STEP;
case OVER: case OVER:
return ExecSuffix.NEXT_INSTRUCTION; return StepCmd.NEXTI;
case OVER_LINE: case OVER_LINE:
return ExecSuffix.NEXT; return StepCmd.NEXT;
case RETURN: case RETURN:
return ExecSuffix.RETURN; return StepCmd.RETURN;
case UNTIL: case UNTIL:
return ExecSuffix.UNTIL; return StepCmd.UNTIL;
case EXTENDED: case EXTENDED:
return ExecSuffix.EXTENDED; return StepCmd.EXTENDED;
default: default:
throw new AssertionError(); throw new AssertionError();
} }

View File

@@ -229,6 +229,7 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
//return impl.gdb.interrupt(); //return impl.gdb.interrupt();
try { try {
impl.gdb.sendInterruptNow(); impl.gdb.sendInterruptNow();
impl.gdb.cancelCurrentCommand();
} }
catch (IOException e) { catch (IOException e) {
Msg.error(this, "Could not interrupt", e); Msg.error(this, "Could not interrupt", e);

View File

@@ -20,7 +20,7 @@ import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import agent.gdb.manager.*; import agent.gdb.manager.*;
import agent.gdb.manager.GdbManager.ExecSuffix; import agent.gdb.manager.GdbManager.StepCmd;
import agent.gdb.manager.impl.GdbFrameInfo; import agent.gdb.manager.impl.GdbFrameInfo;
import agent.gdb.manager.impl.GdbThreadInfo; import agent.gdb.manager.impl.GdbThreadInfo;
import agent.gdb.manager.impl.cmd.GdbStateChangeRecord; import agent.gdb.manager.impl.cmd.GdbStateChangeRecord;
@@ -183,24 +183,24 @@ public class GdbModelTargetThread
} }
} }
protected ExecSuffix convertToGdb(TargetStepKind kind) { protected StepCmd convertToGdb(TargetStepKind kind) {
switch (kind) { switch (kind) {
case FINISH: case FINISH:
return ExecSuffix.FINISH; return StepCmd.FINISH;
case INTO: case INTO:
return ExecSuffix.STEP_INSTRUCTION; return StepCmd.STEPI;
case LINE: case LINE:
return ExecSuffix.STEP; return StepCmd.STEP;
case OVER: case OVER:
return ExecSuffix.NEXT_INSTRUCTION; return StepCmd.NEXTI;
case OVER_LINE: case OVER_LINE:
return ExecSuffix.NEXT; return StepCmd.NEXT;
case RETURN: case RETURN:
return ExecSuffix.RETURN; return StepCmd.RETURN;
case UNTIL: case UNTIL:
return ExecSuffix.UNTIL; return StepCmd.UNTIL;
case EXTENDED: case EXTENDED:
return ExecSuffix.EXTENDED; return StepCmd.EXTENDED;
default: default:
throw new AssertionError(); throw new AssertionError();
} }

View File

@@ -33,7 +33,7 @@ import org.junit.*;
import com.google.common.collect.*; import com.google.common.collect.*;
import agent.gdb.manager.*; import agent.gdb.manager.*;
import agent.gdb.manager.GdbManager.ExecSuffix; import agent.gdb.manager.GdbManager.StepCmd;
import agent.gdb.manager.breakpoint.GdbBreakpointInfo; import agent.gdb.manager.breakpoint.GdbBreakpointInfo;
import agent.gdb.pty.PtyFactory; import agent.gdb.pty.PtyFactory;
import ghidra.async.AsyncReference; import ghidra.async.AsyncReference;
@@ -259,7 +259,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
assertTrue("Didn't stop at syscall", out.contains("syscall")); assertTrue("Didn't stop at syscall", out.contains("syscall"));
// Now the real test // Now the real test
waitOn(mgr.currentInferior().step(ExecSuffix.STEP_INSTRUCTION)); waitOn(mgr.currentInferior().step(StepCmd.STEPI));
CompletableFuture<Void> stopped = mgr.waitForState(GdbState.STOPPED); CompletableFuture<Void> stopped = mgr.waitForState(GdbState.STOPPED);
Thread.sleep(100); // NB: Not exactly reliable, but verify we're waiting Thread.sleep(100); // NB: Not exactly reliable, but verify we're waiting
assertFalse(stopped.isDone()); assertFalse(stopped.isDone());
@@ -400,7 +400,7 @@ public abstract class AbstractGdbManagerTest extends AbstractGhidraHeadlessInteg
GdbThread thread = waitOn(mgr.currentInferior().run()); GdbThread thread = waitOn(mgr.currentInferior().run());
waitOn(mgr.waitForState(GdbState.STOPPED)); waitOn(mgr.waitForState(GdbState.STOPPED));
//waitOn(mgr.waitForPrompt()); //waitOn(mgr.waitForPrompt());
waitOn(thread.step(ExecSuffix.NEXT_INSTRUCTION)); waitOn(thread.step(StepCmd.NEXTI));
waitOn(mgr.waitForState(GdbState.STOPPED)); waitOn(mgr.waitForState(GdbState.STOPPED));
assertNull(mgr.currentInferior().getExitCode()); assertNull(mgr.currentInferior().getExitCode());
} }

View File

@@ -33,7 +33,7 @@ public abstract class AbstractModelForGdbAttacherTest extends AbstractDebuggerMo
@Override @Override
public DebuggerTestSpecimen getAttachSpecimen() { public DebuggerTestSpecimen getAttachSpecimen() {
return GdbLinuxSpecimen.DD; return GdbLinuxSpecimen.SLEEP;
} }
@Override @Override

View File

@@ -54,7 +54,7 @@ public abstract class AbstractModelForGdbInterpreterTest
@Override @Override
public DebuggerTestSpecimen getAttachSpecimen() { public DebuggerTestSpecimen getAttachSpecimen() {
return GdbLinuxSpecimen.DD; return GdbLinuxSpecimen.SLEEP;
} }
@Override @Override

View File

@@ -28,10 +28,10 @@ import ghidra.dbg.testutil.DummyProc;
import ghidra.dbg.util.ShellUtils; import ghidra.dbg.util.ShellUtils;
public enum GdbLinuxSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUtils { public enum GdbLinuxSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUtils {
DD { SLEEP {
@Override @Override
String getCommandLine() { String getCommandLine() {
return "dd"; return "sleep 100000";
} }
}, },
FORK_EXIT { FORK_EXIT {

View File

@@ -15,9 +15,14 @@
*/ */
package agent.gdb.model.invm; package agent.gdb.model.invm;
import static org.junit.Assume.assumeFalse;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test;
import agent.gdb.model.AbstractModelForGdbInferiorAttacherTest; import agent.gdb.model.AbstractModelForGdbInferiorAttacherTest;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
public class InVmModelForGdbInferiorAttacherTest extends AbstractModelForGdbInferiorAttacherTest { public class InVmModelForGdbInferiorAttacherTest extends AbstractModelForGdbInferiorAttacherTest {
@Override @Override
@@ -30,4 +35,21 @@ public class InVmModelForGdbInferiorAttacherTest extends AbstractModelForGdbInfe
public void testAttachableContainerIsWhereExpected() throws Throwable { public void testAttachableContainerIsWhereExpected() throws Throwable {
// nop // nop
} }
/**
* Run a dummy process without a tty. It seems when GDB (I tested with 8.0.1) attaches to such a
* process, it is unable to interrupt it from the opposite interpreter that resumed it.
*/
@Test
@Ignore("Not a real test")
public void testRunADummy() throws Throwable {
assumeFalse(SystemUtilities.isInTestingBatchMode());
DebuggerTestSpecimen specimen = getAttachSpecimen();
dummy = specimen.runDummy();
Msg.info(this, "Dummy pid: " + dummy.pid);
dummy.process.waitFor();
Msg.info(this, "Dummy terminated");
}
} }

View File

@@ -19,6 +19,7 @@ import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.event.*; import java.awt.event.*;
import java.util.*; import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.function.Function; import java.util.function.Function;
import javax.swing.*; import javax.swing.*;
@@ -45,11 +46,13 @@ import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer; import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter; import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
import ghidra.app.services.MarkerService; import ghidra.app.services.MarkerService;
import ghidra.async.AsyncUtils;
import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.util.PluginUtils; import ghidra.framework.plugintool.util.PluginUtils;
import ghidra.program.database.ProgramContentHandler; import ghidra.program.database.ProgramContentHandler;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import resources.MultiIcon; import resources.MultiIcon;
import resources.ResourceManager; import resources.ResourceManager;
import resources.icons.RotateIcon; import resources.icons.RotateIcon;
@@ -1546,7 +1549,13 @@ public interface DebuggerResources {
static <T> Function<Throwable, T> showError(Component parent, String message) { static <T> Function<Throwable, T> showError(Component parent, String message) {
return e -> { return e -> {
Msg.showError(parent, parent, DebuggerPluginPackage.NAME, message, e); Throwable t = AsyncUtils.unwrapThrowable(e);
if (t instanceof CancelledException || t instanceof CancellationException) {
Msg.error(parent, "Cancelled: " + message);
}
else {
Msg.showError(parent, parent, DebuggerPluginPackage.NAME, message, e);
}
return null; return null;
}; };
} }

View File

@@ -324,7 +324,7 @@ public abstract class AbstractDebuggerModelTest extends AbstractGhidraHeadedInte
assertEquals(TargetExecutionState.RUNNING, stateful.getExecutionState()); assertEquals(TargetExecutionState.RUNNING, stateful.getExecutionState());
}, List.of(AssertionError.class)); }, List.of(AssertionError.class));
} }
// NB. Never have to wait to interrupt. (Hmmmm, do we believe this?) // NB. Never have to waitAcc to interrupt. It's likely inaccessible, anyway.
waitOn(interruptible.interrupt()); waitOn(interruptible.interrupt());
if (stateful != null) { if (stateful != null) {
retryVoid(() -> { retryVoid(() -> {

View File

@@ -60,7 +60,9 @@ public class DummyProc implements AutoCloseable {
DummyProc(String... args) throws IOException { DummyProc(String... args) throws IOException {
args[0] = which(args[0]); args[0] = which(args[0]);
process = new ProcessBuilder(args).start(); process = new ProcessBuilder(args)
.inheritIO()
.start();
pid = process.pid(); pid = process.pid();
} }