mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-638: Implementing multi-line commands for dbgeng
This commit is contained in:
parent
2d0743b785
commit
1695e3ae64
9 changed files with 98 additions and 36 deletions
|
@ -15,17 +15,15 @@
|
||||||
*/
|
*/
|
||||||
package agent.dbgeng.dbgeng;
|
package agent.dbgeng.dbgeng;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The interface for receiving input callbacks via {@code IDebugInputCallbacks} or a newer variant.
|
* 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.
|
* Note: The wrapper implementation will select the appropriate native interface version.
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface DebugInputCallbacks {
|
public interface DebugInputCallbacks {
|
||||||
CompletableFuture<String> startInput();
|
|
||||||
|
public void startInput(long bufferSize);
|
||||||
|
|
||||||
default void endInput() {
|
default void endInput() {
|
||||||
// Optional implementation
|
// Optional implementation
|
||||||
|
|
|
@ -15,10 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package agent.dbgeng.impl.dbgeng.io;
|
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.Pointer;
|
||||||
import com.sun.jna.platform.win32.Guid.REFIID;
|
import com.sun.jna.platform.win32.Guid.REFIID;
|
||||||
import com.sun.jna.platform.win32.WinDef.ULONG;
|
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.dbgeng.DebugInputCallbacks;
|
||||||
import agent.dbgeng.impl.dbgeng.client.DebugClientImpl1;
|
import agent.dbgeng.impl.dbgeng.client.DebugClientImpl1;
|
||||||
import agent.dbgeng.jna.dbgeng.io.*;
|
import agent.dbgeng.jna.dbgeng.io.*;
|
||||||
import ghidra.util.exception.CancelledException;
|
|
||||||
|
|
||||||
public class WrapCallbackIDebugInputCallbacks implements CallbackIDebugInputCallbacks {
|
public class WrapCallbackIDebugInputCallbacks implements CallbackIDebugInputCallbacks {
|
||||||
private final DebugClientImpl1 client;
|
private final DebugClientImpl1 client;
|
||||||
private final DebugInputCallbacks cb;
|
private final DebugInputCallbacks cb;
|
||||||
private ListenerIDebugInputCallbacks listener;
|
private ListenerIDebugInputCallbacks listener;
|
||||||
|
|
||||||
private final Set<CompletableFuture<String>> futures = new HashSet<>(); // TODO: Just one?
|
|
||||||
|
|
||||||
public WrapCallbackIDebugInputCallbacks(DebugClientImpl1 client, DebugInputCallbacks cb) {
|
public WrapCallbackIDebugInputCallbacks(DebugClientImpl1 client, DebugInputCallbacks cb) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.cb = cb;
|
this.cb = cb;
|
||||||
|
@ -82,23 +75,7 @@ public class WrapCallbackIDebugInputCallbacks implements CallbackIDebugInputCall
|
||||||
@Override
|
@Override
|
||||||
public HRESULT StartInput(ULONG BufferSize) {
|
public HRESULT StartInput(ULONG BufferSize) {
|
||||||
try {
|
try {
|
||||||
CompletableFuture<String> future = cb.startInput();
|
cb.startInput(BufferSize.longValue());
|
||||||
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;
|
|
||||||
});
|
|
||||||
return WinError.S_OK;
|
return WinError.S_OK;
|
||||||
}
|
}
|
||||||
catch (Throwable e) {
|
catch (Throwable e) {
|
||||||
|
@ -109,9 +86,6 @@ public class WrapCallbackIDebugInputCallbacks implements CallbackIDebugInputCall
|
||||||
@Override
|
@Override
|
||||||
public HRESULT EndInput() {
|
public HRESULT EndInput() {
|
||||||
try {
|
try {
|
||||||
for (CompletableFuture<String> future : futures) {
|
|
||||||
future.cancel(true);
|
|
||||||
}
|
|
||||||
cb.endInput();
|
cb.endInput();
|
||||||
return WinError.S_OK;
|
return WinError.S_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -244,4 +244,10 @@ public interface DbgEventsListener {
|
||||||
* @param mask class of output
|
* @param mask class of output
|
||||||
*/
|
*/
|
||||||
void consoleOutput(String output, int mask);
|
void consoleOutput(String output, int mask);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param prompt for console output
|
||||||
|
*/
|
||||||
|
void promptChanged(String prompt);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,4 +142,10 @@ public interface DbgEventsListenerAdapter extends DbgEventsListener {
|
||||||
public default void consoleOutput(String output, int mask) {
|
public default void consoleOutput(String output, int mask) {
|
||||||
// Extension point
|
// Extension point
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public default void promptChanged(String prompt) {
|
||||||
|
// Extension point
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -43,6 +43,7 @@ import agent.dbgeng.manager.cmd.*;
|
||||||
import agent.dbgeng.manager.evt.*;
|
import agent.dbgeng.manager.evt.*;
|
||||||
import agent.dbgeng.model.iface1.DbgModelTargetActiveScope;
|
import agent.dbgeng.model.iface1.DbgModelTargetActiveScope;
|
||||||
import agent.dbgeng.model.iface1.DbgModelTargetFocusScope;
|
import agent.dbgeng.model.iface1.DbgModelTargetFocusScope;
|
||||||
|
import agent.dbgeng.model.iface1.DbgModelTargetInterpreter;
|
||||||
import ghidra.async.*;
|
import ghidra.async.*;
|
||||||
import ghidra.comm.util.BitmaskSet;
|
import ghidra.comm.util.BitmaskSet;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
@ -110,6 +111,7 @@ public class DbgManagerImpl implements DbgManager {
|
||||||
private DbgThread eventThread;
|
private DbgThread eventThread;
|
||||||
private volatile boolean waiting = false;
|
private volatile boolean waiting = false;
|
||||||
private boolean kernelMode = false;
|
private boolean kernelMode = false;
|
||||||
|
private CompletableFuture<String> continuation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiate a new manager
|
* Instantiate a new manager
|
||||||
|
@ -370,6 +372,7 @@ public class DbgManagerImpl implements DbgManager {
|
||||||
status = dbgeng.getControl().getExecutionStatus();
|
status = dbgeng.getControl().getExecutionStatus();
|
||||||
dbgeng.setOutputCallbacks(new DbgDebugOutputCallbacks(this));
|
dbgeng.setOutputCallbacks(new DbgDebugOutputCallbacks(this));
|
||||||
dbgeng.setEventCallbacks(new DbgDebugEventCallbacksAdapter(this));
|
dbgeng.setEventCallbacks(new DbgDebugEventCallbacksAdapter(this));
|
||||||
|
dbgeng.setInputCallbacks(new DbgDebugInputCallbacks(this));
|
||||||
dbgeng.flushCallbacks();
|
dbgeng.flushCallbacks();
|
||||||
|
|
||||||
if (!create) {
|
if (!create) {
|
||||||
|
@ -1429,6 +1432,13 @@ public class DbgManagerImpl implements DbgManager {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> console(String command) {
|
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(
|
return execute(
|
||||||
new DbgConsoleExecCommand(this, command, DbgConsoleExecCommand.Output.CONSOLE))
|
new DbgConsoleExecCommand(this, command, DbgConsoleExecCommand.Output.CONSOLE))
|
||||||
.thenApply(e -> null);
|
.thenApply(e -> null);
|
||||||
|
@ -1498,4 +1508,7 @@ public class DbgManagerImpl implements DbgManager {
|
||||||
this.kernelMode = kernelMode;
|
this.kernelMode = kernelMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setContinuation(CompletableFuture<String> continuation) {
|
||||||
|
this.continuation = continuation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ import ghidra.dbg.target.TargetInterpreter;
|
||||||
*/
|
*/
|
||||||
public interface DbgModelTargetInterpreter extends DbgModelTargetObject, TargetInterpreter {
|
public interface DbgModelTargetInterpreter extends DbgModelTargetObject, TargetInterpreter {
|
||||||
|
|
||||||
|
public static final String DBG_PROMPT = "(kd)";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public default CompletableFuture<Void> execute(String cmd) {
|
public default CompletableFuture<Void> execute(String cmd) {
|
||||||
return getModel().gateFuture(getManager().console(cmd));
|
return getModel().gateFuture(getManager().console(cmd));
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package agent.dbgeng.model.iface2;
|
package agent.dbgeng.model.iface2;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import agent.dbgeng.dbgeng.DebugClient.DebugOutputFlags;
|
import agent.dbgeng.dbgeng.DebugClient.DebugOutputFlags;
|
||||||
|
@ -70,6 +72,13 @@ public interface DbgModelTargetSession extends //
|
||||||
getListeners().fire.consoleOutput(getProxy(), chan, output);
|
getListeners().fire.consoleOutput(getProxy(), chan, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public default void promptChanged(String prompt) {
|
||||||
|
changeAttributes(List.of(), Map.of( //
|
||||||
|
PROMPT_ATTRIBUTE_NAME, prompt //
|
||||||
|
), "Refreshed");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public default CompletableFuture<Void> setActive() {
|
public default CompletableFuture<Void> setActive() {
|
||||||
DbgManagerImpl manager = getManager();
|
DbgManagerImpl manager = getManager();
|
||||||
|
|
|
@ -22,15 +22,26 @@ import java.util.concurrent.CompletableFuture;
|
||||||
import agent.dbgeng.dbgeng.DebugSessionId;
|
import agent.dbgeng.dbgeng.DebugSessionId;
|
||||||
import agent.dbgeng.manager.*;
|
import agent.dbgeng.manager.*;
|
||||||
import agent.dbgeng.model.iface1.DbgModelSelectableObject;
|
import agent.dbgeng.model.iface1.DbgModelSelectableObject;
|
||||||
|
import agent.dbgeng.model.iface1.DbgModelTargetInterpreter;
|
||||||
import agent.dbgeng.model.iface2.DbgModelTargetProcessContainer;
|
import agent.dbgeng.model.iface2.DbgModelTargetProcessContainer;
|
||||||
import agent.dbgeng.model.iface2.DbgModelTargetSession;
|
import agent.dbgeng.model.iface2.DbgModelTargetSession;
|
||||||
import ghidra.dbg.target.schema.*;
|
import ghidra.dbg.target.schema.*;
|
||||||
import ghidra.dbg.util.PathUtils;
|
import ghidra.dbg.util.PathUtils;
|
||||||
|
|
||||||
@TargetObjectSchemaInfo(name = "Session", elements = {
|
@TargetObjectSchemaInfo(
|
||||||
@TargetElementType(type = Void.class) }, attributes = {
|
name = "Session",
|
||||||
@TargetAttributeType(name = "Attributes", type = DbgModelTargetSessionAttributesImpl.class, fixed = true),
|
elements = {
|
||||||
@TargetAttributeType(name = "Processes", type = DbgModelTargetProcessContainerImpl.class, required = true, fixed = true),
|
@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) })
|
@TargetAttributeType(type = Void.class) })
|
||||||
public class DbgModelTargetSessionImpl extends DbgModelTargetObjectImpl
|
public class DbgModelTargetSessionImpl extends DbgModelTargetObjectImpl
|
||||||
implements DbgModelTargetSession {
|
implements DbgModelTargetSession {
|
||||||
|
@ -71,7 +82,7 @@ public class DbgModelTargetSessionImpl extends DbgModelTargetObjectImpl
|
||||||
processes //
|
processes //
|
||||||
), Map.of( //
|
), Map.of( //
|
||||||
ACCESSIBLE_ATTRIBUTE_NAME, accessible, //
|
ACCESSIBLE_ATTRIBUTE_NAME, accessible, //
|
||||||
PROMPT_ATTRIBUTE_NAME, DBG_PROMPT, //
|
PROMPT_ATTRIBUTE_NAME, DbgModelTargetInterpreter.DBG_PROMPT, //
|
||||||
STATE_ATTRIBUTE_NAME, TargetExecutionState.ALIVE //
|
STATE_ATTRIBUTE_NAME, TargetExecutionState.ALIVE //
|
||||||
), "Initialized");
|
), "Initialized");
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue