flags);
+
+ void setFlags(BreakFlags... flags);
+
+ /**
+ * Get the location on target that triggers the breakpoint
+ *
+ *
+ * If the breakpoint is deferred, this will return {@code null}. In that case, use
+ * {@link #getOffsetExpression()}.
+ *
+ * @return the offset, or {@code null}
+ */
+ Long getOffset();
+
+ void setOffset(long offset);
+
+ String getOffsetExpression();
+
+ void setOffsetExpression(String expression);
+
+ BreakDataParameters getDataParameters();
+
+ void setDataParameters(BreakDataParameters params);
+
+ void setDataParameters(int size, BitmaskSet access);
+
+ void setDataParameters(int size, BreakAccess... access);
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugClient.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugClient.java
new file mode 100644
index 0000000000..b4cac3b939
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugClient.java
@@ -0,0 +1,398 @@
+/* ###
+ * 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.dbgeng;
+
+import java.util.List;
+
+import com.sun.jna.platform.win32.WinBase;
+
+import agent.dbgeng.dbgeng.DebugRunningProcess.Description;
+import agent.dbgeng.dbgeng.DebugRunningProcess.Description.ProcessDescriptionFlags;
+import ghidra.comm.util.BitmaskSet;
+import ghidra.comm.util.BitmaskUniverse;
+
+/**
+ * A wrapper for {@code IDebugClient} and its newer variants.
+ */
+public interface DebugClient extends DebugClientReentrant {
+ public static enum ExecutionState {
+ RUNNING, STOPPED;
+ }
+
+ public static enum DebugStatus {
+ NO_CHANGE(false, null, 13), //
+ GO(true, ExecutionState.RUNNING, 10), //
+ GO_HANDLED(true, ExecutionState.RUNNING, 9), //
+ GO_NOT_HANDLED(true, ExecutionState.RUNNING, 8), //
+ STEP_OVER(true, ExecutionState.RUNNING, 7), //
+ 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, ExecutionState.RUNNING, 6), //
+ IGNORE_EVENT(false, null, 11), //
+ RESTART_REQUESTED(true, null, 12), //
+ REVERSE_GO(true, null, 0xff), //
+ REVERSE_STEP_BRANCH(true, null, 0xff), //
+ REVERSE_STEP_OVER(true, null, 0xff), //
+ REVERSE_STEP_INTO(true, null, 0xff), //
+ OUT_OF_SYNC(false, null, 2), //
+ WAIT_INPUT(false, null, 3), //
+ TIMEOUT(false, null, 4), //
+ ;
+
+ public static final long MASK = 0xaf;
+ public static final long INSIDE_WAIT = 0x100000000L;
+ public static final long WAIT_TIMEOUT = 0x200000000L;
+
+ DebugStatus(boolean shouldWait, ExecutionState threadState, int precedence) {
+ this.shouldWait = shouldWait;
+ this.threadState = threadState;
+ this.precedence = precedence;
+ }
+
+ public final boolean shouldWait;
+ public final ExecutionState threadState;
+ public final int precedence; // 0 is highest
+
+ public static DebugStatus fromArgument(long argument) {
+ return values()[(int) (argument & MASK)];
+ }
+
+ public static boolean isInsideWait(long argument) {
+ return (argument & INSIDE_WAIT) != 0;
+ }
+
+ public static boolean isWaitTimeout(long argument) {
+ return (argument & WAIT_TIMEOUT) != 0;
+ }
+ }
+
+ public static enum SessionStatus {
+ ACTIVE, //
+ END_SESSION_ACTIVE_TERMINATE,//
+ END_SESSION_ACTIVE_DETACH, //
+ END_SESSION_PASSIVE, //
+ END, //
+ REBOOT, //
+ HIBERNATE, //
+ FAILURE, //
+ ;
+ }
+
+ public static enum ChangeDebuggeeState implements BitmaskUniverse {
+ ALL(0xffffffff), //
+ REGISTERS(1 << 0), //
+ DATA(1 << 1), //
+ REFRESH(1 << 2), //
+ ;
+
+ private ChangeDebuggeeState(int mask) {
+ this.mask = mask;
+ }
+
+ private final int mask;
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+ }
+
+ public static enum ChangeEngineState implements BitmaskUniverse {
+ ALL(0xffffffff), //
+ CURRENT_THREAD(1 << 0), //
+ EFFECTIVE_PROCESSOR(1 << 1), //
+ BREAKPOINTS(1 << 2), //
+ CODE_LEVEL(1 << 3), //
+ EXECUTION_STATUS(1 << 4), //
+ ENGINE_OPTIONS(1 << 5), //
+ LOG_FILE(1 << 6), //
+ RADIX(1 << 7), //
+ EVENT_FILTERS(1 << 8), //
+ PROCESS_OPTIONS(1 << 9), //
+ EXTENSIONS(1 << 10), //
+ SYSTEMS(1 << 11), //
+ ASSEMBLY_OPTIONS(1 << 12), //
+ EXPRESSION_SYNTAX(1 << 13), //
+ TEXT_REPLACEMENTS(1 << 14), //
+ ;
+
+ private ChangeEngineState(int mask) {
+ this.mask = mask;
+ }
+
+ private final int mask;
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+ }
+
+ public static enum ChangeSymbolState implements BitmaskUniverse {
+ ALL(0xffffffff), //
+ LOADS(1 << 0), //
+ UNLOADS(1 << 1), //
+ SCOPE(1 << 2), //
+ PATHS(1 << 3), //
+ SYMBOL_OPTIONS(1 << 4), //
+ TYPE_OPTIONS(1 << 5), //
+ ;
+
+ private ChangeSymbolState(int mask) {
+ this.mask = mask;
+ }
+
+ private final int mask;
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+ }
+
+ public static enum DebugAttachFlags implements BitmaskUniverse {
+ DEFAULT(0), //
+ NONINVASIVE(1 << 0), //
+ EXISTING(1 << 1), //
+ NONINVASIVE_NO_SUSPEND(1 << 2), //
+ INVASIVE_NO_INITIAL_BREAK(1 << 3), //
+ INVASIVE_RESUME_PROCESS(1 << 4), //
+ NONINVASIVE_ALLOW_PARTIAL(1 << 5), //
+ ;
+
+ DebugAttachFlags(int mask) {
+ this.mask = mask;
+ }
+
+ private final int mask;
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+ }
+
+ public static enum DebugCreateFlags implements BitmaskUniverse {
+ DEBUG_PROCESS(WinBase.DEBUG_PROCESS), //
+ DEBUG_ONLY_THIS_PROCESS(WinBase.DEBUG_ONLY_THIS_PROCESS), //
+ CREATE_SUSPENDED(WinBase.CREATE_SUSPENDED), //
+ DETACHED_PROCESS(WinBase.DETACHED_PROCESS), //
+
+ CREATE_NEW_CONSOLE(WinBase.CREATE_NEW_CONSOLE), //
+ //NORMAL_PRIORITY_CLASS(WinBase.NORMAL_PRIORITY_CLASS), //
+ //IDLE_PRIORITY_CLASS(WinBase.IDLE_PRIORITY_CLASS), //
+ //HIGH_PRIORITY_CLASS(WinBase.HIGH_PRIORITY_CLASS), //
+
+ //REALTIME_PRIORITY_CLASS(WinBase.REALTIME_PRIORITY_CLASS), //
+ CREATE_NEW_PROCESS_GROUP(WinBase.CREATE_NEW_PROCESS_GROUP), //
+ CREATE_UNICODE_ENVIRONMENT(WinBase.CREATE_UNICODE_ENVIRONMENT), //
+ CREATE_SEPARATE_WOW_VDM(WinBase.CREATE_SEPARATE_WOW_VDM), //
+
+ CREATE_SHARED_WOW_VDM(WinBase.CREATE_SHARED_WOW_VDM), //
+ CREATE_FORCEDOS(WinBase.CREATE_FORCEDOS), //
+ //BELOW_NORMAL_PRIORITY_CLASS(WinBase.BELOW_NORMAL_PRIORITY_CLASS), //
+ //ABOVE_NORMAL_PRIORITY_CLASS(WinBase.ABOVE_NORMAL_PRIORITY_CLASS), //
+
+ INHERIT_PARENT_AFFINITY(WinBase.INHERIT_PARENT_AFFINITY), //
+ //INHERIT_CALLER_PRIORITY(WinBase.INHERIT_CALLER_PRIORITY), //
+ CREATE_PROTECTED_PROCESS(WinBase.CREATE_PROTECTED_PROCESS), //
+ EXTENDED_STARTUPINFO_PRESENT(WinBase.EXTENDED_STARTUPINFO_PRESENT), //
+
+ //PROCESS_MODE_BACKGROUND_BEGIN(WinBase.PROCESS_MODE_BACKGROUND_BEGIN), //
+ //PROCESS_MODE_BACKGROUND_END(WinBase.PROCESS_MODE_BACKGROUND_END), //
+
+ CREATE_BREAKAWAY_FROM_JOB(WinBase.CREATE_BREAKAWAY_FROM_JOB), //
+ CREATE_PRESERVE_CODE_AUTHZ_LEVEL(WinBase.CREATE_PRESERVE_CODE_AUTHZ_LEVEL), //
+ CREATE_DEFAULT_ERROR_MODE(WinBase.CREATE_DEFAULT_ERROR_MODE), //
+ CREATE_NO_WINDOW(WinBase.CREATE_NO_WINDOW), //
+
+ //PROFILE_USER(WinBase.PROFILE_USER), //
+ //PROFILE_KERNEL(WinBase.PROFILE_KERNEL), //
+ //PROFILE_SERVER(WinBase.PROFILE_SERVER), //
+ //CREATE_IGNORE_SYSTEM_DEFAULT(WinBase.CREATE_IGNORE_SYSTEM_DEFAULT), //
+ DEBUG_CREATE_NO_DEBUG_HEAP(0x00000400), //
+ DEBUG_CREATE_THROUGH_RTL(0x00010000), //
+ ;
+
+ DebugCreateFlags(int mask) {
+ this.mask = mask;
+ }
+
+ private final int mask;
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+ }
+
+ public enum DebugEndSessionFlags {
+ DEBUG_END_PASSIVE(0x00000000),
+ DEBUG_END_ACTIVE_TERMINATE(0x00000001),
+ DEBUG_END_ACTIVE_DETACH(0x00000002),
+ DEBUG_END_REENTRANT(0x00000003),
+ DEBUG_END_DISCONNECT(0x00000004);
+
+ DebugEndSessionFlags(int value) {
+ this.value = value;
+ }
+
+ private final int value;
+
+ public long getValue() {
+ return value;
+ }
+ }
+
+ public enum DebugOutputFlags {
+ DEBUG_OUTPUT_NORMAL(0x1), //
+ DEBUG_OUTPUT_ERROR(0x2), //
+ DEBUG_OUTPUT_WARNING(0x4), //
+ DEBUG_OUTPUT_VERBOSE(0x8), //
+ DEBUG_OUTPUT_PROMPT(0x10), //
+ DEBUG_OUTPUT_PROMPT_REGISTERS(0x20), //
+ DEBUG_OUTPUT_EXTENSION_WARNING(0x40), //
+ DEBUG_OUTPUT_DEBUGGEE(0x80), //
+ DEBUG_OUTPUT_DEBUGGEE_PROMPT(0x100), //
+ DEBUG_OUTPUT_SYMBOLS(0x200);
+
+ DebugOutputFlags(int value) {
+ this.value = value;
+ }
+
+ private final int value;
+
+ public long getValue() {
+ return value;
+ }
+ }
+
+ /**
+ * Obtain the advanced interface to this client.
+ *
+ * @return the advanced interface
+ */
+ DebugAdvanced getAdvanced();
+
+ /**
+ * Obtain the control interface to this client
+ *
+ * @return the control interface
+ */
+ @Override
+ DebugControl getControl();
+
+ /**
+ * Obtain the data spaces interface to this client
+ *
+ * @return the data spaces interface
+ */
+ DebugDataSpaces getDataSpaces();
+
+ /**
+ * Obtain the registers interface to this client
+ *
+ * @return the registers interface
+ */
+ DebugRegisters getRegisters();
+
+ /**
+ * Obtain the symbols interface to this client
+ *
+ * @return the symbols interface
+ */
+ DebugSymbols getSymbols();
+
+ /**
+ * Obtain the system objects interface to this client
+ *
+ * @return the system objects interface
+ */
+ DebugSystemObjects getSystemObjects();
+
+ /**
+ * The the ID for the local server
+ *
+ * @return the ID
+ */
+ DebugServerId getLocalServer();
+
+ void attachKernel(long flags, String options);
+
+ void startProcessServer(String options);
+
+ DebugServerId connectProcessServer(String options);
+
+ boolean dispatchCallbacks(int timeout);
+
+ void flushCallbacks();
+
+ default void dispatchCallbacks() {
+ this.dispatchCallbacks(-1);
+ }
+
+ void exitDispatch(DebugClient client);
+
+ default void exitDispatch() {
+ exitDispatch(this);
+ }
+
+ void setInputCallbacks(DebugInputCallbacks cb);
+
+ void setOutputCallbacks(DebugOutputCallbacks cb);
+
+ void setEventCallbacks(DebugEventCallbacks cb);
+
+ List getRunningProcesses(DebugServerId server);
+
+ Description getProcessDescription(DebugServerId si, int systemId,
+ BitmaskSet flags);
+
+ void attachProcess(DebugServerId si, int processId, BitmaskSet attachFlags);
+
+ void createProcess(DebugServerId si, String commandLine,
+ BitmaskSet createFlags);
+
+ void createProcessAndAttach(DebugServerId si, String commandLine,
+ BitmaskSet createFlags, int processId,
+ BitmaskSet attachFlags);
+
+ void startServer(String options);
+
+ // Only in IDebugClient2
+
+ void waitForProcessServerEnd(int timeout);
+
+ default void waitForProcessServerEnd() {
+ waitForProcessServerEnd(-1);
+ }
+
+ void terminateCurrentProcess();
+
+ void detachCurrentProcess();
+
+ void abandonCurrentProcess();
+
+ void connectSession(int flags);
+
+ void endSession(DebugEndSessionFlags flags);
+
+ // Only in IDebugClient4+
+
+ void openDumpFileWide(String fileName);
+
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugClientReentrant.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugClientReentrant.java
new file mode 100644
index 0000000000..cd846f72f9
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugClientReentrant.java
@@ -0,0 +1,46 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * An interface containing the subset of {@link DebugClient} methods which are reentrant.
+ *
+ * All other methods should be called only by the thread which created the client.
+ */
+public interface DebugClientReentrant {
+ /**
+ * Create a new client for the calling thread, connected to the same session as this client.
+ *
+ * @return the new client
+ */
+ DebugClient createClient();
+
+ /**
+ * Get the reentrant control interface to the client
+ *
+ * @return the control interface
+ */
+ DebugControlReentrant getControl();
+
+ /**
+ * End a session without acquiring locks
+ *
+ * Note. This method calls {@code IDebugClient::EndSession(DEBUG_END_REENTRANT)}. Per the MSDN,
+ * this may leave the engine in an indeterminate state. The engine should no longer be used by
+ * this process. It's really only appropriate to use this method when terminating the debugger.
+ */
+ void endSessionReentrant();
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugControl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugControl.java
new file mode 100644
index 0000000000..125dee70f6
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugControl.java
@@ -0,0 +1,359 @@
+/* ###
+ * 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.dbgeng;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.sun.jna.platform.win32.WinBase;
+import com.sun.jna.platform.win32.COM.COMException;
+
+import agent.dbgeng.dbgeng.DebugBreakpoint.BreakType;
+import agent.dbgeng.dbgeng.DebugClient.DebugStatus;
+import ghidra.comm.util.BitmaskSet;
+import ghidra.comm.util.BitmaskUniverse;
+import ghidra.util.Msg;
+
+/**
+ * A wrapper for {@code IDebugControl} and its newer variants.
+ */
+public interface DebugControl extends DebugControlReentrant {
+ public static final BitmaskSet SET_ALL_CLIENTS =
+ BitmaskSet.of(DebugOutputControl.ALL_CLIENTS);
+ public static final BitmaskSet SET_DEFAULT = BitmaskSet.of(DebugExecute.DEFAULT);
+
+ public static enum DebugOutputLevel implements BitmaskUniverse {
+ NORMAL(1 << 0), //
+ ERROR(1 << 1), //
+ WARNING(1 << 2), //
+ VERBOSE(1 << 3), //
+ PROMPT(1 << 4), //
+ PROMPT_REGISTERS(1 << 5), //
+ EXTENSION_WARNING(1 << 6), //
+ OUTPUT_DEBUGEE(1 << 7), //
+ OUTPUT_DEBUGEE_PROMPT(1 << 8), //
+ OUTPUT_SYMBOLS(1 << 9), //
+ OUTPUT_STATUS(1 << 10), //
+ ;
+
+ private final int mask;
+
+ DebugOutputLevel(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+ }
+
+ public static enum DebugOutputControl implements BitmaskUniverse {
+ THIS_CLIENT(0), //
+ ALL_CLIENTS(1), //
+ ALL_OTHER_CLIENTS(2), //
+ IGNORE(3), //
+ LOG_ONLY(4), //
+ SEND_MASK(7), //
+ NOT_LOGGED(1 << 3), //
+ OVERRIDE_MASK(1 << 4), //
+ DML(1 << 5), //
+ AMBIENT_DML(0xfffffffe), //
+ AMBIENT_TEXT(0xffffffff), //
+ AMBIENT(0xffffffff), //
+ ;
+
+ private final int mask;
+
+ DebugOutputControl(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+ }
+
+ public static enum DebugExecute implements BitmaskUniverse {
+ DEFAULT(0), //
+ ECHO(1 << 0), //
+ NOT_LOGGED(1 << 1), //
+ NO_REPEAT(1 << 2), //
+ ;
+
+ private final int mask;
+
+ DebugExecute(int mask) {
+ this.mask = mask;
+ }
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+ }
+
+ public static enum DebugInterrupt {
+ ACTIVE, //
+ PASSIVE, //
+ EXIT, //
+ ;
+ }
+
+ boolean getInterrupt();
+
+ int getInterruptTimeout();
+
+ void setInterruptTimeout(int seconds);
+
+ void print(BitmaskSet levels, String message);
+
+ /**
+ * A shortcut for {@link #print(BitmaskSet, String)} that includes a newline.
+ *
+ * @param levels the log levels for the message
+ * @param message the message
+ */
+ void println(BitmaskSet levels, String message);
+
+ /**
+ * A shortcut for {@link #print(BitmaskSet, String)} that applies to a single level.
+ *
+ * @param level the log level for the message
+ * @param message the message
+ */
+ default void print(DebugOutputLevel level, String message) {
+ print(BitmaskSet.of(level), message);
+ }
+
+ /**
+ * A shortcut for {@link #print(BitmaskSet, String)} that includes a newline and applies to a
+ * single level.
+ *
+ * @param level the log level for the message
+ * @param message the message
+ */
+ default void println(DebugOutputLevel level, String message) {
+ println(BitmaskSet.of(level), message);
+ }
+
+ /**
+ * A shortcut for {@link #print(BitmaskSet, String)} at normal level.
+ *
+ * @param message the message
+ */
+ default void out(String message) {
+ print(DebugOutputLevel.NORMAL, message);
+ }
+
+ /**
+ * A shortcut for {@link #println(BitmaskSet, String)} at normal level.
+ *
+ * @param message the message
+ */
+ default void outln(String message) {
+ println(DebugOutputLevel.NORMAL, message);
+ }
+
+ /**
+ * A shortcut for {@link #print(BitmaskSet, String)} at warning level.
+ *
+ * @param message the message
+ */
+ default void warn(String message) {
+ print(DebugOutputLevel.WARNING, message);
+ }
+
+ /**
+ * A shortcut for {@link #println(BitmaskSet, String)} at warning level.
+ *
+ * @param message the message
+ */
+ default void warnln(String message) {
+ println(DebugOutputLevel.WARNING, message);
+ }
+
+ /**
+ * A shortcut for {@link #print(BitmaskSet, String)} at error level.
+ *
+ * @param message the message
+ */
+ default void err(String message) {
+ print(DebugOutputLevel.ERROR, message);
+ }
+
+ /**
+ * A shortcut for {@link #println(BitmaskSet, String)} at error level.
+ *
+ * @param message the message
+ */
+ default void errln(String message) {
+ println(DebugOutputLevel.ERROR, message);
+ }
+
+ /**
+ * A shortcut for {@link #print(BitmaskSet, String)} at verbose level.
+ *
+ * @param message the message
+ */
+ default void verb(String message) {
+ print(DebugOutputLevel.VERBOSE, message);
+ }
+
+ /**
+ * A shortcut for {@link #println(BitmaskSet, String)} at verbose level.
+ *
+ * @param message the message
+ */
+ default void verbln(String message) {
+ println(DebugOutputLevel.VERBOSE, message);
+ }
+
+ T evaluate(Class desiredType, String expression);
+
+ void execute(BitmaskSet ctl, String str, BitmaskSet flags);
+
+ /**
+ * A shortcut for {@link #execute(BitmaskSet, String, BitmaskSet)} outputting to all clients
+ * with the default execution flag.
+ *
+ * @param str the command string
+ */
+ default void execute(String str) {
+ execute(SET_ALL_CLIENTS, str, SET_DEFAULT);
+ }
+
+ void prompt(BitmaskSet ctl, String message);
+
+ String getPromptText();
+
+ void returnInput(String input);
+
+ DebugStatus getExecutionStatus();
+
+ void setExecutionStatus(DebugStatus status);
+
+ int getNumberBreakpoints();
+
+ DebugBreakpoint getBreakpointByIndex(int index);
+
+ /**
+ * Shortcut to retrieve all breakpoints for the current process.
+ *
+ *
+ * Uses {@link #getNumberBreakpoints()} and {@link #getBreakpointByIndex(int)} to enumerate all
+ * breakpoints for the current process.
+ *
+ * @return the list of retrieved breakpoints.
+ */
+ default List getBreakpoints() {
+ int count = getNumberBreakpoints();
+ List result = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ try {
+ result.add(getBreakpointByIndex(i));
+ }
+ catch (COMException e) {
+ if (!COMUtilsExtra.isE_NOINTERFACE(e)) {
+ throw e;
+ }
+ Msg.trace(this, "Discarding private breakpoint at index " + i);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get a breakpoint by ID
+ *
+ * According to the MSDN, though the IDs may be global, this method should only succeed for
+ * breakpoints belonging to the current process.
+ *
+ * @param id
+ * @return
+ */
+ DebugBreakpoint getBreakpointById(int id);
+
+ /**
+ * Add a (resolved) breakpoint with the given type and desired id
+ *
+ *
+ * This is equivalent, in part, to the {@code bp} command.
+ *
+ * @param type the type
+ * @param desiredId the desired id
+ * @return the breakpoint, disabled at offset 0
+ */
+ DebugBreakpoint addBreakpoint(BreakType type, int desiredId);
+
+ /**
+ * Add a (resolved) breakpoint with the given type and any id
+ *
+ *
+ * This is equivalent, in part, to the {@code bp} command.
+ *
+ * @param type the type
+ * @return the breakpoint, disable at offset 0
+ */
+ DebugBreakpoint addBreakpoint(BreakType type);
+
+ /**
+ * Add an unresolved breakpoint with the given type and desired id
+ *
+ *
+ * This is equivalent, in part, to the {@code bu} command. See the MSDN for a comparison of
+ * {@code bu} and {@code bp}.
+ *
+ * @param type the type
+ * @param desiredId the desired id
+ * @return the breakpoint, disabled at offset 0
+ */
+ DebugBreakpoint addBreakpoint2(BreakType type, int desiredId);
+
+ /**
+ * Add an unresolved breakpoint with the given type and any id
+ *
+ *
+ * This is equivalent, in part, to the {@code bu} command. See the MSDN for a comparison of
+ * {@code bu} and {@code bp}.
+ *
+ * @param desiredId the desired id
+ * @return the breakpoint, disabled at offset 0
+ */
+ DebugBreakpoint addBreakpoint2(BreakType type);
+
+ void waitForEvent(int timeout);
+
+ DebugEventInformation getLastEventInformation();
+
+ DebugStackInformation getStackTrace(long frameOffset, long stackOffset, long instructionOffset);
+
+ /**
+ * Shortcut for {@link #waitForEvent(int)} with infinite timeout.
+ */
+ default void waitForEvent() {
+ waitForEvent(WinBase.INFINITE);
+ }
+
+ int getActualProcessorType();
+
+ int getEffectiveProcessorType();
+
+ int getExecutingProcessorType();
+
+ int getDebuggeeType();
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugControlReentrant.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugControlReentrant.java
new file mode 100644
index 0000000000..544d451e77
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugControlReentrant.java
@@ -0,0 +1,27 @@
+/* ###
+ * 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.dbgeng;
+
+import agent.dbgeng.dbgeng.DebugControl.DebugInterrupt;
+
+/**
+ * An interface containing the subset of {@link DebugControl} methods which are reentrant.
+ *
+ * All other methods should be called only by the thread which created the client.
+ */
+public interface DebugControlReentrant {
+ void setInterrupt(DebugInterrupt interrupt);
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugDataSpaces.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugDataSpaces.java
new file mode 100644
index 0000000000..4fe1e9684f
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugDataSpaces.java
@@ -0,0 +1,285 @@
+/* ###
+ * 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.dbgeng;
+
+import java.nio.ByteBuffer;
+import java.util.*;
+
+import com.sun.jna.platform.win32.COM.COMException;
+
+import ghidra.comm.util.BitmaskSet;
+import ghidra.comm.util.BitmaskUniverse;
+import ghidra.util.Msg;
+
+/**
+ * A wrapper for {@code IDebugDataSpaces} and its newer variants.
+ */
+public interface DebugDataSpaces {
+ public enum PageState {
+ COMMIT(0x1000), FREE(0x10000), RESERVE(0x2000);
+
+ private final int val;
+
+ private PageState(int val) {
+ this.val = val;
+ }
+
+ public static PageState byValue(int val) {
+ for (PageState state : values()) {
+ if (state.val == val) {
+ return state;
+ }
+ }
+ Msg.warn(PageState.class, "No such value: 0x" + Integer.toHexString(val));
+ return null;
+ }
+ }
+
+ public enum PageProtection implements BitmaskUniverse {
+ NOACCESS(1 << 0, false, false, false), //
+ READONLY(1 << 1, true, false, false), //
+ READWRITE(1 << 2, true, true, false), //
+ WRITE_COPY(1 << 3, true, true, false), // Becomes READWRITE after copy
+ EXECUTE(1 << 4, false, false, true), //
+ EXECUTE_READ(1 << 5, true, false, true), //
+ EXECUTE_READWRITE(1 << 6, true, true, true), //
+ EXECUTE_WRITECOPY(1 << 7, true, true, true), //
+ //
+ GUARD(1 << 8, false, false, false), //
+ NOCACHE(1 << 9, false, false, false), //
+ WRITECOMBINE(1 << 10, false, false, false), //
+ ;
+
+ private PageProtection(int mask, boolean isRead, boolean isWrite, boolean isExecute) {
+ this.mask = mask;
+ this.isRead = isRead;
+ this.isWrite = isWrite;
+ this.isExecute = isExecute;
+ }
+
+ final int mask;
+ final boolean isRead;
+ final boolean isWrite;
+ final boolean isExecute;
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+
+ public boolean isRead() {
+ return isRead;
+ }
+
+ public boolean isWrite() {
+ return isWrite;
+ }
+
+ public boolean isExecute() {
+ return isExecute;
+ }
+ }
+
+ public enum PageType {
+ NONE(0), //
+ IMAGE(0x1000000), //
+ MAPPED(0x40000), //
+ PRIVATE(0x20000), //
+ ;
+
+ private final int val;
+
+ private PageType(int val) {
+ this.val = val;
+ }
+
+ public static PageType byValue(int val) {
+ for (PageType type : values()) {
+ if (type.val == val) {
+ return type;
+ }
+ }
+ Msg.warn(PageType.class, "No such value: 0x" + Integer.toHexString(val));
+ return null;
+ }
+ }
+
+ public static class DebugMemoryBasicInformation {
+ public final long baseAddress;
+ public final long allocationBase;
+ public final Set allocationProtect;
+ public final long regionSize;
+ public final PageState state;
+ public final Set protect;
+ public final PageType type;
+
+ public DebugMemoryBasicInformation(long baseAddress, long allocationBase,
+ BitmaskSet allocationProtect, long regionSize, PageState state,
+ BitmaskSet protect, PageType type) {
+ this.baseAddress = baseAddress;
+ this.allocationBase = allocationBase;
+ this.allocationProtect = Collections.unmodifiableSet(allocationProtect);
+ this.regionSize = regionSize;
+ this.state = state;
+ this.protect = Collections.unmodifiableSet(protect);
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(baseAddress, allocationBase, allocationProtect, regionSize, state,
+ protect, type);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof DebugMemoryBasicInformation)) {
+ return false;
+ }
+ DebugMemoryBasicInformation that = (DebugMemoryBasicInformation) obj;
+ if (this.baseAddress != that.baseAddress) {
+ return false;
+ }
+ if (this.allocationBase != that.allocationBase) {
+ return false;
+ }
+ if (!this.allocationProtect.equals(that.allocationProtect)) {
+ return false;
+ }
+ if (this.regionSize != that.regionSize) {
+ return false;
+ }
+ if (this.state != that.state) {
+ return false;
+ }
+ if (!this.protect.equals(that.protect)) {
+ return false;
+ }
+ if (this.type != that.type) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ int readVirtual(long offset, ByteBuffer into, int len);
+
+ int writeVirtual(long offset, ByteBuffer from, int len);
+
+ int readVirtualUncached(long offset, ByteBuffer into, int len);
+
+ int writeVirtualUncached(long offset, ByteBuffer from, int len);
+
+ int readPhysical(long offset, ByteBuffer into, int len);
+
+ int writePhysical(long offset, ByteBuffer from, int len);
+
+ int readControl(int processor, long offset, ByteBuffer into, int len);
+
+ int writeControl(int processor, long offset, ByteBuffer from, int len);
+
+ int readBusData(int busDataType, int busNumber, int slotNumber, long offset, ByteBuffer into,
+ int len);
+
+ int writeBusData(int busDataType, int busNumber, int slotNumber, long offset, ByteBuffer from,
+ int len);
+
+ int readIo(int interfaceType, int busNumber, int addressSpace, long offset, ByteBuffer into,
+ int len);
+
+ int writeIo(int interfaceType, int busNumber, int addressSpace, long offset, ByteBuffer from,
+ int len);
+
+ long readMsr(int msr);
+
+ void writeMsr(int msr, long value);
+
+ int readDebuggerData(int offset, ByteBuffer into, int len);
+
+ DebugMemoryBasicInformation queryVirtual(long offset);
+
+ /**
+ * A shortcut for iterating over virtual memory regions.
+ *
+ * This operates by calling {@link #queryVirtual(long)} to get each next entry, starting at an
+ * offset of -start-, adding the size of the returned region to determine the offset for the
+ * next call.
+ *
+ * @param start the starting offset
+ * @return an iterator over virtual memory regions after the given start
+ */
+ default Iterable iterateVirtual(long start) {
+ return new Iterable() {
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ private long last = start;
+ private long offset = start;
+ private DebugMemoryBasicInformation next = doGetNext();
+
+ private DebugMemoryBasicInformation getNext() {
+ if (Long.compareUnsigned(last, offset) < 0) {
+ return doGetNext();
+ }
+ return null;
+ }
+
+ private DebugMemoryBasicInformation doGetNext() {
+ try {
+ DebugMemoryBasicInformation info = queryVirtual(offset);
+ last = offset;
+ if (info != null) {
+ offset += info.regionSize;
+ }
+ return info;
+ }
+ catch (COMException e) {
+ if (!COMUtilsExtra.isE_NOINTERFACE(e)) {
+ throw e;
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ @Override
+ public DebugMemoryBasicInformation next() {
+ DebugMemoryBasicInformation ret = next;
+ next = getNext();
+ return ret;
+ }
+ };
+ }
+ };
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugEventCallbacks.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugEventCallbacks.java
new file mode 100644
index 0000000000..2cd1c6360f
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugEventCallbacks.java
@@ -0,0 +1,116 @@
+/* ###
+ * 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.dbgeng;
+
+import java.lang.annotation.*;
+
+import agent.dbgeng.dbgeng.DebugClient.*;
+import ghidra.comm.util.BitmaskSet;
+import ghidra.comm.util.BitmaskUniverse;
+
+/**
+ * The interface for receiving event callbacks via {@code IDebugEventCallbacks} or a newer variant.
+ *
+ * Note: The wrapper implementation will select the appropriate native interface version.
+ *
+ * Note: Even though {@link #changeDebuggeeState(BitmaskSet, long)},
+ * {@link #changeEngineState(BitmaskSet, long)} and {@link #changeSymbolState(BitmaskSet, long)}
+ * purport to return a {@link DebugStatus}, the returned value is ignored by {@code dbgeng.dll}.
+ */
+public interface DebugEventCallbacks {
+ public static enum DebugEvent implements BitmaskUniverse {
+ BREAKPOINT(1 << 0), //
+ EXCEPTION(1 << 1), //
+ CREATE_THREAD(1 << 2), //
+ EXIT_THREAD(1 << 3), //
+ CREATE_PROCESS(1 << 4), //
+ EXIT_PROCESS(1 << 5), //
+ LOAD_MODULE(1 << 6), //
+ UNLOAD_MODULE(1 << 7), //
+ SYSTEM_ERROR(1 << 8), //
+ SESSION_STATUS(1 << 9), //
+ CHANGE_DEBUGEE_STATE(1 << 10), //
+ CHANGE_ENGINE_STATE(1 << 11), //
+ CHANGE_SYMBOL_STATE(1 << 12), //
+ ;
+
+ private DebugEvent(int mask) {
+ this.mask = mask;
+ }
+
+ private final int mask;
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+ }
+
+ /**
+ * An annotation for marking each callback with its interest flag.
+ */
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ static @interface ForInterest {
+ /**
+ * The flag corresponding to the annotated callback method
+ *
+ * @return the flag
+ */
+ DebugEvent value();
+ }
+
+ BitmaskSet getInterestMask();
+
+ @ForInterest(DebugEvent.BREAKPOINT)
+ DebugStatus breakpoint(DebugBreakpoint bp);
+
+ @ForInterest(DebugEvent.EXCEPTION)
+ DebugStatus exception(DebugExceptionRecord64 exception, boolean firstChance);
+
+ @ForInterest(DebugEvent.CREATE_THREAD)
+ DebugStatus createThread(DebugThreadInfo debugThreadInfo);
+
+ @ForInterest(DebugEvent.EXIT_THREAD)
+ DebugStatus exitThread(int exitCode);
+
+ @ForInterest(DebugEvent.CREATE_PROCESS)
+ DebugStatus createProcess(DebugProcessInfo debugProcessInfo);
+
+ @ForInterest(DebugEvent.EXIT_PROCESS)
+ DebugStatus exitProcess(int exitCode);
+
+ @ForInterest(DebugEvent.LOAD_MODULE)
+ DebugStatus loadModule(DebugModuleInfo debugModuleInfo);
+
+ @ForInterest(DebugEvent.UNLOAD_MODULE)
+ DebugStatus unloadModule(String imageBaseName, long baseOffset);
+
+ @ForInterest(DebugEvent.SYSTEM_ERROR)
+ DebugStatus systemError(int error, int level);
+
+ @ForInterest(DebugEvent.SESSION_STATUS)
+ DebugStatus sessionStatus(SessionStatus status);
+
+ @ForInterest(DebugEvent.CHANGE_DEBUGEE_STATE)
+ DebugStatus changeDebuggeeState(BitmaskSet flags, long argument);
+
+ @ForInterest(DebugEvent.CHANGE_ENGINE_STATE)
+ DebugStatus changeEngineState(BitmaskSet flags, long argument);
+
+ @ForInterest(DebugEvent.CHANGE_SYMBOL_STATE)
+ DebugStatus changeSymbolState(BitmaskSet flags, long argument);
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugEventInformation.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugEventInformation.java
new file mode 100644
index 0000000000..753fb45cc8
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugEventInformation.java
@@ -0,0 +1,70 @@
+/* ###
+ * 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.dbgeng;
+
+import agent.dbgeng.jna.dbgeng.WinNTExtra;
+
+public class DebugEventInformation {
+
+ private int type;
+ private DebugProcessId pid;
+ private DebugThreadId tid;
+ private DebugSessionId sid;
+ private int executingProcessorType = WinNTExtra.Machine.IMAGE_FILE_MACHINE_AMD64.val;
+
+ public DebugEventInformation(int type, int pid, int tid) {
+ this.type = type;
+ this.pid = new DebugProcessId(pid);
+ this.tid = new DebugThreadId(tid);
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public DebugSessionId getSessionId() {
+ return sid;
+ }
+
+ public DebugProcessId getProcessId() {
+ return pid;
+ }
+
+ public DebugThreadId getThreadId() {
+ return tid;
+ }
+
+ public void setThread(DebugThreadId tid) {
+ this.tid = tid;
+ }
+
+ public void setProcess(DebugProcessId pid) {
+ this.pid = pid;
+ }
+
+ public void setSession(DebugSessionId sid) {
+ this.sid = sid;
+ }
+
+ public int getExecutingProcessorType() {
+ return executingProcessorType;
+ }
+
+ public void setExecutingProcessorType(int execType) {
+ this.executingProcessorType = execType;
+ }
+
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugExceptionRecord64.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugExceptionRecord64.java
new file mode 100644
index 0000000000..447e0cc775
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugExceptionRecord64.java
@@ -0,0 +1,41 @@
+/* ###
+ * 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.dbgeng;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Data copied from a {@code EXCEPTION_RECORD64} as defined in {@code winnt.h}.
+ *
+ * TODO: Some enums, flags, etc., to help interpret some of the fields.
+ */
+public class DebugExceptionRecord64 {
+ public final int code; // TODO: How to interpret
+ public final int flags; // TODO: How to interpret
+ public final long record; // TODO: How to interpret
+ public final long address;
+ public final List information;
+
+ public DebugExceptionRecord64(int code, int flags, long record, long address,
+ List information) {
+ this.code = code;
+ this.flags = flags;
+ this.record = record;
+ this.address = address;
+ this.information = Collections.unmodifiableList(information);
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugInputCallbacks.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugInputCallbacks.java
new file mode 100644
index 0000000000..d6f1507a0f
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugInputCallbacks.java
@@ -0,0 +1,31 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * The interface for receiving input callbacks via {@code IDebugInputCallbacks} or a newer variant.
+ *
+ * Note: The wrapper implementation will select the appropriate native interface version.
+ */
+@FunctionalInterface
+public interface DebugInputCallbacks {
+
+ public void startInput(long bufferSize);
+
+ default void endInput() {
+ // Optional implementation
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugModule.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugModule.java
new file mode 100644
index 0000000000..df524160d1
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugModule.java
@@ -0,0 +1,47 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * Handle to a module (program or library image).
+ */
+public interface DebugModule {
+ public enum DebugModuleName {
+ IMAGE, MODULE, LOADED_IMAGE, SYMBOL_FILE, MAPPED_IMAGE;
+ }
+
+ /**
+ * Get a name for the module.
+ *
+ * @param which identifies which name
+ * @return the requested name, if available
+ */
+ String getName(DebugModuleName which);
+
+ /**
+ * Get the index assigned to this module.
+ *
+ * @return the index
+ */
+ int getIndex();
+
+ /**
+ * Get the base address where this module is loaded, if applicable.
+ *
+ * @return the base address
+ */
+ long getBase();
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugModuleInfo.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugModuleInfo.java
new file mode 100644
index 0000000000..58973e5813
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugModuleInfo.java
@@ -0,0 +1,64 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * Information about a module (program or library image).
+ *
+ * The fields correspond to the parameters taken by {@code LoadModule} of
+ * {@code IDebugEventCallbacks}. They also appear as a subset of parameters taken by
+ * {@code CreateProcess} of {@code IDebugEventCallbacks}.
+ */
+public class DebugModuleInfo {
+ public final long imageFileHandle;
+ public final long baseOffset;
+ public final int moduleSize;
+ public final int checkSum;
+ public final int timeDateStamp;
+ private String moduleName;
+ private String imageName;
+
+ public DebugModuleInfo(long imageFileHandle, long baseOffset, int moduleSize, String moduleName,
+ String imageName, int checkSum, int timeDateStamp) {
+ this.imageFileHandle = imageFileHandle;
+ this.baseOffset = baseOffset;
+ this.moduleSize = moduleSize;
+ this.setModuleName(moduleName);
+ this.setImageName(imageName);
+ this.checkSum = checkSum;
+ this.timeDateStamp = timeDateStamp; // TODO: Convert to DateTime?
+ }
+
+ public String toString() {
+ return Long.toHexString(baseOffset);
+ }
+
+ public String getModuleName() {
+ return moduleName;
+ }
+
+ public void setModuleName(String moduleName) {
+ this.moduleName = moduleName;
+ }
+
+ public String getImageName() {
+ return imageName;
+ }
+
+ public void setImageName(String imageName) {
+ this.imageName = imageName;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugOutputCallbacks.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugOutputCallbacks.java
new file mode 100644
index 0000000000..26fe938dcc
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugOutputCallbacks.java
@@ -0,0 +1,32 @@
+/* ###
+ * 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.dbgeng;
+
+import agent.dbgeng.dbgeng.DebugControl.DebugOutputLevel;
+
+/**
+ * The interface for receiving output callbacks via {@code IDebugOutputCallbacks} or a newer
+ * variant.
+ *
+ * Note: The wrapper implementation will select the apprirate native interface version.
+ *
+ * TODO: Change {@link #output(int, String)} {@code mask} parameter to use {@link DebugOutputLevel}
+ * flags.
+ */
+@FunctionalInterface
+public interface DebugOutputCallbacks {
+ void output(int mask, String text);
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugProcessId.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugProcessId.java
new file mode 100644
index 0000000000..53dd553282
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugProcessId.java
@@ -0,0 +1,61 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * The engine ID assigned to a debugged process.
+ *
+ * Note: This is not the same as the "PID." {@code dbgeng.dll} calls that the system ID of
+ * the process.
+ *
+ * This is essentially just a boxed integer, but having an explicit data type prevents confusion
+ * with other integral values. In particular, this prevents confusion of engine PIDs with system
+ * PIDs.
+ */
+public class DebugProcessId implements Comparable {
+ public final int id;
+
+ public DebugProcessId(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public int hashCode() {
+ return Integer.hashCode(id);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof DebugProcessId)) {
+ return false;
+ }
+ DebugProcessId that = (DebugProcessId) obj;
+ if (this.id != that.id) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int compareTo(DebugProcessId that) {
+ return Integer.compare(this.id, that.id);
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugProcessInfo.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugProcessInfo.java
new file mode 100644
index 0000000000..714fcb61f3
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugProcessInfo.java
@@ -0,0 +1,36 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * Information about a process.
+ *
+ * The fields correspond to parameters taken by {@code CreateProcess} of
+ * {@code IDebugEventCallbacks}. Note that parameters common to other callbacks have been factored
+ * into types aggregated here.
+ */
+public class DebugProcessInfo {
+ public final long handle;
+ public final DebugModuleInfo moduleInfo;
+ public final DebugThreadInfo initialThreadInfo;
+
+ public DebugProcessInfo(long handle, DebugModuleInfo moduleInfo,
+ DebugThreadInfo initialThreadInfo) {
+ this.handle = handle;
+ this.moduleInfo = moduleInfo;
+ this.initialThreadInfo = initialThreadInfo;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugRegisters.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugRegisters.java
new file mode 100644
index 0000000000..6574f72987
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugRegisters.java
@@ -0,0 +1,158 @@
+/* ###
+ * 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.dbgeng;
+
+import java.util.*;
+
+import agent.dbgeng.dbgeng.DebugValue.DebugValueType;
+import ghidra.comm.util.BitmaskSet;
+import ghidra.comm.util.BitmaskUniverse;
+
+/**
+ * A wrapper for {@code IDebugRegisters} and its newer variants.
+ */
+public interface DebugRegisters {
+ public static enum DebugRegisterSource {
+ DEBUG_REGSRC_DEBUGGEE, //
+ DEBUG_REGSRC_EXPLICIT, //
+ DEBUG_REGSRC_FRAME, //
+ ;
+ }
+
+ public static enum DebugRegisterFlags implements BitmaskUniverse {
+ SUB_REGISTER(1 << 0), //
+ ;
+
+ private DebugRegisterFlags(int mask) {
+ this.mask = mask;
+ }
+
+ private final int mask;
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+ }
+
+ public static class DebugRegisterDescription {
+ public final String name;
+ public final int index;
+ public final DebugValueType type;
+ public final Set flags;
+ public final int subregMaster;
+ public final int subregLengthBits;
+ public final long subregMask;
+ public final int subregShift;
+
+ public DebugRegisterDescription(String name, int index, DebugValueType type,
+ BitmaskSet flags, int subregMaster, int subregLengthBits,
+ long subregMask, int subregShift) {
+ this.name = name;
+ this.index = index;
+ this.type = type;
+ this.flags = Collections.unmodifiableSet(flags);
+ this.subregMaster = subregMaster;
+ this.subregLengthBits = subregLengthBits;
+ this.subregMask = subregMask;
+ this.subregShift = subregShift;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "<%s: name='%s' index=%d type=%s flags=%s subregMaster=%d subregLengthBits=%d" +
+ " subregMask=%x subregShift=%d>",
+ getClass().getSimpleName(), name, index, type, flags, subregMaster,
+ subregLengthBits, subregMask, subregShift);
+ }
+ }
+
+ int getNumberRegisters();
+
+ DebugRegisterDescription getDescription(int registerNumber);
+
+ /**
+ * A shortcut to get all register descriptions for the current process.
+ *
+ * Uses {@link #getNumberRegisters()} and {@link #getDescription(int)} to retrieve all
+ * descriptions for the current process.
+ *
+ * @return the list of register descriptions
+ */
+ default Set getAllDescriptions() {
+ Set result = new LinkedHashSet<>();
+ int count = getNumberRegisters();
+ for (int i = 0; i < count; i++) {
+ result.add(getDescription(i));
+ }
+ return result;
+ }
+
+ int getIndexByName(String name);
+
+ /**
+ * A shortcut to get many register indices in one call.
+ *
+ * Uses {@link #getIndexByName(String)}.
+ *
+ * @param names the names whose indices to get
+ * @return the indices in respective order to the given names
+ */
+ default int[] getIndicesByNames(String... names) {
+ int[] indices = new int[names.length];
+ for (int i = 0; i < names.length; i++) {
+ indices[i] = getIndexByName(names[i]);
+ }
+ return indices;
+ }
+
+ DebugValue getValue(int index);
+
+ Map getValues(DebugRegisterSource source, Collection indices);
+
+ /**
+ * A shortcut to get a register value by name.
+ *
+ * Uses {@link #getIndexByName(String)} followed by {@link #getValue(int)}.
+ *
+ * @param name the name of the register
+ * @return the value
+ */
+ default DebugValue getValueByName(String name) {
+ int indexByName = getIndexByName(name);
+ if (indexByName >= 0) {
+ return getValue(indexByName);
+ }
+ return null;
+ }
+
+ void setValue(int index, DebugValue value);
+
+ void setValues(DebugRegisterSource source, Map values);
+
+ /**
+ * A shortcut to set a register value by name.
+ *
+ * Uses {@link #getIndexByName(String)} followed by {@link #setValue(int, DebugValue)}.
+ *
+ * @param name the name of the register
+ * @param value the desired value
+ */
+ default void setValueByName(String name, DebugValue value) {
+ setValue(getIndexByName(name), value);
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugRunningProcess.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugRunningProcess.java
new file mode 100644
index 0000000000..082638ef6a
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugRunningProcess.java
@@ -0,0 +1,123 @@
+/* ###
+ * 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.dbgeng;
+
+import agent.dbgeng.dbgeng.DebugRunningProcess.Description.ProcessDescriptionFlags;
+import ghidra.comm.util.BitmaskUniverse;
+
+/**
+ * Information about a running process, not necessarily a debugged process.
+ */
+public interface DebugRunningProcess {
+ /**
+ * Description of a running process
+ */
+ public static class Description {
+ public static enum ProcessDescriptionFlags implements BitmaskUniverse {
+ NO_PATHS(1 << 0), //
+ NO_SERVICES(1 << 1), //
+ NO_MTS_PACKAGES(1 << 2), //
+ NO_COMMAND_LINE(1 << 3), //
+ NO_SESSION_ID(1 << 4), //
+ NO_USER_NAME(1 << 5), //
+ ;
+
+ ProcessDescriptionFlags(int mask) {
+ this.mask = mask;
+ }
+
+ private final int mask;
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+ }
+
+ public Description(int systemId, String exeName, String description) {
+ this.systemId = systemId;
+ this.exeName = exeName;
+ this.description = description;
+ }
+
+ private final int systemId;
+ private final String exeName;
+ private final String description;
+
+ /**
+ * The system ID (PID) for the process.
+ *
+ * @return the PID
+ */
+ public int getSystemId() {
+ return systemId;
+ }
+
+ /**
+ * The name of the executable defining the process
+ *
+ * @return the name
+ */
+ public String getExecutableName() {
+ return exeName;
+ }
+
+ /**
+ * A textual description of the process.
+ *
+ * @return the description
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PID:%d, EXE:%s, Description:%s", systemId, exeName, description);
+ }
+ }
+
+ /**
+ * The system ID (PID) for the process.
+ *
+ * @return the PID
+ */
+ int getSystemId();
+
+ /**
+ * Get the "full" description of the process.
+ *
+ * @param flags indicate which information to include in the description
+ * @return the description
+ */
+ Description getFullDescription(ProcessDescriptionFlags... flags);
+
+ /**
+ * The name of the executable defining the process.
+ *
+ * @param flags indicate which information to include in the description
+ * @return the name
+ */
+ String getExecutableName(ProcessDescriptionFlags... flags);
+
+ /**
+ * A textual description of the process.
+ *
+ * @param flags indicate which information to include in the description
+ * @return the description
+ */
+ String getDescription(ProcessDescriptionFlags... flags);
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugServerId.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugServerId.java
new file mode 100644
index 0000000000..d44683ab86
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugServerId.java
@@ -0,0 +1,58 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * The ID of a debug server.
+ *
+ * Each server to which a client is connected is assigned a server ID. The local server, to which
+ * every client is connected by default, has the ID 0. This is essentially just a boxed integer, but
+ * having an explicit data type prevents confusion with other integral values.
+ */
+public class DebugServerId implements Comparable {
+ public final long id;
+
+ public DebugServerId(long id) {
+ this.id = id;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(id);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof DebugServerId)) {
+ return false;
+ }
+ DebugServerId that = (DebugServerId) obj;
+ if (this.id != that.id) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int compareTo(DebugServerId that) {
+ return Long.compare(this.id, that.id);
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSessionId.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSessionId.java
new file mode 100644
index 0000000000..84d1757a0e
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSessionId.java
@@ -0,0 +1,61 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * The engine ID assigned to a debugged process.
+ *
+ * Note: This is not the same as the "PID." {@code dbgeng.dll} calls that the system ID of
+ * the process.
+ *
+ * This is essentially just a boxed integer, but having an explicit data type prevents confusion
+ * with other integral values. In particular, this prevents confusion of engine PIDs with system
+ * PIDs.
+ */
+public class DebugSessionId implements Comparable {
+ public final int id;
+
+ public DebugSessionId(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public int hashCode() {
+ return Integer.hashCode(id);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof DebugSessionId)) {
+ return false;
+ }
+ DebugSessionId that = (DebugSessionId) obj;
+ if (this.id != that.id) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int compareTo(DebugSessionId that) {
+ return Integer.compare(this.id, that.id);
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugStackInformation.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugStackInformation.java
new file mode 100644
index 0000000000..a63be6271f
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugStackInformation.java
@@ -0,0 +1,37 @@
+/* ###
+ * 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.dbgeng;
+
+import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_STACK_FRAME;
+
+public class DebugStackInformation {
+
+ private int nFrames;
+ private DEBUG_STACK_FRAME[] stackFrames;
+
+ public DebugStackInformation(int nFrames, DEBUG_STACK_FRAME[] stackFrames) {
+ this.nFrames = nFrames;
+ this.stackFrames = stackFrames;
+ }
+
+ public int getNumberOfFrames() {
+ return nFrames;
+ }
+
+ public DEBUG_STACK_FRAME getFrame(int frameNumber) {
+ return stackFrames[frameNumber];
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSymbolEntry.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSymbolEntry.java
new file mode 100644
index 0000000000..fe0904ae67
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSymbolEntry.java
@@ -0,0 +1,56 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * Data copied from a {@code DEBUG_SYMBOL_ENTRY} as defined in {@code dbgeng.h}.
+ *
+ * TODO: Some enums, flags, etc., to help interpret some of the fields.
+ */
+public class DebugSymbolEntry {
+ public final long moduleBase;
+ public final long offset;
+ public final long symbolId;
+ public final long size;
+ public final int flags;
+ public final int typeId;
+ public final String name;
+ public final int tag;
+
+ public DebugSymbolEntry(long moduleBase, long offset, long symbolId, long size, int flags,
+ int typeId, String name, int tag) {
+ this.moduleBase = moduleBase;
+ this.offset = offset;
+ this.symbolId = symbolId;
+ this.size = size;
+ this.flags = flags;
+ this.typeId = typeId;
+ this.name = name;
+ this.tag = tag;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("", //
+ moduleBase, symbolId, offset, size, flags, typeId, name, tag);
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSymbolId.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSymbolId.java
new file mode 100644
index 0000000000..451093b95b
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSymbolId.java
@@ -0,0 +1,34 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * Symbol identifier, consisting of module ID and symbol index.
+ */
+public class DebugSymbolId {
+ public final long moduleBase;
+ public final long symbolIndex;
+
+ public DebugSymbolId(long moduleBase, long symbolIndex) {
+ this.moduleBase = moduleBase;
+ this.symbolIndex = symbolIndex;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("", moduleBase, symbolIndex);
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSymbolName.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSymbolName.java
new file mode 100644
index 0000000000..325d6db4ff
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSymbolName.java
@@ -0,0 +1,34 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * Symbol name, consisting of textual name and offset.
+ */
+public class DebugSymbolName {
+ public final String name;
+ public final long offset;
+
+ public DebugSymbolName(String name, long offset) {
+ this.name = name;
+ this.offset = offset;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("<%016x: %s>", offset, name);
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSymbols.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSymbols.java
new file mode 100644
index 0000000000..3f7b990f1d
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSymbols.java
@@ -0,0 +1,85 @@
+/* ###
+ * 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.dbgeng;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A wrapper for {@code IDebugSymbols} and its newer variants.
+ */
+public interface DebugSymbols {
+ int getNumberLoadedModules();
+
+ int getNumberUnloadedModules();
+
+ DebugModule getModuleByIndex(int index);
+
+ DebugModule getModuleByModuleName(String name, int startIndex);
+
+ DebugModule getModuleByOffset(long offset, int startIndex);
+
+ DebugModuleInfo getModuleParameters(int count, int startIndex);
+
+ /**
+ * A shortcut for iterating over all loaded modules, lazily.
+ *
+ * @param startIndex the module index to start at
+ * @return an iterator over modules starting at the given index
+ */
+ default Iterable iterateModules(int startIndex) {
+ int count = getNumberLoadedModules(); // TODO: What about unloaded?
+ return new Iterable() {
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ int cur = startIndex;
+
+ @Override
+ public boolean hasNext() {
+ return cur < count;
+ }
+
+ @Override
+ public DebugModule next() {
+ DebugModule ret = getModuleByIndex(cur);
+ cur++;
+ return ret;
+ }
+ };
+ }
+ };
+ }
+
+ Iterable iterateSymbolMatches(String pattern);
+
+ List getSymbolIdsByName(String pattern);
+
+ DebugSymbolEntry getSymbolEntry(DebugSymbolId id);
+
+ String getSymbolPath();
+
+ void setSymbolPath(String path);
+
+ int getSymbolOptions();
+
+ void setSymbolOptions(int options);
+
+ public int getCurrentScopeFrameIndex();
+
+ public void setCurrentScopeFrameIndex(int index);
+
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSystemObjects.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSystemObjects.java
new file mode 100644
index 0000000000..9accb9cbc3
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugSystemObjects.java
@@ -0,0 +1,102 @@
+/* ###
+ * 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.dbgeng;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A wrapper for {@code IDebugSystemObjects} and its newer variants.
+ */
+public interface DebugSystemObjects {
+
+ DebugThreadId getEventThread();
+
+ DebugProcessId getEventProcess();
+
+ DebugSessionId getEventSystem();
+
+ DebugThreadId getCurrentThreadId();
+
+ void setCurrentThreadId(DebugThreadId id);
+
+ DebugProcessId getCurrentProcessId();
+
+ void setCurrentProcessId(DebugProcessId id);
+
+ DebugSessionId getCurrentSystemId();
+
+ void setCurrentSystemId(DebugSessionId id);
+
+ int getNumberThreads();
+
+ int getTotalNumberThreads(); // TODO: LargestProcess?
+
+ /**
+ * Get the threads IDs by index from the current process
+ *
+ * @param start the starting index
+ * @param count the number of threads
+ * @return the list of thread IDs
+ */
+ List getThreads(int start, int count);
+
+ /**
+ * Get all thread IDs in the current process
+ *
+ * @return the list of thread IDs
+ */
+ default List getThreads() {
+ return getThreads(0, getNumberThreads());
+ }
+
+ DebugThreadId getThreadIdByHandle(long handle);
+
+ DebugProcessId getProcessIdByHandle(long handle);
+
+ int getNumberSystems();
+
+ List getSystems(int start, int count);
+
+ default List getSessions() {
+ int numberSystems = getNumberSystems();
+ if (numberSystems < 0) {
+ return new ArrayList();
+ }
+ return getSystems(0, numberSystems);
+ }
+
+ int getNumberProcesses();
+
+ List getProcesses(int start, int count);
+
+ default List getProcesses() {
+ int numberProcesses = getNumberProcesses();
+ if (numberProcesses < 0) {
+ return new ArrayList();
+ }
+ return getProcesses(0, numberProcesses);
+ }
+
+ int getCurrentThreadSystemId();
+
+ int getCurrentProcessSystemId();
+
+ DebugThreadId getThreadIdBySystemId(int systemId);
+
+ DebugProcessId getProcessIdBySystemId(int systemId);
+
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugThreadId.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugThreadId.java
new file mode 100644
index 0000000000..f9799bcc14
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugThreadId.java
@@ -0,0 +1,61 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * The engine ID assigned to a debugged thread.
+ *
+ * Note: This is not the same as the "TID." {@code dbgeng.dll} calls that the system ID of
+ * the thread.
+ *
+ * This is essentially just a boxed integer, but having an explicit data type prevents confusion
+ * with other integral values. In particular, this prevents confusion of engine TIDs with system
+ * TIDs.
+ */
+public class DebugThreadId implements Comparable {
+ public final int id;
+
+ public DebugThreadId(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public int hashCode() {
+ return Integer.hashCode(id);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof DebugThreadId)) {
+ return false;
+ }
+ DebugThreadId that = (DebugThreadId) obj;
+ if (this.id != that.id) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int compareTo(DebugThreadId that) {
+ return Integer.compare(this.id, that.id);
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugThreadInfo.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugThreadInfo.java
new file mode 100644
index 0000000000..96672c0d69
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugThreadInfo.java
@@ -0,0 +1,35 @@
+/* ###
+ * 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.dbgeng;
+
+/**
+ * Information about a thread.
+ *
+ * The fields correspond to parameters taken by {@code CreateThread} of
+ * {@code IDebugEventCallbacks}. They also appear as a subset of parameters taken by
+ * {@code CreateProcess} of {@code IDebugEventCallbacks}.
+ */
+public class DebugThreadInfo {
+ public final long handle;
+ public final long dataOffset;
+ public final long startOffset;
+
+ public DebugThreadInfo(long handle, long dataOffset, long startOffset) {
+ this.handle = handle;
+ this.dataOffset = dataOffset;
+ this.startOffset = startOffset;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugValue.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugValue.java
new file mode 100644
index 0000000000..28f3af5fb0
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/DebugValue.java
@@ -0,0 +1,457 @@
+/* ###
+ * 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.dbgeng;
+
+import java.lang.annotation.*;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+import agent.dbgeng.dbgeng.DebugValue.DebugValueType;
+import agent.dbgeng.dbgeng.DebugValue.ForDebugValueType;
+import ghidra.util.NumericUtilities;
+
+/**
+ * Data copied from a {@code DEBUG_VALUE} as defined in {dbgeng.h}.
+ */
+@ForDebugValueType(DebugValueType.INVALID)
+public interface DebugValue {
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public static @interface ForDebugValueType {
+ DebugValueType value();
+ }
+
+ public static enum DebugValueType {
+ INVALID(0), //
+ INT8(Byte.SIZE), //
+ INT16(Short.SIZE), //
+ INT32(Integer.SIZE), //
+ INT64(Long.SIZE), //
+ FLOAT32(Float.SIZE), //
+ FLOAT64(Double.SIZE), //
+ FLOAT80(80), //
+ FLOAT82(82), //
+ FLOAT128(128), //
+ VECTOR64(64), //
+ VECTOR128(128), //
+ ;
+
+ private static final Class extends DebugValue>[] CLASSES;
+
+ static {
+ @SuppressWarnings("unchecked")
+ Class extends DebugValue>[] supressed = new Class[DebugValueType.values().length];
+ CLASSES = supressed;
+ for (Class> cls : DebugValue.class.getDeclaredClasses()) {
+ if (!DebugValue.class.isAssignableFrom(cls)) {
+ continue;
+ }
+ Class extends DebugValue> dvCls = cls.asSubclass(DebugValue.class);
+ DebugValueType type = getDebugValueTypeForClass(dvCls);
+ CLASSES[type.ordinal()] = dvCls;
+ }
+ }
+
+ public static DebugValueType getDebugValueTypeForClass(Class extends DebugValue> cls) {
+ ForDebugValueType annot = cls.getAnnotation(ForDebugValueType.class);
+ if (annot == null) {
+ throw new AssertionError(
+ "INTERNAL: Missing ForDebugValueType annotation on " + cls);
+ }
+ return annot.value();
+ }
+
+ public final int bitLength;
+ public final int byteLength;
+
+ private DebugValueType(int bitLength) {
+ this.bitLength = bitLength;
+ this.byteLength = (bitLength + 7) / 8;
+ }
+
+ public Class extends DebugValue> getDebugValueClass() {
+ return CLASSES[ordinal()];
+ }
+
+ public DebugValue decodeBytes(byte[] bytes) throws IllegalArgumentException {
+ try {
+ return CLASSES[ordinal()].getConstructor(byte[].class).newInstance(bytes);
+ }
+ catch (InstantiationException | IllegalAccessException | IllegalArgumentException
+ | NoSuchMethodException | SecurityException e) {
+ throw new AssertionError(e);
+ }
+ catch (InvocationTargetException e) {
+ if (e.getCause() instanceof IllegalArgumentException) {
+ throw (IllegalArgumentException) e.getCause();
+ }
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ @ForDebugValueType(DebugValueType.INT8)
+ public static class DebugInt8Value implements DebugValue {
+ private final byte value;
+
+ public DebugInt8Value(byte value) {
+ this.value = value;
+ }
+
+ public DebugInt8Value(byte[] bytes) {
+ if (bytes.length != 1) {
+ throw new IllegalArgumentException("Must have exactly 1 byte");
+ }
+ this.value = bytes[0];
+ }
+
+ public byte byteValue() {
+ return value;
+ }
+
+ @Override
+ public byte[] encodeAsBytes() {
+ return new byte[] { value };
+ }
+
+ @Override
+ public String toString() {
+ return "byte " + Integer.toHexString(value) + "h";
+ }
+ }
+
+ @ForDebugValueType(DebugValueType.INT16)
+ public static class DebugInt16Value implements DebugValue {
+ private final short value;
+
+ public DebugInt16Value(short value) {
+ this.value = value;
+ }
+
+ public DebugInt16Value(byte[] bytes) {
+ if (bytes.length != Short.BYTES) {
+ throw new IllegalArgumentException("Must have exactly " + Short.BYTES + " bytes");
+ }
+ ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
+ this.value = buf.getShort();
+ }
+
+ public short shortValue() {
+ return value;
+ }
+
+ @Override
+ public byte[] encodeAsBytes() {
+ ByteBuffer buf = ByteBuffer.allocate(Short.BYTES).order(ByteOrder.BIG_ENDIAN);
+ buf.putShort(value);
+ return buf.array();
+ }
+
+ @Override
+ public String toString() {
+ return "word " + Integer.toHexString(value) + "h";
+ }
+ }
+
+ @ForDebugValueType(DebugValueType.INT32)
+ public static class DebugInt32Value implements DebugValue {
+ private final int value;
+
+ public DebugInt32Value(int value) {
+ this.value = value;
+ }
+
+ public DebugInt32Value(byte[] bytes) {
+ if (bytes.length != Integer.BYTES) {
+ throw new IllegalArgumentException("Must have exactly " + Integer.BYTES + " bytes");
+ }
+ ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
+ this.value = buf.getInt();
+ }
+
+ public int intValue() {
+ return value;
+ }
+
+ @Override
+ public byte[] encodeAsBytes() {
+ ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.BIG_ENDIAN);
+ buf.putInt(value);
+ return buf.array();
+ }
+
+ @Override
+ public String toString() {
+ return "dword " + Integer.toHexString(value) + "h";
+ }
+ }
+
+ @ForDebugValueType(DebugValueType.INT64)
+ public static class DebugInt64Value implements DebugValue {
+ private final long value;
+
+ public DebugInt64Value(long value) {
+ this.value = value;
+ }
+
+ public DebugInt64Value(byte[] bytes) {
+ if (bytes.length != Long.BYTES) {
+ throw new IllegalArgumentException("Must have exactly " + Long.BYTES + " bytes");
+ }
+ ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
+ this.value = buf.getLong();
+ }
+
+ public long longValue() {
+ return value;
+ }
+
+ @Override
+ public byte[] encodeAsBytes() {
+ ByteBuffer buf = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.BIG_ENDIAN);
+ buf.putLong(value);
+ return buf.array();
+ }
+
+ @Override
+ public String toString() {
+ return "qword " + Long.toHexString(value) + "h";
+ }
+ }
+
+ @ForDebugValueType(DebugValueType.FLOAT32)
+ public static class DebugFloat32Value implements DebugValue {
+ private final float value;
+
+ public DebugFloat32Value(float value) {
+ this.value = value;
+ }
+
+ public DebugFloat32Value(byte[] bytes) {
+ if (bytes.length != Float.BYTES) {
+ throw new IllegalArgumentException("Must have exactly " + Float.BYTES + " bytes");
+ }
+ ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
+ this.value = buf.getFloat();
+ }
+
+ public float floatValue() {
+ return value;
+ }
+
+ @Override
+ public byte[] encodeAsBytes() {
+ ByteBuffer buf = ByteBuffer.allocate(Float.BYTES).order(ByteOrder.BIG_ENDIAN);
+ buf.putFloat(value);
+ return buf.array();
+ }
+
+ @Override
+ public String toString() {
+ return "f32 " + value;
+ }
+ }
+
+ @ForDebugValueType(DebugValueType.FLOAT64)
+ public static class DebugFloat64Value implements DebugValue {
+ private final double value;
+
+ public DebugFloat64Value(double value) {
+ this.value = value;
+ }
+
+ public DebugFloat64Value(byte[] bytes) {
+ if (bytes.length != Double.BYTES) {
+ throw new IllegalArgumentException("Must have exactly " + Double.BYTES + " bytes");
+ }
+ ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
+ this.value = buf.getDouble();
+ }
+
+ public double doubleValue() {
+ return value;
+ }
+
+ @Override
+ public byte[] encodeAsBytes() {
+ ByteBuffer buf = ByteBuffer.allocate(Double.BYTES).order(ByteOrder.BIG_ENDIAN);
+ buf.putDouble(value);
+ return buf.array();
+ }
+
+ @Override
+ public String toString() {
+ return "f64 " + value;
+ }
+ }
+
+ /**
+ * Extended-precision float
+ */
+ @ForDebugValueType(DebugValueType.FLOAT80)
+ public static class DebugFloat80Value implements DebugValue {
+ private final byte[] bytes;
+
+ public DebugFloat80Value(byte[] bytes) {
+ if (bytes.length != 10) {
+ throw new IllegalArgumentException("Must have exactly 10 bytes");
+ }
+ this.bytes = Arrays.copyOf(bytes, 10);
+ }
+
+ public byte[] bytes() {
+ return bytes;
+ }
+
+ @Override
+ public byte[] encodeAsBytes() {
+ return bytes;
+ }
+
+ @Override
+ public String toString() {
+ return "f80 " + NumericUtilities.convertBytesToString(bytes);
+ }
+ }
+
+ /**
+ * Specific to IA-64 (Itanium) floating-point registers
+ *
+ * 17-bit exponent, 64-bit fraction. Not sure how it's aligned in memory, though.
+ */
+ @ForDebugValueType(DebugValueType.FLOAT82)
+ public static class DebugFloat82Value implements DebugValue {
+ private final byte[] bytes;
+
+ public DebugFloat82Value(byte[] bytes) {
+ if (bytes.length != 11) {
+ throw new IllegalArgumentException("Must have exactly 11 bytes");
+ }
+ this.bytes = Arrays.copyOf(bytes, 11);
+ }
+
+ public byte[] bytes() {
+ return bytes;
+ }
+
+ @Override
+ public byte[] encodeAsBytes() {
+ return bytes;
+ }
+
+ @Override
+ public String toString() {
+ return "f82 " + NumericUtilities.convertBytesToString(bytes);
+ }
+ }
+
+ /**
+ * Quadruple-precision float
+ */
+ @ForDebugValueType(DebugValueType.FLOAT128)
+ public static class DebugFloat128Value implements DebugValue {
+ private final byte[] bytes;
+
+ public DebugFloat128Value(byte[] bytes) {
+ if (bytes.length != 16) {
+ throw new IllegalArgumentException("Must have exactly 16 bytes");
+ }
+ this.bytes = Arrays.copyOf(bytes, 16);
+ }
+
+ public byte[] bytes() {
+ return bytes;
+ }
+
+ @Override
+ public byte[] encodeAsBytes() {
+ return bytes;
+ }
+
+ @Override
+ public String toString() {
+ return "f128 " + NumericUtilities.convertBytesToString(bytes);
+ }
+ }
+
+ @ForDebugValueType(DebugValueType.VECTOR64)
+ public static class DebugVector64Value implements DebugValue {
+ private final byte[] bytes;
+
+ public DebugVector64Value(byte[] bytes) {
+ if (bytes.length != 8) {
+ throw new IllegalArgumentException("Must have exactly 8 bytes");
+ }
+ this.bytes = Arrays.copyOf(bytes, 8);
+ }
+
+ public byte[] vi4() {
+ return bytes;
+ }
+
+ @Override
+ public byte[] encodeAsBytes() {
+ return bytes;
+ }
+
+ @Override
+ public String toString() {
+ return "vec64 " + NumericUtilities.convertBytesToString(bytes);
+ }
+ }
+
+ @ForDebugValueType(DebugValueType.VECTOR128)
+ public static class DebugVector128Value implements DebugValue {
+ private final byte[] bytes;
+
+ public DebugVector128Value(byte[] bytes) {
+ if (bytes.length != 16) {
+ throw new IllegalArgumentException(
+ "Must have exactly 16 bytes. got " + bytes.length);
+ }
+ this.bytes = Arrays.copyOf(bytes, 16);
+ }
+
+ public byte[] vi8() {
+ return bytes;
+ }
+
+ @Override
+ public byte[] encodeAsBytes() {
+ return bytes;
+ }
+
+ @Override
+ public String toString() {
+ return "vec128 " + NumericUtilities.convertBytesToString(bytes);
+ }
+ }
+
+ default DebugValueType getValueType() {
+ return DebugValueType.getDebugValueTypeForClass(getClass());
+ }
+
+ /**
+ * TODO: Document me
+ *
+ * Encodes the value as an array of bytes in big-endian order
+ *
+ * @return the encoded value
+ */
+ public byte[] encodeAsBytes();
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/err/DbgEngException.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/err/DbgEngException.java
new file mode 100644
index 0000000000..8aa25a50de
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/err/DbgEngException.java
@@ -0,0 +1,29 @@
+/* ###
+ * 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.dbgeng.err;
+
+/**
+ * The base exception for checked {@code dbgeng.dll} wrapper-related errors.
+ */
+public class DbgEngException extends Exception {
+ public DbgEngException() {
+ super();
+ }
+
+ public DbgEngException(String message) {
+ super(message);
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/err/DbgEngRuntimeException.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/err/DbgEngRuntimeException.java
new file mode 100644
index 0000000000..45409e4ee1
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/err/DbgEngRuntimeException.java
@@ -0,0 +1,29 @@
+/* ###
+ * 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.dbgeng.err;
+
+/**
+ * The base exception for unchecked {@code dbgeng.dll} wrapper-related errors.
+ */
+public class DbgEngRuntimeException extends RuntimeException {
+ public DbgEngRuntimeException() {
+ super();
+ }
+
+ public DbgEngRuntimeException(String message) {
+ super(message);
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/ext/JavaProvider.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/ext/JavaProvider.java
new file mode 100644
index 0000000000..c3db3835f8
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/ext/JavaProvider.java
@@ -0,0 +1,49 @@
+/* ###
+ * 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.dbgeng.ext;
+
+import com.sun.jna.platform.win32.COM.COMUtils;
+import com.sun.jna.ptr.PointerByReference;
+
+import agent.dbgeng.dbgeng.DebugClient;
+import agent.dbgeng.impl.dbgeng.client.DebugClientInternal;
+import agent.dbgeng.jna.dbgeng.client.WrapIDebugClient;
+import agent.dbgeng.jna.javaprovider.JavaProviderNative;
+
+/**
+ * Wrapper for "javaprovider" plugin library
+ *
+ * @deprecated In one (abandoned) use case, the SCTL server can be loaded as a
+ * "{@code engext.cpp}-style" plugin, presumably into any {@code dbgeng.dll}-powered
+ * debugger. This is accomplished by embedding the JVM into the plugin, and then calling
+ * an alternative entry point. This plugin also provides a utility function for invoking
+ * {@code CreateClient} on the client provided to the plugin by the host debugger.
+ */
+@Deprecated
+public class JavaProvider {
+ public static DebugClient createClient() {
+ PointerByReference pClient = new PointerByReference();
+ COMUtils.checkRC(JavaProviderNative.INSTANCE.createClient(pClient.getPointer()));
+ WrapIDebugClient wrap = new WrapIDebugClient(pClient.getValue());
+
+ try {
+ return DebugClientInternal.tryPreferredInterfaces(wrap::QueryInterface);
+ }
+ finally {
+ wrap.Release();
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/util/DebugEventCallbacksAdapter.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/util/DebugEventCallbacksAdapter.java
new file mode 100644
index 0000000000..3440fa29b7
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/dbgeng/util/DebugEventCallbacksAdapter.java
@@ -0,0 +1,125 @@
+/* ###
+ * 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.dbgeng.util;
+
+import java.lang.reflect.Method;
+
+import agent.dbgeng.dbgeng.*;
+import agent.dbgeng.dbgeng.DebugClient.*;
+import ghidra.comm.util.BitmaskSet;
+
+/**
+ * A convenient abstract implementation of {@link DebugEventCallbacks}
+ *
+ * This implementation automatically computes the value for {@link #getInterestMask()} based on the
+ * overridden methods. The default implementations all return {@link DebugStatus#NO_CHANGE}, should
+ * they happen to be called.
+ */
+public class DebugEventCallbacksAdapter implements DebugEventCallbacks {
+
+ private BitmaskSet interests = new BitmaskSet<>(DebugEvent.class);
+
+ public DebugEventCallbacksAdapter() {
+ try {
+ // Compute the interest mask based on methods that are overridden
+ for (Method im : DebugEventCallbacks.class.getDeclaredMethods()) {
+ Method m = this.getClass().getMethod(im.getName(), im.getParameterTypes());
+ if (m.getDeclaringClass() == DebugEventCallbacksAdapter.class) {
+ continue;
+ }
+ // The interface method is overridden, grab the annotation from the interface
+ ForInterest fi = im.getAnnotation(ForInterest.class);
+ if (fi == null) {
+ throw new AssertionError("No ForInterest annotation present on " + m);
+ }
+ interests.add(fi.value());
+ }
+ }
+ catch (NoSuchMethodException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public BitmaskSet getInterestMask() {
+ return interests;
+ }
+
+ @Override
+ public DebugStatus breakpoint(DebugBreakpoint bp) {
+ return DebugStatus.NO_CHANGE;
+ }
+
+ @Override
+ public DebugStatus exception(DebugExceptionRecord64 exception, boolean firstChance) {
+ return DebugStatus.NO_CHANGE;
+ }
+
+ @Override
+ public DebugStatus createThread(DebugThreadInfo debugThreadInfo) {
+ return DebugStatus.NO_CHANGE;
+ }
+
+ @Override
+ public DebugStatus exitThread(int exitCode) {
+ return DebugStatus.NO_CHANGE;
+ }
+
+ @Override
+ public DebugStatus createProcess(DebugProcessInfo debugProcessInfo) {
+ return DebugStatus.NO_CHANGE;
+ }
+
+ @Override
+ public DebugStatus exitProcess(int exitCode) {
+ return DebugStatus.NO_CHANGE;
+ }
+
+ @Override
+ public DebugStatus loadModule(DebugModuleInfo debugModuleInfo) {
+ return DebugStatus.NO_CHANGE;
+ }
+
+ @Override
+ public DebugStatus unloadModule(String imageBaseName, long baseOffset) {
+ return DebugStatus.NO_CHANGE;
+ }
+
+ @Override
+ public DebugStatus systemError(int error, int level) {
+ return DebugStatus.NO_CHANGE;
+ }
+
+ @Override
+ public DebugStatus sessionStatus(SessionStatus status) {
+ return DebugStatus.NO_CHANGE;
+ }
+
+ @Override
+ public DebugStatus changeDebuggeeState(BitmaskSet flags, long argument) {
+ return DebugStatus.NO_CHANGE;
+ }
+
+ @Override
+ public DebugStatus changeEngineState(BitmaskSet flags, long argument) {
+ return DebugStatus.NO_CHANGE;
+ }
+
+ @Override
+ public DebugStatus changeSymbolState(BitmaskSet flags, long argument) {
+ return DebugStatus.NO_CHANGE;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/DbgEngGadpServer.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/DbgEngGadpServer.java
new file mode 100644
index 0000000000..67fbfc60b8
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/DbgEngGadpServer.java
@@ -0,0 +1,242 @@
+/* ###
+ * 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.gadp;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import agent.dbgeng.gadp.impl.DbgEngGadpServerImpl;
+import ghidra.dbg.agent.AgentWindow;
+import ghidra.util.Msg;
+
+public interface DbgEngGadpServer extends AutoCloseable {
+ public static final String DEFAULT_DBGSRV_TRANSPORT = "tcp:port=11200";
+
+ /**
+ * The entry point for the SCTL-DBGENG server in stand-alone mode
+ *
+ * Run it to see help.
+ *
+ * @param args the command-line arguments
+ * @throws IOException if an I/O error occurs
+ * @throws ExecutionException
+ * @throws InterruptedException
+ */
+ public static void main(String[] args) throws Exception {
+ new DbgEngRunner().run(args);
+ }
+
+ /**
+ * Create a new instance of the server
+ *
+ * @param addr the address to bind the SCTL server to
+ * @param busId the client ID the server should use on the bus for synthesized commands
+ * @param dbgSrvTransport the transport specification for the {@code dbgeng.dll} server
+ * @return the server instance
+ * @throws IOException
+ */
+ public static DbgEngGadpServer newInstance(SocketAddress addr) throws IOException {
+ return new DbgEngGadpServerImpl(addr);
+ }
+
+ /**
+ * Runs the server from the command line
+ */
+ public class DbgEngRunner {
+ protected InetSocketAddress bindTo;
+ protected List dbgengArgs = new ArrayList<>();
+ protected byte busId = 1;
+ protected String dbgSrvTransport = DEFAULT_DBGSRV_TRANSPORT;
+ protected String remote = null;
+
+ public DbgEngRunner() {
+ }
+
+ public void run(String args[])
+ throws IOException, InterruptedException, ExecutionException {
+ parseArguments(args);
+
+ try (DbgEngGadpServer server = newInstance(bindTo)) {
+ //TODO: fix/test the debugConnect case when args are passed
+ server.startDbgEng(dbgengArgs.toArray(new String[] {})).exceptionally(e -> {
+ Msg.error(this, "Error starting dbgeng/GADP", e);
+ System.exit(-1);
+ return null;
+ });
+ new AgentWindow("dbgeng.dll Agent for Ghidra", server.getLocalAddress());
+ while (server.isRunning()) {
+ // TODO: Put consoleLoop back?
+ Thread.sleep(1000);
+ }
+ System.exit(0);
+ }
+ }
+
+ protected void parseArguments(String[] args) {
+ String iface = "localhost";
+ int port = 12345;
+ // NOTE: Maybe commons-cli or Argparse4j?
+ Iterator ait = Arrays.asList(args).iterator();
+ while (ait.hasNext()) {
+ String a = ait.next();
+ if ("-h".equals(a) || "--help".equals(a)) {
+ printUsage();
+ System.exit(0);
+ }
+ else if ("-p".equals(a) || "--port".equals(a)) {
+ if (!ait.hasNext()) {
+ System.err.println("Expected PORT");
+ printUsage();
+ System.exit(-1);
+ }
+ String portStr = ait.next();
+ try {
+ port = Integer.parseInt(portStr);
+ }
+ catch (NumberFormatException e) {
+ System.err.println("Integer required. Got " + portStr);
+ printUsage();
+ System.exit(-1);
+ }
+ }
+ else if ("-H".equals(a) || "--host".equals(a)) {
+ if (!ait.hasNext()) {
+ System.err.println("Expected HOST/ADDR");
+ printUsage();
+ System.exit(-1);
+ }
+ iface = ait.next();
+ }
+ else if ("-i".equals(a) || "--bus-id".equals(a)) {
+ if (!ait.hasNext()) {
+ System.err.println("Expected ID");
+ printUsage();
+ System.exit(-1);
+ }
+ String busIdStr = ait.next();
+ try {
+ busId = Byte.parseByte(busIdStr);
+ //dbgengArgs.add(busIdStr);
+ }
+ catch (NumberFormatException e) {
+ System.err.println("Byte required. Got " + busIdStr);
+ printUsage();
+ System.exit(-1);
+ }
+ }
+ else if ("-t".equals(a) || "--transport".equals(a)) {
+ if (!ait.hasNext()) {
+ System.err.println("Expected TRANSPORT");
+ System.err.println("See the MSDN 'Activating a Process Server'");
+ printUsage();
+ System.exit(-1);
+ }
+ dbgSrvTransport = ait.next();
+ dbgengArgs.add(dbgSrvTransport);
+ }
+ else if ("-r".equals(a) || "--remote".equals(a)) {
+ if (!ait.hasNext()) {
+ System.err.println("Expected TRANSPORT:HOST,PORT");
+ printUsage();
+ System.exit(-1);
+ }
+ remote = ait.next();
+ dbgengArgs.add(remote);
+ }
+ else {
+ System.err.println("Unknown option: " + a);
+ printUsage();
+ System.exit(-1);
+ }
+ }
+
+ bindTo = new InetSocketAddress(iface, port);
+ }
+
+ protected void printUsage() {
+ System.out.println("This is the GADP server for Windows dbgeng.dll. Usage:");
+ System.out.println();
+ System.out.println(" [-H HOST/ADDR] [-p PORT] [-i ID] [-t TRANSPORT] [-r REMOTE]");
+ System.out.println();
+ System.out.println("Options:");
+ System.out.println(
+ " --host/-H The address of the interface on which to listen.");
+ System.out.println(" Default is localhost");
+ System.out.println(
+ " --port/-p The TCP port on which to listen for GADP. Default is 12345");
+ System.out.println(
+ " --bus-id/-i The numeric client id for synthetic requests. Default is 1");
+ System.out.println(
+ " --transport/-t The transport specification for the Process Server.");
+ System.out.println(" Default is tcp:port=11200");
+ System.out.println(
+ " --remote/-r The transport specification for a remote server.");
+ }
+ }
+
+ /**
+ * Start the debugging server
+ *
+ * @return a future that completes when the server is ready
+ */
+ CompletableFuture startDbgEng(String[] args);
+
+ /**
+ * Get the local address to which the SCTL server is bound.
+ *
+ * @return the local socket address
+ */
+ SocketAddress getLocalAddress();
+
+ /**
+ * Starts the dbgeng manager's console loop
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ //public void consoleLoop() throws IOException;
+
+ /**
+ * Close all connections and ports, GADP and Process Server, and terminate the server
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ public void terminate() throws IOException;
+
+ /**
+ * Check if the server is running
+ *
+ * This will return false: 1) Before the server has been started, 2) After a call to
+ * {@link #terminate()}, or 3) When an error occurs causing the server to terminate
+ * unexpectedly. Otherwise, it returns true.
+ *
+ * @returns true if the server is currently running.
+ */
+ public boolean isRunning();
+
+ /**
+ * Calls {@link #terminate()}
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ default void close() throws IOException {
+ terminate();
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/DbgEngLocalDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/DbgEngLocalDebuggerModelFactory.java
new file mode 100644
index 0000000000..62c57012f6
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/DbgEngLocalDebuggerModelFactory.java
@@ -0,0 +1,85 @@
+/* ###
+ * 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.gadp;
+
+import java.util.List;
+
+import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory;
+import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
+import ghidra.util.classfinder.ExtensionPointProperties;
+
+@FactoryDescription( //
+ brief = "MS dbgeng.dll (WinDbg) local agent via GADP/TCP", //
+ htmlDetails = "Launch a new agent using the Microsoft Debug Engine." //
+)
+@ExtensionPointProperties(priority = 100)
+public class DbgEngLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory {
+
+ protected String remote = "none"; // Require user to start server
+ @FactoryOption("DebugConnect options (.server)")
+ public final Property agentRemoteOption =
+ Property.fromAccessors(String.class, this::getAgentRemote, this::setAgentRemote);
+
+ protected String transport = "none"; // Require user to start server
+ @FactoryOption("Remote process server options (untested)")
+ public final Property agentTransportOption =
+ Property.fromAccessors(String.class, this::getAgentTransport, this::setAgentTransport);
+
+ @Override
+ public boolean isCompatible() {
+ // TODO: Might instead look for the DLL
+ return System.getProperty("os.name").toLowerCase().contains("windows");
+ }
+
+ public String getAgentTransport() {
+ return transport;
+ }
+
+ public void setAgentTransport(String transport) {
+ this.transport = transport;
+ }
+
+ public String getAgentRemote() {
+ return remote;
+ }
+
+ public void setAgentRemote(String remote) {
+ this.remote = remote;
+ }
+
+ @Override
+ protected String getThreadName() {
+ return "Local dbgeng.dll Agent stdout";
+ }
+
+ protected Class> getServerClass() {
+ return DbgEngGadpServer.class;
+ }
+
+ @Override
+ protected void completeCommandLine(List cmd) {
+ cmd.add(getServerClass().getCanonicalName());
+ cmd.addAll(List.of("-H", host));
+ cmd.addAll(List.of("-p", Integer.toString(port)));
+ //cmd.addAll(List.of("-t", transport));
+ if (!remote.equals("none")) {
+ cmd.addAll(List.of("-r", remote));
+ }
+ if (!transport.equals("none")) {
+ cmd.addAll(List.of("-t", transport));
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/impl/AbstractClientThreadExecutor.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/impl/AbstractClientThreadExecutor.java
new file mode 100644
index 0000000000..24cc191006
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/impl/AbstractClientThreadExecutor.java
@@ -0,0 +1,244 @@
+/* ###
+ * 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.gadp.impl;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+import com.sun.jna.platform.win32.COM.COMException;
+
+import agent.dbgeng.dbgeng.DebugClient;
+import agent.dbgeng.dbgeng.DebugClient.DebugStatus;
+import agent.dbgeng.dbgeng.DebugControl;
+import agent.dbgeng.manager.DbgManager;
+import ghidra.util.Msg;
+
+/**
+ * A single-threaded executor which creates and exclusively accesses the {@code dbgeng.dll} client.
+ *
+ *
+ * The executor also has a priority mechanism, so that callbacks may register follow-on handlers
+ * which take precedence over other tasks in the queue (which could trigger additional callbacks).
+ * This is required since certain operation are not allowed during normal callback processing. For
+ * example, changing the current process is typically not allowed, but it is necessary to retrieve a
+ * thread's context.
+ */
+public abstract class AbstractClientThreadExecutor extends AbstractExecutorService {
+ private static final int DEFAULT_PRIORITY = 10;
+
+ protected DebugClient client;
+ protected boolean shuttingDown = false;
+ protected final Queue queue = new PriorityQueue<>();
+ protected Thread thread = new Thread(this::run, "DebugClient");
+ protected final AtomicBoolean waitRegistered = new AtomicBoolean();
+
+ protected abstract void init();
+
+ public static class Entry implements Comparable {
+ final int priority;
+ public final Runnable command;
+
+ public Entry(int priority, Runnable command) {
+ this.priority = priority;
+ this.command = command;
+ }
+
+ @Override
+ public int compareTo(Entry that) {
+ return Integer.compare(this.priority, that.priority);
+ }
+ }
+
+ /**
+ * Obtain a reference to the client, only if the calling thread is this executor's thread.
+ *
+ * @return the client
+ */
+ public DebugClient getClient() {
+ if (thread != Thread.currentThread()) {
+ //TODO: throw new AssertionError("Cannot get client outside owning thread");
+ }
+ return client;
+ }
+
+ /**
+ * Instruct the executor to call {@link DebugClient#dispatchCallbacks()} when it next idles.
+ */
+ public void cancelWait() {
+ waitRegistered.set(false);
+ }
+
+ /**
+ * Instruct the executor to call {@link DebugControl#waitForEvent()} when it next idles.
+ */
+ public void registerWait() {
+ waitRegistered.set(true);
+ }
+
+ private Entry pollQueue() {
+ synchronized (queue) {
+ return queue.poll();
+ }
+ }
+
+ private void run() {
+ /**
+ * The general idea is to run indefinitely, taking every precaution to protect this thread's
+ * life, since only it can access the client. Granted, if it turns out to be too difficult,
+ * we can always create a new thread and client, using the existing client's reentrant
+ * methods.
+ *
+ *
+ * As stated in the MSDN, this thread repeatedly calls {@code DispatchEvents} in order to
+ * receive callbacks regarding events caused by other clients. If, however, an wait is
+ * registered, or the current engine state indicates that a wait is proper, the thread calls
+ * {@code WaitForEvent} instead. The thread is occupied until the wait completes, which is
+ * fine since the engine is inaccessible (except to certain callbacks) until it completes,
+ * anyway.
+ */
+ try {
+ init();
+ while (!shuttingDown) {
+ Entry next;
+ while (null != (next = pollQueue())) {
+ if (shuttingDown) {
+ return;
+ }
+ try {
+ //System.out.println("Executing: " + next);
+ next.command.run();
+ //System.out.println("Done");
+ }
+ catch (Throwable t) {
+ Msg.error(this, "Task in executor threw: " + t);
+ }
+ }
+ DebugStatus status = client.getControl().getExecutionStatus();
+ if (status.shouldWait && status != DebugStatus.NO_DEBUGGEE ||
+ waitRegistered.get()) {
+ waitRegistered.set(false);
+ try {
+ getManager().waitForEventEx();
+ //client.getControl().waitForEvent();
+ }
+ catch (COMException e) {
+ Msg.error(this, "Error during WaitForEvents: " + e);
+ }
+ }
+ else {
+ try {
+ client.dispatchCallbacks(100); // TODO: Better synchronization
+ }
+ catch (COMException e) {
+ Msg.error(this, "Error during DispatchCallbacks: " + e);
+ }
+ }
+ }
+ }
+ catch (Throwable t) {
+ Msg.error(this, "Non-respawnable executor terminated unexpectedly", t);
+ shuttingDown = true;
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ shuttingDown = true;
+ }
+
+ @Override
+ public List shutdownNow() {
+ shuttingDown = true;
+ client.exitDispatch();
+ thread.interrupt();
+ List left = new ArrayList<>(queue.size());
+ for (Entry ent : queue) {
+ left.add(ent.command);
+ }
+ return left;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return shuttingDown;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return !thread.isAlive();
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ long millis = TimeUnit.MILLISECONDS.convert(timeout, unit);
+ thread.join(millis);
+ return !thread.isAlive();
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ execute(DEFAULT_PRIORITY, command);
+ }
+
+ /**
+ * Schedule a task with a given priority.
+ *
+ *
+ * Smaller priority values indicate earlier execution. The default priority is
+ * {@link #DEFAULT_PRIORITY}.
+ *
+ * @param priority the priority
+ * @param command the task
+ */
+ public void execute(int priority, Runnable command) {
+ if (shuttingDown) {
+ // TODO: Is this the correct exception?
+ throw new RejectedExecutionException("Executor is shutting down");
+ }
+ if (!thread.isAlive()) {
+ throw new RejectedExecutionException("Executor has terminated");
+ }
+ synchronized (queue) {
+ queue.add(new Entry(priority, command));
+ // TODO: Putting this in causes sync/output flushing problems
+ //client.exitDispatch();
+ }
+ }
+
+ public boolean isCurrentThread() {
+ return thread.equals(Thread.currentThread());
+ }
+
+ /**
+ * Schedule a task with the given priority, taking a reference to the client.
+ *
+ *
+ * This is a convenience which spares a call to {@link #getClient()}. See
+ * {@link #execute(int, Runnable)} about priority.
+ *
+ * @param priority the priority
+ * @param command the task
+ */
+ public void execute(int priority, Consumer command) {
+ execute(priority, () -> command.accept(client));
+ }
+
+ public abstract DbgManager getManager();
+
+ public abstract void setManager(DbgManager manager);
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/impl/DbgEngClientThreadExecutor.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/impl/DbgEngClientThreadExecutor.java
new file mode 100644
index 0000000000..282daf06d4
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/impl/DbgEngClientThreadExecutor.java
@@ -0,0 +1,63 @@
+/* ###
+ * 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.gadp.impl;
+
+import java.util.function.Supplier;
+
+import agent.dbgeng.dbgeng.DebugClient;
+import agent.dbgeng.manager.DbgManager;
+
+/**
+ * A single-threaded executor which creates and exclusively accesses the {@code dbgeng.dll} client.
+ *
+ * The executor also has a priority mechanism, so that callbacks may register follow-on handlers
+ * which take precedence over other tasks in the queue (which could trigger additional callbacks).
+ * This is required since certain operation are not allowed during normal callback processing. For
+ * example, changing the current process is typically not allowed, but it is necessary to retrieve a
+ * thread's context.
+ */
+public class DbgEngClientThreadExecutor extends AbstractClientThreadExecutor {
+
+ private final Supplier makeClient;
+ private DbgManager manager;
+
+ /**
+ * Instantiate a new executor, providing a means of creating the client
+ *
+ * @param makeClient the callback to create the client
+ */
+ public DbgEngClientThreadExecutor(Supplier makeClient) {
+ this.makeClient = makeClient;
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ @Override
+ protected void init() {
+ this.client = makeClient.get();
+ }
+
+ @Override
+ public DbgManager getManager() {
+ return manager;
+ }
+
+ @Override
+ public void setManager(DbgManager manager) {
+ this.manager = manager;
+ }
+
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/impl/DbgEngGadpServerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/impl/DbgEngGadpServerImpl.java
new file mode 100644
index 0000000000..455d81da20
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/impl/DbgEngGadpServerImpl.java
@@ -0,0 +1,64 @@
+/* ###
+ * 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.gadp.impl;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.util.concurrent.CompletableFuture;
+
+import agent.dbgeng.gadp.DbgEngGadpServer;
+import agent.dbgeng.model.AbstractDbgModel;
+import agent.dbgeng.model.impl.DbgModelImpl;
+import ghidra.dbg.gadp.server.AbstractGadpServer;
+
+public class DbgEngGadpServerImpl implements DbgEngGadpServer {
+ public class GadpSide extends AbstractGadpServer {
+ public GadpSide(AbstractDbgModel model, SocketAddress addr)
+ throws IOException {
+ super(model, addr);
+ }
+ }
+
+ protected final AbstractDbgModel model;
+ protected final GadpSide server;
+
+ public DbgEngGadpServerImpl(SocketAddress addr) throws IOException {
+ super();
+ this.model = new DbgModelImpl();
+ this.server = new GadpSide(model, addr);
+ }
+
+ @Override
+ public CompletableFuture startDbgEng(String[] args) {
+ return model.startDbgEng(args).thenCompose(__ -> server.launchAsyncService());
+ }
+
+ @Override
+ public SocketAddress getLocalAddress() {
+ return server.getLocalAddress();
+ }
+
+ @Override
+ public boolean isRunning() {
+ return model.isRunning();
+ }
+
+ @Override
+ public void terminate() throws IOException {
+ model.terminate();
+ server.terminate();
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/DbgEngUtil.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/DbgEngUtil.java
new file mode 100644
index 0000000000..429a63e862
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/DbgEngUtil.java
@@ -0,0 +1,86 @@
+/* ###
+ * 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.impl.dbgeng;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.function.Function;
+
+import com.sun.jna.Pointer;
+import com.sun.jna.platform.win32.Guid.REFIID;
+import com.sun.jna.platform.win32.WinDef.ULONG;
+import com.sun.jna.platform.win32.WinNT.HRESULT;
+import com.sun.jna.platform.win32.COM.*;
+import com.sun.jna.ptr.PointerByReference;
+
+import agent.dbgeng.dbgeng.err.DbgEngRuntimeException;
+import ghidra.util.Msg;
+
+public abstract class DbgEngUtil {
+ public static final ULONG DEBUG_ANY_ID = new ULONG(-1);
+
+ private DbgEngUtil() {
+ }
+
+ public static interface InterfaceSupplier {
+ HRESULT get(REFIID refiid, PointerByReference pClient);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static I tryPreferredInterfaces(Class cls,
+ Map> preferred, InterfaceSupplier supplier) {
+ PointerByReference ppClient = new PointerByReference();
+ for (Map.Entry> ent : preferred.entrySet()) {
+ try {
+ COMUtils.checkRC(supplier.get(ent.getKey(), ppClient));
+ if (ppClient.getValue() == null) {
+ continue;
+ }
+ Object impl =
+ ent.getValue().getConstructor(Pointer.class).newInstance(ppClient.getValue());
+ Method instanceFor = cls.getMethod("instanceFor", ent.getValue());
+ Object instance = instanceFor.invoke(null, impl);
+ return (I) instance;
+ }
+ catch (COMException e) {
+ Msg.debug(DbgEngUtil.class, e + " (" + ent.getValue() + ")");
+ // TODO: Only try next on E_NOINTERFACE?
+ // Try next
+ }
+ catch (Exception e) {
+ throw new AssertionError("INTERNAL: Unexpected exception", e);
+ }
+ }
+ throw new DbgEngRuntimeException("None of the preferred interfaces are supported");
+ }
+
+ public static U lazyWeakCache(Map cache, T unk,
+ Function forNew) {
+ U present = cache.get(unk.getPointer());
+ if (present != null) {
+ unk.Release();
+ return present;
+ }
+ U absent = forNew.apply(unk);
+ cache.put(unk.getPointer(), absent);
+ return absent;
+ }
+
+ public static void dbgline() {
+ System.out.println(new Exception().getStackTrace()[1]);
+ System.out.flush();
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/DebugRunningProcessImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/DebugRunningProcessImpl.java
new file mode 100644
index 0000000000..d1efcb178a
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/DebugRunningProcessImpl.java
@@ -0,0 +1,66 @@
+/* ###
+ * 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.impl.dbgeng;
+
+import agent.dbgeng.dbgeng.DebugRunningProcess;
+import agent.dbgeng.dbgeng.DebugServerId;
+import agent.dbgeng.impl.dbgeng.client.DebugClientInternal;
+import ghidra.comm.util.BitmaskSet;
+
+public class DebugRunningProcessImpl
+ implements DebugRunningProcess, Comparable {
+ public DebugRunningProcessImpl(DebugClientInternal client, DebugServerId server, int systemId) {
+ this.client = client;
+ this.server = server;
+ this.systemId = systemId;
+ }
+
+ protected final DebugClientInternal client;
+ protected final DebugServerId server;
+ protected final int systemId;
+
+ @Override
+ public int getSystemId() {
+ return systemId;
+ }
+
+ @Override
+ public Description getFullDescription(Description.ProcessDescriptionFlags... flags) {
+ return client.getProcessDescription(server, systemId, BitmaskSet.of(flags));
+ }
+
+ @Override
+ public String getExecutableName(Description.ProcessDescriptionFlags... flags) {
+ return getFullDescription(flags).getExecutableName();
+ }
+
+ @Override
+ public String getDescription(Description.ProcessDescriptionFlags... flags) {
+ return getFullDescription(flags).getDescription();
+ }
+
+ @Override
+ public int compareTo(DebugRunningProcessImpl that) {
+ int result;
+
+ result = Integer.compare(this.systemId, that.systemId);
+ if (result != 0) {
+ return result;
+ }
+
+ return 0;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/advanced/DebugAdvancedImpl1.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/advanced/DebugAdvancedImpl1.java
new file mode 100644
index 0000000000..277c15eb15
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/advanced/DebugAdvancedImpl1.java
@@ -0,0 +1,38 @@
+/* ###
+ * 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.impl.dbgeng.advanced;
+
+import agent.dbgeng.dbgeng.DbgEng;
+import agent.dbgeng.dbgeng.DebugThreadId;
+import agent.dbgeng.dbgeng.DbgEng.OpaqueCleanable;
+import agent.dbgeng.jna.dbgeng.advanced.IDebugAdvanced;
+
+public class DebugAdvancedImpl1 implements DebugAdvancedInternal {
+ @SuppressWarnings("unused")
+ private final OpaqueCleanable cleanable;
+ @SuppressWarnings("unused")
+ private final IDebugAdvanced jnaAdvanced;
+
+ public DebugAdvancedImpl1(IDebugAdvanced jnaAdvanced) {
+ this.cleanable = DbgEng.releaseWhenPhantom(this, jnaAdvanced);
+ this.jnaAdvanced = jnaAdvanced;
+ }
+
+ @Override
+ public DebugThreadBasicInformation getThreadBasicInformation(DebugThreadId tid) {
+ throw new UnsupportedOperationException("Not supported by this interface");
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/advanced/DebugAdvancedImpl2.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/advanced/DebugAdvancedImpl2.java
new file mode 100644
index 0000000000..13dc3b97aa
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/advanced/DebugAdvancedImpl2.java
@@ -0,0 +1,85 @@
+/* ###
+ * 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.impl.dbgeng.advanced;
+
+import com.sun.jna.platform.win32.WinDef.ULONG;
+import com.sun.jna.platform.win32.WinDef.ULONGLONG;
+
+import agent.dbgeng.dbgeng.DebugThreadId;
+import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_THREAD_BASIC_INFORMATION;
+import agent.dbgeng.jna.dbgeng.advanced.IDebugAdvanced2;
+
+import com.sun.jna.platform.win32.COM.COMUtils;
+
+import ghidra.comm.util.BitmaskSet;
+
+public class DebugAdvancedImpl2 extends DebugAdvancedImpl1 {
+ private final IDebugAdvanced2 jnaAdvanced;
+
+ public DebugAdvancedImpl2(IDebugAdvanced2 jnaAdvanced) {
+ super(jnaAdvanced);
+ this.jnaAdvanced = jnaAdvanced;
+ }
+
+ @Override
+ public DebugThreadBasicInformation getThreadBasicInformation(DebugThreadId tid) {
+ ULONG ulWhich = new ULONG(WhichSystemObjectInformation.THREAD_BASIC_INFORMATION.ordinal());
+ ULONGLONG ullUnused = new ULONGLONG(0);
+ ULONG ulThreadId = new ULONG(tid.id);
+ DEBUG_THREAD_BASIC_INFORMATION sInfo = new DEBUG_THREAD_BASIC_INFORMATION();
+ ULONG ulBufferSize = new ULONG(sInfo.size());
+ COMUtils.checkRC(jnaAdvanced.GetSystemObjectInformation(ulWhich, ullUnused, ulThreadId,
+ sInfo.getPointer(), ulBufferSize, null));
+ sInfo.read();
+
+ Integer exitStatus = null;
+ Integer priorityClass = null;
+ Integer priority = null;
+ Long createTime = null;
+ Long exitTime = null;
+ Long kernelTime = null;
+ Long userTime = null;
+ Long startOffset = null;
+ Long affinity = null;
+
+ BitmaskSet valid =
+ new BitmaskSet<>(ThreadBasicInformationValidBits.class, sInfo.Valid.intValue());
+ if (valid.contains(ThreadBasicInformationValidBits.EXIT_STATUS)) {
+ exitStatus = sInfo.ExitStatus.intValue();
+ }
+ if (valid.contains(ThreadBasicInformationValidBits.PRIORITY_CLASS)) {
+ priorityClass = sInfo.PriorityClass.intValue();
+ }
+ if (valid.contains(ThreadBasicInformationValidBits.PRIORITY)) {
+ priority = sInfo.Priority.intValue();
+ }
+ if (valid.contains(ThreadBasicInformationValidBits.TIMES)) {
+ createTime = sInfo.CreateTime.longValue();
+ exitTime = sInfo.ExitTime.longValue();
+ kernelTime = sInfo.KernelTime.longValue();
+ userTime = sInfo.UserTime.longValue();
+ }
+ if (valid.contains(ThreadBasicInformationValidBits.START_OFFSET)) {
+ startOffset = sInfo.StartOffset.longValue();
+ }
+ if (valid.contains(ThreadBasicInformationValidBits.AFFINITY)) {
+ affinity = sInfo.Affinity.longValue();
+ }
+
+ return new DebugThreadBasicInformation(exitStatus, priorityClass, priority, createTime,
+ exitTime, kernelTime, userTime, startOffset, affinity);
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/advanced/DebugAdvancedImpl3.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/advanced/DebugAdvancedImpl3.java
new file mode 100644
index 0000000000..1d4de8f45d
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/advanced/DebugAdvancedImpl3.java
@@ -0,0 +1,28 @@
+/* ###
+ * 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.impl.dbgeng.advanced;
+
+import agent.dbgeng.jna.dbgeng.advanced.IDebugAdvanced3;
+
+public class DebugAdvancedImpl3 extends DebugAdvancedImpl2 {
+ @SuppressWarnings("unused")
+ private final IDebugAdvanced3 jnaAdvanced;
+
+ public DebugAdvancedImpl3(IDebugAdvanced3 jnaAdvanced) {
+ super(jnaAdvanced);
+ this.jnaAdvanced = jnaAdvanced;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/advanced/DebugAdvancedInternal.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/advanced/DebugAdvancedInternal.java
new file mode 100644
index 0000000000..b1ce0bdfd5
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/advanced/DebugAdvancedInternal.java
@@ -0,0 +1,88 @@
+/* ###
+ * 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.impl.dbgeng.advanced;
+
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import com.sun.jna.Pointer;
+import com.sun.jna.platform.win32.Guid.REFIID;
+
+import agent.dbgeng.dbgeng.DebugAdvanced;
+import agent.dbgeng.impl.dbgeng.DbgEngUtil;
+import agent.dbgeng.impl.dbgeng.DbgEngUtil.InterfaceSupplier;
+import agent.dbgeng.jna.dbgeng.advanced.*;
+import ghidra.comm.util.BitmaskUniverse;
+import ghidra.util.datastruct.WeakValueHashMap;
+
+public interface DebugAdvancedInternal extends DebugAdvanced {
+ Map CACHE = new WeakValueHashMap<>();
+
+ static DebugAdvancedInternal instanceFor(WrapIDebugAdvanced advanced) {
+ return DbgEngUtil.lazyWeakCache(CACHE, advanced, DebugAdvancedImpl1::new);
+ }
+
+ static DebugAdvancedInternal instanceFor(WrapIDebugAdvanced2 advanced) {
+ return DbgEngUtil.lazyWeakCache(CACHE, advanced, DebugAdvancedImpl2::new);
+ }
+
+ static DebugAdvancedInternal instanceFor(WrapIDebugAdvanced3 advanced) {
+ return DbgEngUtil.lazyWeakCache(CACHE, advanced, DebugAdvancedImpl3::new);
+ }
+
+ ImmutableMap.Builder> PREFERRED_ADVANCED_IIDS_BUILDER =
+ ImmutableMap.builder();
+ Map> PREFERRED_ADVANCED_IIDS =
+ PREFERRED_ADVANCED_IIDS_BUILDER //
+ .put(new REFIID(IDebugAdvanced3.IID_IDEBUG_ADVANCED3), WrapIDebugAdvanced3.class) //
+ .put(new REFIID(IDebugAdvanced2.IID_IDEBUG_ADVANCED2), WrapIDebugAdvanced2.class) //
+ .put(new REFIID(IDebugAdvanced.IID_IDEBUG_ADVANCED), WrapIDebugAdvanced.class) //
+ .build();
+
+ static DebugAdvancedInternal tryPreferredInterfaces(InterfaceSupplier supplier) {
+ return DbgEngUtil.tryPreferredInterfaces(DebugAdvancedInternal.class,
+ PREFERRED_ADVANCED_IIDS, supplier);
+ }
+
+ public enum WhichSystemObjectInformation {
+ THREAD_BASIC_INFORMATION, //
+ THREAD_NAME_WIDE, //
+ CURRENT_PROCESS_COOKIE, //
+ ;
+ }
+
+ public enum ThreadBasicInformationValidBits implements BitmaskUniverse {
+ EXIT_STATUS(1 << 0), //
+ PRIORITY_CLASS(1 << 1), //
+ PRIORITY(1 << 2), //
+ TIMES(1 << 3), //
+ START_OFFSET(1 << 4), //
+ AFFINITY(1 << 5), //
+ ALL(0x3f);
+ ;
+
+ ThreadBasicInformationValidBits(int mask) {
+ this.mask = mask;
+ }
+
+ int mask;
+
+ @Override
+ public long getMask() {
+ return mask;
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/breakpoint/DebugBreakpointImpl1.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/breakpoint/DebugBreakpointImpl1.java
new file mode 100644
index 0000000000..85d14959d3
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/breakpoint/DebugBreakpointImpl1.java
@@ -0,0 +1,187 @@
+/* ###
+ * 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.impl.dbgeng.breakpoint;
+
+import com.sun.jna.Native;
+import com.sun.jna.platform.win32.Kernel32;
+import com.sun.jna.platform.win32.WinDef.*;
+import com.sun.jna.platform.win32.WinNT.HRESULT;
+import com.sun.jna.platform.win32.COM.COMUtils;
+import com.sun.jna.ptr.PointerByReference;
+
+import agent.dbgeng.dbgeng.DbgEng;
+import agent.dbgeng.dbgeng.DbgEng.OpaqueCleanable;
+import agent.dbgeng.dbgeng.DebugClient;
+import agent.dbgeng.impl.dbgeng.client.DebugClientInternal;
+import agent.dbgeng.impl.dbgeng.control.DebugControlInternal;
+import agent.dbgeng.jna.dbgeng.WinNTExtra.Machine;
+import agent.dbgeng.jna.dbgeng.breakpoint.IDebugBreakpoint;
+import agent.dbgeng.jna.dbgeng.client.WrapIDebugClient;
+import ghidra.comm.util.BitmaskSet;
+
+public class DebugBreakpointImpl1 implements DebugBreakpointInternal {
+ @SuppressWarnings("unused")
+ private final OpaqueCleanable cleanable;
+ private IDebugBreakpoint jnaBreakpoint;
+ private DebugControlInternal control;
+
+ public DebugBreakpointImpl1(IDebugBreakpoint jnaBreakpoint) {
+ this.cleanable = DbgEng.releaseWhenPhantom(this, jnaBreakpoint);
+ this.jnaBreakpoint = jnaBreakpoint;
+ }
+
+ @Override
+ public void setControl(DebugControlInternal control) {
+ this.control = control;
+ }
+
+ @Override
+ public void remove() {
+ control.removeBreakpoint(jnaBreakpoint);
+ // Prevent accidental access. Will be released during GC. NPE is better than segfault.
+ jnaBreakpoint = null;
+ }
+
+ @Override
+ public int getId() {
+ ULONGByReference pulId = new ULONGByReference();
+ COMUtils.checkRC(jnaBreakpoint.GetId(pulId));
+ return pulId.getValue().intValue();
+ }
+
+ @Override
+ public BreakFullType getType() {
+ ULONGByReference pulBreakType = new ULONGByReference();
+ ULONGByReference pulProcType = new ULONGByReference();
+ COMUtils.checkRC(jnaBreakpoint.GetType(pulBreakType, pulProcType));
+ BreakType breakType = BreakType.values()[pulBreakType.getValue().intValue()];
+ Machine procType = Machine.getByNumber(pulProcType.getValue().intValue());
+ return new BreakFullType(breakType, procType);
+ }
+
+ @Override
+ public DebugClient getAdder() {
+ PointerByReference pClient = new PointerByReference();
+ COMUtils.checkRC(jnaBreakpoint.GetAdder(pClient.getPointer()));
+ WrapIDebugClient wrap = new WrapIDebugClient(pClient.getValue());
+
+ try {
+ return DebugClientInternal.tryPreferredInterfaces(wrap::QueryInterface);
+ }
+ finally {
+ wrap.Release();
+ }
+ }
+
+ @Override
+ public BitmaskSet getFlags() {
+ ULONGByReference pulFlags = new ULONGByReference();
+ COMUtils.checkRC(jnaBreakpoint.GetFlags(pulFlags));
+ return new BitmaskSet<>(BreakFlags.class, pulFlags.getValue().longValue());
+ }
+
+ @Override
+ public void addFlags(BitmaskSet flags) {
+ ULONG ulFlags = new ULONG(flags.getBitmask());
+ COMUtils.checkRC(jnaBreakpoint.AddFlags(ulFlags));
+ }
+
+ @Override
+ public void addFlags(BreakFlags... flags) {
+ addFlags(BitmaskSet.of(flags));
+ }
+
+ @Override
+ public void removeFlags(BitmaskSet flags) {
+ ULONG ulFlags = new ULONG(flags.getBitmask());
+ COMUtils.checkRC(jnaBreakpoint.RemoveFlags(ulFlags));
+ }
+
+ @Override
+ public void removeFlags(BreakFlags... flags) {
+ removeFlags(BitmaskSet.of(flags));
+ }
+
+ @Override
+ public void setFlags(BitmaskSet flags) {
+ ULONG ulFlags = new ULONG(flags.getBitmask());
+ COMUtils.checkRC(jnaBreakpoint.SetFlags(ulFlags));
+ }
+
+ @Override
+ public void setFlags(BreakFlags... flags) {
+ setFlags(BitmaskSet.of(flags));
+ }
+
+ @Override
+ public Long getOffset() {
+ ULONGLONGByReference pullOffset = new ULONGLONGByReference();
+ HRESULT getOffset = jnaBreakpoint.GetOffset(pullOffset);
+ if (getOffset.longValue() == Kernel32.E_NOINTERFACE) {
+ // Per MSDN, this means the placement is deferred
+ return null;
+ }
+ COMUtils.checkRC(getOffset);
+ return pullOffset.getValue().longValue();
+ }
+
+ @Override
+ public void setOffset(long offset) {
+ ULONGLONG ullOffset = new ULONGLONG(offset);
+ COMUtils.checkRC(jnaBreakpoint.SetOffset(ullOffset));
+ }
+
+ @Override
+ public String getOffsetExpression() {
+ ULONGByReference pulExpressionSize = new ULONGByReference();
+ COMUtils.checkRC(jnaBreakpoint.GetOffsetExpression(null, new ULONG(0), pulExpressionSize));
+ byte[] buffer = new byte[pulExpressionSize.getValue().intValue()];
+ COMUtils.checkRC(
+ jnaBreakpoint.GetOffsetExpression(buffer, pulExpressionSize.getValue(), null));
+ return Native.toString(buffer);
+ }
+
+ @Override
+ public void setOffsetExpression(String expression) {
+ COMUtils.checkRC(jnaBreakpoint.SetOffsetExpression(expression));
+ }
+
+ @Override
+ public BreakDataParameters getDataParameters() {
+ ULONGByReference pulSize = new ULONGByReference();
+ ULONGByReference pulAccessType = new ULONGByReference();
+ COMUtils.checkRC(jnaBreakpoint.GetDataParameters(pulSize, pulAccessType));
+ return new BreakDataParameters(pulSize.getValue().intValue(),
+ new BitmaskSet<>(BreakAccess.class, pulAccessType.getValue().intValue()));
+ }
+
+ @Override
+ public void setDataParameters(BreakDataParameters params) {
+ setDataParameters(params.size, params.access);
+ }
+
+ @Override
+ public void setDataParameters(int size, BitmaskSet access) {
+ ULONG ulSize = new ULONG(size);
+ ULONG ulAccessType = new ULONG(access.getBitmask());
+ COMUtils.checkRC(jnaBreakpoint.SetDataParameters(ulSize, ulAccessType));
+ }
+
+ @Override
+ public void setDataParameters(int size, BreakAccess... access) {
+ setDataParameters(size, BitmaskSet.of(access));
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/breakpoint/DebugBreakpointImpl2.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/breakpoint/DebugBreakpointImpl2.java
new file mode 100644
index 0000000000..f24d5f2899
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/breakpoint/DebugBreakpointImpl2.java
@@ -0,0 +1,50 @@
+/* ###
+ * 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.impl.dbgeng.breakpoint;
+
+import com.sun.jna.Native;
+import com.sun.jna.WString;
+import com.sun.jna.platform.win32.WinDef.ULONG;
+import com.sun.jna.platform.win32.WinDef.ULONGByReference;
+import com.sun.jna.platform.win32.COM.COMUtils;
+
+import agent.dbgeng.jna.dbgeng.breakpoint.IDebugBreakpoint2;
+
+public class DebugBreakpointImpl2 extends DebugBreakpointImpl1 {
+ @SuppressWarnings("unused")
+ private final IDebugBreakpoint2 jnaBreakpoint;
+
+ public DebugBreakpointImpl2(IDebugBreakpoint2 jnaBreakpoint) {
+ super(jnaBreakpoint);
+ this.jnaBreakpoint = jnaBreakpoint;
+ }
+
+ @Override
+ public String getOffsetExpression() {
+ ULONGByReference pulExpressionSize = new ULONGByReference();
+ COMUtils.checkRC(
+ jnaBreakpoint.GetOffsetExpressionWide(null, new ULONG(0), pulExpressionSize));
+ char[] buffer = new char[pulExpressionSize.getValue().intValue()];
+ COMUtils.checkRC(
+ jnaBreakpoint.GetOffsetExpressionWide(buffer, pulExpressionSize.getValue(), null));
+ return Native.toString(buffer);
+ }
+
+ @Override
+ public void setOffsetExpression(String expression) {
+ COMUtils.checkRC(jnaBreakpoint.SetOffsetExpressionWide(new WString(expression)));
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/breakpoint/DebugBreakpointImpl3.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/breakpoint/DebugBreakpointImpl3.java
new file mode 100644
index 0000000000..ffc857fdcd
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/breakpoint/DebugBreakpointImpl3.java
@@ -0,0 +1,28 @@
+/* ###
+ * 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.impl.dbgeng.breakpoint;
+
+import agent.dbgeng.jna.dbgeng.breakpoint.IDebugBreakpoint3;
+
+public class DebugBreakpointImpl3 extends DebugBreakpointImpl2 {
+ @SuppressWarnings("unused")
+ private final IDebugBreakpoint3 jnaBreakpoint;
+
+ public DebugBreakpointImpl3(IDebugBreakpoint3 jnaBreakpoint) {
+ super(jnaBreakpoint);
+ this.jnaBreakpoint = jnaBreakpoint;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/breakpoint/DebugBreakpointInternal.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/breakpoint/DebugBreakpointInternal.java
new file mode 100644
index 0000000000..def9c9533a
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/breakpoint/DebugBreakpointInternal.java
@@ -0,0 +1,66 @@
+/* ###
+ * 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.impl.dbgeng.breakpoint;
+
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import com.sun.jna.Pointer;
+import com.sun.jna.platform.win32.Guid.REFIID;
+
+import agent.dbgeng.dbgeng.DebugBreakpoint;
+import agent.dbgeng.impl.dbgeng.DbgEngUtil;
+import agent.dbgeng.impl.dbgeng.DbgEngUtil.InterfaceSupplier;
+import agent.dbgeng.impl.dbgeng.control.DebugControlInternal;
+import agent.dbgeng.jna.dbgeng.breakpoint.*;
+import ghidra.util.datastruct.WeakValueHashMap;
+
+public interface DebugBreakpointInternal extends DebugBreakpoint {
+ Map CACHE = new WeakValueHashMap<>();
+
+ static DebugBreakpointInternal instanceFor(WrapIDebugBreakpoint bp) {
+ return DbgEngUtil.lazyWeakCache(CACHE, bp, DebugBreakpointImpl1::new);
+ }
+
+ static DebugBreakpointInternal instanceFor(WrapIDebugBreakpoint2 bp) {
+ return DbgEngUtil.lazyWeakCache(CACHE, bp, DebugBreakpointImpl2::new);
+ }
+
+ static DebugBreakpointInternal instanceFor(WrapIDebugBreakpoint3 bp) {
+ return DbgEngUtil.lazyWeakCache(CACHE, bp, DebugBreakpointImpl3::new);
+ }
+
+ ImmutableMap.Builder> PREFERRED_BREAKPOINT_IIDS_BUILDER =
+ ImmutableMap.builder();
+ Map> PREFERRED_BREAKPOINT_IIDS =
+ PREFERRED_BREAKPOINT_IIDS_BUILDER //
+ .put(new REFIID(IDebugBreakpoint3.IID_IDEBUG_BREAKPOINT3),
+ WrapIDebugBreakpoint3.class) //
+ .put(new REFIID(IDebugBreakpoint2.IID_IDEBUG_BREAKPOINT2),
+ WrapIDebugBreakpoint2.class) //
+ .put(new REFIID(IDebugBreakpoint.IID_IDEBUG_BREAKPOINT), WrapIDebugBreakpoint.class) //
+ .build();
+
+ static DebugBreakpointInternal tryPreferredInterfaces(DebugControlInternal control,
+ InterfaceSupplier supplier) {
+ DebugBreakpointInternal bpt = DbgEngUtil.tryPreferredInterfaces(
+ DebugBreakpointInternal.class, PREFERRED_BREAKPOINT_IIDS, supplier);
+ bpt.setControl(control);
+ return bpt;
+ }
+
+ void setControl(DebugControlInternal control);
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl1.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl1.java
new file mode 100644
index 0000000000..3eb055bd30
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl1.java
@@ -0,0 +1,323 @@
+/* ###
+ * 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.impl.dbgeng.client;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.sun.jna.Native;
+import com.sun.jna.platform.win32.WinDef.*;
+import com.sun.jna.platform.win32.WinNT;
+import com.sun.jna.platform.win32.WinNT.HRESULT;
+import com.sun.jna.platform.win32.COM.COMUtils;
+import com.sun.jna.ptr.PointerByReference;
+
+import agent.dbgeng.dbgeng.*;
+import agent.dbgeng.dbgeng.DbgEng.OpaqueCleanable;
+import agent.dbgeng.impl.dbgeng.DebugRunningProcessImpl;
+import agent.dbgeng.impl.dbgeng.advanced.DebugAdvancedInternal;
+import agent.dbgeng.impl.dbgeng.control.DebugControlInternal;
+import agent.dbgeng.impl.dbgeng.dataspaces.DebugDataSpacesInternal;
+import agent.dbgeng.impl.dbgeng.event.WrapCallbackIDebugEventCallbacks;
+import agent.dbgeng.impl.dbgeng.io.WrapCallbackIDebugInputCallbacks;
+import agent.dbgeng.impl.dbgeng.io.WrapCallbackIDebugOutputCallbacks;
+import agent.dbgeng.impl.dbgeng.registers.DebugRegistersInternal;
+import agent.dbgeng.impl.dbgeng.symbols.DebugSymbolsInternal;
+import agent.dbgeng.impl.dbgeng.sysobj.DebugSystemObjectsInternal;
+import agent.dbgeng.jna.dbgeng.client.IDebugClient;
+import agent.dbgeng.jna.dbgeng.event.ListenerIDebugEventCallbacks;
+import agent.dbgeng.jna.dbgeng.event.MarkerEventCallbacks;
+import agent.dbgeng.jna.dbgeng.io.*;
+import ghidra.comm.util.BitmaskSet;
+
+public class DebugClientImpl1 implements DebugClientInternal {
+ @SuppressWarnings("unused")
+ private final OpaqueCleanable cleanable;
+ private final IDebugClient jnaClient;
+
+ private DebugAdvancedInternal advanced;
+ private DebugControlInternal control;
+ private DebugDataSpaces data;
+ private DebugRegisters registers;
+ private DebugSymbols symbols;
+ private DebugSystemObjects sysobjs;
+
+ // Keep references to callbacks here, since JNA doesn't keep one for things handed to natives.
+ protected MarkerOutputCallbacks listenerOutput;
+ protected MarkerInputCallbacks listenerInput;
+ protected MarkerEventCallbacks listenerEvent;
+
+ public DebugClientImpl1(IDebugClient jnaClient) {
+ // TODO: Debug and verify COM resource management
+ this.cleanable = DbgEng.releaseWhenPhantom(this, jnaClient);
+ this.jnaClient = jnaClient;
+ }
+
+ @Override
+ public IDebugClient getJNAClient() {
+ return jnaClient;
+ }
+
+ @Override
+ public DebugAdvanced getAdvanced() {
+ if (advanced == null) {
+ advanced = DebugAdvancedInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
+ }
+ return advanced;
+ }
+
+ @Override
+ public DebugControlInternal getControlInternal() {
+ if (control == null) {
+ control = DebugControlInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
+ }
+ return control;
+ }
+
+ @Override
+ public DebugControl getControl() {
+ return getControlInternal();
+ }
+
+ @Override
+ public DebugDataSpaces getDataSpaces() {
+ if (data == null) {
+ data = DebugDataSpacesInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
+ }
+ return data;
+ }
+
+ @Override
+ public DebugRegisters getRegisters() {
+ if (registers == null) {
+ registers = DebugRegistersInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
+ }
+ return registers;
+ }
+
+ @Override
+ public DebugSymbols getSymbols() {
+ if (symbols == null) {
+ symbols = DebugSymbolsInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
+ }
+ return symbols;
+ }
+
+ @Override
+ public DebugSystemObjects getSystemObjects() {
+ if (sysobjs == null) {
+ sysobjs = DebugSystemObjectsInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
+ }
+ return sysobjs;
+ }
+
+ @Override
+ public DebugServerId getLocalServer() {
+ return new DebugServerId(0);
+ }
+
+ @Override
+ public void startProcessServer(String options) {
+ COMUtils.checkRC(jnaClient.StartProcessServer(new ULONG(DebugClass.USER_WINDOWS.ordinal()),
+ options, null));
+ }
+
+ @Override
+ public DebugServerId connectProcessServer(String options) {
+ ULONGLONGByReference pulServer = new ULONGLONGByReference();
+ COMUtils.checkRC(jnaClient.ConnectProcessServer(options, pulServer));
+ return new DebugServerId(pulServer.getValue().longValue());
+ }
+
+ @Override
+ public List getRunningProcesses(DebugServerId si) {
+ ULONGLONG server = new ULONGLONG(si.id);
+ ULONGByReference actualCount = new ULONGByReference();
+ COMUtils.checkRC(
+ jnaClient.GetRunningProcessSystemIds(server, null, new ULONG(0), actualCount));
+
+ int[] ids = new int[actualCount.getValue().intValue()];
+ COMUtils.checkRC(
+ jnaClient.GetRunningProcessSystemIds(server, ids, actualCount.getValue(), null));
+
+ List result = new ArrayList<>(ids.length);
+ for (int id : ids) {
+ result.add(new DebugRunningProcessImpl(this, si, id));
+ }
+ return result;
+ }
+
+ @Override
+ public DebugRunningProcess.Description getProcessDescription(DebugServerId si, int systemId,
+ BitmaskSet flags) {
+ ULONGLONG ullServer = new ULONGLONG(si.id);
+ ULONG ulId = new ULONG(systemId);
+ ULONG ulFlags = new ULONG(flags.getBitmask());
+
+ ULONGByReference actualExeNameSize = new ULONGByReference();
+ ULONGByReference actualDescriptionSize = new ULONGByReference();
+ COMUtils.checkRC(jnaClient.GetRunningProcessDescription(ullServer, ulId, ulFlags, null,
+ new ULONG(0), actualExeNameSize, null, new ULONG(0), actualDescriptionSize));
+
+ byte[] exeName = new byte[actualExeNameSize.getValue().intValue()];
+ byte[] description = new byte[actualDescriptionSize.getValue().intValue()];
+ COMUtils.checkRC(jnaClient.GetRunningProcessDescription(ullServer, ulId, ulFlags, exeName,
+ actualExeNameSize.getValue(), null, description, actualDescriptionSize.getValue(),
+ null));
+
+ return new DebugRunningProcess.Description(systemId, Native.toString(exeName),
+ Native.toString(description));
+ }
+
+ @Override
+ public void attachProcess(DebugServerId si, int processId,
+ BitmaskSet attachFlags) {
+ ULONGLONG ullServer = new ULONGLONG(si.id);
+ ULONG ulPid = new ULONG(processId);
+ ULONG ulFlags = new ULONG(attachFlags.getBitmask());
+ COMUtils.checkRC(jnaClient.AttachProcess(ullServer, ulPid, ulFlags));
+ }
+
+ @Override
+ public void createProcess(DebugServerId si, String commandLine,
+ BitmaskSet createFlags) {
+ ULONGLONG ullServer = new ULONGLONG(si.id);
+ ULONG ulFlags = new ULONG(createFlags.getBitmask());
+ COMUtils.checkRC(jnaClient.CreateProcess(ullServer, commandLine, ulFlags));
+ }
+
+ @Override
+ public void createProcessAndAttach(DebugServerId si, String commandLine,
+ BitmaskSet createFlags, int processId,
+ BitmaskSet attachFlags) {
+ ULONGLONG ullServer = new ULONGLONG(si.id);
+ ULONG ulFlags1 = new ULONG(createFlags.getBitmask());
+ ULONG ulPid = new ULONG(processId);
+ ULONG ulFlags2 = new ULONG(attachFlags.getBitmask());
+ COMUtils.checkRC(
+ jnaClient.CreateProcessAndAttach(ullServer, commandLine, ulFlags1, ulPid, ulFlags2));
+ }
+
+ @Override
+ public void startServer(String options) {
+ COMUtils.checkRC(jnaClient.StartServer(options));
+ }
+
+ @Override
+ public boolean dispatchCallbacks(int timeout) {
+ HRESULT hr = jnaClient.DispatchCallbacks(new ULONG(timeout));
+ COMUtils.checkRC(hr);
+ return hr.equals(WinNT.S_OK);
+ }
+
+ @Override
+ public void flushCallbacks() {
+ HRESULT hr = jnaClient.FlushCallbacks();
+ COMUtils.checkRC(hr);
+ }
+
+ @Override
+ public void exitDispatch(DebugClient client) {
+ DebugClientInternal ic = (DebugClientInternal) client;
+ COMUtils.checkRC(jnaClient.ExitDispatch(ic.getJNAClient()));
+ }
+
+ @Override
+ public DebugClient createClient() {
+ PointerByReference ppClient = new PointerByReference();
+ COMUtils.checkRC(jnaClient.CreateClient(ppClient));
+ return DebugClientInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
+ }
+
+ @Override
+ public void setInputCallbacks(DebugInputCallbacks cb) {
+ ListenerIDebugInputCallbacks listener = null;
+ if (cb != null) {
+ WrapCallbackIDebugInputCallbacks callback =
+ new WrapCallbackIDebugInputCallbacks(this, cb);
+ listener = new ListenerIDebugInputCallbacks(callback);
+ callback.setListener(listener);
+ }
+ COMUtils.checkRC(jnaClient.SetInputCallbacks(listener));
+ listenerInput = listener;
+ }
+
+ @Override
+ public void setOutputCallbacks(DebugOutputCallbacks cb) {
+ ListenerIDebugOutputCallbacks listener = null;
+ if (cb != null) {
+ WrapCallbackIDebugOutputCallbacks callback = new WrapCallbackIDebugOutputCallbacks(cb);
+ listener = new ListenerIDebugOutputCallbacks(callback);
+ callback.setListener(listener);
+ }
+ COMUtils.checkRC(jnaClient.SetOutputCallbacks(listener));
+ listenerOutput = listener;
+ }
+
+ @Override
+ public void setEventCallbacks(DebugEventCallbacks cb) {
+ ListenerIDebugEventCallbacks listener = null;
+ if (cb != null) {
+ WrapCallbackIDebugEventCallbacks callback =
+ new WrapCallbackIDebugEventCallbacks(this, cb);
+ listener = new ListenerIDebugEventCallbacks(callback);
+ callback.setListener(listener);
+ }
+ COMUtils.checkRC(jnaClient.SetEventCallbacks(listener));
+ listenerEvent = listener;
+ }
+
+ @Override
+ public void terminateCurrentProcess() {
+ throw new UnsupportedOperationException("Not implemented by this interface");
+ }
+
+ @Override
+ public void detachCurrentProcess() {
+ throw new UnsupportedOperationException("Not implemented by this interface");
+ }
+
+ @Override
+ public void abandonCurrentProcess() {
+ throw new UnsupportedOperationException("Not implemented by this interface");
+ }
+
+ @Override
+ public void waitForProcessServerEnd(int timeout) {
+ throw new UnsupportedOperationException("Not implemented by this interface");
+ }
+
+ @Override
+ public void endSession(DebugEndSessionFlags flags) {
+ COMUtils.checkRC(jnaClient.EndSession(new ULONG(flags.getValue())));
+ }
+
+ @Override
+ public void connectSession(int flags) {
+ COMUtils.checkRC(jnaClient.ConnectSession(new ULONG(flags), new ULONG(10000)));
+ }
+
+ @Override
+ public void openDumpFileWide(String fileName) {
+ throw new UnsupportedOperationException("Not implemented by this interface");
+ }
+
+ @Override
+ public void attachKernel(long flags, String options) {
+ throw new UnsupportedOperationException("Not implemented by this interface");
+ }
+
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl2.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl2.java
new file mode 100644
index 0000000000..15578d0a24
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl2.java
@@ -0,0 +1,50 @@
+/* ###
+ * 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.impl.dbgeng.client;
+
+import com.sun.jna.platform.win32.WinDef.ULONG;
+import com.sun.jna.platform.win32.COM.COMUtils;
+
+import agent.dbgeng.jna.dbgeng.client.IDebugClient2;
+
+public class DebugClientImpl2 extends DebugClientImpl1 {
+ private final IDebugClient2 jnaClient;
+
+ public DebugClientImpl2(IDebugClient2 jnaClient) {
+ super(jnaClient);
+ this.jnaClient = jnaClient;
+ }
+
+ @Override
+ public void terminateCurrentProcess() {
+ COMUtils.checkRC(jnaClient.TerminateCurrentProcess());
+ }
+
+ @Override
+ public void detachCurrentProcess() {
+ COMUtils.checkRC(jnaClient.DetachCurrentProcess());
+ }
+
+ @Override
+ public void abandonCurrentProcess() {
+ COMUtils.checkRC(jnaClient.AbandonCurrentProcess());
+ }
+
+ @Override
+ public void waitForProcessServerEnd(int timeout) {
+ COMUtils.checkRC(jnaClient.WaitForProcessServerEnd(new ULONG(timeout)));
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl3.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl3.java
new file mode 100644
index 0000000000..106ed2fc34
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl3.java
@@ -0,0 +1,68 @@
+/* ###
+ * 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.impl.dbgeng.client;
+
+import com.sun.jna.Native;
+import com.sun.jna.WString;
+import com.sun.jna.platform.win32.WinDef.*;
+
+import agent.dbgeng.dbgeng.DebugRunningProcess;
+import agent.dbgeng.dbgeng.DebugServerId;
+import agent.dbgeng.dbgeng.DebugRunningProcess.Description.ProcessDescriptionFlags;
+import agent.dbgeng.jna.dbgeng.client.IDebugClient3;
+
+import com.sun.jna.platform.win32.COM.COMUtils;
+
+import ghidra.comm.util.BitmaskSet;
+
+public class DebugClientImpl3 extends DebugClientImpl2 {
+ private final IDebugClient3 jnaClient;
+
+ public DebugClientImpl3(IDebugClient3 jnaClient) {
+ super(jnaClient);
+ this.jnaClient = jnaClient;
+ }
+
+ @Override
+ public void createProcess(DebugServerId si, String commandLine,
+ BitmaskSet createFlags) {
+ ULONGLONG ullServer = new ULONGLONG(si.id);
+ ULONG ulFlags = new ULONG(createFlags.getBitmask());
+ COMUtils.checkRC(jnaClient.CreateProcessWide(ullServer, new WString(commandLine), ulFlags));
+ }
+
+ @Override
+ public DebugRunningProcess.Description getProcessDescription(DebugServerId si, int systemId,
+ BitmaskSet flags) {
+ ULONGLONG server = new ULONGLONG(si.id);
+ ULONG id = new ULONG(systemId);
+ ULONG f = new ULONG(flags.getBitmask());
+
+ ULONGByReference actualExeNameSize = new ULONGByReference();
+ ULONGByReference actualDescriptionSize = new ULONGByReference();
+ COMUtils.checkRC(jnaClient.GetRunningProcessDescriptionWide(server, id, f, null,
+ new ULONG(0), actualExeNameSize, null, new ULONG(0), actualDescriptionSize));
+
+ char[] exeName = new char[actualExeNameSize.getValue().intValue()];
+ char[] description = new char[actualDescriptionSize.getValue().intValue()];
+ COMUtils.checkRC(jnaClient.GetRunningProcessDescriptionWide(server, id, f, exeName,
+ actualExeNameSize.getValue(), null, description, actualDescriptionSize.getValue(),
+ null));
+
+ return new DebugRunningProcess.Description(systemId, Native.toString(exeName),
+ Native.toString(description));
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl4.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl4.java
new file mode 100644
index 0000000000..a97d1fd041
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl4.java
@@ -0,0 +1,39 @@
+/* ###
+ * 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.impl.dbgeng.client;
+
+import com.sun.jna.WString;
+import com.sun.jna.platform.win32.WinDef.ULONGLONG;
+
+import agent.dbgeng.jna.dbgeng.client.IDebugClient4;
+
+import com.sun.jna.platform.win32.COM.COMUtils;
+
+public class DebugClientImpl4 extends DebugClientImpl3 {
+ @SuppressWarnings("unused")
+ private final IDebugClient4 jnaClient;
+
+ public DebugClientImpl4(IDebugClient4 jnaClient) {
+ super(jnaClient);
+ this.jnaClient = jnaClient;
+ }
+
+ @Override
+ public void openDumpFileWide(String fileName) {
+ ULONGLONG ullFileHandle = new ULONGLONG(0);
+ COMUtils.checkRC(jnaClient.OpenDumpFileWide(new WString(fileName), ullFileHandle));
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl5.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl5.java
new file mode 100644
index 0000000000..0170458096
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl5.java
@@ -0,0 +1,82 @@
+/* ###
+ * 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.impl.dbgeng.client;
+
+import com.sun.jna.WString;
+import com.sun.jna.platform.win32.WinDef.ULONG;
+import com.sun.jna.platform.win32.WinDef.ULONGLONGByReference;
+import com.sun.jna.platform.win32.COM.COMUtils;
+
+import agent.dbgeng.dbgeng.*;
+import agent.dbgeng.impl.dbgeng.event.WrapCallbackIDebugEventCallbacksWide;
+import agent.dbgeng.impl.dbgeng.io.WrapCallbackIDebugOutputCallbacksWide;
+import agent.dbgeng.jna.dbgeng.client.IDebugClient5;
+import agent.dbgeng.jna.dbgeng.event.ListenerIDebugEventCallbacksWide;
+import agent.dbgeng.jna.dbgeng.io.ListenerIDebugOutputCallbacksWide;
+
+public class DebugClientImpl5 extends DebugClientImpl4 {
+ private final IDebugClient5 jnaClient;
+
+ public DebugClientImpl5(IDebugClient5 jnaClient) {
+ super(jnaClient);
+ this.jnaClient = jnaClient;
+ }
+
+ @Override
+ public void attachKernel(long flags, String options) {
+ ULONG connectFlags = new ULONG(flags);
+ COMUtils.checkRC(jnaClient.AttachKernelWide(connectFlags, new WString(options)));
+ }
+
+ @Override
+ public void startProcessServer(String options) {
+ COMUtils.checkRC(jnaClient.StartProcessServerWide(
+ new ULONG(DebugClass.USER_WINDOWS.ordinal()), new WString(options), null));
+ }
+
+ @Override
+ public DebugServerId connectProcessServer(String options) {
+ ULONGLONGByReference pulServer = new ULONGLONGByReference();
+ COMUtils.checkRC(jnaClient.ConnectProcessServerWide(new WString(options), pulServer));
+ return new DebugServerId(pulServer.getValue().longValue());
+ }
+
+ @Override
+ public void setOutputCallbacks(DebugOutputCallbacks cb) {
+ ListenerIDebugOutputCallbacksWide listener = null;
+ if (cb != null) {
+ WrapCallbackIDebugOutputCallbacksWide callback =
+ new WrapCallbackIDebugOutputCallbacksWide(cb);
+ listener = new ListenerIDebugOutputCallbacksWide(callback);
+ callback.setListener(listener);
+ }
+ COMUtils.checkRC(jnaClient.SetOutputCallbacksWide(listener));
+ listenerOutput = listener;
+ }
+
+ @Override
+ public void setEventCallbacks(DebugEventCallbacks cb) {
+ ListenerIDebugEventCallbacksWide listener = null;
+ if (cb != null) {
+ WrapCallbackIDebugEventCallbacksWide callback =
+ new WrapCallbackIDebugEventCallbacksWide(this, cb);
+ listener = new ListenerIDebugEventCallbacksWide(callback);
+ callback.setListener(listener);
+ }
+ COMUtils.checkRC(jnaClient.SetEventCallbacksWide(listener));
+ listenerEvent = listener;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl6.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl6.java
new file mode 100644
index 0000000000..8282a39d72
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl6.java
@@ -0,0 +1,35 @@
+/* ###
+ * 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.impl.dbgeng.client;
+
+import agent.dbgeng.dbgeng.DebugEventCallbacks;
+import agent.dbgeng.jna.dbgeng.client.IDebugClient6;
+
+public class DebugClientImpl6 extends DebugClientImpl5 {
+ @SuppressWarnings("unused")
+ private final IDebugClient6 jnaClient;
+
+ public DebugClientImpl6(IDebugClient6 jnaClient) {
+ super(jnaClient);
+ this.jnaClient = jnaClient;
+ }
+
+ @Override
+ public void setEventCallbacks(DebugEventCallbacks cb) {
+ // TODO: Use Context variant. Will require expanding the generic interface.
+ super.setEventCallbacks(cb);
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl7.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl7.java
new file mode 100644
index 0000000000..3ac1b9139c
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientImpl7.java
@@ -0,0 +1,28 @@
+/* ###
+ * 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.impl.dbgeng.client;
+
+import agent.dbgeng.jna.dbgeng.client.IDebugClient7;
+
+public class DebugClientImpl7 extends DebugClientImpl6 {
+ @SuppressWarnings("unused")
+ private final IDebugClient7 jnaClient;
+
+ public DebugClientImpl7(IDebugClient7 jnaClient) {
+ super(jnaClient);
+ this.jnaClient = jnaClient;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientInternal.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientInternal.java
new file mode 100644
index 0000000000..f8e59a8f1a
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/client/DebugClientInternal.java
@@ -0,0 +1,96 @@
+/* ###
+ * 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.impl.dbgeng.client;
+
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import com.sun.jna.Pointer;
+import com.sun.jna.platform.win32.Guid.REFIID;
+
+import agent.dbgeng.dbgeng.DebugClient;
+import agent.dbgeng.impl.dbgeng.DbgEngUtil;
+import agent.dbgeng.impl.dbgeng.DbgEngUtil.InterfaceSupplier;
+import agent.dbgeng.impl.dbgeng.control.DebugControlInternal;
+import agent.dbgeng.jna.dbgeng.client.*;
+import ghidra.util.datastruct.WeakValueHashMap;
+
+public interface DebugClientInternal extends DebugClient {
+ Map CACHE = new WeakValueHashMap<>();
+
+ enum DebugClass {
+ UNINITIALIZED, //
+ KERNEL, //
+ USER_WINDOWS, //
+ IMAGE_FILE, //
+ ;
+ }
+
+ static DebugClientInternal instanceFor(WrapIDebugClient client) {
+ return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl1::new);
+ }
+
+ static DebugClientInternal instanceFor(WrapIDebugClient2 client) {
+ return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl2::new);
+ }
+
+ static DebugClientInternal instanceFor(WrapIDebugClient3 client) {
+ return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl3::new);
+ }
+
+ static DebugClientInternal instanceFor(WrapIDebugClient4 client) {
+ return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl4::new);
+ }
+
+ static DebugClientInternal instanceFor(WrapIDebugClient5 client) {
+ return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl5::new);
+ }
+
+ static DebugClientInternal instanceFor(WrapIDebugClient6 client) {
+ return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl6::new);
+ }
+
+ static DebugClientInternal instanceFor(WrapIDebugClient7 client) {
+ return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl7::new);
+ }
+
+ ImmutableMap.Builder> PREFERRED_CLIENT_IIDS_BUILDER =
+ ImmutableMap.builder();
+ Map> PREFERRED_CLIENT_IIDS =
+ PREFERRED_CLIENT_IIDS_BUILDER //
+ .put(new REFIID(IDebugClient7.IID_IDEBUG_CLIENT7), WrapIDebugClient7.class) //
+ .put(new REFIID(IDebugClient6.IID_IDEBUG_CLIENT6), WrapIDebugClient6.class) //
+ .put(new REFIID(IDebugClient5.IID_IDEBUG_CLIENT5), WrapIDebugClient5.class) //
+ .put(new REFIID(IDebugClient4.IID_IDEBUG_CLIENT4), WrapIDebugClient4.class) //
+ .put(new REFIID(IDebugClient3.IID_IDEBUG_CLIENT3), WrapIDebugClient3.class) //
+ .put(new REFIID(IDebugClient2.IID_IDEBUG_CLIENT2), WrapIDebugClient2.class) //
+ .put(new REFIID(IDebugClient.IID_IDEBUG_CLIENT), WrapIDebugClient.class) //
+ .build();
+
+ static DebugClientInternal tryPreferredInterfaces(InterfaceSupplier supplier) {
+ return DbgEngUtil.tryPreferredInterfaces(DebugClientInternal.class, PREFERRED_CLIENT_IIDS,
+ supplier);
+ }
+
+ IDebugClient getJNAClient();
+
+ DebugControlInternal getControlInternal();
+
+ @Override
+ default void endSessionReentrant() {
+ endSession(DebugEndSessionFlags.DEBUG_END_REENTRANT);
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl1.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl1.java
new file mode 100644
index 0000000000..ac8936f9e2
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl1.java
@@ -0,0 +1,300 @@
+/* ###
+ * 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.impl.dbgeng.control;
+
+import javax.help.UnsupportedOperationException;
+
+import com.sun.jna.Native;
+import com.sun.jna.platform.win32.WinDef.*;
+import com.sun.jna.platform.win32.WinError;
+import com.sun.jna.platform.win32.WinNT.HRESULT;
+import com.sun.jna.platform.win32.COM.COMUtils;
+import com.sun.jna.ptr.PointerByReference;
+
+import agent.dbgeng.dbgeng.*;
+import agent.dbgeng.dbgeng.DbgEng.OpaqueCleanable;
+import agent.dbgeng.dbgeng.DebugBreakpoint.BreakType;
+import agent.dbgeng.dbgeng.DebugClient.DebugStatus;
+import agent.dbgeng.dbgeng.DebugValue.DebugValueType;
+import agent.dbgeng.impl.dbgeng.DbgEngUtil;
+import agent.dbgeng.impl.dbgeng.breakpoint.DebugBreakpointInternal;
+import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_STACK_FRAME;
+import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_VALUE;
+import agent.dbgeng.jna.dbgeng.breakpoint.IDebugBreakpoint;
+import agent.dbgeng.jna.dbgeng.breakpoint.WrapIDebugBreakpoint;
+import agent.dbgeng.jna.dbgeng.control.IDebugControl;
+import ghidra.comm.util.BitmaskSet;
+
+public class DebugControlImpl1 implements DebugControlInternal {
+ @SuppressWarnings("unused")
+ private final OpaqueCleanable cleanable;
+ private final IDebugControl jnaControl;
+
+ public DebugControlImpl1(IDebugControl jnaControl) {
+ this.cleanable = DbgEng.releaseWhenPhantom(this, jnaControl);
+ this.jnaControl = jnaControl;
+ }
+
+ @Override
+ public boolean getInterrupt() {
+ HRESULT interrupt = jnaControl.GetInterrupt();
+ if (interrupt.equals(WinError.S_OK)) {
+ return true;
+ }
+ if (interrupt.equals(WinError.S_FALSE)) {
+ return false;
+ }
+ COMUtils.checkRC(interrupt);
+ throw new AssertionError("Shouldn't get here");
+ }
+
+ @Override
+ public void setInterrupt(DebugInterrupt interrupt) {
+ ULONG flags = new ULONG(interrupt.ordinal());
+ COMUtils.checkRC(jnaControl.SetInterrupt(flags));
+ }
+
+ @Override
+ public int getInterruptTimeout() {
+ ULONGByReference pulSeconds = new ULONGByReference();
+ COMUtils.checkRC(jnaControl.GetInterruptTimeout(pulSeconds));
+ return pulSeconds.getValue().intValue();
+ }
+
+ @Override
+ public void setInterruptTimeout(int seconds) {
+ ULONG ulSeconds = new ULONG(seconds);
+ COMUtils.checkRC(jnaControl.SetInterruptTimeout(ulSeconds));
+ }
+
+ @Override
+ public void print(BitmaskSet levels, String message) {
+ ULONG mask = new ULONG(levels.getBitmask());
+ COMUtils.checkRC(jnaControl.Output(mask, "%s", message));
+ }
+
+ @Override
+ public void println(BitmaskSet levels, String message) {
+ ULONG mask = new ULONG(levels.getBitmask());
+ COMUtils.checkRC(jnaControl.Output(mask, "%s", message + "\r\n"));
+ }
+
+ @Override
+ public void prompt(BitmaskSet ctl, String message) {
+ ULONG ctlMask = new ULONG(ctl.getBitmask());
+ COMUtils.checkRC(jnaControl.OutputPrompt(ctlMask, "%s", message));
+ }
+
+ @Override
+ public String getPromptText() {
+ ULONGByReference pulTextSize = new ULONGByReference();
+ COMUtils.checkRC(jnaControl.GetPromptText(null, new ULONG(0), pulTextSize));
+ byte[] buffer = new byte[pulTextSize.getValue().intValue()];
+ COMUtils.checkRC(jnaControl.GetPromptText(buffer, pulTextSize.getValue(), null));
+ return Native.toString(buffer);
+ }
+
+ protected DEBUG_VALUE doEval(DebugValueType type, String expression) {
+ DEBUG_VALUE.ByReference value = new DEBUG_VALUE.ByReference();
+ ULONGByReference pulRemainder = new ULONGByReference();
+ COMUtils.checkRC(
+ jnaControl.Evaluate(expression, new ULONG(type.ordinal()), value, pulRemainder));
+ int remainder = pulRemainder.getValue().intValue();
+ if (remainder != expression.length()) {
+ throw new RuntimeException("Failed to parse: " + expression.substring(remainder));
+ }
+ return value;
+ }
+
+ @Override
+ public T evaluate(Class desiredType, String expression) {
+ DebugValueType type = DebugValueType.getDebugValueTypeForClass(desiredType);
+ return doEval(type, expression).convertTo(desiredType);
+ }
+
+ @Override
+ public void execute(BitmaskSet ctl, String cmd,
+ BitmaskSet flags) {
+ ULONG ctlMask = new ULONG(ctl.getBitmask());
+ ULONG flagMask = new ULONG(flags.getBitmask());
+ COMUtils.checkRC(jnaControl.Execute(ctlMask, cmd, flagMask));
+ }
+
+ @Override
+ public void returnInput(String input) {
+ COMUtils.checkRC(jnaControl.ReturnInput(input));
+ }
+
+ @Override
+ public DebugStatus getExecutionStatus() {
+ ULONGByReference pulStatus = new ULONGByReference();
+ COMUtils.checkRC(jnaControl.GetExecutionStatus(pulStatus));
+ return DebugStatus.values()[pulStatus.getValue().intValue()];
+ }
+
+ @Override
+ public void setExecutionStatus(DebugStatus status) {
+ ULONG ulStatus = new ULONG(status.ordinal());
+ HRESULT hr = jnaControl.SetExecutionStatus(ulStatus);
+ if (!hr.equals(COMUtilsExtra.E_ACCESS_DENIED)) {
+ COMUtils.checkRC(hr);
+ }
+ }
+
+ public DebugBreakpoint doAddBreakpoint(BreakType type, ULONG ulDesiredId) {
+ ULONG ulType = new ULONG(type.ordinal());
+ PointerByReference ppBp = new PointerByReference();
+ COMUtils.checkRC(jnaControl.AddBreakpoint(ulType, ulDesiredId, ppBp));
+ IDebugBreakpoint Bp = new WrapIDebugBreakpoint(ppBp.getValue());
+ DebugBreakpoint bpt =
+ DebugBreakpointInternal.tryPreferredInterfaces(this, Bp::QueryInterface);
+ // AddRef or no? Probably not.
+ return bpt;
+ }
+
+ @Override
+ public int getNumberBreakpoints() {
+ ULONGByReference ulNumber = new ULONGByReference();
+ COMUtils.checkRC(jnaControl.GetNumberBreakpoints(ulNumber));
+ return ulNumber.getValue().intValue();
+ }
+
+ @Override
+ public DebugBreakpoint getBreakpointByIndex(int index) {
+ ULONG ulIndex = new ULONG(index);
+ PointerByReference ppBp = new PointerByReference();
+ COMUtils.checkRC(jnaControl.GetBreakpointByIndex(ulIndex, ppBp));
+ IDebugBreakpoint Bp = new WrapIDebugBreakpoint(ppBp.getValue());
+ DebugBreakpoint bpt =
+ DebugBreakpointInternal.tryPreferredInterfaces(this, Bp::QueryInterface);
+ // NOTE: Do not AddRef. dbgeng manages lifecycle
+ return bpt;
+ }
+
+ @Override
+ public DebugBreakpoint getBreakpointById(int id) {
+ ULONG ulId = new ULONG(id);
+ PointerByReference ppBp = new PointerByReference();
+ HRESULT hr = jnaControl.GetBreakpointById(ulId, ppBp);
+ if (hr.equals(COMUtilsExtra.E_NOINTERFACE)) {
+ return null;
+ }
+ if (hr.equals(COMUtilsExtra.E_UNEXPECTED)) {
+ return null;
+ }
+ COMUtils.checkRC(hr);
+ IDebugBreakpoint Bp = new WrapIDebugBreakpoint(ppBp.getValue());
+ DebugBreakpoint bpt =
+ DebugBreakpointInternal.tryPreferredInterfaces(this, Bp::QueryInterface);
+ // NOTE: Do not AddRef. dbgeng manages lifecycle
+ return bpt;
+ }
+
+ @Override
+ public DebugBreakpoint addBreakpoint(BreakType type, int desiredId) {
+ return doAddBreakpoint(type, new ULONG(desiredId));
+ }
+
+ @Override
+ public DebugBreakpoint addBreakpoint(BreakType type) {
+ return doAddBreakpoint(type, DbgEngUtil.DEBUG_ANY_ID);
+ }
+
+ @Override
+ public DebugBreakpoint addBreakpoint2(BreakType type) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DebugBreakpoint addBreakpoint2(BreakType type, int desiredId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeBreakpoint(IDebugBreakpoint comBpt) {
+ COMUtils.checkRC(jnaControl.RemoveBreakpoint(comBpt));
+ }
+
+ @Override
+ public void waitForEvent(int timeout) {
+ COMUtils.checkRC(jnaControl.WaitForEvent(new ULONG(0), new ULONG(timeout)));
+ }
+
+ @Override
+ public DebugEventInformation getLastEventInformation() {
+ ULONGByReference pulType = new ULONGByReference();
+ ULONGByReference pulProcessId = new ULONGByReference();
+ ULONGByReference pulThreadId = new ULONGByReference();
+ //PointerByReference pExtraInformation = new PointerByReference();
+ ULONG ulExtraInformationSize = new ULONG(0);
+ ULONGByReference pulExtraInformationUsed = new ULONGByReference();
+ //byte[] pstrDescription = new byte[0];
+ ULONG ulDescriptionSize = new ULONG(0);
+ ULONGByReference pulDescriptionUsed = new ULONGByReference();
+ COMUtils.checkRC(jnaControl.GetLastEventInformation(pulType, pulProcessId, pulThreadId,
+ null, ulExtraInformationSize, pulExtraInformationUsed, null, ulDescriptionSize,
+ pulDescriptionUsed));
+ return new DebugEventInformation(pulType.getValue().intValue(),
+ pulProcessId.getValue().intValue(), pulThreadId.getValue().intValue());
+ }
+
+ @Override
+ public DebugStackInformation getStackTrace(long frameOffset, long stackOffset,
+ long instructionOffset) {
+ ULONGLONG ullFrameOffset = new ULONGLONG(frameOffset);
+ ULONGLONG ullStackOffset = new ULONGLONG(stackOffset);
+ ULONGLONG ullInstructionOffset = new ULONGLONG(instructionOffset);
+ ULONG ulFrameSize = new ULONG(100);
+ DEBUG_STACK_FRAME[] pParams = new DEBUG_STACK_FRAME[ulFrameSize.intValue()];
+ ULONGByReference pulFramesFilled = new ULONGByReference();
+ COMUtils.checkRC(jnaControl.GetStackTrace(ullFrameOffset, ullStackOffset,
+ ullInstructionOffset, pParams, ulFrameSize, pulFramesFilled));
+ return new DebugStackInformation(pulFramesFilled.getValue().intValue(), pParams);
+ }
+
+ @Override
+ public int getActualProcessorType() {
+ ULONGByReference ulType = new ULONGByReference();
+ HRESULT hr = jnaControl.GetActualProcessorType(ulType);
+ if (hr.equals(COMUtilsExtra.E_UNEXPECTED)) {
+ return -1;
+ }
+ COMUtils.checkRC(hr);
+ return ulType.getValue().intValue();
+ }
+
+ @Override
+ public int getEffectiveProcessorType() {
+ ULONGByReference ulType = new ULONGByReference();
+ COMUtils.checkRC(jnaControl.GetEffectiveProcessorType(ulType));
+ return ulType.getValue().intValue();
+ }
+
+ @Override
+ public int getExecutingProcessorType() {
+ ULONGByReference ulType = new ULONGByReference();
+ COMUtils.checkRC(jnaControl.GetExecutingProcessorType(ulType));
+ return ulType.getValue().intValue();
+ }
+
+ @Override
+ public int getDebuggeeType() {
+ ULONGByReference ulClass = new ULONGByReference();
+ ULONGByReference ulQualifier = new ULONGByReference();
+ COMUtils.checkRC(jnaControl.GetDebuggeeType(ulClass, ulQualifier));
+ return ulClass.getValue().intValue();
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl2.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl2.java
new file mode 100644
index 0000000000..eb0f3f70db
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl2.java
@@ -0,0 +1,28 @@
+/* ###
+ * 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.impl.dbgeng.control;
+
+import agent.dbgeng.jna.dbgeng.control.IDebugControl2;
+
+public class DebugControlImpl2 extends DebugControlImpl1 {
+ @SuppressWarnings("unused")
+ private final IDebugControl2 jnaControl;
+
+ public DebugControlImpl2(IDebugControl2 jnaControl) {
+ super(jnaControl);
+ this.jnaControl = jnaControl;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl3.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl3.java
new file mode 100644
index 0000000000..b194837d4b
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl3.java
@@ -0,0 +1,28 @@
+/* ###
+ * 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.impl.dbgeng.control;
+
+import agent.dbgeng.jna.dbgeng.control.IDebugControl3;
+
+public class DebugControlImpl3 extends DebugControlImpl2 {
+ @SuppressWarnings("unused")
+ private final IDebugControl3 jnaControl;
+
+ public DebugControlImpl3(IDebugControl3 jnaControl) {
+ super(jnaControl);
+ this.jnaControl = jnaControl;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl4.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl4.java
new file mode 100644
index 0000000000..30ddba27e2
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl4.java
@@ -0,0 +1,122 @@
+/* ###
+ * 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.impl.dbgeng.control;
+
+import com.sun.jna.Native;
+import com.sun.jna.WString;
+import com.sun.jna.platform.win32.WinDef.ULONG;
+import com.sun.jna.platform.win32.WinDef.ULONGByReference;
+import com.sun.jna.platform.win32.COM.COMUtils;
+import com.sun.jna.ptr.PointerByReference;
+
+import agent.dbgeng.dbgeng.DebugBreakpoint;
+import agent.dbgeng.dbgeng.DebugBreakpoint.BreakType;
+import agent.dbgeng.dbgeng.DebugValue.DebugValueType;
+import agent.dbgeng.impl.dbgeng.DbgEngUtil;
+import agent.dbgeng.impl.dbgeng.breakpoint.DebugBreakpointInternal;
+import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_VALUE;
+import agent.dbgeng.jna.dbgeng.breakpoint.IDebugBreakpoint;
+import agent.dbgeng.jna.dbgeng.breakpoint.WrapIDebugBreakpoint;
+import agent.dbgeng.jna.dbgeng.control.IDebugControl4;
+import ghidra.comm.util.BitmaskSet;
+
+public class DebugControlImpl4 extends DebugControlImpl3 {
+ private final IDebugControl4 jnaControl;
+
+ public DebugControlImpl4(IDebugControl4 jnaControl) {
+ super(jnaControl);
+ this.jnaControl = jnaControl;
+ }
+
+ @Override
+ public void print(BitmaskSet levels, String message) {
+ ULONG mask = new ULONG(levels.getBitmask());
+ COMUtils.checkRC(jnaControl.OutputWide(mask, new WString("%s"), new WString(message)));
+ }
+
+ @Override
+ public void println(BitmaskSet levels, String message) {
+ ULONG mask = new ULONG(levels.getBitmask());
+ COMUtils
+ .checkRC(
+ jnaControl.OutputWide(mask, new WString("%s"), new WString(message + "\r\n")));
+ }
+
+ @Override
+ public void prompt(BitmaskSet ctl, String message) {
+ ULONG ctlMask = new ULONG(ctl.getBitmask());
+ COMUtils
+ .checkRC(
+ jnaControl.OutputPromptWide(ctlMask, new WString("%s"), new WString(message)));
+ }
+
+ @Override
+ public String getPromptText() {
+ ULONGByReference pulTextSize = new ULONGByReference();
+ COMUtils.checkRC(jnaControl.GetPromptTextWide(null, new ULONG(0), pulTextSize));
+ char[] buffer = new char[pulTextSize.getValue().intValue()];
+ COMUtils.checkRC(jnaControl.GetPromptTextWide(buffer, pulTextSize.getValue(), null));
+ return Native.toString(buffer);
+ }
+
+ @Override
+ protected DEBUG_VALUE doEval(DebugValueType type, String expression) {
+ DEBUG_VALUE.ByReference value = new DEBUG_VALUE.ByReference();
+ ULONGByReference pulRemainder = new ULONGByReference();
+ COMUtils.checkRC(jnaControl.EvaluateWide(new WString(expression), new ULONG(type.ordinal()),
+ value, pulRemainder));
+ int remainder = pulRemainder.getValue().intValue();
+ if (remainder != expression.length()) {
+ throw new RuntimeException("Failed to parse: " + expression.substring(remainder));
+ }
+ return value;
+ }
+
+ @Override
+ public void execute(BitmaskSet ctl, String cmd,
+ BitmaskSet flags) {
+ ULONG ctlMask = new ULONG(ctl.getBitmask());
+ ULONG flagMask = new ULONG(flags.getBitmask());
+ COMUtils.checkRC(jnaControl.ExecuteWide(ctlMask, new WString(cmd), flagMask));
+ }
+
+ @Override
+ public void returnInput(String input) {
+ COMUtils.checkRC(jnaControl.ReturnInputWide(new WString(input)));
+ }
+
+ public DebugBreakpoint doAddBreakpoint2(BreakType type, ULONG ulDesiredId) {
+ ULONG ulType = new ULONG(type.ordinal());
+ PointerByReference ppBp = new PointerByReference();
+ COMUtils.checkRC(jnaControl.AddBreakpoint2(ulType, ulDesiredId, ppBp));
+ IDebugBreakpoint Bp = new WrapIDebugBreakpoint(ppBp.getValue());
+ DebugBreakpoint bpt =
+ DebugBreakpointInternal.tryPreferredInterfaces(this, Bp::QueryInterface);
+ // AddRef or no? Probably not.
+ return bpt;
+ }
+
+ @Override
+ public DebugBreakpoint addBreakpoint2(BreakType type, int desiredId) {
+ return doAddBreakpoint2(type, new ULONG(desiredId));
+ }
+
+ @Override
+ public DebugBreakpoint addBreakpoint2(BreakType type) {
+ return doAddBreakpoint2(type, DbgEngUtil.DEBUG_ANY_ID);
+ }
+
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl5.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl5.java
new file mode 100644
index 0000000000..9b501e4d45
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl5.java
@@ -0,0 +1,28 @@
+/* ###
+ * 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.impl.dbgeng.control;
+
+import agent.dbgeng.jna.dbgeng.control.IDebugControl5;
+
+public class DebugControlImpl5 extends DebugControlImpl4 {
+ @SuppressWarnings("unused")
+ private final IDebugControl5 jnaControl;
+
+ public DebugControlImpl5(IDebugControl5 jnaControl) {
+ super(jnaControl);
+ this.jnaControl = jnaControl;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl6.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl6.java
new file mode 100644
index 0000000000..83186a2f5b
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl6.java
@@ -0,0 +1,28 @@
+/* ###
+ * 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.impl.dbgeng.control;
+
+import agent.dbgeng.jna.dbgeng.control.IDebugControl6;
+
+public class DebugControlImpl6 extends DebugControlImpl5 {
+ @SuppressWarnings("unused")
+ private final IDebugControl6 jnaControl;
+
+ public DebugControlImpl6(IDebugControl6 jnaControl) {
+ super(jnaControl);
+ this.jnaControl = jnaControl;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl7.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl7.java
new file mode 100644
index 0000000000..b026dc7bed
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlImpl7.java
@@ -0,0 +1,28 @@
+/* ###
+ * 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.impl.dbgeng.control;
+
+import agent.dbgeng.jna.dbgeng.control.IDebugControl7;
+
+public class DebugControlImpl7 extends DebugControlImpl6 {
+ @SuppressWarnings("unused")
+ private final IDebugControl7 jnaControl;
+
+ public DebugControlImpl7(IDebugControl7 jnaControl) {
+ super(jnaControl);
+ this.jnaControl = jnaControl;
+ }
+}
diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlInternal.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlInternal.java
new file mode 100644
index 0000000000..4e26bf7db6
--- /dev/null
+++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/impl/dbgeng/control/DebugControlInternal.java
@@ -0,0 +1,81 @@
+/* ###
+ * 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.impl.dbgeng.control;
+
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import com.sun.jna.Pointer;
+import com.sun.jna.platform.win32.Guid.REFIID;
+
+import agent.dbgeng.dbgeng.DebugControl;
+import agent.dbgeng.impl.dbgeng.DbgEngUtil;
+import agent.dbgeng.impl.dbgeng.DbgEngUtil.InterfaceSupplier;
+import agent.dbgeng.jna.dbgeng.breakpoint.IDebugBreakpoint;
+import agent.dbgeng.jna.dbgeng.control.*;
+import ghidra.util.datastruct.WeakValueHashMap;
+
+public interface DebugControlInternal extends DebugControl {
+ Map CACHE = new WeakValueHashMap<>();
+
+ static DebugControlInternal instanceFor(WrapIDebugControl control) {
+ return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl1::new);
+ }
+
+ static DebugControlInternal instanceFor(WrapIDebugControl2 control) {
+ return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl2::new);
+ }
+
+ static DebugControlInternal instanceFor(WrapIDebugControl3 control) {
+ return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl3::new);
+ }
+
+ static DebugControlInternal instanceFor(WrapIDebugControl4 control) {
+ return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl4::new);
+ }
+
+ static DebugControlInternal instanceFor(WrapIDebugControl5 control) {
+ return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl5::new);
+ }
+
+ static DebugControlInternal instanceFor(WrapIDebugControl6 control) {
+ return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl6::new);
+ }
+
+ static DebugControlInternal instanceFor(WrapIDebugControl7 control) {
+ return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl7::new);
+ }
+
+ ImmutableMap.Builder