mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-601: Generic configuration + change of base for IDs
This commit is contained in:
parent
314c58e941
commit
92017b9f7d
27 changed files with 602 additions and 111 deletions
|
@ -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 {
|
||||
|
||||
}
|
|
@ -23,4 +23,6 @@ public interface DbgModelTargetAvailable extends DbgModelTargetObject, TargetAtt
|
|||
|
||||
public long getPid();
|
||||
|
||||
public void setBase(Object value);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -66,4 +66,9 @@ public class DbgModel2TargetAvailableImpl extends DbgModel2TargetObjectImpl
|
|||
return pid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBase(Object value) {
|
||||
// Nothing for now
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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(__ -> {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue