GP-617: Fixing stepping. Fixes for object tree.

This commit is contained in:
d-millar 2021-01-22 17:50:10 +00:00 committed by Dan
parent 97b43a4c4e
commit 3ae09277f0
40 changed files with 773 additions and 260 deletions

View file

@ -41,7 +41,7 @@ public interface DebugClient extends DebugClientReentrant {
STEP_INTO(true, ExecutionState.RUNNING, 5), //
BREAK(false, ExecutionState.STOPPED, 0), //
NO_DEBUGGEE(true, null, 1), // shouldWait is true to handle process creation
STEP_BRANCH(true, null, 6), //
STEP_BRANCH(true, ExecutionState.RUNNING, 6), //
IGNORE_EVENT(false, null, 11), //
RESTART_REQUESTED(true, null, 12), //
REVERSE_GO(true, null, 0xff), //

View file

@ -45,7 +45,9 @@ public interface DbgManager extends AutoCloseable, DbgBreakpointInsertions {
/** Equivalent to {@code stepi} in the CLI */
STEP_INSTRUCTION("step-instruction"),
/** Equivalent to {@code until} in the CLI */
UNTIL("until"),;
UNTIL("until"),
/** Equivalent to {@code ext} in the CLI */
EXTENDED("ext"),;
final String str;

View file

@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
import agent.dbgeng.dbgeng.DebugProcessId;
import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.impl.DbgSectionImpl;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.TargetAttachable;
@ -182,6 +183,32 @@ public interface DbgProcess extends DbgMemoryOperations {
*/
CompletableFuture<Void> cont();
/**
* Step the process
*
* Note that the command can complete before the process has finished stepping. The command
* completes as soon as the process is running. A separate stop event is emitted when the step is
* completed.
*
* @param suffix specifies how far to step, or on what conditions stepping ends.
*
* @return a future that completes once the process is running
*/
CompletableFuture<Void> step(ExecSuffix suffix);
/**
* Step the process
*
* Note that the command can complete before the process has finished stepping. The command
* completes as soon as the process is running. A separate stop event is emitted when the step is
* completed.
*
* @param args specifies how far to step, or on what conditions stepping ends.
*
* @return a future that completes once the process is running
*/
CompletableFuture<Void> step(Map<String, ?> args);
/**
* Evaluate an expression
*

View file

@ -16,6 +16,7 @@
package agent.dbgeng.manager;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import agent.dbgeng.dbgeng.DebugThreadId;
@ -106,6 +107,19 @@ public interface DbgThread
*/
CompletableFuture<Void> step(ExecSuffix suffix);
/**
* Step the thread
*
* Note that the command can complete before the thread has finished stepping. The command
* completes as soon as the thread is running. A separate stop event is emitted when the step is
* completed.
*
* @param args specifies how far to step, or on what conditions stepping ends.
*
* @return a future that completes once the thread is running
*/
CompletableFuture<Void> step(Map<String, ?> args);
/**
* Detach from the entire process
*

View file

@ -20,6 +20,7 @@ import agent.dbgeng.manager.DbgProcess;
import agent.dbgeng.manager.impl.DbgManagerImpl;
public class DbgProcessSelectCommand extends AbstractDbgCommand<Void> {
private DbgProcess process;
/**

View file

@ -55,7 +55,7 @@ public class DbgReadRegistersCommand extends AbstractDbgCommand<Map<DbgRegister,
}
}
}
so.setCurrentThreadId(previous);
//so.setCurrentThreadId(previous);
return result;
}

View file

@ -63,6 +63,6 @@ public class DbgStackListFramesCommand extends AbstractDbgCommand<List<DbgStackF
tf.Params[3].longValue());
result.add(frame);
}
so.setCurrentThreadId(previous);
//so.setCurrentThreadId(previous);
}
}

View file

@ -15,25 +15,40 @@
*/
package agent.dbgeng.manager.cmd;
import agent.dbgeng.dbgeng.DebugClient.DebugStatus;
import java.util.Map;
import agent.dbgeng.dbgeng.DebugControl;
import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.manager.DbgEvent;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.manager.evt.*;
import agent.dbgeng.manager.impl.DbgManagerImpl;
import agent.dbgeng.manager.impl.DbgThreadImpl;
import ghidra.util.Msg;
/**
* Implementation of {@link DbgThread#stepInstruction()}
*/
public class DbgStepCommand extends AbstractDbgCommand<Void> {
protected final ExecSuffix suffix;
public DbgStepCommand(DbgManagerImpl manager, ExecSuffix suffix) {
private DebugThreadId id;
protected final ExecSuffix suffix;
private String lastCommand = "tct";
public DbgStepCommand(DbgManagerImpl manager, DebugThreadId id, ExecSuffix suffix) {
super(manager);
this.id = id;
this.suffix = suffix;
}
public DbgStepCommand(DbgManagerImpl manager, DebugThreadId id, Map<String, ?> args) {
super(manager);
this.id = id;
this.suffix = ExecSuffix.EXTENDED;
this.lastCommand = (String) args.get("Command");
}
@Override
public boolean handle(DbgEvent<?> evt, DbgPendingCommand<?> pending) {
if (evt instanceof AbstractDbgCompletedCommandEvent && pending.getCommand().equals(this)) {
@ -48,17 +63,53 @@ public class DbgStepCommand extends AbstractDbgCommand<Void> {
return false;
}
// NB: Would really prefer to do this through the API, but the API does
// not appear to support freeze/unfreeze and suspend/resume thread. These appear
// to be applied via the kernel32 API. Worse, the Windbg/KD API appears to lack
// commands to query the freeze/suspend count for a given thread. Rather than
// wrestle with the underlying API, we're going to just use the WIndbg commands.
// Note that the thread-restricted form is used iff we're stepping a thread other
// then the event thread.
@Override
public void invoke() {
String cmd = "";
String prefix = id == null ? "" : "~" + id.id + " ";
DebugControl control = manager.getControl();
if (suffix.equals(ExecSuffix.STEP_INSTRUCTION)) {
control.setExecutionStatus(DebugStatus.STEP_INTO);
cmd = "t";
//control.setExecutionStatus(DebugStatus.STEP_INTO);
}
else if (suffix.equals(ExecSuffix.NEXT_INSTRUCTION)) {
control.setExecutionStatus(DebugStatus.STEP_OVER);
cmd = "p";
//control.setExecutionStatus(DebugStatus.STEP_OVER);
}
else if (suffix.equals(ExecSuffix.FINISH)) {
control.setExecutionStatus(DebugStatus.STEP_BRANCH);
cmd = "gu";
//control.setExecutionStatus(DebugStatus.STEP_BRANCH);
}
else if (suffix.equals(ExecSuffix.EXTENDED)) {
cmd = getLastCommand();
}
DbgThreadImpl eventThread = manager.getEventThread();
if (eventThread != null && eventThread.getId().equals(id)) {
control.execute(cmd);
}
else {
if (manager.isKernelMode()) {
Msg.info(this, "Thread-specific stepping ignored in kernel-mode");
control.execute(cmd);
}
else {
control.execute(prefix + cmd);
}
}
}
public String getLastCommand() {
return lastCommand;
}
public void setLastCommand(String lastCommand) {
this.lastCommand = lastCommand;
}
}

View file

@ -0,0 +1,73 @@
/* ###
* 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.dbgeng.manager.cmd;
import agent.dbgeng.dbgeng.DebugControl;
import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.manager.impl.DbgManagerImpl;
public class DbgThreadHoldCommand extends AbstractDbgCommand<Void> {
static final String FREEZE_ALL_THREADS_COMMAND = "~* f";
static final String FREEZE_CURRENT_THREAD_COMMAND = "~. f";
static final String UNFREEZE_CURRENT_THREAD_COMMAND = "~. u";
static final String UNFREEZE_ALL_THREADS_COMMAND = "~* u";
static final String SUSPEND_ALL_THREADS_COMMAND = "~* n";
static final String SUSPEND_CURRENT_THREAD_COMMAND = "~. n";
static final String RESUME_CURRENT_THREAD_COMMAND = "~. m";
static final String RESUME_ALL_THREADS_COMMAND = "~* m";
static final Boolean preferFreeze = true;
private DbgThread thread;
private Boolean set;
/**
* Select the given thread and frame level
*
* To simply select a thread, you should use frame 0 as the default.
*
* @param manager the manager to execute the command
* @param thread the desired thread
* @param set hold or release
*/
public DbgThreadHoldCommand(DbgManagerImpl manager, DbgThread thread, Boolean set) {
super(manager);
this.thread = thread;
this.set = set;
}
@Override
public void invoke() {
DebugThreadId id = thread.getId();
if (id != null) {
manager.getSystemObjects().setCurrentThreadId(id);
if (!manager.isKernelMode()) {
DebugControl control = manager.getControl();
if (preferFreeze) {
control.execute(
set ? FREEZE_CURRENT_THREAD_COMMAND : UNFREEZE_CURRENT_THREAD_COMMAND);
}
else {
control.execute(
set ? SUSPEND_CURRENT_THREAD_COMMAND : RESUME_CURRENT_THREAD_COMMAND);
}
}
}
}
}

View file

@ -20,6 +20,7 @@ import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.manager.impl.DbgManagerImpl;
public class DbgThreadSelectCommand extends AbstractDbgCommand<Void> {
private DbgThread thread;
/**

View file

@ -66,6 +66,6 @@ public class DbgWriteRegistersCommand extends AbstractDbgCommand<Void> {
}
}
registers.setValues(DebugRegisterSource.DEBUG_REGSRC_DEBUGGEE, values);
so.setCurrentThreadId(previous);
//so.setCurrentThreadId(previous);
}
}

View file

@ -28,6 +28,7 @@ import com.google.common.collect.RangeSet;
import agent.dbgeng.dbgeng.*;
import agent.dbgeng.dbgeng.DebugClient.DebugAttachFlags;
import agent.dbgeng.manager.*;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.cmd.*;
import ghidra.async.TypeSpec;
import ghidra.comm.util.BitmaskSet;
@ -304,6 +305,24 @@ public class DbgProcessImpl implements DbgProcess {
}).finish();
}
@Override
public CompletableFuture<Void> step(ExecSuffix suffix) {
return sequence(TypeSpec.VOID).then((seq) -> {
select().handle(seq::next);
}).then((seq) -> {
manager.execute(new DbgStepCommand(manager, null, suffix)).handle(seq::exit);
}).finish();
}
@Override
public CompletableFuture<Void> step(Map<String, ?> args) {
return sequence(TypeSpec.VOID).then((seq) -> {
select().handle(seq::next);
}).then((seq) -> {
manager.execute(new DbgStepCommand(manager, null, args)).handle(seq::exit);
}).finish();
}
protected <T> CompletableFuture<T> preferThread(
Function<DbgThreadImpl, CompletableFuture<T>> viaThread,
Supplier<CompletableFuture<T>> viaThis) {

View file

@ -220,7 +220,16 @@ public class DbgThreadImpl implements DbgThread {
return sequence(TypeSpec.VOID).then((seq) -> {
select().handle(seq::next);
}).then((seq) -> {
manager.execute(new DbgStepCommand(manager, suffix)).handle(seq::exit);
manager.execute(new DbgStepCommand(manager, id, suffix)).handle(seq::exit);
}).finish();
}
@Override
public CompletableFuture<Void> step(Map<String, ?> args) {
return sequence(TypeSpec.VOID).then((seq) -> {
select().handle(seq::next);
}).then((seq) -> {
manager.execute(new DbgStepCommand(manager, id, args)).handle(seq::exit);
}).finish();
}

View file

@ -15,11 +15,12 @@
*/
package agent.dbgeng.model.iface1;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.model.iface2.DbgModelTargetObject;
import agent.dbgeng.model.iface2.*;
import ghidra.dbg.target.TargetSteppable;
/**
@ -48,6 +49,8 @@ public interface DbgModelTargetSteppable<T extends TargetSteppable<T>>
return ExecSuffix.RETURN;
case UNTIL:
return ExecSuffix.UNTIL;
case EXTENDED:
return ExecSuffix.EXTENDED;
default:
throw new AssertionError();
}
@ -62,7 +65,21 @@ public interface DbgModelTargetSteppable<T extends TargetSteppable<T>>
case ADVANCE: // Why no exec-advance in dbgeng?
return thread.console("advance");
default:
if (this instanceof DbgModelTargetThread) {
DbgModelTargetThread targetThread = (DbgModelTargetThread) this;
return targetThread.getThread().step(convertToDbg(kind));
}
if (this instanceof DbgModelTargetProcess) {
DbgModelTargetProcess targetProcess = (DbgModelTargetProcess) this;
return targetProcess.getProcess().step(convertToDbg(kind));
}
return thread.step(convertToDbg(kind));
}
}
default CompletableFuture<Void> step(Map<String, ?> args) {
DbgThread thread = getManager().getCurrentThread();
return thread.step(args);
}
}

View file

@ -180,17 +180,21 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl
@Override
public CompletableFuture<Void> step(TargetStepKind kind) {
DbgThread thread = getManager().getCurrentThread();
switch (kind) {
case SKIP:
throw new UnsupportedOperationException(kind.name());
case ADVANCE: // Why no exec-advance in dbgeng?
return thread.console("advance");
throw new UnsupportedOperationException(kind.name());
default:
return thread.step(convertToDbg(kind));
return process.step(convertToDbg(kind));
}
}
@Override
public CompletableFuture<Void> step(Map<String, ?> args) {
return process.step(args);
}
@Override
public void processStarted(Long pid) {
if (pid != null) {

View file

@ -87,9 +87,24 @@ public class DbgModelTargetRootImpl extends DbgModelDefaultTargetModelRoot
boolean doFire;
synchronized (this) {
doFire = !Objects.equals(this.focus, sel);
this.focus = sel;
if (doFire && focus != null) {
List<String> focusPath = focus.getPath();
List<String> selPath = sel.getPath();
for (int i = 0; i < focusPath.size(); i++) {
if (i >= selPath.size()) {
doFire = false;
break;
}
if (!focusPath.get(i).equals(selPath.get(i))) {
doFire = true;
break;
}
}
//doFire = !focusPath.containsAll(selPath);
}
}
if (doFire) {
this.focus = sel;
changeAttributes(List.of(), List.of(), Map.of( //
TargetFocusScope.FOCUS_ATTRIBUTE_NAME, focus //
), "Focus changed");

View file

@ -22,7 +22,6 @@ import java.util.concurrent.atomic.AtomicReference;
import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.manager.*;
import agent.dbgeng.manager.DbgManager.ExecSuffix;
import agent.dbgeng.manager.cmd.DbgThreadSelectCommand;
import agent.dbgeng.manager.impl.DbgManagerImpl;
import agent.dbgeng.model.iface1.DbgModelTargetFocusScope;
@ -46,8 +45,14 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetThread {
protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( //
TargetStepKind.ADVANCE, TargetStepKind.FINISH, TargetStepKind.LINE, TargetStepKind.OVER,
TargetStepKind.OVER_LINE, TargetStepKind.RETURN, TargetStepKind.UNTIL);
TargetStepKind.ADVANCE, //
TargetStepKind.FINISH, //
TargetStepKind.LINE, //
TargetStepKind.OVER, //
TargetStepKind.OVER_LINE, //
TargetStepKind.RETURN, //
TargetStepKind.UNTIL, //
TargetStepKind.EXTENDED);
protected static String indexThread(DebugThreadId debugThreadId) {
return PathUtils.makeIndex(debugThreadId.id);
@ -117,33 +122,12 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl
TargetExecutionState targetState = convertState(state);
String executionType = thread.getExecutingProcessorType().description;
changeAttributes(List.of(), List.of(), Map.of( //
STATE_ATTRIBUTE_NAME, targetState, //
TargetEnvironment.ARCH_ATTRIBUTE_NAME, executionType //
), reason.desc());
setExecutionState(targetState, reason.desc());
}
@Override
public ExecSuffix convertToDbg(TargetStepKind kind) {
switch (kind) {
case FINISH:
return ExecSuffix.FINISH;
case INTO:
return ExecSuffix.STEP_INSTRUCTION;
case LINE:
return ExecSuffix.STEP;
case OVER:
return ExecSuffix.NEXT_INSTRUCTION;
case OVER_LINE:
return ExecSuffix.NEXT;
case RETURN:
return ExecSuffix.RETURN;
case UNTIL:
return ExecSuffix.UNTIL;
default:
throw new AssertionError();
}
}
@Override
public CompletableFuture<Void> step(TargetStepKind kind) {
switch (kind) {
@ -156,6 +140,11 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl
}
}
@Override
public CompletableFuture<Void> step(Map<String, ?> args) {
return thread.step(args);
}
@Override
public CompletableFuture<Void> select() {
DbgManagerImpl manager = getManager();