mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GP-909: Adjusting how GDB resumes/steps to assure interrupts work.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public abstract class AbstractModelForGdbInterpreterTest
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DebuggerTestSpecimen getAttachSpecimen() {
|
public DebuggerTestSpecimen getAttachSpecimen() {
|
||||||
return GdbLinuxSpecimen.DD;
|
return GdbLinuxSpecimen.SLEEP;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(() -> {
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user