Merge remote-tracking branch 'origin/debugger'

This commit is contained in:
ghidra1 2021-05-14 10:46:36 -04:00
commit 191f6414fa
90 changed files with 3292 additions and 1330 deletions

View file

@ -18,6 +18,7 @@ package agent.dbgeng.dbgeng;
/**
* Information about a thread.
*
* <p>
* The fields correspond to parameters taken by {@code CreateThread} of
* {@code IDebugEventCallbacks}. They also appear as a subset of parameters taken by
* {@code CreateProcess} of {@code IDebugEventCallbacks}.
@ -32,4 +33,11 @@ public class DebugThreadInfo {
this.dataOffset = dataOffset;
this.startOffset = startOffset;
}
@Override
public String toString() {
return String.format("<%s@%08x handle=0x%04x,dataOffset=0x%08x,startOffset=0x%08x>",
getClass().getSimpleName(), System.identityHashCode(this),
handle, dataOffset, startOffset);
}
}

View file

@ -40,6 +40,8 @@ import ghidra.comm.util.BitmaskSet;
import ghidra.util.Msg;
public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCallbacks {
private static final HRESULT ERROR_RESULT = new HRESULT(WinError.E_UNEXPECTED);
private final DebugClientInternal client;
private final DebugEventCallbacks cb;
private ListenerIDebugEventCallbacks listener;
@ -103,12 +105,12 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
try {
DebugBreakpoint bpt = DebugBreakpointInternal
.tryPreferredInterfaces(client.getControlInternal(), Bp::QueryInterface);
cb.breakpoint(bpt);
return WinError.S_OK;
DebugStatus status = cb.breakpoint(bpt);
return new HRESULT(status.ordinal());
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -125,12 +127,12 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
Exception.ExceptionFlags.intValue(), Exception.ExceptionRecord.longValue(),
Exception.ExceptionAddress.longValue(), information);
boolean firstChance = FirstChance.intValue() != 0;
cb.exception(exc, firstChance);
return WinError.S_OK;
DebugStatus status = cb.exception(exc, firstChance);
return new HRESULT(status.ordinal());
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -143,7 +145,7 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -155,7 +157,7 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -177,7 +179,7 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -189,7 +191,7 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -211,7 +213,7 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -223,7 +225,7 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -235,7 +237,7 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -248,7 +250,7 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -262,7 +264,7 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -276,7 +278,7 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -290,7 +292,7 @@ public class WrapCallbackIDebugEventCallbacks implements CallbackIDebugEventCall
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
}

View file

@ -41,6 +41,8 @@ import ghidra.comm.util.BitmaskSet;
import ghidra.util.Msg;
public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEventCallbacksWide {
private static final HRESULT ERROR_RESULT = new HRESULT(WinError.E_UNEXPECTED);
private final DebugClientInternal client;
private final DebugEventCallbacks cb;
private ListenerIDebugEventCallbacksWide listener;
@ -96,7 +98,7 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -105,12 +107,12 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
try {
DebugBreakpoint bpt = DebugBreakpointInternal
.tryPreferredInterfaces(client.getControlInternal(), Bp::QueryInterface);
cb.breakpoint(bpt);
return WinError.S_OK;
DebugStatus status = cb.breakpoint(bpt);
return new HRESULT(status.ordinal());
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -127,12 +129,12 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
Exception.ExceptionFlags.intValue(), Exception.ExceptionRecord.longValue(),
Exception.ExceptionAddress.longValue(), information);
boolean firstChance = FirstChance.intValue() != 0;
cb.exception(exc, firstChance);
return WinError.S_OK;
DebugStatus status = cb.exception(exc, firstChance);
return new HRESULT(status.ordinal());
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -145,7 +147,7 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -157,7 +159,7 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -177,7 +179,7 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -189,7 +191,7 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -210,7 +212,7 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -222,7 +224,7 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -234,7 +236,7 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -247,7 +249,7 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -261,7 +263,7 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -275,7 +277,7 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
@ -289,7 +291,7 @@ public class WrapCallbackIDebugEventCallbacksWide implements CallbackIDebugEvent
}
catch (Throwable e) {
Msg.error(this, "Error during callback", e);
return new HRESULT(WinError.E_UNEXPECTED);
return ERROR_RESULT;
}
}
}

View file

@ -38,6 +38,10 @@ public class UnknownWithUtils extends Unknown {
}
protected HRESULT _invokeHR(VTableIndex idx, Object... args) {
/*if (idx != IDebugClient.VTIndices.DISPATCH_CALLBACKS &&
idx != IDebugControl.VTIndices.GET_EXECUTION_STATUS) {
Msg.info(this, Thread.currentThread() + " invoked " + idx + Arrays.asList(args));
}*/
return (HRESULT) this._invokeNativeObject(idx.getIndex(), args, HRESULT.class);
}
}

View file

@ -192,6 +192,16 @@ public interface DbgManager extends AutoCloseable, DbgBreakpointInsertions {
*/
Map<Long, DbgBreakpointInfo> getKnownBreakpoints();
/**
* Get all memory regions known to the manager
*
* This does not ask dbgeng to list its regions. Rather it returns a read-only view of the
* manager's understanding of the current ememory based on its tracking of dbgeng events.
*
* @return a map of dbgeng-assigned breakpoint IDs to corresponding breakpoint information
*/
Map<Long, DbgModuleMemory> getKnownMemoryRegions();
/**
* Send an interrupt to dbgeng regardless of other queued commands
*
@ -242,6 +252,21 @@ public interface DbgManager extends AutoCloseable, DbgBreakpointInsertions {
*/
CompletableFuture<Void> removeSession(DbgSession session);
/**
* Add a memory region
*
* @return a future which completes with the handle to the new process
*/
CompletableFuture<Void> addMemory(DbgModuleMemory region);
/**
* Remove a memory region
*
* @param regionId the region to remove
* @return a future which completes then dbgeng has executed the command
*/
CompletableFuture<Void> removeMemory(Long regionId);
/**
* Execute an arbitrary CLI command, printing output to the CLI console
*

View file

@ -21,6 +21,8 @@ public interface DbgModuleMemory {
String getName();
Long getId();
long getVmaStart();
long getVmaEnd();

View file

@ -85,6 +85,10 @@ public class DbgBreakpointInfo {
offset, expression);
}
public int getId() {
return bpt.getId();
}
@Override
public String toString() {
return Integer.toHexString(bpt.getId());

View file

@ -15,8 +15,8 @@
*/
package agent.dbgeng.manager.cmd;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.Map.Entry;
import com.sun.jna.platform.win32.COM.COMException;
@ -27,6 +27,7 @@ import agent.dbgeng.dbgeng.DebugModule.DebugModuleName;
import agent.dbgeng.manager.DbgModuleMemory;
import agent.dbgeng.manager.impl.DbgManagerImpl;
import agent.dbgeng.manager.impl.DbgModuleMemoryImpl;
import ghidra.util.Msg;
public class DbgListMemoryRegionsCommand extends AbstractDbgCommand<List<DbgModuleMemory>> {
@ -38,6 +39,23 @@ public class DbgListMemoryRegionsCommand extends AbstractDbgCommand<List<DbgModu
@Override
public List<DbgModuleMemory> complete(DbgPendingCommand<?> pending) {
Map<Long, DbgModuleMemory> memory = manager.getKnownMemoryRegions();
for (DbgModuleMemory region : memoryRegions) {
if (memory.containsValue(region)) {
continue; // Do nothing, we're in sync
}
// Need to create the thread as if we receive =thread-created
if (!memory.isEmpty()) {
Msg.warn(this, "Resync: Was missing memory: " + region.getId());
}
manager.addMemory(region);
}
for (Entry<Long, DbgModuleMemory> entry : memory.entrySet()) {
if (memoryRegions.contains(entry.getValue())) {
continue; // Do nothing, we're in sync
}
manager.removeMemory(entry.getKey());
}
return memoryRegions;
}
@ -83,10 +101,10 @@ public class DbgListMemoryRegionsCommand extends AbstractDbgCommand<List<DbgModu
isWrite |= protect.isWrite();
isExec |= protect.isExecute();
}
DbgModuleMemoryImpl section =
DbgModuleMemoryImpl region =
new DbgModuleMemoryImpl(Long.toHexString(vmaStart), vmaStart, vmaEnd,
info.allocationBase, ap, ip, info.state, type, isRead, isWrite, isExec);
memoryRegions.add(section);
memoryRegions.add(region);
}
}

View file

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

View file

@ -16,6 +16,7 @@
package agent.dbgeng.manager.cmd;
import agent.dbgeng.dbgeng.DebugProcessId;
import agent.dbgeng.dbgeng.DebugSystemObjects;
import agent.dbgeng.manager.DbgProcess;
import agent.dbgeng.manager.impl.DbgManagerImpl;
@ -39,7 +40,11 @@ public class DbgSetActiveProcessCommand extends AbstractDbgCommand<Void> {
if (process != null) {
DebugProcessId id = process.getId();
if (id != null) {
manager.getSystemObjects().setCurrentProcessId(id);
DebugSystemObjects so = manager.getSystemObjects();
DebugProcessId currentProcessId = so.getCurrentProcessId();
if (id.id != currentProcessId.id) {
so.setCurrentProcessId(id);
}
}
}
}

View file

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

View file

@ -15,8 +15,7 @@
*/
package agent.dbgeng.manager.cmd;
import agent.dbgeng.dbgeng.DebugControl;
import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.dbgeng.*;
import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.manager.impl.DbgManagerImpl;
@ -56,7 +55,9 @@ public class DbgThreadHoldCommand extends AbstractDbgCommand<Void> {
public void invoke() {
DebugThreadId id = thread.getId();
if (id != null) {
manager.getSystemObjects().setCurrentThreadId(id);
DebugSystemObjects so = manager.getSystemObjects();
DebugThreadId previous = so.getCurrentThreadId();
so.setCurrentThreadId(id);
if (!manager.isKernelMode()) {
DebugControl control = manager.getControl();
if (preferFreeze) {
@ -68,6 +69,7 @@ public class DbgThreadHoldCommand extends AbstractDbgCommand<Void> {
set ? SUSPEND_CURRENT_THREAD_COMMAND : RESUME_CURRENT_THREAD_COMMAND);
}
}
so.setCurrentThreadId(previous);
}
}
}

View file

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

View file

@ -42,6 +42,8 @@ import agent.dbgeng.manager.breakpoint.DbgBreakpointType;
import agent.dbgeng.manager.cmd.*;
import agent.dbgeng.manager.evt.*;
import agent.dbgeng.model.iface1.*;
import agent.dbgeng.model.iface2.DbgModelTargetObject;
import agent.dbgeng.model.iface2.DbgModelTargetThread;
import ghidra.async.*;
import ghidra.comm.util.BitmaskSet;
import ghidra.dbg.target.TargetObject;
@ -92,6 +94,10 @@ public class DbgManagerImpl implements DbgManager {
private final Map<Long, DbgBreakpointInfo> unmodifiableBreakpoints =
Collections.unmodifiableMap(breakpoints);
private final NavigableMap<Long, DbgModuleMemory> memory = new TreeMap<>();
private final NavigableMap<Long, DbgModuleMemory> unmodifiableMemory =
Collections.unmodifiableNavigableMap(memory);
protected final AsyncReference<DbgState, DbgCause> state =
new AsyncReference<>(DbgState.NOT_STARTED);
private final HandlerMap<DbgEvent<?>, Void, DebugStatus> handlerMap = new HandlerMap<>();
@ -109,6 +115,7 @@ public class DbgManagerImpl implements DbgManager {
private DbgThread eventThread;
private volatile boolean waiting = false;
private boolean kernelMode = false;
private boolean ignoreEventThread = false;
private CompletableFuture<String> continuation;
private long processCount = 0;
@ -267,6 +274,11 @@ public class DbgManagerImpl implements DbgManager {
return unmodifiableBreakpoints;
}
@Override
public Map<Long, DbgModuleMemory> getKnownMemoryRegions() {
return unmodifiableMemory;
}
private DbgBreakpointInfo addKnownBreakpoint(DbgBreakpointInfo bkpt, boolean expectExisting) {
DbgBreakpointInfo old = breakpoints.put(bkpt.getNumber(), bkpt);
if (expectExisting && old == null) {
@ -606,8 +618,8 @@ public class DbgManagerImpl implements DbgManager {
DebugControl control = dbgeng.getControl();
int execType = WinNTExtra.Machine.IMAGE_FILE_MACHINE_AMD64.val;
try {
so.setCurrentProcessId(epid);
so.setCurrentThreadId(etid);
//so.setCurrentProcessId(epid);
//so.setCurrentThreadId(etid);
execType = control.getExecutingProcessorType();
}
catch (Exception e) {
@ -652,6 +664,7 @@ public class DbgManagerImpl implements DbgManager {
DebugBreakpoint bp = evt.getInfo();
DbgBreakpointInfo info = new DbgBreakpointInfo(bp, getEventProcess(), getEventThread());
getEventListeners().fire.threadSelected(eventThread, null, evt.getCause());
getEventListeners().fire.breakpointHit(info, evt.getCause());
String key = Integer.toHexString(bp.getId());
@ -670,6 +683,7 @@ public class DbgManagerImpl implements DbgManager {
*/
protected DebugStatus processException(DbgExceptionEvent evt, Void v) {
DebugThreadId eventId = updateState();
getEventListeners().fire.threadSelected(eventThread, null, evt.getCause());
DebugExceptionRecord64 info = evt.getInfo();
String key = Integer.toHexString(info.code);
@ -764,16 +778,17 @@ public class DbgManagerImpl implements DbgManager {
DebugProcessInfo info = evt.getInfo();
long handle = info.handle;
DebugProcessId id = so.getProcessIdByHandle(handle);
so.setCurrentProcessId(id);
//so.setCurrentProcessId(id);
int pid = so.getCurrentProcessSystemId();
DbgProcessImpl proc = getProcessComputeIfAbsent(id, pid);
getEventListeners().fire.processAdded(proc, DbgCause.Causes.UNCLAIMED);
getEventListeners().fire.processAdded(proc, evt.getCause());
getEventListeners().fire.processSelected(proc, evt.getCause());
handle = info.initialThreadInfo.handle;
DebugThreadId idt = so.getThreadIdByHandle(handle);
int tid = so.getCurrentThreadSystemId();
DbgThreadImpl thread = getThreadComputeIfAbsent(idt, proc, tid);
getEventListeners().fire.threadCreated(thread, evt.getCause());
getEventListeners().fire.threadSelected(thread, null, evt.getCause());
//proc.moduleLoaded(info.moduleInfo);
@ -1041,24 +1056,25 @@ public class DbgManagerImpl implements DbgManager {
if (bptId == DbgEngUtil.DEBUG_ANY_ID.longValue()) {
changeBreakpoints();
}
DebugBreakpoint bpt = getControl().getBreakpointById((int) bptId);
if (bpt == null) {
doBreakpointDeleted(bptId, evt.getCause());
return;
}
DbgBreakpointInfo knownBreakpoint = getKnownBreakpoint(bptId);
if (knownBreakpoint == null) {
breakpointInfo = new DbgBreakpointInfo(bpt, getCurrentProcess());
if (breakpointInfo.getOffset() != null) {
doBreakpointCreated(breakpointInfo, evt.getCause());
else {
DebugBreakpoint bpt = getControl().getBreakpointById((int) bptId);
if (bpt == null && bptId != DbgEngUtil.DEBUG_ANY_ID.longValue()) {
doBreakpointDeleted(bptId, evt.getCause());
return;
}
return;
DbgBreakpointInfo knownBreakpoint = getKnownBreakpoint(bptId);
if (knownBreakpoint == null) {
breakpointInfo = new DbgBreakpointInfo(bpt, getCurrentProcess());
if (breakpointInfo.getOffset() != null) {
doBreakpointCreated(breakpointInfo, evt.getCause());
}
return;
}
breakpointInfo = knownBreakpoint;
breakpointInfo.setBreakpoint(bpt);
doBreakpointModified(breakpointInfo, evt.getCause());
}
breakpointInfo = knownBreakpoint;
breakpointInfo.setBreakpoint(bpt);
}
doBreakpointModified(breakpointInfo, evt.getCause());
}
/**
@ -1274,6 +1290,18 @@ public class DbgManagerImpl implements DbgManager {
return execute(new DbgRemoveSessionCommand(this, session.getId()));
}
@Override
public CompletableFuture<Void> addMemory(DbgModuleMemory region) {
memory.put(region.getId(), region);
return AsyncUtils.NIL;
}
@Override
public CompletableFuture<Void> removeMemory(Long id) {
memory.remove(id);
return AsyncUtils.NIL;
}
@Override
public CompletableFuture<?> launch(List<String> args) {
return execute(new DbgLaunchProcessCommand(this, args));
@ -1492,6 +1520,7 @@ public class DbgManagerImpl implements DbgManager {
//System.err.println("EXIT");
waiting = false;
updateState();
getEventListeners().fire.threadSelected(eventThread, null, Causes.UNCLAIMED);
return CompletableFuture.completedFuture(null);
}
@ -1511,6 +1540,18 @@ public class DbgManagerImpl implements DbgManager {
return lastEventInformation;
}
public boolean shouldUpdate(TargetObject object) {
if (ignoreEventThread || !(object instanceof DbgModelTargetObject)) {
return true;
}
DbgModelTargetObject modelObject = (DbgModelTargetObject) object;
DbgModelTargetThread parentThread = modelObject.getParentThread();
if (parentThread == null) {
return true;
}
return parentThread.getThread().equals(eventThread);
}
public CompletableFuture<? extends Map<String, ?>> getRegisterMap(List<String> path) {
return null;
}

View file

@ -55,6 +55,11 @@ public class DbgModuleMemoryImpl implements DbgModuleMemory {
return index;
}
@Override
public Long getId() {
return vmaStart;
}
@Override
public long getVmaStart() {
return vmaStart;

View file

@ -43,9 +43,11 @@ public interface DbgModelTargetExecutionStateful
}
public default void setExecutionState(TargetExecutionState state, String reason) {
changeAttributes(List.of(), Map.of( //
STATE_ATTRIBUTE_NAME, state //
), reason);
if (isValid()) {
changeAttributes(List.of(), Map.of( //
STATE_ATTRIBUTE_NAME, state //
), reason);
}
}
}

View file

@ -31,4 +31,6 @@ public interface DbgModelTargetMemoryContainer extends DbgModelTargetObject, Tar
@Override
public CompletableFuture<Void> writeMemory(Address address, byte[] data);
public CompletableFuture<Void> requestElements(boolean refresh);
}

View file

@ -31,17 +31,26 @@ public interface DbgModelTargetModule extends DbgModelTargetObject, TargetModule
public default CompletableFuture<Void> init(Map<String, Object> map) {
AddressSpace space = getModel().getAddressSpace("ram");
return requestNativeAttributes().thenAccept(attrs -> {
if (!isValid()) {
return;
}
if (attrs != null) {
map.putAll(attrs);
TargetObject baseOffset2 = (TargetObject) attrs.get("BaseAddress");
TargetObject baseAttr = (TargetObject) attrs.get("BaseAddress");
TargetObject nameAttr = (TargetObject) attrs.get("Name");
TargetObject size = (TargetObject) attrs.get("Size");
String basestr = baseOffset2 == null ? "0"
: baseOffset2.getCachedAttribute(VALUE_ATTRIBUTE_NAME).toString();
String namestr = nameAttr == null ? ""
: nameAttr.getCachedAttribute(VALUE_ATTRIBUTE_NAME).toString();
String sizestr =
size == null ? "1" : size.getCachedAttribute(VALUE_ATTRIBUTE_NAME).toString();
TargetObject sizeAttr = (TargetObject) attrs.get("Size");
Object baseval = baseAttr == null ? null
: baseAttr.getCachedAttribute(VALUE_ATTRIBUTE_NAME);
Object nameval = nameAttr == null ? null
: nameAttr.getCachedAttribute(VALUE_ATTRIBUTE_NAME);
Object sizeval = sizeAttr == null ? null
: sizeAttr.getCachedAttribute(VALUE_ATTRIBUTE_NAME);
String basestr = baseval == null ? "0" : baseval.toString();
String namestr = nameval == null ? "" : nameval.toString();
String sizestr = sizeval == null ? "1" : sizeval.toString();
String shortnamestr = namestr;
int sep = shortnamestr.lastIndexOf('\\');
if (sep > 0 && sep < shortnamestr.length()) {

View file

@ -49,6 +49,8 @@ public interface DbgModelTargetProcess extends //
public DbgModelTargetModuleContainer getModules();
public DbgModelTargetMemoryContainer getMemory();
public void threadStateChangedSpecific(DbgThread thread, DbgState state);
public default DbgProcess getProcess() {

View file

@ -21,7 +21,7 @@ import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import agent.dbgeng.manager.*;
import agent.dbgeng.manager.DbgThread;
import agent.dbgeng.manager.impl.*;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
@ -36,10 +36,6 @@ public interface DbgModelTargetRegisterBank extends DbgModelTargetObject, Target
public DbgModelTargetRegister getTargetRegister(DbgRegister register);
public default void threadStateChangedSpecific(DbgState state, DbgReason reason) {
readRegistersNamed(getCachedElements().keySet());
}
@Override
public default CompletableFuture<? extends Map<String, byte[]>> readRegistersNamed(
Collection<String> names) {

View file

@ -59,6 +59,9 @@ public interface DbgModelTargetSession extends //
@Override
public default void consoleOutput(String output, int mask) {
if (!isValid()) {
return;
}
Channel chan = TargetConsole.Channel.STDOUT;
if (((mask & DebugOutputFlags.DEBUG_OUTPUT_ERROR.getValue()) //
== DebugOutputFlags.DEBUG_OUTPUT_ERROR.getValue()) || //
@ -69,6 +72,9 @@ public interface DbgModelTargetSession extends //
if (output.contains("loaded *kernel* extension dll for usermode")) {
return;
}
if (!isValid()) {
return;
}
getListeners().fire.consoleOutput(getProxy(), chan, output);
}

View file

@ -23,6 +23,7 @@ import agent.dbgeng.manager.DbgStackFrame;
import agent.dbgeng.manager.impl.DbgManagerImpl;
import agent.dbgeng.manager.impl.DbgThreadImpl;
import agent.dbgeng.model.iface1.DbgModelSelectableObject;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.program.model.address.Address;
@ -62,26 +63,38 @@ public interface DbgModelTargetStackFrame extends //
AddressSpace space = getModel().getAddressSpace("ram");
return requestNativeAttributes().thenCompose(attrs -> {
if (attrs == null) {
return CompletableFuture.completedFuture(null);
return AsyncUtils.NIL;
}
map.putAll(attrs);
DbgModelTargetObject attributes = (DbgModelTargetObject) attrs.get("Attributes");
if (attributes == null) {
return CompletableFuture.completedFuture(null);
return AsyncUtils.NIL;
}
return attributes.requestAugmentedAttributes().thenCompose(ax -> {
Map<String, ?> subattrs = attributes.getCachedAttributes();
if (subattrs == null) {
return CompletableFuture.completedFuture(null);
if (!isValid()) {
return AsyncUtils.NIL;
}
Map<String, ?> subattrs = attributes.getCachedAttributes();
DbgModelTargetObject frameNumber =
(DbgModelTargetObject) subattrs.get("FrameNumber");
if (frameNumber == null) {
return AsyncUtils.NIL;
}
return frameNumber.requestAugmentedAttributes().thenCompose(bx -> {
if (!isValid()) {
return AsyncUtils.NIL;
}
Object noval = frameNumber.getCachedAttribute(VALUE_ATTRIBUTE_NAME);
String nostr = noval.toString();
DbgModelTargetObject instructionOffset =
(DbgModelTargetObject) subattrs.get("InstructionOffset");
if (instructionOffset == null) {
return AsyncUtils.NIL;
}
return instructionOffset.requestAugmentedAttributes().thenAccept(cx -> {
if (!isValid()) {
return;
}
String oldval = (String) getCachedAttribute(DISPLAY_ATTRIBUTE_NAME);
Object pcval = instructionOffset.getCachedAttribute(VALUE_ATTRIBUTE_NAME);
String pcstr = pcval.toString();

View file

@ -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.*;
import ghidra.dbg.target.TargetThread;
import ghidra.dbg.util.PathUtils;
public interface DbgModelTargetThread extends //
@ -55,15 +55,6 @@ public interface DbgModelTargetThread extends //
}
}
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() {
DbgManagerImpl manager = getManager();
@ -75,4 +66,6 @@ public interface DbgModelTargetThread extends //
public String getExecutingProcessorType();
public void threadStateChangedSpecific(DbgState state, DbgReason reason);
}

View file

@ -28,11 +28,15 @@ import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.*;
@TargetObjectSchemaInfo(name = "BreakpointContainer", elements = { //
@TargetElementType(type = DbgModelTargetBreakpointSpecImpl.class) //
}, attributes = { //
@TargetAttributeType(type = Void.class) //
}, canonicalContainer = true)
@TargetObjectSchemaInfo(
name = "BreakpointContainer",
elements = { //
@TargetElementType(type = DbgModelTargetBreakpointSpecImpl.class) //
},
attributes = { //
@TargetAttributeType(type = Void.class) //
},
canonicalContainer = true)
public class DbgModelTargetBreakpointContainerImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetBreakpointContainer {

View file

@ -28,19 +28,30 @@ import ghidra.dbg.target.schema.TargetObjectSchemaInfo;
import ghidra.dbg.util.PathUtils;
import ghidra.util.datastruct.ListenerSet;
@TargetObjectSchemaInfo(name = "BreakpointSpec", attributes = { //
@TargetAttributeType( //
@TargetObjectSchemaInfo(
name = "BreakpointSpec",
attributes = { //
@TargetAttributeType( //
name = TargetBreakpointSpec.CONTAINER_ATTRIBUTE_NAME, //
type = DbgModelTargetBreakpointContainerImpl.class), //
@TargetAttributeType( //
@TargetAttributeType( //
name = TargetBreakpointLocation.SPEC_ATTRIBUTE_NAME, //
type = DbgModelTargetBreakpointSpecImpl.class), //
@TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_TYPE_ATTRIBUTE_NAME, type = String.class), //
@TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_DISP_ATTRIBUTE_NAME, type = String.class), //
@TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_PENDING_ATTRIBUTE_NAME, type = String.class), //
@TargetAttributeType(name = DbgModelTargetBreakpointSpecImpl.BPT_TIMES_ATTRIBUTE_NAME, type = Integer.class), //
@TargetAttributeType(type = Void.class) //
}, canonicalContainer = true)
@TargetAttributeType(
name = DbgModelTargetBreakpointSpecImpl.BPT_TYPE_ATTRIBUTE_NAME,
type = String.class), //
@TargetAttributeType(
name = DbgModelTargetBreakpointSpecImpl.BPT_DISP_ATTRIBUTE_NAME,
type = String.class), //
@TargetAttributeType(
name = DbgModelTargetBreakpointSpecImpl.BPT_PENDING_ATTRIBUTE_NAME,
type = String.class), //
@TargetAttributeType(
name = DbgModelTargetBreakpointSpecImpl.BPT_TIMES_ATTRIBUTE_NAME,
type = Integer.class), //
@TargetAttributeType(type = Void.class) //
},
canonicalContainer = true)
public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetBreakpointSpec {
@ -117,10 +128,9 @@ public class DbgModelTargetBreakpointSpecImpl extends DbgModelTargetObjectImpl
/**
* Update the enabled field
*
* This does not actually toggle the breakpoint. It just updates the field
* and calls the proper listeners. To actually toggle the breakpoint, use
* {@link #toggle(boolean)} instead, which if effective, should eventually
* cause this method to be called.
* This does not actually toggle the breakpoint. It just updates the field and calls the proper
* listeners. To actually toggle the breakpoint, use {@link #toggle(boolean)} instead, which if
* effective, should eventually cause this method to be called.
*
* @param enabled true if enabled, false if disabled
* @param reason a description of the cause (not really used, yet)

View file

@ -35,9 +35,13 @@ import ghidra.dbg.target.schema.*;
import ghidra.program.model.address.Address;
import ghidra.util.datastruct.WeakValueHashMap;
@TargetObjectSchemaInfo(name = "Memory", elements = {
@TargetElementType(type = DbgModelTargetMemoryRegionImpl.class) }, attributes = {
@TargetAttributeType(type = Void.class) }, canonicalContainer = true)
@TargetObjectSchemaInfo(
name = "Memory",
elements = {
@TargetElementType(type = DbgModelTargetMemoryRegionImpl.class) },
attributes = {
@TargetAttributeType(type = Void.class) },
canonicalContainer = true)
public class DbgModelTargetMemoryContainerImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetMemoryContainer {
@ -49,12 +53,13 @@ public class DbgModelTargetMemoryContainerImpl extends DbgModelTargetObjectImpl
public DbgModelTargetMemoryContainerImpl(DbgModelTargetProcess process) {
super(process.getModel(), process, "Memory", "MemoryContainer");
this.process = process;
requestElements(true);
}
@Override
public CompletableFuture<Void> requestElements(boolean refresh) {
DbgModelTargetProcess targetProcess = getParentProcess();
if (!targetProcess.getProcess().equals(getManager().getCurrentProcess())) {
if (!refresh || !targetProcess.getProcess().equals(getManager().getCurrentProcess())) {
return AsyncUtils.NIL;
}
return listMemory().thenAccept(byName -> {
@ -258,8 +263,4 @@ public class DbgModelTargetMemoryContainerImpl extends DbgModelTargetObjectImpl
return CompletableFuture.completedFuture(null);
}
@Override
protected void update() {
requestElements(true);
}
}

View file

@ -84,22 +84,12 @@ public class DbgModelTargetObjectImpl extends DefaultTargetObject<TargetObject,
public void onStopped() {
setAccessible(true);
update();
}
public void onExit() {
setAccessible(true);
}
protected void update() {
Map<String, ?> existingAttributes = getCachedAttributes();
Boolean autoupdate = (Boolean) existingAttributes.get("autoupdate");
if (autoupdate != null && autoupdate) {
requestAttributes(true);
requestElements(true);
}
}
protected void checkExited(DbgState state, DbgCause cause) {
TargetExecutionState exec = TargetExecutionState.INACTIVE;
switch (state) {

View file

@ -30,12 +30,16 @@ import ghidra.dbg.target.TargetConfigurable;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.*;
@TargetObjectSchemaInfo(name = "ProcessContainer", elements = { //
@TargetElementType(type = DbgModelTargetProcessImpl.class) //
}, attributes = { //
@TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class), //
@TargetAttributeType(type = Void.class) //
}, canonicalContainer = true)
@TargetObjectSchemaInfo(
name = "ProcessContainer",
elements = { //
@TargetElementType(type = DbgModelTargetProcessImpl.class) //
},
attributes = { //
@TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class), //
@TargetAttributeType(type = Void.class) //
},
canonicalContainer = true)
public class DbgModelTargetProcessContainerImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetProcessContainer, DbgModelTargetConfigurable {
@ -99,12 +103,20 @@ public class DbgModelTargetProcessContainerImpl extends DbgModelTargetObjectImpl
if (modules != null) {
modules.libraryLoaded(info.toString());
}
DbgModelTargetMemoryContainer memory = process.getMemory();
if (memory != null) {
memory.requestElements(false);
}
}
@Override
public void moduleUnloaded(DbgProcess proc, DebugModuleInfo info, DbgCause cause) {
DbgModelTargetProcess process = getTargetProcess(proc);
process.getModules().libraryUnloaded(info.toString());
DbgModelTargetMemoryContainer memory = process.getMemory();
if (memory != null) {
memory.requestElements(false);
}
}
@Override

View file

@ -29,13 +29,34 @@ import ghidra.dbg.target.TargetEventScope.TargetEventType;
import ghidra.dbg.target.schema.*;
import ghidra.dbg.util.PathUtils;
@TargetObjectSchemaInfo(name = "Process", elements = {
@TargetElementType(type = Void.class) }, attributes = {
@TargetAttributeType(name = "Debug", type = DbgModelTargetDebugContainerImpl.class, required = true, fixed = true),
@TargetAttributeType(name = "Memory", type = DbgModelTargetMemoryContainerImpl.class, required = true, fixed = true),
@TargetAttributeType(name = "Modules", type = DbgModelTargetModuleContainerImpl.class, required = true, fixed = true),
@TargetAttributeType(name = "Threads", type = DbgModelTargetThreadContainerImpl.class, required = true, fixed = true),
@TargetAttributeType(name = DbgModelTargetProcessImpl.EXIT_CODE_ATTRIBUTE_NAME, type = Long.class),
@TargetObjectSchemaInfo(
name = "Process",
elements = {
@TargetElementType(type = Void.class) },
attributes = {
@TargetAttributeType(
name = "Debug",
type = DbgModelTargetDebugContainerImpl.class,
required = true,
fixed = true),
@TargetAttributeType(
name = "Memory",
type = DbgModelTargetMemoryContainerImpl.class,
required = true,
fixed = true),
@TargetAttributeType(
name = "Modules",
type = DbgModelTargetModuleContainerImpl.class,
required = true,
fixed = true),
@TargetAttributeType(
name = "Threads",
type = DbgModelTargetThreadContainerImpl.class,
required = true,
fixed = true),
@TargetAttributeType(
name = DbgModelTargetProcessImpl.EXIT_CODE_ATTRIBUTE_NAME,
type = Long.class),
@TargetAttributeType(type = Void.class) })
public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetProcess {
@ -219,6 +240,11 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl
return modules;
}
@Override
public DbgModelTargetMemoryContainer getMemory() {
return memory;
}
@Override
public DbgProcess getProcess() {
return process;

View file

@ -31,11 +31,17 @@ import ghidra.dbg.target.schema.*;
import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode;
import ghidra.dbg.util.ConversionUtils;
@TargetObjectSchemaInfo(name = "RegisterContainer", elements = {
@TargetElementType(type = DbgModelTargetRegisterImpl.class) }, elementResync = ResyncMode.ONCE, //
attributes = {
@TargetAttributeType(name = TargetRegisterBank.DESCRIPTIONS_ATTRIBUTE_NAME, type = DbgModelTargetRegisterContainerImpl.class),
@TargetAttributeType(type = Void.class) }, canonicalContainer = true)
@TargetObjectSchemaInfo(
name = "RegisterContainer",
elements = {
@TargetElementType(type = DbgModelTargetRegisterImpl.class) },
elementResync = ResyncMode.ONCE, //
attributes = {
@TargetAttributeType(
name = TargetRegisterBank.DESCRIPTIONS_ATTRIBUTE_NAME,
type = DbgModelTargetRegisterContainerImpl.class),
@TargetAttributeType(type = Void.class) },
canonicalContainer = true)
public class DbgModelTargetRegisterContainerImpl extends DbgModelTargetObjectImpl
implements DbgModelTargetRegisterContainerAndBank {
@ -78,7 +84,7 @@ public class DbgModelTargetRegisterContainerImpl extends DbgModelTargetObjectImp
}
public void threadStateChangedSpecific(DbgState state, DbgReason reason) {
if (state.equals(DbgState.STOPPED)) {
if (!state.equals(DbgState.RUNNING)) {
readRegistersNamed(getCachedElements().keySet());
}
}

View file

@ -20,7 +20,7 @@ import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import agent.dbgeng.manager.DbgStackFrame;
import agent.dbgeng.manager.*;
import agent.dbgeng.model.iface2.*;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.*;
@ -83,32 +83,12 @@ public class DbgModelTargetStackImpl extends DbgModelTargetObjectImpl
}
*/
@Override
public void onRunning() {
// NB: We don't want to do this apparently
//invalidateRegisterCaches();
setAccessible(false);
}
@Override
public void onStopped() {
setAccessible(true);
if (thread.getThread().getId().equals(getManager().getEventThread().getId())) {
update();
public void threadStateChangedSpecific(DbgState state, DbgReason reason) {
if (!state.equals(DbgState.RUNNING)) {
requestElements(true).exceptionally(e -> {
Msg.error(this, "Could not update stack " + this + " on STOPPED");
return null;
});
}
}
/**
* Re-fetch the stack frames, generating events for updates
*
* GDB doesn't produce stack change events, but they should only ever happen by running a
* target. Thus, every time we're STOPPED, this method should be called.
*/
@Override
public void update() {
requestElements(true).exceptionally(e -> {
Msg.error(this, "Could not update stack " + this + " on STOPPED");
return null;
});
}
}

View file

@ -123,6 +123,7 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl
), reason.desc());
//setExecutionState(targetState, reason.desc());
registers.threadStateChangedSpecific(state, reason);
stack.threadStateChangedSpecific(state, reason);
}
@Override

View file

@ -15,6 +15,9 @@
*/
package agent.dbgeng.model.invm;
import org.junit.Ignore;
import org.junit.Test;
import agent.dbgeng.model.AbstractModelForDbgengRootAttacherTest;
public class InVmModelForDbgengRootAttacherTest extends AbstractModelForDbgengRootAttacherTest {
@ -22,4 +25,12 @@ public class InVmModelForDbgengRootAttacherTest extends AbstractModelForDbgengRo
public ModelHost modelHost() throws Throwable {
return new InVmDbgengModelHost();
}
@Override
@Ignore
@Test
// Takes forever - passes w/ OTE on Memory in tear down
public void testAttachByPidThenResumeInterrupt() throws Throwable {
super.testAttachByPidThenResumeInterrupt();
}
}

View file

@ -580,7 +580,11 @@ public class WrappedDbgModel
@Override
public void setCurrentThreadId(DebugThreadId dti) {
client.getSystemObjects().setCurrentThreadId(dti);
DebugSystemObjects so = client.getSystemObjects();
DebugThreadId currentThreadId = so.getCurrentThreadId();
if (dti.id != currentThreadId.id) {
so.setCurrentThreadId(dti);
}
/*
if (USE_CLIENT) {
System.err.println("setCurrentThread");

View file

@ -94,7 +94,10 @@ public class HDMAUtil {
if (kind.equals(ModelObjectKind.OBJECT_INTRINSIC) ||
kind.equals(ModelObjectKind.OBJECT_TARGET_OBJECT) ||
kind.equals(ModelObjectKind.OBJECT_TARGET_OBJECT_REFERENCE)) {
return target.getRawValueMap();
Map<String, ModelObject> map = target.getRawValueMap();
if (!map.isEmpty()) {
return map;
}
}
return target.getKeyValueMap();
}

View file

@ -49,26 +49,32 @@ public class DbgListAttributesCommand extends AbstractDbgCommand<Map<String, ?>>
@Override
public void invoke() {
updatedAttributes = new TreeMap<>(TargetObjectKeyComparator.ATTRIBUTE);
Map<String, ModelObject> map = access.getAttributes(path);
Map<String, ?> existingAttributes = targetObject.getCachedAttributes();
for (String key : map.keySet()) {
DbgModel2TargetProxy proxyAttribute;
ModelObject obj = map.get(key);
String atKey = obj.getSearchKey();
Object object = existingAttributes.get(atKey);
if (object != null && (object instanceof DbgModelTargetObject)) {
proxyAttribute = (DbgModel2TargetProxy) object;
DelegateDbgModel2TargetObject delegate = proxyAttribute.getDelegate();
delegate.setModelObject(obj);
updatedAttributes.put(key, proxyAttribute);
}
else {
proxyAttribute = (DbgModel2TargetProxy) DelegateDbgModel2TargetObject
.makeProxy(targetObject.getModel(), targetObject, atKey, obj);
updatedAttributes.put(key, proxyAttribute);
try {
updatedAttributes = new TreeMap<>(TargetObjectKeyComparator.ATTRIBUTE);
Map<String, ModelObject> map = access.getAttributes(path);
Map<String, ?> existingAttributes = targetObject.getCachedAttributes();
for (String key : map.keySet()) {
DbgModel2TargetProxy proxyAttribute;
ModelObject obj = map.get(key);
String atKey = obj.getSearchKey();
Object object = existingAttributes.get(atKey);
if (object != null && (object instanceof DbgModelTargetObject)) {
proxyAttribute = (DbgModel2TargetProxy) object;
DelegateDbgModel2TargetObject delegate = proxyAttribute.getDelegate();
delegate.setModelObject(obj);
updatedAttributes.put(key, proxyAttribute);
}
else {
proxyAttribute = (DbgModel2TargetProxy) DelegateDbgModel2TargetObject
.makeProxy(targetObject.getModel(), targetObject, atKey, obj);
updatedAttributes.put(key, proxyAttribute);
}
}
updatedAttributes.putAll(targetObject.getIntrinsics());
}
catch (Exception e) {
System.err.println("Failure in ListAttributes " + targetObject);
e.printStackTrace();
}
updatedAttributes.putAll(targetObject.getIntrinsics());
}
}

View file

@ -265,7 +265,7 @@ public class UnknownWithUtils extends Unknown {
}
protected HRESULT _invokeHR(VTableIndex idx, Object... args) {
//System.err.println(idx);
//Msg.info(this, Thread.currentThread() + " invoked " + idx + Arrays.asList(args));
return (HRESULT) this._invokeNativeObject(idx.getIndex(), args, HRESULT.class);
}

View file

@ -93,6 +93,7 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
return (DbgModel2Impl) super.getModel();
}
@Override
public CompletableFuture<List<TargetObject>> requestNativeElements() {
DbgManager2Impl manager2 = (DbgManager2Impl) getManager();
List<String> pathX = PathUtils.extend(List.of("Debugger"), path);
@ -106,6 +107,7 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
return manager2.listAttributes(pathX, this);
}
@Override
public CompletableFuture<Void> requestAugmentedAttributes() {
return requestAttributes(false);
}
@ -137,6 +139,22 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
});
}
protected boolean isReallyValid() {
//synchronized (model.lock) {
//System.out.println("checking validity: " + getJoinedPath(".") + "," +
// this.getDelegate().getClass());
for (TargetObject p = this; p != null; p = p.getParent()) {
//System.out.print(" .");
if (!p.isValid()) {
//System.out.println("x");
return false;
}
}
//System.out.println("_");
//}
return true;
}
@Override
public CompletableFuture<Void> requestAttributes(boolean refresh) {
Map<String, Object> nmap = new HashMap<>();
@ -162,6 +180,10 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
return addModelObjectAttributes(nmap);
}
}).thenAccept(__ -> {
// Meh
if (!isReallyValid()) {
return;
}
changeAttributes(List.of(), nmap, "Refreshed");
});
}
@ -188,7 +210,7 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
}
protected CompletableFuture<Void> addModelObjectAttributes(Map<String, Object> attrs) {
if (modelObject == null) {
if (modelObject == null || !valid) {
return CompletableFuture.completedFuture(null);
}
String key = modelObject.getSearchKey();
@ -227,9 +249,11 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
attrs.put(TargetAccessConditioned.ACCESSIBLE_ATTRIBUTE_NAME, accessible);
}
if (proxy instanceof TargetExecutionStateful) {
TargetExecutionStateful stateful = (TargetExecutionStateful) proxy;
TargetExecutionState state = stateful.getExecutionState();
attrs.put(TargetExecutionStateful.STATE_ATTRIBUTE_NAME, state);
if (isValid()) {
TargetExecutionStateful stateful = (TargetExecutionStateful) proxy;
TargetExecutionState state = stateful.getExecutionState();
attrs.put(TargetExecutionStateful.STATE_ATTRIBUTE_NAME, state);
}
}
if (proxy instanceof TargetAttacher) {
attrs.put(TargetAttacher.SUPPORTED_ATTACH_KINDS_ATTRIBUTE_NAME,
@ -271,6 +295,7 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
memory = new DbgModelTargetMemoryContainerImpl((DbgModelTargetProcess) proxy);
}
attrs.put(memory.getName(), memory);
memory.requestElements(true);
}
if (proxy instanceof TargetThread) {
DbgModelTargetThread targetThread = (DbgModelTargetThread) proxy;
@ -351,7 +376,7 @@ public class DbgModel2TargetObjectImpl extends DefaultTargetObject<TargetObject,
this.modelObject = modelObject;
Map<String, Object> attrs = new HashMap<>();
addModelObjectAttributes(attrs).thenAccept(__ -> {
if (!attrs.isEmpty()) {
if (isReallyValid() && !attrs.isEmpty()) {
changeAttributes(List.of(), List.of(), attrs, "Refreshed");
}
}).exceptionally(ex -> {

View file

@ -114,8 +114,8 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
TargetFocusScope.FOCUS_ATTRIBUTE_NAME, focus //
), "Focus changed");
intrinsics.put(TargetFocusScope.FOCUS_ATTRIBUTE_NAME, focus);
DbgModelTargetSession session = focus.getParentSession();
session.setActive();
//DbgModelTargetSession session = focus.getParentSession();
//session.setActive();
}
return doFire;
}
@ -179,7 +179,6 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
System.err.println("processAdded - null");
return;
}
System.err.println("SERVER:processAdded: " + proc);
getListeners().fire.event(getProxy(), null, TargetEventType.PROCESS_CREATED,
"Process " + proc.getId() + " started " + "notepad.exe" + " pid=" + proc.getPid(),
List.of(targetProcess));
@ -194,9 +193,11 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
System.err.println("threadCreated - null");
return;
}
System.err.println("SERVER:threadCreated: " + targetThread);
getListeners().fire.event(getProxy(), targetThread, TargetEventType.THREAD_CREATED,
"Thread " + thread.getId() + " started", List.of(targetThread));
DelegateDbgModel2TargetObject delegate =
(DelegateDbgModel2TargetObject) targetThread.getDelegate();
delegate.threadStateChangedSpecific(DbgState.STARTING, DbgReason.Reasons.UNKNOWN);
});
}
@ -212,6 +213,12 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_LOADED,
"Library " + info.getModuleName() + " loaded", List.of(mod));
});
getObject(getManager().getEventProcess()).thenAccept(p -> {
DbgModelTargetProcess eventProcess = (DbgModelTargetProcess) p;
DbgModel2TargetObjectImpl memory =
(DbgModel2TargetObjectImpl) eventProcess.getCachedAttribute("Memory");
memory.requestElements(false);
});
});
}
@ -227,6 +234,12 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
getListeners().fire.event(getProxy(), eventThread, TargetEventType.MODULE_UNLOADED,
"Library " + info.getModuleName() + " unloaded", List.of(mod));
});
getObject(getManager().getEventProcess()).thenAccept(p -> {
DbgModelTargetProcess eventProcess = (DbgModelTargetProcess) p;
DbgModel2TargetObjectImpl memory =
(DbgModel2TargetObjectImpl) eventProcess.getCachedAttribute("Memory");
memory.requestElements(false);
});
});
}
@ -335,6 +348,12 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
if (!process.getExecutionState().equals(TargetExecutionState.TERMINATED)) {
process.setExecutionState(TargetExecutionState.INACTIVE, "Detached");
}
DbgModelTargetObject container = (DbgModelTargetObject) process.getParent();
DelegateDbgModel2TargetObject delegate =
(DelegateDbgModel2TargetObject) container.getDelegate();
delegate.changeElements(List.of( //
process.getIndex() //
), List.of(), Map.of(), "Removed");
process.getParent().resync();
});
}
@ -344,10 +363,12 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
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");
if (targetProcess.isValid()) {
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(),
@ -382,7 +403,9 @@ 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);
DelegateDbgModel2TargetObject delegate =
(DelegateDbgModel2TargetObject) targetThread.getDelegate();
delegate.threadStateChangedSpecific(state, reason);
});
}
@ -417,7 +440,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
@Override
public void breakpointCreated(DbgBreakpointInfo info, DbgCause cause) {
int id = info.getDebugBreakpoint().getId();
int id = info.getId();
bptInfoMap.put(id, info);
getObjectRevisited(info.getProc(), List.of("Debug", "Breakpoints"), info);
}
@ -425,7 +448,7 @@ public class DbgModel2TargetRootImpl extends DbgModel2DefaultTargetModelRoot
@Override
public void breakpointModified(DbgBreakpointInfo newInfo, DbgBreakpointInfo oldInfo,
DbgCause cause) {
int id = newInfo.getDebugBreakpoint().getId();
int id = newInfo.getId();
bptInfoMap.put(id, newInfo);
getObjectRevisited(newInfo.getProc(), List.of("Debug", "Breakpoints"), newInfo);
}

View file

@ -195,7 +195,7 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
model.getManager().addEventsListener((DbgEventsListener) proxy);
}
setModelObject(modelObject);
update0();
init();
}
public DelegateDbgModel2TargetObject clone(String key, ModelObject modelObject) {
@ -224,6 +224,12 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
return ret;
}
@Override
protected void doInvalidate(TargetObject branch, String reason) {
super.doInvalidate(branch, reason);
getManager().removeStateListener(accessListener);
}
protected void checkExited(DbgState state, DbgCause cause) {
TargetExecutionState exec = TargetExecutionState.INACTIVE;
switch (state) {
@ -274,7 +280,7 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
}
}
public void update0() {
public void init() {
if (PathUtils.isLink(parent.getPath(), proxy.getName(), proxy.getPath())) {
return;
}
@ -285,22 +291,11 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
return;
}
if (proxy instanceof DbgModelTargetRegisterContainer || //
proxy.getName().equals("Stack")) {
proxy.getName().equals("Stack") ||
proxy.getName().equals("Debug")) {
requestAttributes(false);
return;
}
/*
if (proxy instanceof DbgModelTargetRegisterBank) {
requestAttributes(false).thenAccept(__ -> {
DbgModelTargetRegisterBank bank = (DbgModelTargetRegisterBank) proxy;
Map<String, byte[]> result = bank.getValues();
System.err.println("SERVER:fire.registersUpdated " + bank);
listeners.fire.registersUpdated(bank, result);
});
return;
}
*/
if (proxy instanceof DbgModelTargetProcessContainer || //
proxy instanceof DbgModelTargetThreadContainer || //
proxy instanceof DbgModelTargetModuleContainer || //
@ -311,44 +306,6 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
}
}
private void update() {
if (PathUtils.isLink(parent.getPath(), proxy.getName(), proxy.getPath())) {
return;
}
if (proxy instanceof DbgModelTargetProcessContainer || //
proxy instanceof DbgModelTargetThreadContainer || //
proxy instanceof DbgModelTargetModuleContainer || //
proxy instanceof DbgModelTargetBreakpointContainer || //
proxy instanceof DbgModelTargetRegisterContainer || //
proxy instanceof DbgModelTargetRegisterBank || //
proxy instanceof DbgModelTargetStack || //
proxy instanceof DbgModelTargetTTD) {
requestElements(false);
requestAttributes(false);
return;
}
/*
if (proxy instanceof DbgModelTargetRegisterBank) {
requestAttributes(false).thenAccept(__ -> {
DbgModelTargetRegisterBank bank = (DbgModelTargetRegisterBank) proxy;
Map<String, byte[]> result = bank.getValues();
System.err.println("SERVER:fire.registersUpdated " + bank);
listeners.fire.registersUpdated(bank, result);
});
return;
}
*/
if (proxy instanceof DbgModelTargetRegister || //
proxy instanceof DbgModelTargetStackFrame) {
requestAttributes(false);
return;
}
if (proxy.getName().equals("Debug")) {
requestAttributes(false);
return;
}
}
public void onRunning() {
invalidate();
setAccessible(false);
@ -356,7 +313,6 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
public void onStopped() {
setAccessible(true);
update();
}
public void onExit() {
@ -416,7 +372,6 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
@Override
public void setBreakpointEnabled(boolean enabled) {
update();
this.breakpointEnabled = enabled;
}
@ -425,4 +380,53 @@ public class DelegateDbgModel2TargetObject extends DbgModel2TargetObjectImpl imp
return breakpointActions;
}
public void threadStateChangedSpecific(DbgState state, DbgReason reason) {
if (state.equals(DbgState.RUNNING)) {
return;
}
if (proxy instanceof TargetThread) {
List<DelegateDbgModel2TargetObject> delegates = new ArrayList<>();
TargetObject stack =
(TargetObject) getCachedAttribute("Stack");
DbgModelTargetStack frames =
(DbgModelTargetStack) stack.getCachedAttribute("Frames");
delegates.add((DelegateDbgModel2TargetObject) frames.getDelegate());
DbgModelTargetRegisterContainer container =
(DbgModelTargetRegisterContainer) getCachedAttribute("Registers");
delegates.add((DelegateDbgModel2TargetObject) container.getDelegate());
DbgModelTargetRegisterBank bank =
(DbgModelTargetRegisterBank) container.getCachedAttribute("User");
delegates.add((DelegateDbgModel2TargetObject) bank.getDelegate());
for (DelegateDbgModel2TargetObject delegate : delegates) {
delegate.threadStateChangedSpecific(state, reason);
}
}
if (proxy instanceof TargetRegisterContainer) {
requestElements(false);
requestAttributes(false);
}
if (proxy instanceof TargetRegisterBank) {
TargetRegisterBank bank = (TargetRegisterBank) proxy;
//requestElements(false);
requestAttributes(false).thenAccept(__ -> {
bank.readRegistersNamed(getCachedAttributes().keySet());
});
}
if (proxy instanceof TargetStack) {
requestAttributes(false);
requestElements(false).thenAccept(__ -> {
for (TargetObject obj : getCachedElements().values()) {
if (obj instanceof TargetStackFrame) {
DbgModelTargetObject frame = (DbgModelTargetObject) obj;
DelegateDbgModel2TargetObject delegate =
(DelegateDbgModel2TargetObject) frame.getDelegate();
delegate.threadStateChangedSpecific(state, reason);
}
}
});
}
if (proxy instanceof TargetStackFrame) {
requestAttributes(false);
}
}
}

View file

@ -18,8 +18,8 @@
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_event_process" schema="STRING" hidden="yes" />
<attribute name="_event_thread" schema="STRING" hidden="yes" />
<attribute name="_event_process" schema="OBJECT" hidden="yes" />
<attribute name="_event_thread" schema="OBJECT" hidden="yes" />
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
<attribute name="_focus" schema="OBJECT" required="yes" hidden="yes" />
<attribute name="_system" schema="OBJECT" hidden="yes" />

View file

@ -0,0 +1,326 @@
/* ###
* 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.dbgmodel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import agent.dbgeng.dbgeng.*;
import agent.dbgeng.dbgeng.DebugBreakpoint.BreakFlags;
import agent.dbgeng.dbgeng.DebugBreakpoint.BreakType;
import agent.dbgeng.dbgeng.DebugClient.*;
import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_STACK_FRAME;
import agent.dbgmodel.dbgmodel.bridge.HostDataModelAccess;
import agent.dbgmodel.dbgmodel.main.ModelObject;
import agent.dbgmodel.impl.dbgmodel.bridge.HDMAUtil;
import ghidra.comm.util.BitmaskSet;
import ghidra.dbg.util.PathUtils;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.util.Msg;
public class DbgModelSetContextMWETest extends AbstractGhidraHeadlessIntegrationTest {
@Before
public void setUp() {
DbgEngTest.assumeDbgengDLLLoadable();
}
@Test
public void testMWE() {
HostDataModelAccess access = DbgModel.debugCreate();
DebugClient client = access.getClient();
DebugControl control = client.getControl();
DebugRegisters registers = client.getRegisters();
DebugSystemObjects so = client.getSystemObjects();
HDMAUtil util = new HDMAUtil(access);
var cb = new NoisyDebugEventCallbacksAdapter(DebugStatus.GO) {
volatile boolean hit = false;
private void dumpAllThreads(Runnable runnable, boolean reverse, boolean shuffle) {
try {
DebugThreadId restore = so.getCurrentThreadId();
try {
List<DebugThreadId> threads = so.getThreads();
if (shuffle) {
Collections.shuffle(threads);
}
if (reverse) {
Collections.reverse(threads);
}
for (DebugThreadId id : threads) {
so.setCurrentThreadId(id);
runnable.run();
}
}
finally {
so.setCurrentThreadId(restore);
}
}
catch (Exception e) {
Msg.error(this, "Issue getting current thread: " + e);
}
}
private void dumpRegsViaDX() {
DebugThreadId id = so.getCurrentThreadId();
if (id.id == -1) {
return;
}
int pid = so.getCurrentProcessSystemId();
int tid = so.getCurrentThreadSystemId();
String prefix = String.format(
"Debugger.Sessions[0x0].Processes[0x%x].Threads[0x%x]", pid, tid);
try {
control.execute("dx " + prefix + ".Registers.User");
}
catch (Exception e) {
Msg.error(this, "Could not dump regs of " + prefix + ": " + e);
}
}
private void dumpFrame0ViaDX() {
DebugThreadId id = so.getCurrentThreadId();
if (id.id == -1) {
return;
}
int pid = so.getCurrentProcessSystemId();
int tid = so.getCurrentThreadSystemId();
String prefix = String.format(
"Debugger.Sessions[0x0].Processes[0x%x].Threads[0x%x]", pid, tid);
String path = prefix + ".Stack.Frames[0x0].Attributes.InstructionOffset";
List<String> parsed = PathUtils.parse(path);
try {
//for (int i = 0; i < parsed.size(); i++) {
//List<String> sub = parsed.subList(0, i + 1);
List<String> sub = parsed;
ModelObject obj = util.getTerminalModelObject(sub);
Msg.info(this, PathUtils.toString(sub) + "=" + obj);
//}
}
catch (Exception e) {
Msg.error(this, "Could not get object " + path + ": " + e);
}
try {
control.execute("dx " + path);
}
catch (Exception e) {
Msg.error(this, "Could not execute dx " + path + ": " + e);
}
}
private void dumpFrame0ViaK() {
DebugThreadId id = so.getCurrentThreadId();
if (id.id == -1) {
return;
}
try {
DebugStackInformation stackInfo = control.getStackTrace(0, 0, 0);
if (stackInfo.getNumberOfFrames() == 0) {
Msg.info(this, "t" + id.id + ".Stack is empty?");
}
else {
DEBUG_STACK_FRAME frame = stackInfo.getFrame(0);
Msg.info(this,
String.format("t%d.Frame[0].io=%08x", id.id,
frame.InstructionOffset.longValue()));
}
}
catch (Exception e) {
Msg.info(this, "Could not read t" + id.id + ".Frame[0].io: " + e);
}
}
private void dumpPCViaRegsAPI() {
DebugThreadId id = so.getCurrentThreadId();
if (id.id == -1) {
return;
}
try {
Msg.info(this, String.format("t%d.rip=%s", id.id,
registers.getValueByName("rip")));
}
catch (Exception e) {
Msg.info(this, "Could not read t" + id.id + ".RIP: " + e);
}
try {
Msg.info(this, String.format("t%d.eip=%s", id.id,
registers.getValueByName("eip")));
}
catch (Exception e) {
Msg.info(this, "Could not read t" + id.id + ".EIP: " + e);
}
}
private void dumpCurrentThread() {
dumpRegsViaDX();
dumpFrame0ViaDX();
dumpFrame0ViaK();
dumpPCViaRegsAPI();
}
@Override
public DebugStatus breakpoint(DebugBreakpoint bp) {
super.breakpoint(bp);
hit = true;
Msg.info(this, "HIT!!!!");
//dumpAllThreads();
return DebugStatus.BREAK;
}
@Override
public DebugStatus exception(DebugExceptionRecord64 exception, boolean firstChance) {
DebugStatus status = super.exception(exception, firstChance);
//dumpAllThreads();
return status;
}
@Override
public DebugStatus changeEngineState(BitmaskSet<ChangeEngineState> flags,
long argument) {
DebugStatus status = super.changeEngineState(flags, argument);
if (flags.contains(ChangeEngineState.CURRENT_THREAD)) {
return status;
}
if (!flags.contains(ChangeEngineState.EXECUTION_STATUS)) {
return status;
}
if (DebugStatus.isInsideWait(argument)) {
return status;
}
if (DebugStatus.fromArgument(argument) != DebugStatus.BREAK) {
return status;
}
//dumpAllThreads(this::dumpRegsViaDX, false, false);
//dumpAllThreads(this::dumpFrame0ViaDX, false, false);
return status;
}
@Override
public DebugStatus changeDebuggeeState(BitmaskSet<ChangeDebuggeeState> flags,
long argument) {
DebugStatus status = super.changeDebuggeeState(flags, argument);
return status;
}
Map<Integer, ModelObject> frame0sByT = new HashMap<>();
protected void cacheFrame0() {
dumpAllThreads(() -> {
int pid = so.getCurrentProcessSystemId();
int tid = so.getCurrentThreadSystemId();
String path = makePrefix(pid, tid) + ".Stack.Frames";
ModelObject object = getObject(path);
if (object == null) {
Msg.error(this, "Could not get object: " + path);
}
else {
frame0sByT.put(tid, object);
}
}, false, false);
}
@Override
public DebugStatus createThread(DebugThreadInfo debugThreadInfo) {
DebugStatus status = super.createThread(debugThreadInfo);
cacheFrame0();
return status;
}
@Override
public DebugStatus createProcess(DebugProcessInfo debugProcessInfo) {
DebugStatus status = super.createProcess(debugProcessInfo);
cacheFrame0();
return status;
}
private ModelObject getObject(String path) {
List<String> parsed = PathUtils.parse(path);
return util.getTerminalModelObject(parsed);
}
private String makePrefix(int pid, int tid) {
return String.format("Debugger.Sessions[0x0].Processes[0x%x].Threads[0x%x]",
pid, tid);
}
@Override
public DebugStatus exitThread(int exitCode) {
DebugStatus status = super.exitThread(exitCode);
return status;
}
@Override
public DebugStatus changeSymbolState(BitmaskSet<ChangeSymbolState> flags,
long argument) {
return defaultStatus;
}
};
try (ProcMaker maker = new ProcMaker(client, "C:\\Software\\Winmine__XP.exe")) {
maker.start();
client.setEventCallbacks(cb);
DebugSymbols symbols = client.getSymbols();
//assertEquals(1, symbols.getNumberLoadedModules());
DebugModule modWinmine = symbols.getModuleByModuleName("winmine", 0);
assertNotNull(modWinmine);
long baseWinmine = modWinmine.getBase();
assertEquals(0x01000000, baseWinmine);
DebugBreakpoint bpt0 = control.addBreakpoint(BreakType.CODE);
bpt0.setOffset(baseWinmine + 0x367a);
bpt0.setFlags(BreakFlags.ENABLED);
control.setExecutionStatus(DebugStatus.GO);
while (!cb.hit) {
Msg.info(this, "Not hit yet. Waiting");
control.waitForEvent();
Msg.info(this, " ...");
}
Msg.info(this, "DONE");
for (Map.Entry<Integer, ModelObject> ent : cb.frame0sByT.entrySet()) {
Msg.info(this, String.format("IO-cached(0x%x): %s", ent.getKey(),
ent.getValue()
.getElements()
.get(0)
.getKeyValue("Attributes")
.getKeyValue("InstructionOffset")));
}
cb.dumpFrame0ViaDX();
/**
* TODO: Didn't finish because the SetContext failed issue turned out to be mixed and/or
* broken DLLs.
*/
}
}
}

View file

@ -33,13 +33,12 @@ import com.sun.jna.platform.win32.COM.Unknown;
import agent.dbgeng.dbgeng.*;
import agent.dbgeng.dbgeng.DebugBreakpoint.BreakType;
import agent.dbgeng.dbgeng.DebugClient.*;
import agent.dbgeng.dbgeng.DebugClient.DebugStatus;
import agent.dbgeng.dbgeng.DebugDataSpaces.*;
import agent.dbgeng.dbgeng.DebugModule.DebugModuleName;
import agent.dbgeng.dbgeng.DebugRegisters.DebugRegisterDescription;
import agent.dbgeng.dbgeng.DebugRegisters.DebugRegisterSource;
import agent.dbgeng.dbgeng.DebugValue.DebugInt64Value;
import agent.dbgeng.dbgeng.util.DebugEventCallbacksAdapter;
import agent.dbgmodel.dbgmodel.bridge.HostDataModelAccess;
import agent.dbgmodel.dbgmodel.datamodel.DataModelManager1;
import agent.dbgmodel.dbgmodel.datamodel.script.*;
@ -51,9 +50,7 @@ import agent.dbgmodel.impl.dbgmodel.debughost.DebugHostModuleImpl1;
import agent.dbgmodel.impl.dbgmodel.main.ModelPropertyAccessorInternal;
import agent.dbgmodel.jna.dbgmodel.DbgModelNative.*;
import agent.dbgmodel.jna.dbgmodel.UnknownWithUtils;
import ghidra.comm.util.BitmaskSet;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@ -99,7 +96,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testServer() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
control.execute(".server tcp:port=54321");
@ -118,7 +115,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testOpenTrace() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
// NB: This does not work! TTDReplay must live in TTD\TTReplay.dll wherever
@ -189,7 +186,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testInterfaces() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -210,7 +207,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testHammerEnumerate() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -283,7 +280,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testGetChild() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -296,7 +293,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testEnv() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
//control.execute(".server tcp:port=54321");
@ -339,7 +336,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testEnvEx() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
//control.execute(".server tcp:port=54321");
@ -402,7 +399,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testGetProcessSystemIds() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -420,7 +417,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testGetProcesses() {
DebugSystemObjects so = access.getClient().getSystemObjects();
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
System.out.println(so.getNumberProcesses());
@ -439,7 +436,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testGetProcessDescriptions() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -462,7 +459,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testGetRegistersNew() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -479,7 +476,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testGetAllRegisters() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
WrappedDbgModel dbgmodel = new WrappedDbgModel(access);
@ -492,7 +489,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testGetRegisters() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
List<String> out = maker.execCapture("r");
@ -521,7 +518,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testSetCurrentThread() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -539,7 +536,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testGetElements() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -553,7 +550,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testGetAttributes() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -566,7 +563,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testCall() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -584,7 +581,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testCallWithParameter() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -608,7 +605,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testCallWithParametersEx() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -630,7 +627,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
/*
@Test
public void testSetSingleRegister() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client,"notepad")) {
maker.start();
DebugRegisters regs = client.getRegisters();
@ -645,7 +642,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testSetRegisters() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client,"notepad")) {
maker.start();
DebugRegisters regs = client.getRegisters();
@ -666,7 +663,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testQueryVirtual() {
// Also, an experiment to figure out how it works
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client,"notepad")) {
maker.start();
List<DebugMemoryBasicInformation> collected1 = new ArrayList<>();
@ -702,7 +699,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testModules() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -733,7 +730,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testStack() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
HDMAUtil util = new HDMAUtil(access);
@ -762,7 +759,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testReadMemory() throws FileNotFoundException, IOException {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
int len = 256;
@ -800,7 +797,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testScriptInterface() throws FileNotFoundException, IOException {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
client.getControl()
@ -835,7 +832,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testBreakpoints() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
DebugBreakpoint bpt = control.addBreakpoint(BreakType.CODE);
@ -862,7 +859,8 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testSymbols() {
try (ProcMaker maker = new ProcMaker("c:\\Users\\user\\Desktop\\ConsoleApplication1.exe")) {
try (ProcMaker maker =
new ProcMaker(client, "c:\\Users\\user\\Desktop\\ConsoleApplication1.exe")) {
maker.start();
DebugSymbols ds = client.getSymbols();
@ -932,7 +930,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
//@Test(expected = COMException.class)
public void testModuleOutOfBounds() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
DebugModule umod = client.getSymbols()
@ -943,7 +941,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testQueryVirtualWithModule() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
for (DebugMemoryBasicInformation info : client.getDataSpaces().iterateVirtual(0)) {
@ -967,7 +965,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
@Test
public void testSymbolInfo() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
int count = 0;
@ -986,7 +984,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
//@Test
public void testWriteMemory() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client, "notepad")) {
maker.start();
// TODO: How to write to protected memory?
@ -1022,7 +1020,7 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
/*
@Test
public void testFreezeUnfreeze() {
try (ProcMaker maker = new ProcMaker("notepad")) {
try (ProcMaker maker = new ProcMaker(client,"notepad")) {
maker.start();
// Trying to see if any events will help me track frozen threads
@ -1126,178 +1124,4 @@ public class DbgModelTest extends AbstractGhidraHeadlessIntegrationTest {
}
}
*/
public static abstract class NoisyDebugEventCallbacksAdapter
extends DebugEventCallbacksAdapter {
final DebugStatus defaultStatus;
public NoisyDebugEventCallbacksAdapter(DebugStatus defaultStatus) {
this.defaultStatus = defaultStatus;
}
@Override
public DebugStatus createProcess(DebugProcessInfo debugProcessInfo) {
Msg.info(this, "createProcess: " + debugProcessInfo);
return defaultStatus;
}
@Override
public DebugStatus createThread(DebugThreadInfo debugThreadInfo) {
Msg.info(this, "createThread: " + debugThreadInfo);
return defaultStatus;
}
@Override
public DebugStatus exitProcess(int exitCode) {
Msg.info(this, "exitProcess: " + Integer.toHexString(exitCode));
return defaultStatus;
}
@Override
public DebugStatus breakpoint(DebugBreakpoint bp) {
Msg.info(this, "breakpoint: " + bp);
return defaultStatus;
}
@Override
public DebugStatus changeDebuggeeState(BitmaskSet<ChangeDebuggeeState> flags,
long argument) {
Msg.info(this, "changeDebuggeeState: " + flags + ", " + argument);
return defaultStatus;
}
@Override
public DebugStatus changeEngineState(BitmaskSet<ChangeEngineState> flags, long argument) {
Msg.info(this, "changeEngineState: " + flags + ", " + argument);
return defaultStatus;
}
@Override
public DebugStatus changeSymbolState(BitmaskSet<ChangeSymbolState> flags, long argument) {
Msg.info(this, "changeSymbolState: " + flags + ", " + argument);
return defaultStatus;
}
@Override
public DebugStatus exception(DebugExceptionRecord64 exception, boolean firstChance) {
Msg.info(this, "exception: " + exception + ", " + firstChance);
return defaultStatus;
}
@Override
public DebugStatus exitThread(int exitCode) {
Msg.info(this, "exitThread: " + Integer.toHexString(exitCode));
return defaultStatus;
}
@Override
public DebugStatus loadModule(DebugModuleInfo debugModuleInfo) {
Msg.info(this, "loadModule: " + debugModuleInfo);
return defaultStatus;
}
@Override
public DebugStatus sessionStatus(SessionStatus status) {
Msg.info(this, "sessionStatus: " + status);
return defaultStatus;
}
@Override
public DebugStatus systemError(int error, int level) {
Msg.info(this, "systemError: " + error + ", " + level);
return defaultStatus;
}
@Override
public DebugStatus unloadModule(String imageBaseName, long baseOffset) {
Msg.info(this, "unloadModule: " + imageBaseName + ", " + baseOffset);
return defaultStatus;
}
}
protected class ProcMaker implements AutoCloseable {
public ProcMaker(String cmdLine) {
this.cmdLine = cmdLine;
}
final String cmdLine;
final CompletableFuture<DebugProcessInfo> procInfo = new CompletableFuture<>();
final CompletableFuture<DebugThreadInfo> threadInfo = new CompletableFuture<>();
final CompletableFuture<Integer> procExit = new CompletableFuture<>();
StringBuilder outputCapture = null;
public void start() {
client.setEventCallbacks(new NoisyDebugEventCallbacksAdapter(DebugStatus.NO_CHANGE) {
@Override
public DebugStatus createProcess(DebugProcessInfo debugProcessInfo) {
super.createProcess(debugProcessInfo);
procInfo.complete(debugProcessInfo);
return DebugStatus.BREAK;
}
@Override
public DebugStatus createThread(DebugThreadInfo debugThreadInfo) {
super.createThread(debugThreadInfo);
threadInfo.complete(debugThreadInfo);
return DebugStatus.BREAK;
}
@Override
public DebugStatus exitProcess(int exitCode) {
super.exitProcess(exitCode);
procExit.complete(exitCode);
return DebugStatus.BREAK;
}
});
client.setOutputCallbacks(new DebugOutputCallbacks() {
@Override
public void output(int mask, String text) {
System.out.print(text);
if (outputCapture != null) {
outputCapture.append(text);
}
}
});
Msg.debug(this, "Starting " + cmdLine + " with client " + client);
control.execute(".create " + cmdLine);
control.waitForEvent();
DebugProcessInfo pi = procInfo.getNow(null);
assertNotNull(pi);
control.execute("g");
control.waitForEvent();
DebugThreadInfo ti = threadInfo.getNow(null);
assertNotNull(ti);
}
public void kill() {
Msg.debug(this, "Killing " + cmdLine);
control.execute(".kill");
control.waitForEvent();
Integer exitCode = procExit.getNow(null);
client.setOutputCallbacks(null);
assertNotNull(exitCode);
}
public List<String> execCapture(String command) {
try {
outputCapture = new StringBuilder();
control.execute(command);
return Arrays.asList(outputCapture.toString().split("\n"));
}
finally {
outputCapture = null;
}
}
@Override
public void close() {
if (procInfo.isDone() && !procExit.isDone()) {
kill();
}
}
}
}

View file

@ -0,0 +1,118 @@
/* ###
* 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.dbgmodel;
import agent.dbgeng.dbgeng.*;
import agent.dbgeng.dbgeng.DebugClient.*;
import agent.dbgeng.dbgeng.util.DebugEventCallbacksAdapter;
import ghidra.comm.util.BitmaskSet;
import ghidra.util.Msg;
public abstract class NoisyDebugEventCallbacksAdapter
extends DebugEventCallbacksAdapter {
final DebugStatus defaultStatus;
public NoisyDebugEventCallbacksAdapter(DebugStatus defaultStatus) {
this.defaultStatus = defaultStatus;
}
@Override
public DebugStatus createProcess(DebugProcessInfo debugProcessInfo) {
Msg.info(this, "createProcess: " + debugProcessInfo);
return defaultStatus;
}
@Override
public DebugStatus createThread(DebugThreadInfo debugThreadInfo) {
Msg.info(this, "createThread: " + debugThreadInfo);
return defaultStatus;
}
@Override
public DebugStatus exitProcess(int exitCode) {
Msg.info(this, "exitProcess: " + Integer.toHexString(exitCode));
return defaultStatus;
}
@Override
public DebugStatus breakpoint(DebugBreakpoint bp) {
Msg.info(this, "breakpoint: " + bp);
return defaultStatus;
}
@Override
public DebugStatus changeDebuggeeState(BitmaskSet<ChangeDebuggeeState> flags,
long argument) {
Msg.info(this, "changeDebuggeeState: " + flags + ", " + Long.toHexString(argument));
return defaultStatus;
}
@Override
public DebugStatus changeEngineState(BitmaskSet<ChangeEngineState> flags, long argument) {
if (flags.contains(ChangeEngineState.EXECUTION_STATUS)) {
DebugStatus status = DebugStatus.values()[(int) (argument & 0x0_ffff_ffffL)];
Msg.info(this, "changeEngineState: " + flags + ", " +
Long.toHexString(argument) + " (" + status + ")");
}
else {
Msg.info(this, "changeEngineState: " + flags + ", " + Long.toHexString(argument));
}
return defaultStatus;
}
@Override
public DebugStatus changeSymbolState(BitmaskSet<ChangeSymbolState> flags, long argument) {
Msg.info(this, "changeSymbolState: " + flags + ", " + Long.toHexString(argument));
return defaultStatus;
}
@Override
public DebugStatus exception(DebugExceptionRecord64 exception, boolean firstChance) {
Msg.info(this, "exception: " + exception + ", " + firstChance);
return defaultStatus;
}
@Override
public DebugStatus exitThread(int exitCode) {
Msg.info(this, "exitThread: " + Integer.toHexString(exitCode));
return defaultStatus;
}
@Override
public DebugStatus loadModule(DebugModuleInfo debugModuleInfo) {
Msg.info(this, "loadModule: " + debugModuleInfo);
return defaultStatus;
}
@Override
public DebugStatus sessionStatus(SessionStatus status) {
Msg.info(this, "sessionStatus: " + status);
return defaultStatus;
}
@Override
public DebugStatus systemError(int error, int level) {
Msg.info(this, "systemError: " + error + ", " + level);
return defaultStatus;
}
@Override
public DebugStatus unloadModule(String imageBaseName, long baseOffset) {
Msg.info(this, "unloadModule: " + imageBaseName + ", " + baseOffset);
return defaultStatus;
}
}

View file

@ -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.dbgmodel.dbgmodel;
import static org.junit.Assert.assertNotNull;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import agent.dbgeng.dbgeng.*;
import agent.dbgeng.dbgeng.DebugClient.DebugStatus;
import ghidra.util.Msg;
public class ProcMaker implements AutoCloseable {
final DebugClient client;
final DebugControl control;
final String cmdLine;
final CompletableFuture<DebugProcessInfo> procInfo = new CompletableFuture<>();
final CompletableFuture<DebugThreadInfo> threadInfo = new CompletableFuture<>();
final CompletableFuture<Integer> procExit = new CompletableFuture<>();
StringBuilder outputCapture = null;
public ProcMaker(DebugClient client, String cmdLine) {
this.client = client;
this.cmdLine = cmdLine;
this.control = client.getControl();
}
public void start() {
client.setEventCallbacks(new NoisyDebugEventCallbacksAdapter(DebugStatus.NO_CHANGE) {
@Override
public DebugStatus createProcess(DebugProcessInfo debugProcessInfo) {
super.createProcess(debugProcessInfo);
procInfo.complete(debugProcessInfo);
return DebugStatus.BREAK;
}
@Override
public DebugStatus createThread(DebugThreadInfo debugThreadInfo) {
super.createThread(debugThreadInfo);
threadInfo.complete(debugThreadInfo);
return DebugStatus.BREAK;
}
@Override
public DebugStatus exitProcess(int exitCode) {
super.exitProcess(exitCode);
procExit.complete(exitCode);
return DebugStatus.BREAK;
}
});
client.setOutputCallbacks(new DebugOutputCallbacks() {
@Override
public void output(int mask, String text) {
System.out.print(text);
if (outputCapture != null) {
outputCapture.append(text);
}
}
});
Msg.debug(this, "Starting " + cmdLine + " with client " + client);
control.execute(".create " + cmdLine);
control.waitForEvent();
DebugProcessInfo pi = procInfo.getNow(null);
assertNotNull(pi);
control.execute("g");
control.waitForEvent();
DebugThreadInfo ti = threadInfo.getNow(null);
assertNotNull(ti);
}
public void kill() {
Msg.debug(this, "Killing " + cmdLine);
control.execute(".kill");
control.waitForEvent();
Integer exitCode = procExit.getNow(null);
client.setOutputCallbacks(null);
assertNotNull(exitCode);
}
public List<String> execCapture(String command) {
try {
outputCapture = new StringBuilder();
control.execute(command);
return Arrays.asList(outputCapture.toString().split("\n"));
}
finally {
outputCapture = null;
}
}
@Override
public void close() {
if (procInfo.isDone() && !procExit.isDone()) {
kill();
}
}
}

View file

@ -16,10 +16,33 @@
package agent.dbgmodel.model.invm;
import agent.dbgeng.model.AbstractModelForDbgengFactoryTest;
import ghidra.dbg.testutil.TestDebuggerModelProvider.ModelHost.WithoutThreadValidation;
public class InVmModelForDbgmodelFactoryTest extends AbstractModelForDbgengFactoryTest {
@Override
public ModelHost modelHost() throws Throwable {
return new InVmDbgmodelModelHost();
}
@Override
public void validateCompletionThread() {
super.validateCompletionThread();
}
/**
* The externally-accessible fetchX methods are being invoked internally. Unfortunately, this
* demarcation was not made clear at the beginning, so now, adding a gate kicks internal object
* retrieval off the DebugClient thread, which spells disaster for synchronization. The "real
* fix" will be to write internal object retrieval methods. These internal implementations will
* probably be left to each particular model. Dbgeng/model should be able to implement them
* synchronously. External invocations will still need to be handed to the DebugClient thread
* asynchronously. For now, we're going to disable the assertion.
*/
@Override
public void testNonExistentPathGivesNull() throws Throwable {
try (WithoutThreadValidation wtv = m.withoutThreadValidation()) {
super.testNonExistentPathGivesNull();
}
}
}

View file

@ -38,11 +38,12 @@ src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoint-mixed-ed.p
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-clear-all.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-disable-all.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-enable-all.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerConsolePlugin/images/DebuggerConsolePlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerInterpreterPlugin/DebuggerInterpreterPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerListingPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerModuleImportDialog.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerMemviewPlugin/images/DebuggerMemviewPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerMemviewPlugin/images/DebuggerMemviewPlugin_old.png||GHIDRA||||END|

View file

@ -74,6 +74,10 @@
target="help/topics/DebuggerModelServicePlugin/DebuggerModelServicePlugin.html" />
</tocdef>
<tocdef id="DebuggerConsolePlugin" text="Debug Console"
sortgroup="c1"
target="help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html" />
<tocdef id="DebuggerObjectsPlugin" text="Commands and Objects"
sortgroup="d"
target="help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html" />

View file

@ -15,9 +15,10 @@
<H2>Error Console</H2>
<P>The first place to look when you're having trouble is the error console. In Eclipse, this is
just the "Console" window. In Ghidra, it can be accessed from the main application window.
Sometimes it reports known issues; sometimes it reports unexpected behavior; etc., which may be
<P>The first place to look when you're having trouble is the Debug Console. Second, if you're
in Eclipse, you can check its "Console" window. Often, Ghidra's Debug Console will offer
actions to help you resolve a well-known issue or configuration problem. It also duplicates the
error log, when those messages are emitted from a debugger-related class. These typically offer
clues to exactly what has gone wrong.</P>
<H2>Settings and Toggles</H2>
@ -38,9 +39,6 @@
<P>In the Dynamic Listing:</P>
<UL>
<LI>"Auto-Import Current Module" will cause the user to be prompted for information to sync
static and dynamic listings.</LI>
<LI>"Sync to Static Listing" controls the tracking of the Static listing.</LI>
</UL>

View file

@ -0,0 +1,68 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Debugger: Memory Regions</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="plugin"></A>Debugger: Console</H1>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" border="1" src=
"images/DebuggerConsolePlugin.png"></TD>
</TR>
</TBODY>
</TABLE>
<P>The console logs messages from Ghidra related to the debugger. Depending on the exact
configuration, this can comprise a wide range of components, including all GUI views, active
connectors, and running agents. Currently, it implements an appender to gather all Log4J
messages emitted by Ghidra and filters for debugger-related packages and a level in the range
INFO through and including FATAL. That feature will likely be removed as more components are
programmed to work directly with the console. Soon, it may also provide a command-line
interface to control Ghidra's debugging sessions and interact with traces.</P>
<P>Some log messages include an action context, allowing plug-ins to offer actions on that
message. These are said to be "actionable" messages. A noteworthy example is when navigating to
a module that could not be automatically mapped from the current project. Instead of displaying
a prompt, it will log a message and suggest actions to resolve the issue. A successful
resolution typically removes the message from the log. Note that additional actions may be
available from the context menu.</P>
<P>By default, the log is sorted so that actionable messages appear at the top. Then, it is
sorted by descending date, so that the most recent messages appear at the top. Like any other
Ghidra table, it can customized and filtered. Note that the filter box is at the top, because
we anticipate a command-line input in the future, which we'd like to place at the bottom.</P>
<H2>Table Columns</H2>
<P>The table has the following columns:</P>
<UL>
<LI>Icon - an icon to identify the type, topic, or source of a message.</LI>
<LI>Message - the message itself.</LI>
<LI>Actions - if actionable, a row of buttons for available actions.</LI>
<LI>Time - the time the message was generated in 24-hour HH:mm:ss.SSS format.</LI>
</UL>
<H2>Actions</H2>
<P>Not considering extension actions from other plugins, the console provides the
following:</P>
<H3><A name="clear"></A>Clear</H3>
<P>Removes all messages, including actionable messages, from the log.</P>
</BODY>
</HTML>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -42,9 +42,9 @@
immediately upon the associated target interpreter becoming invalid, i.e., the connection was
closed. Pinning an interpreter keeps it open, but in a disabled state, so that the buffer can
be examined after invalidation.</P>
<H3><A name="interrupt"></A>Interrupt</H3>
<P>This action is always available. It interrupts the current target's execution.</P>
</BODY>
</HTML>

View file

@ -131,7 +131,9 @@
computed using information about loaded modules reported by the debugger. For the finer
details, see the <A href=
"help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html">Static Mappings</A>
window.</P>
window. When you navigate to a location contained by a module, but there is no corresponding
static location, the listing logs a "missing module" to the console, offering either to import
the module or map it to an existing program.</P>
<H3><A name="capture_memory"></A>Capture Memory</H3>
@ -162,29 +164,6 @@
neglect to capture read-only ranges that have been captured previously.</LI>
</UL>
<H3><A name="auto_import_module"></A>Auto-Import Current Module</H3>
<P>This toggle is available whenever Sync to Static Listing is enabled. It causes Ghidra to
prompt the user to import unknown modules. Specifically, when Ghidra cannot map the dynamic
listing's location to a static location, but the debugger reports a module containing the
dynamic address, it prompts the user to import that module:</P>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" src=
"images/DebuggerModuleImportDialog.png"></TD>
</TR>
</TBODY>
</TABLE>
<P>This non-modal dialog collects those prompts and appears whenever a new module is suggested.
To import a module, click the import icon to the suggestion's right. To remove an entry, click
the delete icon to the suggestions's left. To ignore an entry, check the box to the left of the
suggestion and dismiss the dialog. Note that a removed suggestion is not ignored. Ghidra may
suggest that module again. To import modules manually, see the <A href=
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html">Modules</A> window.</P>
<H2><A name="colors"></A>Tool Options: Colors</H2>
<P>The memory-state and tracked-location background colors can all be configured here.</P>

View file

@ -145,6 +145,16 @@
It behaves like Map Sections, except that it will propose the selected section be mapped to the
block containing the cursor in the static listing.</P>
<H3><A name="import_missing_module"></A>Import Missing Module</H3>
<P>This action is offered to resolve a "Missing Module" console message. It is equivalent to <A
href="#import_from_fs">Import From File System</A> on the missing module.</P>
<H3><A name="map_missing_module"></A>Map Missing Module</H3>
<P>This action is offered to resolve a "Missing Module" console message. It is equivalent to <A
href="#map_module_to">Map Module To</A> on the missing module.</P>
<H3><A name="filter_by_module"></A>Filter Sections by Module</H3>
<P>This action is always available. By default the bottom table displays all sections in the

View file

@ -32,6 +32,7 @@ import docking.widgets.table.*;
import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsPlugin;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionsPlugin;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesPlugin;
@ -118,6 +119,7 @@ public interface DebuggerResources {
ImageIcon ICON_CLOSE = ResourceManager.loadImage("images/x.gif");
ImageIcon ICON_ADD = ResourceManager.loadImage("images/add.png");
ImageIcon ICON_DELETE = ResourceManager.loadImage("images/delete.png");
ImageIcon ICON_CLEAR = ResourceManager.loadImage("images/erase16.png");
ImageIcon ICON_REFRESH = ResourceManager.loadImage("images/view-refresh.png");
ImageIcon ICON_FILTER = ResourceManager.loadImage("images/filter_off.png"); // Eww.
ImageIcon ICON_SELECT_ROWS = ResourceManager.loadImage("images/table_go.png");
@ -128,7 +130,7 @@ public interface DebuggerResources {
//ResourceManager.loadImage("images/capture-memory.png");
// TODO: Draw an icon
ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/map-modules.png");
ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png");
ImageIcon ICON_MAP_SECTIONS = ICON_MAP_MODULES; // TODO
ImageIcon ICON_BLOCK = ICON_MAP_SECTIONS; // TODO
// TODO: Draw an icon
@ -138,6 +140,10 @@ public interface DebuggerResources {
// TODO: Draw an icon?
ImageIcon ICON_CAPTURE_SYMBOLS = ResourceManager.loadImage("images/closedFolderLabels.png");
ImageIcon ICON_LOG_FATAL = ResourceManager.loadImage("images/edit-bomg.png");
ImageIcon ICON_LOG_ERROR = ResourceManager.loadImage("images/dialog-warning_red.png");
ImageIcon ICON_LOG_WARN = ResourceManager.loadImage("images/dialog-warning.png");
ImageIcon ICON_SYNC = ResourceManager.loadImage("images/sync_enabled.png");
ImageIcon ICON_VISIBILITY = ResourceManager.loadImage("images/format-text-bold.png");
@ -156,6 +162,11 @@ public interface DebuggerResources {
HelpLocation HELP_PROVIDER_BREAKPOINTS = new HelpLocation(
PluginUtils.getPluginNameFromClass(DebuggerBreakpointsPlugin.class), HELP_ANCHOR_PLUGIN);
String TITLE_PROVIDER_CONSOLE = "Debug Console";
ImageIcon ICON_PROVIDER_CONSOLE = ICON_CONSOLE;
HelpLocation HELP_PROVIDER_CONSOLE = new HelpLocation(
PluginUtils.getPluginNameFromClass(DebuggerConsolePlugin.class), HELP_ANCHOR_PLUGIN);
String TITLE_PROVIDER_LISTING = "Dynamic";
ImageIcon ICON_PROVIDER_LISTING = ICON_LISTING;
HelpLocation HELP_PROVIDER_LISTING = new HelpLocation(
@ -310,6 +321,9 @@ public interface DebuggerResources {
"Colors.Ineffective Disabled Breakpoint Markers Have Background";
boolean DEFAULT_COLOR_INEFFECTIVE_D_BREAKPOINT_COLORING_BACKGROUND = false;
String OPTION_NAME_LOG_BUFFER_LIMIT = "Log Buffer Size";
int DEFAULT_LOG_BUFFER_LIMIT = 100;
// TODO: Re-assign/name groups
String GROUP_GENERAL = "Dbg1. General";
String GROUP_CONNECTION = "Dbg2. Connection";
@ -645,7 +659,7 @@ public interface DebuggerResources {
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface InterpreterInterruptAction {
String NAME = "Interpreter Interrupt";
String DESCRIPTION = "Send an interrupt through this Interpreter";
@ -733,17 +747,36 @@ public interface DebuggerResources {
}
}
interface AutoImportCurrentModuleAction {
String NAME = "Auto-Import Current Module";
String DESCRIPTION = "Import missing module at the cursor";
interface ImportMissingModuleAction {
String NAME = "Import Missing Module";
String DESCRIPTION = "Import the missing module from disk";
Icon ICON = ICON_IMPORT;
String HELP_ANCHOR = "auto_import_module";
String HELP_ANCHOR = "import_missing_module";
static ToggleActionBuilder builder(Plugin owner) {
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ToggleActionBuilder(NAME, ownerName).description(DESCRIPTION)
.menuIcon(ICON)
.menuPath(NAME)
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.popupMenuIcon(ICON)
.popupMenuPath(NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface MapMissingModuleAction {
String NAME = "Map Missing Module";
String DESCRIPTION = "Map the missing module to an existing import";
Icon ICON = ICON_MAP_MODULES;
String HELP_ANCHOR = "map_missing_module";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.popupMenuIcon(ICON)
.popupMenuPath(NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
@ -862,7 +895,8 @@ public interface DebuggerResources {
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName).toolBarGroup(GROUP)
return new ActionBuilder(NAME, ownerName)
.toolBarGroup(GROUP)
.toolBarIcon(ICON)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
@ -879,7 +913,26 @@ public interface DebuggerResources {
}
static ActionBuilder builder(String ownerName) {
return new ActionBuilder(NAME, ownerName).toolBarGroup(GROUP)
return new ActionBuilder(NAME, ownerName)
.toolBarGroup(GROUP)
.toolBarIcon(ICON)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface ClearAction {
String NAME = "Clear";
String GROUP = "yyyy";
Icon ICON = ICON_CLEAR;
String HELP_ANCHOR = "clear";
static ActionBuilder builder(Plugin owner) {
return builder(owner.getName());
}
static ActionBuilder builder(String ownerName) {
return new ActionBuilder(NAME, ownerName)
.toolBarGroup(GROUP)
.toolBarIcon(ICON)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
@ -892,7 +945,21 @@ public interface DebuggerResources {
static ToggleActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ToggleActionBuilder(NAME, ownerName).toolBarGroup(GROUP).toolBarIcon(ICON);
return new ToggleActionBuilder(NAME, ownerName)
.toolBarGroup(GROUP)
.toolBarIcon(ICON);
}
}
interface SelectNoneAction {
String NAME = "Select None";
String GROUP = "Select";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.popupMenuGroup(GROUP)
.popupMenuPath(NAME);
}
}
@ -904,7 +971,8 @@ public interface DebuggerResources {
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName).toolBarGroup(GROUP)
return new ActionBuilder(NAME, ownerName)
.toolBarGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR))
.toolBarIcon(ICON);
}

View file

@ -0,0 +1,67 @@
/* ###
* 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 ghidra.app.plugin.core.debug.gui.console;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.table.TableCellEditor;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.ActionList;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
public class ConsoleActionsCellEditor extends AbstractCellEditor
implements TableCellEditor, ActionListener {
private static final ActionList EMPTY_ACTION_LIST = new ActionList();
protected final JPanel box = new JPanel();
protected final List<JButton> buttonCache = new ArrayList<>();
protected ActionList value;
public ConsoleActionsCellEditor() {
ConsoleActionsCellRenderer.configureBox(box);
}
@Override
public Object getCellEditorValue() {
return EMPTY_ACTION_LIST;
}
@Override
public Component getTableCellEditorComponent(JTable table, Object v, boolean isSelected,
int row, int column) {
// I can't think of when you'd be "editing" a non-selected cell.
box.setBackground(table.getSelectionBackground());
value = (ActionList) v;
ConsoleActionsCellRenderer.populateBox(box, buttonCache, value,
button -> button.addActionListener(this));
return box;
}
@Override
public void actionPerformed(ActionEvent e) {
int index = buttonCache.indexOf(e.getSource());
BoundAction action = value.get(index);
stopCellEditing();
action.perform();
}
}

View file

@ -0,0 +1,89 @@
/* ###
* 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 ghidra.app.plugin.core.debug.gui.console;
import java.awt.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.swing.*;
import docking.widgets.table.GTableCellRenderingData;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.ActionList;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.AbstractGhidraColumnRenderer;
public class ConsoleActionsCellRenderer extends AbstractGhidraColumnRenderer<ActionList> {
static void configureBox(JPanel box) {
box.setLayout(new BoxLayout(box, BoxLayout.X_AXIS));
box.setOpaque(true);
box.setAlignmentX(0.5f);
}
static void ensureCacheSize(List<JButton> buttonCache, int size,
Consumer<JButton> extraConfig) {
int diff = size - buttonCache.size();
for (int i = 0; i < diff; i++) {
JButton button = new JButton();
button.setMinimumSize(DebuggerConsoleProvider.ACTION_BUTTON_DIM);
button.setMaximumSize(DebuggerConsoleProvider.ACTION_BUTTON_DIM);
extraConfig.accept(button);
buttonCache.add(button);
}
}
static void populateBox(JPanel box, List<JButton> buttonCache, ActionList value,
Consumer<JButton> extraConfig) {
box.removeAll();
ensureCacheSize(buttonCache, value.size(), extraConfig);
int i = 0;
for (BoundAction a : value) {
JButton button = buttonCache.get(i);
button.setToolTipText(a.getTooltipText());
button.setIcon(a.getIcon());
button.setEnabled(a.isEnabled());
box.add(button);
i++;
}
}
protected final JPanel box = new JPanel();
protected final List<JButton> buttonCache = new ArrayList<>();
public ConsoleActionsCellRenderer() {
configureBox(box);
}
@Override
public String getFilterString(ActionList t, Settings settings) {
return t.stream().map(a -> a.getName()).collect(Collectors.joining(" "));
}
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data); // A bit of a waste, but sets the background
box.setBackground(getBackground());
ActionList value = (ActionList) data.getValue();
populateBox(box, buttonCache, value, button -> {
});
return box;
}
}

View file

@ -0,0 +1,130 @@
/* ###
* 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 ghidra.app.plugin.core.debug.gui.console;
import javax.swing.Icon;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.filter.LevelRangeFilter;
import docking.ActionContext;
import docking.action.DockingActionIf;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.services.DebuggerConsoleService;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo(
shortDescription = "Debugger console panel plugin",
description = "A tool-global console for controlling a debug/trace session",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
servicesRequired = {},
servicesProvided = {
DebuggerConsoleService.class,
})
public class DebuggerConsolePlugin extends Plugin implements DebuggerConsoleService {
protected static final String APPENDER_NAME = "debuggerAppender";
protected class ConsolePluginAppender extends AbstractAppender {
public ConsolePluginAppender() {
super(APPENDER_NAME, null, null, true, Property.EMPTY_ARRAY);
addFilter(LevelRangeFilter.createFilter(Level.FATAL, Level.INFO, null, null));
}
@Override
public void append(LogEvent event) {
String loggerName = event.getLoggerName();
if (loggerName.contains(".debug") ||
loggerName.contains(".dbg.") ||
loggerName.contains("agent.")) {
provider.logEvent(event);
}
}
}
protected DebuggerConsoleProvider provider;
protected final ConsolePluginAppender appender;
protected Logger rootLogger;
public DebuggerConsolePlugin(PluginTool tool) {
super(tool);
appender = new ConsolePluginAppender();
}
@Override
protected void init() {
super.init();
provider = new DebuggerConsoleProvider(this);
rootLogger = (Logger) LogManager.getRootLogger();
appender.start();
rootLogger.addAppender(appender);
}
@Override
protected void dispose() {
if (rootLogger != null) {
rootLogger.removeAppender(appender);
appender.stop();
provider.dispose();
tool.removeComponentProvider(provider);
}
super.dispose();
}
@Override
public void log(Icon icon, String message, ActionContext context) {
provider.log(icon, message, context);
}
@Override
public void remove(ActionContext context) {
provider.remove(context);
}
@Override
public void addResolutionAction(DockingActionIf action) {
provider.addResolutionAction(action);
}
@Override
public void removeResolutionAction(DockingActionIf action) {
provider.removeResolutionAction(action);
}
/**
* For testing: get the number of rows having a given class of action context
*
* @param ctxCls the context class
*/
public long getRowCount(Class<? extends ActionContext> ctxCls) {
return provider.getRowCount(ctxCls);
}
}

View file

@ -0,0 +1,487 @@
/* ###
* 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 ghidra.app.plugin.core.debug.gui.console;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.TableModelEvent;
import javax.swing.table.*;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LogEvent;
import docking.*;
import docking.action.DockingAction;
import docking.action.DockingActionIf;
import docking.actions.PopupActionProvider;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.CustomToStringCellRenderer;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.ClearAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectNoneAction;
import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.annotation.*;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.util.*;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
public class DebuggerConsoleProvider extends ComponentProviderAdapter
implements PopupActionProvider {
static final int ACTION_BUTTON_SIZE = 32;
static final Dimension ACTION_BUTTON_DIM =
new Dimension(ACTION_BUTTON_SIZE, ACTION_BUTTON_SIZE);
static final int MAX_ROW_HEIGHT = 300;
protected enum LogTableColumns implements EnumeratedTableColumn<LogTableColumns, LogRow> {
LEVEL("Level", Icon.class, LogRow::getIcon, SortDirection.ASCENDING, false),
MESSAGE("Message", String.class, LogRow::getMessage, SortDirection.ASCENDING, false),
ACTIONS("Actions", ActionList.class, LogRow::getActions, SortDirection.DESCENDING, true),
TIME("Time", Date.class, LogRow::getDate, SortDirection.DESCENDING, false);
private final String header;
private final Function<LogRow, ?> getter;
private final Class<?> cls;
private final SortDirection defaultSortDirection;
private final boolean editable;
<T> LogTableColumns(String header, Class<T> cls, Function<LogRow, T> getter,
SortDirection defaultSortDirection, boolean editable) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.defaultSortDirection = defaultSortDirection;
this.editable = editable;
}
@Override
public String getHeader() {
return header;
}
@Override
public Class<?> getValueClass() {
return cls;
}
@Override
public Object getValueOf(LogRow row) {
return getter.apply(row);
}
@Override
public boolean isEditable(LogRow row) {
return editable;
}
@Override
public void setValueOf(LogRow row, Object value) {
}
@Override
public SortDirection defaultSortDirection() {
return defaultSortDirection;
}
}
protected static class BoundAction {
protected final DockingActionIf action;
protected final ActionContext context;
public BoundAction(DockingActionIf action, ActionContext context) {
this.action = action;
this.context = context;
}
@Override
public String toString() {
return getName();
}
public String getName() {
return action.getName();
}
public Icon getIcon() {
return action.getToolBarData().getIcon();
}
public boolean isEnabled() {
return action.isEnabledForContext(context);
}
public String getTooltipText() {
return action.getDescription();
}
public void perform() {
action.actionPerformed(context);
}
}
protected static class ActionList extends ArrayList<BoundAction> {
}
protected static class LogRow {
private final Icon icon;
private final String message;
private final Date date;
private final ActionContext context;
private final ActionList actions;
public LogRow(Icon icon, String message, Date date, ActionContext context,
ActionList actions) {
this.icon = icon;
this.message = message;
this.date = date;
this.context = context;
this.actions = actions;
}
public Icon getIcon() {
return icon;
}
public String getMessage() {
return message;
}
public Date getDate() {
return date;
}
public ActionContext getActionContext() {
return context;
}
public ActionList getActions() {
return actions;
}
}
protected static class LogTableModel extends DebouncedRowWrappedEnumeratedColumnTableModel< //
LogTableColumns, ActionContext, LogRow, LogRow> {
public LogTableModel() {
super("Log", LogTableColumns.class, r -> r.getActionContext(), r -> r);
}
@Override
public java.util.List<LogTableColumns> defaultSortOrder() {
return java.util.List.of(LogTableColumns.ACTIONS, LogTableColumns.TIME);
}
}
protected static class LogTable extends GhidraTable {
public LogTable(LogTableModel model) {
super(model);
}
@Override
public void tableChanged(TableModelEvent e) {
super.tableChanged(e);
Swing.runIfSwingOrRunLater(() -> updateRowHeights());
}
@Override
public void columnMarginChanged(ChangeEvent e) {
super.columnMarginChanged(e);
// TODO: Debounce or otherwise delay this
Swing.runIfSwingOrRunLater(() -> updateRowHeights());
}
protected void updateRowHeights() {
// TODO: Be more selective in which rows
// Those changed
// Those visible?
TableModel model = getModel();
int rows = model.getRowCount();
int cols = getColumnCount();
for (int r = 0; r < rows; r++) {
int height = 0;
for (int c = 0; c < cols; c++) {
height = Math.max(height, computePreferredHeight(r, c));
}
setRowHeight(r, height);
}
}
protected int computePreferredHeight(int r, int c) {
TableCellRenderer renderer = getCellRenderer(r, c);
if (renderer instanceof ConsoleActionsCellRenderer) {
ActionList actions = (ActionList) getModel().getValueAt(r, c);
if (!actions.isEmpty()) {
return ACTION_BUTTON_SIZE;
}
return 0;
}
if (renderer instanceof CustomToStringCellRenderer<?>) {
CustomToStringCellRenderer<?> custom = (CustomToStringCellRenderer<?>) renderer;
int colWidth = getColumnModel().getColumn(c).getWidth();
prepareRenderer(renderer, r, c);
return custom.getRowHeight(colWidth);
}
return 0;
}
}
private final DebuggerConsolePlugin plugin;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@AutoOptionDefined(
name = DebuggerResources.OPTION_NAME_LOG_BUFFER_LIMIT,
description = "The maximum number of entries in the console log (0 or less for unlimited)",
help = @HelpInfo(anchor = "buffer_limit"))
private int logBufferLimit = DebuggerResources.DEFAULT_LOG_BUFFER_LIMIT;
@SuppressWarnings("unused")
private final AutoOptions.Wiring autoOptionsWiring;
protected final Map<String, Map<String, DockingActionIf>> actionsByOwnerThenName =
new LinkedHashMap<>();
protected final LogTableModel logTableModel = new LogTableModel();
protected GhidraTable logTable;
private GhidraTableFilterPanel<LogRow> logFilterPanel;
private Deque<LogRow> buffer = new ArrayDeque<>();
private final JPanel mainPanel = new JPanel(new BorderLayout());
DockingAction actionClear;
DockingAction actionSelectNone;
public DebuggerConsoleProvider(DebuggerConsolePlugin plugin) {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_CONSOLE, plugin.getName());
this.plugin = plugin;
tool.addPopupActionProvider(this);
setIcon(DebuggerResources.ICON_PROVIDER_CONSOLE);
setHelpLocation(DebuggerResources.HELP_PROVIDER_CONSOLE);
setWindowMenuGroup(DebuggerPluginPackage.NAME);
buildMainPanel();
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
this.autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
setDefaultWindowPosition(WindowPosition.BOTTOM);
setVisible(true);
createActions();
}
protected void dispose() {
tool.removePopupActionProvider(this);
}
protected void buildMainPanel() {
logTable = new LogTable(logTableModel);
logTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
mainPanel.add(new JScrollPane(logTable));
logFilterPanel = new GhidraTableFilterPanel<>(logTable, logTableModel);
mainPanel.add(logFilterPanel, BorderLayout.NORTH);
logTable.setRowHeight(ACTION_BUTTON_SIZE);
TableColumnModel columnModel = logTable.getColumnModel();
TableColumn levelCol = columnModel.getColumn(LogTableColumns.LEVEL.ordinal());
levelCol.setMaxWidth(24);
levelCol.setMinWidth(24);
TableColumn msgCol = columnModel.getColumn(LogTableColumns.MESSAGE.ordinal());
msgCol.setPreferredWidth(150);
msgCol.setCellRenderer(CustomToStringCellRenderer.HTML);
TableColumn actCol = columnModel.getColumn(LogTableColumns.ACTIONS.ordinal());
actCol.setPreferredWidth(50);
actCol.setCellRenderer(new ConsoleActionsCellRenderer());
actCol.setCellEditor(new ConsoleActionsCellEditor());
TableColumn timeCol = columnModel.getColumn(LogTableColumns.TIME.ordinal());
timeCol.setCellRenderer(CustomToStringCellRenderer.TIME_24HMSms);
timeCol.setPreferredWidth(15);
}
protected void createActions() {
actionClear = ClearAction.builder(plugin)
.onAction(this::activatedClear)
.buildAndInstallLocal(this);
actionSelectNone = SelectNoneAction.builder(plugin)
.popupWhen(ctx -> ctx.getSourceComponent() == logTable)
.onAction(this::activatedSelectNone)
.buildAndInstallLocal(this);
}
private void activatedClear(ActionContext ctx) {
synchronized (buffer) {
logTableModel.clear();
buffer.clear();
}
}
private void activatedSelectNone(ActionContext ctx) {
logTable.clearSelection();
}
@Override
public ActionContext getActionContext(MouseEvent event) {
if (logTable.getSelectedRowCount() != 1) {
return super.getActionContext(event);
}
LogRow sel = logFilterPanel.getSelectedItem();
if (sel == null) {
// I guess this can happen because of timing?
return super.getActionContext(event);
}
return sel.getActionContext();
}
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_LOG_BUFFER_LIMIT)
private void setLogBufferLimit(int logBufferLimit) {
truncateLog();
}
@Override
public JComponent getComponent() {
return mainPanel;
}
protected void truncateLog() {
synchronized (buffer) {
while (logBufferLimit > 0 && buffer.size() > logBufferLimit) {
logTableModel.deleteItem(buffer.removeFirst());
}
}
}
protected void log(Icon icon, String message, ActionContext context) {
logRow(new LogRow(icon, message, new Date(), context, computeToolbarActions(context)));
}
protected void logRow(LogRow row) {
synchronized (buffer) {
LogRow old = logTableModel.deleteKey(row.getActionContext());
if (old != null) {
buffer.remove(old);
}
logTableModel.addItem(row);
buffer.addLast(row);
truncateLog();
}
//logTable.scrollRectToVisible(new Rectangle(0, Integer.MAX_VALUE - 1, 1, 1));
}
protected Icon iconForLevel(Level level) {
if (level == Level.FATAL) {
return DebuggerResources.ICON_LOG_FATAL;
}
else if (level == Level.ERROR) {
return DebuggerResources.ICON_LOG_ERROR;
}
else if (level == Level.WARN) {
return DebuggerResources.ICON_LOG_WARN;
}
return null;
}
protected void logEvent(LogEvent event) {
ActionContext context = new LogRowConsoleActionContext();
logRow(new LogRow(iconForLevel(event.getLevel()),
"<html>" + HTMLUtilities.escapeHTML(event.getMessage().getFormattedMessage()),
new Date(event.getTimeMillis()), context, computeToolbarActions(context)));
}
protected void remove(ActionContext context) {
synchronized (buffer) {
LogRow r = logTableModel.deleteKey(context);
buffer.remove(r);
}
}
protected void addResolutionAction(DockingActionIf action) {
DockingActionIf replaced =
actionsByOwnerThenName.computeIfAbsent(action.getOwner(), o -> new LinkedHashMap<>())
.put(action.getName(), action);
if (replaced != null) {
Msg.warn(this, "Duplicate resolution action registered: " + action.getFullName());
}
}
protected void removeResolutionAction(DockingActionIf action) {
Map<String, DockingActionIf> byName = actionsByOwnerThenName.get(action.getOwner());
if (byName == null) {
Msg.warn(this, "Action to remove was never added: " + action.getFullName());
return;
}
DockingActionIf removed = byName.get(action.getName());
if (removed != action) {
if (removed != null) {
Msg.warn(this,
"Action to remove did not match that added: " + action.getFullName());
}
else {
Msg.warn(this, "Action to removed was never added: " + action.getFullName());
}
return;
}
if (byName.isEmpty()) {
actionsByOwnerThenName.remove(action.getOwner());
}
}
protected Stream<DockingActionIf> streamActions(ActionContext context) {
return actionsByOwnerThenName.values()
.stream()
.flatMap(m -> m.values().stream())
.filter(a -> a.isValidContext(context));
}
protected ActionList computeToolbarActions(ActionContext context) {
return streamActions(context)
.filter(a -> a.getToolBarData() != null)
.map(a -> new BoundAction(a, context))
.collect(Collectors.toCollection(ActionList::new));
}
@Override
public java.util.List<DockingActionIf> getPopupActions(Tool tool, ActionContext context) {
return streamActions(context)
.filter(a -> a.isAddToPopup(context))
.collect(Collectors.toList());
}
protected long getRowCount(Class<? extends ActionContext> ctxCls) {
return logTableModel.getModelData()
.stream()
.filter(r -> ctxCls.isInstance(r.context))
.count();
}
}

View file

@ -0,0 +1,22 @@
/* ###
* 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 ghidra.app.plugin.core.debug.gui.console;
import docking.ActionContext;
public class LogRowConsoleActionContext extends ActionContext {
}

View file

@ -31,19 +31,18 @@ import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo( //
shortDescription = "Debugger interpreter panel service", //
description = "Manage interpreter panels within debug sessions", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
servicesRequired = { //
InterpreterPanelService.class //
}, //
@PluginInfo(
shortDescription = "Debugger interpreter panel service",
description = "Manage interpreter panels within debug sessions",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
servicesRequired = {
InterpreterPanelService.class,
},
servicesProvided = {
DebuggerInterpreterService.class //
} //
)
DebuggerInterpreterService.class,
})
public class DebuggerInterpreterPlugin extends AbstractDebuggerPlugin
implements DebuggerInterpreterService {

View file

@ -342,7 +342,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
//cbGoTo.invoke(() -> {
DebuggerListingProvider provider = getConnectedProvider();
provider.doSyncToStatic(location);
provider.doAutoImportCurrentModule();
provider.doCheckCurrentModuleMissing();
//});
return true;
}

View file

@ -19,7 +19,6 @@ import static ghidra.app.plugin.core.debug.gui.DebuggerResources.ICON_REGISTER_M
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.OPTION_NAME_COLORS_REGISTER_MARKERS;
import java.awt.Color;
import java.io.File;
import java.lang.invoke.MethodHandles;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@ -50,6 +49,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec;
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec.TrackingSpecConfigFieldCodec;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
import ghidra.app.plugin.core.debug.utils.BackgroundUtils;
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
import ghidra.app.plugin.core.exporter.ExporterDialog;
@ -85,8 +85,7 @@ import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.*;
import utilities.util.SuppressableCallback;
import utilities.util.SuppressableCallback.Suppression;
@ -323,6 +322,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
//@AutoServiceConsumed via method
private DebuggerStaticMappingService mappingService;
@AutoServiceConsumed
private DebuggerConsoleService consoleService;
@AutoServiceConsumed
private ProgramManager programManager;
@AutoServiceConsumed
private FileImporterService importerService;
@ -349,12 +350,10 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
protected MultiStateDockingAction<LocationTrackingSpec> actionTrackLocation;
protected DockingAction actionGoTo;
protected SyncToStaticListingAction actionSyncToStaticListing;
protected ToggleDockingAction actionAutoImportCurrentModule;
protected FollowsCurrentThreadAction actionFollowsCurrentThread;
protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoReadMemory;
protected DockingAction actionExportView;
protected final DebuggerModuleImportDialog importDialog;
protected final DebuggerGoToDialog goToDialog;
@AutoConfigStateField(codec = TrackingSpecConfigFieldCodec.class)
@ -362,8 +361,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
@AutoConfigStateField
protected boolean syncToStaticListing;
@AutoConfigStateField
protected boolean autoImportCurrentModule;
@AutoConfigStateField
protected boolean followsCurrentThread = true;
@AutoConfigStateField(codec = AutoReadMemorySpecConfigFieldCodec.class)
protected AutoReadMemorySpec autoReadMemorySpec = defaultReadMemorySpec;
@ -389,7 +386,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
super(plugin, formatManager, isConnected);
this.plugin = plugin;
importDialog = new DebuggerModuleImportDialog(tool);
goToDialog = new DebuggerGoToDialog(this);
ListingPanel listingPanel = getListingPanel();
@ -403,7 +399,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
autoOptionsWiring = AutoOptions.wireOptionsConsumed(plugin, this);
syncToStaticListing = isConnected;
autoImportCurrentModule = isConnected;
setVisible(true);
createActions();
@ -473,12 +468,10 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
actionTrackLocation.setCurrentActionStateByUserData(trackingSpec);
if (isConnected()) {
actionSyncToStaticListing.setSelected(syncToStaticListing);
actionAutoImportCurrentModule.setSelected(autoImportCurrentModule);
followsCurrentThread = true;
}
else {
syncToStaticListing = false;
autoImportCurrentModule = false;
actionFollowsCurrentThread.setSelected(followsCurrentThread);
updateBorder();
}
@ -744,11 +737,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
if (isConnected()) {
actionSyncToStaticListing = new SyncToStaticListingAction();
actionAutoImportCurrentModule = AutoImportCurrentModuleAction.builder(plugin)
.enabledWhen(ctx -> syncToStaticListing)
.onAction(this::autoImportCurrentModuleActionToggled)
.selected(autoImportCurrentModule)
.buildAndInstallLocal(this);
}
else {
actionFollowsCurrentThread = new FollowsCurrentThreadAction();
@ -803,10 +791,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
doSetTrackingSpec(newState.getUserData());
}
protected void autoImportCurrentModuleActionToggled(ActionContext ctx) {
doSetAutoImportCurrentModule(actionAutoImportCurrentModule.isSelected());
}
protected void activatedAutoReadMemory(ActionContext ctx) {
doAutoReadMemory();
}
@ -891,7 +875,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
super.programLocationChanged(location, trigger);
if (trigger == EventTrigger.GUI_ACTION) {
doSyncToStatic(location);
doAutoImportCurrentModule();
doCheckCurrentModuleMissing();
}
}
@ -928,11 +912,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
}
}
protected void doAutoImportCurrentModule() {
if (!autoImportCurrentModule) {
return;
}
if (importerService == null) {
protected void doCheckCurrentModuleMissing() {
if (importerService == null || consoleService == null) {
return;
}
Trace trace = current.getTrace();
@ -962,8 +943,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
}
}
Map<TraceModule, File> missing = new LinkedHashMap<>();
Set<DomainFile> toOpen = new LinkedHashSet<>();
Set<TraceModule> missing = new HashSet<>();
Set<DomainFile> toOpen = new HashSet<>();
TraceModuleManager modMan = trace.getModuleManager();
Collection<TraceModule> modules = Stream.concat(
modMan.getModulesAt(snap, address).stream().filter(m -> m.getSections().isEmpty()),
@ -975,7 +956,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
for (TraceModule mod : modules) {
Set<DomainFile> matches = mappingService.findProbableModulePrograms(mod);
if (matches.isEmpty()) {
missing.put(mod, new File(mod.getName()));
missing.add(mod);
}
else {
toOpen.addAll(matches);
@ -988,7 +969,12 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
ProgramManager.OPEN_VISIBLE);
}
}
importDialog.addFiles(missing.values());
for (TraceModule mod : missing) {
consoleService.log(DebuggerResources.ICON_LOG_ERROR,
"<html>The module <b><tt>" + HTMLUtilities.escapeHTML(mod.getName()) +
"</tt></b> was not found in the project",
new DebuggerMissingModuleActionContext(mod));
}
/**
* Once the programs are opened, including those which are successfully imported, the
* section mapper should take over, eventually invoking callbacks to our mapping change
@ -1027,11 +1013,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
doSyncToStatic(getLocation());
}
protected void doSetAutoImportCurrentModule(boolean autoImport) {
this.autoImportCurrentModule = autoImport;
doAutoImportCurrentModule();
}
public boolean isSyncToStaticListing() {
return syncToStaticListing;
}
@ -1117,13 +1098,13 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
if (!syncToStaticListing || trackedStatic == null) {
Swing.runIfSwingOrRunLater(() -> {
goTo(curView, loc);
doAutoImportCurrentModule();
doCheckCurrentModuleMissing();
});
}
else {
Swing.runIfSwingOrRunLater(() -> {
goTo(curView, loc);
doAutoImportCurrentModule();
doCheckCurrentModuleMissing();
plugin.fireStaticLocationEvent(trackedStatic);
});
}

View file

@ -1,251 +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 ghidra.app.plugin.core.debug.gui.listing;
import java.awt.BorderLayout;
import java.io.File;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import javax.swing.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import docking.DialogComponentProvider;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.table.CellEditorUtils;
import docking.widgets.table.DefaultEnumeratedColumnTableModel;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.services.FileImporterService;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.Project;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.MessageType;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
public class DebuggerModuleImportDialog extends DialogComponentProvider {
static final String BLANK = "";
static final int BUTTON_SIZE = 32;
protected static class FileRow {
private final File file;
private boolean isIgnored;
public FileRow(File file) {
this.file = file;
}
public File getFile() {
return file;
}
public boolean isIgnored() {
return isIgnored;
}
public void setIgnored(boolean isIgnored) {
this.isIgnored = isIgnored;
}
}
protected static enum FileTableColumns
implements EnumeratedTableColumn<FileTableColumns, FileRow> {
REMOVE("Remove", String.class, m -> BLANK, (m, v) -> nop()),
IGNORE("Ignore", Boolean.class, FileRow::isIgnored, FileRow::setIgnored),
PATH("Path", File.class, FileRow::getFile),
IMPORT("Import", String.class, m -> BLANK, (m, v) -> nop());
private String header;
private Class<?> cls;
private Function<FileRow, ?> getter;
private BiConsumer<FileRow, Object> setter;
private static void nop() {
}
@SuppressWarnings("unchecked")
<T> FileTableColumns(String header, Class<T> cls,
Function<FileRow, T> getter, BiConsumer<FileRow, T> setter) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.setter = (BiConsumer<FileRow, Object>) setter;
}
<T> FileTableColumns(String header, Class<T> cls,
Function<FileRow, T> getter) {
this(header, cls, getter, null);
}
@Override
public String getHeader() {
return header;
}
@Override
public Class<?> getValueClass() {
return cls;
}
@Override
public Object getValueOf(FileRow row) {
return getter.apply(row);
}
@Override
public boolean isEditable(FileRow row) {
return setter != null;
}
@Override
public void setValueOf(FileRow row, Object value) {
setter.accept(row, value);
}
}
protected static class FileTableModel
extends DefaultEnumeratedColumnTableModel<FileTableColumns, FileRow> {
public FileTableModel() {
super("Suggested Files to Import", FileTableColumns.class);
}
}
private final PluginTool tool;
final FileTableModel fileTableModel = new FileTableModel();
private final Map<File, FileRow> map = new HashMap<>();
private GhidraTable fileTable;
private GhidraTableFilterPanel<FileRow> fileFilterPanel;
protected DebuggerModuleImportDialog(PluginTool tool) {
super("Suggested Modules to Import", false, true, false, false);
this.tool = tool;
populateComponents();
}
protected void populateComponents() {
JPanel panel = new JPanel(new BorderLayout());
fileTable = new GhidraTable(fileTableModel);
fileTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
panel.add(new JScrollPane(fileTable));
fileFilterPanel = new GhidraTableFilterPanel<>(fileTable, fileTableModel);
panel.add(fileFilterPanel, BorderLayout.SOUTH);
TableColumnModel columnModel = fileTable.getColumnModel();
TableColumn removeCol = columnModel.getColumn(FileTableColumns.REMOVE.ordinal());
CellEditorUtils.installButton(fileTable, fileFilterPanel, removeCol,
DebuggerResources.ICON_DELETE, BUTTON_SIZE, this::removeFile);
TableColumn ignoreCol = columnModel.getColumn(FileTableColumns.IGNORE.ordinal());
ignoreCol.setPreferredWidth(30);
TableColumn importCol = columnModel.getColumn(FileTableColumns.IMPORT.ordinal());
CellEditorUtils.installButton(fileTable, fileFilterPanel, importCol,
DebuggerResources.ICON_IMPORT, BUTTON_SIZE, this::importFile);
addWorkPanel(panel);
}
private void importFile(FileRow mod) {
FileImporterService importerService = tool.getService(FileImporterService.class);
if (importerService == null) {
setStatusText("No FileImporterService!", MessageType.ERROR);
return;
}
GhidraFileChooser chooser = new GhidraFileChooser(getComponent());
chooser.setSelectedFile(mod.getFile());
File file = chooser.getSelectedFile(); // Shows modal
if (file == null) { // Includes cancelled case
return;
}
Project activeProject = Objects.requireNonNull(AppInfo.getActiveProject());
DomainFolder root = activeProject.getProjectData().getRootFolder();
importerService.importFile(root, file);
removeFile(mod);
}
private void removeFile(FileRow mod) {
removeFiles(Set.of(mod.getFile()));
}
public void show() {
tool.showDialog(this);
}
/**
* Suggest files to import.
*
* <p>
* If this causes a change to the suggested file list, or the list is not currently showing, the
* dialog will be shown. The user may leave the list in the background to avoid being pestered
* again.
*
* @param files the collection of files to suggest importing
*/
public void addFiles(Collection<File> files) {
synchronized (map) {
List<FileRow> mods = new ArrayList<>();
for (File file : files) {
map.computeIfAbsent(file, f -> {
FileRow mod = new FileRow(f);
mods.add(mod);
return mod;
});
}
fileTableModel.addAll(mods);
// Do not steal focus if suggested files are already on screen, or ignored
boolean anyNotIgnored =
fileTableModel.getModelData().stream().anyMatch(r -> !r.isIgnored());
if (!mods.isEmpty() || (!isShowing() && anyNotIgnored)) {
show();
}
}
}
/**
* Remove suggested files from the dialog.
*
* <p>
* If this causes the list to become empty, the dialog is automatically hidden.
*
* @param files the collection of files to no longer suggest
*/
public void removeFiles(Collection<File> files) {
synchronized (map) {
Set<FileRow> mods = new HashSet<>();
for (File file : files) {
FileRow mod = map.remove(file);
if (mod != null) {
mods.add(mod);
}
}
fileTableModel.deleteWith(mods::contains);
if (fileTableModel.getModelData().isEmpty()) {
close();
}
}
}
}

View file

@ -0,0 +1,55 @@
/* ###
* 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 ghidra.app.plugin.core.debug.gui.modules;
import java.util.Objects;
import docking.ActionContext;
import ghidra.trace.model.modules.TraceModule;
public class DebuggerMissingModuleActionContext extends ActionContext {
private final TraceModule module;
private final int hashCode;
public DebuggerMissingModuleActionContext(TraceModule module) {
this.module = Objects.requireNonNull(module);
this.hashCode = Objects.hash(getClass(), module);
}
public TraceModule getModule() {
return module;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof DebuggerMissingModuleActionContext)) {
return false;
}
DebuggerMissingModuleActionContext that = (DebuggerMissingModuleActionContext) obj;
if (!this.module.equals(that.module)) {
return false;
}
return true;
}
}

View file

@ -25,23 +25,23 @@ import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo( //
shortDescription = "Debugger module and section manager", //
description = "GUI to manage modules and sections", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
eventsConsumed = {
ProgramActivatedPluginEvent.class, //
ProgramLocationPluginEvent.class, //
ProgramClosedPluginEvent.class, //
TraceActivatedPluginEvent.class, //
}, //
servicesRequired = { //
DebuggerModelService.class, //
DebuggerStaticMappingService.class, //
DebuggerTraceManagerService.class, //
ProgramManager.class, //
} //
shortDescription = "Debugger module and section manager", //
description = "GUI to manage modules and sections", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
eventsConsumed = {
ProgramActivatedPluginEvent.class, //
ProgramLocationPluginEvent.class, //
ProgramClosedPluginEvent.class, //
TraceActivatedPluginEvent.class, //
}, //
servicesRequired = { //
DebuggerModelService.class, //
DebuggerStaticMappingService.class, //
DebuggerTraceManagerService.class, //
ProgramManager.class, //
} //
)
public class DebuggerModulesPlugin extends AbstractDebuggerPlugin {
protected DebuggerModulesProvider provider;
@ -58,6 +58,7 @@ public class DebuggerModulesPlugin extends AbstractDebuggerPlugin {
@Override
protected void dispose() {
provider.dispose();
tool.removeComponentProvider(provider);
super.dispose();
}

View file

@ -458,15 +458,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
return;
}
TraceModule mod = modules.iterator().next();
GhidraFileChooser chooser = new GhidraFileChooser(getComponent());
chooser.setSelectedFile(new File(mod.getName()));
File file = chooser.getSelectedFile();
if (file == null) { // Perhaps cancelled
return;
}
Project activeProject = Objects.requireNonNull(AppInfo.getActiveProject());
DomainFolder root = activeProject.getProjectData().getRootFolder();
importerService.importFile(root, file);
importModuleFromFileSystem(mod);
}
@Override
@ -508,6 +500,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
@AutoServiceConsumed
private DebuggerListingService listingService;
@AutoServiceConsumed
private DebuggerConsoleService consoleService;
@AutoServiceConsumed
ProgramManager programManager;
@AutoServiceConsumed
private GoToService goToService;
@ -551,6 +545,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
DockingAction actionMapSectionTo;
DockingAction actionMapSectionsTo;
DockingAction actionImportMissingModule;
DockingAction actionMapMissingModule;
SelectAddressesAction actionSelectAddresses;
CaptureTypesAction actionCaptureTypes;
CaptureSymbolsAction actionCaptureSymbols;
@ -580,6 +577,18 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
createActions();
}
private void importModuleFromFileSystem(TraceModule module) {
GhidraFileChooser chooser = new GhidraFileChooser(getComponent());
chooser.setSelectedFile(new File(module.getName()));
File file = chooser.getSelectedFile();
if (file == null) { // Perhaps cancelled
return;
}
Project activeProject = Objects.requireNonNull(AppInfo.getActiveProject());
DomainFolder root = activeProject.getProjectData().getRootFolder();
importerService.importFile(root, file);
}
@AutoServiceConsumed
private void setModelService(DebuggerModelService modelService) {
if (this.modelService != null) {
@ -592,6 +601,29 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
contextChanged();
}
@AutoServiceConsumed
private void setConsoleService(DebuggerConsoleService consoleService) {
if (consoleService != null) {
if (actionImportMissingModule != null) {
consoleService.addResolutionAction(actionImportMissingModule);
}
if (actionMapMissingModule != null) {
consoleService.addResolutionAction(actionMapMissingModule);
}
}
}
protected void dispose() {
if (consoleService != null) {
if (actionImportMissingModule != null) {
consoleService.removeResolutionAction(actionImportMissingModule);
}
if (actionMapMissingModule != null) {
consoleService.removeResolutionAction(actionMapMissingModule);
}
}
}
@Override
public ActionContext getActionContext(MouseEvent event) {
if (myActionContext == null) {
@ -743,6 +775,15 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
.onAction(this::activatedMapSectionsTo)
.buildAndInstallLocal(this);
actionImportMissingModule = ImportMissingModuleAction.builder(plugin)
.withContext(DebuggerMissingModuleActionContext.class)
.onAction(this::activatedImportMissingModule)
.build();
actionMapMissingModule = MapMissingModuleAction.builder(plugin)
.withContext(DebuggerMissingModuleActionContext.class)
.onAction(this::activatedMapMissingModule)
.build();
actionSelectAddresses = new SelectAddressesAction();
actionCaptureTypes = new CaptureTypesAction();
actionCaptureSymbols = new CaptureSymbolsAction();
@ -775,6 +816,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
private boolean isContextSectionsOfOneModule(ActionContext ignored) {
Set<TraceSection> sel = getSelectedSections(myActionContext);
if (sel == null || sel.isEmpty()) {
return false;
}
return sel.stream().map(TraceSection::getModule).distinct().count() == 1;
}
@ -818,6 +862,19 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
mapSectionTo(sel.iterator().next());
}
private void activatedImportMissingModule(DebuggerMissingModuleActionContext context) {
if (importerService == null) {
Msg.error(this, "Import service is not present");
}
importModuleFromFileSystem(context.getModule());
consoleService.remove(context); // TODO: Should remove when mapping is created
}
private void activatedMapMissingModule(DebuggerMissingModuleActionContext context) {
mapModuleTo(context.getModule());
consoleService.remove(context); // TODO: Should remove when mapping is created
}
private void toggledFilter(ActionContext ignored) {
if (actionFilterSectionsByModules.isSelected()) {
sectionFilterPanel.setSecondaryFilter(filterSectionsBySelectedModules);

View file

@ -1622,6 +1622,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
if (visibleChange) {
container.propagateProvider(DebuggerObjectsProvider.this);
update(container);
getComponent().repaint();
}
}
}
@ -1645,6 +1646,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
if (visibleChange) {
container.propagateProvider(DebuggerObjectsProvider.this);
update(container);
getComponent().repaint();
}
}
if (parent != null && isAutorecord() &&

View file

@ -159,14 +159,14 @@ public class DebuggerModelServicePlugin extends Plugin
protected class ListenerOnRecorders implements TraceRecorderListener {
@Override
public void snapAdvanced(TraceRecorder recorder, long snap) {
TimedMsg.info(this, "Got snapAdvanced callback");
TimedMsg.debug(this, "Got snapAdvanced callback");
fireSnapEvent(recorder, snap);
List<DebuggerModelServiceProxyPlugin> copy;
synchronized (proxies) {
copy = List.copyOf(proxies);
}
for (DebuggerModelServiceProxyPlugin proxy : copy) {
TimedMsg.info(this, "Firing SnapEvent on " + proxy);
TimedMsg.debug(this, "Firing SnapEvent on " + proxy);
proxy.fireSnapEvent(recorder, snap);
}
}

View file

@ -264,7 +264,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
long snap = recorder.getSnap();
String path = bank.getJoinedPath(".");
TimedMsg.info(this, "Reg values changed: " + updates.keySet());
TimedMsg.debug(this, "Reg values changed: " + updates.keySet());
recorder.parTx.execute("Registers " + path + " changed", () -> {
TraceCodeManager codeManager = trace.getCodeManager();
TraceCodeRegisterSpace codeRegisterSpace =
@ -377,7 +377,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
if (targetRange == null) {
return AsyncUtils.NIL;
}
TimedMsg.info(this,
TimedMsg.debug(this,
" Reading memory at " + name + " (" + targetAddress + " -> " + targetRange + ")");
// NOTE: Recorder takes data via memoryUpdated callback
// TODO: In that callback, sort out process memory from thread memory?

View file

@ -119,7 +119,7 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener {
if (!valid) {
return;
}
TimedMsg.info(this, "Event: " + type + " thread=" + eventThread + " description=" +
TimedMsg.debug(this, "Event: " + type + " thread=" + eventThread + " description=" +
description + " params=" + parameters);
// Just use this to step the snaps. Creation/destruction still handled in add/remove
if (eventThread == null) {
@ -162,7 +162,7 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener {
if (!valid) {
return;
}
TimedMsg.info(this, "State " + state + " for " + stateful);
TimedMsg.debug(this, "State " + state + " for " + stateful);
TargetObject x = recorder.objectManager.findThreadOrProcess(stateful);
if (x != null) {
if (x == target && state == TargetExecutionState.TERMINATED) {
@ -204,7 +204,7 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener {
}
Address traceAddr = recorder.getMemoryMapper().targetToTrace(address);
long snap = recorder.getSnap();
TimedMsg.info(this, "Memory updated: " + address + " (" + data.length + ")");
TimedMsg.debug(this, "Memory updated: " + address + " (" + data.length + ")");
String path = memory.getJoinedPath(".");
recorder.parTx.execute("Memory observed: " + path, () -> {
memoryManager.putBytes(snap, traceAddr, ByteBuffer.wrap(data));

View file

@ -618,7 +618,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
else if (event instanceof TraceRecorderAdvancedPluginEvent) {
TraceRecorderAdvancedPluginEvent ev = (TraceRecorderAdvancedPluginEvent) event;
TimedMsg.info(this, "Processing trace-advanced event");
TimedMsg.debug(this, "Processing trace-advanced event");
doTraceRecorderAdvanced(ev.getRecorder(), ev.getSnap());
}
}
@ -1016,6 +1016,19 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
recorder.requestFocus(focus);
}
public void activateNoFocusChange(DebuggerCoordinates coordinates) {
DebuggerCoordinates prev;
DebuggerCoordinates resolved;
synchronized (listenersByTrace) {
prev = current;
resolved = doSetCurrent(coordinates);
}
if (resolved == null) {
return;
}
prepareViewAndFireEvent(resolved);
}
@Override
public void activateTrace(Trace trace) {
activate(DebuggerCoordinates.trace(trace));
@ -1028,7 +1041,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@Override
public void activateSnap(long snap) {
activate(DebuggerCoordinates.snap(snap));
activateNoFocusChange(DebuggerCoordinates.snap(snap));
}
@Override

View file

@ -0,0 +1,87 @@
/* ###
* 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 ghidra.app.services;
import javax.swing.Icon;
import docking.ActionContext;
import docking.action.DockingActionIf;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
import ghidra.dbg.DebuggerConsoleLogger;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.util.HTMLUtilities;
@ServiceInfo(defaultProvider = DebuggerConsolePlugin.class)
public interface DebuggerConsoleService extends DebuggerConsoleLogger {
/**
* Log an actionable message to the console
*
* <p>
* <b>WARNING:</b> The log accepts and will interpret HTML in its messages, allowing a rich and
* flexible display; however, you MUST sanitize any content derived from the user or target. We
* recommend using {@link HTMLUtilities#escapeHTML(String)}.
*
* @param icon an icon for the message
* @param message the HTML-formatted message
* @param context an (immutable) context for actions
*/
void log(Icon icon, String message, ActionContext context);
/**
* Remove an actionable message from the console
*
* <p>
* It is common courtesy to remove the entry when the user has resolved the issue, whether via
* the presented actions, or some other means. The framework does not do this automatically,
* because simply activating an action does not imply the issue will be resolved.
*
* @param context the context of the entry to remove
*/
void remove(ActionContext context);
/**
* Add an action which might be applied to an actionable log message
*
* <p>
* Please invoke this method from the Swing thread. Only toolbar and pop-up menu placement is
* considered. Toolbar actions are placed as icon-only buttons in the "Actions" column for any
* log message where the action is applicable to the context given for that message. Pop-up
* actions are placed in the context menu when a single message is selected and the action is
* applicable to its given context. In most cases, the action should be presented both as a
* button and as a pop-up menu. Less commonly, an action may be presented only as a pop-up,
* likely because it is an uncommon resolution, or because you don't want the user to activated
* it accidentally. Rarely, if ever, should an action be a button, but not in the menu. The user
* may expect the menu to give more complete descriptions of actions presented as buttons.
*
* <p>
* <b>IMPORTANT:</b> Unlike other action managers, you are required to remove your actions upon
* plugin disposal.
*
* @param action the action
*/
void addResolutionAction(DockingActionIf action);
/**
* Remove an action
*
* <p>
* Please invoke this method from the Swing thread.
*
* @param action the action
*/
void removeResolutionAction(DockingActionIf action);
}

View file

@ -0,0 +1,77 @@
/* ###
* 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 ghidra.app.plugin.core.debug.gui.console;
import static org.junit.Assert.assertEquals;
import org.junit.*;
import org.junit.rules.TestName;
import docking.ActionContext;
import docking.action.builder.ActionBuilder;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.util.Msg;
import help.screenshot.GhidraScreenShotGenerator;
public class DebuggerConsolePluginScreenShots extends GhidraScreenShotGenerator {
public static class ScreenShotActionContext extends ActionContext {
}
DebuggerConsolePlugin consolePlugin;
DebuggerConsoleProvider consoleProvider;
@Rule
public TestName name = new TestName();
@Before
public void setUpMine() throws Throwable {
consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class);
consoleProvider = waitForComponentProvider(DebuggerConsoleProvider.class);
consolePlugin.addResolutionAction(new ActionBuilder("Import", name.getMethodName())
.toolBarIcon(DebuggerResources.ICON_IMPORT)
.popupMenuIcon(DebuggerResources.ICON_IMPORT)
.popupMenuPath("Map")
.description("Import")
.withContext(ScreenShotActionContext.class)
.onAction(ctx -> Msg.info(this, "Import clicked"))
.build());
consolePlugin.addResolutionAction(new ActionBuilder("Map", name.getMethodName())
.toolBarIcon(DebuggerResources.ICON_MODULES)
.popupMenuIcon(DebuggerResources.ICON_MODULES)
.popupMenuPath("Map")
.description("Map")
.withContext(ScreenShotActionContext.class)
.onAction(ctx -> Msg.info(this, "Map clicked"))
.build());
}
@Test
public void testCaptureDebuggerConsolePlugin() throws Throwable {
Msg.warn(this, "This is a warning message");
Msg.error(this, "This is an error message");
consolePlugin.log(DebuggerResources.ICON_DEBUGGER,
"<html>You can take <b>action</b> to resolve this message</html>",
new ScreenShotActionContext());
AbstractGhidraHeadedDebuggerGUITest
.waitForPass(() -> assertEquals(3, consolePlugin.getRowCount(ActionContext.class)));
captureIsolatedProvider(consoleProvider, 600, 300);
}
}

View file

@ -31,7 +31,6 @@ import ghidra.test.ToyProgramBuilder;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.symbol.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.UndoableTransaction;
@ -140,36 +139,4 @@ public class DebuggerListingPluginScreenShots extends GhidraScreenShotGenerator
captureDialog(dialog);
}
@Test
public void testCaptureDebuggerModuleImportDialog() throws Throwable {
try (UndoableTransaction tid = tb.startTransaction()) {
long snap = tb.trace.getTimeManager().createSnapshot("First").getKey();
tb.trace.getMemoryManager()
.addRegion("bash:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
tb.trace.getMemoryManager()
.addRegion("libc:.text", Range.atLeast(0L), tb.range(0x7fac0000, 0x7facffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
TraceModule bin = tb.trace.getModuleManager()
.addLoadedModule("/bin/bash", "/bin/bash",
tb.range(0x00400000, 0x0040ffff), snap);
bin.addSection("bash[.text]", tb.range(0x00400000, 0x0040ffff));
TraceModule lib = tb.trace.getModuleManager()
.addLoadedModule("/lib/libc.so.6", "/lib/libc.so.6",
tb.range(0x7fac0000, 0x7facffff), snap);
lib.addSection("libc[.text]", tb.range(0x7fac0000, 0x7facffff));
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
listingPlugin.goTo(tb.addr(0x7fac1234), true);
waitForSwing();
listingPlugin.goTo(tb.addr(0x00401234), true);
waitForSwing();
captureDialog(DebuggerModuleImportDialog.class);
}
}
}

View file

@ -0,0 +1,78 @@
/* ###
* 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 ghidra.app.plugin.core.debug.gui.console;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
import docking.ActionContext;
import docking.action.builder.ActionBuilder;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.util.Msg;
public class DebuggerConsoleProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
DebuggerConsolePlugin consolePlugin;
DebuggerConsoleProvider consoleProvider;
@Before
public void setUpConsoleProviderTest() throws Exception {
consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class);
consoleProvider = waitForComponentProvider(DebuggerConsoleProvider.class);
}
public static class TestConsoleActionContext extends ActionContext {
}
@Test
public void testActions() throws Exception {
consolePlugin.addResolutionAction(new ActionBuilder("Add", name.getMethodName())
.toolBarIcon(DebuggerResources.ICON_ADD)
.description("Add")
.withContext(TestConsoleActionContext.class)
.onAction(ctx -> Msg.info(this, "Add clicked"))
.build());
consolePlugin.addResolutionAction(new ActionBuilder("Delete", name.getMethodName())
.popupMenuIcon(DebuggerResources.ICON_DELETE)
.popupMenuPath("Delete")
.description("Delete")
.withContext(TestConsoleActionContext.class)
.onAction(ctx -> Msg.info(this, "Delete clicked"))
.build());
consolePlugin.log(DebuggerResources.ICON_DEBUGGER, "<html><b>Test message</b></html>",
new TestConsoleActionContext());
consolePlugin.log(DebuggerResources.ICON_DEBUGGER, "Test message 2",
new TestConsoleActionContext());
waitForPass(() -> assertEquals(2, consoleProvider.logTable.getRowCount()));
}
@Test
public void testHTMLLabel() throws Exception {
consolePlugin.log(DebuggerResources.ICON_DEBUGGER,
"<html><b>A rather lengthy test message. " +
"Here's some more text just to prove it!</b></html>",
new TestConsoleActionContext());
consolePlugin.log(DebuggerResources.ICON_DEBUGGER, "Test message 2",
new TestConsoleActionContext());
waitForPass(() -> assertEquals(2, consoleProvider.logTable.getRowCount()));
}
}

View file

@ -36,6 +36,8 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.SwingExecutorService;
@ -1078,9 +1080,9 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
}
@Test
public void testActionAutoImportCurrentModuleWithSections() throws Exception {
public void testPromptImportCurrentModuleWithSections() throws Exception {
addPlugin(tool, ImporterPlugin.class);
assertTrue(listingProvider.actionAutoImportCurrentModule.isEnabled());
DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class);
createAndOpenTrace();
try (UndoableTransaction tid = tb.startTransaction()) {
@ -1099,16 +1101,19 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
// In the module, but not in its section
listingPlugin.goTo(tb.addr(0x00411234), true);
waitForSwing();
assertFalse(listingProvider.importDialog.isVisible());
waitForPass(() -> assertEquals(0,
consolePlugin.getRowCount(DebuggerMissingModuleActionContext.class)));
listingPlugin.goTo(tb.addr(0x00401234), true);
waitForDialogComponent(DebuggerModuleImportDialog.class);
waitForSwing();
waitForPass(() -> assertEquals(1,
consolePlugin.getRowCount(DebuggerMissingModuleActionContext.class)));
}
@Test
public void testActionAutoImportCurrentModuleWithoutSections() throws Exception {
public void testPromptImportCurrentModuleWithoutSections() throws Exception {
addPlugin(tool, ImporterPlugin.class);
assertTrue(listingProvider.actionAutoImportCurrentModule.isEnabled());
DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class);
createAndOpenTrace();
try (UndoableTransaction tid = tb.startTransaction()) {
@ -1116,7 +1121,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
.addRegion("bash:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0041ffff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
TraceModule bin = tb.trace.getModuleManager()
tb.trace.getModuleManager()
.addLoadedModule("/bin/bash", "/bin/bash",
tb.range(0x00400000, 0x0041ffff), 0);
@ -1125,7 +1130,9 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
// In the module, but not in its section
listingPlugin.goTo(tb.addr(0x00411234), true);
waitForDialogComponent(DebuggerModuleImportDialog.class);
waitForSwing();
waitForPass(() -> assertEquals(1,
consolePlugin.getRowCount(DebuggerMissingModuleActionContext.class)));
}
@Test

View file

@ -0,0 +1,20 @@
/* ###
* 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 ghidra.dbg;
public interface DebuggerConsoleLogger {
}

View file

@ -80,15 +80,17 @@ public abstract class AbstractTargetObject<P extends TargetObject> implements Sp
this.path = PathUtils.extend(parent.getPath(), key);
}
model.removeExisting(path);
synchronized (model.lock) {
model.removeExisting(path);
this.hash = computeHashCode();
this.typeHint = typeHint;
this.hash = computeHashCode();
this.typeHint = typeHint;
this.schema = schema;
this.proxy = proxyFactory.createProxy(this, proxyInfo);
this.schema = schema;
this.proxy = proxyFactory.createProxy(this, proxyInfo);
fireCreated();
fireCreated();
}
}
public AbstractTargetObject(AbstractDebuggerObjectModel model, P parent, String key,

View file

@ -28,6 +28,20 @@ import ghidra.dbg.util.ConfigurableFactory.Property;
import ghidra.dbg.util.PathUtils.PathComparator;
public abstract class AbstractModelHost implements ModelHost, DebuggerModelTestUtils {
public class WithoutThreadValidationImpl implements WithoutThreadValidation {
public WithoutThreadValidationImpl() {
withoutThreadValCount++;
}
@Override
public void close() throws Exception {
withoutThreadValCount--;
}
}
private int withoutThreadValCount = 0;
protected DebuggerObjectModel model;
public CallbackValidator callbackValidator;
public EventValidator eventValidator;
@ -74,7 +88,7 @@ public abstract class AbstractModelHost implements ModelHost, DebuggerModelTestU
@Override
public void validateCompletionThread() {
if (callbackValidator != null) {
if (callbackValidator != null && withoutThreadValCount == 0) {
callbackValidator.validateCompletionThread();
}
}
@ -169,6 +183,11 @@ public abstract class AbstractModelHost implements ModelHost, DebuggerModelTestU
return true;
}
@Override
public WithoutThreadValidation withoutThreadValidation() {
return new WithoutThreadValidationImpl();
}
@Override
public TargetObjectAddedWaiter getAddedWaiter() {
return waiter;

View file

@ -276,7 +276,7 @@ public class CallbackValidator implements DebuggerModelListener, AutoCloseable {
fail("created twice (same object): " + object.getJoinedPath("."));
}
else {
fail("replaced before invalidation. old= " + exists + ", new=" + object);
fail("replaced before invalidation. old=" + exists.object + ", new=" + object);
}
}
validateCallbackThread("created");

View file

@ -23,6 +23,9 @@ import ghidra.dbg.target.TargetObject;
public interface TestDebuggerModelProvider {
interface ModelHost extends AutoCloseable {
interface WithoutThreadValidation extends AutoCloseable {
}
Map<String, Object> getFactoryOptions();
ModelHost build() throws Throwable;
@ -57,6 +60,8 @@ public interface TestDebuggerModelProvider {
boolean hasProcessContainer();
WithoutThreadValidation withoutThreadValidation();
<T extends TargetObject> T find(Class<T> cls, List<String> seedPath) throws Throwable;
/**

View file

@ -165,7 +165,7 @@ public class TraceDomainObjectListener implements DomainObjectListener {
for (DomainObjectChangeRecord rec : ev) {
if (rec.getEventType() == DomainObject.DO_OBJECT_RESTORED) {
restoredHandler.accept(rec);
TimedMsg.info(this, " Done: OBJECT_RESTORED");
TimedMsg.debug(this, " Done: OBJECT_RESTORED");
return;
}
}

View file

@ -328,14 +328,16 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
@Override
public <T> T getTop(Function<Long, T> func) {
synchronized (ordered) {
for (Range<Long> rng : ordered) {
T t = func.apply(rng.upperEndpoint());
if (t != null) {
return t;
try (LockHold hold = trace.lockRead()) {
synchronized (ordered) {
for (Range<Long> rng : ordered) {
T t = func.apply(rng.upperEndpoint());
if (t != null) {
return t;
}
}
return null;
}
return null;
}
}

View file

@ -18,10 +18,15 @@ package docking.widgets.table;
import java.awt.Component;
import java.awt.Font;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.function.BiFunction;
import javax.swing.JTable;
import javax.swing.*;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.table.TableModel;
import javax.swing.text.View;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.AbstractGColumnRenderer;
@ -40,6 +45,14 @@ public class CustomToStringCellRenderer<T> extends AbstractGColumnRenderer<T> {
return v.signum() < 0 ? "-0x" + v.negate().toString(16) : "0x" + v.toString(16);
}
public static final DateFormat TIME_FORMAT_24HMSms = new SimpleDateFormat("HH:mm:ss.SSS");
public static final CustomToStringCellRenderer<Date> TIME_24HMSms =
new CustomToStringCellRenderer<>(CustomFont.DEFAULT, Date.class,
(v, s) -> v == null ? "<null>" : TIME_FORMAT_24HMSms.format(v), false);
public static final CustomToStringCellRenderer<String> HTML =
new CustomToStringCellRenderer<String>(CustomFont.DEFAULT, String.class,
(v, s) -> v == null ? "<null>" : v, true);
public static final CustomToStringCellRenderer<Object> MONO_OBJECT =
new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Object.class,
(v, s) -> v == null ? "<null>" : v.toString(), false);
@ -60,6 +73,9 @@ public class CustomToStringCellRenderer<T> extends AbstractGColumnRenderer<T> {
private final Class<T> cls;
private final BiFunction<T, Settings, String> toString;
private final JPanel panelForSize = new JPanel();
private final BoxLayout layoutForSize = new BoxLayout(panelForSize, BoxLayout.Y_AXIS);
public CustomToStringCellRenderer(Class<T> cls, BiFunction<T, Settings, String> toString,
boolean enableHtml) {
this(null, cls, toString, enableHtml);
@ -71,6 +87,8 @@ public class CustomToStringCellRenderer<T> extends AbstractGColumnRenderer<T> {
this.customFont = font;
this.cls = cls;
this.toString = toString;
panelForSize.setLayout(layoutForSize);
}
@Override
@ -100,6 +118,21 @@ public class CustomToStringCellRenderer<T> extends AbstractGColumnRenderer<T> {
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);
setText(toString.apply(cls.cast(data.getValue()), data.getColumnSettings()));
if (getHTMLRenderingEnabled()) {
setVerticalAlignment(SwingConstants.TOP);
}
else {
setVerticalAlignment(SwingConstants.CENTER);
}
return this;
}
public int getRowHeight(int colWidth) {
View v = (View) getClientProperty(BasicHTML.propertyKey);
if (v == null) {
return 0;
}
v.setSize(colWidth, Short.MAX_VALUE);
return (int) v.getPreferredSpan(View.Y_AXIS);
}
}

View file

@ -163,18 +163,22 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
R row = modelData.get(rowIndex);
C col = cols[columnIndex];
Class<?> cls = col.getValueClass();
col.setValueOf(row, cls.cast(aValue));
synchronized (modelData) {
R row = modelData.get(rowIndex);
C col = cols[columnIndex];
Class<?> cls = col.getValueClass();
col.setValueOf(row, cls.cast(aValue));
}
fireTableCellUpdated(rowIndex, columnIndex);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
R row = modelData.get(rowIndex);
C col = cols[columnIndex];
return col.isEditable(row);
synchronized (modelData) {
R row = modelData.get(rowIndex);
C col = cols[columnIndex];
return col.isEditable(row);
}
}
@Override
@ -200,35 +204,47 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
@Override
public void add(R row) {
int rowIndex = modelData.size();
modelData.add(row);
int rowIndex;
synchronized (modelData) {
rowIndex = modelData.size();
modelData.add(row);
}
fireTableRowsInserted(rowIndex, rowIndex);
}
@Override
public void addAll(Collection<R> c) {
int startIndex = modelData.size();
modelData.addAll(c);
int endIndex = modelData.size() - 1;
int startIndex;
int endIndex;
synchronized (modelData) {
startIndex = modelData.size();
modelData.addAll(c);
endIndex = modelData.size() - 1;
}
fireTableRowsInserted(startIndex, endIndex);
}
@Override
public void notifyUpdated(R row) {
int rowIndex = modelData.indexOf(row);
int rowIndex;
synchronized (modelData) {
rowIndex = modelData.indexOf(row);
}
fireTableRowsUpdated(rowIndex, rowIndex);
}
@Override
public List<R> notifyUpdatedWith(Predicate<R> predicate) {
int lastIndexUpdated = 0;
ListIterator<R> rit = modelData.listIterator();
List<R> updated = new ArrayList<>();
while (rit.hasNext()) {
R row = rit.next();
if (predicate.test(row)) {
lastIndexUpdated = rit.previousIndex();
updated.add(row);
ListIterator<R> rit = modelData.listIterator();
synchronized (modelData) {
while (rit.hasNext()) {
R row = rit.next();
if (predicate.test(row)) {
lastIndexUpdated = rit.previousIndex();
updated.add(row);
}
}
}
int size = updated.size();
@ -245,25 +261,30 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
@Override
public void delete(R row) {
int rowIndex = modelData.indexOf(row);
if (rowIndex == -1) {
return;
int rowIndex;
synchronized (modelData) {
rowIndex = modelData.indexOf(row);
if (rowIndex == -1) {
return;
}
modelData.remove(rowIndex);
}
modelData.remove(rowIndex);
fireTableRowsDeleted(rowIndex, rowIndex);
}
@Override
public List<R> deleteWith(Predicate<R> predicate) {
int lastIndexRemoved = 0;
ListIterator<R> rit = modelData.listIterator();
List<R> removed = new ArrayList<>();
while (rit.hasNext()) {
R row = rit.next();
if (predicate.test(row)) {
lastIndexRemoved = rit.previousIndex();
rit.remove();
removed.add(row);
synchronized (modelData) {
ListIterator<R> rit = modelData.listIterator();
while (rit.hasNext()) {
R row = rit.next();
if (predicate.test(row)) {
lastIndexRemoved = rit.previousIndex();
rit.remove();
removed.add(row);
}
}
}
int size = removed.size();
@ -280,17 +301,28 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
@Override
public R findFirst(Predicate<R> predicate) {
for (R row : modelData) {
if (predicate.test(row)) {
return row;
synchronized (modelData) {
for (R row : modelData) {
if (predicate.test(row)) {
return row;
}
}
return null;
}
return null;
}
@Override
public void clear() {
modelData.clear();
synchronized (modelData) {
modelData.clear();
}
fireTableDataChanged();
}
@Override
protected void sort(List<R> data, TableSortingContext<R> sortingContext) {
synchronized (data) {
super.sort(data, sortingContext);
}
}
}

View file

@ -47,8 +47,12 @@ public class RowWrappedEnumeratedColumnTableModel<C extends Enum<C> & Enumerated
return map.computeIfAbsent(keyFunc.apply(t), k -> wrapper.apply(t));
}
protected synchronized R delFor(T t) {
return map.remove(keyFunc.apply(t));
protected R delFor(T t) {
return delKey(keyFunc.apply(t));
}
protected synchronized R delKey(K k) {
return map.remove(k);
}
protected synchronized List<R> rowsFor(Collection<? extends T> c) {
@ -79,9 +83,15 @@ public class RowWrappedEnumeratedColumnTableModel<C extends Enum<C> & Enumerated
delete(delFor(t));
}
public R deleteKey(K k) {
R r = delKey(k);
delete(r);
return r;
}
public synchronized void deleteAllItems(Collection<T> c) {
deleteWith(rowsFor(c)::contains);
map.keySet().removeAll(c);
map.keySet().removeAll(c.stream().map(keyFunc).collect(Collectors.toList()));
}
public synchronized Map<K, R> getMap() {

View file

@ -34,7 +34,7 @@ public class TimedMsg {
}
}
public static void info(Object originator, String message) {
doMsg(Msg::info, originator, message);
public static void debug(Object originator, String message) {
doMsg(Msg::debug, originator, message);
}
}