diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetConfigurable.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetConfigurable.java new file mode 100644 index 0000000000..5d0101239a --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface1/DbgModelTargetConfigurable.java @@ -0,0 +1,28 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package agent.dbgeng.model.iface1; + +import agent.dbgeng.model.iface2.DbgModelTargetObject; +import ghidra.dbg.target.TargetConfigurable; + +/** + * An interface which indicates this object is configurable. + * + * @param type for this + */ +public interface DbgModelTargetConfigurable extends DbgModelTargetObject, TargetConfigurable { + +} diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetAvailable.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetAvailable.java index 250dd0a4ed..1e9488325c 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetAvailable.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/iface2/DbgModelTargetAvailable.java @@ -23,4 +23,6 @@ public interface DbgModelTargetAvailable extends DbgModelTargetObject, TargetAtt public long getPid(); + public void setBase(Object value); + } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetAvailableContainerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetAvailableContainerImpl.java index b3c02d8b62..1c9a1fdf0d 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetAvailableContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetAvailableContainerImpl.java @@ -22,30 +22,31 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Pair; +import agent.dbgeng.model.iface1.DbgModelTargetConfigurable; import agent.dbgeng.model.iface2.*; +import ghidra.async.AsyncUtils; +import ghidra.dbg.error.DebuggerIllegalArgumentException; +import ghidra.dbg.target.TargetConfigurable; import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode; import ghidra.util.datastruct.WeakValueHashMap; -@TargetObjectSchemaInfo( - name = "AvailableContainer", - elements = { - @TargetElementType(type = DbgModelTargetAvailableImpl.class) - }, - elementResync = ResyncMode.ALWAYS, - attributes = { - @TargetAttributeType(type = Void.class) - }, - canonicalContainer = true) +@TargetObjectSchemaInfo(name = "AvailableContainer", elements = { + @TargetElementType(type = DbgModelTargetAvailableImpl.class) // +}, elementResync = ResyncMode.ALWAYS, attributes = { // + @TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class), // + @TargetAttributeType(type = Void.class) // +}, canonicalContainer = true) public class DbgModelTargetAvailableContainerImpl extends DbgModelTargetObjectImpl - implements DbgModelTargetAvailableContainer { + implements DbgModelTargetAvailableContainer, DbgModelTargetConfigurable { protected final Map attachablesById = new WeakValueHashMap<>(); public DbgModelTargetAvailableContainerImpl(DbgModelTargetRoot root) { super(root.getModel(), root, "Available", "AvailableContainer"); + this.changeAttributes(List.of(), Map.of(BASE_ATTRIBUTE_NAME, 16), "Initialized"); } @Override @@ -71,4 +72,24 @@ public class DbgModelTargetAvailableContainerImpl extends DbgModelTargetObjectIm return attachablesById.computeIfAbsent(pid, i -> new DbgModelTargetAvailableImpl(this, pid)); } + + @Override + public CompletableFuture writeConfigurationOption(String key, Object value) { + switch (key) { + case BASE_ATTRIBUTE_NAME: + if (value instanceof Integer) { + this.changeAttributes(List.of(), Map.of(BASE_ATTRIBUTE_NAME, value), + "Modified"); + for (DbgModelTargetAvailable child : attachablesById.values()) { + child.setBase(value); + } + } + else { + throw new DebuggerIllegalArgumentException("Base should be numeric"); + } + default: + } + return AsyncUtils.NIL; + } + } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetAvailableImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetAvailableImpl.java index c9e2989ae1..b4fd38706d 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetAvailableImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetAvailableImpl.java @@ -23,23 +23,26 @@ import agent.dbgeng.model.iface2.DbgModelTargetAvailableContainer; import ghidra.dbg.target.schema.*; import ghidra.dbg.util.PathUtils; -@TargetObjectSchemaInfo( - name = "Available", - elements = { - @TargetElementType(type = Void.class) - }, - attributes = { - @TargetAttributeType(type = Void.class) - }) +@TargetObjectSchemaInfo(name = "Available", elements = { + @TargetElementType(type = Void.class) }, attributes = { + @TargetAttributeType(type = Void.class) }) public class DbgModelTargetAvailableImpl extends DbgModelTargetObjectImpl implements DbgModelTargetAvailable { - protected static String indexAttachable(int pid) { - return Integer.toHexString(pid); + protected static String indexAttachable(int pid, Integer base) { + String pidstr = Integer.toString(pid, base); + if (base == 16) { + pidstr = "0x" + pidstr; + } + return pidstr; + } + + protected static String keyAttachable(int pid, Integer base) { + return PathUtils.makeKey(indexAttachable(pid, base)); } protected static String keyAttachable(int pid) { - return PathUtils.makeKey(indexAttachable(pid)); + return PathUtils.makeKey(indexAttachable(pid, 16)); } protected final int pid; @@ -71,4 +74,10 @@ public class DbgModelTargetAvailableImpl extends DbgModelTargetObjectImpl return pid; } + public void setBase(Object value) { + this.changeAttributes(List.of(), List.of(), Map.of(// + DISPLAY_ATTRIBUTE_NAME, keyAttachable(pid, (Integer) value) // + ), "Initialized"); + } + } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessContainerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessContainerImpl.java index ec47124bf7..4b38c0253f 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessContainerImpl.java @@ -22,18 +22,26 @@ import java.util.stream.Collectors; import agent.dbgeng.dbgeng.*; import agent.dbgeng.manager.*; +import agent.dbgeng.model.iface1.DbgModelTargetConfigurable; import agent.dbgeng.model.iface2.*; +import ghidra.async.AsyncUtils; +import ghidra.dbg.error.DebuggerIllegalArgumentException; +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(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 { + implements DbgModelTargetProcessContainer, DbgModelTargetConfigurable { public DbgModelTargetProcessContainerImpl(DbgModelTargetSession session) { super(session.getModel(), session, "Processes", "ProcessContainer"); + this.changeAttributes(List.of(), Map.of(BASE_ATTRIBUTE_NAME, 16), "Initialized"); getManager().addEventsListener(this); } @@ -133,4 +141,27 @@ public class DbgModelTargetProcessContainerImpl extends DbgModelTargetObjectImpl return new DbgModelTargetProcessImpl(this, process); } + @Override + public CompletableFuture writeConfigurationOption(String key, Object value) { + switch (key) { + case BASE_ATTRIBUTE_NAME: + if (value instanceof Integer) { + this.changeAttributes(List.of(), Map.of(BASE_ATTRIBUTE_NAME, value), + "Modified"); + for (TargetObject child : getCachedElements().values()) { + if (child instanceof DbgModelTargetProcessImpl) { + DbgModelTargetProcessImpl targetProcess = + (DbgModelTargetProcessImpl) child; + targetProcess.setBase(value); + } + } + } + else { + throw new DebuggerIllegalArgumentException("Base should be numeric"); + } + default: + } + return AsyncUtils.NIL; + } + } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessImpl.java index 9aa683ef5c..0d0cd2c015 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetProcessImpl.java @@ -67,6 +67,8 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl // Note: not sure section info is available from the dbgeng //protected final DbgModelTargetProcessSectionContainer sections; + private Integer base = 16; + public DbgModelTargetProcessImpl(DbgModelTargetProcessContainer processes, DbgProcess process) { super(processes.getModel(), processes, keyProcess(process), "Process"); this.getModel().addModelObject(process, this); @@ -102,7 +104,12 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl if (getManager().isKernelMode()) { return "[kernel]"; } - return "[" + process.getId().id + ":0x" + Long.toHexString(process.getPid()) + "]"; + + String pidstr = Long.toString(process.getPid(), base); + if (base == 16) { + pidstr = "0x" + pidstr; + } + return "[" + process.getId().id + ":" + pidstr + "]"; } @Override @@ -177,7 +184,7 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl if (pid != null) { changeAttributes(List.of(), List.of(), Map.of( // PID_ATTRIBUTE_NAME, pid, // - DISPLAY_ATTRIBUTE_NAME, "[0x" + Long.toHexString(pid) + "]" // + DISPLAY_ATTRIBUTE_NAME, getDisplay()// ), "Started"); } setExecutionState(TargetExecutionState.ALIVE, "Started"); @@ -221,4 +228,11 @@ public class DbgModelTargetProcessImpl extends DbgModelTargetObjectImpl public boolean isAccessible() { return accessible; } + + public void setBase(Object value) { + this.base = (Integer) value; + changeAttributes(List.of(), List.of(), Map.of( // + DISPLAY_ATTRIBUTE_NAME, getDisplay()// + ), "Started"); + } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetSessionImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetSessionImpl.java index c2b96ede6f..e6ae22671e 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetSessionImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetSessionImpl.java @@ -27,25 +27,17 @@ import agent.dbgeng.model.iface2.DbgModelTargetSession; import ghidra.dbg.target.schema.*; import ghidra.dbg.util.PathUtils; -@TargetObjectSchemaInfo( - name = "Session", - elements = { - @TargetElementType(type = Void.class) }, - attributes = { - @TargetAttributeType( - name = "Attributes", - type = DbgModelTargetSessionAttributesImpl.class, - fixed = true), - @TargetAttributeType( - name = "Processes", - type = DbgModelTargetProcessContainerImpl.class, - required = true, - fixed = true), +@TargetObjectSchemaInfo(name = "Session", elements = { + @TargetElementType(type = Void.class) }, attributes = { + @TargetAttributeType(name = "Attributes", type = DbgModelTargetSessionAttributesImpl.class, fixed = true), + @TargetAttributeType(name = "Processes", type = DbgModelTargetProcessContainerImpl.class, required = true, fixed = true), @TargetAttributeType(type = Void.class) }) public class DbgModelTargetSessionImpl extends DbgModelTargetObjectImpl implements DbgModelTargetSession { protected static final String DBG_PROMPT = "(kd)"; + private Integer base = 16; + // NB: This should almost certainly always be implemented by the root of the object tree protected static String indexSession(DebugSessionId debugSystemId) { diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadContainerImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadContainerImpl.java index e12b9d627f..9822d50a3c 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadContainerImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadContainerImpl.java @@ -23,21 +23,29 @@ import java.util.stream.Collectors; import agent.dbgeng.dbgeng.DebugThreadId; import agent.dbgeng.manager.*; import agent.dbgeng.manager.reason.*; +import agent.dbgeng.model.iface1.DbgModelTargetConfigurable; import agent.dbgeng.model.iface2.*; +import ghidra.async.AsyncUtils; +import ghidra.dbg.error.DebuggerIllegalArgumentException; +import ghidra.dbg.target.TargetConfigurable; import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.*; -@TargetObjectSchemaInfo(name = "ThreadContainer", elements = { - @TargetElementType(type = DbgModelTargetThreadImpl.class) }, attributes = { - @TargetAttributeType(type = Void.class) }, canonicalContainer = true) +@TargetObjectSchemaInfo(name = "ThreadContainer", elements = { // + @TargetElementType(type = DbgModelTargetThreadImpl.class) // +}, attributes = { // + @TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class), // + @TargetAttributeType(type = Void.class) // +}, canonicalContainer = true) public class DbgModelTargetThreadContainerImpl extends DbgModelTargetObjectImpl - implements DbgModelTargetThreadContainer { + implements DbgModelTargetThreadContainer, DbgModelTargetConfigurable { protected final DbgProcess process; public DbgModelTargetThreadContainerImpl(DbgModelTargetProcessImpl process) { super(process.getModel(), process, "Threads", "ThreadContainer"); this.process = process.process; + this.changeAttributes(List.of(), Map.of(BASE_ATTRIBUTE_NAME, 16), "Initialized"); getManager().addEventsListener(this); requestElements(false); @@ -126,4 +134,26 @@ public class DbgModelTargetThreadContainerImpl extends DbgModelTargetObjectImpl return new DbgModelTargetThreadImpl(this, (DbgModelTargetProcess) parent, thread); } + @Override + public CompletableFuture writeConfigurationOption(String key, Object value) { + switch (key) { + case BASE_ATTRIBUTE_NAME: + if (value instanceof Integer) { + this.changeAttributes(List.of(), Map.of(BASE_ATTRIBUTE_NAME, value), + "Modified"); + for (TargetObject child : getCachedElements().values()) { + if (child instanceof DbgModelTargetThreadImpl) { + DbgModelTargetThreadImpl targetThread = + (DbgModelTargetThreadImpl) child; + targetThread.setBase(value); + } + } + } + else { + throw new DebuggerIllegalArgumentException("Base should be numeric"); + } + default: + } + return AsyncUtils.NIL; + } } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadImpl.java index ef50cbfbc7..b17d8a7ac7 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetThreadImpl.java @@ -67,6 +67,7 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl protected final DbgModelTargetStackImpl stack; private DbgModelTargetProcess process; + private Integer base = 16; public DbgModelTargetThreadImpl(DbgModelTargetThreadContainer threads, DbgModelTargetProcess process, DbgThread thread) { @@ -98,7 +99,11 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl if (getManager().isKernelMode()) { return "[PR" + thread.getId().id + "]"; } - return "[" + thread.getId().id + ":0x" + Long.toHexString(thread.getTid()) + "]"; + String tidstr = Long.toString(thread.getTid(), base); + if (base == 16) { + tidstr = "0x" + tidstr; + } + return "[" + thread.getId().id + ":" + tidstr + "]"; } @Override @@ -171,4 +176,11 @@ public class DbgModelTargetThreadImpl extends DbgModelTargetObjectImpl return thread.getExecutingProcessorType().description; } + public void setBase(Object value) { + this.base = (Integer) value; + changeAttributes(List.of(), List.of(), Map.of( // + DISPLAY_ATTRIBUTE_NAME, getDisplay()// + ), "Started"); + } + } diff --git a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2TargetAvailableImpl.java b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2TargetAvailableImpl.java index b8fafb9196..5710da6f52 100644 --- a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2TargetAvailableImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/model/impl/DbgModel2TargetAvailableImpl.java @@ -66,4 +66,9 @@ public class DbgModel2TargetAvailableImpl extends DbgModel2TargetObjectImpl return pid; } + @Override + public void setBase(Object value) { + // Nothing for now + } + } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetAttachable.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetAttachable.java index 14b5529eef..378aab729b 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetAttachable.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetAttachable.java @@ -25,14 +25,9 @@ import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.*; import ghidra.dbg.util.PathUtils; -@TargetObjectSchemaInfo( - name = "Attachable", - elements = { - @TargetElementType(type = Void.class) - }, - attributes = { - @TargetAttributeType(type = Void.class) - }) +@TargetObjectSchemaInfo(name = "Attachable", elements = { + @TargetElementType(type = Void.class) }, attributes = { + @TargetAttributeType(type = Void.class) }) public class GdbModelTargetAttachable extends DefaultTargetObject implements TargetAttachable { @@ -48,18 +43,23 @@ public class GdbModelTargetAttachable return PathUtils.makeKey(indexAttachable(process)); } - protected static String computeDisplay(GdbProcessThreadGroup process) { + protected static String computeDisplay(GdbProcessThreadGroup process, Integer base) { + if (base == 16) { + return String.format("0x%x %s", process.getPid(), process.getDescription()); + } return String.format("%d %s", process.getPid(), process.getDescription()); } - protected final long pid; - protected final String display; + private GdbProcessThreadGroup process; + protected long pid; + protected String display; public GdbModelTargetAttachable(GdbModelImpl impl, GdbModelTargetAvailableContainer parent, GdbProcessThreadGroup process) { super(impl, parent, keyAttachable(process), "Attachable"); + this.process = process; this.pid = process.getPid(); - this.display = computeDisplay(process); + this.display = computeDisplay(process, 10); this.changeAttributes(List.of(), List.of(), Map.of( // PID_ATTRIBUTE_NAME, pid, // @@ -76,4 +76,12 @@ public class GdbModelTargetAttachable public String getDisplay() { return display; } + + public void setBase(Object value) { + this.display = computeDisplay(process, (Integer) value); + this.changeAttributes(List.of(), List.of(), Map.of( // + DISPLAY_ATTRIBUTE_NAME, display // + ), "Initialized"); + } + } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetAvailableContainer.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetAvailableContainer.java index aab42ee9f1..dbec3f15d3 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetAvailableContainer.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetAvailableContainer.java @@ -21,21 +21,22 @@ import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import agent.gdb.manager.GdbProcessThreadGroup; +import ghidra.async.AsyncUtils; import ghidra.dbg.agent.DefaultTargetObject; +import ghidra.dbg.error.DebuggerIllegalArgumentException; +import ghidra.dbg.target.TargetConfigurable; import ghidra.dbg.target.schema.TargetAttributeType; import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode; import ghidra.dbg.target.schema.TargetObjectSchemaInfo; import ghidra.util.datastruct.WeakValueHashMap; -@TargetObjectSchemaInfo( - name = "AvailableContainer", - elementResync = ResyncMode.ALWAYS, - attributes = { - @TargetAttributeType(type = Void.class) - }, - canonicalContainer = true) +@TargetObjectSchemaInfo(name = "AvailableContainer", elementResync = ResyncMode.ALWAYS, attributes = { + @TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class), // + @TargetAttributeType(type = Void.class) // +}, canonicalContainer = true) public class GdbModelTargetAvailableContainer - extends DefaultTargetObject { + extends DefaultTargetObject + implements TargetConfigurable { public static final String NAME = "Available"; protected final GdbModelImpl impl; @@ -46,6 +47,7 @@ public class GdbModelTargetAvailableContainer public GdbModelTargetAvailableContainer(GdbModelTargetSession session) { super(session.impl, session, NAME, "AvailableContainer"); this.impl = session.impl; + this.changeAttributes(List.of(), Map.of(BASE_ATTRIBUTE_NAME, 10), "Initialized"); } @Override @@ -54,9 +56,8 @@ public class GdbModelTargetAvailableContainer List available; synchronized (this) { // NOTE: If more details added to entries, should clear attachablesById - available = list.stream() - .map(this::getTargetAttachable) - .collect(Collectors.toList()); + available = + list.stream().map(this::getTargetAttachable).collect(Collectors.toList()); } setElements(available, "Refreshed"); }); @@ -67,4 +68,24 @@ public class GdbModelTargetAvailableContainer return attachablesById.computeIfAbsent(process.getPid(), i -> new GdbModelTargetAttachable(impl, this, process)); } + + @Override + public CompletableFuture writeConfigurationOption(String key, Object value) { + switch (key) { + case BASE_ATTRIBUTE_NAME: + if (value instanceof Integer) { + this.changeAttributes(List.of(), Map.of(BASE_ATTRIBUTE_NAME, value), + "Modified"); + for (GdbModelTargetAttachable child : this.getCachedElements().values()) { + child.setBase(value); + } + } + else { + throw new DebuggerIllegalArgumentException("Base should be numeric"); + } + default: + } + return AsyncUtils.NIL; + } + } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferior.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferior.java index 5b994469dc..1f59d00695 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferior.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferior.java @@ -31,11 +31,8 @@ import ghidra.dbg.target.schema.*; import ghidra.dbg.util.PathUtils; import ghidra.lifecycle.Internal; -@TargetObjectSchemaInfo( - name = "Inferior", - elements = { - @TargetElementType(type = Void.class) }, - attributes = { +@TargetObjectSchemaInfo(name = "Inferior", elements = { + @TargetElementType(type = Void.class) }, attributes = { @TargetAttributeType(type = Void.class) }) public class GdbModelTargetInferior extends DefaultTargetObject @@ -79,6 +76,7 @@ public class GdbModelTargetInferior protected final GdbModelTargetBreakpointLocationContainer breakpoints; protected Long exitCode; + private Integer base = 10; public GdbModelTargetInferior(GdbModelTargetInferiorContainer inferiors, GdbInferior inferior) { super(inferiors.impl, inferiors, keyInferior(inferior), "Inferior"); @@ -140,10 +138,7 @@ public class GdbModelTargetInferior return threads; } - @TargetAttributeType( - name = GdbModelTargetBreakpointLocationContainer.NAME, - required = true, - fixed = true) + @TargetAttributeType(name = GdbModelTargetBreakpointLocationContainer.NAME, required = true, fixed = true) public GdbModelTargetBreakpointLocationContainer getBreakpoints() { return breakpoints; } @@ -347,8 +342,13 @@ public class GdbModelTargetInferior if (inferior.getPid() == null) { return display = String.format("%d - ", inferior.getId()); } - return display = String.format("%d - %s - %s", inferior.getId(), inferior.getDescriptor(), - inferior.getExecutable()); + String descriptor = inferior.getDescriptor(); + String[] split = descriptor.split(" "); + if (base == 16) { + descriptor = split[0] + " 0x" + Long.toHexString(Long.decode(split[1])); + } + return display = + String.format("%d - %s - %s", inferior.getId(), descriptor, inferior.getExecutable()); } @Override @@ -443,4 +443,10 @@ public class GdbModelTargetInferior public void removeBreakpointLocation(GdbModelTargetBreakpointLocation loc) { breakpoints.removeBreakpointLocation(loc); } + + public void setBase(Object value) { + this.base = (Integer) value; + updateDisplayAttribute(); + } + } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferiorContainer.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferiorContainer.java index 00bb4c1687..56ec3aabc1 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferiorContainer.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferiorContainer.java @@ -24,19 +24,20 @@ import agent.gdb.manager.*; import agent.gdb.manager.impl.cmd.GdbStateChangeRecord; import ghidra.async.AsyncUtils; import ghidra.dbg.agent.DefaultTargetObject; +import ghidra.dbg.error.DebuggerIllegalArgumentException; +import ghidra.dbg.target.TargetConfigurable; import ghidra.dbg.target.TargetEventScope.TargetEventType; import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.TargetAttributeType; import ghidra.dbg.target.schema.TargetObjectSchemaInfo; -@TargetObjectSchemaInfo( - name = "InferiorContainer", - attributes = { - @TargetAttributeType(type = Void.class) }, - canonicalContainer = true) +@TargetObjectSchemaInfo(name = "InferiorContainer", attributes = { + @TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class), // + @TargetAttributeType(type = Void.class) // +}, canonicalContainer = true) public class GdbModelTargetInferiorContainer extends DefaultTargetObject - implements GdbEventsListenerAdapter { + implements TargetConfigurable, GdbEventsListenerAdapter { public static final String NAME = "Inferiors"; protected final GdbModelImpl impl; @@ -44,6 +45,7 @@ public class GdbModelTargetInferiorContainer public GdbModelTargetInferiorContainer(GdbModelTargetSession session) { super(session.impl, session, NAME, "InferiorContainer"); this.impl = session.impl; + this.changeAttributes(List.of(), Map.of(BASE_ATTRIBUTE_NAME, 10), "Initialized"); impl.gdb.addEventsListener(this); } @@ -168,4 +170,24 @@ public class GdbModelTargetInferiorContainer public CompletableFuture stateChanged(GdbStateChangeRecord sco) { return getTargetInferior(sco.getInferior()).stateChanged(sco); } + + @Override + public CompletableFuture writeConfigurationOption(String key, Object value) { + switch (key) { + case BASE_ATTRIBUTE_NAME: + if (value instanceof Integer) { + this.changeAttributes(List.of(), Map.of(BASE_ATTRIBUTE_NAME, value), + "Modified"); + for (GdbModelTargetInferior child : this.getCachedElements().values()) { + child.setBase(value); + } + } + else { + throw new DebuggerIllegalArgumentException("Base should be numeric"); + } + default: + } + return AsyncUtils.NIL; + } + } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java index e5406236a1..9826f57e79 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java @@ -70,6 +70,7 @@ public class GdbModelTargetThread protected String display; protected String shortDisplay; protected GdbThreadInfo info; + private Integer base = 10; protected final GdbModelTargetStack stack; @@ -158,9 +159,14 @@ public class GdbModelTargetThread } else { sb.append(info.getId()); - if (info.getTid() != null) { + Integer tid = info.getTid(); + if (tid != null) { + String tidstr = Integer.toString(tid, base); + if (base == 16) { + tidstr = "0x" + tidstr; + } sb.append(":"); - sb.append(info.getTid()); + sb.append(tidstr); } } sb.append("]"); @@ -243,4 +249,10 @@ public class GdbModelTargetThread ), sco.getReason().desc()); return result; } + + public void setBase(Object value) { + this.base = (Integer) value; + updateInfo(); + } + } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThreadContainer.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThreadContainer.java index 7d31ee506e..69019ea8b0 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThreadContainer.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThreadContainer.java @@ -26,6 +26,8 @@ import agent.gdb.manager.reason.GdbBreakpointHitReason; import ghidra.async.AsyncFence; import ghidra.async.AsyncUtils; import ghidra.dbg.agent.DefaultTargetObject; +import ghidra.dbg.error.DebuggerIllegalArgumentException; +import ghidra.dbg.target.TargetConfigurable; import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.TargetAttributeType; import ghidra.dbg.target.schema.TargetObjectSchemaInfo; @@ -34,10 +36,13 @@ import ghidra.util.Msg; @TargetObjectSchemaInfo( name = "ThreadContainer", attributes = { - @TargetAttributeType(type = Void.class) }, + @TargetAttributeType(name = TargetConfigurable.BASE_ATTRIBUTE_NAME, type = Integer.class), // + @TargetAttributeType(type = Void.class) // + }, canonicalContainer = true) public class GdbModelTargetThreadContainer - extends DefaultTargetObject { + extends DefaultTargetObject + implements TargetConfigurable { public static final String NAME = "Threads"; protected final GdbModelImpl impl; @@ -47,6 +52,7 @@ public class GdbModelTargetThreadContainer super(inferior.impl, inferior, NAME, "ThreadContainer"); this.impl = inferior.impl; this.inferior = inferior.inferior; + this.changeAttributes(List.of(), Map.of(BASE_ATTRIBUTE_NAME, 10), "Initialized"); } public GdbModelTargetThread threadCreated(GdbThread thread) { @@ -136,4 +142,24 @@ public class GdbModelTargetThreadContainer GdbThread thread = impl.gdb.getThread(reason.getThreadId()); return getTargetThread(thread).breakpointHit(reason); } + + @Override + public CompletableFuture writeConfigurationOption(String key, Object value) { + switch (key) { + case BASE_ATTRIBUTE_NAME: + if (value instanceof Integer) { + this.changeAttributes(List.of(), Map.of(BASE_ATTRIBUTE_NAME, value), + "Modified"); + for (GdbModelTargetThread child : this.getCachedElements().values()) { + child.setBase(value); + } + } + else { + throw new DebuggerIllegalArgumentException("Base should be numeric"); + } + default: + } + return AsyncUtils.NIL; + } + } diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/GadpRegistry.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/GadpRegistry.java index df4c4bb2f6..3bbf75ddc2 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/GadpRegistry.java +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/GadpRegistry.java @@ -52,16 +52,18 @@ public enum GadpRegistry { registerInterface(TargetAggregate.class, GadpClientTargetAggregate.class); registerInterface(TargetAttachable.class, GadpClientTargetAttachable.class); registerInterface(TargetAttacher.class, GadpClientTargetAttacher.class); + registerInterface(TargetBreakpointLocation.class, GadpClientTargetBreakpointLocation.class); registerInterface(TargetBreakpointLocationContainer.class, GadpClientTargetBreakpointLocationContainer.class); registerInterface(TargetBreakpointSpecContainer.class, GadpClientTargetBreakpointSpecContainer.class); registerInterface(TargetBreakpointSpec.class, GadpClientTargetBreakpointSpec.class); + registerInterface(TargetConfigurable.class, GadpClientTargetConfigurable.class); + registerInterface(TargetConsole.class, GadpClientTargetConsole.class); registerInterface(TargetDataTypeMember.class, GadpClientTargetDataTypeMember.class); registerInterface(TargetDataTypeNamespace.class, GadpClientTargetDataTypeNamespace.class); registerInterface(TargetDeletable.class, GadpClientTargetDeletable.class); registerInterface(TargetDetachable.class, GadpClientTargetDetachable.class); - registerInterface(TargetBreakpointLocation.class, GadpClientTargetBreakpointLocation.class); registerInterface(TargetEnvironment.class, GadpClientTargetEnvironment.class); registerInterface(TargetEventScope.class, GadpClientTargetEventScope.class); registerInterface(TargetExecutionStateful.class, GadpClientTargetExecutionStateful.class); diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClientTargetConfigurable.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClientTargetConfigurable.java new file mode 100644 index 0000000000..1f6a78aef6 --- /dev/null +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpClientTargetConfigurable.java @@ -0,0 +1,34 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.dbg.gadp.client; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import ghidra.dbg.gadp.protocol.Gadp; +import ghidra.dbg.target.TargetConfigurable; + +public interface GadpClientTargetConfigurable extends GadpClientTargetObject, TargetConfigurable { + + @Override + default CompletableFuture writeConfigurationOption(String key, Object value) { + getDelegate().assertValid(); + return getModel().sendChecked(Gadp.ConfigureRequest.newBuilder() + .setPath(GadpValueUtils.makePath(getPath())) + .setOption(GadpValueUtils.makeNamedValue(Map.entry(key, value))), + Gadp.ConfigureReply.getDefaultInstance()).thenApply(rep -> null); + } +} diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpValueUtils.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpValueUtils.java index e59b948cf2..edb4efef3d 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpValueUtils.java +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpValueUtils.java @@ -30,8 +30,8 @@ import ghidra.dbg.gadp.protocol.Gadp; import ghidra.dbg.gadp.protocol.Gadp.ModelObjectDelta; import ghidra.dbg.target.TargetAttacher.TargetAttachKind; import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet; -import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet; import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; +import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet; import ghidra.dbg.target.TargetEventScope.TargetEventType; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.dbg.target.TargetMethod; @@ -690,12 +690,12 @@ public enum GadpValueUtils { } } - public static Object getAttributeValue(GadpClientTargetObject object, Gadp.NamedValue attr) { + public static Object getAttributeValue(TargetObject object, Gadp.NamedValue attr) { return getValue(object.getModel(), PathUtils.extend(object.getPath(), attr.getName()), attr.getValue()); } - public static GadpClientTargetObject getElementValue(GadpClientTargetObject object, + public static GadpClientTargetObject getElementValue(TargetObject object, Gadp.NamedValue elem) { Object value = getValue(object.getModel(), PathUtils.index(object.getPath(), elem.getName()), elem.getValue()); diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/server/GadpClientHandler.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/server/GadpClientHandler.java index 60a1bbba03..6ffd3d882e 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/server/GadpClientHandler.java +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/server/GadpClientHandler.java @@ -387,6 +387,8 @@ public class GadpClientHandler return processBreakToggle(msg.getSequence(), msg.getBreakToggleRequest()); case CACHE_INVALIDATE_REQUEST: return processCacheInvalidate(msg.getSequence(), msg.getCacheInvalidateRequest()); + case CONFIGURE_REQUEST: + return processConfigure(msg.getSequence(), msg.getConfigureRequest()); case DELETE_REQUEST: return processDelete(msg.getSequence(), msg.getDeleteRequest()); case DETACH_REQUEST: @@ -670,8 +672,8 @@ public class GadpClientHandler protected CompletableFuture processCacheInvalidate(int seqno, Gadp.CacheInvalidateRequest req) { - TargetObject obj = getObjectChecked(req.getPath()); List path = req.getPath().getEList(); + TargetObject obj = getObjectChecked(path); return obj.invalidateCaches().thenCompose(__ -> { return model.flushEvents(); }).thenCompose(__ -> { @@ -682,6 +684,21 @@ public class GadpClientHandler }); } + protected CompletableFuture processConfigure(int seqno, Gadp.ConfigureRequest req) { + TargetConfigurable configurable = + getObjectChecked(req.getPath()).as(TargetConfigurable.class); + String key = req.getOption().getName(); + Object value = GadpValueUtils.getAttributeValue(configurable, req.getOption()); + return configurable.writeConfigurationOption(key, value).thenCompose(__ -> { + return model.flushEvents(); + }).thenCompose(__ -> { + return channel.write(Gadp.RootMessage.newBuilder() + .setSequence(seqno) + .setConfigureReply(Gadp.ConfigureReply.getDefaultInstance()) + .build()); + }); + } + protected CompletableFuture processKill(int seqno, Gadp.KillRequest req) { TargetKillable killable = getObjectChecked(req.getPath()).as(TargetKillable.class); return killable.kill().thenCompose(__ -> { diff --git a/Ghidra/Debug/Debugger-gadp/src/main/proto/gadp.proto b/Ghidra/Debug/Debugger-gadp/src/main/proto/gadp.proto index e91cfefd4b..ee6cff381b 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/proto/gadp.proto +++ b/Ghidra/Debug/Debugger-gadp/src/main/proto/gadp.proto @@ -470,6 +470,14 @@ message ResyncRequest { message ResyncReply { } +message ConfigureRequest { + Path path = 1; + NamedValue option = 2; +} + +message ConfigureReply { +} + enum TargetEventType { EV_STOPPED = 0; EV_RUNNING = 1; @@ -584,12 +592,14 @@ message RootMessage { InvokeRequest invoke_request = 105; InvokeReply invoke_reply = 205; - + ResyncRequest resync_request = 125; ResyncReply resync_reply = 225; - + ActivationRequest activation_request = 126; ActivationReply activation_reply = 226; + ConfigureRequest configure_request = 127; + ConfigureReply configure_reply = 227; } } diff --git a/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java b/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java index 46081c9f39..2057ffdc09 100644 --- a/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java +++ b/Ghidra/Debug/Debugger-gadp/src/test/java/ghidra/dbg/gadp/GadpClientServerTest.java @@ -40,6 +40,7 @@ import ghidra.async.*; import ghidra.dbg.*; import ghidra.dbg.agent.*; import ghidra.dbg.attributes.TargetStringList; +import ghidra.dbg.error.DebuggerIllegalArgumentException; import ghidra.dbg.gadp.GadpClientServerTest.EventListener.CallEntry; import ghidra.dbg.gadp.client.GadpClient; import ghidra.dbg.gadp.protocol.Gadp; @@ -383,27 +384,85 @@ public class GadpClientServerTest implements AsyncTestUtils { @TargetObjectSchemaInfo(name = "ProcessContainer") public class TestGadpTargetProcessContainer extends TestTargetObject - implements TargetLauncher { + implements TargetLauncher, TargetConfigurable { + + static final String BASE_ATTRIBUTE_NAME = "base"; + + int base = 10; + public TestGadpTargetProcessContainer(TestGadpTargetSession session) { super(session.getModel(), session, "Processes", "ProcessContainer"); + + changeAttributes(List.of(), Map.ofEntries( + Map.entry(BASE_ATTRIBUTE_NAME, base)), + "Initialized"); } @Override public CompletableFuture launch(Map args) { launches.offer(args); - TestGadpTargetProcess process = new TestGadpTargetProcess(this, 0); + long pid = args.containsKey("pid") ? (Long) args.get("pid") : 0; + TestGadpTargetProcess process = new TestGadpTargetProcess(this, pid, base); changeElements(List.of(), List.of(process), Map.of(), "Launched"); parent.setFocus(process); return AsyncUtils.NIL; } + + @Override + public CompletableFuture writeConfigurationOption(String key, Object value) { + if (BASE_ATTRIBUTE_NAME.equals(key)) { + if (!(value instanceof Integer)) { + throw new DebuggerIllegalArgumentException( + "base must be an Integer in [0, 36]"); + } + int base = (Integer) value; + if (base < 0 || 36 < base) { + throw new DebuggerIllegalArgumentException( + "base must be an Integer in [0, 36]"); + } + setBase(base); + } + else { + throw new DebuggerIllegalArgumentException("unrecognized option: '" + key + "'"); + } + return AsyncUtils.NIL; + } + + public void setBase(int base) { + this.base = base; + for (TestGadpTargetProcess proc : elements.values()) { + proc.setBase(base); + } + } } @TargetObjectSchemaInfo(name = "Process") public class TestGadpTargetProcess - extends TestTargetObject { - public TestGadpTargetProcess(TestGadpTargetProcessContainer processes, int index) { - super(processes.getModel(), processes, PathUtils.makeKey(PathUtils.makeIndex(index)), + extends TestTargetObject + implements TargetProcess { + final long pid; + int base = 10; + + public TestGadpTargetProcess(TestGadpTargetProcessContainer processes, long pid, int base) { + super(processes.getModel(), processes, PathUtils.makeKey(PathUtils.makeIndex(pid)), "Process"); + this.pid = pid; + setBase(base); + } + + void setBase(int base) { + this.base = base; + updateDisplay("Reconfigured base"); + } + + void updateDisplay(String reason) { + changeAttributes(List.of(), + Map.ofEntries(Map.entry(DISPLAY_ATTRIBUTE_NAME, computeDisplay())), + reason); + } + + String computeDisplay() { + return Long.toString(pid, base); } } @@ -1396,4 +1455,35 @@ public class GadpClientServerTest implements AsyncTestUtils { new CallEntry("rootAdded", List.of(client.getModelRoot()))), listener.record); } } + + @Test + public void testWriteConfigurationOption() throws Throwable { + AsynchronousSocketChannel socket = socketChannel(); + try (ServerRunner runner = new ServerRunner()) { + GadpClient client = new PrintingGadpClient("Test", socket); + + try (TargetObjectAddedWaiter waiter = new TargetObjectAddedWaiter(client)) { + waitOn(AsyncUtils.completable(TypeSpec.VOID, socket::connect, + runner.server.getLocalAddress())); + waitOn(client.connect()); + + TargetObject procs = + (TargetObject) waitOn(waiter.wait(PathUtils.parse("Processes"))); + waitOn(((TargetConfigurable) procs).writeConfigurationOption( + TestGadpTargetProcessContainer.BASE_ATTRIBUTE_NAME, 8)); + waitOn(((TargetLauncher) procs).launch(Map.ofEntries(Map.entry("pid", 1000L)))); + + TargetProcess p1000 = + (TargetProcess) waitOn(waiter.wait(PathUtils.parse("Processes[1000]"))); + + assertEquals("1750", p1000.getDisplay()); + + waitOn(((TargetConfigurable) procs).writeConfigurationOption( + TestGadpTargetProcessContainer.BASE_ATTRIBUTE_NAME, 10)); + + // NB. attribute change should arrive before completion of write + assertEquals("1000", p1000.getDisplay()); + } + } + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java index acf5498a42..ec51f76f74 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java @@ -223,6 +223,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter ImportFromFactsAction importFromFactsAction; OpenWinDbgTraceAction openTraceAction; + private ToggleDockingAction actionToggleBase; private ToggleDockingAction actionToggleSubscribe; private ToggleDockingAction actionToggleAutoRecord; private ToggleDockingAction actionToggleHideIntrinsics; @@ -238,6 +239,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter @AutoConfigStateField private boolean ignoreState = false; + Set configurables = new HashSet<>(); + public DebuggerObjectsProvider(final DebuggerObjectsPlugin plugin, DebuggerObjectModel model, ObjectContainer container, boolean asTree) throws Exception { super(plugin.getTool(), container.getPrefixedName(), plugin.getName()); @@ -672,6 +675,9 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter setFocus(f, targetObject); }); } + if (targetObject instanceof TargetConfigurable) { + configurables.add((TargetConfigurable) targetObject); + } } } @@ -681,6 +687,9 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter synchronized (targetMap) { targetMap.remove(targetObject.getJoinedPath(PATH_JOIN_CHAR)); refSet.remove(targetObject); + if (targetObject instanceof TargetConfigurable) { + configurables.remove(targetObject); + } } } } @@ -859,6 +868,16 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter groupTargetIndex++; + actionToggleBase = new ToggleActionBuilder("Toggle Base", plugin.getName()) + .keyBinding("B") + .menuPath("&Toggle base") + .menuGroup(DebuggerResources.GROUP_TARGET, "M" + groupTargetIndex) + .helpLocation(new HelpLocation(plugin.getName(), "toggle_base")) + .onAction(ctx -> performToggleBase(ctx)) + .buildAndInstallLocal(this); + + groupTargetIndex++; + actionToggleSubscribe = new ToggleActionBuilder("Toggle Subscription", plugin.getName()) .keyBinding("U") .menuPath("&Toggle subscription") @@ -1238,6 +1257,18 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter */ } + public void performToggleBase(ActionContext context) { + //Object contextObject = context.getContextObject(); + for (TargetConfigurable configurable : configurables) { + Object value = configurable.getCachedAttribute(TargetConfigurable.BASE_ATTRIBUTE_NAME); + if (value != null) { + Integer base = (Integer) value; + base = base == 10 ? 16 : 10; + configurable.writeConfigurationOption(TargetConfigurable.BASE_ATTRIBUTE_NAME, base); + } + } + } + public void performToggleSubscription(ActionContext context) { Object contextObject = context.getContextObject(); ObjectContainer container = getSelectedContainer(contextObject); diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetConfigurable.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetConfigurable.java new file mode 100644 index 0000000000..33df530d99 --- /dev/null +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetConfigurable.java @@ -0,0 +1,58 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.dbg.target; + +import java.util.concurrent.CompletableFuture; + +import ghidra.dbg.DebuggerTargetObjectIface; + +/** + * A target with writable configuration options + * + *

+ * In general, the options are stored as attributes, so that the current values are retrievable by + * the client, and so that the names and types of options are known. Note that not every attribute + * denotes a writable option. Enumeration of available options is not yet specified, but for the + * moment, we assume a subset of the attributes. + * + *

+ * Options should be close to their scope of applicability. For example, if an object affects the + * whole model, it should be an option of the root, or perhaps an option of a top-level "Options" + * object. If an option affects an object's elements, that option should be on the containing + * object. If an option affects a singular object, that option should probably be on that object + * itself. + * + *

+ * Furthermore, writing an option should not be the means of triggering an action. Though certainly, + * the model may react to their modification. Actions, in general, should instead be exposed as + * {@link TargetMethod}s. + */ +@DebuggerTargetObjectIface("Configurable") +public interface TargetConfigurable extends TargetObject { + + String BASE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "base"; + + /** + * Write a single option to this object + * + * @param key the name of the option, typically corresponding to the same-named attribute + * @param value the value to assign the option, typically conforming to the attribute schema + * @return a future which completes when the change is processed. + * @throws {@link DebuggerIllegalArgumentException} if the key is not writable, or if the value + * is not valid. + */ + public CompletableFuture writeConfigurationOption(String key, Object value); +} diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetMethod.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetMethod.java index 0e2633b6ea..2647535b3d 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetMethod.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetMethod.java @@ -29,6 +29,7 @@ import ghidra.dbg.util.CollectionUtils.AbstractNMap; /** * An object which can be invoked as a method * + *

* TODO: Should parameters and return type be something incorporated into Schemas? */ @DebuggerTargetObjectIface("Method") @@ -295,7 +296,11 @@ public interface TargetMethod extends TargetObject { * * @return the name-description map of parameters */ - @TargetAttributeType(name = PARAMETERS_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true) + @TargetAttributeType( + name = PARAMETERS_ATTRIBUTE_NAME, + required = true, + fixed = true, + hidden = true) default public TargetParameterMap getParameters() { return getParameters(this); } @@ -310,7 +315,11 @@ public interface TargetMethod extends TargetObject { * * @return the return type */ - @TargetAttributeType(name = RETURN_TYPE_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true) + @TargetAttributeType( + name = RETURN_TYPE_ATTRIBUTE_NAME, + required = true, + fixed = true, + hidden = true) default public Class getReturnType() { return getTypedAttributeNowByName(RETURN_TYPE_ATTRIBUTE_NAME, Class.class, Object.class); } diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java index bea48f48b7..a51924f939 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java @@ -159,13 +159,13 @@ import ghidra.lifecycle.Internal; public interface TargetObject extends Comparable { Set> ALL_INTERFACES = - Set.of(TargetAccessConditioned.class, TargetAggregate.class, TargetAttachable.class, - TargetAttacher.class, TargetBreakpointLocation.class, + Set.of(TargetAccessConditioned.class, TargetActiveScope.class, TargetAggregate.class, + TargetAttachable.class, TargetAttacher.class, TargetBreakpointLocation.class, TargetBreakpointLocationContainer.class, TargetBreakpointSpec.class, - TargetBreakpointSpecContainer.class, TargetConsole.class, TargetDataTypeMember.class, - TargetDataTypeNamespace.class, TargetDeletable.class, TargetDetachable.class, - TargetEnvironment.class, TargetEventScope.class, TargetExecutionStateful.class, - TargetActiveScope.class, TargetFocusScope.class, TargetInterpreter.class, + TargetBreakpointSpecContainer.class, TargetConfigurable.class, TargetConsole.class, + TargetDataTypeMember.class, TargetDataTypeNamespace.class, TargetDeletable.class, + TargetDetachable.class, TargetEnvironment.class, TargetEventScope.class, + TargetExecutionStateful.class, TargetFocusScope.class, TargetInterpreter.class, TargetInterruptible.class, TargetKillable.class, TargetLauncher.class, TargetMemory.class, TargetMemoryRegion.class, TargetMethod.class, TargetModule.class, TargetModuleContainer.class, TargetNamedDataType.class, TargetProcess.class, diff --git a/Ghidra/Debug/Framework-Debugging/src/test/resources/ghidra/dbg/model/test_schema.xml b/Ghidra/Debug/Framework-Debugging/src/test/resources/ghidra/dbg/model/test_schema.xml index 65c377406a..61db60cd05 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/resources/ghidra/dbg/model/test_schema.xml +++ b/Ghidra/Debug/Framework-Debugging/src/test/resources/ghidra/dbg/model/test_schema.xml @@ -50,6 +50,7 @@