GP-601: Generic configuration + change of base for IDs

This commit is contained in:
d-millar 2021-04-01 18:39:33 -04:00 committed by Dan
parent 314c58e941
commit 92017b9f7d
27 changed files with 602 additions and 111 deletions

View file

@ -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 <T> type for this
*/
public interface DbgModelTargetConfigurable extends DbgModelTargetObject, TargetConfigurable {
}

View file

@ -23,4 +23,6 @@ public interface DbgModelTargetAvailable extends DbgModelTargetObject, TargetAtt
public long getPid();
public void setBase(Object value);
}

View file

@ -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<Integer, DbgModelTargetAvailable> 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<Void> 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;
}
}

View file

@ -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");
}
}

View file

@ -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<Void> 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;
}
}

View file

@ -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");
}
}

View file

@ -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) {

View file

@ -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<Void> 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;
}
}

View file

@ -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");
}
}

View file

@ -66,4 +66,9 @@ public class DbgModel2TargetAvailableImpl extends DbgModel2TargetObjectImpl
return pid;
}
@Override
public void setBase(Object value) {
// Nothing for now
}
}

View file

@ -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<TargetObject, GdbModelTargetAvailableContainer>
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");
}
}

View file

@ -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<GdbModelTargetAttachable, GdbModelTargetSession> {
extends DefaultTargetObject<GdbModelTargetAttachable, GdbModelTargetSession>
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<GdbModelTargetAttachable> 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<Void> 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;
}
}

View file

@ -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<TargetObject, GdbModelTargetInferiorContainer>
@ -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 - <null>", 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();
}
}

View file

@ -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<GdbModelTargetInferior, GdbModelTargetSession>
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<Void> stateChanged(GdbStateChangeRecord sco) {
return getTargetInferior(sco.getInferior()).stateChanged(sco);
}
@Override
public CompletableFuture<Void> 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;
}
}

View file

@ -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();
}
}

View file

@ -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<GdbModelTargetThread, GdbModelTargetInferior> {
extends DefaultTargetObject<GdbModelTargetThread, GdbModelTargetInferior>
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<Void> 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;
}
}

View file

@ -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);

View file

@ -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<Void> 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);
}
}

View file

@ -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());

View file

@ -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<String> 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(__ -> {

View file

@ -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;
@ -591,5 +599,7 @@ message RootMessage {
ActivationRequest activation_request = 126;
ActivationReply activation_reply = 226;
ConfigureRequest configure_request = 127;
ConfigureReply configure_reply = 227;
}
}

View file

@ -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<TestGadpTargetProcess, TestGadpTargetSession>
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<Void> launch(Map<String, ?> 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<Void> 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<TargetObject, TestGadpTargetProcessContainer> {
public TestGadpTargetProcess(TestGadpTargetProcessContainer processes, int index) {
super(processes.getModel(), processes, PathUtils.makeKey(PathUtils.makeIndex(index)),
extends TestTargetObject<TargetObject, TestGadpTargetProcessContainer>
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());
}
}
}
}

View file

@ -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<TargetConfigurable> 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);

View file

@ -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
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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<Void> writeConfigurationOption(String key, Object value);
}

View file

@ -29,6 +29,7 @@ import ghidra.dbg.util.CollectionUtils.AbstractNMap;
/**
* An object which can be invoked as a method
*
* <p>
* 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);
}

View file

@ -159,13 +159,13 @@ import ghidra.lifecycle.Internal;
public interface TargetObject extends Comparable<TargetObject> {
Set<Class<? extends TargetObject>> 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,

View file

@ -50,6 +50,7 @@
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="base" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="Interpreter" elementResync="NEVER" attributeResync="NEVER">