GP-638: Implementing multi-line commands for dbgeng

This commit is contained in:
d-millar 2021-04-08 18:15:03 +00:00 committed by Dan
parent 2d0743b785
commit 1695e3ae64
9 changed files with 98 additions and 36 deletions

View File

@ -15,17 +15,15 @@
*/
package agent.dbgeng.dbgeng;
import java.util.concurrent.CompletableFuture;
/**
* The interface for receiving input callbacks via {@code IDebugInputCallbacks} or a newer variant.
*
* <p>
* Note: The wrapper implementation will select the appropriate native interface version.
*/
@FunctionalInterface
public interface DebugInputCallbacks {
CompletableFuture<String> startInput();
public void startInput(long bufferSize);
default void endInput() {
// Optional implementation

View File

@ -15,10 +15,6 @@
*/
package agent.dbgeng.impl.dbgeng.io;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Guid.REFIID;
import com.sun.jna.platform.win32.WinDef.ULONG;
@ -30,15 +26,12 @@ import com.sun.jna.ptr.PointerByReference;
import agent.dbgeng.dbgeng.DebugInputCallbacks;
import agent.dbgeng.impl.dbgeng.client.DebugClientImpl1;
import agent.dbgeng.jna.dbgeng.io.*;
import ghidra.util.exception.CancelledException;
public class WrapCallbackIDebugInputCallbacks implements CallbackIDebugInputCallbacks {
private final DebugClientImpl1 client;
private final DebugInputCallbacks cb;
private ListenerIDebugInputCallbacks listener;
private final Set<CompletableFuture<String>> futures = new HashSet<>(); // TODO: Just one?
public WrapCallbackIDebugInputCallbacks(DebugClientImpl1 client, DebugInputCallbacks cb) {
this.client = client;
this.cb = cb;
@ -82,23 +75,7 @@ public class WrapCallbackIDebugInputCallbacks implements CallbackIDebugInputCall
@Override
public HRESULT StartInput(ULONG BufferSize) {
try {
CompletableFuture<String> future = cb.startInput();
if (future == null) {
return WinError.S_OK;
}
future.handle((input, exc) -> {
if (exc == null) {
client.getControl().returnInput(input);
}
else if (exc instanceof CancelledException) {
// Normal if another client provides input
}
else {
client.getControl().errln("ERROR getting input: " + exc.getMessage());
}
futures.remove(future);
return null;
});
cb.startInput(BufferSize.longValue());
return WinError.S_OK;
}
catch (Throwable e) {
@ -109,9 +86,6 @@ public class WrapCallbackIDebugInputCallbacks implements CallbackIDebugInputCall
@Override
public HRESULT EndInput() {
try {
for (CompletableFuture<String> future : futures) {
future.cancel(true);
}
cb.endInput();
return WinError.S_OK;
}

View File

@ -244,4 +244,10 @@ public interface DbgEventsListener {
* @param mask class of output
*/
void consoleOutput(String output, int mask);
/**
* @param prompt for console output
*/
void promptChanged(String prompt);
}

View File

@ -142,4 +142,10 @@ public interface DbgEventsListenerAdapter extends DbgEventsListener {
public default void consoleOutput(String output, int mask) {
// Extension point
}
@Override
public default void promptChanged(String prompt) {
// Extension point
}
}

View File

@ -0,0 +1,43 @@
/* ###
* 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.impl;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import agent.dbgeng.dbgeng.DebugInputCallbacks;
public class DbgDebugInputCallbacks implements DebugInputCallbacks {
private DbgManagerImpl manager;
public DbgDebugInputCallbacks(DbgManagerImpl manager) {
this.manager = manager;
}
@Override
public void startInput(long bufsize) {
manager.getEventListeners().fire.promptChanged(">>>");
CompletableFuture<String> cf = new CompletableFuture<String>();
try {
manager.setContinuation(cf);
manager.getControl().returnInput(cf.get());
}
catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}

View File

@ -43,6 +43,7 @@ import agent.dbgeng.manager.cmd.*;
import agent.dbgeng.manager.evt.*;
import agent.dbgeng.model.iface1.DbgModelTargetActiveScope;
import agent.dbgeng.model.iface1.DbgModelTargetFocusScope;
import agent.dbgeng.model.iface1.DbgModelTargetInterpreter;
import ghidra.async.*;
import ghidra.comm.util.BitmaskSet;
import ghidra.dbg.target.TargetObject;
@ -110,6 +111,7 @@ public class DbgManagerImpl implements DbgManager {
private DbgThread eventThread;
private volatile boolean waiting = false;
private boolean kernelMode = false;
private CompletableFuture<String> continuation;
/**
* Instantiate a new manager
@ -370,6 +372,7 @@ public class DbgManagerImpl implements DbgManager {
status = dbgeng.getControl().getExecutionStatus();
dbgeng.setOutputCallbacks(new DbgDebugOutputCallbacks(this));
dbgeng.setEventCallbacks(new DbgDebugEventCallbacksAdapter(this));
dbgeng.setInputCallbacks(new DbgDebugInputCallbacks(this));
dbgeng.flushCallbacks();
if (!create) {
@ -1429,6 +1432,13 @@ public class DbgManagerImpl implements DbgManager {
@Override
public CompletableFuture<Void> console(String command) {
if (continuation != null) {
String prompt = command.equals("") ? DbgModelTargetInterpreter.DBG_PROMPT : ">>>";
getEventListeners().fire.promptChanged(prompt);
continuation.complete(command);
setContinuation(null);
return AsyncUtils.NIL;
}
return execute(
new DbgConsoleExecCommand(this, command, DbgConsoleExecCommand.Output.CONSOLE))
.thenApply(e -> null);
@ -1498,4 +1508,7 @@ public class DbgManagerImpl implements DbgManager {
this.kernelMode = kernelMode;
}
public void setContinuation(CompletableFuture<String> continuation) {
this.continuation = continuation;
}
}

View File

@ -29,6 +29,8 @@ import ghidra.dbg.target.TargetInterpreter;
*/
public interface DbgModelTargetInterpreter extends DbgModelTargetObject, TargetInterpreter {
public static final String DBG_PROMPT = "(kd)";
@Override
public default CompletableFuture<Void> execute(String cmd) {
return getModel().gateFuture(getManager().console(cmd));

View File

@ -15,6 +15,8 @@
*/
package agent.dbgeng.model.iface2;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import agent.dbgeng.dbgeng.DebugClient.DebugOutputFlags;
@ -70,6 +72,13 @@ public interface DbgModelTargetSession extends //
getListeners().fire.consoleOutput(getProxy(), chan, output);
}
@Override
public default void promptChanged(String prompt) {
changeAttributes(List.of(), Map.of( //
PROMPT_ATTRIBUTE_NAME, prompt //
), "Refreshed");
}
@Override
public default CompletableFuture<Void> setActive() {
DbgManagerImpl manager = getManager();

View File

@ -22,15 +22,26 @@ import java.util.concurrent.CompletableFuture;
import agent.dbgeng.dbgeng.DebugSessionId;
import agent.dbgeng.manager.*;
import agent.dbgeng.model.iface1.DbgModelSelectableObject;
import agent.dbgeng.model.iface1.DbgModelTargetInterpreter;
import agent.dbgeng.model.iface2.DbgModelTargetProcessContainer;
import agent.dbgeng.model.iface2.DbgModelTargetSession;
import ghidra.dbg.target.schema.*;
import ghidra.dbg.util.PathUtils;
@TargetObjectSchemaInfo(name = "Session", elements = {
@TargetElementType(type = Void.class) }, attributes = {
@TargetAttributeType(name = "Attributes", type = DbgModelTargetSessionAttributesImpl.class, fixed = true),
@TargetAttributeType(name = "Processes", type = DbgModelTargetProcessContainerImpl.class, required = true, fixed = true),
@TargetObjectSchemaInfo(
name = "Session",
elements = {
@TargetElementType(type = Void.class) },
attributes = {
@TargetAttributeType(
name = "Attributes",
type = DbgModelTargetSessionAttributesImpl.class,
fixed = true),
@TargetAttributeType(
name = "Processes",
type = DbgModelTargetProcessContainerImpl.class,
required = true,
fixed = true),
@TargetAttributeType(type = Void.class) })
public class DbgModelTargetSessionImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetSession {
@ -71,7 +82,7 @@ public class DbgModelTargetSessionImpl extends DbgModelTargetObjectImpl
processes //
), Map.of( //
ACCESSIBLE_ATTRIBUTE_NAME, accessible, //
PROMPT_ATTRIBUTE_NAME, DBG_PROMPT, //
PROMPT_ATTRIBUTE_NAME, DbgModelTargetInterpreter.DBG_PROMPT, //
STATE_ATTRIBUTE_NAME, TargetExecutionState.ALIVE //
), "Initialized");