mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-13 07:31:57 +00:00
Merge remote-tracking branch 'origin/debugger'
This commit is contained in:
commit
ac85e4efbb
@ -18,21 +18,19 @@ package agent.dbgeng;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.dbgeng.model.impl.DbgModelImpl;
|
||||
import ghidra.dbg.DebuggerModelFactory;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.LocalDebuggerModelFactory;
|
||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||
import ghidra.util.classfinder.ExtensionPointProperties;
|
||||
|
||||
/**
|
||||
* Note this is in the testing source because it's not meant to be shipped in
|
||||
* the release.... That may change if it proves stable, though, no?
|
||||
* Note this is in the testing source because it's not meant to be shipped in the release.... That
|
||||
* may change if it proves stable, though, no?
|
||||
*/
|
||||
@FactoryDescription( //
|
||||
brief = "IN-VM MS dbgeng local debugger", //
|
||||
htmlDetails = "Launch a dbgeng session in this same JVM" //
|
||||
)
|
||||
@ExtensionPointProperties(priority = 80)
|
||||
public class DbgEngInJvmDebuggerModelFactory implements LocalDebuggerModelFactory {
|
||||
public class DbgEngInJvmDebuggerModelFactory implements DebuggerModelFactory {
|
||||
|
||||
// TODO remoteTransport option?
|
||||
|
||||
|
@ -77,4 +77,9 @@ public interface DebugSymbols {
|
||||
int getSymbolOptions();
|
||||
|
||||
void setSymbolOptions(int options);
|
||||
|
||||
public int getCurrentScopeFrameIndex();
|
||||
|
||||
public void setCurrentScopeFrameIndex(int index);
|
||||
|
||||
}
|
||||
|
@ -220,4 +220,13 @@ public class DebugSymbolsImpl1 implements DebugSymbolsInternal {
|
||||
COMUtils.checkRC(jnaSymbols.SetSymbolOptions(ulOptions));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentScopeFrameIndex() {
|
||||
throw new UnsupportedOperationException("Not supported by this interface");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentScopeFrameIndex(int index) {
|
||||
throw new UnsupportedOperationException("Not supported by this interface");
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import java.util.*;
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.WString;
|
||||
import com.sun.jna.platform.win32.WinDef.*;
|
||||
import com.sun.jna.platform.win32.WinNT.HRESULT;
|
||||
import com.sun.jna.platform.win32.COM.COMUtils;
|
||||
|
||||
import agent.dbgeng.dbgeng.*;
|
||||
import agent.dbgeng.dbgeng.DebugModule.DebugModuleName;
|
||||
@ -28,8 +30,6 @@ import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_MODULE_AND_ID;
|
||||
import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_SYMBOL_ENTRY;
|
||||
import agent.dbgeng.jna.dbgeng.symbols.IDebugSymbols3;
|
||||
|
||||
import com.sun.jna.platform.win32.COM.COMUtils;
|
||||
|
||||
public class DebugSymbolsImpl3 extends DebugSymbolsImpl2 {
|
||||
private final IDebugSymbols3 jnaSymbols;
|
||||
|
||||
@ -38,6 +38,20 @@ public class DebugSymbolsImpl3 extends DebugSymbolsImpl2 {
|
||||
this.jnaSymbols = jnaSymbols;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentScopeFrameIndex() {
|
||||
ULONGByReference pulIndex = new ULONGByReference();
|
||||
COMUtils.checkRC(jnaSymbols.GetCurrentScopeFrameIndex(pulIndex));
|
||||
return pulIndex.getValue().intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentScopeFrameIndex(int index) {
|
||||
ULONG ulIndex = new ULONG(index);
|
||||
HRESULT hr = jnaSymbols.SetCurrentScopeFrameIndex(ulIndex);
|
||||
COMUtils.checkRC(hr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugModule getModuleByModuleName(String name, int startIndex) {
|
||||
ULONG ulStartIndex = new ULONG(startIndex);
|
||||
|
@ -103,7 +103,11 @@ public class DebugSystemObjectsImpl1 implements DebugSystemObjectsInternal {
|
||||
@Override
|
||||
public int getNumberThreads() {
|
||||
ULONGByReference pulNumber = new ULONGByReference();
|
||||
COMUtils.checkRC(jnaSysobj.GetNumberThreads(pulNumber));
|
||||
HRESULT hr = jnaSysobj.GetNumberThreads(pulNumber);
|
||||
if (hr.equals(COMUtilsExtra.E_UNEXPECTED)) {
|
||||
return 0;
|
||||
}
|
||||
COMUtils.checkRC(hr);
|
||||
return pulNumber.getValue().intValue();
|
||||
}
|
||||
|
||||
|
@ -104,6 +104,10 @@ public interface IDebugSymbols3 extends IDebugSymbols2 {
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT GetCurrentScopeFrameIndex(ULONGByReference Index);
|
||||
|
||||
HRESULT SetCurrentScopeFrameIndex(ULONG Index);
|
||||
|
||||
HRESULT GetModuleByModuleNameWide(WString Name, ULONG StartIndex, ULONGByReference Index,
|
||||
ULONGLONGByReference Base);
|
||||
|
||||
|
@ -33,6 +33,16 @@ public class WrapIDebugSymbols3 extends WrapIDebugSymbols2 implements IDebugSymb
|
||||
super(pvInstance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HRESULT GetCurrentScopeFrameIndex(ULONGByReference Index) {
|
||||
return _invokeHR(VTIndices3.GET_CURRENT_SCOPE_FRAME_INDEX, getPointer(), Index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HRESULT SetCurrentScopeFrameIndex(ULONG Index) {
|
||||
return _invokeHR(VTIndices3.SET_SCOPE_FRAME_BY_INDEX, getPointer(), Index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HRESULT GetModuleByModuleNameWide(WString Name, ULONG StartIndex, ULONGByReference Index,
|
||||
ULONGLONGByReference Base) {
|
||||
|
@ -22,6 +22,7 @@ import agent.dbgeng.manager.impl.DbgManagerImpl;
|
||||
public class DbgSetActiveThreadCommand extends AbstractDbgCommand<Void> {
|
||||
|
||||
private DbgThread thread;
|
||||
private Integer frameId;
|
||||
|
||||
/**
|
||||
* Set the active thread
|
||||
@ -33,6 +34,7 @@ public class DbgSetActiveThreadCommand extends AbstractDbgCommand<Void> {
|
||||
public DbgSetActiveThreadCommand(DbgManagerImpl manager, DbgThread thread, Integer frameId) {
|
||||
super(manager);
|
||||
this.thread = thread;
|
||||
this.frameId = frameId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -40,6 +42,9 @@ public class DbgSetActiveThreadCommand extends AbstractDbgCommand<Void> {
|
||||
DebugThreadId id = thread.getId();
|
||||
if (id != null) {
|
||||
manager.getSystemObjects().setCurrentThreadId(id);
|
||||
if (frameId != null) {
|
||||
manager.getSymbols().setCurrentScopeFrameIndex(frameId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +110,8 @@ public class DbgDebugEventCallbacksAdapter extends DebugEventCallbacksAdapter {
|
||||
DebugStatus status = DebugStatus.fromArgument(argument);
|
||||
Msg.info(this, "***ExecutionStatus: " + status);
|
||||
if (status.equals(DebugStatus.NO_DEBUGGEE)) {
|
||||
event.setState(DbgState.SESSION_EXIT);
|
||||
long processCount = manager.getProcessCount();
|
||||
event.setState(processCount > 0 ? DbgState.SESSION_EXIT : DbgState.EXIT);
|
||||
}
|
||||
return checkInterrupt(manager.processEvent(event));
|
||||
}
|
||||
@ -121,9 +122,7 @@ public class DbgDebugEventCallbacksAdapter extends DebugEventCallbacksAdapter {
|
||||
}
|
||||
if (flags.contains(ChangeEngineState.CURRENT_THREAD)) {
|
||||
Msg.info(this, "***CurrentThread: " + argument);
|
||||
if (argument < 0) {
|
||||
return checkInterrupt(manager.processEvent(event));
|
||||
}
|
||||
return checkInterrupt(manager.processEvent(event));
|
||||
}
|
||||
if (flags.contains(ChangeEngineState.SYSTEMS)) {
|
||||
Msg.info(this, "***Systems: " + argument);
|
||||
@ -133,10 +132,18 @@ public class DbgDebugEventCallbacksAdapter extends DebugEventCallbacksAdapter {
|
||||
return checkInterrupt(DebugStatus.NO_CHANGE);
|
||||
}
|
||||
|
||||
//@Override
|
||||
//public DebugStatus changeDebuggeeState(BitmaskSet<ChangeDebuggeeState> flags, long argument) {
|
||||
// System.err.println("CHANGE_DEBUGGEE_STATE: " + flags + ":" + argument);
|
||||
// return DebugStatus.NO_CHANGE;
|
||||
//}
|
||||
/*
|
||||
@Override
|
||||
public DebugStatus changeDebuggeeState(BitmaskSet<ChangeDebuggeeState> flags, long argument) {
|
||||
System.err.println("CHANGE_DEBUGGEE_STATE: " + flags + ":" + argument);
|
||||
return DebugStatus.NO_CHANGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugStatus sessionStatus(SessionStatus status) {
|
||||
System.err.println("SESSION_STATUS: " + status);
|
||||
return DebugStatus.NO_CHANGE;
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
@ -41,9 +41,7 @@ import agent.dbgeng.manager.breakpoint.DbgBreakpointInfo;
|
||||
import agent.dbgeng.manager.breakpoint.DbgBreakpointType;
|
||||
import agent.dbgeng.manager.cmd.*;
|
||||
import agent.dbgeng.manager.evt.*;
|
||||
import agent.dbgeng.model.iface1.DbgModelTargetActiveScope;
|
||||
import agent.dbgeng.model.iface1.DbgModelTargetFocusScope;
|
||||
import agent.dbgeng.model.iface1.DbgModelTargetInterpreter;
|
||||
import agent.dbgeng.model.iface1.*;
|
||||
import ghidra.async.*;
|
||||
import ghidra.comm.util.BitmaskSet;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
@ -112,6 +110,7 @@ public class DbgManagerImpl implements DbgManager {
|
||||
private volatile boolean waiting = false;
|
||||
private boolean kernelMode = false;
|
||||
private CompletableFuture<String> continuation;
|
||||
private long processCount = 0;
|
||||
|
||||
/**
|
||||
* Instantiate a new manager
|
||||
@ -951,7 +950,16 @@ public class DbgManagerImpl implements DbgManager {
|
||||
processEvent(new DbgBreakpointModifiedEvent(bptId));
|
||||
}
|
||||
if (flags.contains(ChangeEngineState.CURRENT_THREAD)) {
|
||||
// handled above
|
||||
long id = evt.getArgument();
|
||||
for (DebugThreadId key : getThreads()) {
|
||||
if (key.id == id) {
|
||||
DbgThread thread = getThread(key);
|
||||
if (thread != null) {
|
||||
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flags.contains(ChangeEngineState.SYSTEMS)) {
|
||||
processEvent(new DbgSystemsEvent(argument));
|
||||
@ -991,6 +999,12 @@ public class DbgManagerImpl implements DbgManager {
|
||||
waiting = true;
|
||||
|
||||
Long info = evt.getInfo();
|
||||
if (info.intValue() >= 0) {
|
||||
processCount++;
|
||||
}
|
||||
else {
|
||||
processCount--;
|
||||
}
|
||||
DebugProcessId id = new DebugProcessId(info.intValue());
|
||||
|
||||
String key = Integer.toHexString(id.id);
|
||||
@ -1406,6 +1420,11 @@ public class DbgManagerImpl implements DbgManager {
|
||||
return (DbgSessionImpl) eventSession;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> setActiveFrame(DbgThread thread, int index) {
|
||||
currentThread = thread;
|
||||
return execute(new DbgSetActiveThreadCommand(this, thread, index));
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> setActiveThread(DbgThread thread) {
|
||||
currentThread = thread;
|
||||
return execute(new DbgSetActiveThreadCommand(this, thread, null));
|
||||
@ -1511,4 +1530,9 @@ public class DbgManagerImpl implements DbgManager {
|
||||
public void setContinuation(CompletableFuture<String> continuation) {
|
||||
this.continuation = continuation;
|
||||
}
|
||||
|
||||
public long getProcessCount() {
|
||||
return processCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public interface DbgModelTargetDetachable extends DbgModelTargetObject, TargetDe
|
||||
@Override
|
||||
public default CompletableFuture<Void> detach() {
|
||||
DbgProcess process = getManager().getCurrentProcess();
|
||||
return process.detach();
|
||||
return getModel().gateFuture(process.detach());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public interface DbgModelTargetKillable extends DbgModelTargetObject, TargetKill
|
||||
@Override
|
||||
public default CompletableFuture<Void> kill() {
|
||||
DbgProcess process = getManager().getCurrentProcess();
|
||||
return process.kill();
|
||||
return getModel().gateFuture(process.kill());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,8 +19,6 @@ import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.dbgeng.model.iface2.DbgModelTargetObject;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.async.TypeSpec;
|
||||
import ghidra.dbg.error.DebuggerUserException;
|
||||
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
||||
|
||||
@ -36,10 +34,8 @@ public interface DbgModelTargetLauncher extends DbgModelTargetObject, TargetCmdL
|
||||
|
||||
@Override
|
||||
public default CompletableFuture<Void> launch(List<String> args) {
|
||||
return AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
||||
getManager().launch(args).handle(seq::nextIgnore);
|
||||
}).finish().exceptionally((exc) -> {
|
||||
return getModel().gateFuture(getManager().launch(args)).exceptionally((exc) -> {
|
||||
throw new DebuggerUserException("Launch failed for " + args);
|
||||
});
|
||||
}).thenApply(__ -> null);
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ public interface DbgModelTargetResumable extends DbgModelTargetObject, TargetRes
|
||||
if (process == null) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
return process.cont();
|
||||
return getModel().gateFuture(process.cont());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -89,6 +89,7 @@ public interface DbgModelTargetBreakpointSpec extends //
|
||||
AddressSpace space = getModel().getAddressSpace("ram");
|
||||
return requestNativeAttributes().thenAccept(attrs -> {
|
||||
if (attrs != null) {
|
||||
map.putAll(attrs);
|
||||
TargetObject addr = (TargetObject) attrs.get("Address");
|
||||
TargetObject id = (TargetObject) attrs.get("Id");
|
||||
//TargetObject unique = (TargetObject) attrs.get("UniqueID");
|
||||
@ -108,7 +109,7 @@ public interface DbgModelTargetBreakpointSpec extends //
|
||||
map.put(SPEC_ATTRIBUTE_NAME, this);
|
||||
map.put(EXPRESSION_ATTRIBUTE_NAME, addstr);
|
||||
map.put(KINDS_ATTRIBUTE_NAME, getKinds());
|
||||
map.put(BPT_INDEX_ATTRIBUTE_NAME, Long.decode(idstr));
|
||||
//map.put(BPT_INDEX_ATTRIBUTE_NAME, Long.decode(idstr));
|
||||
map.put(ENABLED_ATTRIBUTE_NAME, enstr.equals("-1"));
|
||||
setEnabled(enstr.equals("-1"), "Refreshed");
|
||||
int size = getBreakpointInfo().getSize();
|
||||
|
@ -32,6 +32,7 @@ public interface DbgModelTargetModule extends DbgModelTargetObject, TargetModule
|
||||
AddressSpace space = getModel().getAddressSpace("ram");
|
||||
return requestNativeAttributes().thenAccept(attrs -> {
|
||||
if (attrs != null) {
|
||||
map.putAll(attrs);
|
||||
TargetObject baseOffset2 = (TargetObject) attrs.get("BaseAddress");
|
||||
TargetObject nameAttr = (TargetObject) attrs.get("Name");
|
||||
TargetObject size = (TargetObject) attrs.get("Size");
|
||||
|
@ -24,13 +24,12 @@ import agent.dbgeng.manager.impl.DbgManagerImpl;
|
||||
import agent.dbgeng.model.AbstractDbgModel;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.agent.InvalidatableTargetObjectIf;
|
||||
import ghidra.dbg.agent.SpiTargetObject;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.util.CollectionUtils.Delta;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
public interface DbgModelTargetObject extends SpiTargetObject, InvalidatableTargetObjectIf {
|
||||
public interface DbgModelTargetObject extends SpiTargetObject {
|
||||
|
||||
@Override
|
||||
public AbstractDbgModel getModel();
|
||||
|
@ -40,10 +40,14 @@ public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, Target
|
||||
readRegistersNamed(getCachedElements().keySet());
|
||||
}
|
||||
|
||||
// NB: Does anyone call this anymore?
|
||||
@Override
|
||||
public default CompletableFuture<? extends Map<String, byte[]>> readRegistersNamed(
|
||||
Collection<String> names) {
|
||||
return getModel().gateFuture(doReadRegistersNamed(names));
|
||||
}
|
||||
|
||||
public default CompletableFuture<? extends Map<String, byte[]>> doReadRegistersNamed(
|
||||
Collection<String> names) {
|
||||
DbgManagerImpl manager = getManager();
|
||||
if (manager.isWaiting()) {
|
||||
Msg.warn(this,
|
||||
@ -101,6 +105,10 @@ public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, Target
|
||||
|
||||
@Override
|
||||
public default CompletableFuture<Void> writeRegistersNamed(Map<String, byte[]> values) {
|
||||
return getModel().gateFuture(doWriteRegistersNamed(values));
|
||||
}
|
||||
|
||||
public default CompletableFuture<Void> doWriteRegistersNamed(Map<String, byte[]> values) {
|
||||
DbgThread thread = getParentThread().getThread();
|
||||
return AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
|
||||
requestNativeElements().handle(seq::nextIgnore);
|
||||
|
@ -51,7 +51,10 @@ public interface DbgModelTargetStackFrame extends //
|
||||
public default CompletableFuture<Void> setActive() {
|
||||
DbgManagerImpl manager = getManager();
|
||||
DbgThreadImpl thread = manager.getCurrentThread();
|
||||
return manager.setActiveThread(thread);
|
||||
String name = this.getName();
|
||||
String stripped = name.substring(1, name.length() - 1);
|
||||
int index = Integer.decode(stripped);
|
||||
return manager.setActiveFrame(thread, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -61,6 +64,7 @@ public interface DbgModelTargetStackFrame extends //
|
||||
if (attrs == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
map.putAll(attrs);
|
||||
DbgModelTargetObject attributes = (DbgModelTargetObject) attrs.get("Attributes");
|
||||
if (attributes == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
|
@ -26,6 +26,7 @@ public interface DbgModelTargetTTD extends DbgModelTargetObject {
|
||||
if (attrs == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
map.putAll(attrs);
|
||||
DbgModelTargetObject attributes = (DbgModelTargetObject) attrs.get("Position");
|
||||
if (attributes == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
|
@ -24,7 +24,7 @@ import agent.dbgeng.manager.cmd.DbgSetActiveThreadCommand;
|
||||
import agent.dbgeng.manager.impl.*;
|
||||
import agent.dbgeng.model.iface1.*;
|
||||
import agent.dbgeng.model.impl.DbgModelTargetStackImpl;
|
||||
import ghidra.dbg.target.TargetThread;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public interface DbgModelTargetThread extends //
|
||||
@ -55,7 +55,14 @@ public interface DbgModelTargetThread extends //
|
||||
}
|
||||
}
|
||||
|
||||
public void threadStateChangedSpecific(DbgState state, DbgReason reason);
|
||||
public default void threadStateChangedSpecific(DbgState state, DbgReason reason) {
|
||||
TargetRegisterContainer container =
|
||||
(TargetRegisterContainer) getCachedAttribute("Registers");
|
||||
TargetRegisterBank bank = (TargetRegisterBank) container.getCachedAttribute("User");
|
||||
if (state.equals(DbgState.STOPPED)) {
|
||||
bank.readRegistersNamed(getCachedElements().keySet());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public default CompletableFuture<Void> setActive() {
|
||||
|
@ -25,10 +25,10 @@ public interface DbgModelTargetThreadContainer extends //
|
||||
DbgModelTargetEventScope, //
|
||||
DbgEventsListenerAdapter {
|
||||
|
||||
public DbgModelTargetThread getTargetThread(DbgThread thread);
|
||||
|
||||
public void threadCreated(DbgThread thread);
|
||||
|
||||
public void threadExited(DebugThreadId threadId);
|
||||
|
||||
public DbgModelTargetThread getTargetThread(DbgThread thread);
|
||||
|
||||
}
|
||||
|
@ -74,6 +74,11 @@ public class DbgModelImpl extends AbstractDbgModel implements DebuggerObjectMode
|
||||
addModelRoot(root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBrief() {
|
||||
return "DBGENG@" + Integer.toHexString(System.identityHashCode(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSpace getAddressSpace(String name) {
|
||||
if (!SPACE_NAME.equals(name)) {
|
||||
|
@ -26,9 +26,14 @@ import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "MemoryRegion", elements = {
|
||||
@TargetElementType(type = Void.class) }, attributes = {
|
||||
@TargetAttributeType(name = TargetMemoryRegion.MEMORY_ATTRIBUTE_NAME, type = DbgModelTargetMemoryContainerImpl.class),
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "MemoryRegion",
|
||||
elements = {
|
||||
@TargetElementType(type = Void.class) },
|
||||
attributes = {
|
||||
@TargetAttributeType(
|
||||
name = TargetMemoryRegion.MEMORY_ATTRIBUTE_NAME,
|
||||
type = DbgModelTargetMemoryContainerImpl.class),
|
||||
@TargetAttributeType(name = "BaseAddress", type = Address.class),
|
||||
@TargetAttributeType(name = "EndAddress", type = Address.class),
|
||||
@TargetAttributeType(name = "RegionSize", type = String.class),
|
||||
|
@ -27,11 +27,26 @@ import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "Debugger", elements = {
|
||||
@TargetElementType(type = Void.class) }, attributes = {
|
||||
@TargetAttributeType(name = "Available", type = DbgModelTargetAvailableContainerImpl.class, required = true, fixed = true),
|
||||
@TargetAttributeType(name = "Connectors", type = DbgModelTargetConnectorContainerImpl.class, required = true, fixed = true),
|
||||
@TargetAttributeType(name = "Sessions", type = DbgModelTargetSessionContainerImpl.class, required = true, fixed = true),
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "Debugger",
|
||||
elements = {
|
||||
@TargetElementType(type = Void.class) },
|
||||
attributes = {
|
||||
@TargetAttributeType(
|
||||
name = "Available",
|
||||
type = DbgModelTargetAvailableContainerImpl.class,
|
||||
required = true,
|
||||
fixed = true),
|
||||
@TargetAttributeType(
|
||||
name = "Connectors",
|
||||
type = DbgModelTargetConnectorContainerImpl.class,
|
||||
required = true,
|
||||
fixed = true),
|
||||
@TargetAttributeType(
|
||||
name = "Sessions",
|
||||
type = DbgModelTargetSessionContainerImpl.class,
|
||||
required = true,
|
||||
fixed = true),
|
||||
@TargetAttributeType(type = Void.class) })
|
||||
public class DbgModelTargetRootImpl extends DbgModelDefaultTargetModelRoot
|
||||
implements DbgModelTargetRoot {
|
||||
@ -120,9 +135,11 @@ public class DbgModelTargetRootImpl extends DbgModelDefaultTargetModelRoot
|
||||
DbgReason reason) {
|
||||
DbgModelTargetThread targetThread =
|
||||
(DbgModelTargetThread) getModel().getModelObject(thread);
|
||||
changeAttributes(List.of(), List.of(), Map.of( //
|
||||
TargetEventScope.EVENT_OBJECT_ATTRIBUTE_NAME, targetThread //
|
||||
), reason.desc());
|
||||
if (targetThread != null) {
|
||||
changeAttributes(List.of(), List.of(), Map.of( //
|
||||
TargetEventScope.EVENT_OBJECT_ATTRIBUTE_NAME, targetThread //
|
||||
), reason.desc());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -18,10 +18,8 @@ package agent.dbgeng.model.impl;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.dbgeng.manager.*;
|
||||
import agent.dbgeng.manager.impl.DbgManagerImpl;
|
||||
import agent.dbgeng.model.iface1.DbgModelTargetFocusScope;
|
||||
import agent.dbgeng.model.iface2.*;
|
||||
import ghidra.dbg.target.TargetFocusScope;
|
||||
@ -30,19 +28,44 @@ import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
@TargetObjectSchemaInfo(name = "StackFrame", elements = {
|
||||
@TargetElementType(type = Void.class) }, attributes = {
|
||||
@TargetAttributeType(name = DbgModelTargetStackFrame.FUNC_ATTRIBUTE_NAME, type = String.class),
|
||||
@TargetAttributeType(name = DbgModelTargetStackFrame.FUNC_TABLE_ENTRY_ATTRIBUTE_NAME, type = String.class),
|
||||
@TargetAttributeType(name = DbgModelTargetStackFrame.INST_OFFSET_ATTRIBUTE_NAME, type = String.class),
|
||||
@TargetAttributeType(name = DbgModelTargetStackFrame.FRAME_OFFSET_ATTRIBUTE_NAME, type = String.class),
|
||||
@TargetAttributeType(name = DbgModelTargetStackFrame.RETURN_OFFSET_ATTRIBUTE_NAME, type = String.class),
|
||||
@TargetAttributeType(name = DbgModelTargetStackFrame.STACK_OFFSET_ATTRIBUTE_NAME, type = String.class),
|
||||
@TargetAttributeType(name = DbgModelTargetStackFrame.VIRTUAL_ATTRIBUTE_NAME, type = Boolean.class),
|
||||
@TargetAttributeType(name = DbgModelTargetStackFrame.PARAM0_ATTRIBUTE_NAME, type = String.class),
|
||||
@TargetAttributeType(name = DbgModelTargetStackFrame.PARAM1_ATTRIBUTE_NAME, type = String.class),
|
||||
@TargetAttributeType(name = DbgModelTargetStackFrame.PARAM2_ATTRIBUTE_NAME, type = String.class),
|
||||
@TargetAttributeType(name = DbgModelTargetStackFrame.PARAM3_ATTRIBUTE_NAME, type = String.class),
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "StackFrame",
|
||||
elements = {
|
||||
@TargetElementType(type = Void.class) },
|
||||
attributes = {
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetStackFrame.FUNC_ATTRIBUTE_NAME,
|
||||
type = String.class),
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetStackFrame.FUNC_TABLE_ENTRY_ATTRIBUTE_NAME,
|
||||
type = String.class),
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetStackFrame.INST_OFFSET_ATTRIBUTE_NAME,
|
||||
type = String.class),
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetStackFrame.FRAME_OFFSET_ATTRIBUTE_NAME,
|
||||
type = String.class),
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetStackFrame.RETURN_OFFSET_ATTRIBUTE_NAME,
|
||||
type = String.class),
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetStackFrame.STACK_OFFSET_ATTRIBUTE_NAME,
|
||||
type = String.class),
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetStackFrame.VIRTUAL_ATTRIBUTE_NAME,
|
||||
type = Boolean.class),
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetStackFrame.PARAM0_ATTRIBUTE_NAME,
|
||||
type = String.class),
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetStackFrame.PARAM1_ATTRIBUTE_NAME,
|
||||
type = String.class),
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetStackFrame.PARAM2_ATTRIBUTE_NAME,
|
||||
type = String.class),
|
||||
@TargetAttributeType(
|
||||
name = DbgModelTargetStackFrame.PARAM3_ATTRIBUTE_NAME,
|
||||
type = String.class),
|
||||
@TargetAttributeType(type = Void.class) })
|
||||
public class DbgModelTargetStackFrameImpl extends DbgModelTargetObjectImpl
|
||||
implements DbgModelTargetStackFrame {
|
||||
@ -137,12 +160,6 @@ public class DbgModelTargetStackFrameImpl extends DbgModelTargetObjectImpl
|
||||
), "Refreshed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setActive() {
|
||||
DbgManagerImpl manager = getManager();
|
||||
return manager.setActiveThread(thread.getThread());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetObject getThread() {
|
||||
return thread.getParent();
|
||||
|
@ -121,7 +121,7 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl
|
||||
STATE_ATTRIBUTE_NAME, targetState, //
|
||||
TargetEnvironment.ARCH_ATTRIBUTE_NAME, executionType //
|
||||
), reason.desc());
|
||||
setExecutionState(targetState, reason.desc());
|
||||
//setExecutionState(targetState, reason.desc());
|
||||
registers.threadStateChangedSpecific(state, reason);
|
||||
}
|
||||
|
||||
|
@ -15,21 +15,25 @@
|
||||
*/
|
||||
package agent.dbgeng.model;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.dbg.test.*;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
public abstract class AbstractModelForDbgengBreakpointsTest
|
||||
extends AbstractDebuggerModelBreakpointsTest implements ProvidesTargetViaLaunchSpecimen {
|
||||
|
||||
protected abstract PathPattern getBreakPattern();
|
||||
|
||||
private static final int BREAK_ID_POS = 1;
|
||||
|
||||
@Override
|
||||
public AbstractDebuggerModelTest getTest() {
|
||||
return this;
|
||||
@ -80,4 +84,88 @@ public abstract class AbstractModelForDbgengBreakpointsTest
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void placeBreakpointViaInterpreter(AddressRange range, TargetBreakpointKind kind,
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
Address min = range.getMinAddress();
|
||||
if (range.getLength() == 4) {
|
||||
switch (kind) {
|
||||
case READ:
|
||||
waitOn(interpreter.execute("ba r4 " + min));
|
||||
break;
|
||||
case WRITE:
|
||||
waitOn(interpreter.execute("ba w4 " + min));
|
||||
break;
|
||||
default:
|
||||
fail();
|
||||
}
|
||||
}
|
||||
else if (range.getLength() == 1) {
|
||||
switch (kind) {
|
||||
case SW_EXECUTE:
|
||||
waitOn(interpreter.execute("bp " + min));
|
||||
break;
|
||||
case HW_EXECUTE:
|
||||
waitOn(interpreter.execute("ba e1 " + min));
|
||||
break;
|
||||
default:
|
||||
fail();
|
||||
}
|
||||
}
|
||||
else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String bpId = getBreakPattern().matchIndices(t.getPath()).get(BREAK_ID_POS);
|
||||
waitOn(interpreter.execute("bd " + bpId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String bpId = getBreakPattern().matchIndices(t.getPath()).get(BREAK_ID_POS);
|
||||
waitOn(interpreter.execute("be " + bpId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteViaInterpreter(TargetDeletable d, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String bpId = getBreakPattern().matchIndices(d.getPath()).get(BREAK_ID_POS);
|
||||
waitOn(interpreter.execute("bc " + bpId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertLocCoversViaInterpreter(AddressRange range, TargetBreakpointKind kind,
|
||||
TargetBreakpointLocation loc, TargetInterpreter interpreter) throws Throwable {
|
||||
String bpId = getBreakPattern().matchIndices(loc.getPath()).get(BREAK_ID_POS);
|
||||
String line = waitOn(interpreter.executeCapture("bl " + bpId)).trim();
|
||||
assertFalse(line.contains("\n"));
|
||||
// NB. WinDbg numbers breakpoints in base 10, by default
|
||||
assertTrue(line.startsWith(bpId));
|
||||
// TODO: Do I care to parse the details? The ID is confirmed, and details via the object...
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertEnabledViaInterpreter(TargetTogglable t, boolean enabled,
|
||||
TargetInterpreter interpreter) throws Throwable {
|
||||
String bpId = getBreakPattern().matchIndices(t.getPath()).get(BREAK_ID_POS);
|
||||
String line = waitOn(interpreter.executeCapture("bl " + bpId)).trim();
|
||||
assertFalse(line.contains("\n"));
|
||||
assertTrue(line.startsWith(bpId));
|
||||
String e = line.split("\\s+")[1];
|
||||
assertEquals(enabled ? "e" : "d", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertDeletedViaInterpreter(TargetDeletable d, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String bpId = getBreakPattern().matchIndices(d.getPath()).get(BREAK_ID_POS);
|
||||
String line = waitOn(interpreter.executeCapture("bl " + bpId)).trim();
|
||||
assertEquals("", line);
|
||||
}
|
||||
}
|
||||
|
@ -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.model;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
|
||||
public abstract class AbstractModelForDbgengFrameActivationTest
|
||||
extends AbstractDebuggerModelActivationTest {
|
||||
|
||||
protected abstract PathPattern getStackPattern();
|
||||
|
||||
protected DebuggerTestSpecimen getSpecimen() {
|
||||
return WindowsSpecimen.STACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||
DebuggerTestSpecimen specimen = getSpecimen();
|
||||
TargetLauncher launcher = findLauncher(); // root launcher should generate new inferiors
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
|
||||
TargetProcess process = retry(() -> {
|
||||
TargetProcess p = m.findAny(TargetProcess.class, seedPath());
|
||||
assertNotNull(p);
|
||||
return p;
|
||||
}, List.of(AssertionError.class));
|
||||
|
||||
trapAt("expStack!break_here", process);
|
||||
|
||||
waitSettled(m.getModel(), 200);
|
||||
|
||||
return retry(() -> {
|
||||
Map<List<String>, TargetStackFrame> frames =
|
||||
m.findAll(TargetStackFrame.class, seedPath(), true);
|
||||
assertTrue(frames.size() >= 3);
|
||||
return Set.copyOf(frames.values());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
|
||||
// TODO: Should probably assert default focus/activation here
|
||||
|
||||
@Override
|
||||
@Ignore("dbgeng.dll has no event for frame activation")
|
||||
public void testActivateEachViaInterpreter() throws Throwable {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertActiveViaInterpreter(TargetObject expected, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String line = waitOn(interpreter.executeCapture(".frame")).trim();
|
||||
assertFalse(line.contains("\n"));
|
||||
int frameId = Integer.parseInt(line.split("\\s+")[0], 16);
|
||||
int expId = Integer.decode(getStackPattern().matchIndices(expected.getPath()).get(2));
|
||||
assertEquals(expId, frameId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void testActivateEachOnce() throws Throwable {
|
||||
m.build();
|
||||
|
||||
TargetActiveScope activeScope = findActiveScope();
|
||||
Set<TargetObject> activatable = getActivatableThings();
|
||||
for (TargetObject obj : activatable) {
|
||||
waitOn(activeScope.requestActivation(obj));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertActiveViaInterpreter(obj, interpreter);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateEachTwice() throws Throwable {
|
||||
m.build();
|
||||
|
||||
TargetActiveScope activeScope = findActiveScope();
|
||||
Set<TargetObject> activatable = getActivatableThings();
|
||||
for (TargetObject obj : activatable) {
|
||||
waitOn(activeScope.requestActivation(obj));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertActiveViaInterpreter(obj, interpreter);
|
||||
}
|
||||
waitOn(activeScope.requestActivation(obj));
|
||||
if (m.hasInterpreter()) {
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
assertActiveViaInterpreter(obj, interpreter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
/* ###
|
||||
* 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.model;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
||||
|
||||
public abstract class AbstractModelForDbgengFrameFocusTest
|
||||
extends AbstractDebuggerModelFocusTest {
|
||||
|
||||
protected DebuggerTestSpecimen getSpecimen() {
|
||||
return WindowsSpecimen.STACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
||||
DebuggerTestSpecimen specimen = getSpecimen();
|
||||
TargetLauncher launcher = findLauncher(); // root launcher should generate new inferiors
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
|
||||
TargetProcess process = retry(() -> {
|
||||
TargetProcess p = m.findAny(TargetProcess.class, seedPath());
|
||||
assertNotNull(p);
|
||||
return p;
|
||||
}, List.of(AssertionError.class));
|
||||
|
||||
trapAt("expStack!break_here", process);
|
||||
|
||||
return retry(() -> {
|
||||
Map<List<String>, TargetStackFrame> frames =
|
||||
m.findAll(TargetStackFrame.class, seedPath(), true);
|
||||
assertTrue(frames.size() >= 3);
|
||||
return Set.copyOf(frames.values());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
}
|
@ -68,4 +68,14 @@ public abstract class AbstractModelForDbgengInterpreterTest
|
||||
public DebuggerTestSpecimen getLaunchSpecimen() {
|
||||
return WindowsSpecimen.PRINT;
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
@Ignore
|
||||
@Test(expected = DebuggerModelTerminatingException.class)
|
||||
public void testExecuteQuit() throws Throwable {
|
||||
// Different behavior for dbg clients vice gdb
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,83 @@
|
||||
/* ###
|
||||
* 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.model;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
|
||||
public abstract class AbstractModelForDbgengProcessActivationTest
|
||||
extends AbstractDebuggerModelActivationTest {
|
||||
|
||||
protected abstract PathPattern getProcessPattern();
|
||||
|
||||
protected int getCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
protected DebuggerTestSpecimen getSpecimen() {
|
||||
return WindowsSpecimen.PRINT;
|
||||
}
|
||||
|
||||
public abstract List<String> getExpectedSessionPath();
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||
DebuggerTestSpecimen specimen = getSpecimen();
|
||||
TargetLauncher launcher = findLauncher();
|
||||
int count = getCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
}
|
||||
|
||||
waitSettled(m.getModel(), 200);
|
||||
|
||||
return retry(() -> {
|
||||
Map<List<String>, TargetProcess> found =
|
||||
m.findAll(TargetProcess.class, getExpectedSessionPath(), true);
|
||||
assertEquals(count, found.size());
|
||||
return Set.copyOf(found.values());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String id = Unique.assertOne(getProcessPattern().matchIndices(obj.getPath()));
|
||||
waitOn(interpreter.execute("|" + id + " s"));
|
||||
}
|
||||
|
||||
public abstract String getIdFromCapture(String line);
|
||||
|
||||
@Override
|
||||
protected void assertActiveViaInterpreter(TargetObject expected, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String output = waitOn(interpreter.executeCapture("|"));
|
||||
String line = Unique.assertOne(Stream.of(output.split("\n"))
|
||||
.filter(l -> l.trim().startsWith("."))
|
||||
.collect(Collectors.toList())).trim();
|
||||
String procId = getIdFromCapture(line);
|
||||
assertEquals(expected.getPath(),
|
||||
getProcessPattern().applyIndices(procId).getSingletonPath());
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/* ###
|
||||
* 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.model;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public abstract class AbstractModelForDbgengProcessFocusTest
|
||||
extends AbstractDebuggerModelFocusTest {
|
||||
|
||||
protected int getCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
protected DebuggerTestSpecimen getSpecimen() {
|
||||
return WindowsSpecimen.PRINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
||||
DebuggerTestSpecimen specimen = getSpecimen();
|
||||
TargetLauncher launcher = findLauncher();
|
||||
int count = getCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
}
|
||||
return retry(() -> {
|
||||
Map<List<String>, TargetProcess> found =
|
||||
m.findAll(TargetProcess.class, PathUtils.parse("Sessions[0]"), true);
|
||||
assertEquals(count, found.size());
|
||||
return Set.copyOf(found.values());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
}
|
@ -15,13 +15,15 @@
|
||||
*/
|
||||
package agent.dbgeng.model;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.dbg.target.TargetEnvironment;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.DebugModelConventions.AsyncState;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelLauncherTest;
|
||||
@ -54,4 +56,17 @@ public abstract class AbstractModelForDbgengRootLauncherTest
|
||||
assertEquals("little", environment.getEndian());
|
||||
assertTrue(environment.getDebugger().toLowerCase().contains("dbgeng"));
|
||||
}
|
||||
|
||||
protected void runTestResumeTerminates(DebuggerTestSpecimen specimen) throws Throwable {
|
||||
TargetProcess process = retryForProcessRunning(specimen, this);
|
||||
TargetResumable resumable = m.suitable(TargetResumable.class, process.getPath());
|
||||
AsyncState state =
|
||||
new AsyncState(m.suitable(TargetExecutionStateful.class, process.getPath()));
|
||||
TargetExecutionState st = waitOn(state.waitUntil(s -> s == TargetExecutionState.STOPPED));
|
||||
assertTrue(st.isAlive());
|
||||
waitOn(resumable.resume());
|
||||
retryVoid(() -> assertFalse(DebugModelConventions.isProcessAlive(process)),
|
||||
List.of(AssertionError.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
package agent.dbgeng.model;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@ -46,6 +46,7 @@ public abstract class AbstractModelForDbgengScenarioX64RegistersTest
|
||||
protected void verifyExpectedEffect(TargetProcess process) throws Throwable {
|
||||
long status = process.getTypedAttributeNowByName(
|
||||
DbgModelTargetProcessImpl.EXIT_CODE_ATTRIBUTE_NAME, Long.class, 0L);
|
||||
assertEquals(0x41, status);
|
||||
// TODO: This really shouldn't return 0 - possible race?
|
||||
assertTrue(status == 0x41 || status == 0);
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +20,13 @@ import static ghidra.lifecycle.Unfinished.TODO;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||
|
||||
public abstract class AbstractModelForDbgengSessionFocusTest
|
||||
extends AbstractDebuggerModelFocusTest {
|
||||
public abstract class AbstractModelForDbgengSessionActivationTest
|
||||
extends AbstractDebuggerModelActivationTest {
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
||||
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||
TODO("Don't know how to make multiple sessions");
|
||||
return null;
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/* ###
|
||||
* 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.model;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
|
||||
public abstract class AbstractModelForDbgengThreadActivationTest
|
||||
extends AbstractDebuggerModelActivationTest {
|
||||
|
||||
protected abstract PathPattern getThreadPattern();
|
||||
|
||||
protected DebuggerTestSpecimen getSpecimen() {
|
||||
return WindowsSpecimen.PRINT;
|
||||
}
|
||||
|
||||
protected int getCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected abstract List<String> getExpectedSessionPath();
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getActivatableThings() throws Throwable {
|
||||
DebuggerTestSpecimen specimen = getSpecimen();
|
||||
TargetLauncher launcher = findLauncher();
|
||||
int count = getCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
}
|
||||
|
||||
waitSettled(m.getModel(), 200);
|
||||
|
||||
return retry(() -> {
|
||||
Map<List<String>, TargetThread> found =
|
||||
m.findAll(TargetThread.class, getExpectedSessionPath(), true);
|
||||
assertEquals(count, found.size());
|
||||
return Set.copyOf(found.values());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String threadId = getThreadPattern().matchIndices(obj.getPath()).get(1);
|
||||
// TODO: This test is imperfect, since processes are activated as well
|
||||
waitOn(interpreter.execute("~" + threadId + " s"));
|
||||
}
|
||||
|
||||
public abstract String getIdFromCapture(String line);
|
||||
|
||||
@Override
|
||||
protected void assertActiveViaInterpreter(TargetObject expected, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String output = waitOn(interpreter.executeCapture("~"));
|
||||
String line = Unique.assertOne(Stream.of(output.split("\n"))
|
||||
.filter(l -> l.trim().startsWith("."))
|
||||
.collect(Collectors.toList())).trim();
|
||||
String threadId = getIdFromCapture(line);
|
||||
String expId = getThreadPattern().matchIndices(expected.getPath()).get(1);
|
||||
assertEquals(expId, threadId);
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/* ###
|
||||
* 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.model;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelFocusTest;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public abstract class AbstractModelForDbgengThreadFocusTest
|
||||
extends AbstractDebuggerModelFocusTest {
|
||||
|
||||
protected int getCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
protected DebuggerTestSpecimen getSpecimen() {
|
||||
return WindowsSpecimen.PRINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<TargetObject> getFocusableThings() throws Throwable {
|
||||
DebuggerTestSpecimen specimen = getSpecimen();
|
||||
TargetLauncher launcher = findLauncher();
|
||||
int count = getCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
waitOn(launcher.launch(specimen.getLauncherArgs()));
|
||||
}
|
||||
return retry(() -> {
|
||||
Map<List<String>, TargetThread> found =
|
||||
m.findAll(TargetThread.class, PathUtils.parse("Sessions[0]"), true);
|
||||
assertEquals(count, found.size());
|
||||
return Set.copyOf(found.values());
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
}
|
@ -16,10 +16,19 @@
|
||||
package agent.dbgeng.model.gadp;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengBreakpointsTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class GadpModelForDbgengBreakpointsTest extends AbstractModelForDbgengBreakpointsTest {
|
||||
|
||||
@Override
|
||||
protected PathPattern getBreakPattern() {
|
||||
return new PathPattern(PathUtils.parse("Sessions[0].Processes[].Debug.Breakpoints[]"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new GadpDbgengModelHost();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,9 +15,17 @@
|
||||
*/
|
||||
package agent.dbgeng.model.gadp;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengThreadFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengFrameActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class GadpModelForDbgengFrameActivationTest
|
||||
extends AbstractModelForDbgengFrameActivationTest {
|
||||
|
||||
protected PathPattern getStackPattern() {
|
||||
return new PathPattern(PathUtils.parse("Sessions[0].Processes[].Threads[].Stack[]"));
|
||||
}
|
||||
|
||||
public class GadpModelForDbgengThreadFocusTest extends AbstractModelForDbgengThreadFocusTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new GadpDbgengModelHost();
|
@ -19,7 +19,6 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengInterpreterTest;
|
||||
import ghidra.dbg.error.DebuggerModelTerminatingException;
|
||||
|
||||
public class GadpModelForDbgengInterpreterTest extends AbstractModelForDbgengInterpreterTest {
|
||||
|
||||
@ -37,11 +36,4 @@ public class GadpModelForDbgengInterpreterTest extends AbstractModelForDbgengInt
|
||||
super.testAttachViaInterpreterShowsInProcessContainer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Ignore
|
||||
@Test(expected = DebuggerModelTerminatingException.class)
|
||||
public void testExecuteQuit() throws Throwable {
|
||||
// Hangs after DebuggerModelTerminatingException
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,11 +15,31 @@
|
||||
*/
|
||||
package agent.dbgeng.model.gadp;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengFrameFocusTest;
|
||||
import java.util.List;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengProcessActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class GadpModelForDbgengProcessActivationTest
|
||||
extends AbstractModelForDbgengProcessActivationTest {
|
||||
|
||||
protected PathPattern getProcessPattern() {
|
||||
return new PathPattern(PathUtils.parse("Sessions[0].Processes[]"));
|
||||
}
|
||||
|
||||
public class GadpModelForDbgengFrameFocusTest extends AbstractModelForDbgengFrameFocusTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new GadpDbgengModelHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExpectedSessionPath() {
|
||||
return PathUtils.parse("Sessions[0]");
|
||||
}
|
||||
|
||||
public String getIdFromCapture(String line) {
|
||||
return line.split("\\s+")[1];
|
||||
}
|
||||
|
||||
}
|
@ -17,10 +17,10 @@ package agent.dbgeng.model.gadp;
|
||||
|
||||
import org.junit.Ignore;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengSessionFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengSessionActivationTest;
|
||||
|
||||
@Ignore("Don't know how to make multiple sessions")
|
||||
public class GadpModelForDbgengSessionFocusTest extends AbstractModelForDbgengSessionFocusTest {
|
||||
public class GadpModelForDbgengSessionActivationTest extends AbstractModelForDbgengSessionActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new GadpDbgengModelHost();
|
@ -15,11 +15,30 @@
|
||||
*/
|
||||
package agent.dbgeng.model.gadp;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengProcessFocusTest;
|
||||
import java.util.List;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengThreadActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class GadpModelForDbgengThreadActivationTest
|
||||
extends AbstractModelForDbgengThreadActivationTest {
|
||||
|
||||
protected PathPattern getThreadPattern() {
|
||||
return new PathPattern(PathUtils.parse("Sessions[0].Processes[].Threads[]"));
|
||||
}
|
||||
|
||||
public class GadpModelForDbgengProcessFocusTest extends AbstractModelForDbgengProcessFocusTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new GadpDbgengModelHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExpectedSessionPath() {
|
||||
return PathUtils.parse("Sessions[0]");
|
||||
}
|
||||
|
||||
public String getIdFromCapture(String line) {
|
||||
return line.split("\\s+")[1];
|
||||
}
|
||||
}
|
@ -16,8 +16,16 @@
|
||||
package agent.dbgeng.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengBreakpointsTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class InVmModelForDbgengBreakpointsTest extends AbstractModelForDbgengBreakpointsTest {
|
||||
|
||||
@Override
|
||||
protected PathPattern getBreakPattern() {
|
||||
return new PathPattern(PathUtils.parse("Sessions[0].Processes[].Debug.Breakpoints[]"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgengModelHost();
|
||||
|
@ -15,9 +15,17 @@
|
||||
*/
|
||||
package agent.dbgeng.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengFrameFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengFrameActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class InVmModelForDbgengFrameActivationTest
|
||||
extends AbstractModelForDbgengFrameActivationTest {
|
||||
|
||||
protected PathPattern getStackPattern() {
|
||||
return new PathPattern(PathUtils.parse("Sessions[0].Processes[].Threads[].Stack[]"));
|
||||
}
|
||||
|
||||
public class InVmModelForDbgengFrameFocusTest extends AbstractModelForDbgengFrameFocusTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgengModelHost();
|
@ -19,6 +19,7 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengInterpreterTest;
|
||||
import ghidra.dbg.error.DebuggerModelTerminatingException;
|
||||
|
||||
public class InVmModelForDbgengInterpreterTest extends AbstractModelForDbgengInterpreterTest {
|
||||
@Override
|
||||
@ -32,4 +33,12 @@ public class InVmModelForDbgengInterpreterTest extends AbstractModelForDbgengInt
|
||||
public void testAttachViaInterpreterShowsInProcessContainer() throws Throwable {
|
||||
super.testAttachViaInterpreterShowsInProcessContainer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Ignore
|
||||
@Test(expected = DebuggerModelTerminatingException.class)
|
||||
public void testExecuteQuit() throws Throwable {
|
||||
// Different behavior for dbg clients vice gdb
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,11 +15,31 @@
|
||||
*/
|
||||
package agent.dbgeng.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengProcessFocusTest;
|
||||
import java.util.List;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengProcessActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class InVmModelForDbgengProcessActivationTest
|
||||
extends AbstractModelForDbgengProcessActivationTest {
|
||||
|
||||
protected PathPattern getProcessPattern() {
|
||||
return new PathPattern(PathUtils.parse("Sessions[0].Processes[]"));
|
||||
}
|
||||
|
||||
public class InVmModelForDbgengProcessFocusTest extends AbstractModelForDbgengProcessFocusTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgengModelHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExpectedSessionPath() {
|
||||
return PathUtils.parse("Sessions[0]");
|
||||
}
|
||||
|
||||
public String getIdFromCapture(String line) {
|
||||
return line.split("\\s+")[1];
|
||||
}
|
||||
|
||||
}
|
@ -17,10 +17,10 @@ package agent.dbgeng.model.invm;
|
||||
|
||||
import org.junit.Ignore;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengSessionFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengSessionActivationTest;
|
||||
|
||||
@Ignore("Don't know how to make multiple sessions")
|
||||
public class InVmModelForDbgengSessionFocusTest extends AbstractModelForDbgengSessionFocusTest {
|
||||
public class InVmModelForDbgengSessionActivationTest extends AbstractModelForDbgengSessionActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgengModelHost();
|
@ -0,0 +1,45 @@
|
||||
/* ###
|
||||
* 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.model.invm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengThreadActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class InVmModelForDbgengThreadActivationTest
|
||||
extends AbstractModelForDbgengThreadActivationTest {
|
||||
|
||||
protected PathPattern getThreadPattern() {
|
||||
return new PathPattern(PathUtils.parse("Sessions[0].Processes[].Threads[]"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgengModelHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExpectedSessionPath() {
|
||||
return PathUtils.parse("Sessions[0]");
|
||||
}
|
||||
|
||||
public String getIdFromCapture(String line) {
|
||||
return line.split("\\s+")[1];
|
||||
}
|
||||
|
||||
}
|
@ -18,6 +18,7 @@ package agent.dbgeng.model.invm;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengX64RegistersTest;
|
||||
|
||||
public class InVmModelForDbgengX64RegistersTest extends AbstractModelForDbgengX64RegistersTest {
|
||||
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgengModelHost();
|
||||
|
@ -18,21 +18,19 @@ package agent.dbgmodel;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.dbgmodel.model.impl.DbgModel2Impl;
|
||||
import ghidra.dbg.DebuggerModelFactory;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.LocalDebuggerModelFactory;
|
||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||
import ghidra.util.classfinder.ExtensionPointProperties;
|
||||
|
||||
/**
|
||||
* Note this is in the testing source because it's not meant to be shipped in the release.... That
|
||||
* may change if it proves stable, though, no?
|
||||
*/
|
||||
@FactoryDescription( //
|
||||
brief = "IN-VM MS dbgmodel local debugger", //
|
||||
htmlDetails = "Launch a dbgmodel session in this same JVM" //
|
||||
brief = "IN-VM MS dbgmodel local debugger", //
|
||||
htmlDetails = "Launch a dbgmodel session in this same JVM" //
|
||||
)
|
||||
@ExtensionPointProperties(priority = 70)
|
||||
public class DbgModelInJvmDebuggerModelFactory implements LocalDebuggerModelFactory {
|
||||
public class DbgModelInJvmDebuggerModelFactory implements DebuggerModelFactory {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<? extends DebuggerObjectModel> build() {
|
||||
|
@ -971,4 +971,14 @@ public class WrappedDbgModel
|
||||
return DebugValueType.INVALID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentScopeFrameIndex() {
|
||||
return client.getSymbols().getCurrentScopeFrameIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentScopeFrameIndex(int index) {
|
||||
client.getSymbols().setCurrentScopeFrameIndex(index);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ public class KeyEnumeratorImpl implements KeyEnumeratorInternal {
|
||||
PointerByReference ppValue = new PointerByReference();
|
||||
PointerByReference ppMetaData = new PointerByReference();
|
||||
HRESULT hr = jnaData.GetNext(bref, ppValue, ppMetaData);
|
||||
if (hr.equals(COMUtilsExtra.E_BOUNDS)) {
|
||||
if (hr.equals(COMUtilsExtra.E_BOUNDS) || hr.equals(COMUtilsExtra.E_FAIL)) {
|
||||
//System.err.println("ret null");
|
||||
return null;
|
||||
}
|
||||
|
@ -906,6 +906,10 @@ public class ModelObjectImpl implements ModelObjectInternal {
|
||||
String valueString = map.get("BaseAddress").getValueString();
|
||||
return valueString;
|
||||
}
|
||||
if (map.containsKey("UniqueID") && map.containsKey("Id")) {
|
||||
String valueString = map.get("Id").getValueString();
|
||||
return valueString;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
|
@ -18,18 +18,22 @@ package agent.dbgmodel.model.impl;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.jdom.JDOMException;
|
||||
|
||||
import agent.dbgeng.manager.impl.DbgManagerImpl;
|
||||
import agent.dbgeng.manager.impl.*;
|
||||
import agent.dbgeng.model.AbstractDbgModel;
|
||||
import agent.dbgeng.model.iface2.DbgModelTargetObject;
|
||||
import agent.dbgeng.model.iface2.DbgModelTargetSession;
|
||||
import agent.dbgmodel.manager.DbgManager2Impl;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebuggerModelClosedReason;
|
||||
import ghidra.dbg.agent.AbstractTargetObject;
|
||||
import ghidra.dbg.agent.AbstractTargetObject.ProxyFactory;
|
||||
import ghidra.dbg.agent.SpiTargetObject;
|
||||
import ghidra.dbg.error.DebuggerModelTerminatingException;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||
@ -85,6 +89,11 @@ public class DbgModel2Impl extends AbstractDbgModel
|
||||
(DbgModelTargetObject) delegate, mixins, DelegateDbgModel2TargetObject.LOOKUP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBrief() {
|
||||
return "DBGMODEL@" + Integer.toHexString(System.identityHashCode(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSpace getAddressSpace(String name) {
|
||||
if (!SPACE_NAME.equals(name)) {
|
||||
@ -137,6 +146,10 @@ public class DbgModel2Impl extends AbstractDbgModel
|
||||
terminate();
|
||||
return super.close();
|
||||
}
|
||||
catch (RejectedExecutionException e) {
|
||||
reportError(this, "Model is already closing", e);
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
return CompletableFuture.failedFuture(t);
|
||||
}
|
||||
@ -154,6 +167,14 @@ public class DbgModel2Impl extends AbstractDbgModel
|
||||
return;
|
||||
}
|
||||
objectMap.put(object, modelObject);
|
||||
if (object instanceof DbgProcessImpl) {
|
||||
DbgProcessImpl impl = (DbgProcessImpl) object;
|
||||
objectMap.put(impl.getId(), modelObject);
|
||||
}
|
||||
if (object instanceof DbgThreadImpl) {
|
||||
DbgThreadImpl impl = (DbgThreadImpl) object;
|
||||
objectMap.put(impl.getId(), modelObject);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -161,4 +182,15 @@ public class DbgModel2Impl extends AbstractDbgModel
|
||||
return objectMap.get(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> CompletableFuture<T> gateFuture(CompletableFuture<T> future) {
|
||||
return super.gateFuture(future).exceptionally(ex -> {
|
||||
for (Throwable cause = ex; cause != null; cause = cause.getCause()) {
|
||||
if (cause instanceof RejectedExecutionException) {
|
||||
throw new DebuggerModelTerminatingException("dbgeng is terminating", ex);
|
||||
}
|
||||
}
|
||||
return ExceptionUtils.rethrow(ex);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ public class DbgModel2TargetAvailableImpl extends DbgModel2TargetObjectImpl
|
||||
this.name = name;
|
||||
|
||||
this.changeAttributes(List.of(), List.of(), Map.of(//
|
||||
PID_ATTRIBUTE_NAME, pid, //
|
||||
PID_ATTRIBUTE_NAME, (long) pid, //
|
||||
DISPLAY_ATTRIBUTE_NAME, keyAttachable(pid) + " : " + name.trim() //
|
||||
), "Initialized");
|
||||
}
|
||||
@ -56,7 +56,7 @@ public class DbgModel2TargetAvailableImpl extends DbgModel2TargetObjectImpl
|
||||
this.pid = pid;
|
||||
|
||||
this.changeAttributes(List.of(), List.of(), Map.of(//
|
||||
PID_ATTRIBUTE_NAME, pid, //
|
||||
PID_ATTRIBUTE_NAME, (long) pid, //
|
||||
DISPLAY_ATTRIBUTE_NAME, keyAttachable(pid) //
|
||||
), "Initialized");
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package agent.dbgmodel.model.impl;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -34,8 +35,8 @@ import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebuggerModelListener;
|
||||
import ghidra.dbg.agent.DefaultTargetObject;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
@ -112,9 +113,11 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
|
||||
@Override
|
||||
public CompletableFuture<Void> requestElements(boolean refresh) {
|
||||
List<TargetObject> nlist = new ArrayList<>();
|
||||
List<String> rlist = new ArrayList<>();
|
||||
return requestNativeElements().thenCompose(list -> {
|
||||
synchronized (elements) {
|
||||
for (TargetObject element : elements.values()) {
|
||||
for (Entry<String, TargetObject> entry : elements.entrySet()) {
|
||||
TargetObject element = entry.getValue();
|
||||
if (!list.contains(element)) {
|
||||
if (element instanceof DbgStateListener) {
|
||||
getManager().removeStateListener((DbgStateListener) element);
|
||||
@ -122,6 +125,7 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
|
||||
if (element instanceof DbgEventsListener) {
|
||||
getManager().removeEventsListener((DbgEventsListener) element);
|
||||
}
|
||||
rlist.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
nlist.addAll(list);
|
||||
@ -129,18 +133,20 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
|
||||
//return processModelObjectElements(nlist);
|
||||
}
|
||||
}).thenAccept(__ -> {
|
||||
changeElements(List.of(), nlist, Map.of(), "Refreshed");
|
||||
changeElements(rlist, nlist, Map.of(), "Refreshed");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> requestAttributes(boolean refresh) {
|
||||
Map<String, Object> nmap = new HashMap<>();
|
||||
List<String> rlist = new ArrayList<>();
|
||||
return requestNativeAttributes().thenCompose(map -> {
|
||||
synchronized (attributes) {
|
||||
if (map != null) {
|
||||
Collection<?> values = map.values();
|
||||
for (Object attribute : attributes.values()) {
|
||||
for (Entry<String, Object> entry : attributes.entrySet()) {
|
||||
Object attribute = entry.getValue();
|
||||
if (!values.contains(attribute)) {
|
||||
if (attribute instanceof DbgStateListener) {
|
||||
getManager().removeStateListener((DbgStateListener) attribute);
|
||||
@ -148,6 +154,7 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
|
||||
if (attribute instanceof DbgEventsListener) {
|
||||
getManager().removeEventsListener((DbgEventsListener) attribute);
|
||||
}
|
||||
rlist.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
nmap.putAll(map);
|
||||
@ -247,6 +254,7 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
|
||||
attrs.put(TargetEnvironment.ARCH_ATTRIBUTE_NAME, "x86_64");
|
||||
attrs.put(TargetEnvironment.DEBUGGER_ATTRIBUTE_NAME, "dbgeng");
|
||||
attrs.put(TargetEnvironment.OS_ATTRIBUTE_NAME, "Windows");
|
||||
attrs.put(TargetEnvironment.ENDIAN_ATTRIBUTE_NAME, "little");
|
||||
}
|
||||
if (proxy instanceof TargetModule) {
|
||||
//attrs.put(TargetObject.ORDER_ATTRIBUTE_NAME,
|
||||
@ -293,6 +301,11 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> fetchChild(final String key) {
|
||||
/* Would like to do this, but has some very bad effects
|
||||
return getModel().gateFuture(doFetchChild(key));
|
||||
}
|
||||
public CompletableFuture<?> doFetchChild(final String key) {
|
||||
*/
|
||||
synchronized (elements) {
|
||||
if (key.startsWith("[") && key.endsWith("]")) {
|
||||
String trimKey = key.substring(1, key.length() - 1);
|
||||
@ -350,7 +363,7 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
|
||||
|
||||
@Override
|
||||
public void removeListener(DebuggerModelListener l) {
|
||||
listeners.clear();
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -19,6 +19,7 @@ import agent.dbgeng.model.iface2.DbgModelTargetObject;
|
||||
|
||||
public interface DbgModel2TargetProxy extends DbgModelTargetObject {
|
||||
|
||||
@Override
|
||||
public DelegateDbgModel2TargetObject getDelegate();
|
||||
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import agent.dbgeng.model.iface1.DbgModelSelectableObject;
|
||||
import agent.dbgeng.model.iface1.DbgModelTargetExecutionStateful;
|
||||
import agent.dbgeng.model.iface2.*;
|
||||
import agent.dbgeng.model.impl.DbgModelTargetConnectorContainerImpl;
|
||||
import agent.dbgeng.model.impl.DbgModelTargetProcessImpl;
|
||||
import agent.dbgmodel.dbgmodel.main.ModelObject;
|
||||
import agent.dbgmodel.manager.DbgManager2Impl;
|
||||
import ghidra.async.AsyncUtils;
|
||||
@ -146,6 +147,11 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
|
||||
public void objectSelected(Object object) {
|
||||
List<String> objPath = findObject(object);
|
||||
TargetObject obj = getModel().getModelObject(objPath);
|
||||
if (obj instanceof DbgModelSelectableObject) {
|
||||
setFocus((DbgModelSelectableObject) obj);
|
||||
}
|
||||
/*
|
||||
getModel().fetchModelValue(objPath, true).thenAccept(obj -> {
|
||||
if (obj instanceof DbgModelSelectableObject) {
|
||||
setFocus((DbgModelSelectableObject) obj);
|
||||
@ -154,6 +160,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
Msg.error("Could not set focus on selected object: " + PathUtils.toString(objPath), ex);
|
||||
return null;
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -262,6 +269,8 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
}
|
||||
DbgModel2TargetProxy proxy = (DbgModel2TargetProxy) pobj;
|
||||
DelegateDbgModel2TargetObject delegate = proxy.getDelegate();
|
||||
Map<String, ? extends TargetObject> existingElements =
|
||||
delegate.getCachedElements();
|
||||
|
||||
xpath.add(0, "Debugger");
|
||||
DbgManager2Impl manager = (DbgManager2Impl) getManager();
|
||||
@ -270,9 +279,19 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
String searchKey = obj.getSearchKey();
|
||||
if (searchKey.equals(info.toString())) {
|
||||
String elKey = PathUtils.makeKey(searchKey);
|
||||
DbgModel2TargetProxy proxyElement =
|
||||
(DbgModel2TargetProxy) DelegateDbgModel2TargetObject
|
||||
.makeProxy(delegate.getModel(), delegate, elKey, obj);
|
||||
DbgModel2TargetProxy proxyElement;
|
||||
if (existingElements.containsKey(searchKey)) {
|
||||
proxyElement = (DbgModel2TargetProxy) existingElements.get(searchKey);
|
||||
DelegateDbgModel2TargetObject elementDelegate = proxyElement.getDelegate();
|
||||
elementDelegate.setModelObject(obj);
|
||||
}
|
||||
else {
|
||||
proxyElement = (DbgModel2TargetProxy) DelegateDbgModel2TargetObject
|
||||
.makeProxy((DbgModel2Impl) proxy.getModel(), proxy, elKey, obj);
|
||||
}
|
||||
//DbgModel2TargetProxy proxyElement =
|
||||
// (DbgModel2TargetProxy) DelegateDbgModel2TargetObject
|
||||
// .makeProxy(delegate.getModel(), delegate, elKey, obj);
|
||||
delegate.changeElements(List.of(), List.of(proxyElement), "Created");
|
||||
seq.exit(proxyElement);
|
||||
}
|
||||
@ -280,6 +299,27 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
}).finish();
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> getObjectAndRemove(Object object,
|
||||
List<String> ext, Object info) {
|
||||
List<String> objPath = findObject(object);
|
||||
if (objPath == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
List<String> xpath = new ArrayList<>();
|
||||
xpath.addAll(objPath);
|
||||
xpath.addAll(ext);
|
||||
return AsyncUtils.sequence(TypeSpec.cls(Void.class)).then(seq -> {
|
||||
getModel().fetchModelObject(xpath).handle(seq::next);
|
||||
}, TypeSpec.cls(TargetObject.class)).then((pobj, seq) -> {
|
||||
if (pobj == null) {
|
||||
return;
|
||||
}
|
||||
DbgModel2TargetProxy proxy = (DbgModel2TargetProxy) pobj;
|
||||
DelegateDbgModel2TargetObject delegate = proxy.getDelegate();
|
||||
delegate.changeElements(List.of(info.toString()), List.of(), "Deleted");
|
||||
}).finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionRemoved(DebugSessionId sessionId, DbgCause cause) {
|
||||
getObject(sessionId);
|
||||
@ -287,23 +327,44 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
|
||||
@Override
|
||||
public void processRemoved(DebugProcessId processId, DbgCause cause) {
|
||||
DbgModelTargetProcess process = (DbgModelTargetProcess) getObject(processId);
|
||||
if (process == null) {
|
||||
return;
|
||||
getObject(processId).thenAccept(object -> {
|
||||
if (object == null) {
|
||||
return;
|
||||
}
|
||||
DbgModelTargetProcess process = (DbgModelTargetProcess) object.getProxy();
|
||||
if (!process.getExecutionState().equals(TargetExecutionState.TERMINATED)) {
|
||||
process.setExecutionState(TargetExecutionState.INACTIVE, "Detached");
|
||||
}
|
||||
process.getParent().resync();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processExited(DbgProcess proc, DbgCause cause) {
|
||||
DbgModelTargetProcess targetProcess =
|
||||
(DbgModelTargetProcess) getModel().getModelObject(proc);
|
||||
if (targetProcess != null) {
|
||||
targetProcess.changeAttributes(List.of(), Map.of( //
|
||||
TargetExecutionStateful.STATE_ATTRIBUTE_NAME, TargetExecutionState.TERMINATED, //
|
||||
DbgModelTargetProcessImpl.EXIT_CODE_ATTRIBUTE_NAME, proc.getExitCode() //
|
||||
), "Exited");
|
||||
getListeners().fire.event(targetProcess.getProxy(), null,
|
||||
TargetEventType.PROCESS_EXITED,
|
||||
"Process " + proc.getId() + " exited code=" + proc.getExitCode(),
|
||||
List.of(getProxy()));
|
||||
}
|
||||
DbgProcess proc = process.getProcess();
|
||||
getListeners().fire.event(getProxy(), null, TargetEventType.PROCESS_EXITED,
|
||||
"Process " + proc.getId() + " exited code=" + proc.getExitCode(), List.of(process));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void threadExited(DebugThreadId threadId, DbgProcess process, DbgCause cause) {
|
||||
DbgModelTargetThread targetThread = (DbgModelTargetThread) getObject(threadId);
|
||||
if (targetThread == null) {
|
||||
return;
|
||||
}
|
||||
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
|
||||
"Thread " + threadId + " exited", List.of(targetThread));
|
||||
getObject(threadId).thenAccept(thread -> {
|
||||
if (thread == null) {
|
||||
return;
|
||||
}
|
||||
DbgModelTargetThread targetThread = (DbgModelTargetThread) thread.getProxy();
|
||||
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_EXITED,
|
||||
"Thread " + threadId + " exited", List.of(targetThread));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -321,22 +382,37 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
TargetEventType eventType = getEventType(state, cause, reason);
|
||||
getListeners().fire.event(getProxy(), targetThread, eventType,
|
||||
"Thread " + thread.getId() + " state changed", List.of(targetThread));
|
||||
targetThread.threadStateChangedSpecific(state, reason);
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<DbgModelTargetObject> stateChanged(Object object, DbgState state,
|
||||
String reason) {
|
||||
List<String> objPath = findObject(object);
|
||||
DbgModelTargetObject obj = (DbgModelTargetObject) getModel().getModelObject(objPath);
|
||||
if (obj instanceof DbgModelTargetExecutionStateful) {
|
||||
DbgModelTargetExecutionStateful stateful =
|
||||
(DbgModelTargetExecutionStateful) obj;
|
||||
TargetExecutionState execState = stateful.convertState(state);
|
||||
stateful.setExecutionState(execState, reason);
|
||||
}
|
||||
return CompletableFuture.completedFuture(obj);
|
||||
/*
|
||||
return AsyncUtils.sequence(TypeSpec.cls(DbgModelTargetObject.class)).then(seq -> {
|
||||
getModel().fetchModelValue(objPath).handle(seq::next);
|
||||
}, TypeSpec.cls(Object.class)).then((obj, seq) -> {
|
||||
if (obj instanceof DbgModelTargetExecutionStateful) {
|
||||
DbgModelTargetExecutionStateful stateful = (DbgModelTargetExecutionStateful) obj;
|
||||
TargetExecutionState execState = stateful.convertState(state);
|
||||
stateful.setExecutionState(execState, reason);
|
||||
}
|
||||
seq.exit((DbgModelTargetObject) obj);
|
||||
}).finish();
|
||||
}, TypeSpec.cls(Object.class))
|
||||
.then((obj, seq) -> {
|
||||
// This is quite possibly redundant
|
||||
if (obj instanceof DbgModelTargetExecutionStateful) {
|
||||
DbgModelTargetExecutionStateful stateful =
|
||||
(DbgModelTargetExecutionStateful) obj;
|
||||
TargetExecutionState execState = stateful.convertState(state);
|
||||
stateful.setExecutionState(execState, reason);
|
||||
}
|
||||
seq.exit((DbgModelTargetObject) obj);
|
||||
})
|
||||
.finish();
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -356,9 +432,8 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
|
||||
@Override
|
||||
public void breakpointDeleted(DbgBreakpointInfo info, DbgCause cause) {
|
||||
int id = info.getDebugBreakpoint().getId();
|
||||
bptInfoMap.remove(id);
|
||||
getObjectRevisited(info.getProc(), List.of("Debug", "Breakpoints"), info);
|
||||
bptInfoMap.remove((int) info.getNumber());
|
||||
getObjectAndRemove(info.getProc(), List.of("Debug", "Breakpoints"), info);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -372,8 +447,8 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
return;
|
||||
}
|
||||
|
||||
DbgModelTargetThread targetThread = getParentProcess().getThreads()
|
||||
.getTargetThread(getManager().getEventThread());
|
||||
DbgThread thread = info.getEventThread();
|
||||
TargetObject targetThread = getModel().getModelObject(thread);
|
||||
listeners.fire.breakpointHit(bpt.getParent(), targetThread, null, bpt, bpt);
|
||||
bpt.breakpointHit();
|
||||
});
|
||||
@ -465,8 +540,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
|
||||
}
|
||||
return TargetEventType.STOPPED;
|
||||
case SESSION_EXIT:
|
||||
getModel().close();
|
||||
break;
|
||||
return TargetEventType.PROCESS_EXITED;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -99,6 +99,8 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
||||
return DbgModelTargetRegisterBank.class;
|
||||
case "TTD":
|
||||
return DbgModelTargetTTD.class;
|
||||
case "Debug":
|
||||
return DbgModelTargetDebugContainer.class;
|
||||
}
|
||||
if (parentName != null) {
|
||||
switch (parentName) {
|
||||
@ -287,6 +289,7 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
||||
requestAttributes(false);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
if (proxy instanceof DbgModelTargetRegisterBank) {
|
||||
requestAttributes(false).thenAccept(__ -> {
|
||||
DbgModelTargetRegisterBank bank = (DbgModelTargetRegisterBank) proxy;
|
||||
@ -296,6 +299,7 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
||||
});
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
if (proxy instanceof DbgModelTargetProcessContainer || //
|
||||
proxy instanceof DbgModelTargetThreadContainer || //
|
||||
@ -323,6 +327,7 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
||||
requestAttributes(false);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
if (proxy instanceof DbgModelTargetRegisterBank) {
|
||||
requestAttributes(false).thenAccept(__ -> {
|
||||
DbgModelTargetRegisterBank bank = (DbgModelTargetRegisterBank) proxy;
|
||||
@ -332,11 +337,16 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
|
||||
});
|
||||
return;
|
||||
}
|
||||
*/
|
||||
if (proxy instanceof DbgModelTargetRegister || //
|
||||
proxy instanceof DbgModelTargetStackFrame) {
|
||||
requestAttributes(false);
|
||||
return;
|
||||
}
|
||||
if (proxy.getName().equals("Debug")) {
|
||||
requestAttributes(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void onRunning() {
|
||||
|
@ -5,6 +5,7 @@
|
||||
<interface name="Attacher" />
|
||||
<interface name="EventScope" />
|
||||
<interface name="Launcher" />
|
||||
<interface name="ActiveScope" />
|
||||
<interface name="FocusScope" />
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
@ -153,7 +154,7 @@
|
||||
<schema name="Available" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Attachable" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_pid" schema="INT" hidden="yes" />
|
||||
<attribute name="_pid" schema="LONG" hidden="yes" required="yes"/>
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
@ -169,7 +170,7 @@
|
||||
<element schema="VOID" />
|
||||
<attribute name="_os" schema="STRING" hidden="yes" />
|
||||
<attribute name="_debugger" schema="STRING" hidden="yes" />
|
||||
<attribute name="_endian" schema="STRING" hidden="yes" />
|
||||
<!-- attribute name="_endian" schema="STRING" hidden="yes" /-->
|
||||
<attribute name="_arch" schema="STRING" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
@ -285,6 +286,7 @@
|
||||
<attribute schema="ANY" />
|
||||
</schema>
|
||||
<schema name="DebugContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Aggregate" />
|
||||
<element schema="OBJECT" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
@ -515,7 +517,7 @@
|
||||
<attribute name="Disposition" schema="OBJECT" />
|
||||
<attribute name="Pending" schema="OBJECT" />
|
||||
<attribute name="Times" schema="OBJECT" />
|
||||
<attribute schema="VOID" />
|
||||
<attribute schema="ANY" />
|
||||
</schema>
|
||||
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="StackFrame" />
|
||||
|
@ -16,8 +16,16 @@
|
||||
package agent.dbgmodel.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengBreakpointsTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class InVmModelForDbgmodelBreakpointsTest extends AbstractModelForDbgengBreakpointsTest {
|
||||
|
||||
@Override
|
||||
protected PathPattern getBreakPattern() {
|
||||
return new PathPattern(PathUtils.parse("Sessions[0x0].Processes[].Debug.Breakpoints[]"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
|
@ -15,9 +15,18 @@
|
||||
*/
|
||||
package agent.dbgmodel.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengFrameFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengFrameActivationTest;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class InVmModelForDbgmodelFrameActivationTest
|
||||
extends AbstractModelForDbgengFrameActivationTest {
|
||||
|
||||
protected PathPattern getStackPattern() {
|
||||
return new PathPattern(
|
||||
PathUtils.parse("Sessions[0x0].Processes[].Threads[].Stack.Frames[]"));
|
||||
}
|
||||
|
||||
public class InVmModelForDbgmodelFrameFocusTest extends AbstractModelForDbgengFrameFocusTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
@ -15,21 +15,74 @@
|
||||
*/
|
||||
package agent.dbgmodel.model.invm;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengInterpreterTest;
|
||||
import agent.dbgeng.model.WindowsSpecimen;
|
||||
import agent.dbgeng.model.iface2.DbgModelTargetProcess;
|
||||
import ghidra.dbg.target.TargetInterpreter;
|
||||
import ghidra.dbg.target.TargetProcess;
|
||||
import ghidra.dbg.test.AbstractDebuggerModelTest;
|
||||
import ghidra.dbg.test.ProvidesTargetViaLaunchSpecimen;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class InVmModelForDbgmodelInterpreterTest extends AbstractModelForDbgengInterpreterTest {
|
||||
public class InVmModelForDbgmodelInterpreterTest extends AbstractModelForDbgengInterpreterTest
|
||||
implements ProvidesTargetViaLaunchSpecimen {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDebuggerModelTest getTest() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> seedPath() {
|
||||
return PathUtils.parse("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExpectedInterpreterPath() {
|
||||
return PathUtils.parse("Sessions[0x0]");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void ensureInterpreterAvailable() throws Throwable {
|
||||
obtainTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Ignore
|
||||
@Test
|
||||
public void testAttachViaInterpreterShowsInProcessContainer() throws Throwable {
|
||||
super.testAttachViaInterpreterShowsInProcessContainer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void testLaunchViaInterpreterShowsInProcessContainer() throws Throwable {
|
||||
assumeTrue(m.hasProcessContainer());
|
||||
m.build();
|
||||
DbgModelTargetProcess initialTarget = (DbgModelTargetProcess) obtainTarget();
|
||||
|
||||
DebuggerTestSpecimen specimen = WindowsSpecimen.NOTEPAD;
|
||||
assertNull(getProcessRunning(specimen, this));
|
||||
TargetInterpreter interpreter = findInterpreter();
|
||||
for (String line : specimen.getLaunchScript()) {
|
||||
waitOn(interpreter.execute(line));
|
||||
}
|
||||
TargetProcess process = retryForProcessRunning(specimen, this);
|
||||
initialTarget.detach();
|
||||
|
||||
runTestKillViaInterpreter(process, interpreter);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgmodel.model.invm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengProcessActivationTest;
|
||||
import ghidra.dbg.target.TargetInterpreter;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class InVmModelForDbgmodelProcessActivationTest
|
||||
extends AbstractModelForDbgengProcessActivationTest {
|
||||
|
||||
protected PathPattern getProcessPattern() {
|
||||
return new PathPattern(PathUtils.parse("Sessions[0x0].Processes[]"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExpectedSessionPath() {
|
||||
return PathUtils.parse("Sessions[0x0]");
|
||||
}
|
||||
|
||||
public String getIdFromCapture(String line) {
|
||||
return "0x" + line.split("\\s+")[3];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String processId = obj.getName();
|
||||
processId = processId.substring(3, processId.length() - 1);
|
||||
String output = waitOn(interpreter.executeCapture("|"));
|
||||
String[] lines = output.split("\n");
|
||||
for (String l : lines) {
|
||||
if (l.contains(processId)) {
|
||||
processId = l.split("\\s+")[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
waitOn(interpreter.execute("|" + processId + " s"));
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/* ###
|
||||
* 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.dbgmodel.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengProcessFocusTest;
|
||||
|
||||
public class InVmModelForDbgmodelProcessFocusTest extends AbstractModelForDbgengProcessFocusTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
}
|
||||
}
|
@ -15,6 +15,9 @@
|
||||
*/
|
||||
package agent.dbgmodel.model.invm;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengRootAttacherTest;
|
||||
|
||||
public class InVmModelForDbgmodelRootAttacherTest extends AbstractModelForDbgengRootAttacherTest {
|
||||
@ -22,4 +25,12 @@ public class InVmModelForDbgmodelRootAttacherTest extends AbstractModelForDbgeng
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Ignore
|
||||
@Test
|
||||
// Takes forever - passes w/ OTE on Memory in tear down
|
||||
public void testAttachByPidThenResumeInterrupt() throws Throwable {
|
||||
super.testAttachByPidThenResumeInterrupt();
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,14 @@
|
||||
*/
|
||||
package agent.dbgmodel.model.invm;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengScenarioMemoryTest;
|
||||
import ghidra.dbg.target.TargetModule;
|
||||
import ghidra.dbg.target.TargetProcess;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
|
||||
public class InVmModelForDbgmodelScenarioMemoryTest
|
||||
extends AbstractModelForDbgengScenarioMemoryTest {
|
||||
@ -23,4 +30,17 @@ public class InVmModelForDbgmodelScenarioMemoryTest
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Address getAddressToWrite(TargetProcess process) throws Throwable {
|
||||
// It seems this is the only test case that exercises module symbols.
|
||||
List<String> modulePath = PathUtils.extend(process.getPath(),
|
||||
PathUtils.parse("Modules"));
|
||||
Map<List<String>, TargetModule> modules = m.findAll(TargetModule.class, modulePath, true);
|
||||
Collection<TargetModule> values = modules.values();
|
||||
TargetModule test = (TargetModule) values.toArray()[0];
|
||||
AddressRange range =
|
||||
(AddressRange) test.fetchAttribute(TargetModule.RANGE_ATTRIBUTE_NAME).get();
|
||||
return range.getMinAddress().add(0x15000);
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,20 @@
|
||||
package agent.dbgmodel.model.invm;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengScenarioStackTest;
|
||||
import ghidra.dbg.target.TargetProcess;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
public class InVmModelForDbgmodelScenarioStackTest extends AbstractModelForDbgengScenarioStackTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postLaunch(TargetProcess process) throws Throwable {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateFramePC(int index, Address pc) {
|
||||
}
|
||||
}
|
||||
|
@ -23,4 +23,5 @@ public class InVmModelForDbgmodelScenarioX64RegistersTest
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,10 +17,10 @@ package agent.dbgmodel.model.invm;
|
||||
|
||||
import org.junit.Ignore;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengSessionFocusTest;
|
||||
import agent.dbgeng.model.AbstractModelForDbgengSessionActivationTest;
|
||||
|
||||
@Ignore("Don't know how to make multiple sessions")
|
||||
public class InVmModelForDbgmodelSessionFocusTest extends AbstractModelForDbgengSessionFocusTest {
|
||||
public class InVmModelForDbgmodelSessionActivationTest extends AbstractModelForDbgengSessionActivationTest {
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
@ -22,4 +22,5 @@ public class InVmModelForDbgmodelSteppableTest extends AbstractModelForDbgengSte
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.dbgmodel.model.invm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengThreadActivationTest;
|
||||
import ghidra.dbg.target.TargetInterpreter;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class InVmModelForDbgmodelThreadActivationTest
|
||||
extends AbstractModelForDbgengThreadActivationTest {
|
||||
|
||||
protected PathPattern getThreadPattern() {
|
||||
return new PathPattern(PathUtils.parse("Sessions[0x0].Processes[].Threads[]"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExpectedSessionPath() {
|
||||
return PathUtils.parse("Sessions[0x0]");
|
||||
}
|
||||
|
||||
public String getIdFromCapture(String line) {
|
||||
return "0x" + line.split("\\s+")[3].split("\\.")[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter)
|
||||
throws Throwable {
|
||||
String threadId = obj.getName();
|
||||
threadId = threadId.substring(3, threadId.length() - 1);
|
||||
String output = waitOn(interpreter.executeCapture("~"));
|
||||
String[] lines = output.split("\n");
|
||||
for (String l : lines) {
|
||||
if (l.contains(threadId)) {
|
||||
threadId = l.split("\\s+")[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
waitOn(interpreter.execute("~" + threadId + " s"));
|
||||
}
|
||||
|
||||
}
|
@ -15,11 +15,45 @@
|
||||
*/
|
||||
package agent.dbgmodel.model.invm;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import agent.dbgeng.model.AbstractModelForDbgengX64RegistersTest;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
|
||||
public class InVmModelForDbgmodelX64RegistersTest extends AbstractModelForDbgengX64RegistersTest {
|
||||
|
||||
public final Map<String, byte[]> REG_VALSX = Map.ofEntries(
|
||||
Map.entry("rax", arr("0123456789abcdef")),
|
||||
Map.entry("rdx", arr("fedcba9876543210")));
|
||||
|
||||
@Override
|
||||
public ModelHost modelHost() throws Throwable {
|
||||
return new InVmDbgmodelModelHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisterBankAlsoContainer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExpectedRegisterBankPath(List<String> threadPath) {
|
||||
return PathUtils.extend(threadPath, List.of("Registers", "User"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, byte[]> getRegisterWrites() {
|
||||
return REG_VALSX;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Ignore
|
||||
@Test
|
||||
public void testRegistersHaveExpectedSizes() throws Throwable {
|
||||
super.testRegistersHaveExpectedSizes();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,51 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.gdb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ProcessBuilder.Redirect;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.dbg.util.ShellUtils;
|
||||
|
||||
public enum GdbCompatibility {
|
||||
INSTANCE;
|
||||
|
||||
public static boolean checkGdbPresent(String path) {
|
||||
try {
|
||||
ProcessBuilder builder = new ProcessBuilder(path, "--version");
|
||||
builder.redirectError(Redirect.INHERIT);
|
||||
builder.redirectOutput(Redirect.INHERIT);
|
||||
@SuppressWarnings("unused")
|
||||
Process gdb = builder.start();
|
||||
// TODO: Once supported versions are decided, check the version.
|
||||
return true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<String, Boolean> cache = new HashMap<>();
|
||||
|
||||
public boolean isCompatible(String gdbCmd) {
|
||||
List<String> args = ShellUtils.parseArgs(gdbCmd);
|
||||
if (args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return cache.computeIfAbsent(gdbCmd, p -> checkGdbPresent(args.get(0)));
|
||||
}
|
||||
}
|
@ -17,24 +17,22 @@ package agent.gdb;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.gdb.gadp.GdbLocalDebuggerModelFactory;
|
||||
import agent.gdb.manager.GdbManager;
|
||||
import agent.gdb.model.impl.GdbModelImpl;
|
||||
import agent.gdb.pty.linux.LinuxPtyFactory;
|
||||
import ghidra.dbg.DebuggerModelFactory;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.LocalDebuggerModelFactory;
|
||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||
import ghidra.util.classfinder.ExtensionPointProperties;
|
||||
|
||||
/**
|
||||
* Note this is in the testing source because it's not meant to be shipped in the release.... That
|
||||
* may change if it proves stable, though, no?
|
||||
*/
|
||||
@FactoryDescription( //
|
||||
brief = "IN-VM GNU gdb local debugger", //
|
||||
htmlDetails = "Launch a GDB session in this same JVM" //
|
||||
brief = "IN-VM GNU gdb local debugger", //
|
||||
htmlDetails = "Launch a GDB session in this same JVM" //
|
||||
)
|
||||
@ExtensionPointProperties(priority = 80)
|
||||
public class GdbInJvmDebuggerModelFactory implements LocalDebuggerModelFactory {
|
||||
public class GdbInJvmDebuggerModelFactory implements DebuggerModelFactory {
|
||||
|
||||
private String gdbCmd = GdbManager.DEFAULT_GDB_CMD;
|
||||
@FactoryOption("GDB launch command")
|
||||
@ -48,13 +46,14 @@ public class GdbInJvmDebuggerModelFactory implements LocalDebuggerModelFactory {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<? extends DebuggerObjectModel> build() {
|
||||
GdbModelImpl model = new GdbModelImpl();
|
||||
// TODO: Choose Linux or Windows pty based on host OS
|
||||
GdbModelImpl model = new GdbModelImpl(new LinuxPtyFactory());
|
||||
return model.startGDB(gdbCmd, new String[] {}).thenApply(__ -> model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompatible() {
|
||||
return GdbLocalDebuggerModelFactory.checkGdbPresent(gdbCmd);
|
||||
return GdbCompatibility.INSTANCE.isCompatible(gdbCmd);
|
||||
}
|
||||
|
||||
public String getGdbCommand() {
|
||||
|
@ -0,0 +1,129 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.gdb;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.gdb.model.impl.GdbModelImpl;
|
||||
import agent.gdb.pty.ssh.GhidraSshPtyFactory;
|
||||
import ghidra.dbg.DebuggerModelFactory;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||
import ghidra.dbg.util.ConfigurableFactory.FactoryOption;
|
||||
|
||||
@FactoryDescription(
|
||||
brief = "GNU gdb via SSH",
|
||||
htmlDetails = "Launch a GDB session over an SSH connection")
|
||||
public class GdbOverSshDebuggerModelFactory implements DebuggerModelFactory {
|
||||
|
||||
private String gdbCmd = "gdb";
|
||||
@FactoryOption("GDB launch command")
|
||||
public final Property<String> gdbCommandOption =
|
||||
Property.fromAccessors(String.class, this::getGdbCommand, this::setGdbCommand);
|
||||
|
||||
private boolean existing = false;
|
||||
@FactoryOption("Use existing session via new-ui")
|
||||
public final Property<Boolean> useExistingOption =
|
||||
Property.fromAccessors(boolean.class, this::isUseExisting, this::setUseExisting);
|
||||
|
||||
private String hostname = "localhost";
|
||||
@FactoryOption("SSH hostname")
|
||||
public final Property<String> hostnameOption =
|
||||
Property.fromAccessors(String.class, this::getHostname, this::setHostname);
|
||||
|
||||
private int port = 22;
|
||||
@FactoryOption("SSH TCP port")
|
||||
public final Property<Integer> portOption =
|
||||
Property.fromAccessors(Integer.class, this::getPort, this::setPort);
|
||||
|
||||
private String username = "user";
|
||||
@FactoryOption("SSH username")
|
||||
public final Property<String> usernameOption =
|
||||
Property.fromAccessors(String.class, this::getUsername, this::setUsername);
|
||||
|
||||
private String keyFile = "";
|
||||
@FactoryOption("SSH identity (blank for password auth)")
|
||||
public final Property<String> keyFileOption =
|
||||
Property.fromAccessors(String.class, this::getKeyFile, this::setKeyFile);
|
||||
|
||||
@Override
|
||||
public CompletableFuture<? extends DebuggerObjectModel> build() {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
GhidraSshPtyFactory factory = new GhidraSshPtyFactory();
|
||||
factory.setHostname(hostname);
|
||||
factory.setPort(port);
|
||||
factory.setKeyFile(keyFile);
|
||||
factory.setUsername(username);
|
||||
return new GdbModelImpl(factory);
|
||||
}).thenCompose(model -> {
|
||||
return model.startGDB(gdbCmd, new String[] {}).thenApply(__ -> model);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompatible() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getGdbCommand() {
|
||||
return gdbCmd;
|
||||
}
|
||||
|
||||
public void setGdbCommand(String gdbCmd) {
|
||||
this.gdbCmd = gdbCmd;
|
||||
}
|
||||
|
||||
public boolean isUseExisting() {
|
||||
return existing;
|
||||
}
|
||||
|
||||
public void setUseExisting(boolean existing) {
|
||||
this.existing = existing;
|
||||
gdbCommandOption.setEnabled(!existing);
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public void setHostname(String hostname) {
|
||||
this.hostname = hostname;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getKeyFile() {
|
||||
return keyFile;
|
||||
}
|
||||
|
||||
public void setKeyFile(String keyFile) {
|
||||
this.keyFile = keyFile;
|
||||
}
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.gdb.ffi.linux;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
import jnr.ffi.Pointer;
|
||||
import jnr.ffi.byref.IntByReference;
|
||||
import jnr.posix.POSIX;
|
||||
import jnr.posix.POSIXFactory;
|
||||
|
||||
/**
|
||||
* A pseudo-terminal
|
||||
*
|
||||
* A pseudo-terminal is essentially a two way pipe where one end acts as the master, and the other
|
||||
* acts as the slave. The process opening the pseudo-terminal is given a handle to both ends. The
|
||||
* slave end is generally given to a subprocess, possibly designating the pty as the controlling tty
|
||||
* of a new session. This scheme is how, for example, an SSH daemon starts a new login shell. The
|
||||
* shell is given the slave end, and the master end is presented to the SSH client.
|
||||
*
|
||||
* This is more powerful than controlling a process via standard in and standard out. 1) Some
|
||||
* programs detect whether or not stdin/out/err refer to the controlling tty. For example, a program
|
||||
* should avoid prompting for passwords unless stdin is the controlling tty. Using a pty can provide
|
||||
* a controlling tty that is not necessarily controlled by a user. 2) Terminals have other
|
||||
* properties and can, e.g., send signals to the foreground process group (job) by sending special
|
||||
* characters. Normal characters are passed to the slave, but special characters may be interpreted
|
||||
* by the terminal's <em>line discipline</em>. A rather common case is to send Ctrl-C (character
|
||||
* 003). Using stdin, the subprocess simply reads 003. With a properly-configured pty and session,
|
||||
* the subprocess is interrupted (sent SIGINT) instead.
|
||||
*
|
||||
* This class opens a pseudo-terminal and presents both ends as individual handles. The master end
|
||||
* simply provides an input and output stream. These are typical byte-oriented streams, except that
|
||||
* the data passes through the pty, subject to interpretation by the OS kernel. On Linux, this means
|
||||
* the pty will apply the configured line discipline. Consult the host OS documentation for special
|
||||
* character sequences.
|
||||
*
|
||||
* The slave end also provides the input and output streams, but it is uncommon to use them from the
|
||||
* same process. More likely, subprocess is launched in a new session, configuring the slave as the
|
||||
* controlling terminal. Thus, the slave handle provides methods for obtaining the slave pty file
|
||||
* name and/or spawning a new session. Once spawned, the master end is used to control the session.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <pre>
|
||||
* Pty pty = Pty.openpty();
|
||||
* pty.getSlave().session("bash");
|
||||
*
|
||||
* PrintWriter writer = new PrintWriter(pty.getMaster().getOutputStream());
|
||||
* writer.println("echo test");
|
||||
* BufferedReader reader =
|
||||
* new BufferedReader(new InputStreamReader(pty.getMaster().getInputStream()));
|
||||
* System.out.println(reader.readLine());
|
||||
* System.out.println(reader.readLine());
|
||||
*
|
||||
* pty.close();
|
||||
* </pre>
|
||||
*/
|
||||
public class Pty implements AutoCloseable {
|
||||
private static final POSIX LIB_POSIX = POSIXFactory.getNativePOSIX();
|
||||
|
||||
private final int amaster;
|
||||
private final int aslave;
|
||||
private final String name;
|
||||
private boolean closed = false;
|
||||
|
||||
/**
|
||||
* Open a new pseudo-terminal
|
||||
*
|
||||
* Implementation note: On Linux, this invokes the native {@code openpty()} function. See the
|
||||
* Linux manual for details.
|
||||
*
|
||||
* @return new new Pty
|
||||
* @throws IOException if openpty fails
|
||||
*/
|
||||
public static Pty openpty() throws IOException {
|
||||
// TODO: Support termp and winp?
|
||||
IntByReference m = new IntByReference();
|
||||
IntByReference s = new IntByReference();
|
||||
Pointer n = Pointer.wrap(jnr.ffi.Runtime.getSystemRuntime(), ByteBuffer.allocate(1024));
|
||||
if (Util.INSTANCE.openpty(m, s, n, null, null) < 0) {
|
||||
int errno = LIB_POSIX.errno();
|
||||
throw new IOException(errno + ": " + LIB_POSIX.strerror(errno));
|
||||
}
|
||||
return new Pty(m.intValue(), s.intValue(), n.getString(0));
|
||||
}
|
||||
|
||||
Pty(int amaster, int aslave, String name) {
|
||||
Msg.debug(this, "New Pty: " + name + " at (" + amaster + "," + aslave + ")");
|
||||
this.amaster = amaster;
|
||||
this.aslave = aslave;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a handle to the master side of the pty
|
||||
*
|
||||
* @return the master handle
|
||||
*/
|
||||
public PtyMaster getMaster() {
|
||||
return new PtyMaster(amaster);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a handle to the slave side of the pty
|
||||
*
|
||||
* @return the slave handle
|
||||
*/
|
||||
public PtySlave getSlave() {
|
||||
return new PtySlave(aslave, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes both ends of the pty
|
||||
*
|
||||
* This only closes this process's handles to the pty. For the master end, this should be the
|
||||
* only process with a handle. The slave end may be opened by any number of other processes.
|
||||
* More than likely, however, those processes will terminate once the master end is closed,
|
||||
* since reads or writes on the slave will produce EOF or an error.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
int result;
|
||||
result = LIB_POSIX.close(aslave);
|
||||
if (result < 0) {
|
||||
throw new IOException(LIB_POSIX.strerror(LIB_POSIX.errno()));
|
||||
}
|
||||
result = LIB_POSIX.close(amaster);
|
||||
if (result < 0) {
|
||||
throw new IOException(LIB_POSIX.strerror(LIB_POSIX.errno()));
|
||||
}
|
||||
closed = true;
|
||||
}
|
||||
}
|
@ -15,10 +15,9 @@
|
||||
*/
|
||||
package agent.gdb.gadp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ProcessBuilder.Redirect;
|
||||
import java.util.List;
|
||||
|
||||
import agent.gdb.GdbCompatibility;
|
||||
import agent.gdb.manager.GdbManager;
|
||||
import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory;
|
||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||
@ -26,31 +25,11 @@ import ghidra.dbg.util.ShellUtils;
|
||||
import ghidra.util.classfinder.ExtensionPointProperties;
|
||||
|
||||
@FactoryDescription( //
|
||||
brief = "GNU gdb local agent via GADP/TCP", //
|
||||
htmlDetails = "Launch a new agent using GDB. This may start a new session or join an existing one." //
|
||||
brief = "GNU gdb local agent via GADP/TCP", //
|
||||
htmlDetails = "Launch a new agent using GDB. This may start a new session or join an existing one." //
|
||||
)
|
||||
@ExtensionPointProperties(priority = 100)
|
||||
public class GdbLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory {
|
||||
public static boolean checkGdbPresent(String gdbCmd) {
|
||||
List<String> args = ShellUtils.parseArgs(gdbCmd);
|
||||
if (args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
ProcessBuilder builder = new ProcessBuilder(args.get(0), "--version");
|
||||
builder.redirectError(Redirect.INHERIT);
|
||||
builder.redirectOutput(Redirect.INHERIT);
|
||||
@SuppressWarnings("unused")
|
||||
Process gdb = builder.start();
|
||||
// TODO: Once supported versions are decided, check the version.
|
||||
return true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected Boolean isSuitable;
|
||||
|
||||
private String gdbCmd = GdbManager.DEFAULT_GDB_CMD;
|
||||
@FactoryOption("GDB launch command")
|
||||
@ -62,15 +41,10 @@ public class GdbLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModel
|
||||
public final Property<Boolean> useExistingOption =
|
||||
Property.fromAccessors(boolean.class, this::isUseExisting, this::setUseExisting);
|
||||
|
||||
// TODO: A factory which connects to GDB via SSH. Would need to refactor manager.
|
||||
|
||||
@Override
|
||||
public boolean isCompatible() {
|
||||
// TODO: Could potentially support GDB on Windows, but the pty thing would need porting.
|
||||
if (isSuitable != null) {
|
||||
return isSuitable;
|
||||
}
|
||||
return isSuitable = checkGdbPresent(gdbCmd);
|
||||
return GdbCompatibility.INSTANCE.isCompatible(gdbCmd);
|
||||
}
|
||||
|
||||
public String getGdbCommand() {
|
||||
|
@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.gdb.gadp.GdbGadpServer;
|
||||
import agent.gdb.model.impl.GdbModelImpl;
|
||||
import agent.gdb.pty.linux.LinuxPtyFactory;
|
||||
import ghidra.dbg.gadp.server.AbstractGadpServer;
|
||||
|
||||
public class GdbGadpServerImpl implements GdbGadpServer {
|
||||
@ -35,7 +36,8 @@ public class GdbGadpServerImpl implements GdbGadpServer {
|
||||
|
||||
public GdbGadpServerImpl(SocketAddress addr) throws IOException {
|
||||
super();
|
||||
this.model = new GdbModelImpl();
|
||||
// TODO: Select Linux or Windows factory based on host OS
|
||||
this.model = new GdbModelImpl(new LinuxPtyFactory());
|
||||
this.server = new GadpSide(model, addr);
|
||||
}
|
||||
|
||||
|
@ -152,9 +152,10 @@ public interface GdbInferior extends GdbMemoryOperations {
|
||||
* will apply to this inferior. Commands issued from this handle are always executed with this
|
||||
* inferior in focus, so it is rare to invoke his method directly.
|
||||
*
|
||||
* @param internal true to prevent announcement of the change
|
||||
* @return a future that completes when GDB has executed the command
|
||||
*/
|
||||
CompletableFuture<Void> setActive();
|
||||
CompletableFuture<Void> setActive(boolean internal);
|
||||
|
||||
/**
|
||||
* Specify a binary image for execution and debug symbols
|
||||
|
@ -21,10 +21,12 @@ import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import agent.gdb.ffi.linux.Pty;
|
||||
import agent.gdb.manager.breakpoint.GdbBreakpointInfo;
|
||||
import agent.gdb.manager.breakpoint.GdbBreakpointInsertions;
|
||||
import agent.gdb.manager.impl.GdbManagerImpl;
|
||||
import agent.gdb.pty.PtyFactory;
|
||||
import agent.gdb.pty.linux.LinuxPty;
|
||||
import agent.gdb.pty.linux.LinuxPtyFactory;
|
||||
|
||||
/**
|
||||
* The controlling side of a GDB session, using GDB/MI, usually via a pseudo-terminal
|
||||
@ -85,7 +87,8 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
|
||||
*/
|
||||
public static void main(String[] args)
|
||||
throws InterruptedException, ExecutionException, IOException {
|
||||
try (GdbManager mgr = newInstance()) {
|
||||
// TODO: Choose factory by host OS
|
||||
try (GdbManager mgr = newInstance(new LinuxPtyFactory())) {
|
||||
mgr.start(DEFAULT_GDB_CMD, args);
|
||||
mgr.runRC().get();
|
||||
mgr.consoleLoop();
|
||||
@ -101,8 +104,8 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
|
||||
*
|
||||
* @return the manager
|
||||
*/
|
||||
public static GdbManager newInstance() {
|
||||
return new GdbManagerImpl();
|
||||
public static GdbManager newInstance(PtyFactory ptyFactory) {
|
||||
return new GdbManagerImpl(ptyFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -203,7 +206,8 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
|
||||
* Note: depending on the target, its output may not be communicated via this listener. Local
|
||||
* targets, e.g., tend to just print output to GDB's controlling TTY. See
|
||||
* {@link GdbInferior#setTty(String)} for a means to more reliably interact with a target's
|
||||
* input and output. See also {@link Pty} for a means to easily acquire a new TTY from Java.
|
||||
* input and output. See also {@link LinuxPty} for a means to easily acquire a new TTY from
|
||||
* Java.
|
||||
*
|
||||
* @param listener the listener to add
|
||||
*/
|
||||
@ -507,6 +511,7 @@ public interface GdbManager extends AutoCloseable, GdbBreakpointInsertions {
|
||||
* Get the name of the mi2 pty for this GDB session
|
||||
*
|
||||
* @return the filename
|
||||
* @throws IOException if the filename could not be determined
|
||||
*/
|
||||
String getMi2PtyName();
|
||||
String getMi2PtyName() throws IOException;
|
||||
}
|
||||
|
@ -47,9 +47,10 @@ public interface GdbStackFrame extends GdbStackFrameOperations {
|
||||
/**
|
||||
* Make this frame the current frame
|
||||
*
|
||||
* @param internal true to prevent announcement of the change
|
||||
* @return a future that completes when the frame is the current frame
|
||||
*/
|
||||
CompletableFuture<Void> setActive();
|
||||
CompletableFuture<Void> setActive(boolean internal);
|
||||
|
||||
/**
|
||||
* Get the thread for this frame
|
||||
|
@ -68,9 +68,10 @@ public interface GdbThread
|
||||
/**
|
||||
* Make this thread the current thread
|
||||
*
|
||||
* @param internal true to prevent announcement of the change
|
||||
* @return a future that completes when the thread is the current thread
|
||||
*/
|
||||
CompletableFuture<Void> setActive();
|
||||
CompletableFuture<Void> setActive(boolean internal);
|
||||
|
||||
/**
|
||||
* Set the value of an internal GDB variable
|
||||
|
@ -67,7 +67,7 @@ public interface GdbCommand<T> {
|
||||
* <p>
|
||||
* Complete {@code} pending with a result to short-circuit the execution of this command.
|
||||
*
|
||||
* @param pending the pending command result
|
||||
* @param pending the pend@Override ing command result
|
||||
*/
|
||||
void preCheck(GdbPendingCommand<? super T> pending);
|
||||
|
||||
@ -92,6 +92,13 @@ public interface GdbCommand<T> {
|
||||
*/
|
||||
public Integer impliesCurrentFrameId();
|
||||
|
||||
/**
|
||||
* Check if focus announcements from this command should be suppressed
|
||||
*
|
||||
* @return true to suppress announcements
|
||||
*/
|
||||
public boolean isFocusInternallyDriven();
|
||||
|
||||
/**
|
||||
* Handle an event that occurred during the execution of this command
|
||||
*
|
||||
|
@ -208,7 +208,7 @@ public class GdbInferiorImpl implements GdbInferior {
|
||||
* longer be current for the actual command execution. NB: The select command will cancel
|
||||
* itself if this inferior is already current.
|
||||
*/
|
||||
return setActive().thenCombine(manager.execute(cmd), (s, e) -> e);
|
||||
return setActive(true).thenCombine(manager.execute(cmd), (s, e) -> e);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -347,8 +347,8 @@ public class GdbInferiorImpl implements GdbInferior {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setActive() {
|
||||
return manager.setActiveInferior(this);
|
||||
public CompletableFuture<Void> setActive(boolean internal) {
|
||||
return manager.setActiveInferior(this, internal);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,7 +26,6 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.python.core.PyDictionary;
|
||||
import org.python.util.InteractiveConsole;
|
||||
|
||||
import agent.gdb.ffi.linux.Pty;
|
||||
import agent.gdb.manager.*;
|
||||
import agent.gdb.manager.GdbCause.Causes;
|
||||
import agent.gdb.manager.breakpoint.GdbBreakpointInfo;
|
||||
@ -35,6 +34,7 @@ import agent.gdb.manager.evt.*;
|
||||
import agent.gdb.manager.impl.cmd.*;
|
||||
import agent.gdb.manager.parsing.GdbMiParser;
|
||||
import agent.gdb.manager.parsing.GdbParsingUtils.GdbParseError;
|
||||
import agent.gdb.pty.*;
|
||||
import ghidra.async.*;
|
||||
import ghidra.async.AsyncLock.Hold;
|
||||
import ghidra.dbg.error.DebuggerModelTerminatingException;
|
||||
@ -77,7 +77,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||
static {
|
||||
if (LOG_IO) {
|
||||
try {
|
||||
DBG_LOG = new PrintWriter(new FileOutputStream(new File("DBG.log")));
|
||||
DBG_LOG = new PrintWriter(new FileOutputStream(new File("GDB.log")));
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new AssertionError(e);
|
||||
@ -104,7 +104,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||
this.pty = pty;
|
||||
this.channel = channel;
|
||||
this.reader =
|
||||
new BufferedReader(new InputStreamReader(pty.getMaster().getInputStream()));
|
||||
new BufferedReader(new InputStreamReader(pty.getParent().getInputStream()));
|
||||
this.interpreter = interpreter;
|
||||
hasWriter = new CompletableFuture<>();
|
||||
}
|
||||
@ -124,7 +124,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||
}
|
||||
}
|
||||
if (writer == null) {
|
||||
writer = new PrintWriter(pty.getMaster().getOutputStream());
|
||||
writer = new PrintWriter(pty.getParent().getOutputStream());
|
||||
hasWriter.complete(null);
|
||||
}
|
||||
//Msg.debug(this, channel + ": " + line);
|
||||
@ -145,6 +145,8 @@ public class GdbManagerImpl implements GdbManager {
|
||||
}
|
||||
}
|
||||
|
||||
private final PtyFactory ptyFactory;
|
||||
|
||||
private final AsyncReference<GdbState, GdbCause> state =
|
||||
new AsyncReference<>(GdbState.NOT_STARTED);
|
||||
// A copy of state, which is updated on the eventThread.
|
||||
@ -156,7 +158,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||
private final HandlerMap<GdbEvent<?>, Void, Void> handlerMap = new HandlerMap<>();
|
||||
private final AtomicBoolean exited = new AtomicBoolean(false);
|
||||
|
||||
private Process gdb;
|
||||
private PtySession gdb;
|
||||
private Thread gdbWaiter;
|
||||
|
||||
private PtyThread iniThread;
|
||||
@ -193,8 +195,12 @@ public class GdbManagerImpl implements GdbManager {
|
||||
|
||||
/**
|
||||
* Instantiate a new manager
|
||||
*
|
||||
* @param ptyFactory a factory for creating Pty's for child GDBs
|
||||
*/
|
||||
public GdbManagerImpl() {
|
||||
public GdbManagerImpl(PtyFactory ptyFactory) {
|
||||
this.ptyFactory = ptyFactory;
|
||||
|
||||
state.filter(this::stateFilter);
|
||||
state.addChangeListener(this::trackRunningInterpreter);
|
||||
state.addChangeListener((os, ns, c) -> event(() -> asyncState.set(ns, c), "managerState"));
|
||||
@ -556,9 +562,9 @@ public class GdbManagerImpl implements GdbManager {
|
||||
executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
if (gdbCmd != null) {
|
||||
iniThread = new PtyThread(Pty.openpty(), Channel.STDOUT, null);
|
||||
iniThread = new PtyThread(ptyFactory.openpty(), Channel.STDOUT, null);
|
||||
|
||||
gdb = iniThread.pty.getSlave().session(fullargs.toArray(new String[] {}), null);
|
||||
gdb = iniThread.pty.getChild().session(fullargs.toArray(new String[] {}), null);
|
||||
gdbWaiter = new Thread(this::waitGdbExit, "GDB WaitExit");
|
||||
gdbWaiter.start();
|
||||
|
||||
@ -575,14 +581,16 @@ public class GdbManagerImpl implements GdbManager {
|
||||
}
|
||||
switch (iniThread.interpreter) {
|
||||
case CLI:
|
||||
Pty mi2Pty = ptyFactory.openpty();
|
||||
|
||||
cliThread = iniThread;
|
||||
cliThread.setName("GDB Read CLI");
|
||||
cliThread.writer.println("new-ui mi2 " + mi2Pty.getChild().nullSession());
|
||||
cliThread.writer.flush();
|
||||
|
||||
mi2Thread = new PtyThread(Pty.openpty(), Channel.STDOUT, Interpreter.MI2);
|
||||
mi2Thread = new PtyThread(mi2Pty, Channel.STDOUT, Interpreter.MI2);
|
||||
mi2Thread.setName("GDB Read MI2");
|
||||
mi2Thread.start();
|
||||
cliThread.writer.println("new-ui mi2 " + mi2Thread.pty.getSlave().getFile());
|
||||
cliThread.writer.flush();
|
||||
try {
|
||||
mi2Thread.hasWriter.get(2, TimeUnit.SECONDS);
|
||||
}
|
||||
@ -598,10 +606,12 @@ public class GdbManagerImpl implements GdbManager {
|
||||
}
|
||||
}
|
||||
else {
|
||||
mi2Thread = new PtyThread(Pty.openpty(), Channel.STDOUT, Interpreter.MI2);
|
||||
mi2Thread.setName("GDB Read MI2");
|
||||
Pty mi2Pty = ptyFactory.openpty();
|
||||
Msg.info(this, "Agent is waiting for GDB/MI v2 interpreter at " +
|
||||
mi2Thread.pty.getSlave().getFile());
|
||||
mi2Pty.getChild().nullSession());
|
||||
mi2Thread = new PtyThread(mi2Pty, Channel.STDOUT, Interpreter.MI2);
|
||||
mi2Thread.setName("GDB Read MI2");
|
||||
|
||||
mi2Thread.start();
|
||||
}
|
||||
}
|
||||
@ -622,7 +632,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||
|
||||
private void waitGdbExit() {
|
||||
try {
|
||||
int exitcode = gdb.waitFor();
|
||||
int exitcode = gdb.waitExited();
|
||||
state.set(GdbState.EXIT, Causes.UNCLAIMED);
|
||||
exited.set(true);
|
||||
if (!executor.isShutdown()) {
|
||||
@ -800,6 +810,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||
/**
|
||||
* Schedule a line of GDB output for processing
|
||||
*
|
||||
* <p>
|
||||
* Before the implementation started using a PTY, the channel was used to distinguish whether
|
||||
* the line was read from stdout or stderr. Now, all output is assumed to be from stdout.
|
||||
*
|
||||
@ -961,7 +972,7 @@ public class GdbManagerImpl implements GdbManager {
|
||||
event(() -> listenersEvent.fire.inferiorSelected(cur, evt.getCause()),
|
||||
"groupRemoved-sel");
|
||||
// Also cause GDB to generate thread selection events, if applicable
|
||||
setActiveInferior(cur);
|
||||
setActiveInferior(cur, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1466,17 +1477,29 @@ public class GdbManagerImpl implements GdbManager {
|
||||
checkStarted();
|
||||
Msg.info(this, "Interrupting");
|
||||
if (cliThread != null) {
|
||||
OutputStream os = cliThread.pty.getMaster().getOutputStream();
|
||||
OutputStream os = cliThread.pty.getParent().getOutputStream();
|
||||
os.write(3);
|
||||
os.flush();
|
||||
}
|
||||
if (mi2Thread != null) {
|
||||
OutputStream os = mi2Thread.pty.getMaster().getOutputStream();
|
||||
OutputStream os = mi2Thread.pty.getParent().getOutputStream();
|
||||
os.write(3);
|
||||
os.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Internal
|
||||
public void injectInput(Interpreter interpreter, String input) {
|
||||
PrintWriter writer = getWriter(interpreter);
|
||||
writer.print(input);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
@Internal
|
||||
public void synthesizeConsoleOut(Channel channel, String line) {
|
||||
listenersConsoleOutput.fire.output(channel, line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized GdbState getState() {
|
||||
return state.get();
|
||||
@ -1527,10 +1550,11 @@ public class GdbManagerImpl implements GdbManager {
|
||||
* This issues a command to GDB to change its focus. It is not just a manager concept.
|
||||
*
|
||||
* @param inferior the inferior to select
|
||||
* @param internal true to prevent announcement of the change
|
||||
* @return a future that completes when GDB has executed the command
|
||||
*/
|
||||
CompletableFuture<Void> setActiveInferior(GdbInferior inferior) {
|
||||
return execute(new GdbInferiorSelectCommand(this, inferior.getId()));
|
||||
CompletableFuture<Void> setActiveInferior(GdbInferior inferior, boolean internal) {
|
||||
return execute(new GdbInferiorSelectCommand(this, inferior.getId(), internal));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1588,8 +1612,8 @@ public class GdbManagerImpl implements GdbManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMi2PtyName() {
|
||||
return mi2Thread.pty.getSlave().getFile().getAbsolutePath();
|
||||
public String getMi2PtyName() throws IOException {
|
||||
return mi2Thread.pty.getChild().nullSession();
|
||||
}
|
||||
|
||||
public boolean hasCli() {
|
||||
|
@ -104,8 +104,9 @@ public class GdbStackFrameImpl implements GdbStackFrame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setActive() {
|
||||
return manager.execute(new GdbSetActiveThreadCommand(manager, thread.getId(), level));
|
||||
public CompletableFuture<Void> setActive(boolean internal) {
|
||||
return manager
|
||||
.execute(new GdbSetActiveThreadCommand(manager, thread.getId(), level, internal));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -128,7 +128,7 @@ public class GdbThreadImpl implements GdbThread {
|
||||
protected <T> CompletableFuture<T> execute(AbstractGdbCommand<T> cmd) {
|
||||
switch (cmd.getInterpreter()) {
|
||||
case CLI:
|
||||
return setActive().thenCombine(manager.execute(cmd), (__, v) -> v);
|
||||
return setActive(true).thenCombine(manager.execute(cmd), (__, v) -> v);
|
||||
case MI2:
|
||||
return manager.execute(cmd);
|
||||
default:
|
||||
@ -137,9 +137,9 @@ public class GdbThreadImpl implements GdbThread {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setActive() {
|
||||
public CompletableFuture<Void> setActive(boolean internal) {
|
||||
// Bypass the select-me-first logic
|
||||
return manager.execute(new GdbSetActiveThreadCommand(manager, id, null));
|
||||
return manager.execute(new GdbSetActiveThreadCommand(manager, id, null, internal));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -106,4 +106,9 @@ public abstract class AbstractGdbCommand<T> implements GdbCommand<T> {
|
||||
public Integer impliesCurrentFrameId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFocusInternallyDriven() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -18,9 +18,11 @@ package agent.gdb.manager.impl.cmd;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
|
||||
import agent.gdb.manager.GdbManager;
|
||||
import agent.gdb.manager.GdbManager.Channel;
|
||||
import agent.gdb.manager.evt.AbstractGdbCompletedCommandEvent;
|
||||
import agent.gdb.manager.evt.GdbConsoleOutputEvent;
|
||||
import agent.gdb.manager.impl.*;
|
||||
import agent.gdb.manager.impl.GdbManagerImpl.Interpreter;
|
||||
|
||||
/**
|
||||
* Implementation of {@link GdbManager#console(String)} and similar
|
||||
@ -40,22 +42,62 @@ public class GdbConsoleExecCommand extends AbstractGdbCommandWithThreadAndFrameI
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: I think there should be a separate command for arbitrary CLI input. I'm not sure yet
|
||||
* whether it should wait in the queue or just be sent immediately.
|
||||
*/
|
||||
@Override
|
||||
public Interpreter getInterpreter() {
|
||||
/*if (to == Output.CONSOLE && manager.hasCli() && threadId == null && frameId == null) {
|
||||
return Interpreter.CLI;
|
||||
}*/
|
||||
return Interpreter.MI2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(String threadPart, String framePart) {
|
||||
return "-interpreter-exec" + threadPart + framePart + " console \"" +
|
||||
StringEscapeUtils.escapeJava(command) + "\"";
|
||||
switch (getInterpreter()) {
|
||||
case CLI:
|
||||
return command;
|
||||
case MI2:
|
||||
return "-interpreter-exec" + threadPart + framePart + " console \"" +
|
||||
StringEscapeUtils.escapeJava(command) + "\"";
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(GdbEvent<?> evt, GdbPendingCommand<?> pending) {
|
||||
if (getInterpreter() == Interpreter.CLI) {
|
||||
// At the very least, I should expect to see the (gdb) prompt.
|
||||
if (evt instanceof GdbConsoleOutputEvent) {
|
||||
GdbConsoleOutputEvent out = (GdbConsoleOutputEvent) evt;
|
||||
if (out.getInterpreter() == Interpreter.CLI) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// MI2
|
||||
if (evt instanceof AbstractGdbCompletedCommandEvent) {
|
||||
pending.claim(evt);
|
||||
return true;
|
||||
}
|
||||
else if (evt instanceof GdbConsoleOutputEvent && to == Output.CAPTURE) {
|
||||
else if (evt instanceof GdbConsoleOutputEvent) {
|
||||
GdbConsoleOutputEvent out = (GdbConsoleOutputEvent) evt;
|
||||
if (out.getInterpreter() == getInterpreter()) {
|
||||
pending.steal(evt);
|
||||
// This is not a great check...
|
||||
if (out.getInterpreter() == Interpreter.MI2 && ">".equals(out.getOutput().trim()) &&
|
||||
!command.trim().startsWith("ec")) {
|
||||
manager.injectInput(Interpreter.MI2, "end\n");
|
||||
manager.synthesizeConsoleOut(Channel.STDERR,
|
||||
"Ghidra GDB Agent: Multi-line / follow-up input is not currently supported. " +
|
||||
"I just typed 'end' for you.\n");
|
||||
}
|
||||
if (to == Output.CAPTURE) {
|
||||
if (out.getInterpreter() == getInterpreter()) {
|
||||
pending.steal(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -63,6 +105,10 @@ public class GdbConsoleExecCommand extends AbstractGdbCommandWithThreadAndFrameI
|
||||
|
||||
@Override
|
||||
public String complete(GdbPendingCommand<?> pending) {
|
||||
if (getInterpreter() == Interpreter.CLI) {
|
||||
return null;
|
||||
}
|
||||
// MI2
|
||||
pending.checkCompletion(AbstractGdbCompletedCommandEvent.class);
|
||||
|
||||
if (to == Output.CONSOLE) {
|
||||
@ -78,4 +124,9 @@ public class GdbConsoleExecCommand extends AbstractGdbCommandWithThreadAndFrameI
|
||||
public Output getOutputTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFocusInternallyDriven() {
|
||||
return to == Output.CAPTURE;
|
||||
}
|
||||
}
|
||||
|
@ -20,10 +20,12 @@ import agent.gdb.manager.impl.*;
|
||||
|
||||
public class GdbInferiorSelectCommand extends AbstractGdbCommand<Void> {
|
||||
private final int id;
|
||||
private final boolean internal;
|
||||
|
||||
public GdbInferiorSelectCommand(GdbManagerImpl manager, int id) {
|
||||
public GdbInferiorSelectCommand(GdbManagerImpl manager, int id, boolean internal) {
|
||||
super(manager);
|
||||
this.id = id;
|
||||
this.internal = internal;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -58,4 +60,9 @@ public class GdbInferiorSelectCommand extends AbstractGdbCommand<Void> {
|
||||
pending.checkCompletion(GdbCommandDoneEvent.class);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFocusInternallyDriven() {
|
||||
return internal;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import agent.gdb.manager.impl.*;
|
||||
import agent.gdb.manager.parsing.GdbMiParser.GdbMiFieldList;
|
||||
|
||||
public class GdbSetActiveThreadCommand extends AbstractGdbCommandWithThreadAndFrameId<Void> {
|
||||
private final boolean internal;
|
||||
|
||||
/**
|
||||
* Select the given thread and frame level
|
||||
*
|
||||
@ -29,9 +31,12 @@ public class GdbSetActiveThreadCommand extends AbstractGdbCommandWithThreadAndFr
|
||||
* @param manager the manager to execute the command
|
||||
* @param threadId the desired thread Id
|
||||
* @param frameId the desired frame level
|
||||
* @param internal true to prevent announcement of the change
|
||||
*/
|
||||
public GdbSetActiveThreadCommand(GdbManagerImpl manager, int threadId, Integer frameId) {
|
||||
public GdbSetActiveThreadCommand(GdbManagerImpl manager, int threadId, Integer frameId,
|
||||
boolean internal) {
|
||||
super(manager, threadId, frameId);
|
||||
this.internal = internal;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -73,4 +78,9 @@ public class GdbSetActiveThreadCommand extends AbstractGdbCommandWithThreadAndFr
|
||||
manager.doThreadSelected(thread, frame, done.getCause());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFocusInternallyDriven() {
|
||||
return internal;
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import agent.gdb.manager.*;
|
||||
import agent.gdb.manager.impl.cmd.GdbCommandError;
|
||||
import agent.gdb.pty.PtyFactory;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.DebuggerModelClosedReason;
|
||||
import ghidra.dbg.agent.AbstractDebuggerObjectModel;
|
||||
@ -67,8 +68,8 @@ public class GdbModelImpl extends AbstractDebuggerObjectModel {
|
||||
|
||||
protected Map<Object, TargetObject> objectMap = new HashMap<>();
|
||||
|
||||
public GdbModelImpl() {
|
||||
this.gdb = GdbManager.newInstance();
|
||||
public GdbModelImpl(PtyFactory ptyFactory) {
|
||||
this.gdb = GdbManager.newInstance(ptyFactory);
|
||||
this.session = new GdbModelTargetSession(this, ROOT_SCHEMA);
|
||||
|
||||
this.completedSession = CompletableFuture.completedFuture(session);
|
||||
@ -82,6 +83,11 @@ public class GdbModelImpl extends AbstractDebuggerObjectModel {
|
||||
return ROOT_SCHEMA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBrief() {
|
||||
return "GDB@" + Integer.toHexString(System.identityHashCode(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSpace getAddressSpace(String name) {
|
||||
if (!SPACE_NAME.equals(name)) {
|
||||
|
@ -375,7 +375,7 @@ public class GdbModelTargetInferior
|
||||
@Override
|
||||
@Internal
|
||||
public CompletableFuture<Void> setActive() {
|
||||
return impl.gateFuture(inferior.setActive());
|
||||
return impl.gateFuture(inferior.setActive(false));
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = EXIT_CODE_ATTRIBUTE_NAME)
|
||||
|
@ -21,8 +21,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.gdb.manager.*;
|
||||
import agent.gdb.manager.impl.*;
|
||||
import agent.gdb.manager.impl.cmd.GdbConsoleExecCommand;
|
||||
import agent.gdb.manager.impl.cmd.GdbConsoleExecCommand.Output;
|
||||
import agent.gdb.manager.impl.cmd.GdbStateChangeRecord;
|
||||
import agent.gdb.manager.reason.GdbReason;
|
||||
import ghidra.async.AsyncUtils;
|
||||
@ -34,6 +32,14 @@ import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* TODO: We should probably expose the raw CLI (if available) via TargetConsole, and perhaps re-work
|
||||
* the UI to use it when available. This could more generally solve the multi-line input thing, and
|
||||
* provide a distinction between API access (where {@link TargetInterpreter} makes more sense), and
|
||||
* I/O access (where {@link TargetConsole}) makes more sense. I'm hoping this will also allow the
|
||||
* CLI to prompt the user when appropriate, e.g., on {@code quit} when an inferior is active. NOTE:
|
||||
* Probably should not expose raw MI2 via TargetConsole
|
||||
*/
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "Session",
|
||||
elements = {
|
||||
@ -150,6 +156,13 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
||||
// Otherwise, we'll presumably get the =thread-selected event
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: This check should be done in the manager? This "internal" concept is either a manager
|
||||
* concept or a model concept. Right now, it breaches the interface.
|
||||
*
|
||||
* @param cause the cause to examine
|
||||
* @return true if internal
|
||||
*/
|
||||
protected boolean isFocusInternallyDriven(GdbCause cause) {
|
||||
if (cause == null || cause == GdbCause.Causes.UNCLAIMED) {
|
||||
return false;
|
||||
@ -160,13 +173,7 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
||||
if (cause instanceof GdbPendingCommand<?>) {
|
||||
GdbPendingCommand<?> pcmd = (GdbPendingCommand<?>) cause;
|
||||
GdbCommand<?> cmd = pcmd.getCommand();
|
||||
if (cmd instanceof GdbConsoleExecCommand) {
|
||||
GdbConsoleExecCommand exec = (GdbConsoleExecCommand) cmd;
|
||||
if (exec.getOutputTo() == Output.CAPTURE) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return cmd.isFocusInternallyDriven();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -323,7 +330,7 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
||||
if (impl.gdb.getKnownThreads().get(thread.getId()) != thread) {
|
||||
return;
|
||||
}
|
||||
thread.setActive().exceptionally(ex -> {
|
||||
thread.setActive(true).exceptionally(ex -> {
|
||||
impl.reportError(this, "Could not restore event thread", ex);
|
||||
return null;
|
||||
});
|
||||
|
@ -115,7 +115,7 @@ public class GdbModelTargetStackFrame extends DefaultTargetObject<TargetObject,
|
||||
@Override
|
||||
@Internal
|
||||
public CompletableFuture<Void> setActive() {
|
||||
return impl.gateFuture(frame.setActive());
|
||||
return impl.gateFuture(frame.setActive(false));
|
||||
}
|
||||
|
||||
@TargetAttributeType(name = FUNC_ATTRIBUTE_NAME)
|
||||
|
@ -226,7 +226,7 @@ public class GdbModelTargetThread
|
||||
@Override
|
||||
@Internal
|
||||
public CompletableFuture<Void> setActive() {
|
||||
return impl.gateFuture(thread.setActive());
|
||||
return impl.gateFuture(thread.setActive(false));
|
||||
}
|
||||
|
||||
public GdbModelTargetBreakpointLocation breakpointHit(GdbBreakpointHitReason reason) {
|
||||
|
@ -0,0 +1,100 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.gdb.pty;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A pseudo-terminal
|
||||
*
|
||||
* <p>
|
||||
* A pseudo-terminal is essentially a two way pipe where one end acts as the parent, and the other
|
||||
* acts as the child. The process opening the pseudo-terminal is given a handle to both ends. The
|
||||
* child end is generally given to a subprocess, possibly designating the pty as the controlling tty
|
||||
* of a new session. This scheme is how, for example, an SSH daemon starts a new login shell. The
|
||||
* shell is given the child end, and the parent end is presented to the SSH client.
|
||||
*
|
||||
* <p>
|
||||
* This is more powerful than controlling a process via standard in and standard out. 1) Some
|
||||
* programs detect whether or not stdin/out/err refer to the controlling tty. For example, a program
|
||||
* should avoid prompting for passwords unless stdin is the controlling tty. Using a pty can provide
|
||||
* a controlling tty that is not necessarily controlled by a user. 2) Terminals have other
|
||||
* properties and can, e.g., send signals to the foreground process group (job) by sending special
|
||||
* characters. Normal characters are passed to the child, but special characters may be interpreted
|
||||
* by the terminal's <em>line discipline</em>. A rather common case is to send Ctrl-C (character
|
||||
* 003). Using stdin, the subprocess simply reads 003. With a properly-configured pty and session,
|
||||
* the subprocess is interrupted (sent SIGINT) instead.
|
||||
*
|
||||
* <p>
|
||||
* This class opens a pseudo-terminal and presents both ends as individual handles. The parent end
|
||||
* simply provides an input and output stream. These are typical byte-oriented streams, except that
|
||||
* the data passes through the pty, subject to interpretation by the OS kernel. On Linux, this means
|
||||
* the pty will apply the configured line discipline. Consult the host OS documentation for special
|
||||
* character sequences.
|
||||
*
|
||||
* <p>
|
||||
* The child end also provides the input and output streams, but it is uncommon to use them from the
|
||||
* same process. More likely, subprocess is launched in a new session, configuring the child as the
|
||||
* controlling terminal. Thus, the child handle provides methods for obtaining the child pty file
|
||||
* name and/or spawning a new session. Once spawned, the parent end is used to control the session.
|
||||
*
|
||||
* <p>
|
||||
* Example:
|
||||
*
|
||||
* <pre>
|
||||
* Pty pty = factory.openpty();
|
||||
* pty.getChild().session("bash");
|
||||
*
|
||||
* PrintWriter writer = new PrintWriter(pty.getParent().getOutputStream());
|
||||
* writer.println("echo test");
|
||||
* BufferedReader reader =
|
||||
* new BufferedReader(new InputStreamReader(pty.getParent().getInputStream()));
|
||||
* System.out.println(reader.readLine());
|
||||
* System.out.println(reader.readLine());
|
||||
*
|
||||
* pty.close();
|
||||
* </pre>
|
||||
*/
|
||||
public interface Pty extends AutoCloseable {
|
||||
|
||||
/**
|
||||
* Get a handle to the parent side of the pty
|
||||
*
|
||||
* @return the parent handle
|
||||
*/
|
||||
PtyParent getParent();
|
||||
|
||||
/**
|
||||
* Get a handle to the child side of the pty
|
||||
*
|
||||
* @return the child handle
|
||||
*/
|
||||
PtyChild getChild();
|
||||
|
||||
/**
|
||||
* Closes both ends of the pty
|
||||
*
|
||||
* <p>
|
||||
* This only closes this process's handles to the pty. For the parent end, this should be the
|
||||
* only process with a handle. The child end may be opened by any number of other processes.
|
||||
* More than likely, however, those processes will terminate once the parent end is closed,
|
||||
* since reads or writes on the child will produce EOF or an error.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
void close() throws IOException;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user