Merge remote-tracking branch 'origin/debugger'

This commit is contained in:
ghidra1 2021-04-30 19:48:08 -04:00
commit 5c9edaed45
25 changed files with 229 additions and 158 deletions

View File

@ -20,7 +20,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import agent.gdb.manager.GdbManager.ExecSuffix;
import agent.gdb.manager.GdbManager.StepCmd;
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
*/
CompletableFuture<Void> step(ExecSuffix suffix);
CompletableFuture<Void> step(StepCmd suffix);
/**
* 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";
/**
* Possible values for {@link GdbThread#step(ExecSuffix)}
* Possible values for {@link GdbThread#step(StepCmd)}
*/
public enum ExecSuffix {
/** Equivalent to {@code finish} in the CLI */
public enum StepCmd {
FINISH("finish"),
/** Equivalent to {@code next} in the CLI */
NEXT("next"),
/** Equivalent to {@code nexti} in the CLI */
NEXT_INSTRUCTION("next-instruction"),
/** Equivalent to {@code return} in the CLI */
NEXTI("nexti", "next-instruction"),
RETURN("return"),
/** Equivalent to {@code step} in the CLI */
STEP("step"),
/** Equivalent to {@code stepi} in the CLI */
STEP_INSTRUCTION("step-instruction"),
/** Equivalent to {@code until} in the CLI */
STEPI("stepi", "step-instruction"),
UNTIL("until"),
/** User-defined */
EXTENDED("until"),;
EXTENDED("echo extended-step?", "???"),;
final String str;
public final String mi2;
public final String cli;
ExecSuffix(String str) {
this.str = str;
StepCmd(String cli, String execSuffix) {
this.cli = cli;
this.mi2 = "-exec-" + execSuffix;
}
StepCmd(String cli) {
this(cli, cli);
}
@Override
public String toString() {
return str;
return mi2;
}
}
@ -305,12 +304,23 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
*
* <p>
* 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 InterruptedException
*/
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
*
@ -521,5 +531,4 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
* @return the description
*/
String getPtyDescription();
}

View File

@ -18,7 +18,7 @@ package agent.gdb.manager;
import java.util.List;
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.impl.GdbThreadInfo;
@ -121,7 +121,7 @@ public interface GdbThread
* @param suffix specifies how far to step, or on what conditions stepping ends.
* @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

View File

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

View File

@ -735,7 +735,9 @@ public class GdbManagerImpl implements GdbManager {
}).exceptionally((exc) -> {
pcmd.completeExceptionally(exc);
//Msg.debug(this, "ON_EXCEPTION: CURCMD = " + curCmd);
curCmd = null;
synchronized (this) {
curCmd = null;
}
//Msg.debug(this, "SET CURCMD = null");
//Msg.debug(this, "RELEASING cmdLock");
Hold hold = cmdLockHold.getAndSet(null);
@ -747,6 +749,23 @@ public class GdbManagerImpl implements GdbManager {
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) {
switch (interpreter) {
case CLI:
@ -1481,7 +1500,7 @@ public class GdbManagerImpl implements GdbManager {
os.write(3);
os.flush();
}
if (mi2Thread != null) {
else if (mi2Thread != null) {
OutputStream os = mi2Thread.pty.getParent().getOutputStream();
os.write(3);
os.flush();

View File

@ -26,7 +26,7 @@ import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.RangeSet;
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.GdbBreakpointType;
import agent.gdb.manager.impl.cmd.*;
@ -249,7 +249,7 @@ public class GdbThreadImpl implements GdbThread {
}
@Override
public CompletableFuture<Void> step(ExecSuffix suffix) {
public CompletableFuture<Void> step(StepCmd 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.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);
}

View File

@ -16,62 +16,39 @@
package agent.gdb.manager.impl.cmd;
import agent.gdb.manager.GdbInferior;
import agent.gdb.manager.evt.*;
import agent.gdb.manager.impl.*;
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
/**
* 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) {
super(manager, threadId);
}
@Override
public Interpreter getInterpreter() {
if (manager.hasCli()) {
return Interpreter.CLI;
}
return Interpreter.MI2;
return getInterpreter(manager);
}
@Override
public String encode(String threadPart) {
switch (getInterpreter()) {
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-continue" + threadPart + "\"";
case MI2:
return "-exec-continue" + threadPart;
default:
throw new AssertionError();
if (getInterpreter() == Interpreter.CLI) {
return "continue";
}
return "-exec-continue" + threadPart;
}
@Override
public boolean handle(GdbEvent<?> evt, GdbPendingCommand<?> pending) {
evt = checkErrorViaCli(evt);
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;
evt = checkErrorViaCli(evt); // TODO: Deprecated, since that hack can crash GDB
return handleExpectingRunning(evt, pending);
}
@Override
public Void complete(GdbPendingCommand<?> pending) {
pending.checkCompletion(GdbCommandRunningEvent.class);
return null;
return completeOnRunning(pending);
}
}

View File

@ -22,7 +22,7 @@ import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
/**
* Implementation of {@link GdbInferior#run()}
*/
public class GdbRunCommand extends AbstractGdbCommandExpectRunning {
public class GdbRunCommand extends AbstractLaunchGdbCommand {
public GdbRunCommand(GdbManagerImpl manager) {
super(manager);
@ -30,23 +30,11 @@ public class GdbRunCommand extends AbstractGdbCommandExpectRunning {
@Override
public Interpreter getInterpreter() {
if (manager.hasCli()) {
return Interpreter.CLI;
}
return Interpreter.MI2;
}
@Override
public String encode() {
switch (getInterpreter()) {
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();
}
return "-exec-run";
}
}

View File

@ -22,7 +22,7 @@ import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
/**
* Implementation of {@link GdbInferior#start()}
*/
public class GdbStartCommand extends AbstractGdbCommandExpectRunning {
public class GdbStartCommand extends AbstractLaunchGdbCommand {
public GdbStartCommand(GdbManagerImpl manager) {
super(manager);
@ -35,6 +35,6 @@ public class GdbStartCommand extends AbstractGdbCommandExpectRunning {
@Override
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()}
*/
public class GdbStartInstructionCommand extends AbstractGdbCommandExpectRunning {
public class GdbStartInstructionCommand extends AbstractLaunchGdbCommand {
public GdbStartInstructionCommand(GdbManagerImpl manager) {
super(manager);

View File

@ -15,67 +15,44 @@
*/
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.evt.*;
import agent.gdb.manager.impl.*;
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
/**
* Implementation of {@link GdbThread#stepInstruction()}
*/
public class GdbStepCommand extends AbstractGdbCommandWithThreadId<Void> {
protected final ExecSuffix suffix;
public class GdbStepCommand extends AbstractGdbCommandWithThreadId<Void>
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);
this.suffix = suffix;
this.cmd = cmd;
}
@Override
public Interpreter getInterpreter() {
if (manager.hasCli()) {
return Interpreter.CLI;
}
return Interpreter.MI2;
return getInterpreter(manager);
}
@Override
protected String encode(String threadPart) {
String mi2Cmd = "-exec-" + suffix + threadPart;
switch (getInterpreter()) {
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();
if (getInterpreter() == Interpreter.CLI) {
return cmd.cli;
}
return cmd.mi2 + threadPart;
}
@Override
public boolean handle(GdbEvent<?> evt, GdbPendingCommand<?> pending) {
evt = checkErrorViaCli(evt);
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) {
pending.claim(evt);
return pending.hasAny(GdbCommandRunningEvent.class);
}
return false;
evt = checkErrorViaCli(evt); // TODO: Deprecated, since that hack can crash GDB
return handleExpectingRunning(evt, pending);
}
@Override
public Void complete(GdbPendingCommand<?> pending) {
pending.checkCompletion(GdbCommandRunningEvent.class);
return null;
return completeOnRunning(pending);
}
}

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 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.reason.*;
import ghidra.async.AsyncFence;
@ -160,24 +160,24 @@ public class GdbModelTargetInferior
return impl.gateFuture(inferior.cont());
}
protected ExecSuffix convertToGdb(TargetStepKind kind) {
protected StepCmd convertToGdb(TargetStepKind kind) {
switch (kind) {
case FINISH:
return ExecSuffix.FINISH;
return StepCmd.FINISH;
case INTO:
return ExecSuffix.STEP_INSTRUCTION;
return StepCmd.STEPI;
case LINE:
return ExecSuffix.STEP;
return StepCmd.STEP;
case OVER:
return ExecSuffix.NEXT_INSTRUCTION;
return StepCmd.NEXTI;
case OVER_LINE:
return ExecSuffix.NEXT;
return StepCmd.NEXT;
case RETURN:
return ExecSuffix.RETURN;
return StepCmd.RETURN;
case UNTIL:
return ExecSuffix.UNTIL;
return StepCmd.UNTIL;
case EXTENDED:
return ExecSuffix.EXTENDED;
return StepCmd.EXTENDED;
default:
throw new AssertionError();
}

View File

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

View File

@ -20,7 +20,7 @@ import java.util.Map;
import java.util.concurrent.CompletableFuture;
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.GdbThreadInfo;
import agent.gdb.manager.impl.cmd.GdbStateChangeRecord;
@ -70,6 +70,7 @@ public class GdbModelTargetThread
protected String display;
protected String shortDisplay;
protected GdbThreadInfo info;
protected TargetExecutionState state = TargetExecutionState.INACTIVE;
private Integer base = 10;
protected final GdbModelTargetStack stack;
@ -85,7 +86,7 @@ public class GdbModelTargetThread
this.stack = new GdbModelTargetStack(this, inferior);
changeAttributes(List.of(), List.of(stack), Map.of( //
STATE_ATTRIBUTE_NAME, convertState(thread.getState()), //
STATE_ATTRIBUTE_NAME, state = convertState(thread.getState()), //
SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME, SUPPORTED_KINDS, //
SHORT_DISPLAY_ATTRIBUTE_NAME, shortDisplay = computeShortDisplay(), //
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay() //
@ -119,7 +120,7 @@ public class GdbModelTargetThread
sb.append(" ");
sb.append(info.getInferiorName());
sb.append(" ");
sb.append(info.getState());
sb.append(state.name().toLowerCase());
sb.append(" ");
List<GdbFrameInfo> frames = info.getFrames();
if (!frames.isEmpty()) {
@ -183,24 +184,24 @@ public class GdbModelTargetThread
}
}
protected ExecSuffix convertToGdb(TargetStepKind kind) {
protected StepCmd convertToGdb(TargetStepKind kind) {
switch (kind) {
case FINISH:
return ExecSuffix.FINISH;
return StepCmd.FINISH;
case INTO:
return ExecSuffix.STEP_INSTRUCTION;
return StepCmd.STEPI;
case LINE:
return ExecSuffix.STEP;
return StepCmd.STEP;
case OVER:
return ExecSuffix.NEXT_INSTRUCTION;
return StepCmd.NEXTI;
case OVER_LINE:
return ExecSuffix.NEXT;
return StepCmd.NEXT;
case RETURN:
return ExecSuffix.RETURN;
return StepCmd.RETURN;
case UNTIL:
return ExecSuffix.UNTIL;
return StepCmd.UNTIL;
case EXTENDED:
return ExecSuffix.EXTENDED;
return StepCmd.EXTENDED;
default:
throw new AssertionError();
}
@ -237,15 +238,15 @@ public class GdbModelTargetThread
}
public CompletableFuture<Void> stateChanged(GdbStateChangeRecord sco) {
GdbState state = sco.getState();
GdbState gdbState = sco.getState();
CompletableFuture<Void> result = AsyncUtils.NIL;
if (state == GdbState.STOPPED) {
if (gdbState == GdbState.STOPPED) {
Msg.debug(this, "Updating stack for " + this);
result = CompletableFuture.allOf(updateInfo(), stack.stateChanged(sco));
}
TargetExecutionState targetState = convertState(state);
changeAttributes(List.of(), Map.of( //
STATE_ATTRIBUTE_NAME, targetState //
STATE_ATTRIBUTE_NAME, state = convertState(gdbState), //
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay() //
), sco.getReason().desc());
return result;
}

View File

@ -128,22 +128,26 @@ public class GdbModelTargetThreadContainer
* =thread-exited, we will wind up invalidating that thread early.
*/
if (sco.getState() != GdbState.STOPPED) {
return AsyncUtils.NIL;
return updateThreadStates(sco);
}
return requestElements(false).thenCompose(__ -> {
AsyncFence fence = new AsyncFence();
for (GdbThread thread : inferior.getKnownThreads().values()) {
GdbModelTargetThread targetThread =
(GdbModelTargetThread) impl.getModelObject(thread);
fence.include(targetThread.stateChanged(sco));
}
return fence.ready();
return updateThreadStates(sco);
}).exceptionally(__ -> {
Msg.error(this, "Could not update threads " + this + " on STOPPED");
return null;
});
}
protected CompletableFuture<Void> updateThreadStates(GdbStateChangeRecord sco) {
AsyncFence fence = new AsyncFence();
for (GdbThread thread : inferior.getKnownThreads().values()) {
GdbModelTargetThread targetThread =
(GdbModelTargetThread) impl.getModelObject(thread);
fence.include(targetThread.stateChanged(sco));
}
return fence.ready();
}
public GdbModelTargetBreakpointLocation breakpointHit(GdbBreakpointHitReason reason) {
GdbThread thread = impl.gdb.getThread(reason.getThreadId());
return getTargetThread(thread).breakpointHit(reason);

View File

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

View File

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

View File

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

View File

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

View File

@ -15,9 +15,14 @@
*/
package agent.gdb.model.invm;
import static org.junit.Assume.assumeFalse;
import org.junit.Ignore;
import org.junit.Test;
import agent.gdb.model.AbstractModelForGdbInferiorAttacherTest;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
public class InVmModelForGdbInferiorAttacherTest extends AbstractModelForGdbInferiorAttacherTest {
@Override
@ -30,4 +35,21 @@ public class InVmModelForGdbInferiorAttacherTest extends AbstractModelForGdbInfe
public void testAttachableContainerIsWhereExpected() throws Throwable {
// 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.event.*;
import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.function.Function;
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.services.DebuggerTraceManagerService.BooleanChangeAdapter;
import ghidra.app.services.MarkerService;
import ghidra.async.AsyncUtils;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.util.PluginUtils;
import ghidra.program.database.ProgramContentHandler;
import ghidra.trace.model.Trace;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import resources.MultiIcon;
import resources.ResourceManager;
import resources.icons.RotateIcon;
@ -1546,7 +1549,13 @@ public interface DebuggerResources {
static <T> Function<Throwable, T> showError(Component parent, String message) {
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;
};
}

View File

@ -324,7 +324,7 @@ public abstract class AbstractDebuggerModelTest extends AbstractGhidraHeadedInte
assertEquals(TargetExecutionState.RUNNING, stateful.getExecutionState());
}, 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());
if (stateful != null) {
retryVoid(() -> {

View File

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