GP-71: Prepping for source release.

This commit is contained in:
Dan 2020-12-10 09:39:41 -05:00
parent ddbfbfe198
commit 8201baef2b
2705 changed files with 305722 additions and 53 deletions

View file

@ -0,0 +1,33 @@
/* ###
* 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.
*/
#include <pthread.h>
#include <stdio.h>
pthread_t thread;
void* work(void* param) {
printf("I'm %d, PID: %d\n", (int)param, getpid());
char *const argv[] = { "echo", "test", NULL };
execv("/usr/bin/echo", argv);
printf("Should never get here\n");
}
int main() {
pthread_create(&thread, NULL, work, (void*)1);
while (1) {
sleep(10);
}
}

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.
*/
#include <pthread.h>
#include <stdio.h>
pthread_t thread;
void* work(void* param) {
printf("I'm %d, PID: %d\n", (int)param, getpid());
if (param == NULL) {
return 1;
} else {
//sleep(10);
return 2;
}
}
int main() {
pthread_create(&thread, NULL, work, (void*)1);
return work(NULL);
}

View file

@ -0,0 +1,32 @@
/* ###
* 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.
*/
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
pthread_t thread;
void* work(void* param) {
printf("I'm PID: %d\n", getpid());
for (int i = 0; i < 10; i++) {
sleep(1);
}
}
int main() {
pthread_create(&thread, NULL, work, NULL);
work(NULL);
}

View file

@ -0,0 +1,46 @@
/* ###
* 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.
*/
#include <stdio.h>
#include <Windows.h>
int __declspec(dllexport) func(char* msg) {
printf("%s\n", msg);
}
int main(int argc, char** argv) {
if (argc != 1) {
func("I'm the child");
return 1;
}
STARTUPINFO sStartupInfo = {sizeof(sStartupInfo)};
PROCESS_INFORMATION sProcessInformation = {0};
BOOL result = CreateProcess(argv[0], "expCreateProcess child", NULL, NULL, FALSE, 0, NULL, NULL, &sStartupInfo, &sProcessInformation);
if (result == FALSE) {
DWORD le = GetLastError();
fprintf(stderr, "Could not create child process: %d\n", le);
char err[1024];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, le, 0, err, sizeof(err), NULL);
fprintf(stderr, " Message: '%s'\n", err);
DebugBreak();
return -1;
}
Sleep(100); // Hack: Try to ensure process is created before hitting break on 'work'
func("I'm the parent: ");
printf(" %p,%p (%d,%d)\n", sProcessInformation.hProcess, sProcessInformation.hThread, sProcessInformation.dwProcessId, sProcessInformation.dwThreadId);
CloseHandle(sProcessInformation.hThread);
CloseHandle(sProcessInformation.hProcess);
return 0;
}

View file

@ -0,0 +1,41 @@
/* ###
* 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.
*/
#include <stdio.h>
#include <Windows.h>
#include <process.h>
__declspec(dllexport) unsigned int WINAPI work(DWORD* param) {
printf("I'm %d, PID: %d\n", *param, GetCurrentProcessId());
if (*param == 0) {
return 1;
} else {
return 2;
}
}
int main(int argc, char** argv) {
DWORD zero = 0;
DWORD one = 1;
HANDLE thread = _beginthreadex(NULL, 0, work, &one, 0, NULL);
if (thread == NULL) {
fprintf(stderr, "Could not create child thread\n");
DebugBreak();
return -1;
}
Sleep(100); // Hack: Try to ensure thread is created before hitting break on 'work'
CloseHandle(thread);
return work(&zero);
}

View file

@ -0,0 +1,39 @@
/* ###
* 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.
*/
#include <stdio.h>
#include <Windows.h>
#include <process.h>
__declspec(dllexport) unsigned int WINAPI work(DWORD* param) {
printf("I'm %d, PID: %d\n", *param, GetCurrentProcessId());
for (int i = 0; i < 10; i++) {
Sleep(1);
}
}
int main(int argc, char** argv) {
DWORD zero = 0;
DWORD one = 1;
HANDLE thread = _beginthreadex(NULL, 0, work, &one, 0, NULL);
if (thread == NULL) {
fprintf(stderr, "Could not create child thread\n");
DebugBreak();
return -1;
}
Sleep(100); // Hack: Try to ensure thread is created before hitting break on 'work'
CloseHandle(thread);
return work(&zero);
}

View file

@ -0,0 +1,30 @@
/* ###
* 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.
*/
#include <stdio.h>
int func(int id) {
if (id) {
printf("I'm the parent\n");
return 1;
} else {
printf("I'm the child\n");
return 0;
}
}
int main() {
return func(fork());
}

View file

@ -0,0 +1,29 @@
/* ###
* 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.
*/
#include <stdio.h>
#ifdef WIN32
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT
#endif
DLLEXPORT volatile char overwrite[] = "Hello, World!";
int main(int argc, char** argv) {
printf("%s\n", overwrite);
return overwrite[0];
}

View file

@ -0,0 +1,22 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <Windows.h>
__declspec(dllexport) int main(int argc, char** argv) {
for (int i = 0; i < 10; i++) {
Sleep(1000);
}
}

View file

@ -0,0 +1,111 @@
/* ###
* 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.
*/
#include "stdio.h"
#include "complex.h"
typedef struct _mystruct {
int f1;
long f2;
short f3:4;
short f4:4;
short f5:4;
short f6:4;
} mystruct, *mystruct_p, mystruct_arr[5];
typedef union _myunion {
long as_long;
float as_float;
} myunion, *myunion_p, myunion_arr[6];
typedef enum _myenum {
FIRST = 0,
SECOND,
} myenum, *myenum_p, myenum_arr[7];
typedef void (*myfunc_p)(int arg0, long arg1);
typedef void (*myvargfunc_p)(int arg0, long arg1, ...);
typedef myundef;
int int_var;
void* void_p_var;
__attribute__((section ("complex")))
float complex complex_var = 1 + 2*I;
__attribute__((section ("doublex")))
double complex double_complex_var = 3 + 4*I;
/*__attribute__((section ("ldoublex")))
long double complex long_double_complex_var = 5 + 6*I;*/
typedef struct _mycomplex {
float real;
float imag;
} mycomplex, *mycomplex_p;
typedef struct _mydoublex {
double real;
double imag;
} mydoublex, *mydoublex_p;
typedef struct _myldoublex {
long double real;
long double imag;
} myldoublex, *myldoublex_p;
typedef struct _mylist {
struct _mylist* next;
void* val;
} mylist, *mylist_p;
mystruct mystruct_var;
struct _mystruct struct_mystruct_var;
mystruct_p mystruct_p_var;
mystruct_arr mystruct_arr_var;
myunion myunion_var;
myenum myenum_var;
myfunc_p myfunc_p_var;
myvargfunc_p myvargfunc_p_var;
myundef myundef_var;
mylist_p mylist_p_var;
int main(int argc, char** argv) {
printf("complex: %d\n", sizeof(complex_var));
printf("double complex: %d\n", sizeof(double_complex_var));
register mycomplex_p cparts = &complex_var;
printf("single real: %f\n", cparts->real);
printf("single imag: %f\n", cparts->imag);
mydoublex_p dparts = &double_complex_var;
printf("double real: %g\n", dparts->real);
printf("double imag: %g\n", dparts->imag);
/*myldoublex_p ldparts = &long_double_complex_var;
printf("long double real: %lg\n", ldparts->real);
printf("long double imag: %lg\n", ldparts->imag);*/
}
/*
Wrote:
1d000000000000002e0b00000000000000000000000407000000000000005f6d79656e756d
1d000000000000002e0f00000000000000000000000407000000000000005f6d79656e756d
Read: 49000000000000002f0b0000000407000000000000005f6d79656e756d07020000000000000005000000000000004649525354000000000000000006000000000000005345434f4e440100000000000000
Wrote: 1d000000000000002e0f00000000000000000000000407000000000000005f6d79656e756d
Read: 49000000000000002f0f0000000407000000000000005f6d79656e756d07020000000000000005000000000000004649525354000000000000000006000000000000005345434f4e440100000000000000
*/

View file

@ -0,0 +1,788 @@
/* ###
* 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;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import ghidra.async.*;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetAccessConditioned.TargetAccessibility;
import ghidra.dbg.target.TargetAccessConditioned.TargetAccessibilityListener;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetObject.TargetObjectListener;
import ghidra.dbg.util.PathUtils;
import ghidra.dbg.util.PathUtils.PathComparator;
import ghidra.util.Msg;
import ghidra.util.Swing;
public enum DebugModelConventions {
;
protected static CompletableFuture<Void> runNotInSwing(Object originator, Runnable runnable,
String cbName) {
if (Swing.isSwingThread()) {
return CompletableFuture.runAsync(runnable).exceptionally(e -> {
Msg.error(originator, "Error in " + cbName, e);
return null;
});
}
try {
runnable.run();
}
catch (Throwable e) {
Msg.error(originator, "Error in " + cbName, e);
}
return AsyncUtils.NIL;
}
/**
* Fetch everything in a particular collection of refs
*
* <p>
* This was added as part of GP-251. Where there are uses of this method, devs should consider
* opportunities to be more selective in what they fetch.
*
* @param <K> the type of keys
* @param refs the collection of refs
* @return the collection of objects
* @deprecated Just to draw attention to it
*/
@Deprecated(forRemoval = false)
public static <K> CompletableFuture<Map<K, TargetObject>> fetchAll(
Map<K, ? extends TargetObjectRef> refs) {
if (refs == null) {
return AsyncUtils.nil();
}
Map<K, TargetObject> result = new HashMap<>();
AsyncFence fence = new AsyncFence();
for (Map.Entry<K, ? extends TargetObjectRef> ent : refs.entrySet()) {
fence.include(ent.getValue().fetch().thenAccept(obj -> {
synchronized (result) {
result.put(ent.getKey(), obj);
}
}));
}
return fence.ready().thenApply(__ -> result);
}
/**
* Fetch all non-link refs in an attribute map
*
* <p>
* This was added as part of GP-251. Where there are uses of this method, devs should consider
* opportunities to be more selective in what they fetch.
*
* @param refs the attribute map
* @return the attribute map, but with non-link refs fetched as objects
* @deprecated Just to draw attention to it
*/
@Deprecated(forRemoval = false)
public static CompletableFuture<Map<String, ?>> fetchObjAttrs(TargetObjectRef parent,
Map<String, ?> attrs) {
if (attrs == null) {
return AsyncUtils.nil();
}
Map<String, Object> result = new HashMap<>();
AsyncFence fence = new AsyncFence();
for (Map.Entry<String, ?> ent : attrs.entrySet()) {
String name = ent.getKey();
Object a = ent.getValue();
if (!(a instanceof TargetObjectRef)) {
synchronized (result) {
result.put(name, a);
}
continue;
}
TargetObjectRef ref = (TargetObjectRef) a;
if (PathUtils.isLink(parent.getPath(), ent.getKey(), ref.getPath())) {
synchronized (result) {
result.put(name, a);
}
continue;
}
fence.include(ref.fetch().thenAccept(obj -> {
synchronized (result) {
result.put(name, obj);
}
}));
}
return fence.ready().thenApply(__ -> result);
}
/**
* Search for a suitable object implementing the given interface, starting at a given seed.
*
* @see #findSuitable(Class, TargetObject)
*/
public static <T extends TargetObject> CompletableFuture<T> findSuitable(Class<T> iface,
TargetObjectRef seed) {
return seed.fetch().thenCompose(obj -> findSuitable(iface, obj));
}
/**
* Search for a suitable object implementing the given interface, starting at a given seed.
*
* <p>
* This performs an n-up-1-down search starting at the given seed, seeking an object which
* implements the given interface. The 1-down part is only applied from objects implementing
* {@link TargetAggregate}. See {@link TargetObject} for the specifics of expected model
* conventions.
*
* <p>
* Note that many a debugger target object interface type require a self-referential {@code T}
* parameter referring to the implementing class type. To avoid referring to a particular
* implementation, it becomes necessary to leave {@code T} as {@code ?}, but that can never
* satisfy the constraints of this method. To work around this, such interfaces must provide a
* static {@code tclass} field, which can properly satisfy the type constraints of this method
* for such self-referential type variables. The returned value must be ascribed to the
* wild-carded type, because the work-around involves a hidden class. Perhaps a little verbose
* (hey, it's Java!), the following is the recommended pattern, e.g., to discover the
* environment of a given process:
*
* <pre>
* CompletableFuture<? extends TargetEnvironment<?>> futureEnv =
* DebugModelConventions.findSuitable(TargetEnvironment.tclass, aProcess);
* </pre>
*
* @param <T> the desired interface type.
* @param iface the (probably {@code tclass}) of the desired interface type
* @param seed the starting object
* @return a future which completes with the discovered object or completes with null, if not
* found.
*/
public static <T extends TargetObject> CompletableFuture<T> findSuitable(Class<T> iface,
TargetObject seed) {
if (iface.isAssignableFrom(seed.getClass())) {
return CompletableFuture.completedFuture(iface.cast(seed));
}
if (seed instanceof TargetAggregate) {
return findInAggregate(iface, seed).thenCompose(agg -> {
if (agg.size() == 1) {
return CompletableFuture.completedFuture(agg.iterator().next());
}
return findParentSuitable(iface, seed);
});
}
return findParentSuitable(iface, seed);
}
private static <T extends TargetObject> CompletableFuture<T> findParentSuitable(Class<T> iface,
TargetObject obj) {
return obj.fetchParent().thenCompose(parent -> {
if (parent == null) {
return AsyncUtils.nil();
}
return findSuitable(iface, parent);
});
}
/**
* Search for an object implementing the given interface among itself and its attributes.
*
* <p>
* This method descends into the attributes of objects which implement the
* {@link TargetAggregate} interface. All found objects will comes from the same "level" in the
* tree, the algorithm terminating as soon as it finds a level with at least one object having
* the interface. When it terminates, all such objects at that level will be included. The
* resulting collection is in no particular order.
*
* @param <T> the desired interface type.
* @param iface the (probably {@code tclass}) of the desired interface type
* @param seed the starting object
* @return a future which completes with the, possibly empty, collection of discovered objects
*/
public static <T extends TargetObject> CompletableFuture<Collection<T>> findInAggregate(
Class<T> iface, TargetObject seed) {
return findInAggregate(iface, Set.of(seed));
}
/**
* Search for an object implementing the given interface among those given and their attributes.
*
* <p>
* All seeds should be at the same "level", or else the result is not well defined.
*
* @see #findInAggregate(Class, TargetObject)
*/
public static <T extends TargetObject> CompletableFuture<Collection<T>> findInAggregate(
Class<T> iface, Collection<? extends TargetObject> seeds) {
if (seeds.isEmpty()) {
return CompletableFuture.completedFuture(Set.of());
}
Set<T> result = seeds.stream()
.filter(obj -> iface.isAssignableFrom(obj.getClass()))
.map(obj -> iface.cast(obj))
.collect(Collectors.toSet());
if (!result.isEmpty()) {
return CompletableFuture.completedFuture(result);
}
AsyncFence fence = new AsyncFence();
Set<TargetObject> nextLevel = new HashSet<>();
for (TargetObject seed : seeds) {
if (!(seed instanceof TargetAggregate)) {
continue;
}
fence.include(seed.fetchAttributes().thenCompose(attributes -> {
AsyncFence f2 = new AsyncFence();
for (Map.Entry<String, ?> ent : attributes.entrySet()) {
Object val = ent.getValue();
if (!(val instanceof TargetObjectRef)) {
continue;
}
TargetObjectRef ref = (TargetObjectRef) val;
if (PathUtils.isLink(seed.getPath(), ent.getKey(), ref.getPath())) {
// TODO: Resolve refs? Must ensure I don't re-visit anyone
continue;
}
f2.include(ref.fetch().thenAccept(obj -> {
synchronized (nextLevel) {
nextLevel.add(obj);
}
}));
}
return f2.ready();
}));
}
return fence.ready().thenCompose(__ -> findInAggregate(iface, nextLevel));
}
public abstract static class AncestorTraversal<T> extends CompletableFuture<T> {
public enum Result {
FOUND, CONTINUE, TERMINATE;
}
protected TargetObject cur;
public AncestorTraversal(TargetObject successor) {
cur = successor;
}
protected abstract Result check(TargetObject obj);
protected abstract T finish(TargetObject obj);
public AncestorTraversal<T> start() {
try {
next(cur);
}
catch (Throwable ex) {
completeExceptionally(ex);
}
return this;
}
protected void next(TargetObject ancestor) {
cur = ancestor;
if (cur == null) {
complete(null);
return;
}
switch (check(cur)) {
case FOUND:
complete(finish(cur));
return;
case CONTINUE:
cur.fetchParent().thenAccept(this::next).exceptionally(this::exc);
return;
case TERMINATE:
complete(null);
return;
}
}
protected Void exc(Throwable ex) {
completeExceptionally(ex);
return null;
}
}
/**
* Find the nearest ancestor which implements the given interface.
*
* <p>
* This is similar to {@link #findSuitable(Class, TargetObject)}, except without the 1-down
* rule.
*
* @param <T> the type of the required interface
* @param iface the (probably {@code tclass}) for the required interface
* @param successor the seed object
* @return a future which completes with the found object or completes with null if not found.
*/
public static <T extends TargetObject> CompletableFuture<T> nearestAncestor(Class<T> iface,
TargetObject successor) {
return new AncestorTraversal<T>(successor) {
@Override
protected Result check(TargetObject obj) {
if (iface.isAssignableFrom(obj.getClass())) {
return Result.FOUND;
}
return Result.CONTINUE;
}
@Override
protected T finish(TargetObject obj) {
return iface.cast(obj);
}
}.start();
}
/**
* Collect all ancestors (including seed) supporting the given interface
*
* @param <T> the type of interface
* @param seed the starting point
* @param iface the class of the interface
* @return the collection of ancestors supporting the interface
*/
public static <T extends TargetObject> CompletableFuture<Collection<T>> collectAncestors(
TargetObject seed, Class<T> iface) {
DebuggerObjectModel model = seed.getModel();
List<T> result = new ArrayList<>(seed.getPath().size() + 1);
AsyncFence fence = new AsyncFence();
for (List<String> path = seed.getPath(); path != null; path = PathUtils.parent(path)) {
fence.include(model.fetchModelObject(path).thenAccept(obj -> {
if (iface.isAssignableFrom(obj.getClass())) {
result.add(iface.cast(obj));
}
}));
}
return fence.ready().thenApply(__ -> {
result.sort(Comparator.comparing(o -> o.getPath().size()));
return result;
});
}
/**
* Collect all successors (including seed) that are elements supporting the given interface.
*
* @param <T> the type of interface
* @param seed the starting point (root of subtree to inspect)
* @param iface the class of the interface
* @return the collection of successor elements supporting the interface
*/
// TODO: Test this method
public static <T extends TargetObject> CompletableFuture<Collection<T>> collectSuccessors(
TargetObject seed, Class<T> iface) {
Collection<T> result =
new TreeSet<>(Comparator.comparing(TargetObject::getPath, PathComparator.KEYED));
AsyncFence fence = new AsyncFence();
fence.include(seed.fetchElements().thenCompose(elements -> {
AsyncFence elemFence = new AsyncFence();
for (TargetObjectRef r : elements.values()) {
elemFence.include(r.fetch().thenCompose(e -> {
if (iface.isInstance(e)) {
synchronized (result) {
result.add(iface.cast(e));
}
return AsyncUtils.NIL;
}
return collectSuccessors(e, iface).thenAccept(sub -> {
synchronized (result) {
result.addAll(sub);
}
});
}));
}
return elemFence.ready();
}));
fence.include(seed.fetchAttributes().thenCompose(attributes -> {
AsyncFence attrFence = new AsyncFence();
for (Map.Entry<String, ?> ent : attributes.entrySet()) {
Object obj = ent.getValue();
if (!(obj instanceof TargetObjectRef)) {
continue;
}
TargetObjectRef r = (TargetObjectRef) obj;
if (PathUtils.isLink(seed.getPath(), ent.getKey(), r.getPath())) {
continue;
}
attrFence.include(r.fetch().thenCompose(a -> {
if (iface.isInstance(a)) {
synchronized (result) {
result.add(iface.cast(a));
}
return AsyncUtils.NIL;
}
return collectSuccessors(a, iface).thenAccept(sub -> {
synchronized (result) {
result.addAll(sub);
}
});
}));
}
return attrFence.ready();
}));
return fence.ready().thenApply(__ -> {
return result;
});
}
/**
* Find the nearest ancestor thread
*
* @param successor the seed object
* @return a future which completes with the found thread or completes with {@code null}.
*/
public static CompletableFuture<TargetThread<?>> findThread(TargetObject successor) {
return new AncestorTraversal<TargetThread<?>>(successor) {
@Override
protected Result check(TargetObject obj) {
if (obj.isRoot()) {
return Result.TERMINATE;
}
if (obj instanceof TargetThread) {
return Result.FOUND;
}
return Result.CONTINUE;
}
@Override
protected TargetThread<?> finish(TargetObject obj) {
return (TargetThread<?>) obj;
}
}.start();
}
/**
* Find the nearest ancestor thread
*
* @see #findThread(TargetObject)
*/
public static CompletableFuture<TargetThread<?>> findThread(TargetObjectRef successorRef) {
return successorRef.fetch().thenCompose(DebugModelConventions::findThread);
}
/**
* Check if a target is a live process
*
* @param target the potential process
* @return the process if live, or null
*/
public static TargetProcess<?> liveProcessOrNull(TargetObject target) {
if (!(target instanceof TargetProcess<?>)) {
return null;
}
// TODO: When schemas are introduced, we'll better handle "associated"
// For now, require "implements"
if (!(target instanceof TargetExecutionStateful<?>)) {
return (TargetProcess<?>) target;
}
TargetExecutionStateful<?> exe = (TargetExecutionStateful<?>) target;
TargetExecutionState state = exe.getExecutionState();
if (!state.isAlive()) {
return null;
}
return (TargetProcess<?>) target;
}
/**
* A convenience for listening to selected portions (possible all) of a sub-tree of a model
*/
public abstract static class SubTreeListenerAdapter implements TargetObjectListener {
protected boolean disposed = false;
protected final NavigableMap<List<String>, TargetObject> objects =
new TreeMap<>(PathComparator.KEYED);
/**
* An object has been removed from the sub-tree
*
* @param removed the removed object
*/
protected abstract void objectRemoved(TargetObject removed);
/**
* An object has been added to the sub-tree
*
* @param added the added object
*/
protected abstract void objectAdded(TargetObject added);
/**
* Decide whether a sub-tree (of the sub-tree) should be tracked
*
* @param ref the root of the sub-tree to consider
* @return false to ignore, true to track
*/
protected abstract boolean checkDescend(TargetObjectRef ref);
@Override
public void invalidated(TargetObject object, String reason) {
runNotInSwing(this, () -> doInvalidated(object, reason), "invalidated");
}
private void doInvalidated(TargetObject object, String reason) {
synchronized (objects) {
if (disposed) {
return;
}
/**
* NOTE: Can't use iteration, because subtrees will also remove stuff, causing
* ConcurrentModificationException, even if removal is via the iterator...
*/
List<String> path = object.getPath();
List<TargetObject> removed = new ArrayList<>();
while (true) {
Entry<List<String>, TargetObject> ent = objects.ceilingEntry(path);
if (ent == null || !PathUtils.isAncestor(path, ent.getKey())) {
break;
}
objects.remove(ent.getKey());
TargetObject succ = ent.getValue();
succ.removeListener(this);
removed.add(succ);
}
for (TargetObject r : removed) {
objectRemovedSafe(r);
}
}
}
private void objectRemovedSafe(TargetObject removed) {
try {
objectRemoved(removed);
}
catch (Throwable t) {
Msg.error(this, "Error in callback", t);
}
}
private void objectAddedSafe(TargetObject obj) {
try {
objectAdded(obj);
}
catch (Throwable t) {
Msg.error(this, "Error in callback", t);
}
}
private void considerRef(TargetObjectRef ref) {
if (!checkDescend(ref)) {
return;
}
ref.fetch().thenAcceptAsync(this::addListenerAndConsiderSuccessors);
}
private void considerElements(TargetObject parent,
Map<String, ? extends TargetObjectRef> elements) {
synchronized (objects) {
if (disposed) {
return;
}
if (!objects.containsKey(parent.getPath())) {
return;
}
}
for (TargetObjectRef e : elements.values()) {
considerRef(e);
}
}
private void considerAttributes(TargetObject obj, Map<String, ?> attributes) {
synchronized (objects) {
if (disposed) {
return;
}
if (!objects.containsKey(obj.getPath())) {
return;
}
}
for (Map.Entry<String, ?> ent : attributes.entrySet()) {
String name = ent.getKey();
Object a = ent.getValue();
if (!(a instanceof TargetObjectRef)) {
continue;
}
TargetObjectRef r = (TargetObjectRef) a;
if (PathUtils.isLink(obj.getPath(), name, r.getPath())) {
continue;
}
considerRef(r);
}
}
/**
* Track a specified object, without initially adding the sub-tree
*
* <p>
* Note that {@link #checkDescend(TargetObject)} must also exclude the sub-tree, otherwise
* children added later will be tracked.
*
* @param obj the object to track
* @return true if the object was not already being listened to
*/
public boolean addListener(TargetObject obj) {
if (obj == null) {
return false;
}
obj.addListener(this);
synchronized (objects) {
if (objects.put(obj.getPath(), obj) == obj) {
return false;
}
}
objectAddedSafe(obj);
return true;
}
/**
* Add a specified sub-tree to this listener
*
* @param obj
* @return true if the object was not already being listened to
*/
public boolean addListenerAndConsiderSuccessors(TargetObject obj) {
boolean result = addListener(obj);
if (result && checkDescend(obj)) {
obj.fetchElements().thenAcceptAsync(elems -> considerElements(obj, elems));
obj.fetchAttributes().thenAcceptAsync(attrs -> considerAttributes(obj, attrs));
}
return result;
}
@Override
public void elementsChanged(TargetObject parent, Collection<String> removed,
Map<String, ? extends TargetObjectRef> added) {
runNotInSwing(this, () -> doElementsChanged(parent, removed, added), "elementsChanged");
}
private void doElementsChanged(TargetObject parent, Collection<String> removed,
Map<String, ? extends TargetObjectRef> added) {
if (checkDescend(parent)) {
considerElements(parent, added);
}
}
@Override
public void attributesChanged(TargetObject parent, Collection<String> removed,
Map<String, ?> added) {
runNotInSwing(this, () -> doAttributesChanged(parent, removed, added),
"attributesChanged");
}
private void doAttributesChanged(TargetObject parent, Collection<String> removed,
Map<String, ?> added) {
if (checkDescend(parent)) {
considerAttributes(parent, added);
}
}
/**
* Dispose of this sub-tree tracker/listener
*
* <p>
* This uninstalls the listener from every tracked object and clears its collection of
* tracked objects.
*/
public void dispose() {
synchronized (objects) {
disposed = true;
for (Iterator<TargetObject> it = objects.values().iterator(); it.hasNext();) {
TargetObject obj = it.next();
obj.removeListener(this);
it.remove();
}
}
}
}
public static class AllRequiredAccess extends AsyncReference<TargetAccessibility, Void> {
protected class ListenerForAccess implements TargetAccessibilityListener {
protected final TargetAccessConditioned<?> access;
private boolean accessible;
public ListenerForAccess(TargetAccessConditioned<?> access) {
this.access = access;
this.access.addListener(this);
this.accessible = access.getAccessibility() == TargetAccessibility.ACCESSIBLE;
}
@Override
public void accessibilityChanged(TargetAccessConditioned<?> object,
TargetAccessibility accessibility) {
//Msg.debug(this, "Obj " + object + " has become " + accessibility);
synchronized (AllRequiredAccess.this) {
this.accessible = accessibility == TargetAccessibility.ACCESSIBLE;
// Check that all requests have been issued (fence is ready)
if (listeners != null) {
set(getAllAccessibility(), null);
}
}
}
}
protected final List<ListenerForAccess> listeners;
protected final AsyncFence initFence = new AsyncFence();
public AllRequiredAccess(Collection<? extends TargetAccessConditioned<?>> allReq) {
Msg.debug(this, "Listening for access on: " + allReq);
listeners = allReq.stream().map(ListenerForAccess::new).collect(Collectors.toList());
set(getAllAccessibility(), null);
}
public TargetAccessibility getAllAccessibility() {
return TargetAccessibility.fromBool(listeners.stream().allMatch(l -> l.accessible));
}
}
/**
* Obtain an object which tracks accessibility for a given target object.
*
* <p>
* Recall that for an object to be considered accessible, it and its ancestors must all be
* accessible. Objects without the {@link TargetAccessConditioned} interface, are assumed
* accessible.
*
* <p>
* <b>Caution:</b> The returned {@link AllRequiredAccess} object has the only strong references
* to the listeners. If you intend to wait for access, e.g., by calling
* {@link AsyncReference#waitValue(Object)}, you must ensure a strong reference to this object
* is maintained for the duration of the wait. If not, it could be garbage collected, and you
* will never get a callback.
*
* @param obj the object whose accessibility to track
* @return a future which completes with an {@link AsyncReference} of the objects effective
* accessibility.
*/
public static CompletableFuture<AllRequiredAccess> trackAccessibility(TargetObject obj) {
CompletableFuture<? extends Collection<? extends TargetAccessConditioned<?>>> collectAncestors =
collectAncestors(obj, TargetAccessConditioned.tclass);
return collectAncestors.thenApply(AllRequiredAccess::new);
}
/**
* Request focus on the given object in its nearest focus scope
*
* <p>
* Note if the object has no suitable focus scope, this method fails silently.
*
* @param obj the object on which to request focus
* @return a future which completes when focus is granted, or exceptionally
*/
public static CompletableFuture<Void> requestFocus(TargetObjectRef obj) {
CompletableFuture<? extends TargetFocusScope<?>> futureScope =
DebugModelConventions.findSuitable(TargetFocusScope.tclass, obj);
return futureScope.thenCompose(scope -> {
if (scope == null) {
return AsyncUtils.NIL;
}
return scope.requestFocus(obj);
});
}
}

View file

@ -0,0 +1,39 @@
/* ###
* 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;
class DebuggerAbnormalModelClosedReason implements DebuggerModelClosedReason {
final Throwable exc;
public DebuggerAbnormalModelClosedReason(Throwable exc) {
this.exc = exc;
}
@Override
public boolean hasException() {
return true;
}
@Override
public boolean isClientInitiated() {
return false;
}
@Override
public Throwable getException() {
return exc;
}
}

View file

@ -0,0 +1,60 @@
/* ###
* 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;
/**
* A reason given for a closed connection
*/
public interface DebuggerModelClosedReason {
DebuggerModelClosedReason NORMAL = DebuggerNormalModelClosedReason.INSTANCE;
static DebuggerModelClosedReason normal() {
return NORMAL;
}
static DebuggerModelClosedReason abnormal(Throwable exc) {
return new DebuggerAbnormalModelClosedReason(exc);
}
/**
* Check for exceptional cause for the closed model
*
* <p>
* Usually, if the model is closed unexpectedly, there is an exception to document the cause. If
* available, the implementation should provide this exception.
*
* @return true if an exception is recorded
*/
boolean hasException();
/**
* Check if the model was closed by the client
*
* <p>
* In this case, the closed model is completely ordinary. While the model is still no longer
* valid, there is no cause to alert the user.
*
* @return true if the model was closed by the client
*/
boolean isClientInitiated();
/**
* Get the recorded exception, if available
*
* @return the exception or null
*/
Throwable getException();
}

View file

@ -0,0 +1,36 @@
/* ###
* 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;
import ghidra.dbg.util.ConfigurableFactory;
import ghidra.util.classfinder.ExtensionPoint;
/**
* A factory for a debugger model
*
* This provides a discoverable means of creating a debug model.
*/
public interface DebuggerModelFactory
extends ExtensionPoint, ConfigurableFactory<DebuggerObjectModel> {
/**
* Check if this factory is compatible with the local system.
*
* @return true if compatible
*/
default boolean isCompatible() {
return true;
}
}

View file

@ -0,0 +1,53 @@
/* ###
* 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;
/**
* A listener for events related to the debugger model, usually a connection
*
* <p>
* TODO: Most (non-client) models do not implement this. Even the client ones do not implement
* {@link #modelStateChanged()}
*/
public interface DebuggerModelListener {
/**
* The model has been successfully opened
*
* <p>
* For example, the connection to a debugger daemon has been established and negotiated.
*/
default public void modelOpened() {
}
/**
* The model was closed
*
* <p>
* For example, the remote closed the connection, or the connection was lost. Whatever the case,
* the model is invalid after this callback.
*
* @param reason the reason for the model to close
*/
default public void modelClosed(DebuggerModelClosedReason reason) {
}
/**
* The model's state has changed, prompting an update to its description
*/
default public void modelStateChanged() {
}
}

View file

@ -0,0 +1,35 @@
/* ###
* 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;
enum DebuggerNormalModelClosedReason implements DebuggerModelClosedReason {
INSTANCE;
@Override
public boolean hasException() {
return false;
}
@Override
public boolean isClientInitiated() {
return true;
}
@Override
public Throwable getException() {
return null;
}
}

View file

@ -0,0 +1,463 @@
/* ###
* 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;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.error.DebuggerModelNoSuchPathException;
import ghidra.dbg.error.DebuggerModelTypeException;
import ghidra.dbg.target.TargetMemory;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.*;
/**
* A debugger model, often a connection to an external debugger
*
* <p>
* This is an abstraction of debugger operations and attempts to limit assumptions to those that
* generalize to most platforms. Debuggers and the target processes may be temperamental, so an
* asynchronous pattern is employed to prevent deadlocks on dropped connections, slow connections,
* buggy daemons, etc.
*
* <p>
* For methods returning a {@link CompletableFuture}, the documentation describes its return value
* assuming successful completion. Any of the futures may complete exceptionally.
*
* <p>
* The completion of any future returned by a method does not imply any state change in the
* debugger. It merely acknowledges that the request was received by the debugger. Only listener
* callbacks confirm or otherwise communicate actual state changes. If, in the underlying protocol,
* confirmation of a request implies a state change, then the implementation must make the
* appropriate callbacks.
*
* <p>
* The model object only exposes the connection state and a root object. The model comprises an
* arbitrary tree of {@link TargetObject}s each supporting zero or more discoverable interfaces. The
* debugging framework provides a number of "stock" interfaces which should be sufficient to model
* most debuggers. The tree is how the client accesses objects, e.g., processes and threads, on the
* target.
*
* <p>
* Users and implementors of this interface may find {@link AsyncUtils} useful. An implementation of
* this interface should never block the calling thread to wait on an external event, otherwise, you
* risk deadlocking Ghidra's UI.
*/
public interface DebuggerObjectModel {
public static final TypeSpec<Map<String, ? extends TargetObject>> ELEMENT_MAP_TYPE =
TypeSpec.auto();
public static final TypeSpec<Map<String, ?>> ATTRIBUTE_MAP_TYPE = TypeSpec.auto();
/**
* Check that a given {@link TargetObject} interface has a name
*
* <p>
* Names are assigned using the {@link DebuggerTargetObjectIface} annotation.
*
* @implNote To be language agnostic, we never use the name of the Java implementation of the
* interface.
*
* @param iface the class of the interface
* @return the name of the interface
* @throws IllegalArgumentException if the interface is not annotated
*/
public static String requireIfaceName(Class<? extends TargetObject> iface) {
DebuggerTargetObjectIface annot = iface.getAnnotation(DebuggerTargetObjectIface.class);
if (annot == null) {
throw new IllegalArgumentException(iface + " has no @" +
DebuggerTargetObjectIface.class.getSimpleName() + " annotation.");
}
return annot.value();
}
/**
* Check that the given value is not null
*
* @param <T> the type of the value
* @param val the value
* @param path the path where the value was expected
* @return the non-null value
* @throws DebuggerModelNoSuchPathException if -val- is null
*/
public static <T> T requireNonNull(T val, List<String> path) {
if (val == null) {
throw new DebuggerModelNoSuchPathException("Path " + path + " does not exist");
}
return val;
}
/**
* Check that the given object is non-null and supports a required interface
*
* <p>
* Because most of the {@link TargetObject} interfaces have a (self-referential) type parameter,
* this call will most likely be on its own line, assigned to a variable of the interface type
* using a wildcard {@code <?>} parameter. Otherwise, raw types get involved, making things
* rather messy.
*
* @param <T> the type of the interface
* @param iface the class for the interface
* @param obj the object to check
* @param path the path where the object was expected
* @return the (non-null) object cast to the required interface
* @throws DebuggerModelNoSuchPathException if -obj- is null
* @throws DebuggerModelTypeException if -obj- does not support -iface-
*/
public static <T extends TargetObject> T requireIface(Class<T> iface, TargetObject obj,
List<String> path) {
requireNonNull(obj, path);
String name = requireIfaceName(iface);
if (iface.isAssignableFrom(obj.getClass())) {
return iface.cast(obj);
}
throw new DebuggerModelTypeException("Object " + path + " is missing " + name);
}
/**
* Get a brief description of the client, suitable for display in lists
*
* @return the description
*/
public default String getBrief() {
return toString();
}
/**
* Add a listener for model events
*
* @param listener the listener
*/
public void addModelListener(DebuggerModelListener listener);
/**
* Remove a model event listener
*
* @param listener the listener
*/
public void removeModelListener(DebuggerModelListener listener);
/**
* Check if the model believes it is alive
*
* <p>
* Basically, this should be true if the model has started, but not yet terminated. To test
* whether the model is actually responsive, use {@link #ping(String)}.
*
* @return true if alive
*/
public boolean isAlive();
/**
* Check if the debugger agent is alive (optional operation)
*
* <p>
* For models providing such a mechanism, check if the debugger is alive and able to process
* commands. Even if an explicit "ping" command is not available, an implementor is encouraged
* to use some sort of NOP or echo command to test for responsiveness.
*
* @param content some content to optionally incorporate into the test
* @return a future that completes when the daemon is verified to be alive
*/
public CompletableFuture<Void> ping(String content);
/**
* Check that a given reference (or object) belongs to this model
*
* <p>
* As a convenience, this method takes an expected class and casts -ref- to it. This is meant
* only to cast to an implementation-specific type, not for checking that an object supports a
* given interface. Use {@link #requireIface(Class, TargetObject, List)} for interface checking.
*
* @param <T> the required implementation-specific type
* @param cls the class for the required type
* @param ref the reference (or object) to check
* @return the object, cast to the desired typed
* @throws IllegalArgumentException if -ref- does not belong to this model
*/
default <T extends TargetObjectRef> T assertMine(Class<T> cls, TargetObjectRef ref) {
if (ref.getModel() != this) {
throw new IllegalArgumentException(
"TargetObject (or ref)" + ref + " does not belong to this model");
}
return cls.cast(ref);
}
/**
* Create a reference to the given path in this model
*
* Note that the path is not checked until the object is fetched. Thus, it is possible for a
* reference to refer to a non-existent object.
*
* @param path the path of the object
* @return a reference to the object
*/
public TargetObjectRef createRef(List<String> path);
/**
* @see #createRef(List)
*/
public default TargetObjectRef createRef(String... path) {
return createRef(List.of(path));
}
/**
* Fetch the attributes of a given model path
*
* Giving an empty path will retrieve the attributes of the root object. If the path does not
* exist, the future completes with {@code null}.
*
* @param path the path
* @param refresh true to invalidate caches involved in handling this request
* @return a future map of attributes
*/
public CompletableFuture<? extends Map<String, ?>> fetchObjectAttributes(List<String> path,
boolean refresh);
/**
* Fetch the attributes of the given model path, without refreshing
*
* @see #fetchObjectAttributes(List, boolean)
*/
public default CompletableFuture<? extends Map<String, ?>> fetchObjectAttributes(
List<String> path) {
return fetchObjectAttributes(path, false);
}
/**
* @see #fetchObjectAttributes(List)
*/
public default CompletableFuture<? extends Map<String, ?>> fetchObjectAttributes(
String... path) {
return fetchObjectAttributes(List.of(path));
}
/**
* Fetch the elements of a given model path
*
* Giving an empty path will retrieve all the top-level objects, i.e., elements of the root. If
* the path does not exist, the future completes with {@code null}.
*
* @param path the path
* @param refresh true to invalidate caches involved in handling this request
* @return a future map of elements
*/
public CompletableFuture<? extends Map<String, ? extends TargetObjectRef>> fetchObjectElements(
List<String> path, boolean refresh);
/**
* Fetch the elements of the given model path, without refreshing
*
* @see #fetchObjectElements(List, boolean)
*/
public default CompletableFuture<? extends Map<String, ? extends TargetObjectRef>> fetchObjectElements(
List<String> path) {
return fetchObjectElements(path, false);
}
/**
* @see #fetchObjectElements(List)
*/
public default CompletableFuture<? extends Map<String, ? extends TargetObjectRef>> fetchObjectElements(
String... path) {
return fetchObjectElements(List.of(path));
}
/**
* Fetch the root object of the model
*
* The root is a virtual object to contain all the top-level objects of the model tree. This
* object represents the debugger itself.
*
* @return the root
*/
public CompletableFuture<? extends TargetObject> fetchModelRoot();
/**
* Fetch the value at the given path
*
*
* @param path the path of the value
* @return a future completing with the value or with {@code null} if the path does not exist
*/
public CompletableFuture<?> fetchModelValue(List<String> path);
/**
* Fetch a model value, optionally refreshing caches along the path
*
* <p>
* By convention, no attribute nor element may have a {@code null} value. Thus, a {@code null}
* return value always indicates the path does not exist.
*
* <p>
* When refresh is true, only the applicable cache at each successor is refreshed. For example,
* when the path is {@code A.B[1].C[2]}, then only {@code B}'s and {@code C}'s element caches
* are refreshed; and {@code A}'s, {@code B[1]}'s, and {@code C[2]}'s attribute caches are
* refreshed.
*
* @implNote The returned value cannot be a {@link TargetObjectRef} unless the value represents
* a link. In other words, if the path refers to an object, the model must return the
* object, not a ref. When the value is a link, the implementation may optionally
* resolve the object, but should only do so if it doesn't incur a significant cost.
* Furthermore, such links cannot be resolved -- though they can be substituted for
* the target object at the linked path. In other words, the path of the returned ref
* (or object) must represent the link's target. Suppose {@code A[1]} is a link to
* {@code B[1]}, which is in turn a link to {@code C[1]} -- honestly, linked links
* ought to be a rare occurrence -- then fetching {@code A[1]} must return a ref to
* {@code B[1]}. It must not return {@code C[1]} nor a ref to it. The reason deals
* with caching and updates. If a request for {@code A[1]} were to return
* {@code C[1]}, a client may cache that result. Suppose that client then observes a
* change causing {@code B[1]} to link to {@code C[2]}. This implies that {@code A[1]}
* now resolves to {@code C[2]}; however, the client has not received enough
* information to update or invalidate its cache.
*
* @param path the path
* @param refresh true to refresh caches
* @return the found value, or {@code null} if it does not exist
*/
public CompletableFuture<?> fetchModelValue(List<String> path, boolean refresh);
/**
* @see #fetchModelValue(List)
*/
public default CompletableFuture<?> fetchModelValue(String... path) {
return fetchModelValue(List.of(path));
}
/**
* Fetch the object with the given path
*
* <p>
* If the value at the path is a link, this will attempt to fetch it.
*
* @param path the path of the object
* @param refresh ignore the cache
* @return a future completing with the object or with {@code null} if it does not exist
* @throws DebuggerModelTypeException if the value at the path is not a {@link TargetObject}
*/
public default CompletableFuture<? extends TargetObject> fetchModelObject(List<String> path,
boolean refresh) {
return fetchModelValue(path, refresh).thenCompose(v -> {
if (v == null) {
return AsyncUtils.nil();
}
if (!(v instanceof TargetObjectRef)) {
throw DebuggerModelTypeException.typeRequired(v, path, TargetObjectRef.class);
}
TargetObjectRef ref = (TargetObjectRef) v;
if (path.equals(ref.getPath()) && !(v instanceof TargetObject)) {
throw DebuggerModelTypeException.typeRequired(v, path, TargetObject.class);
}
return ref.fetch();
});
}
/**
* @see #fetchModelObject(List)
*/
public default CompletableFuture<? extends TargetObject> fetchModelObject(List<String> path) {
return fetchModelObject(path, false);
}
/**
* @see #fetchModelObject(List)
*/
public default CompletableFuture<? extends TargetObject> fetchModelObject(String... path) {
return fetchModelObject(List.of(path));
}
/**
* Fetch the attribute with the given path
*
* Note that model implementations should avoid nullable attributes, since a null-valued
* attribute cannot easily be distinguished from a non-existent attribute.
*
* @param path the path of the attribute
* @return a future that completes with the value or with {@code null} if it does not exist
*/
public default CompletableFuture<?> fetchObjectAttribute(List<String> path) {
return fetchModelObject(PathUtils.parent(path)).thenApply(
parent -> parent == null ? null : parent.fetchAttribute(PathUtils.getKey(path)));
}
/**
* @see #fetchObjectAttribute(List)
*/
public default CompletableFuture<?> getObjectAttribute(String... path) {
return fetchObjectAttribute(List.of(path));
}
/**
* Get a factory for target addresses
*
* <p>
* Technically, putting this here instead of just {@link TargetMemory} imposes a subtle
* limitation: All targets in the model have to have the same factory. I'm not certain that's a
* huge concern at this point. The alternative is that the memory mapper has to accept and
* compose new address factories, or we need a separate mapper per factory encountered along
* with a mechanism to choose the correct one.
*
* @return the factory
*/
public AddressFactory getAddressFactory();
/**
* TODO Document me
*
* @param name
* @return
*/
default public AddressSpace getAddressSpace(String name) {
return getAddressFactory().getAddressSpace(name);
}
/**
* TODO Document me
*
* @param space
* @param offset
* @return
*/
public default Address getAddress(String space, long offset) {
if (Address.NO_ADDRESS.getAddressSpace().getName().equals(space)) {
return Address.NO_ADDRESS;
}
return getAddressSpace(space).getAddress(offset);
}
/**
* Invalidate the caches for every object known locally.
*
* Unlike, {@link TargetObject#invalidateCaches()}, this does not push the request to a remote
* object. If the objects are proxies, just the proxies' caches are cleared. Again, this does
* not apply to caches for the objects' children.
*/
public void invalidateAllLocalCaches();
/**
* Close the session and dispose the model
*
* For local sessions, terminate the debugger. For client sessions, disconnect.
*
* @return a future which completes when the session is closed
*/
public CompletableFuture<Void> close();
}

View file

@ -0,0 +1,33 @@
/* ###
* 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;
import java.lang.annotation.*;
import ghidra.dbg.target.TargetObject;
/**
* Annotation to assign the name of a target object interface to its Java implementation, an
* interface extending {@link TargetObject}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DebuggerTargetObjectIface {
/**
* The name of the interface
*/
String value();
}

View file

@ -0,0 +1,44 @@
/* ###
* 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;
import ghidra.util.classfinder.ExtensionPointProperties;
/**
* A factory for a local debugger model
*
* <p>
* These factories are searched when attempting to create a new default debug model targeting the
* local environment.
*/
public interface LocalDebuggerModelFactory extends DebuggerModelFactory {
/**
* Get the priority of this factory
*
* <p>
* In the event multiple compatible factories are discovered, the one with the highest priority
* is selected, breaking ties arbitrarily.
*
* <p>
* The default implementation returns the priority given by {@link ExtensionPointProperties}. If
* the priority must be determined dynamically, then override this implementation.
*
* @return the priority, where lower values indicate higher priority.
*/
default int getPriority() {
return ExtensionPointProperties.Util.getPriority(getClass());
}
}

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.agent;
import ghidra.dbg.DebuggerModelListener;
import ghidra.util.datastruct.ListenerSet;
public abstract class AbstractDebuggerObjectModel implements SpiDebuggerObjectModel {
protected final ListenerSet<DebuggerModelListener> listeners =
new ListenerSet<>(DebuggerModelListener.class);
@Override
public void addModelListener(DebuggerModelListener listener) {
listeners.add(listener);
}
@Override
public void removeModelListener(DebuggerModelListener listener) {
listeners.remove(listener);
}
}

View file

@ -0,0 +1,198 @@
/* ###
* 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.agent;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.util.PathUtils;
import ghidra.util.datastruct.ListenerSet;
/**
* An abstract implementation of {@link TargetObject}
*
* <p>
* Implementors should probably use {@link DefaultTargetObject} as the base class for all objects in
* the model. If your model employs proxies (i.e., using
* {@link Proxy#newProxyInstance(ClassLoader, Class[], java.lang.reflect.InvocationHandler)}),
* please see {@link InvalidatableTargetObjectIf} to ensure subtree invalidation is handled
* properly.
*
* @param <P> the type of the parent
*/
public abstract class AbstractTargetObject<P extends TargetObject>
implements TargetObject, InvalidatableTargetObjectIf {
protected static final CompletableFuture<Map<String, TargetObject>> COMPLETED_EMPTY_ELEMENTS =
CompletableFuture.completedFuture(Map.of());
protected static final CompletableFuture<Map<String, Object>> COMPLETED_EMPTY_ATTRIBUTES =
CompletableFuture.completedFuture(Map.of());
protected final DebuggerObjectModel model;
protected final P parent;
protected final CompletableFuture<P> completedParent;
protected final List<String> path;
protected final int hash;
protected final String typeHint;
protected boolean valid = true;
protected final ListenerSet<TargetObjectListener> listeners =
new ListenerSet<>(TargetObjectListener.class);
public AbstractTargetObject(DebuggerObjectModel model, P parent, String key, String typeHint) {
this.model = model;
this.parent = parent;
this.completedParent = CompletableFuture.completedFuture(parent);
if (parent == null) {
this.path = key == null ? List.of() : List.of(key);
}
else {
this.path = PathUtils.extend(parent.getPath(), key);
}
this.hash = computeHashCode();
this.typeHint = typeHint;
}
@Override
public boolean equals(Object obj) {
return doEquals(obj);
}
@Override
public int hashCode() {
return hash;
}
@Override
public String toString() {
return "<Local " + getClass().getSimpleName() + ": " + path + " in " +
getModel() + ">";
}
@Override
public String getTypeHint() {
return typeHint;
}
@Override
public boolean isValid() {
return valid;
}
@Override
public void addListener(TargetObjectListener l) {
listeners.add(l);
}
@Override
public void removeListener(TargetObjectListener l) {
listeners.remove(l);
}
@Override
public DebuggerObjectModel getModel() {
return model;
}
/**
* {@inheritDoc}
*
* Overridden to avoid an infinite loop / stack overflow
*/
@Override
public CompletableFuture<? extends Map<String, ? extends TargetObject>> fetchElements() {
return COMPLETED_EMPTY_ELEMENTS;
}
@Override
public CompletableFuture<? extends TargetObject> fetchElement(String index) {
return fetchElements().thenApply(elements -> elements.get(index));
}
/**
* {@inheritDoc}
*
* Overridden to avoid an infinite loop / stack overflow
*/
@Override
public CompletableFuture<? extends Map<String, ?>> fetchAttributes() {
return COMPLETED_EMPTY_ATTRIBUTES;
}
@Override
public Object getProtocolID() {
return getPath();
}
@Override
public List<String> getPath() {
return path;
}
@Override
public CompletableFuture<? extends P> fetchParent() {
return completedParent;
}
/**
* Get the parent immediately
*
* Since the parent is fixed and known to the implementation, it can be retrieved immediately.
*
* @return the parent
*/
public P getImplParent() {
return parent;
}
protected void doInvalidate(String reason) {
valid = false;
listeners.fire.invalidated(this, reason);
}
protected void doInvalidateElements(Collection<?> elems, String reason) {
for (Object e : elems) {
if (e instanceof InvalidatableTargetObjectIf) {
InvalidatableTargetObjectIf obj = (InvalidatableTargetObjectIf) e;
obj.invalidateSubtree(reason);
}
}
}
protected void doInvalidateAttributes(Map<String, ?> attrs, String reason) {
for (Map.Entry<String, ?> ent : attrs.entrySet()) {
String name = ent.getKey();
Object a = ent.getValue();
if (a instanceof InvalidatableTargetObjectIf) {
InvalidatableTargetObjectIf obj = (InvalidatableTargetObjectIf) a;
if (!PathUtils.isLink(getPath(), name, obj.getPath())) {
obj.invalidateSubtree(reason);
}
}
}
}
@Override
public void invalidateSubtree(String reason) {
// Pre-ordered traversal
doInvalidate(reason);
doInvalidateElements(getCachedElements().values(), reason);
doInvalidateAttributes(getCachedAttributes(), reason);
}
}

View file

@ -0,0 +1,104 @@
/* ###
* 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.agent;
import java.awt.*;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.net.SocketAddress;
import javax.swing.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
public class AgentWindow extends JFrame implements WindowListener {
public static final int MAX_LOG_CHARS = 10000;
protected class WindowAppender extends AbstractAppender {
protected WindowAppender() {
super("agentAppender", null, null, true, Property.EMPTY_ARRAY);
}
@Override
public void append(LogEvent event) {
String allText = logArea.getText() + "\n" + event.getMessage().getFormattedMessage();
logArea.setText(
allText.substring(Math.max(0, allText.length() - MAX_LOG_CHARS), allText.length()));
// TODO: Scroll to bottom
}
}
protected final JTextArea logArea = new JTextArea();
protected final JScrollPane logScroll = new JScrollPane(logArea);
public AgentWindow(String title, SocketAddress localAddress) {
super(title);
setLayout(new BorderLayout());
addWindowListener(this);
add(new JLabel("<html>This agent is listening at <b>" + localAddress +
"</b>. Close this window to terminate it.</html>"), BorderLayout.NORTH);
logArea.setEditable(false);
logArea.setFont(Font.getFont(Font.MONOSPACED));
logArea.setAutoscrolls(true);
logScroll.setAutoscrolls(true);
add(logScroll);
setMinimumSize(new Dimension(400, 300));
setVisible(true);
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
ctx.getConfiguration().addAppender(new WindowAppender());
}
@Override
public void windowOpened(WindowEvent e) {
// Dont' care
}
@Override
public void windowClosing(WindowEvent e) {
System.out.println("User closed agent window. Exiting");
System.exit(0);
}
@Override
public void windowClosed(WindowEvent e) {
// Dont' care
}
@Override
public void windowIconified(WindowEvent e) {
// Dont' care
}
@Override
public void windowDeiconified(WindowEvent e) {
// Dont' care
}
@Override
public void windowActivated(WindowEvent e) {
// Dont' care
}
@Override
public void windowDeactivated(WindowEvent e) {
// Dont' care
}
}

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 ghidra.dbg.agent;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetAggregate;
import ghidra.dbg.target.TargetObject;
public class DefaultTargetModelRoot extends DefaultTargetObject<TargetObject, TargetObject>
implements TargetAggregate {
public DefaultTargetModelRoot(DebuggerObjectModel model, String typeHint) {
super(model, null, null, typeHint);
}
}

View file

@ -0,0 +1,447 @@
/* ###
* 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.agent;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.util.CollectionUtils.Delta;
import ghidra.dbg.util.PathUtils;
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
import ghidra.util.Msg;
import ghidra.util.datastruct.ListenerSet;
/**
* A default implementation of {@link TargetObject} suitable for cases where the implementation
* defines the model structure.
*
* @see AbstractTargetObject
* @param <E> the type of child elements
* @param <P> the type of the parent
*/
public class DefaultTargetObject<E extends TargetObject, P extends TargetObject>
extends AbstractTargetObject<P> {
/** Note modifying this directly subverts notifications */
protected final Map<String, E> elements = new TreeMap<>(TargetObjectKeyComparator.ELEMENT);
protected CompletableFuture<Void> curElemsRequest;
/** Note modifying this directly subverts notifications */
protected final Map<String, Object> attributes =
new TreeMap<>(TargetObjectKeyComparator.ATTRIBUTE);
protected CompletableFuture<Void> curAttrsRequest;
/**
* Construct a new default target object
*
* <p>
* Note, this will automatically construct the appropriate path for this object. The implementor
* should not create two objects with the same path. In that event, collisions will probably
* favor the second, but in general, it produces undefined behavior. Also, this does not add the
* new object to its parent. The implementor must do that. This affords an opportunity to
* populate this object's elements and attributes before it is added to the model.
*
* <p>
* The default update mode is set to {@link TargetUpdateMode#UNSOLICITED}, which implies the
* implementation will keep the elements cache in sync with the debugger. It is preferable to
* initialize the cache (via {@link #changeElements(Collection, Collection, String)}) before
* adding this object to the model. If it is infeasible to keep this object's elements cache
* updated, the implementor MUST set the update mode to {@link TargetUpdateMode#SOLICITED}.
* Ideally, for objects whose elements will never change, the mode can be set to
* {@link TargetUpdateMode#FIXED} immediately after populating the elements.
*
* @param model the model to which the object belongs
* @param parent the parent of this object
* @param key the key (attribute name or element index) of this object
* @param typeHint the type hint for this object
*/
public DefaultTargetObject(DebuggerObjectModel model, P parent, String key, String typeHint) {
super(model, parent, key, typeHint);
changeAttributes(List.of(), List.of(), Map.of(DISPLAY_ATTRIBUTE_NAME,
key == null ? "<root>" : key, UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.UNSOLICITED),
"Initialized");
}
/**
* Get an alternative for {@code this} when invoking the listeners.
*
* <p>
* Some implementations may use on a proxy-delegate pattern to implement target objects with
* various combinations of supported interfaces. When this pattern is employed, the delegate
* will extend {@link DefaultTargetObject}, causing {@code this} to refer to the delegate rather
* than the proxy. When invoking listeners, the proxy given by this method is used instead. By
* default, it simply returns {@code this}, providing the expected behavior for typical
* implementations.
*
* @return
*/
public TargetObject getProxy() {
return this;
}
/**
* Check if this object is being observed
*
* <p>
* TODO: It'd be nice if we could know what is being observed: attributes, elements, console
* output, etc. In other words, the sub-types and overrides of the listeners.
*
* <p>
* Note, if an implementation chooses to cull requests because no one is listening, it should
* take care to re-synchronize when a listener is added. The implementor will need to override
* {@link #addListener(TargetObjectListener)}.
*
* @implNote The recommended pattern on the client side for keeping a synchronized cache is to
* add a listener, and then retrieve the current elements. Thus, it is acceptable to
* neglect invoking the callback on the new listener during re-synchronization.
* However, more testing is needed to verify this doesn't cause problems when network
* messaging is involved.
*
* @return true if there is at least one listener on this object
*/
protected boolean isObserved() {
return !listeners.isEmpty();
}
/**
* The elements for this object need to be updated, optionally invalidating caches
*
* <p>
* Note that cache invalidation need not imply flushing {@link #elements}. In fact, it's
* preferable not to, as it becomes unclear how to invoke callbacks without some thrashing
* (i.e., one callback to remove everything, and another to re-populate). Instead, the entries
* in {@link #elements} should be assumed stale. The implementation should additionally not rely
* on any of its internal caches, in order to ensure the fetched elements are fresh. Once
* refreshed, only the changes from the stale cache to the fresh entries need be included in the
* callback.
*
* <p>
* Note that this method completes with {@link Void}. The default implementation of
* {@link #fetchElements(boolean)} will complete with the cached elements, so this method should
* call {@link #changeElements(Collection, Collection, String)} before completion.
*
* @param refresh true to invalidate all caches involved in handling this request
* @return a future which completes when the cache has been updated
*/
protected CompletableFuture<Void> requestElements(boolean refresh) {
return AsyncUtils.NIL;
}
/**
* {@inheritDoc}
*
* @implNote In general, an object should attempt to keep an up-to-date map of its elements,
* usually by capturing the elements and subscribing to changes. This is not possible
* in all circumstances. In those cases, implementations should override this method.
* It may take whatever asynchronous action are necessary to get an up-to-date
* response, then complete with {@link #elementsView}.
*/
@Override
public CompletableFuture<? extends Map<String, ? extends E>> fetchElements(boolean refresh) {
CompletableFuture<Void> req;
synchronized (elements) {
if (refresh || curElemsRequest == null || curElemsRequest.isCompletedExceptionally() ||
getUpdateMode() == TargetUpdateMode.SOLICITED) {
curElemsRequest = requestElements(refresh);
}
req = curElemsRequest;
}
return req.thenApply(__ -> getCachedElements());
}
@Override
public CompletableFuture<? extends Map<String, ? extends E>> fetchElements() {
return fetchElements(false);
}
@Override
public Map<String, E> getCachedElements() {
synchronized (elements) {
return Map.copyOf(elements);
}
}
/**
* {@inheritDoc}
*
* @implNote Overridden here for type
*/
@Override
public CompletableFuture<E> fetchElement(String index) {
return fetchElements().thenApply(elems -> elems.get(index));
}
protected Map<String, E> combineElements(Collection<? extends E> canonical,
Map<String, ? extends E> links) {
Map<String, E> asMap = new LinkedHashMap<>();
for (E e : canonical) {
if (!PathUtils.parent(e.getPath()).equals(getPath())) {
Msg.error(this, "Link found in canonical elements: " + e);
}
asMap.put(e.getIndex(), e);
}
for (Map.Entry<String, ? extends E> ent : links.entrySet()) {
if (!PathUtils.isLink(getPath(), PathUtils.makeKey(ent.getKey()),
ent.getValue().getPath())) {
//Msg.error(this, "Canonical element found in links: " + ent);
}
asMap.put(ent.getKey(), ent.getValue());
}
return asMap;
}
/**
* Set the elements to the given collection, invoking listeners for the delta
*
* <p>
* An existing element is left in place if it's identical to its replacement as in {@code ==}.
* This method also invalidates the sub-trees of removed elements, if any.
*
* @param canonical the desired set of canonical elements
* @param links the desired map of linked elements
* @param reason the reason for the change (used as the reason for invalidation)
* @return the delta from the previous elements
*/
public Delta<E, E> setElements(Collection<? extends E> canonical,
Map<String, ? extends E> links, String reason) {
Map<String, E> elements = combineElements(canonical, links);
return setElements(elements, reason);
}
/**
* TODO: Consider multiple paths for objects, using schema to denote canonical location.
*/
public Delta<E, E> setElements(Collection<? extends E> elements, String reason) {
return setElements(elements, Map.of(), reason);
}
private Delta<E, E> setElements(Map<String, E> elements, String reason) {
Delta<E, E> delta;
synchronized (this.elements) {
delta = Delta.computeAndSet(this.elements, elements, Delta.SAME);
}
doInvalidateElements(delta.removed.values(), reason);
if (!delta.isEmpty()) {
listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
}
return delta;
}
/**
* Change the elements using the given "delta," invoking listeners
*
* <p>
* An existing element is left in place if it's identical to its replacement as in {@code ==}.
* This method also invalidates the sub-trees of removed elements, if any.
*
* @param remove the set of indices to remove
* @param addCanonical the set of canonical elements to add
* @param addLinks the map of linked elements to add
* @param reason the reason for the change (used as the reason for invalidation)
* @return the actual delta from the previous to the current elements
*/
public Delta<E, E> changeElements(Collection<String> remove,
Collection<? extends E> addCanonical, Map<String, ? extends E> addLinks,
String reason) {
Map<String, E> add = combineElements(addCanonical, addLinks);
return changeElements(remove, add, reason);
}
/**
* TODO: Document me
*/
public Delta<E, E> changeElements(Collection<String> remove, Collection<? extends E> add,
String reason) {
return changeElements(remove, add, Map.of(), reason);
}
private Delta<E, E> changeElements(Collection<String> remove, Map<String, E> add,
String reason) {
Delta<E, E> delta;
synchronized (elements) {
delta = Delta.apply(this.elements, remove, add, Delta.SAME);
}
doInvalidateElements(delta.removed.values(), reason);
if (!delta.isEmpty()) {
listeners.fire.elementsChanged(getProxy(), delta.getKeysRemoved(), delta.added);
}
return delta;
}
/**
* The attributes for this object need to be updated, optionally invalidating caches
*
* <p>
* This method being called with -refresh- set is almost always an indication of something gone
* wrong. The client or user should not be attempting to refresh attributes except when there's
* reason to believe the model is not keeping its attribute cache up to date.
*
* <p>
* This method otherwise operates analogously to {@link #requestElements(boolean)}.
*
* @param refresh true to invalidate all caches involved in handling this request
* @return a future which completes when the cache has been updated
*/
protected CompletableFuture<Void> requestAttributes(boolean refresh) {
return AsyncUtils.NIL;
}
/**
* {@inheritDoc}
*
* @implNote An object, except in very limited circumstances, must keep an up-to-date map of its
* attributes, usually by capturing them at construction and subscribing to changes.
* In those limited circumstances, it's usually the case that the object's parent has
* update mode {@link TargetUpdateMode#SOLICITED}, which typically implies this
* object's attributes are unchanging.
*/
@Override
public CompletableFuture<? extends Map<String, ?>> fetchAttributes(boolean refresh) {
CompletableFuture<Void> req;
synchronized (attributes) {
// update_mode does not affect attributes. They always behave as if UNSOLICITED.
if (refresh || curAttrsRequest == null || curAttrsRequest.isCompletedExceptionally()) {
curAttrsRequest = requestAttributes(refresh);
}
req = curAttrsRequest;
}
return req.thenApply(__ -> getCachedAttributes());
}
@Override
public CompletableFuture<? extends Map<String, ?>> fetchAttributes() {
return fetchAttributes(false);
}
@Override
public Map<String, ?> getCachedAttributes() {
synchronized (attributes) {
return Map.copyOf(attributes);
}
}
@Override
public Object getCachedAttribute(String name) {
synchronized (attributes) {
return attributes.get(name);
}
}
protected Map<String, Object> combineAttributes(
Collection<? extends TargetObjectRef> canonicalObjects, Map<String, ?> linksAndValues) {
Map<String, Object> asMap = new LinkedHashMap<>();
for (TargetObjectRef ca : canonicalObjects) {
if (!PathUtils.parent(ca.getPath()).equals(getPath())) {
Msg.error(this, "Link found in canonical attributes: " + ca);
}
asMap.put(ca.getName(), ca);
}
for (Map.Entry<String, ?> ent : linksAndValues.entrySet()) {
Object av = ent.getValue();
if (av instanceof TargetObjectRef) {
TargetObjectRef link = (TargetObjectRef) av;
if (!PathUtils.isLink(getPath(), ent.getKey(), link.getPath())) {
//Msg.error(this, "Canonical attribute found in links: " + ent);
}
}
asMap.put(ent.getKey(), ent.getValue());
}
return asMap;
}
/**
* Set the attributes to the given map, invoking listeners for the delta
*
* <p>
* An existing attribute value is left in place if it's considered equal to its replacement as
* defined by {@link Objects#equals(Object, Object)}. This method also invalidates the sub-trees
* of removed non-reference object-valued attributes.
*
* @param canonicalObjects the desired set of canonical object-valued attributes
* @param linksAndValues the desired map of other attributes
* @param reason the reason for the change (used as the reason for invalidation)
* @return the delta from the previous attributes
*/
public Delta<?, ?> setAttributes(Collection<? extends TargetObject> canonicalObjects,
Map<String, ?> linksAndValues, String reason) {
Map<String, ?> attributes = combineAttributes(canonicalObjects, linksAndValues);
return setAttributes(attributes, reason);
}
/**
* TODO: Document me.
*/
public Delta<?, ?> setAttributes(Map<String, ?> attributes, String reason) {
Delta<?, ?> delta;
synchronized (this.attributes) {
delta = Delta.computeAndSet(this.attributes, attributes, Delta.EQUAL);
}
doInvalidateAttributes(delta.removed, reason);
if (!delta.isEmpty()) {
listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
}
return delta;
}
/**
* Change the attributes using the given "delta," invoking listeners
*
* <p>
* An existing attribute value is left in place if it's considered equal to its replacement as
* defined by {@link Objects#equals(Object, Object)}. This method also invalidates the sub-trees
* of removed non-reference object-valued attributes.
*
* @param remove the set of names to remove
* @param addCanonicalObjects the set of canonical object-valued attributes to add
* @param addLinksAndValues the map of other attributes to add
* @param reason the reason for the change (used as the reason for invalidation)
* @return the actual delta from the previous to the current attributes
*/
public Delta<?, ?> changeAttributes(List<String> remove,
Collection<? extends TargetObject> addCanonicalObjects,
Map<String, ?> addLinksAndValues, String reason) {
Map<String, ?> add = combineAttributes(addCanonicalObjects, addLinksAndValues);
return changeAttributes(remove, add, reason);
}
/**
* This method may soon be made private. Consider
* {@link DefaultTargetObject#changeAttributes(List, Collection, Map, String)} instead.
*
* <p>
* TODO: Consider allowing objects to move and/or occupy multiple paths. The schema could be
* used to specify the "canonical" location.
*/
public Delta<?, ?> changeAttributes(List<String> remove, Map<String, ?> add, String reason) {
Delta<?, ?> delta;
synchronized (attributes) {
delta = Delta.apply(this.attributes, remove, add, Delta.EQUAL);
}
doInvalidateAttributes(delta.removed, reason);
if (!delta.isEmpty()) {
listeners.fire.attributesChanged(getProxy(), delta.getKeysRemoved(), delta.added);
}
return delta;
}
public ListenerSet<TargetObjectListener> getListeners() {
return listeners;
}
}

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.agent;
import java.util.List;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.attributes.TargetObjectRef;
public class DefaultTargetObjectRef implements TargetObjectRef {
private final DebuggerObjectModel model;
private final List<String> path;
private final int hash;
public DefaultTargetObjectRef(DebuggerObjectModel model, List<String> path) {
this.model = model;
this.path = path;
this.hash = computeHashCode();
}
@Override
public boolean equals(Object obj) {
return doEquals(obj);
}
@Override
public int hashCode() {
return hash;
}
@Override
public DebuggerObjectModel getModel() {
return model;
}
@Override
public List<String> getPath() {
return path;
}
@Override
public String toString() {
return "<Ref to " + path + " in " + model + ">";
}
}

View file

@ -0,0 +1,62 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.agent;
import java.util.*;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.target.TargetObject;
/**
* An interface for {@link TargetObject} implementations which would like notifications of
* invalidation from parents whose implementations derive from {@link AbstractTargetObject}.
*
* <p>
* This is a mechanism used internally by model implementations to manage invalidation of subtrees
* according to conventions. If every object implementation in the model is based on
* {@link AbstractTargetObject} (or {@link DefaultTargetObject}), then this is all taken care of.
* The implementor need only call {@link DefaultTargetObject#setAttributes(Map, String)} or similar,
* and invalidation is taken care of. To invalidate the entire model, simply call
* {@link #invalidateSubtree(String)} on the root.
*
* <p>
* Explicitly implementing this interface becomes necessary when there exists a mixture of objects
* in the model, some derived from {@link AbstractTargetObject} and some not, including cases where
* {@link AbstractTargetObject} is obscured by a proxy scheme. A parent based on
* {@link AbstractTargetObject} will call {@link #invalidateSubtree(String)} on any of its children
* being removed from the model, including when the parent is invalidated. The implementation should
* release its resources and also invalidate its children accordingly. For the case of a proxy whose
* delegate derives from {@link AbstractTargetObject}, the proxy must include this interface, and
* the call need only be forwarded to the delegate.
*/
public interface InvalidatableTargetObjectIf extends TargetObjectRef {
/**
* Invalidate this subtree
*
* <p>
* In most cases, this need only be invoked on the root to destroy the entire model, or if the
* implementation is managing the collections of children. Otherwise,
* {@link DefaultTargetObject#changeAttributes(List, Map, String)},
* {@link DefaultTargetObject#changeElements(Collection, Collection, String)},
* {@link DefaultTargetObject#setAttributes(Map, String)}, and
* {@link DefaultTargetObject#setElements(Collection, String)} will automatically invoke this
* method when they detect object removal.
*
* @param reason a human-consumable explanation for the removal
*/
void invalidateSubtree(String reason);
}

View file

@ -0,0 +1,138 @@
/* ###
* 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.agent;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.error.DebuggerModelTypeException;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.util.PathUtils;
public interface SpiDebuggerObjectModel extends DebuggerObjectModel {
@Override
default boolean isAlive() {
return true;
}
@Override
default CompletableFuture<Void> ping(String content) {
return AsyncUtils.NIL;
}
@Override
default TargetObjectRef createRef(List<String> path) {
return new DefaultTargetObjectRef(this, path);
}
public default CompletableFuture<Object> fetchFreshChild(TargetObject obj, String key) {
if (PathUtils.isIndex(key)) {
return obj.fetchElements(true).thenApply(elements -> {
return elements.get(PathUtils.parseIndex(key));
});
}
return obj.fetchAttributes(true).thenApply(attributes -> {
return attributes.get(key);
});
}
public default CompletableFuture<Object> fetchSuccessorValue(TargetObject obj,
List<String> path, boolean refresh, boolean followLinks) {
if (path.isEmpty()) {
return CompletableFuture.completedFuture(obj);
}
String key = path.get(0);
CompletableFuture<?> futureChild;
if (refresh) {
futureChild = fetchFreshChild(obj, key);
}
else {
futureChild = obj.fetchChild(key);
}
return futureChild.thenCompose(c -> {
if (c == null) {
return AsyncUtils.nil();
}
if (!(c instanceof TargetObjectRef)) {
if (path.size() == 1) {
return CompletableFuture.completedFuture(c);
}
else {
List<String> p = PathUtils.extend(obj.getPath(), key);
throw DebuggerModelTypeException.typeRequired(c, p, TargetObjectRef.class);
}
}
TargetObjectRef childRef = (TargetObjectRef) c;
if (PathUtils.isLink(obj.getPath(), key, childRef.getPath()) && !followLinks) {
if (path.size() == 1) {
return CompletableFuture.completedFuture(c);
}
else {
List<String> p = PathUtils.extend(obj.getPath(), key);
throw DebuggerModelTypeException.linkForbidden(childRef, p);
}
}
return childRef.fetch().thenCompose(childObj -> {
List<String> remains = path.subList(1, path.size());
return fetchSuccessorValue(childObj, remains, refresh, followLinks);
});
});
}
@Override
public default CompletableFuture<?> fetchModelValue(List<String> path, boolean refresh) {
return fetchModelRoot().thenCompose(root -> {
return fetchSuccessorValue(root, path, refresh, true);
});
}
@Override
public default CompletableFuture<?> fetchModelValue(List<String> path) {
return fetchModelValue(path, false);
}
@Override
public default CompletableFuture<? extends Map<String, ? extends TargetObjectRef>> fetchObjectElements(
List<String> path, boolean refresh) {
return fetchModelObject(path).thenCompose(obj -> {
if (obj == null) {
return AsyncUtils.nil();
}
return obj.fetchElements(refresh);
});
}
@Override
default CompletableFuture<? extends Map<String, ?>> fetchObjectAttributes(List<String> path,
boolean refresh) {
return fetchModelObject(path).thenCompose(obj -> {
if (obj == null) {
return AsyncUtils.nil();
}
return obj.fetchAttributes(refresh);
});
}
@Override
default void invalidateAllLocalCaches() {
// Do nothing
}
}

View file

@ -0,0 +1,42 @@
/* ###
* 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.attributes;
public interface TargetArrayDataType extends TargetDataType {
public class DefaultTargetArrayDataType implements TargetArrayDataType {
protected final TargetDataType elementType;
protected final int elementCount;
public DefaultTargetArrayDataType(TargetDataType elementType, int elementCount) {
this.elementType = elementType;
this.elementCount = elementCount;
}
@Override
public TargetDataType getElementType() {
return elementType;
}
@Override
public int getElementCount() {
return elementCount;
}
}
TargetDataType getElementType();
int getElementCount();
}

View file

@ -0,0 +1,57 @@
/* ###
* 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.attributes;
/**
* A bitfield-modified data type
*
* This is only applicable to fields of structures.
*/
public interface TargetBitfieldDataType extends TargetDataType {
public class DefaultTargetBitfieldDataType implements TargetBitfieldDataType {
protected final TargetDataType fieldType;
protected final int leastBitPosition;
protected final int bitLength;
public DefaultTargetBitfieldDataType(TargetDataType fieldType, int leastBitPosition,
int bitLength) {
this.fieldType = fieldType;
this.leastBitPosition = leastBitPosition;
this.bitLength = bitLength;
}
@Override
public TargetDataType getFieldType() {
return fieldType;
}
@Override
public int getLeastBitPosition() {
return leastBitPosition;
}
@Override
public int getBitLength() {
return bitLength;
}
}
TargetDataType getFieldType();
int getLeastBitPosition();
int getBitLength();
}

View file

@ -0,0 +1,24 @@
/* ###
* 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.attributes;
import ghidra.dbg.attributes.TargetPrimitiveDataType.DefaultTargetPrimitiveDataType;
import ghidra.dbg.attributes.TargetPrimitiveDataType.PrimitiveKind;
public interface TargetDataType {
TargetDataType UNDEFINED1 =
new DefaultTargetPrimitiveDataType(PrimitiveKind.UNDEFINED, 1);
}

View file

@ -0,0 +1,22 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.attributes;
import ghidra.dbg.target.TargetNamedDataType;
public interface TargetNamedDataTypeRef<T extends TargetNamedDataType<T>>
extends TypedTargetObjectRef<T>, TargetDataType {
}

View file

@ -0,0 +1,542 @@
/* ###
* 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.attributes;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import ghidra.async.AsyncFence;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.error.DebuggerModelTypeException;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
import ghidra.dbg.util.PathUtils.PathComparator;
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
/**
* A reference (or stub) to a target object
*
* <p>
* These can be constructed and manipulated client-side, without querying the agent. However, a
* reference is not guaranteed to refer to a valid object.
*
* <p>
* Note that it is OK for more than one {@link TargetObjectRef} to refer to the same path. These
* objects must override {@link #equals(Object)} and {@link #hashCode()}.
*/
public interface TargetObjectRef extends Comparable<TargetObjectRef> {
/**
* Check for target object equality
*
* <p>
* Because interfaces cannot provide default implementations of {@link #equals(Object)}, this
* methods provides a means of quickly implementing it within a class. Because everything that
* constitutes target object equality is contained in the reference (model, path), there should
* never be a need to perform more comparison than is provided here.
*
* @param obj the other object
* @return true if they are equal
*/
default boolean doEquals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof TargetObjectRef)) {
return false;
}
TargetObjectRef that = (TargetObjectRef) obj;
return this.getModel() == that.getModel() &&
Objects.equals(this.getPath(), that.getPath());
}
/**
* Pre-compute this object's hash code
*
* <p>
* Because interfaces cannot provide default implementations of {@link #hashCode()}, this method
* provides a means of quickly implementing it within a class. Because everything that
* constitutes target object equality is <em>immutable</em> and contained in the reference
* (model, path), this hash should be pre-computed a construction. There should never be a need
* to incorporate more fields into the hash than is incorporated here.
*
* @return the hash
*/
default int computeHashCode() {
return System.identityHashCode(getModel()) * 31 + Objects.hash(getPath().toArray());
}
/**
* {@inheritDoc}
*
* <p>
* A friendly reminder to override
*
* @see #doEquals(Object)
*/
@Override
boolean equals(Object obj);
/**
* {@inheritDoc}
*
* <p>
* A friendly reminder to override
*
* @see #computeHashCode()
*/
@Override
int hashCode();
/**
* {@inheritDoc}
*
* <p>
* Along with {@link #doEquals(Object)} and {@link #computeHashCode()}, these obey the Rule of
* Three, comparing first the objects' models (by name, then identity), then their paths. Like
* the other methods, this incorporates everything that constitutes a unique target object.
* There should never be a need to override or otherwise extend this.
*/
@Override
default int compareTo(TargetObjectRef that) {
if (this == that) {
return 0;
}
DebuggerObjectModel thisModel = this.getModel();
DebuggerObjectModel thatModel = that.getModel();
if (thisModel != thatModel) {
if (thisModel == null) {
return -1;
}
if (thatModel == null) {
return 1;
}
int result = thisModel.toString().compareTo(thatModel.toString());
if (result == 0) {
return Integer.compare(
System.identityHashCode(thisModel),
System.identityHashCode(thatModel));
}
return result;
}
return PathComparator.KEYED.compare(this.getPath(), that.getPath());
}
/**
* Get the actual object
*
* @return a future which completes with the object
*/
public default CompletableFuture<? extends TargetObject> fetch() {
return getModel().fetchModelObject(getPath());
}
/**
* Cast the reference (or object) to the requested interface
*
* <p>
* Upon retrieval, or if the object is already available locally (by implementation or by
* proxy), the object is checked for the requested interface and cast appropriately.
*
* @param cls the class for the required interface. Use {@code tclass} to satisfy the recursive
* type parameter.
* @return the reference (or object) conforming to the required type
* @throws DebuggerModelTypeException if the object is available locally but does not support
* the desired interface
*/
public default <T extends TypedTargetObject<T>> TypedTargetObjectRef<T> as(Class<T> cls) {
return TypedTargetObjectRef.casting(cls, this);
}
/**
* Get the model to which this object belongs
*
* @return the model
*/
public DebuggerObjectModel getModel();
/**
* Get the path (i.e., list of names from root to this object).
*
* <p>
* Every object must have a unique path. Parts of the path which are indices, i.e., which
* navigate the elements, are enclosed in brackets @{code []}. Parts which navigate attributes
* are simply the attribute name.
*
* <p>
* More than just a location, the path provides a hint to the object's scope of applicability.
* For example, a {@link TargetMemory} attribute of a process is assumed accessible to every
* thread of that process, since those threads are descendants.
*
* @implNote it would be wise to cache the result of this computation. If the object has a
* strict location, then the implementation should just return it directly.
*
* @return the canonical path of the object
*/
public List<String> getPath();
/**
* Get the path joined by the given separator
*
* <p>
* Note that no check is applied to guarantee the path separator does not appear in an element
* name.
*
* @see #getPath()
* @param sep the path separator
* @return the joined path
* @deprecated use {@link PathUtils#toString()} instead
*/
@Deprecated
public default String getJoinedPath(String sep) {
return StringUtils.join(getPath(), sep);
}
/**
* Get the key for this object
*
* <p>
* The object's key should be that assigned by the actual debugger, if applicable. If this is an
* element, the key should include the brackets {@code []}. If it is an attribute, it should
* simply be the name.
*
* @return the key, or {@code null} if this is the root
*/
public default String getName() {
return PathUtils.getKey(getPath());
}
/**
* Get the index for this object
*
* @return they index, or {@code null} if this is the root
* @throws IllegalArgumentException if this object is not an element of its parent
*/
public default String getIndex() {
return PathUtils.getIndex(getPath());
}
/**
* Check if this is the root target debug object
*
* @return true if root, false otherwise
*/
public default boolean isRoot() {
return getPath().isEmpty();
}
/**
* Get a reference to the parent of this reference
*
* @return the parent reference, or {@code null} if this refers to the root
*/
public default TargetObjectRef getParent() {
List<String> parentPath = PathUtils.parent(getPath());
if (parentPath == null) {
return null;
}
return getModel().createRef(parentPath);
}
/**
* Fetch all the attributes of this object
*
* <p>
* Attributes are usually keyed by a string, and the types are typically not uniform. Some
* attributes are primitives, while others are other target objects.
*
* <p>
* Note, for objects, {@link TargetObject#getCachedAttributes()} should be sufficient to get an
* up-to-date view of the attributes, since the model should be pushing attribute updates to the
* object automatically. {@code fetchAttributes} should only be invoked on references, or in the
* rare case the client needs to ensure the attributes are fresh.
*
* @param refresh true to invalidate all caches involved in handling this request
* @return a future which completes with a name-value map of attributes
*/
public default CompletableFuture<? extends Map<String, ?>> fetchAttributes(boolean refresh) {
return getModel().fetchObjectAttributes(getPath(), refresh);
}
/**
* Fetch all attributes of this object, without refreshing
*
* @see #fetchAttributes(boolean)
*/
public default CompletableFuture<? extends Map<String, ?>> fetchAttributes() {
return fetchAttributes(false);
}
/**
* Fetch an attribute by name
*
* @see #fetchAttributes()
* @see PathUtils#isInvocation(String)
* @implNote for attributes representing method invocations, the name will not likely be in the
* map given by {@link #fetchAttributes()}. It will be generated upon request. The
* implementation should cache the generated attribute until the attribute cache is
* refreshed. TODO: Even if the method is likely to return a different value on its
* next invocation? Yes, I think so. The user should manually refresh in those cases.
* @return a future which completes with the attribute or with {@code null} if the attribute
* does not exist
*/
public default CompletableFuture<?> fetchAttribute(String name) {
return fetchAttributes().thenApply(m -> m.get(name));
}
/**
* Fetch all the elements of this object
*
* <p>
* Elements are usually keyed numerically, but allows strings for flexibility. They values are
* target objects, uniform in type, and should generally share the same attributes. The keys
* must not contain the brackets {@code []}. Implementations should ensure that the elements are
* presented in order by key -- not necessarily lexicographically. To ensure clients can easily
* maintain correct sorting, the recommendation is to present the keys as follows: Keys should
* be the numeric value encoded as strings in base 10 or base 16 as appropriate, using the least
* number of digits needed. While rarely used, a comma-separated list of indices may be
* presented. Key comparators should separate the indices, attempt to convert each to a number,
* and then sort using the left-most indices first. Indices which cannot be converted to numbers
* should be sorted lexicographically. It is the implementation's responsibility to ensure all
* indices follow a consistent scheme.
*
* @param refresh true to invalidate all caches involved in handling this request
* @return a future which completes with a index-value map of elements
*/
public default CompletableFuture<? extends Map<String, ? extends TargetObjectRef>> fetchElements(
boolean refresh) {
return getModel().fetchObjectElements(getPath(), refresh);
}
/**
* Fetch all elements of this object, without refreshing
*
* @see #fetchElements(boolean)
*/
public default CompletableFuture<? extends Map<String, ? extends TargetObjectRef>> fetchElements() {
return fetchElements(false);
}
/**
* Fetch all children (elements and attributes) of this object
*
* <p>
* Note that keys for element indices here must contain the brackets {@code []} to distinguish
* them from attribute names.
*
* @see #fetchElements()
* @see #fetchAttributes()
*
* @param refresh true to invalidate all caches involved in handling this request
* @return a future which completes with a name-value map of children
*/
public default CompletableFuture<? extends Map<String, ?>> fetchChildren(boolean refresh) {
AsyncFence fence = new AsyncFence();
Map<String, Object> children = new TreeMap<>(TargetObjectKeyComparator.CHILD);
fence.include(fetchElements(refresh).thenAccept(elements -> {
for (Map.Entry<String, ?> ent : elements.entrySet()) {
children.put(PathUtils.makeKey(ent.getKey()), ent.getValue());
}
}));
fence.include(fetchAttributes(refresh).thenAccept(children::putAll));
return fence.ready().thenApply(__ -> children);
}
/**
* Fetch all children of this object, without refreshing
*
* @see #fetchChildren(boolean)
*/
public default CompletableFuture<? extends Map<String, ?>> fetchChildren() {
return fetchChildren(false);
}
/**
* Fetch an element by its index
*
* @return a future which completes with the element or with {@code null} if it does not exist
*/
public default CompletableFuture<? extends TargetObject> fetchElement(String index) {
return getModel().fetchModelObject(PathUtils.index(getPath(), index));
}
/**
* Fetch a child (element or attribute) by key (index or name, respectively)
*
* <p>
* Indices are distinguished from names by the presence or absence of brackets {@code []}. If
* the key is a bracket-enclosed index, this will retrieve a child, otherwise, it will retrieve
* an attribute.
*
* @see #fetchAttribute(String)
* @see #fetchElement(String)
* @return a future which completes with the child
*/
public default CompletableFuture<?> fetchChild(String key) {
if (PathUtils.isIndex(key)) {
return fetchElement(PathUtils.parseIndex(key));
}
return fetchAttribute(key);
}
/**
* Fetch the children (elements and attributes) of this object which support the requested
* interface
*
* <p>
* If no children support the given interface, the result is the empty set.
*
* @param <T> the requested interface
* @param iface the class of the requested interface
* @return a future which completes with a name-value map of children supporting the given
* interface
*/
public default <T extends TargetObject> //
CompletableFuture<? extends Map<String, ? extends T>> fetchChildrenSupporting(
Class<T> iface) {
return fetchChildren().thenApply(m -> m.entrySet()
.stream()
.filter(e -> iface.isAssignableFrom(e.getValue().getClass()))
.collect(Collectors.toMap(Entry::getKey, e -> iface.cast(e.getValue()))));
}
/**
* Fetch the value at the given sub-path from this object
*
* <p>
* Extend this reference's path with the given sub-path and request that value from the same
* model.
*
* @param sub the sub-path to the value
* @return a future which completes with the value or with {@code null} if the path does not
* exist
*/
public default CompletableFuture<?> fetchValue(List<String> sub) {
return getModel().fetchModelObject(PathUtils.extend(getPath(), sub));
}
/**
* @see #fetchValue(List)
*/
public default CompletableFuture<?> fetchValue(String... sub) {
return fetchValue(List.of(sub));
}
/**
* Fetch the successor object at the given sub-path from this object
*
* <p>
* Extend this reference's path with the given sub-path and request that object from the same
* model.
*
* @param sub the sub-path to the successor
* @return a future which completes with the object or with {@code null} if it does not exist
*/
public default CompletableFuture<? extends TargetObject> fetchSuccessor(List<String> sub) {
return getModel().fetchModelObject(PathUtils.extend(getPath(), sub));
}
/**
* @see #fetchSuccessor(List)
*/
public default CompletableFuture<? extends TargetObject> fetchSuccessor(String... sub) {
return fetchSuccessor(List.of(sub));
}
/**
* Get a reference to a successor of this object
*
* <p>
* Extend this reference's path with the given sub-path, creating a new reference in the same
* model. This is mere path manipulation. The referenced object may not exist.
*
* @param sub the sub-path to the successor
* @return a reference to the successor
*/
public default TargetObjectRef getSuccessor(List<String> sub) {
return getModel().createRef(PathUtils.extend(getPath(), sub));
}
/**
* @see #getSuccessor(List)
*/
public default TargetObjectRef getSuccessor(String... sub) {
return getSuccessor(List.of(sub));
}
/**
* Fetch the attributes of the model at the given sub-path from this object
*
* @param sub the sub-path to the successor whose attributes to list
* @return a future map of attributes
*/
public default CompletableFuture<? extends Map<String, ?>> fetchSubAttributes(
List<String> sub) {
return getModel().fetchObjectAttributes(PathUtils.extend(getPath(), sub));
}
/**
* @see #fetchSubAttributes(List)
*/
public default CompletableFuture<? extends Map<String, ?>> fetchSubAttributes(
String... sub) {
return fetchSubAttributes(List.of(sub));
}
/**
* Fetch the attribute of a successor object, using a sub-path from this object
*
* <p>
* Extends this object's path with the given sub-path and request that attribute from the same
* model.
*
* @param sub the sub-path to the attribute
* @return a future which completes with the value or with {@code null} if it does not exist
*/
public default CompletableFuture<?> fetchSubAttribute(List<String> sub) {
return getModel().fetchObjectAttribute(PathUtils.extend(getPath(), sub));
}
/**
* @see #fetchSubAttribute(List)
*/
public default CompletableFuture<?> fetchSubAttribute(String... sub) {
return fetchSubAttribute(List.of(sub));
}
/**
* Fetch the elements of the model at the given sub-path from this object
*
* @param sub the sub-path to the successor whose elements to list
* @return a future map of elements
*/
public default CompletableFuture<? extends Map<String, ? extends TargetObjectRef>> fetchSubElements(
List<String> sub) {
return getModel().fetchObjectElements(PathUtils.extend(getPath(), sub));
}
/**
* @see #fetchSubElements(List)
*/
public default CompletableFuture<? extends Map<String, ? extends TargetObjectRef>> fetchSubElements(
String... sub) {
return fetchSubElements(List.of(sub));
}
}

View file

@ -0,0 +1,66 @@
/* ###
* 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.attributes;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncFence;
import ghidra.dbg.target.TargetObject;
public interface TargetObjectRefList<T extends TargetObjectRef> extends List<T> {
public static class EmptyTargetObjectRefList<T extends TargetObjectRef> extends AbstractList<T>
implements TargetObjectRefList<T> {
@Override
public T get(int index) {
return null;
}
@Override
public int size() {
return 0;
}
}
public static class DefaultTargetObjectRefList<T extends TargetObjectRef> extends ArrayList<T>
implements TargetObjectRefList<T> {
// Nothing to add
}
public static final TargetObjectRefList<?> EMPTY = new EmptyTargetObjectRefList<>();
@SuppressWarnings("unchecked")
public static <T extends TargetObjectRef> TargetObjectRefList<T> of() {
return (TargetObjectRefList<T>) EMPTY;
}
@SuppressWarnings("unchecked")
public static <T extends TargetObjectRef> TargetObjectRefList<T> of(T... e) {
DefaultTargetObjectRefList<T> list = new DefaultTargetObjectRefList<>();
list.addAll(List.of(e));
return list;
}
public default CompletableFuture<? extends List<? extends TargetObject>> fetchAll() {
AsyncFence fence = new AsyncFence();
TargetObject[] result = new TargetObject[size()];
for (int i = 0; i < result.length; i++) {
int j = i;
fence.include(get(i).fetch().thenAccept(obj -> result[j] = obj));
}
return fence.ready().thenApply(__ -> Arrays.asList(result));
}
}

View file

@ -0,0 +1,33 @@
/* ###
* 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.attributes;
public interface TargetPointerDataType extends TargetDataType {
public class DefaultTargetPointerDataType implements TargetPointerDataType {
protected final TargetDataType referentType;
public DefaultTargetPointerDataType(TargetDataType referentType) {
this.referentType = referentType;
}
@Override
public TargetDataType getReferentType() {
return referentType;
}
}
TargetDataType getReferentType();
}

View file

@ -0,0 +1,55 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.attributes;
public interface TargetPrimitiveDataType extends TargetDataType {
public static final TargetDataType VOID =
new DefaultTargetPrimitiveDataType(PrimitiveKind.VOID, 0);
enum PrimitiveKind {
UNDEFINED,
@SuppressWarnings("hiding")
VOID,
UINT,
SINT,
FLOAT,
COMPLEX;
}
public class DefaultTargetPrimitiveDataType implements TargetPrimitiveDataType {
protected final PrimitiveKind kind;
protected final int length;
public DefaultTargetPrimitiveDataType(PrimitiveKind kind, int length) {
this.kind = kind;
this.length = length;
}
@Override
public PrimitiveKind getKind() {
return kind;
}
@Override
public int getLength() {
return length;
}
}
PrimitiveKind getKind();
int getLength();
}

View file

@ -0,0 +1,57 @@
/* ###
* 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.attributes;
import java.util.*;
import org.apache.commons.collections4.list.AbstractListDecorator;
import ghidra.dbg.util.CollectionUtils.AbstractEmptyList;
public interface TargetStringList extends List<String> {
public static class EmptyTargetStringList extends AbstractEmptyList<String>
implements TargetStringList {
}
public static class ImmutableTargetStringList extends AbstractListDecorator<String>
implements TargetStringList {
public ImmutableTargetStringList(String... strings) {
super(List.of(strings));
}
public ImmutableTargetStringList(Collection<String> col) {
super(List.copyOf(col));
}
}
public static class MutableTargetStringList extends ArrayList<String>
implements TargetStringList {
}
public static final TargetStringList EMPTY = new EmptyTargetStringList();
public static TargetStringList of() {
return EMPTY;
}
public static TargetStringList of(String... strings) {
return new ImmutableTargetStringList(strings);
}
public static TargetStringList copyOf(Collection<String> strings) {
return new ImmutableTargetStringList(strings);
}
}

View file

@ -0,0 +1,74 @@
/* ###
* 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.attributes;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TypedTargetObject;
public interface TypedTargetObjectRef<T extends TargetObject> extends TargetObjectRef {
public class CastingTargetObjectRef<T extends TypedTargetObject<T>>
implements TypedTargetObjectRef<T> {
private final Class<T> cls;
private final TargetObjectRef ref;
public CastingTargetObjectRef(Class<T> cls, TargetObjectRef ref) {
this.cls = cls;
this.ref = ref;
}
@Override
public boolean equals(Object obj) {
return ref.equals(obj);
}
@Override
public int hashCode() {
return ref.hashCode();
}
@Override
public DebuggerObjectModel getModel() {
return ref.getModel();
}
@Override
public List<String> getPath() {
return ref.getPath();
}
@Override
public CompletableFuture<? extends T> fetch() {
return ref.fetch().thenApply(o -> o.as(cls));
}
}
public static <T extends TypedTargetObject<T>> TypedTargetObjectRef<T> casting(Class<T> cls,
TargetObjectRef ref) {
if (ref instanceof CastingTargetObjectRef) {
CastingTargetObjectRef<?> casting = (CastingTargetObjectRef<?>) ref;
return new CastingTargetObjectRef<>(cls, casting.ref);
}
return new CastingTargetObjectRef<>(cls, ref);
}
@Override
CompletableFuture<? extends T> fetch();
}

View file

@ -0,0 +1,30 @@
/* ###
* 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.attributes;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.TypedTargetObject;
public interface TypedTargetObjectRefList<T extends TypedTargetObject<T>>
extends TargetObjectRefList<TypedTargetObjectRef<T>> {
@Override
@SuppressWarnings("unchecked")
default CompletableFuture<? extends List<? extends T>> fetchAll() {
return (CompletableFuture<? extends List<? extends T>>) TargetObjectRefList.super.fetchAll();
}
}

View file

@ -0,0 +1,36 @@
/* ###
* 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.error;
/**
* A model method was given an illegal argument.
*
* If the argument is an object, but of the wrong type, please use
* {@link DebuggerModelTypeException} instead. If the argument is a path which doesn't exist
* in the model, use {@link DebuggerModelNoSuchPathException} instead.
*
* @implNote I am not re-using {@link IllegalArgumentException} here, as I don't want any of those
* thrown internally to be passed to the client.
*/
public class DebuggerIllegalArgumentException extends DebuggerRuntimeException {
public DebuggerIllegalArgumentException(String message) {
super(message);
}
public DebuggerIllegalArgumentException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,38 @@
/* ###
* 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.error;
import ghidra.dbg.target.TargetMemory;
import ghidra.program.model.address.Address;
/**
* An exception for when there is an unknown (possibly permanent) condition preventing memory access
* via {@link TargetMemory#readMemory(Address, int)} and
* {@link TargetMemory#writeMemory(Address, byte[])}
*
* <p>
* If the underlying debugger is simply in a state that prevents the request from being fulfilled,
* e.g., the target process is running, then use {@link DebuggerModelAccessException} instead.
*/
public class DebuggerMemoryAccessException extends DebuggerRuntimeException {
public DebuggerMemoryAccessException(String message, Throwable cause) {
super(message, cause);
}
public DebuggerMemoryAccessException(String message) {
super(message);
}
}

View file

@ -0,0 +1,44 @@
/* ###
* 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.error;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.target.TargetAccessConditioned.TargetAccessibility;
import ghidra.dbg.util.PathUtils;
/**
* An exception that may be thrown if an ancestor of an object is
* {@link TargetAccessibility#INACCESSIBLE} at the time a method is invoked on that object.
*
* <p>
* In general, this exception should be considered a temporary condition, meaning the client should
* just try again later. If a UI is involved, the error, if displayed at all, should be displayed in
* the least obtrusive manner possible.
*/
public class DebuggerModelAccessException extends DebuggerRuntimeException {
public DebuggerModelAccessException(String message, Throwable cause) {
super(message, cause);
}
public DebuggerModelAccessException(String message) {
super(message);
}
public DebuggerModelAccessException(TargetObjectRef ref) {
super("Model path " + PathUtils.toString(ref.getPath()) + " is not accessible");
}
}

View file

@ -0,0 +1,26 @@
/* ###
* 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.error;
public class DebuggerModelNoSuchPathException extends DebuggerRuntimeException {
public DebuggerModelNoSuchPathException(String message, Throwable cause) {
super(message, cause);
}
public DebuggerModelNoSuchPathException(String message) {
super(message);
}
}

View file

@ -0,0 +1,49 @@
/* ###
* 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.error;
import java.util.List;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.util.PathUtils;
/**
*
* @implNote I am not re-using {@link ClassCastException} here, as I don't want any of those thrown
* internally to be passed to the client.
*/
public class DebuggerModelTypeException extends DebuggerRuntimeException {
public static DebuggerModelTypeException typeRequired(Object got, List<String> path,
Class<?> expected) {
return new DebuggerModelTypeException("Path " + PathUtils.toString(path) +
" does not refer to a " + expected.getSimpleName() + ". Got " + got + " (of " +
got.getClass().getSimpleName() + ")");
}
public static DebuggerModelTypeException linkForbidden(TargetObjectRef got, List<String> path) {
return new DebuggerModelTypeException("Path " + PathUtils.toString(path) +
" is a link to " + PathUtils.toString(got.getPath()) +
", but following links was forbidden");
}
public DebuggerModelTypeException(String message) {
super(message);
}
public DebuggerModelTypeException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,37 @@
/* ###
* 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.error;
import ghidra.dbg.target.TargetRegisterBank;
/**
* An exception for when there is an unknown (possibly permanent) condition preventing register
* access via {@link TargetRegisterBank#readRegisters(java.util.Collection)},
* {@link TargetRegisterBank#writeRegisters(java.util.Map)}, and related methods.
*
* <p>
* If the underlying debugger is simply in a state that prevents the request from being fulfilled,
* e.g., the target process is running, then use {@link DebuggerModelAccessException} instead.
*/
public class DebuggerRegisterAccessException extends DebuggerRuntimeException {
public DebuggerRegisterAccessException(String message, Throwable cause) {
super(message, cause);
}
public DebuggerRegisterAccessException(String message) {
super(message);
}
}

View file

@ -0,0 +1,26 @@
/* ###
* 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.error;
public class DebuggerRuntimeException extends RuntimeException {
public DebuggerRuntimeException(String message, Throwable cause) {
super(message, cause);
}
public DebuggerRuntimeException(String message) {
super(message);
}
}

View file

@ -0,0 +1,25 @@
/* ###
* 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.error;
/**
* The request generated an error with a message for the user
*/
public class DebuggerUserException extends DebuggerRuntimeException {
public DebuggerUserException(String message) {
super(message);
}
}

View file

@ -0,0 +1,237 @@
/* ###
* 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.memory;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.lang3.exception.ExceptionUtils;
import com.google.common.collect.*;
import com.google.common.primitives.UnsignedLong;
import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.TargetProcess;
import ghidra.dbg.target.TargetThread;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.util.Msg;
/**
* A cached memory wrapper
*
* Because debugging channels can be slow, memory reads and writes ought to be cached for the
* duration a thread (and threads sharing the same memory) are stopped. This highly-recommended
* convenience implements a write-through single-layer cache. The implementor need only provide
* references to the basic asynchronous read/write methods. Those are usually private methods of the
* {@link TargetThread} or {@link TargetProcess} implementation. The public read/write methods just
* wrap the read/write methods provided by this cache.
*
* Implementation note: The cache is backed by a {@link SemisparseByteArray}, which is well-suited
* for reads and writes within a locality. Nothing is evicted from the cache automatically. All
* eviction is done manually by a call to {@link #clear()}. During a debug session, there are
* typically few reads or writes between execution steps. Given the purpose is to eliminate
* unnecessary reads, there is little motivation to implement an automatic eviction strategy. The
* debugger client implementation must clear the cache at each execution step, unless it can
* accurately determine that certain ranges of memory cannot be affected by a given step.
*/
public class CachedMemory implements MemoryReader, MemoryWriter {
private final SemisparseByteArray memory = new SemisparseByteArray();
private final NavigableMap<UnsignedLong, PendingRead> pendingByLoc = new TreeMap<>();
private final MemoryReader reader;
private final MemoryWriter writer;
protected static class PendingRead {
final Range<UnsignedLong> range;
final CompletableFuture<Void> future;
protected PendingRead(Range<UnsignedLong> range, CompletableFuture<Void> future) {
this.range = range;
this.future = future;
}
}
/**
* Create a new cache wrapping the given read/write methods
*
* The wrapped read/write methods are usually private
*
* @param reader the read implementation, usually a method reference
* @param writer the write implementation, usually a method reference
*/
public CachedMemory(MemoryReader reader, MemoryWriter writer) {
this.reader = reader;
this.writer = writer;
}
@Override
public CompletableFuture<Void> writeMemory(long addr, byte[] data) {
// TODO: Do I write to the cache first, and correct if an error occurs?
// Or leave it as write to cache on known success
return writer.writeMemory(addr, data).thenAccept(__ -> {
memory.putData(addr, data);
});
}
protected synchronized CompletableFuture<Void> waitForReads(long addr, int len) {
RangeSet<UnsignedLong> undefined = memory.getUninitialized(addr, addr + len);
// Do the reads in parallel
AsyncFence fence = new AsyncFence();
for (Range<UnsignedLong> rng : undefined.asRanges()) {
findPendingOrSchedule(rng, fence);
}
return fence.ready();
}
protected synchronized void findPendingOrSchedule(final Range<UnsignedLong> rng,
final AsyncFence fence) {
RangeSet<UnsignedLong> needRequests = TreeRangeSet.create();
needRequests.add(rng);
// Find all existing requests and include them in the fence
// Check if there is a preceding range which overlaps the desired range:
Entry<UnsignedLong, PendingRead> prec = pendingByLoc.lowerEntry(rng.lowerEndpoint());
if (prec != null) {
PendingRead pending = prec.getValue();
if (!pending.future.isCompletedExceptionally() && rng.isConnected(pending.range)) {
needRequests.remove(pending.range);
fence.include(pending.future);
}
}
NavigableMap<UnsignedLong, PendingRead> applicablePending =
pendingByLoc.subMap(rng.lowerEndpoint(), true, rng.upperEndpoint(), false);
for (Map.Entry<UnsignedLong, PendingRead> ent : applicablePending.entrySet()) {
PendingRead pending = ent.getValue();
if (pending.future.isCompletedExceptionally()) {
continue;
}
needRequests.remove(pending.range);
fence.include(pending.future);
}
// Now we're left with a set of needed ranges. Make a request for each
for (Range<UnsignedLong> needed : needRequests.asRanges()) {
final UnsignedLong lower = needed.lowerEndpoint();
final UnsignedLong upper = needed.upperEndpoint();
/*Msg.debug(this,
"Need to read: [" + lower.toString(16) + ":" + upper.toString(16) + ")");*/
CompletableFuture<byte[]> futureRead =
reader.readMemory(lower.longValue(), upper.minus(lower).intValue());
// Async to avoid re-entrant lock problem
CompletableFuture<Void> futureStored = futureRead.thenAcceptAsync(data -> {
synchronized (this) {
/*Msg.debug(this, "Completed read at " + lower.toString(16) + ": " +
NumericUtilities.convertBytesToString(data));*/
if (pendingByLoc.remove(lower) != null) {
/**
* If the cache was cleared while this read was still pending, we do not
* want to record the result.
*/
memory.putData(lower.longValue(), data);
//Msg.debug(this, "Cached read at " + lower.toString(16));
}
}
}).exceptionally(e -> {
Msg.error(this, "Unexpected error caching memory: ", e);
synchronized (this) {
pendingByLoc.remove(lower);
}
return ExceptionUtils.rethrow(e);
});
pendingByLoc.put(lower, new PendingRead(rng, futureStored));
fence.include(futureStored);
}
}
/**
* {@inheritDoc}
*
* @implNote In some circumstances, it may actually be less efficient to split a request,
* especially if the split only saves a few bytes. The logic required to efficiently
* handle those circumstances would require a bit of calibration based on empirical
* measures, so until such a change becomes necessary, the naive splitting logic
* remains.
*/
@Override
public CompletableFuture<byte[]> readMemory(long addr, int len) {
AssertionError defaultErr =
new AssertionError("No data available even after a successful read?");
AtomicReference<Throwable> exc = new AtomicReference<>(defaultErr);
//Msg.debug(this, "Reading " + len + " bytes at " + Long.toUnsignedString(addr, 16));
return waitForReads(addr, len).handle((v, e) -> {
int available = memory.contiguousAvailableAfter(addr);
if (available == 0) {
if (e == null) {
// TODO: This is happening. Fix it!
throw new AssertionError("No data available at " +
Long.toUnsignedString(addr, 16) + " even after a successful read?");
}
else {
return ExceptionUtils.rethrow(e);
}
}
if (e != null && !isTimeout(e)) {
Msg.error(this,
"Some reads requested by the cache failed. Returning a partial result: " +
exc.get());
}
byte[] result = new byte[Math.min(len, available)];
memory.getData(addr, result);
return result;
});
}
/**
* Update target memory cache by some out-of-band means
*
* @param address the offset of the address
* @param data the contents to cache
*/
public void updateMemory(long address, byte[] data) {
/*Msg.debug(this, "Memory Cache updated at " + address + ": " +
NumericUtilities.convertBytesToString(data));*/
memory.putData(address, data);
}
/**
* Reset the cache
*
* The next read command is guaranteed to be forwarded in its entirety.
*/
public void clear() {
List<PendingRead> toCancel;
synchronized (this) {
//Msg.debug(this, "Memory Cache cleared");
memory.clear();
toCancel = List.copyOf(pendingByLoc.values());
pendingByLoc.clear();
}
for (PendingRead pendingRead : toCancel) {
pendingRead.future.cancel(true);
}
}
protected boolean isTimeout(Throwable e) {
e = AsyncUtils.unwrapThrowable(e);
if (e instanceof TimeoutException) {
return true;
}
return false;
}
}

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.memory;
import java.util.concurrent.CompletableFuture;
/**
* The functional interface for reads from a cached memory
*
* @see CachedMemory
*/
public interface MemoryReader {
/**
* Read target memory
*
* If cached, any cached regions are removed from the request. If this results in non-contiguous
* regions, each generates a new request, forwarded to the wrapped read method.
*/
// TODO: Use ByteBuffer instead?
public CompletableFuture<byte[]> readMemory(long address, int length);
}

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.memory;
import java.util.concurrent.CompletableFuture;
/**
* The functional interface for writes to a cached memory
*
* @see CachedMemory
*/
public interface MemoryWriter {
/**
* Write target memory
*
* If cached, the given write command is immediately forwarded to the wrapped write, and the
* cache is updated so that subsequent reads within the same region do not get forwarded.
*/
// TODO: Use ByteBuffer instead?
public CompletableFuture<Void> writeMemory(long address, byte[] data);
}

View file

@ -0,0 +1,75 @@
/* ###
* 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 org.apache.commons.lang3.reflect.TypeLiteral;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.util.ValueUtils;
import ghidra.lifecycle.Internal;
/**
* A target object which may not be accessible
*
* Depending on the state of the debugger, it may not be able to process commands for certain target
* objects. Objects which may not be accessible should support this interface. Note, that the
* granularity of accessibility is the entire object, including its children (excluding links). If,
* e.g., an object can process memory commands but not control commands, it should be separated into
* two objects.
*/
@DebuggerTargetObjectIface("Access")
public interface TargetAccessConditioned<T extends TargetAccessConditioned<T>>
extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetAccessConditioned<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetAccessConditioned.class;
TypeLiteral<TargetAccessConditioned<?>> type = new TypeLiteral<>() {};
String ACCESSIBLE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "accessible";
public enum TargetAccessibility {
ACCESSIBLE, INACCESSIBLE;
public static TargetAccessibility fromBool(boolean accessible) {
return accessible ? TargetAccessibility.ACCESSIBLE : TargetAccessibility.INACCESSIBLE;
}
}
@Internal
default TargetAccessibility fromObj(Object obj) {
if (obj == null) {
return TargetAccessibility.ACCESSIBLE;
}
return TargetAccessibility
.fromBool(ValueUtils.expectBoolean(obj, this, ACCESSIBLE_ATTRIBUTE_NAME, true));
}
public default TargetAccessibility getAccessibility() {
return fromObj(getCachedAttributes().get(ACCESSIBLE_ATTRIBUTE_NAME));
}
public interface TargetAccessibilityListener extends TargetObjectListener {
default void accessibilityChanged(TargetAccessConditioned<?> object,
TargetAccessibility accessibility) {
System.err.println("default");
}
}
}

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 ghidra.dbg.target;
import ghidra.dbg.DebuggerTargetObjectIface;
/**
* A marker interface which indicates its attributes represent the object as a whole
*
* Often applied to processes and sessions, this causes ancestry traversals to include this object's
* children when visited.
*/
@DebuggerTargetObjectIface("Aggregate")
public interface TargetAggregate extends TargetObject {
}

View file

@ -0,0 +1,35 @@
/* ###
* 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 ghidra.dbg.DebuggerTargetObjectIface;
/**
* An object, usually process, to which the debugger can attach
*/
@DebuggerTargetObjectIface("Attachable")
public interface TargetAttachable<T extends TargetAttachable<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetAttachable<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetAttachable.class;
// These are fairly opaque
}

View file

@ -0,0 +1,122 @@
/* ###
* 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.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet.ImmutableTargetStepKindSet;
import ghidra.dbg.util.CollectionUtils;
import ghidra.dbg.util.CollectionUtils.AbstractEmptySet;
@DebuggerTargetObjectIface("Attacher")
public interface TargetAttacher<T extends TargetAttacher<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetAttacher<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetAttacher.class;
public interface TargetAttachKindSet extends Set<TargetAttachKind> {
public static class EmptyTargetAttachKindSet extends AbstractEmptySet<TargetAttachKind>
implements TargetAttachKindSet {
// Nothing
}
public static class ImmutableTargetAttachKindSet extends
CollectionUtils.AbstractNSet<TargetAttachKind> implements TargetAttachKindSet {
public ImmutableTargetAttachKindSet(TargetAttachKind... kinds) {
super(kinds);
}
public ImmutableTargetAttachKindSet(Set<TargetAttachKind> set) {
super(set);
}
}
TargetAttachKindSet EMPTY = new EmptyTargetAttachKindSet();
public static TargetAttachKindSet of() {
return EMPTY;
}
public static TargetStepKindSet of(TargetStepKind... kinds) {
return new ImmutableTargetStepKindSet(kinds);
}
public static TargetStepKindSet copyOf(Set<TargetStepKind> set) {
return new ImmutableTargetStepKindSet(set);
}
}
enum TargetAttachKind {
/**
* Use an "attachable" object
*/
BY_OBJECT_REF,
/**
* Use the id of some object
*/
BY_ID,
}
String SUPPORTED_ATTACH_KINDS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "supported_attach_kinds";
/**
* Get the kinds of multi-stepping implemented by the debugger
*
* Different debuggers may provide similar, but slightly different vocabularies of stepping.
* This method queries the connected debugger for its supported step kinds.
*
* @return the set of supported multi-step operations
*/
public default TargetAttachKindSet getSupportedAttachKinds() {
return getTypedAttributeNowByName(SUPPORTED_ATTACH_KINDS_ATTRIBUTE_NAME,
TargetAttachKindSet.class, TargetAttachKindSet.of());
}
/**
* Attach to the given {@link TargetAttachable} or reference
*
* This is mostly applicable to user-space contexts, in which case, this usually means to attach
* to a process.
*
* @param attachable the object or reference to attach to
* @return a future which completes when the command is confirmed
*/
public CompletableFuture<Void> attach(TypedTargetObjectRef<? extends TargetAttachable<?>> ref);
/**
* Attach to the given id
*
* This is mostly applicable to user-space contexts, in which case, this usually means to attach
* to a process using its pid.
*
* @param id the identifier for and object to attach to
* @return a future which completes when the command is confirmed
*/
public CompletableFuture<Void> attach(long id);
}

View file

@ -0,0 +1,115 @@
/* ###
* 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.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.util.CollectionUtils.AbstractEmptySet;
import ghidra.dbg.util.CollectionUtils.AbstractNSet;
import ghidra.program.model.address.*;
@DebuggerTargetObjectIface("BreakpointContainer")
public interface TargetBreakpointContainer<T extends TargetBreakpointContainer<T>>
extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetBreakpointContainer<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetBreakpointContainer.class;
String SUPPORTED_BREAK_KINDS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "supported_breakpoint_kinds";
public interface TargetBreakpointKindSet extends Set<TargetBreakpointKind> {
public static class EmptyTargetBreakpointKindSet
extends AbstractEmptySet<TargetBreakpointKind> implements TargetBreakpointKindSet {
// Nothing
}
public static class ImmutableTargetBreakpointKindSet
extends AbstractNSet<TargetBreakpointKind>
implements TargetBreakpointKindSet {
public ImmutableTargetBreakpointKindSet(TargetBreakpointKind... kinds) {
super(kinds);
}
public ImmutableTargetBreakpointKindSet(Set<TargetBreakpointKind> set) {
super(set);
}
}
TargetBreakpointKindSet EMPTY = new EmptyTargetBreakpointKindSet();
public static TargetBreakpointKindSet of() {
return EMPTY;
}
public static TargetBreakpointKindSet of(TargetBreakpointKind... kinds) {
return new ImmutableTargetBreakpointKindSet(kinds);
}
public static TargetBreakpointKindSet copyOf(Set<TargetBreakpointKind> set) {
return new ImmutableTargetBreakpointKindSet(set);
}
}
public default TargetBreakpointKindSet getSupportedBreakpointKinds() {
return getTypedAttributeNowByName(SUPPORTED_BREAK_KINDS_ATTRIBUTE_NAME,
TargetBreakpointKindSet.class, TargetBreakpointKindSet.of());
}
public CompletableFuture<Void> placeBreakpoint(String expression,
Set<TargetBreakpointKind> kinds);
public CompletableFuture<Void> placeBreakpoint(AddressRange range,
Set<TargetBreakpointKind> kinds);
public default CompletableFuture<Void> placeBreakpoint(Address address,
Set<TargetBreakpointKind> kinds) {
return placeBreakpoint(new AddressRangeImpl(address, address), kinds);
}
public interface TargetBreakpointListener extends TargetObjectListener {
/**
* A breakpoint trapped execution
*
* <p>
* The program counter can be obtained in a few ways. The most reliable is to get the
* address of the effective breakpoint. If available, the frame will also contain the
* program counter. Finally, the trapped object or one of its relatives may offer the
* program counter.
*
* @param container the container whose breakpoint trapped execution
* @param trapped the object whose execution was trapped
* @param frame the innermost stack frame, if available, of the trapped object
* @param spec the breakpoint specification
* @param breakpoint the breakpoint location that actually trapped execution
*/
default void breakpointHit(TargetBreakpointContainer<?> container, TargetObjectRef trapped,
TypedTargetObjectRef<? extends TargetStackFrame<?>> frame,
TypedTargetObjectRef<? extends TargetBreakpointSpec<?>> spec,
TypedTargetObjectRef<? extends TargetBreakpointLocation<?>> breakpoint) {
}
}
}

View file

@ -0,0 +1,85 @@
/* ###
* 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 ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetObjectRefList;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.program.model.address.Address;
@DebuggerTargetObjectIface("BreakpointLocation")
public interface TargetBreakpointLocation<T extends TargetBreakpointLocation<T>>
extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetBreakpointLocation<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetBreakpointLocation.class;
String ADDRESS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "address";
String AFFECTS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "affects";
// NOTE: address and length are treated separately (not using AddressRange)
// On GDB, e.g., the length may not be offered immediately.
String LENGTH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "length";
String SPEC_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "spec";
public default Address getAddress() {
return getTypedAttributeNowByName(ADDRESS_ATTRIBUTE_NAME, Address.class, null);
}
public default TargetObjectRefList<?> getAffects() {
return getTypedAttributeNowByName(AFFECTS_ATTRIBUTE_NAME, TargetObjectRefList.class,
TargetObjectRefList.of());
}
/**
* If available, get the length in bytes, of the range covered by the
* breakpoint.
*
* In most cases, where the length is not available, a length of 1 should be
* presumed.
*
* TODO: Should this be Long?
*
* @return the length, or {@code null} if not known
*/
public default Integer getLength() {
return getTypedAttributeNowByName(LENGTH_ATTRIBUTE_NAME, Integer.class, null);
}
public default int getLengthOrDefault(int fallback) {
return getTypedAttributeNowByName(LENGTH_ATTRIBUTE_NAME, Integer.class, fallback);
}
/**
* Get a reference to the specification which generated this breakpoint.
*
* If the debugger does not separate specifications from actual breakpoints,
* then the "specification" is this breakpoint. Otherwise, this
* specification is the parent. The default implementation distinguishes the
* cases by examining the implemented interfaces. Implementors may slightly
* increase efficiency by overriding this method.
*
* @return the reference to the specification
*/
public default TypedTargetObjectRef<? extends TargetBreakpointSpec<?>> getSpecification() {
return getTypedRefAttributeNowByName(SPEC_ATTRIBUTE_NAME, TargetBreakpointSpec.tclass,
null);
}
}

View file

@ -0,0 +1,186 @@
/* ###
* 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.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.target.TargetBreakpointContainer.TargetBreakpointKindSet;
/**
* The specification of a breakpoint applied to a target object
*
* <p>
* Note that a single specification could result in several locations, or no locations at all. For
* example, a breakpoint placed on a function within a module which has not been loaded ("pending"
* in GDB's nomenclature), will not have any location. On the other hand, a breakpoint expressed by
* line number in a C++ template or a C macro could resolve to many addresses. The children of this
* object include the resolved {@link TargetBreakpointLocation}s. If the debugger does not share
* this same concept, then its breakpoints should implement both the specification and the location;
* the specification need not have any children.
*/
@DebuggerTargetObjectIface("BreakpointSpec")
public interface TargetBreakpointSpec<T extends TargetBreakpointSpec<T>>
extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetBreakpointSpec<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetBreakpointSpec.class;
public enum TargetBreakpointKind {
READ, WRITE, EXECUTE, SOFTWARE;
}
String CONTAINER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "container";
String EXPRESSION_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "expression";
String KINDS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "kinds";
String ENABLED_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "enabled";
/**
* Get the container of this breakpoint.
*
* <p>
* While it is most common for a breakpoint specification to be an immediate child of its
* container, that is not necessarily the case. This method is a reliable and type-safe means of
* obtaining that container.
*
* @return a reference to the container
*/
public default TypedTargetObjectRef<? extends TargetBreakpointContainer<?>> getContainer() {
return getTypedRefAttributeNowByName(CONTAINER_ATTRIBUTE_NAME,
TargetBreakpointContainer.tclass, null);
}
/**
* Get the user-specified expression describing the breakpoint
*
* <p>
* Depending on the underlying debugger, this could be a variety of forms, e.g., source file and
* line number, module and symbol, address.
*
* @return the expression
*/
public default String getExpression() {
return getTypedAttributeNowByName(EXPRESSION_ATTRIBUTE_NAME, String.class, "");
}
/**
* Get the kinds of breakpoint
*
* @return the kinds
*/
public default TargetBreakpointKindSet getKinds() {
return getTypedAttributeNowByName(KINDS_ATTRIBUTE_NAME, TargetBreakpointKindSet.class,
TargetBreakpointKindSet.EMPTY);
}
/**
* Check if the breakpoint is enabled
*
* @return true if enabled, false otherwise
*/
public default boolean isEnabled() {
return getTypedAttributeNowByName(ENABLED_ATTRIBUTE_NAME, Boolean.class, false);
}
/**
* Add an action to execute locally when this breakpoint traps execution
*
* <p>
* Note that unlike other parts of this API, the breakpoint specification implementation must
* keep a strong reference to its actions. Adding the same action a second time may cause
* undefined behavior. Ideally, the implementation would at least detect this condition and log
* a warning.
*
* @param action the action to execute
*/
public void addAction(TargetBreakpointAction action);
/**
* Remove an action from this breakpoint
*
* @param action the action to remove
*/
public void removeAction(TargetBreakpointAction action);
public interface TargetBreakpointAction {
/**
* An effective breakpoint from this specification trapped execution
*
* @param spec the breakpoint specification
* @param trapped the object whose execution was trapped
* @param frame the innermost stack frame, if available, of the trapped object
* @param breakpoint the effective breakpoint that actually trapped execution
*/
void breakpointHit(TargetBreakpointSpec<?> spec, TargetObjectRef trapped,
TypedTargetObjectRef<? extends TargetStackFrame<?>> frame,
TypedTargetObjectRef<? extends TargetBreakpointLocation<?>> breakpoint);
}
/**
* Disable all breakpoints resulting from this specification
*/
public CompletableFuture<Void> disable();
/**
* Enable all breakpoints resulting from this specification
*/
public CompletableFuture<Void> enable();
/**
* Enable or disable all breakpoints resulting from this specification
*
* @param enabled true to enable, false to disable
*/
public default CompletableFuture<Void> toggle(boolean enabled) {
return enabled ? enable() : disable();
}
/**
* Get the locations created by this specification.
*
* <p>
* While it is most common for locations to be immediate children of the specification, that is
* not necessarily the case.
*
* @implNote By default, this method collects all successor locations ordered by path.
* Overriding that behavior is not yet supported.
* @return the effective breakpoints
*/
public default CompletableFuture< //
? extends Collection<? extends TargetBreakpointLocation<?>>> getLocations() {
if (this instanceof TargetBreakpointLocation<?>) {
return CompletableFuture.completedFuture(List.of((TargetBreakpointLocation<?>) this));
}
return DebugModelConventions.collectSuccessors(this, TargetBreakpointLocation.tclass);
}
// TODO: Make hit count part of the common interface?
public interface TargetBreakpointSpecListener extends TargetObjectListener {
default void breakpointToggled(TargetBreakpointSpec<?> spec, boolean enabled) {
}
}
}

View file

@ -0,0 +1,79 @@
/* ###
* 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.nio.charset.Charset;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
@DebuggerTargetObjectIface("Console")
public interface TargetConsole<T extends TargetConsole<T>> extends TypedTargetObject<T> {
Charset CHARSET = Charset.forName("utf-8");
enum Private {
;
private abstract class Cls implements TargetConsole<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetConsole.class;
/**
* For console output notifications, indicates whether it is normal or error output
*/
public static enum Channel {
STDOUT, STDERR;
}
/**
* Write data to the console's input
*
* @param data the data, often utf-8-encoded text
* @return a future which completes when the data is sent
*/
public CompletableFuture<Void> write(byte[] data);
public interface TargetConsoleListener extends TargetObjectListener {
/**
* The console has produced output
*
* @param console the console producing the output
* @param channel identifies the "output stream", stdout or stderr
* @param data the output data
*/
default void consoleOutput(TargetObject console, Channel channel, byte[] data) {
}
}
public interface TargetTextConsoleListener extends TargetConsoleListener {
/**
* The console has produced output
*
* @param console the console producing the output
* @param channel identifies the "output stream", stdout or stderr
* @param text the output text
*/
default void consoleOutput(TargetObject console, Channel channel, String text) {
}
@Override
default void consoleOutput(TargetObject console, Channel channel, byte[] data) {
consoleOutput(console, channel, new String(data, CHARSET));
}
}
}

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 ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetDataType;
@DebuggerTargetObjectIface("TypeMember")
public interface TargetDataTypeMember<T extends TargetDataTypeMember<T>>
extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetDataTypeMember<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetDataTypeMember.class;
String POSITION_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "position";
String MEMBER_NAME_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "member_name";
String OFFSET_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "offset";
String DATA_TYPE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "data_type";
/**
* The position of the member in the composite
*
* @return the position
*/
default int getPosition() {
return getTypedAttributeNowByName(POSITION_ATTRIBUTE_NAME, Integer.class, -1);
}
default String getMemberName() {
return getTypedAttributeNowByName(MEMBER_NAME_ATTRIBUTE_NAME, String.class, "");
}
default long getOffset() {
return getTypedAttributeNowByName(OFFSET_ATTRIBUTE_NAME, Long.class, -1L);
}
default TargetDataType getDataType() {
return getTypedAttributeNowByName(DATA_TYPE_ATTRIBUTE_NAME, TargetDataType.class, null);
}
}

View file

@ -0,0 +1,56 @@
/* ###
* 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.Collection;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebuggerTargetObjectIface;
/**
* A container of data types
*
* The debugger should present these in as granular of unit as possible. Consider a desktop
* application, for example. The debugger should present each module as a namespace rather than the
* entire target (or worse, the entire session) as a single namespace.
*/
@DebuggerTargetObjectIface("DataTypeNamespace")
public interface TargetDataTypeNamespace<T extends TargetDataTypeNamespace<T>>
extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetDataTypeNamespace<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetDataTypeNamespace.class;
/**
* Get the types in this namespace
*
* While it is most common for types to be immediate children of the namespace, that is not
* necessarily the case.
*
* @implNote By default, this method collects all successor types ordered by path. Overriding
* that behavior is not yet supported.
* @return the types
*/
default CompletableFuture<? extends Collection<? extends TargetNamedDataType<?>>> getTypes() {
return DebugModelConventions.collectSuccessors(this, TargetNamedDataType.tclass);
}
}

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.target;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
@DebuggerTargetObjectIface("Deletable")
public interface TargetDeletable<T extends TargetDeletable<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetDeletable<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetDeletable.class;
public CompletableFuture<Void> delete();
}

View file

@ -0,0 +1,39 @@
/* ###
* 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;
@DebuggerTargetObjectIface("Detachable")
public interface TargetDetachable<T extends TargetDetachable<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetDetachable<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetDetachable.class;
/**
* Detach from this target
*
* @return a future which completes upon successfully detaching
*/
public CompletableFuture<Void> detach();
}

View file

@ -0,0 +1,120 @@
/* ###
* 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 ghidra.dbg.DebuggerTargetObjectIface;
/**
* Provides information about a given target object
*
* <p>
* This is mostly a marker interface so that the client knows where to look for information about a
* target. This may be attached to the entire session, or it may be attached to individual targets
* in a session. The information is generally encoded as string-valued attributes, for which this
* interface provides convenient accessors. The form of the strings is not strictly specified. They
* should generally just take verbatim whatever string the host debugger would use to describe the
* platform. It is up to the client to interpret the information into an equivalent specification in
* the UI/database.
*
* @implNote to simplify the automatic choice of mapper when recording a trace, it is required to
* update a target's environment attributes before reporting that it has started. Relaxing
* this requirement is TODO. Note that targets which do not support
* {@link TargetExecutionStateful} are assumed started by virtue of their creation.
*/
@DebuggerTargetObjectIface("Environment")
public interface TargetEnvironment<T extends TargetEnvironment<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetEnvironment<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetEnvironment.class;
String ARCH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "arch";
String DEBUGGER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "debugger";
String OS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "os";
String ENDIAN_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "endian";
String VISIBLE_ARCH_ATTRIBUTE_NAME = "arch";
String VISIBLE_OS_ATTRIBUTE_NAME = "os";
String VISIBLE_ENDIAN_ATTRIBUTE_NAME = "endian";
/**
* Get a description of the target architecture
*
* <p>
* This should be as specific a description of the processor as possible. Ideally, the processor
* family is apparent in the description. The client will interpret this to determine the
* appropriate Instruction Set Architecture and any nuances in the processor's behavior. For
* example {@code family:version:variant}. If the debugger has its own format for these
* descriptors, please use it.
*
* @return the target architecture
*/
default String getArchitecture() {
return getTypedAttributeNowByName(ARCH_ATTRIBUTE_NAME, String.class, "");
}
/**
* Get a description of the debugger
*
* <p>
* This should be as specific a description of the debugger as possible. Ideally, the debugger's
* name is apparent in the description. The client may use this to properly interpret the other
* environment descriptors. For example {@code GNU gdb (GDB) 8.0}. While version and additional
* platform information may be presented, clients should avoid relying on it, esp., to account
* for nuances in debugger behavior. The model implementation (i.e., the "agent") is responsible
* for presenting a model consistent with the debugger's behavior.
*
* @return the host debugger
*/
default String getDebugger() {
return getTypedAttributeNowByName(DEBUGGER_ATTRIBUTE_NAME, String.class, "");
}
/**
* Get a description of the target operating system
*
* <p>
* This should be as specific a description of the operating system as possible. Ideally, the OS
* name is apparent in the description. The client will interpret this to determine the
* appropriate Application Binary Interface. For example {@code GNU/Linux}. The client may also
* use this to decide how to interpret other information present in the model, e.g., file system
* paths.
*
* @return the target operating system
*/
default String getOperatingSystem() {
return getTypedAttributeNowByName(OS_ATTRIBUTE_NAME, String.class, "");
}
/**
* Get the endianness of the target
*
* <p>
* In most cases, a simple "little" or "big" should do, but there may exist cases where code is
* in one form and data is in another. For those, choose something recognizable to someone
* writing an opinion for tracing the target. TODO: Formalize those conventions?
*
* @return the target endianness
*/
default String getEndian() {
return getTypedAttributeNowByName(ENDIAN_ATTRIBUTE_NAME, String.class, "");
}
// TODO: Devices? File System?
}

View file

@ -0,0 +1,159 @@
/* ###
* 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.List;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TypedTargetObjectRef;
/**
* The object can emit events affecting itself and its successors
*
* <p>
* Most often, this interface is supported by the (root) session.
*/
@DebuggerTargetObjectIface("EventScope")
public interface TargetEventScope<T extends TargetEventScope<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetEventScope<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetEventScope.class;
String EVENT_PROCESS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "event_process";
String EVENT_THREAD_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "event_thread";
public enum TargetEventType {
/**
* The session has stopped for an unspecified reason
*/
STOPPED,
/**
* The session is running for an unspecified reason
*
* <p>
* Note that execution state changes are communicated via {@link TargetExecutionStateful},
* since the sessiopn may specify such state on a per-target and/or per-thread basis.
*/
RUNNING,
/**
* A new target process was created by this session
*
* <p>
* If the new process is part of the session, too, it must be passed as a parameter.
*/
PROCESS_CREATED,
/**
* A target process in this session has exited
*/
PROCESS_EXITED,
/**
* A new target thread was created by this session
*
* <p>
* The new thread must be part of the session, too, and must be given as the event thread.
*/
THREAD_CREATED,
/**
* A target thread in this session has exited
*/
THREAD_EXITED,
/**
* A new module has been loaded by this session
*
* <p>
* The new module must be passed as a parameter.
*/
MODULE_LOADED,
/**
* A module has been unloaded by this session
*/
MODULE_UNLOADED,
/**
* The session has stopped, because one if its targets was trapped by a breakpoint
*
* <p>
* If the breakpoint (specification) is part of the session, too, it must be passed as a
* parameter. The trapped target must also be passed as a parameter.
*/
BREAKPOINT_HIT,
/**
* The session has stopped, because a stepping command has completed
*
* <p>
* The target completing the command must also be passed as a parameter, unless it is the
* event thread. If it is a thread, it must be given as the event thread.
*/
STEP_COMPLETED,
/**
* The session has stopped, because one if its targets was trapped on an exception
*
* <p>
* The trapped target must also be passed as a parameter, unless it is the event thread. If
* it is a thread, it must be given as the event thread.
*/
EXCEPTION,
/**
* The session has stopped, because one of its targets was trapped on a signal
*
* <p>
* The trapped target must also be passed as a parameter, unless it is the event thread. If
* it is a thread, it must be given as the event thread.
*/
SIGNAL,
}
public interface TargetEventScopeListener extends TargetObjectListener {
/**
* An event affecting a target in this scope has occurred
*
* <p>
* When present, this callback must be invoked before any other callback which results from
* this event, except creation events. E.g., for PROCESS_EXITED, this must be called before
* the affected process is removed from the tree.
*
* <p>
* Whenever possible, event thread must be given. This is often the thread given focus by
* the debugger immediately upon stopping for the event. Parameters are not (yet) strictly
* specified, but it should include the stopped target, if that target is not already given
* by the event thread. It may optionally contain other useful information, such as an exit
* code, but no listener should depend on that information being given.
*
* <p>
* The best way to communicate to users what has happened is via the description. Almost
* every other result of an event is communicated by other means in the model, e.g., state
* changes, object creation, destruction. The description should contain as much information
* as possible to cue users as to why the other changes have occurred, and point them to
* relevant objects. For example, if trapped on a breakpoint, the description might contain
* the breakpoint's identifier. If the debugger prints a message for this event, that
* message is probably a sufficient description.
*
* @param object the event scope
* @param eventThread if applicable, the thread causing the event
* @param type the type of event
* @param description a human-readable description of the event
* @param parameters extra parameters for the event. TODO: Specify these for each type
*/
default void event(TargetEventScope<?> object,
TypedTargetObjectRef<? extends TargetThread<?>> eventThread, TargetEventType type,
String description, List<Object> parameters) {
}
}
}

View file

@ -0,0 +1,166 @@
/* ###
* 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 ghidra.dbg.DebuggerTargetObjectIface;
@DebuggerTargetObjectIface("ExecutionStateful")
public interface TargetExecutionStateful<T extends TargetExecutionStateful<T>>
extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetExecutionStateful<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetExecutionStateful.class;
String STATE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "state";
/**
* The execution state of a debug target object
*/
public enum TargetExecutionState {
/**
* The object has been created, but it not yet alive
*
* This may apply, e.g., to a GDB "Inferior" which has no yet been used to launch or attach
* to a process.
*/
INACTIVE {
@Override
public boolean isAlive() {
return false;
}
@Override
public boolean isRunning() {
return false;
}
@Override
public boolean isStopped() {
return false;
}
},
/**
* The object is alive, but its execution state is unspecified
*/
ALIVE {
@Override
public boolean isAlive() {
return true;
}
@Override
public boolean isRunning() {
return false;
}
@Override
public boolean isStopped() {
return false;
}
},
/**
* The object is alive, but not executing
*/
STOPPED {
@Override
public boolean isAlive() {
return true;
}
@Override
public boolean isRunning() {
return false;
}
@Override
public boolean isStopped() {
return true;
}
},
/**
* The object is alive and executing
*
* "Running" is loosely defined. For example, with respect to a thread, it may indicate the
* thread is currently executing, waiting on an event, or scheduled for execution. It does
* not necessarily mean it is executing on a CPU at this exact moment.
*/
RUNNING {
@Override
public boolean isAlive() {
return true;
}
@Override
public boolean isRunning() {
return true;
}
@Override
public boolean isStopped() {
return false;
}
},
/**
* The object is no longer alive
*
* The object still exists but no longer represents something alive. This could be used for
* stale handles to objects which may still be queried (e.g., for a process exit code), or
* e.g., a GDB "Inferior" which could be re-used to launch or attach to another process.
*/
TERMINATED {
@Override
public boolean isAlive() {
return false;
}
@Override
public boolean isRunning() {
return false;
}
@Override
public boolean isStopped() {
return false;
}
};
public abstract boolean isAlive();
public abstract boolean isRunning();
public abstract boolean isStopped();
}
public default TargetExecutionState getExecutionState() {
return getTypedAttributeNowByName(STATE_ATTRIBUTE_NAME, TargetExecutionState.class,
TargetExecutionState.STOPPED);
}
public interface TargetExecutionStateListener extends TargetObjectListener {
default void executionStateChanged(TargetExecutionStateful<?> object,
TargetExecutionState state) {
}
}
}

View file

@ -0,0 +1,86 @@
/* ###
* 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.DebugModelConventions;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetObjectRef;
@DebuggerTargetObjectIface("FocusScope")
public interface TargetFocusScope<T extends TargetFocusScope<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetFocusScope<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetFocusScope.class;
String FOCUS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "focus";
/**
* Focus on the given object
*
* -obj- must be successor of this scope. The debugger may reject or ignore the request for any
* reason. If the debugger cannot focus the given object, it should attempt to do so for each
* ancestor until it succeeds or reaches this focus scope.
*
* @param obj the object to receive focus
* @return a future which completes upon successfully changing focus.
*/
CompletableFuture<Void> requestFocus(TargetObjectRef obj);
/**
* Get the focused object in this scope
*
* @return a reference to the focused object or {@code null} if no object is focused.
*/
default TargetObjectRef getFocus() {
return getTypedAttributeNowByName(FOCUS_ATTRIBUTE_NAME, TargetObjectRef.class, null);
}
public interface TargetFocusScopeListener extends TargetObjectListener {
/**
* Focused has changed within this scope
*
* <p>
* Note that client UIs should be careful about event loops and user intuition when
* receiving this event. The client should avoid calling
* {@link TargetFocusScope#requestFocus(TargetObject)} in response to this event. Perhaps
* the simplest way is to only request focus when the selected object has actually changed.
* Also, the debugger may "adjust" the focus. For example, when focusing a thread, the
* debugger may additionally focus a particular frame in that thread (a successor). Or, when
* focusing a memory region, the debugger may only focus the owning process (an ancestor).
* The suggested strategy (a work in progress) is the "same level, same type" rule. It may
* be appropriate to highlight the actual focused object to cue the user in, but the user's
* selection should remain at the same level. If an ancestor or successor receives focus,
* leave the user's selection as is. If a sibling element or one of its successors receives
* focus, select that sibling. A similar rule applies to "cousin" elements, so long as they
* have the same type. In most other cases, it's appropriate to select the focused element.
*
* <p>
* TODO: Implement this rule in {@link DebugModelConventions}
*
* @param object this scope
* @param focused the object receiving focus
*/
default void focusChanged(TargetFocusScope<?> object, TargetObjectRef focused) {
}
}
}

View file

@ -0,0 +1,108 @@
/* ###
* 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;
import ghidra.dbg.target.TargetConsole.Channel;
import ghidra.dbg.target.TargetConsole.TargetTextConsoleListener;
/**
* A command interpreter, usually that of an external debugger
*/
@DebuggerTargetObjectIface("Interpreter")
public interface TargetInterpreter<T extends TargetInterpreter<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetInterpreter<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetInterpreter.class;
String PROMPT_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "prompt";
/**
* Execute an interpreter command
*
* <p>
* Usually, this means executing a command as if typed into the debugger's CLI. Thus, the
* meaning of the command will depend on the debugger. As such, this method is discouraged for
* scripts that intend to be debugger agnostic. On the other hand, this is often a very useful
* feature for a user interface.
*
* @param cmd the command to issue to the CLI
* @return a future that completes when the command has completed execution
*/
public CompletableFuture<Void> execute(String cmd);
/**
* Execute an interpreter command, capturing the output
*
* <p>
* This works the same as {@link #execute(String)}, but instead of writing the output to the
* console, it is captured. This method is discouraged for scripts that intend to be debugger
* agnostic.
*
* @param cmd the command to issue to the CLI
* @return a future that completes with the captured output
*/
public CompletableFuture<String> executeCapture(String cmd);
/**
* Get the prompt for user input
*
* <p>
* Generally, this should indicate to the user the command language and/or state of the
* interpreter. For some debuggers, this may change as the interpreter context changes. For
* example, the {@code dbgeng.dll} interpreter provides a prompt indicating the effective
* instruction set architecture for the current thread.
*
* @return the current prompt
*/
public default String getPrompt() {
return getTypedAttributeNowByName(PROMPT_ATTRIBUTE_NAME, String.class, ">");
}
public interface TargetInterpreterListener extends TargetTextConsoleListener {
/**
* {@inheritDoc}
*
* <p>
* This should only receive console output for non-captured commands. See
* {@link TargetInterpreter#executeCapture(String)}.
*/
@Override
default void consoleOutput(TargetObject console, Channel channel, String text) {
TargetTextConsoleListener.super.consoleOutput(console, channel, text);
}
/**
* The interpreter's prompt has changed
*
* <p>
* Any UI elements presenting the prompt should be updated immediately.
*
* @param interpreter the interpreter whose prompt changed
* @param prompt the new prompt
*/
default void promptChanged(TargetInterpreter<?> interpreter, String prompt) {
}
}
}

View file

@ -0,0 +1,47 @@
/* ###
* 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;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
@DebuggerTargetObjectIface("Interruptible")
public interface TargetInterruptible<T extends TargetInterruptible<T>>
extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetInterruptible<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetInterruptible.class;
/**
* Interrupt the target object
*
* Typically, this breaks, i.e., stops, all target objects in scope of the receiver. Note the
* command completes when the interrupt has been sent, whether or not it actually stopped
* anything. Users wishing to confirm execution has stopped should wait for the target object to
* enter the {@link TargetExecutionState#STOPPED} state. Depending on the temperament of the
* debugger and the target, it may be necessary to send multiple interrupts.
*
* @return a future which completes when the interrupt has been sent
*/
public CompletableFuture<Void> interrupt();
}

View file

@ -0,0 +1,39 @@
/* ###
* 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;
@DebuggerTargetObjectIface("Killable")
public interface TargetKillable<T extends TargetKillable<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetKillable<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetKillable.class;
/**
* Kill this target
*
* @return a future which completes upon successfully terminating the target
*/
public CompletableFuture<Void> kill();
}

View file

@ -0,0 +1,144 @@
/* ###
* 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.io.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
/**
* An interface which indicates this object is capable of launching targets.
*
* <p>
* The targets this launcher creates ought to appear in its successors.
*/
@DebuggerTargetObjectIface("Launcher")
public interface TargetLauncher<T extends TargetLauncher<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetLauncher<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetLauncher.class;
/**
* An interface which provides default implementations for command-line launchers
*
* <p>
* This interface should only be used by implementors. It is not necessarily marshalled by
* remote clients. Clients should instead interrogate {@link TargetLauncher} for its supported
* parameters.
*
* <p>
* For the sake of parameter marshalling, the implementation must still set
* {@link TargetMethod#PARAMETERS_ATTRIBUTE_NAME} explicitly, usually in its constructor.
*/
interface TargetCmdLineLauncher<T extends TargetLauncher<T>> extends TargetLauncher<T> {
String CMDLINE_ARGS_NAME = "args";
/**
* The {@code args} parameter
*/
ParameterDescription<String> PARAMETER_CMDLINE_ARGS = ParameterDescription.create(
String.class,
CMDLINE_ARGS_NAME, true, "", "Command Line", "space-separated command-line arguments");
/**
* A map of parameters suitable for invoking {@link #launch(List)}
*/
TargetParameterMap PARAMETERS = TargetMethod.makeParameters(PARAMETER_CMDLINE_ARGS);
@Override
default public TargetParameterMap getParameters() {
return PARAMETERS;
}
/**
* Launch a target using the given arguments
*
* <p>
* This is mostly applicable to user-space contexts, in which case, this usually means to
* launch a new process with the given arguments, where the first argument is the path to
* the executable image on the target host's file system.
*
* @param args the arguments
* @return a future which completes when the command has been processed
*/
public CompletableFuture<Void> launch(List<String> args);
/**
* @see #launch(List)
*/
public default CompletableFuture<Void> launch(String... args) {
return launch(Arrays.asList(args));
}
@Override
public default CompletableFuture<Void> launch(Map<String, ?> args) {
return launch(CmdLineParser.tokenize(PARAMETER_CMDLINE_ARGS.get(args)));
}
}
class CmdLineParser extends StreamTokenizer {
public static List<String> tokenize(String cmdLine) {
return new CmdLineParser(cmdLine).tokens();
}
public CmdLineParser(Reader r) {
super(r);
wordChars(0, 255);
whitespaceChars(' ', ' ');
quoteChar('"');
}
public CmdLineParser(String cmdLine) {
this(new StringReader(cmdLine));
}
public List<String> tokens() {
List<String> list = new ArrayList<>();
try {
while (StreamTokenizer.TT_EOF != nextToken()) {
list.add(sval);
}
}
catch (IOException e) {
throw new AssertionError(e);
}
return list;
}
}
default public TargetParameterMap getParameters() {
return TargetMethod.getParameters(this);
}
/**
* Launch a target using the given arguments
*
* @param args the map of arguments.
* @return a future which completes when the command is completed
*/
public CompletableFuture<Void> launch(Map<String, ?> args);
}

View file

@ -0,0 +1,135 @@
/* ###
* 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.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.error.DebuggerMemoryAccessException;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
/**
* The memory model of a target object
*
* <p>
* This interface provides methods for reading and writing target memory. It should use addresses
* produced by the provided address factory. The convention for modeling valid addresses is to have
* children supporting {@link TargetMemoryRegion}. If no such children exist, then the client should
* assume no address is valid. Thus, for the client to confidently access any memory, at least one
* child region must exist. It may present the memory's entire address space in a single region.
*
* <p>
* TODO: Decide convention: should a single region appear in multiple locations? If it does, does it
* imply that region's contents are common to all memories possessing it? Or, should contents be
* distinguished by the memory objects? I'm leaning toward the latter. Duplicate locations for
* regions may just be an efficiency bit, if used at all. This decision is primarily because, at the
* moment, read and write belong to the memory interface, not the region.
*/
@DebuggerTargetObjectIface("Memory")
public interface TargetMemory<T extends TargetMemory<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetMemory<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetMemory.class;
/**
* Read memory at the given address
*
* <p>
* If the target architecture is not a byte-per-address, then the implementation should
* interpret each unit in bytes using the target space's native byte order.
*
* <p>
* TODO: This circumstance has not been well-tested, as non-x86 architectures are left for
* future implementation.
*
* @param address the address to start reading at
* @param length the number of bytes to read
* @return a future which completes with the read data
*/
public CompletableFuture<byte[]> readMemory(Address address, int length);
/**
* Write memory at the given address
*
* <p>
* If the target architecture is not a byte-per-address, then the implementation should
* interpret each unit in bytes using the memory space's native byte order.
*
* <p>
* TODO: This circumstance has not been well-tested, as non-x86 architectures are left for
* future implementation.
*
* @param address the address to start writing at
* @param data the data to write
* @return a future which completes upon successfully writing
*/
public CompletableFuture<Void> writeMemory(Address address, byte[] data);
/**
* Get the regions of valid addresses
*
* <p>
* This is a convenience, exactly equivalent to getting all children supporting the
* {@link TargetMemoryRegion} interface.
*
* @return the collection of child regions
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public default CompletableFuture<? extends Map<String, ? extends TargetMemoryRegion<?>>> getRegions() {
return fetchChildrenSupporting((Class) TargetMemoryRegion.class);
}
public interface TargetMemoryListener extends TargetObjectListener {
/**
* Memory was successfully read or written
*
* <p>
* If the implementation employs a cache, then it need only report reads or writes which
* updated that cache. However, that cache must be invalidated whenever any other event
* occurs which could change memory, e.g., the target stepping or running.
*
* <p>
* If the implementation can detect memory reads or writes <em>driven by the debugger</em>
* then it is also acceptable to call this method for those events. However, this method
* <em>must not</em> be called for memory changes <em>driven by the target</em>. In other
* words, this method should only be called for reads or writes requested by the user.
*
* @param memory this memory object
* @param address the starting address of the affected range
* @param data the new data for the affected range
*/
default void memoryUpdated(TargetMemory<?> memory, Address address, byte[] data) {
}
/**
* An attempt to read memory failed
*
* @param memory the memory object
* @param range the range for the read which generated the error
* @param e the error
*/
default void memoryReadError(TargetMemory<?> memory, AddressRange range,
DebuggerMemoryAccessException e) {
}
}
}

View file

@ -0,0 +1,72 @@
/* ###
* 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 ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.program.model.address.AddressRange;
@DebuggerTargetObjectIface("MemoryRegion")
public interface TargetMemoryRegion<T extends TargetMemoryRegion<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetMemoryRegion<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetMemoryRegion.class;
String RANGE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "range";
String READABLE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "readable";
String WRITABLE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "writable";
String EXECUTABLE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "executable";
String MEMORY_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "memory";
public default AddressRange getRange() {
return getTypedAttributeNowByName(RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
}
public default boolean isReadable() {
return getTypedAttributeNowByName(READABLE_ATTRIBUTE_NAME, Boolean.class, false);
}
public default boolean isWritable() {
return getTypedAttributeNowByName(WRITABLE_ATTRIBUTE_NAME, Boolean.class, false);
}
public default boolean isExecutable() {
return getTypedAttributeNowByName(EXECUTABLE_ATTRIBUTE_NAME, Boolean.class, false);
}
// TODO: Should probably just have getFlags() and "flags" attribute
// TODO: Other flags? "committed", "reserved", etc?
/**
* Get the memory for this region.
*
* <p>
* While it is most common for a region to be an immediate child of its containing memory, that
* is not necessarily the case. This method is a reliable and type-safe means of obtaining that
* memory.
*
* @return a reference to the memory
*/
public default TypedTargetObjectRef<? extends TargetMemory<?>> getMemory() {
return getTypedRefAttributeNowByName(MEMORY_ATTRIBUTE_NAME, TargetMemory.tclass, null);
}
}

View file

@ -0,0 +1,320 @@
/* ###
* 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.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.error.DebuggerIllegalArgumentException;
import ghidra.dbg.util.CollectionUtils.AbstractEmptyMap;
import ghidra.dbg.util.CollectionUtils.AbstractNMap;
/**
* A marker interface which indicates a method on an object
*
*/
@DebuggerTargetObjectIface("Method")
public interface TargetMethod<T extends TargetMethod<T>> extends TypedTargetObject<T> {
String PARAMETERS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "parameters";
String RETURN_TYPE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "return_type";
enum Private {
;
private abstract class Cls implements TargetMethod<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetMethod.class;
/**
* A description of a method parameter
*
* <p>
* TODO: Most of this should probably eventually go into {@link TargetMethod}
* <p>
* TODO: For convenience, these should be programmable via annotations.
*
* @param <T> the type of the parameter
*/
class ParameterDescription<T> {
public static <T> ParameterDescription<T> create(Class<T> type, String name,
boolean required, T defaultValue, String display, String description) {
return new ParameterDescription<>(type, name, required, defaultValue, display,
description, List.of());
}
public static <T> ParameterDescription<T> choices(Class<T> type, String name,
Collection<T> choices, String display, String description) {
T defaultValue = choices.iterator().next();
return new ParameterDescription<>(type, name, false, defaultValue, display, description,
choices);
}
public final Class<T> type;
public final String name;
public final T defaultValue;
public final boolean required;
public final String display;
public final String description;
public final Set<T> choices;
private ParameterDescription(Class<T> type, String name, boolean required, T defaultValue,
String display, String description, Collection<T> choices) {
this.type = type;
this.name = name;
this.defaultValue = defaultValue;
this.required = required;
this.display = display;
this.description = description;
this.choices = Set.copyOf(choices);
}
@Override
public int hashCode() {
return Objects.hash(type, name, defaultValue, required, display, description, choices);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ParameterDescription<?>)) {
return false;
}
ParameterDescription<?> that = (ParameterDescription<?>) obj;
if (this.type != that.type) {
return false;
}
if (!Objects.equals(this.name, that.name)) {
return false;
}
if (!Objects.equals(this.defaultValue, that.defaultValue)) {
return false;
}
if (this.required != that.required) {
return false;
}
if (!Objects.equals(this.display, that.display)) {
return false;
}
if (!Objects.equals(this.description, that.description)) {
return false;
}
if (!Objects.equals(this.choices, that.choices)) {
return false;
}
return true;
}
/**
* Extract the argument for this parameter
*
* <p>
* You must validate the arguments, using
* {@link TargetMethod#validateArguments(Map, Map, boolean)}, first.
*
* @param arguments the validated arguments
* @return the parameter
*/
@SuppressWarnings("unchecked")
public T get(Map<String, ?> arguments) {
if (arguments.containsKey(name)) {
return (T) arguments.get(name);
}
if (required) {
throw new DebuggerIllegalArgumentException("Missing required parameter " + name);
}
return defaultValue;
}
@Override
public String toString() {
return String.format("<ParameterDescription " +
"name=%s type=%s default=%s required=%s " +
"display='%s' description='%s' choices=%s",
name, type, defaultValue, required, display, description, choices);
}
}
public interface TargetParameterMap extends Map<String, ParameterDescription<?>> {
public static class EmptyTargetParameterMap extends
AbstractEmptyMap<String, ParameterDescription<?>> implements TargetParameterMap {
// Nothing
}
public static class ImmutableTargetParameterMap
extends AbstractNMap<String, ParameterDescription<?>>
implements TargetParameterMap {
public ImmutableTargetParameterMap(Map<String, ParameterDescription<?>> map) {
super(map);
}
}
TargetParameterMap EMPTY = new EmptyTargetParameterMap();
public static TargetParameterMap of() {
return EMPTY;
}
public static TargetParameterMap copyOf(Map<String, ParameterDescription<?>> map) {
return new ImmutableTargetParameterMap(map);
}
}
/**
* Construct a map of parameter descriptions from a stream
*
* @param params the descriptions
* @return a map of descriptions by name
*/
static TargetParameterMap makeParameters(Stream<ParameterDescription<?>> params) {
return TargetParameterMap.copyOf(
params.collect(Collectors.toMap(p -> p.name, p -> p)));
}
/**
* Construct a map of parameter descriptions from a collection
*
* @param params the descriptions
* @return a map of descriptions by name
*/
static TargetParameterMap makeParameters(
Collection<ParameterDescription<?>> params) {
return makeParameters(params.stream());
}
/**
* Construct a map of parameter descriptions from an array
*
* @param params the descriptions
* @return a map of descriptions by name
*/
static TargetParameterMap makeParameters(
ParameterDescription<?>... params) {
return makeParameters(Stream.of(params));
}
/**
* Validate the given arguments against the given parameters
*
* @param parameters the parameter descriptions
* @param arguments the arguments
* @param permitExtras false to require every named argument has a named parameter
* @return the map of validated arguments
*/
static Map<String, ?> validateArguments(Map<String, ParameterDescription<?>> parameters,
Map<String, ?> arguments, boolean permitExtras) {
if (!permitExtras) {
if (!parameters.keySet().containsAll(arguments.keySet())) {
Set<String> extraneous = new TreeSet<>(arguments.keySet());
extraneous.removeAll(parameters.keySet());
throw new DebuggerIllegalArgumentException(
"Extraneous parameters: " + extraneous);
}
}
Map<String, Object> valid = new LinkedHashMap<>();
Map<String, String> typeErrors = null;
Set<String> extraneous = null;
for (Map.Entry<String, ?> ent : arguments.entrySet()) {
String name = ent.getKey();
Object val = ent.getValue();
ParameterDescription<?> d = parameters.get(name);
if (d == null && !permitExtras) {
if (extraneous == null) {
extraneous = new TreeSet<>();
}
extraneous.add(name);
}
else if (val != null && !d.type.isAssignableFrom(val.getClass())) {
if (typeErrors == null) {
typeErrors = new TreeMap<>();
}
typeErrors.put(name, "val '" + val + "' is not a " + d.type);
}
else {
valid.put(name, val);
}
}
if (typeErrors != null || extraneous != null) {
StringBuilder sb = new StringBuilder();
if (typeErrors != null) {
sb.append("Type mismatches: ");
sb.append(typeErrors);
}
if (extraneous != null) {
sb.append("Extraneous parameters: ");
sb.append(extraneous);
}
throw new DebuggerIllegalArgumentException(sb.toString());
}
return valid;
}
static TargetParameterMap getParameters(TargetObject obj) {
return obj.getTypedAttributeNowByName(PARAMETERS_ATTRIBUTE_NAME,
TargetParameterMap.class, TargetParameterMap.of());
}
/**
* Get the parameter descriptions of this method
*
* <p>
* TODO: This attribute needs better type checking. Probably delay for schemas.
*
* @return the name-description map of parameters
*/
default public TargetParameterMap getParameters() {
return getParameters(this);
}
/**
* Get the return type of this method
*
* <p>
* If the return type is {@link TargetObject} then it is most likely a link, but that is not
* necessarily the case. If the arguments alone determine the returned object, then the returned
* object can in fact be a canonical object whose path includes the invocation syntax.
*
* @return the return type
*/
default public Class<?> getReturnType() {
return getTypedAttributeNowByName(RETURN_TYPE_ATTRIBUTE_NAME, Class.class,
Object.class);
}
/**
* Check if extra parameters are allowed
*
* <p>
* If not allowed, any named parameter not in the descriptions is considered an error.
*
* @return true if extras allowed, false if not
*/
default public boolean allowsExtra() {
return false;
}
/**
* Invoke the method with the given arguments
*
* @param arguments the map of named arguments
* @return a future which completes with the return value
*/
CompletableFuture<Object> invoke(Map<String, ?> arguments);
}

View file

@ -0,0 +1,64 @@
/* ###
* 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 ghidra.async.TypeSpec;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.program.model.address.AddressRange;
/**
* A binary module loaded by the debugger
*/
@DebuggerTargetObjectIface("Module")
public interface TargetModule<T extends TargetModule<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetModule<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetModule.class;
TypeSpec<TargetModule<?>> TYPE = TypeSpec.auto();
String VISIBLE_RANGE_ATTRIBUTE_NAME = "range";
String RANGE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "range";
String MODULE_NAME_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "module_name";
/**
* Get the range containing all mapped sections of this module
*
* <p>
* The minimum address should be the base address. The maximum address is the largest address
* mapped to any section belonging to this module. This attribute is especially important if
* sections are not given in the model. This attribute communicates the range which <em>may</em>
* belong to the module.
*
* @return the base address, or {@code null}
*/
public default AddressRange getRange() {
return getTypedAttributeNowByName(RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
}
/**
* Get the name of the module as defined by the target platform
*
* @return the module name
*/
public default String getModuleName() {
return getTypedAttributeNowByName(MODULE_NAME_ATTRIBUTE_NAME, String.class, null);
}
}

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;
import ghidra.lifecycle.Experimental;
/**
* A place for modules to reside
*
* Also a hint interface which helps the user of the client locate modules which apply to a given
* target object
*
* TODO: Experiment with the idea of "synthetic modules" as presented by {@code dbgeng.dll}. Is
* there a similar idea in GDB? This could allow us to expose Ghidra's symbol table to the connected
* debugger.
*/
@DebuggerTargetObjectIface("ModuleContainer")
public interface TargetModuleContainer<T extends TargetModuleContainer<T>>
extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetModuleContainer<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetModuleContainer.class;
String SUPPORTS_SYNTHETIC_MODULES_ATTRIBUTE_NAME =
PREFIX_INVISIBLE + "supports_synthetic_modules";
@Experimental
public default boolean supportsSyntheticModules() {
return getTypedAttributeNowByName(SUPPORTS_SYNTHETIC_MODULES_ATTRIBUTE_NAME, Boolean.class,
false);
}
@Experimental
public default CompletableFuture<? extends TargetModule<?>> addSyntheticModule(String name) {
throw new UnsupportedOperationException();
}
}

View file

@ -0,0 +1,108 @@
/* ###
* 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.Collection;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.async.TypeSpec;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetNamedDataTypeRef;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.util.TargetDataTypeConverter;
/**
* A data type that would have a name in Ghidra's data type manager
*
* <ul>
* <li>{@code enum}</li>
* <li>Function signature</li>
* <li>{@code struct}</li>
* <li>{@code typedef}</li>
* <li>{@code union}</li>
* </ul>
*
* Other types, e.g., pointers, arrays, are modeled as attributes.
*
* See {@link TargetDataTypeConverter} to get a grasp of the conventions
*
* @param <T> the type of this object
*/
@DebuggerTargetObjectIface("DataType")
public interface TargetNamedDataType<T extends TargetNamedDataType<T>>
extends TypedTargetObject<T>, TargetNamedDataTypeRef<T> {
TypeSpec<Map<String, ? extends TargetDataTypeMember<?>>> MEMBER_MAP_TYPE = TypeSpec.auto();
enum Private {
;
private abstract class Cls implements TargetNamedDataType<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetNamedDataType.class;
enum NamedDataTypeKind {
ENUM, FUNCTION, STRUCT, TYPEDEF, UNION;
}
String NAMED_DATA_TYPE_KIND_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "kind";
String ENUM_BYTE_LENGTH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "byte_length";
String NAMESPACE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "namespace";
String FUNCTION_RETURN_INDEX = "return";
String FUNCTION_PARAMETER_DIM = "param";
/**
* Get the members of this data type in order.
*
* While it is most common for members to be immediate children of the type, that is not
* necessarily the case.
*
* @implNote By default, this method collects all successor members ordered by path. Overriding
* that behavior is not yet supported.
* @return the members
*/
default CompletableFuture<? extends Collection<? extends TargetDataTypeMember<?>>> getMembers() {
return DebugModelConventions.collectSuccessors(this, TargetDataTypeMember.tclass);
}
/**
* Get the namespace for this data type.
*
* While it is most common for a data type to be an immediate child of its namespace, that is
* not necessarily the case. This method is a reliable and type-safe means of obtaining that
* namespace.
*
* @return a reference to the namespace
*/
default TypedTargetObjectRef<? extends TargetDataTypeNamespace<?>> getNamespace() {
return getTypedRefAttributeNowByName(NAMESPACE_ATTRIBUTE_NAME,
TargetDataTypeNamespace.tclass, null);
}
/**
* Get the kind of this data type
*
* @return the kind
*/
default NamedDataTypeKind getKind() {
return getTypedAttributeNowByName(NAMED_DATA_TYPE_KIND_ATTRIBUTE_NAME,
NamedDataTypeKind.class, null);
}
}

View file

@ -0,0 +1,717 @@
/* ###
* 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.*;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
import ghidra.dbg.*;
import ghidra.dbg.attributes.TargetObjectRef;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.error.DebuggerModelTypeException;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.util.PathUtils;
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
import ghidra.dbg.util.ValueUtils;
import ghidra.util.Msg;
/**
* A handle to a target object in a debugger
*
* <p>
* This object supports querying for and obtaining the interfaces which constitute what the object
* is and define how the client may interact with it. The object may also have children, e.g., a
* process should likely have threads.
*
* <p>
* This interface is the focal point of the "debug target model." A debugger may present itself as
* an arbitrary directory of "target objects." The root object is typically the debugger's session,
* and one its attributes is a collection for its attached targets. These objects, including the
* root object, may implement any number of interfaces extending {@link TargetObject}. These
* interfaces comprise the type and behavior of the object. An object's children comprise its
* elements (for collection-like objects) and attributes. Every object in the directory has a path.
* Each element in the path identifies an index (if the child is an element) or a name (if the child
* is an attribute). It is the implementation's responsibility to ensure each object's path
* correctly identifies that same object in the model directory. The root has the empty path. Every
* object must have a unique path; thus, every object must have a unique name among its sibling.
*
* <p>
* The objects are arranged in a directory with links permitted. Links come in the form of
* object-valued attributes where the attribute path is not its value's path. Thus, the overall
* structure remains a tree, but by resolving links, the model may be treated as a directed graph,
* likely containing cycles. See {@link PathUtils#isLink(List, String, List)}.
*
* <p>
* The implementation must guarantee that distinct {@link TargetObject}s from the same model do not
* refer to the same path. That is, checking for object identity is sufficient to check that two
* variables refer to the same object. It is recommended that client-side implementations use a
* weak-valued map of paths to cached target objects.
*
* <p>
* Various conventions govern where the client/user should search to obtain a given interface in the
* context of some target object. For example, if the user is interacting with a thread, and wishes
* to access that thread's memory, it needs to follow a given search order to find the appropriate
* target object(s), if they exist, implementing the desired interface. See
* {@link DebugModelConventions#findSuitable(Class, TargetObject)} and
* {@link DebugModelConventions#findInAggregate(Class, TargetObject)} for details. In summary, the
* order is:
*
* <ol>
* <li><b>The object itself:</b> Test if the context target object supports the desired interface.
* If it does, take it.</li>
* <li><b>Aggregate objects:</b> If the object is marked with {@link TargetAggregate}, collect all
* attributes supporting the desired interface. If there are any, take them. This step is applied
* recursively if the child attributes are also marked with {@link TargetAggregate}.</li>
* <li><b>Ancestry:</b> Apply these same steps to the object's (canonical) parent, recursively.</li>
* </ol>
*
* <p>
* For some situations, exactly one object is required. In that case, take the first obtained by
* applying the above rules. In other situations, multiple objects may be acceptable. Again, apply
* the rules until a sufficient collection of objects is obtained. If an object is in conflict with
* another, take the first encountered. This situation may be appropriate if, e.g., multiple target
* memories present disjoint regions. There should not be conflicts among sibling. If there are,
* then either the model or the query is not sound. The order sibling are considered should not
* matter. These rules are incubating and are implemented in {@link DebugModelConventions}.
*
* <p>
* This relatively free structure and corresponding conventions allow for debuggers to present a
* model which closely reflects the structure of its session. For example, the following structure
* may be presented by a user-space debugger for a desktop operating system:
*
* <ul>
* <li>"Session" : {@link TargetAccessConditioned}, {@link TargetInterpreter},
* {@link TargetAttacher}, {@link TargetLauncher}, {@link TargetInterruptible}</li>
* <ul>
* <li>"Process 789" : {@link TargetAggregate}, {@link TargetDetachable}, {@link TargetKillable},
* {@link TargetResumable}</li>
* <ul>
* <li>"Threads" : {@link TargetObject}</li>
* <ul>
* <li>"Thread 1" : {@link TargetExecutionStateful}, {@link TargetSingleSteppable},
* {@link TargetMultiSteppable}</li>
* <ul>
* <li>"Registers" : {@link TargetRegisterBank}</li>
* <ul>
* <li>"r1" : {@link TargetRegister}</li>
* <li>...</li>
* </ul>
* </ul>
* <li>...more threads</li>
* </ul>
* <li>"Memory" : {@link TargetMemory}</li>
* <ul>
* <li>"[0x00400000:0x00401234]" : {@link TargetMemoryRegion}</li>
* <li>...more regions</li>
* </ul>
* <li>"Modules" : {@link TargetModuleContainer}</li>
* <ul>
* <li>"/usr/bin/echo" : {@link TargetModule}</li>
* <ul>
* <li>".text" ({@link TargetSection})</li>
* <li>...more sections</li>
* <li>"Namespace" : {@link TargetSymbolNamespace}</li>
* <ul>
* <li>"main" : {@link TargetSymbol}</li>
* <li>"astruct" : {@link TargetNamedDataType}</li>
* <li>...more symbols and types</li>
* </ul>
* </ul>
* <li>...more modules</li>
* </ul>
* </ul>
* <li>"Environment": {@link TargetEnvironment}</li>
* <ul>
* <li>"Process 321" : {@link TargetAttachable}</li>
* <li>...more processes</li>
* </ul>
* </ul>
* </ul>
*
* <p>
* TODO: Should I have a different type for leaf vs. branch objects? Attribute-/element-only
* objects?
*
* <p>
* Note that several methods of this interface and its sub-types return {@link CompletableFuture},
* because they are actions which may be transported over a network, or otherwise require
* asynchronous communication with a debugger. The documentation may say these methods return an
* object or throw an exception. In those cases, unless otherwise noted, this actually means the
* future will complete with that object, or complete exceptionally. Specifying this in every
* instance is just pedantic.
*/
public interface TargetObject extends TargetObjectRef {
String PREFIX_INVISIBLE = "_";
String DISPLAY_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "display";
String SHORT_DISPLAY_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "short_display";
String KIND_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "kind";
String MODIFIED_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "modified";
String TYPE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "type";
String UPDATE_MODE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "update_mode";
String VALUE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "value";
String ORDER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "order";
enum Protected {
;
protected static final Map<Class<? extends TargetObject>, Collection<Class<? extends TargetObject>>> INTERFACES_BY_CLASS =
new HashMap<>();
protected static final Map<Class<? extends TargetObject>, Collection<String>> INTERFACE_NAMES_BY_CLASS =
new HashMap<>();
/**
* Get all {@link DebuggerTargetObjectIface}-annotated interfaces in the given class's
* hierarchy.
*
* @param cls the class
*/
protected static Collection<Class<? extends TargetObject>> getInterfacesOf(
Class<? extends TargetObject> cls) {
return INTERFACES_BY_CLASS.computeIfAbsent(cls, Protected::doGetInterfacesOf);
}
protected static Collection<Class<? extends TargetObject>> doGetInterfacesOf(
Class<? extends TargetObject> cls) {
List<Class<? extends TargetObject>> result = new ArrayList<>();
doCollectInterfaces(cls, result);
return result;
}
protected static void doCollectInterfaces(Class<?> cls,
Collection<Class<? extends TargetObject>> result) {
if (TargetObject.class == cls) {
return;
}
if (!TargetObject.class.isAssignableFrom(cls)) {
return;
}
if (cls.isInterface()) {
result.add(cls.asSubclass(TargetObject.class));
}
Class<?> sup = cls.getSuperclass();
if (sup != null) {
doCollectInterfaces(sup, result);
}
for (Class<?> si : cls.getInterfaces()) {
doCollectInterfaces(si, result);
}
}
protected static Collection<String> getInterfaceNamesOf(Class<? extends TargetObject> cls) {
return INTERFACE_NAMES_BY_CLASS.computeIfAbsent(cls, Protected::doGetInterfaceNamesOf);
}
protected static Collection<String> doGetInterfaceNamesOf(
Class<? extends TargetObject> cls) {
List<String> result = new ArrayList<>();
for (Class<? extends TargetObject> iface : getInterfacesOf(cls)) {
DebuggerTargetObjectIface annot =
iface.getAnnotation(DebuggerTargetObjectIface.class);
if (annot == null) {
continue;
}
result.add(annot.value());
}
result.sort(Comparator.naturalOrder());
return result;
}
}
enum TargetUpdateMode {
/**
* The object's elements are kept up to date via unsolicited push notifications / callbacks.
*
* <p>
* This is the default.
*/
UNSOLICITED,
/**
* The object's elements are only updated when requested.
*
* <p>
* The request may still generate push notifications / callbacks if others are listening
*/
SOLICITED,
/**
* The object's elements will not change.
*
* <p>
* This is a promise made by the model implementation. Once the {@code update_mode}
* attribute has this value, it should never be changed back. Note that other attributes of
* this object are still expected to be kept up to date, if they change.
*/
FIXED;
}
/**
* Get an informal name identify the type of this object.
*
* <p>
* This is an informal notion of type and may only be used for visual styling, logging, or other
* informational purposes. Scripts should not rely on this to predict behavior, but instead on
* {@link #getAs(Class)} or {@link #getInterfaces()};
*
* @return an informal name of this object's type
*/
public String getTypeHint();
/**
* Get the interfaces this object actually supports, and that the client recognizes.
*
* @implNote Proxy implementations should likely override this method.
*
* @return the set of interfaces
*/
public default Collection<? extends Class<? extends TargetObject>> getInterfaces() {
return Protected.getInterfacesOf(getClass());
}
/**
* Get the interface names this object actually supports.
*
* <p>
* When this object is a proxy, this set must include the names of all interfaces reported by
* the agent, whether or not they are recognized by the client.
*
* @return the set of interface names
*/
public default Collection<String> getInterfaceNames() {
return Protected.doGetInterfaceNamesOf(getClass());
}
/**
* Check that this object is still valid
*
* <p>
* In general, an invalid object should be disposed by the user immediately on discovering it is
* invalid. See {@link TargetObjectListener#invalidated(TargetObject)} for a means of reacting
* to object invalidation. Nevertheless, it is acceptable to access stale attributes and element
* keys, for informational purposes only. Implementors must reject all commands, including
* non-cached gets, on an invalid object by throwing an {@link IllegalStateException}.
*
* @return true if valid, false if invalid
*/
public boolean isValid();
/**
* Fetch the canonical parent of this object
*
* <p>
* Note that if this is the root, a future is still returned, but it will complete with
* {@code null}.
*
* @return a future which completes with the parent object
*/
default CompletableFuture<? extends TargetObject> fetchParent() {
TargetObjectRef parent = getParent();
if (parent == null) {
return AsyncUtils.nil();
}
return parent.fetch();
}
/**
* Get the cached elements of this object
*
* <p>
* Note these are cached elements, and there's no requirement on the model's part to keep this
* cache up to date (unlike attributes). Thus, the indices (keys) in the returned map may be
* out-of-date.
*
* <p>
* Note that this method is not required to provide objects, but only object references. Local
* implementations, and clients having the appropriate proxies cached may present some, all, or
* none of the entries with actual objects. Users should NEVER depend on this being the case,
* however. Always call {@link TargetObjectRef#fetch()}, or use {@link #fetchElements} instead,
* to guarantee the actual objects are presented.
*
* @return the map of indices to element references
*/
public Map<String, ? extends TargetObjectRef> getCachedElements();
/**
* Get a description of the object, suitable for display in the UI.
*
* @return the display description
*/
public default String getDisplay() {
return getTypedAttributeNowByName(DISPLAY_ATTRIBUTE_NAME, String.class, getName());
}
/**
* Get a brief description of the object, suitable for display in the UI.
*
* @return the display description
*/
public default String getShortDisplay() {
return getTypedAttributeNowByName(SHORT_DISPLAY_ATTRIBUTE_NAME, String.class, getDisplay());
}
/**
* Get the element update mode for this object
*
* <p>
* The update mode informs the client's caching implementation. If set to
* {@link TargetUpdateMode#UNSOLICITED}, the client will assume its cache is kept up to date via
* listener callbacks, and may avoid querying for the object's elements. If set to
* {@link TargetUpdateMode#FIXED}, the client can optionally remove its listener for element
* changes but still assume its cache is up to date, since the object's elements are no longer
* changing. If set to {@link TargetUpdateMode#SOLICITED}, the client must re-validate its cache
* whenever the elements are requested. It is still recommended that the client listen for
* element changes, since the local cache may be updated (resulting in callbacks) when handling
* requests from another client.
*
* <p>
* IMPORTANT: Update mode does not apply to attributes. Except in rare circumstances, the model
* must keep an object's attributes up to date.
*
* @return the update mode
*/
public default TargetUpdateMode getUpdateMode() {
return getTypedAttributeNowByName(UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.class,
TargetUpdateMode.UNSOLICITED);
}
@Override
default CompletableFuture<? extends TargetObject> fetch() {
return CompletableFuture.completedFuture(this);
}
/**
* Get the (usually opaque) identifier that the underlying connection uses for this object
*
* <p>
* The opaque identifier should implement proper {@link Object#hashCode()} and
* {@link Object#equals(Object)}, so that paired with the client, it forms a unique key for this
* target object. It should also implement {@link Object#toString()}; however, it is for
* debugging or informational purposes only. It is common for this identifier to be the object's
* path.
*
* @return the identifier
*/
public Object getProtocolID();
/**
* Get this same object, cast to the requested interface, if supported.
*
* @param <T> the requested interface
* @param iface the class of the requested interface
* @return the same object, cast to the interface
* @throws DebuggerModelTypeException if the interface is not supported by this object
*/
@Override
public default <T extends TypedTargetObject<T>> T as(Class<T> iface) {
return DebuggerObjectModel.requireIface(iface, this, getPath());
}
/**
* Get the cached attributes of this object
*
* <p>
* While this technically only returns "cached" attributes, the model should be pushing
* attribute updates to the object automatically. Thus, the names (keys) and values in the
* returned map should be up-to-date.
*
* <p>
* Note that object-valued attributes are only guaranteed to be a {@link TargetObjectRef}. Local
* implementations, and clients having the appropriate proxies cached may present some, all, or
* none of the object-valued attributes with the actual object. Users should NEVER depend on
* this being the case, however. Always call {@link TargetObjectRef#fetch()}, or use
* {@link #fetchAttributes()} instead, to guarantee the actual objects are presented.
*
* @return the cached name-value map of attributes
*/
public Map<String, ?> getCachedAttributes();
/**
* Get the named attribute from the cache
*
* @param name the name of the attribute
* @return the value
*/
public default Object getCachedAttribute(String name) {
return getCachedAttributes().get(name);
}
/**
* Cast the named attribute to the given type, if possible
*
* <p>
* If the attribute value is {@code null} or cannot be cast to the given type, an error message
* is printed, and the fallback value is returned.
*
* @param <T> the expected type of the attribute
* @param name the name of the attribute
* @param cls the class giving the expected type
* @param fallback the fallback value
* @return the value casted to the expected type, or the fallback value
*/
public default <T> T getTypedAttributeNowByName(String name, Class<T> cls, T fallback) {
Object obj = getCachedAttribute(name);
return ValueUtils.expectType(obj, cls, this, name, fallback);
}
/**
* Cast the named object-reference attribute to the given type, if possible
*
* <p>
* In addition to casting the attribute to an object reference, if possible, this wraps that
* reference in {@link TypedTargetObjectRef}, which if fetched, casts the object to the required
* type, if possible.
*
* @param <T> the expected type of the object
* @param name the name of the attribute
* @param cls the class giving the expected type
* @param fallback the fallback object
* @return the typed object reference
*/
public default <T extends TypedTargetObject<T>> TypedTargetObjectRef<T> getTypedRefAttributeNowByName(
String name, Class<T> cls, T fallback) {
TargetObjectRef ref = getTypedAttributeNowByName(name, TargetObjectRef.class, null);
return ref == null ? fallback : TypedTargetObjectRef.casting(cls, ref);
}
/**
* Invalidate caches associated with this object, other than those for cached children
*
* <p>
* Some objects, e.g., memories and register banks, may have caches to reduce requests and
* callbacks. This method should clear such caches, if applicable, but <em>should not</em> clear
* caches of elements or attributes.
*
* <p>
* In the case of a proxy, the proxy must invalidate all local caches, as well as request the
* remote object invalidate its caches. In this way, all caches between the user and the actual
* data, no matter where they are hosted, are invalidated.
*
* @return a future which completes when the caches are invalidated
*/
public default CompletableFuture<Void> invalidateCaches() {
return AsyncUtils.NIL;
}
/**
* Listen for object events
*
* <p>
* The caller must maintain a strong reference to the listener. To allow stale listeners to be
* garbage collected, the implementation should use weak or soft references. That said, the
* client user must not rely on the implementation to garbage collect its listeners. All
* unneeded listeners should be removed using {@link #removeListener(TargetObjectListener)}. The
* exception is when an object is destroyed. The user may safely neglect removing any listeners
* it registered with that object. If the object does not keep listeners, i.e., it produces no
* events, this method may do nothing.
*
* @param l the listener
*/
public default void addListener(TargetObjectListener l) {
throw new UnsupportedOperationException();
}
/**
* Remove a listener
*
* <p>
* If the given listener is not registered with this object, this method should do nothing.
*
* @param l the listener
*/
public default void removeListener(TargetObjectListener l) {
throw new UnsupportedOperationException();
}
public interface TargetObjectListener {
/**
* The object's display string has changed
*
* @param object the object
* @param display the new display string
*/
default void displayChanged(TargetObject object, String display) {
}
/**
* The object is no longer valid
*
* <p>
* This should be the final callback ever issued for this object. Invalidation of an object
* implies invalidation of all its successors; nevertheless, the implementation MUST
* explicitly invoke this callback for those successors in preorder. Users need only listen
* for invalidation by installing a listener on the object of interest. However, a user must
* be able to ignore invalidation events on an object it has already removed and/or
* invalidated. For models that are managed by a client connection, disconnecting or
* otherwise terminating the session should invalidate the root, and thus every object must
* receive this callback.
*
* <p>
* If an invalidated object is replaced (i.e., a new object with the same path is added to
* the model), the implementation must be careful to issue all invalidations related to the
* removed object before the replacement is added, so that delayed invalidations are not
* mistakenly applied to the replacement or its successors.
*
* @param object the now-invalid object
* @param reason an informational, human-consumable reason, if applicable
*/
default void invalidated(TargetObject object, String reason) {
}
/**
* The object's elements changed
*
* @param parent the object whose children changed
* @param removed the list of removed children
* @param added a map of indices to new children references
*/
default void elementsChanged(TargetObject parent, Collection<String> removed,
Map<String, ? extends TargetObjectRef> added) {
}
/**
* The object's attributes changed
*
* <p>
* In the case of an object-valued attribute, changes to that object do not constitute a
* changed attribute. The attribute is considered changed only when that attribute is
* assigned to a completely different object.
*
* @param parent the object whose attributes changed
* @param removed the list of removed attributes
* @param added a map of names to new/changed attributes
*/
default void attributesChanged(TargetObject parent, Collection<String> removed,
Map<String, ?> added) {
}
/**
* The model has requested the user invalidate caches associated with this object
*
* <p>
* For objects with methods exposing contents which transcend elements and attributes (e.g.,
* memory contents), this callback requests that any caches associated with that content be
* invalidated. Most notably, this usually occurs when an object (e.g., thread) enters the
* {@link TargetExecutionState#RUNNING} state, to inform proxies that they should invalidate
* their memory and register caches. In most cases, users need not worry about this
* callback. Protocol implementations that use the model, however, should forward this
* request to the client implementation.
*
* <p>
* Note caches of elements and attributes are not affected by this callback. See
* {@link TargetObject#invalidateCaches()}.
*
* @param object the object whose caches must be invalidated
*/
default void invalidateCacheRequested(TargetObject object) {
}
}
/**
* An adapter which automatically gets new children from the model
*/
public interface TargetObjectFetchingListener extends TargetObjectListener {
@Override
default void elementsChanged(TargetObject parent, Collection<String> removed,
Map<String, ? extends TargetObjectRef> added) {
AsyncFence fence = new AsyncFence();
Map<String, TargetObject> objects = new TreeMap<>(TargetObjectKeyComparator.ELEMENT);
for (Map.Entry<String, ? extends TargetObjectRef> ent : added.entrySet()) {
fence.include(ent.getValue().fetch().thenAccept(o -> {
synchronized (objects) {
objects.put(ent.getKey(), o);
}
}).exceptionally(e -> {
Msg.error(this, "Could not retrieve an object just added: " + ent.getValue());
return null;
}));
}
fence.ready().thenAccept(__ -> {
elementsChangedObjects(parent, removed, objects);
}).exceptionally(e -> {
Msg.error(this, "Error in callback to elementsChangedObjects: ", e);
return null;
});
}
@Override
default void attributesChanged(TargetObject parent, Collection<String> removed,
Map<String, ?> added) {
AsyncFence fence = new AsyncFence();
Map<String, Object> attributes = new TreeMap<>(TargetObjectKeyComparator.ATTRIBUTE);
for (Map.Entry<String, ?> ent : added.entrySet()) {
Object val = ent.getValue();
if (!(val instanceof TargetObjectRef)) {
attributes.put(ent.getKey(), val);
continue;
}
// NOTE: if it's the actual object, it should already be completed.
TargetObjectRef ref = (TargetObjectRef) val;
if (PathUtils.isLink(parent.getPath(), ent.getKey(), ref.getPath())) {
attributes.put(ent.getKey(), val);
continue;
}
fence.include(ref.fetch().thenAccept(o -> {
synchronized (attributes) {
attributes.put(ent.getKey(), o);
}
}).exceptionally(e -> {
Msg.error(this, "Could not retrieve an object just added: " + ent.getValue());
return null;
}));
}
fence.ready().thenAccept(__ -> {
attributesChangedObjects(parent, removed, attributes);
}).exceptionally(e -> {
Msg.error(this, "Error in callback to attributesChangedObjects: ", e);
return null;
});
}
/**
* The object's children changed
*
* <p>
* In this adapter, the map contains the actual objects. In the case of client proxies, it
* is the protocol implementation's responsibility to ensure that object attributes are kept
* up to date.
*
* @param parent the object whose children changed
* @param removed the list of removed children
* @param added a map of indices to new children
* @see #elementsChanged(TargetObject, List, Map)
*/
default void elementsChangedObjects(TargetObject parent, Collection<String> removed,
Map<String, ? extends TargetObject> added) {
};
/**
* The object's attributes changed
*
* <p>
* In this adapter, where an attribute has an object value, the map contains the retrieved
* object. In the case of client proxies, it is the protocol implementation's responsibility
* to ensure that object attributes are kept up to date.
*
* @param parent the object whose attributes changed
* @param removed the list of removed attributes
* @param added a map of names to new/changed attributes
* @see #attributesChanged(TargetObject, List, Map)
*/
default void attributesChangedObjects(TargetObject parent, Collection<String> removed,
Map<String, ?> added) {
};
}
}

View file

@ -0,0 +1,40 @@
/* ###
* 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 ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
/**
* A marker interface which indicates a process, usually on a host operating system
*
* If this object does not support {@link TargetExecutionStateful}, then its mere existence in the
* model implies that it is {@link TargetExecutionState#ALIVE}. TODO: Should allow association, but
* that may have to wait until schemas are introduced.
*/
@DebuggerTargetObjectIface("Process")
public interface TargetProcess<T extends TargetProcess<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetProcess<Cls> {
}
}
String PID_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "pid";
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetProcess.class;
}

View file

@ -0,0 +1,80 @@
/* ###
* 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 ghidra.async.TypeSpec;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.util.PathUtils;
/**
* This is a description of a register
*
* This describes a register abstractly. It does not represent the actual value of a register. For
* values, see {@link TargetRegisterBank}. The description and values are separated, since the
* descriptions typically apply to the entire platform, and so can be presented just once.
*/
@DebuggerTargetObjectIface("Register")
public interface TargetRegister<T extends TargetRegister<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetRegister<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetRegister.class;
TypeSpec<TargetRegister<?>> TYPE = TypeSpec.auto();
String CONTAINER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "container";
String LENGTH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "length";
/**
* Get the container of this register.
*
* While it is most common for a register descriptor to be an immediate child of its container,
* that is not necessarily the case. In fact, some models may present sub-registers as children
* of another register. This method is a reliable and type-safe means of obtaining the
* container.
*
* @return a reference to the container
*/
default TypedTargetObjectRef<? extends TargetRegisterContainer<?>> getContainer() {
return getTypedRefAttributeNowByName(CONTAINER_ATTRIBUTE_NAME,
TargetRegisterContainer.tclass, null);
}
/**
* Get the length, in bits, of the register
*
* @return the length of the register
*/
default int getBitLength() {
return getTypedAttributeNowByName(LENGTH_ATTRIBUTE_NAME, Integer.class, 0);
}
@Override
public default String getIndex() {
return PathUtils.isIndex(getPath()) ? PathUtils.getIndex(getPath())
: PathUtils.getKey(getPath());
//return PathUtils.getIndex(getPath());
}
// TODO: Any typical type assignment or structure definition?
// TODO: (Related) Should describe if typically a pointer?
// TODO: What if the register is memory-mapped?
}

View file

@ -0,0 +1,221 @@
/* ###
* 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.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.error.DebuggerRegisterAccessException;
import ghidra.util.Msg;
/**
* A bank of registers on the debug target
*/
@DebuggerTargetObjectIface("RegisterBank")
public interface TargetRegisterBank<T extends TargetRegisterBank<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetRegisterBank<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetRegisterBank.class;
String DESCRIPTIONS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "descriptions";
/**
* Get the object describing the registers in this bank
*
* @return a future which completes with object
*/
@SuppressWarnings("unchecked")
public default TypedTargetObjectRef<? extends TargetRegisterContainer<?>> getDescriptions() {
return getTypedRefAttributeNowByName(DESCRIPTIONS_ATTRIBUTE_NAME,
TargetRegisterContainer.class, null);
}
/**
* Read the given registers
*
* <p>
* The value of each register is given as a byte array in big-endian order, <em>no matter the
* byte order of the target platform</em>.
*
* <p>
* <b>WARNING:</b> the implementation is not required to have any understanding of the register
* structure. In particular, caches are not aware of child registers. To avoid the issue, it is
* highly recommended to only read and write base registers.
*
* @param registers the registers to read
* @return a future which completes with a name-value map of the values read
*/
public default CompletableFuture<? extends Map<String, byte[]>> readRegisters(
Collection<TargetRegister<?>> registers) {
return readRegistersNamed(
registers.stream().map(TargetRegister::getIndex).collect(Collectors.toSet()));
}
/**
* Write the given registers
*
* <p>
* The value of each register is given as a byte array in big-endian order, <em>no matter the
* byte order of the target platform</em>.
*
* <p>
* <b>WARNING:</b> the implementation is not required to have any understanding of the register
* structure. In particular, caches are not aware of child registers. To avoid the issue, it is
* highly recommended to only read and write base registers.
*
* @param values the register-value map to write
* @return a future which completes upon successfully writing all given registers
*/
public default CompletableFuture<Void> writeRegisters(Map<TargetRegister<?>, byte[]> values) {
Map<String, byte[]> named = new LinkedHashMap<>();
for (Entry<TargetRegister<?>, byte[]> ent : values.entrySet()) {
named.put(ent.getKey().getIndex(), ent.getValue());
}
return writeRegistersNamed(named);
}
/**
* Read the named registers
*
* @see #readRegisters(Collection)
* @param names the names of registers to read
* @return a future which completes with a name-value map of the values read
* @throws DebuggerRegisterAccessException if a named register does not exist
*/
public CompletableFuture<? extends Map<String, byte[]>> readRegistersNamed(
Collection<String> names);
/**
* Write the named registers
*
* @see #writeRegistersNamed(Map)
* @param values the name-value map to write
* @return a future which completes upon successfully writing all given registers
* @throws DebuggerRegisterAccessException if a named register does not exist
*/
public CompletableFuture<Void> writeRegistersNamed(Map<String, byte[]> values);
/**
* Read the named registers
*
* @see #readRegistersNamed(Collection)
*/
public default CompletableFuture<? extends Map<String, byte[]>> readRegistersNamed(
String... names) {
return readRegistersNamed(List.of(names));
}
/**
* Read the given register
*
* @see #readRegisters(Collection)
* @param register the register to read
* @return a future which completes with the value read
*/
public default CompletableFuture<byte[]> readRegister(TargetRegister<?> register) {
return readRegister(register.getIndex());
}
/**
* Write the given register
*
* @see #writeRegistersNamed(Map)
* @param register the register to write
* @param value the value to write
* @return a future which completes upon successfully writing the register
*/
public default CompletableFuture<Void> writeRegister(TargetRegister<?> register, byte[] value) {
return writeRegistersNamed(Map.of(register.getIndex(), value));
}
/**
* Read the named register
*
* @see #readRegisters(Collection)
* @param name the name of the register to read
* @return a future which completes with the value read
*/
public default CompletableFuture<byte[]> readRegister(String name) {
return readRegistersNamed(List.of(name)).thenApply(m -> m.get(name));
}
/**
* Write the named register
*
* @see #writeRegistersNamed(Map)
* @param name the name of the register to write
* @param value the value to write
* @return a future which completes upon successfully writing the register
*/
public default CompletableFuture<Void> writeRegister(String name, byte[] value) {
return writeRegistersNamed(Map.of(name, value));
}
/**
* Get a view of the locally-cached register values, if available
*
* <p>
* If caching is not done locally, this returns the empty map.
*
* @return the cached register values
*/
public default Map<String, byte[]> getCachedRegisters() {
return Map.of();
}
/**
* Clear the register cache
*
* <p>
* To avoid duplicate requests for the same registers, proxies are encouraged to implement a
* write-through register cache. If the proxy does so, then calling this method must flush that
* cache. If no cache is used, then no action is necessary.
*
* @deprecated Override {@link #invalidateCaches()} instead
*/
@Deprecated
public default void clearRegisterCache() {
invalidateCaches().exceptionally(e -> {
Msg.error(this, "Error clearing register caches");
return null;
});
}
public interface TargetRegisterBankListener extends TargetObjectListener {
/**
* Registers were successfully read or written
*
* <p>
* If the implementation employs a cache, then it need only report reads or writes which
* updated that cache. However, that cache must be invalidated whenever any other event
* occurs which could change register values, e.g., the target stepping or running.
*
* @param bank this register bank object
* @param updates a name-value map of updated registers
*/
default void registersUpdated(TargetRegisterBank<?> bank, Map<String, byte[]> updates) {
};
}
}

View file

@ -0,0 +1,56 @@
/* ###
* 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.Collection;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebuggerTargetObjectIface;
/**
* A container of register descriptions
*/
@DebuggerTargetObjectIface("RegisterContainer")
public interface TargetRegisterContainer<T extends TargetRegisterContainer<T>>
extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetRegisterContainer<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetRegisterContainer.class;
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<TargetRegisterContainer<?>> wclass = (Class) TargetRegisterContainer.class;
/**
* Get the register descriptions in this container
*
* While it is most common for registers to be immediate children of the container, that is not
* necessarily the case. In fact, some models may present sub-registers as children of another
* register. This method must return all registers (including sub-registers, if applicable) in
* the container.
*
* @implNote By default, this method collects all successor registers ordered by path.
* Overriding that behavior is not yet supported.
* @return the register descriptions
*/
default CompletableFuture<? extends Collection<? extends TargetRegister<?>>> getRegisters() {
return DebugModelConventions.collectSuccessors(this, TargetRegister.tclass);
}
}

View file

@ -0,0 +1,44 @@
/* ###
* 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 which can be resumed, i.e., continued
*/
@DebuggerTargetObjectIface("Resumable")
public interface TargetResumable<T extends TargetResumable<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetResumable<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetResumable.class;
/**
* Resume execution of this object
*
* Note, this would be called "continue" if it weren't a Java reserved word :( .
*
* @return a future which completes upon successful resumption
*/
public CompletableFuture<Void> resume();
}

View file

@ -0,0 +1,86 @@
/* ###
* 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 ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
/**
* An allocated section of a binary module
*
* Note that the model should only present those sections which are allocated in memory. Otherwise
* strange things may happen, such as zero-length ranges (which AddressRange hates), or overlapping
* ranges (which Trace hates).
*
* TODO: Present all sections, but include start, length, isAllocated instead?
*/
@DebuggerTargetObjectIface("Section")
public interface TargetSection<T extends TargetSection<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetSection<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetSection.class;
String MODULE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "module";
String RANGE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "range";
String VISIBLE_RANGE_ATTRIBUTE_NAME = "range";
/**
* Get the module to which this section belongs
*
* @return the owning module
*/
@SuppressWarnings("unchecked")
public default TypedTargetObjectRef<? extends TargetModule<?>> getModule() {
return getTypedRefAttributeNowByName(MODULE_ATTRIBUTE_NAME, TargetModule.class, null);
}
// TODO: Should there be a getSectionName(), like getModuleName()
// in case getIndex() isn't accurate?
/**
* Get the range of addresses comprising the section
*
* @return the range
*/
public default AddressRange getRange() {
return getTypedAttributeNowByName(RANGE_ATTRIBUTE_NAME, AddressRange.class, null);
}
/**
* Get the lowest address in the section
*
* @return the start
*/
public default Address getStart() {
return getRange().getMinAddress();
}
/**
* Get the highest address (inclusive) in the section
*
* @return the end
*/
public default Address getEnd() {
return getRange().getMaxAddress();
}
}

View file

@ -0,0 +1,54 @@
/* ###
* 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.Collection;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebuggerTargetObjectIface;
/**
* Represents the execution stack, as unwound into frames by the debugger
*
* Conventionally, if the debugger can also unwind register values, then each frame should present a
* register bank. Otherwise, the same object presenting this stack should present the register bank.
*/
@DebuggerTargetObjectIface("Stack")
public interface TargetStack<T extends TargetStack<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetStack<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetStack.class;
/**
* Get the frames in this stack
*
* While it is most common for frames to be immediate children of the stack, that is not
* necessarily the case.
*
* @implNote By default, this method collects all successor frames ordered by path. Overriding
* that behavior is not yet supported.
* @return the stack frames
*/
default CompletableFuture<? extends Collection<? extends TargetStackFrame<?>>> getFrames() {
return DebugModelConventions.collectSuccessors(this, TargetStackFrame.tclass);
}
}

View file

@ -0,0 +1,47 @@
/* ###
* 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 ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.program.model.address.Address;
/**
* One frame of an execution stack
*/
@DebuggerTargetObjectIface("StackFrame")
public interface TargetStackFrame<T extends TargetStackFrame<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetStackFrame<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetStackFrame.class;
String PC_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "pc";
/**
* Get the program counter for the frame
*
* Note for some platforms, this may differ from the value in the program counter register.
*
* @return a future completing with the address of the executing (or next) instruction.
*/
public default Address getProgramCounter() {
return getTypedAttributeNowByName(PC_ATTRIBUTE_NAME, Address.class, Address.NO_ADDRESS);
}
}

View file

@ -0,0 +1,254 @@
/* ###
* 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 static ghidra.lifecycle.Unfinished.TODO;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.util.CollectionUtils;
import ghidra.dbg.util.CollectionUtils.AbstractEmptySet;
import ghidra.lifecycle.Experimental;
@DebuggerTargetObjectIface("Steppable")
public interface TargetSteppable<T extends TargetSteppable<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetSteppable<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetSteppable.class;
public interface TargetStepKindSet extends Set<TargetStepKind> {
public static class EmptyTargetStepKindSet extends AbstractEmptySet<TargetStepKind>
implements TargetStepKindSet {
// Nothing
}
public static class ImmutableTargetStepKindSet
extends CollectionUtils.AbstractNSet<TargetStepKind> implements TargetStepKindSet {
public ImmutableTargetStepKindSet(TargetStepKind... kinds) {
super(kinds);
}
public ImmutableTargetStepKindSet(Set<TargetStepKind> set) {
super(set);
}
}
TargetStepKindSet EMPTY = new EmptyTargetStepKindSet();
public static TargetStepKindSet of() {
return EMPTY;
}
public static TargetStepKindSet of(TargetStepKind... kinds) {
return new ImmutableTargetStepKindSet(kinds);
}
public static TargetStepKindSet copyOf(Set<TargetStepKind> set) {
return new ImmutableTargetStepKindSet(set);
}
}
/**
* Produce a client-side implementation using other target objects
*
* <p>
* TODO: While this is an interesting idea, I hesitate to pursue it, because it is likely
* inefficient. For socket-based clients, there will be a lot of messages exchanged.
*
* @param resumable the interface for resuming the target
* @param breakpoints the interface for placing breakpoints on the target
* @param registers the interface for reading the target's registers
* @param memory the interface for reading the target's memory
* @return the implementation
*/
@Experimental
public static TargetSteppable<?> synthesize(TargetResumable<?> resumable,
TargetBreakpointContainer<?> breakpoints, TargetRegisterBank<?> registers,
TargetMemory<?> memory) {
return TODO();
}
enum TargetStepKind {
/**
* Step strictly forward
*
* <p>
* To avoid runaway execution, stepping should cease if execution returns from the current
* frame.
*
* <p>
* In more detail: step until execution reaches the instruction following this one,
* regardless of the current frame. This differs from {@link #UNTIL} in that it doesn't
* regard the current frame.
*/
ADVANCE,
/**
* Step out of the current function.
*
* <p>
* In more detail: step until the object has executed the return instruction that returns
* from the current frame.
*
* <p>
* TODO: This step is geared toward GDB's {@code advance}, which actually takes a parameter.
* Perhaps this API should adjust to accommodate stepping parameters. Would probably want a
* strict set of forms, though, and a given kind should have the same form everywhere. If we
* do that, then we could do nifty pop-up actions, like "Step: Advance to here".
*/
FINISH,
/**
* Step a single instruction
*
* <p>
* In more detail: trap after execution of exactly the next instruction. If the instruction
* is a function call, stepping will descend into the function.
*/
INTO,
/**
* Step to the next line of source code.
*
* <p>
* In more detail: if the debugger is a source-based debugger and it has access to debug
* information that includes line numbers, step until execution reaches an instruction
* generated by a line of source code other than the line which generated the instruction
* about to be executed.
*/
LINE,
/**
* Step over a function call.
*
* <p>
* In more detail: if the instruction to be executed is a function call, step until the
* object returns from that call, but before it executes the instruction following the call.
* Otherwise, behave the same as a single step.
*/
OVER,
/**
* Step over a function call, to the next line of source code
*
* <p>
* In more detail: if the debugger is a source-based debugger and it has access to debug
* information that includes line numbers, step (over function calls) until execution
* reaches an instruction generated by a line of source code other than the line which
* generated the instruction about to be executed.
*/
OVER_LINE,
/**
* Skip an instruction.
*
* <p>
* In more detail: advance the program counter to the next instruction without actually
* executing the current instruction.
*/
SKIP,
/**
* Skip the remainder of the current function.
*
* <p>
* In more detail: remove the current stack frame and position the program counter as if the
* current function had just returned, i.e., the instruction following the function call.
* Note it is up to the client user to set the appropriate registers to a given return
* value, if desired.
*/
RETURN,
/**
* Step out of a loop.
*
* <p>
* To avoid runaway execution, stepping should cease if execution returns from the current
* frame.
*
* <p>
* In more detail: if the instruction to be executed is a backward jump, step until
* execution reaches the following instruction in the same stack frame. Alternatively, if
* the debugger is a source-based debugger and it has access to debug information that
* includes line numbers, it may step until execution reaches an instruction generated by a
* line of source code after the line which generated the instruction about to be executed.
*/
UNTIL,
}
String SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "supported_step_kinds";
/**
* Get the kinds of multi-stepping implemented by the debugger
*
* <p>
* Different debuggers may provide similar, but slightly different vocabularies of stepping.
* This method queries the connected debugger for its supported step kinds.
*
* @return the set of supported multi-step operations
*/
public default TargetStepKindSet getSupportedStepKinds() {
return getTypedAttributeNowByName(SUPPORTED_STEP_KINDS_ATTRIBUTE_NAME,
TargetStepKindSet.class, TargetStepKindSet.of());
}
/**
* Step/resume the object until some condition is met
*
* <p>
* A step command may complete with {@link UnsupportedOperationException} despite the
* implementation reporting its kind as supported. This may happen if the current execution
* context prevents its implementation, e.g., debug information is not available for the current
* frame. In many cases, with some expense, the client user can synthesize the desired stepping
* using information it knows in combination with other step kinds, breakpoints, etc.
*
* <p>
* The step command completes when the object is running, and not when it has actually completed
* the step. If, as is usual, the step completes immediately, then the object will immediately
* stop again. If, on the other hand, the single instruction comprises a system call, and the
* debugger is limited to user space, then the step may not immediately complete, if ever. A
* client user wishing to wait for the actual step completion should wait for this object to
* re-enter the {@link TargetExecutionState#STOPPED} state.
*
* <p>
* More nuances may be at play depending on the connected debugger and the target platform. For
* example, the debugger may still be reporting some other event, e.g., module load, and may
* stop before completing the step. Or, e.g., for GDB on Linux x86_64, a thread interrupted
* during a {@code SYSCALL} will have {@code RIP} pointing at the following instruction. When
* the {@code SYSCALL} returns, and that step completes, {@code RIP} will still point to that
* same instruction. As a general note, we do not intend to "overcome" these nuances. Instead,
* we strive to ensure the view presented by this API (and thus by the Ghidra UI) reflects
* exactly the view presented by the connected debugger, nuances and all.
*
* @return a future which completes when the object is stepping
*/
public CompletableFuture<Void> step(TargetStepKind kind);
/**
* Step a single instruction
*
* <p>
* This convenience is exactly equivalent to calling {@code step(TargetStepKind.INSTRUCTION)}
*
* @see #step(TargetStepKind)
* @see TargetStepKind#INTO
*/
public default CompletableFuture<Void> step() {
return step(TargetStepKind.INTO);
}
}

View file

@ -0,0 +1,146 @@
/* ###
* 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.async.TypeSpec;
import ghidra.dbg.DebuggerTargetObjectIface;
import ghidra.dbg.attributes.TargetDataType;
import ghidra.dbg.attributes.TypedTargetObjectRef;
import ghidra.dbg.util.TargetDataTypeConverter;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManager;
/**
* The description of a debugging symbol
*
* @see TargetSymbolNamespace
*/
@DebuggerTargetObjectIface("Symbol")
public interface TargetSymbol<T extends TargetSymbol<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetSymbol<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetSymbol.class;
TypeSpec<TargetSymbol<?>> TYPE = TypeSpec.auto();
String DATA_TYPE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "data_type";
String SIZE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "size";
String NAMESPACE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "namespace";
/**
* Get the type of this symbol
*
* @return a future completing with the type
*/
public default TargetDataType getDataType() {
return getTypedAttributeNowByName(DATA_TYPE_ATTRIBUTE_NAME, TargetDataType.class,
TargetDataType.UNDEFINED1);
}
/**
* Get the type of this symbol converted to a Ghidra data type
*
* @return a future completing with the type
*/
public default CompletableFuture<DataType> getGhidraDataType(
TargetDataTypeConverter converter) {
return converter.convertTargetDataType(getDataType()).thenApply(dt -> dt);
}
/**
* Get the type of this symbol converted to a Ghidra data type
*
* Each call to this variant creates a new {@link TargetDataTypeConverter}, and so does not take
* full advantage of its internal cache.
*
* @see #getGhidraDataType(TargetDataTypeConverter)
*/
public default CompletableFuture<DataType> getGhidraDataType(DataTypeManager dtm) {
return getGhidraDataType(new TargetDataTypeConverter(dtm));
}
/**
* Get the type of this symbol converted to a Ghidra data type, without using a
* {@link DataTypeManager}
*
* It is better to use variants with a {@link DataTypeManager} directly, rather than using no
* manager and cloning to one later. The former will select types suited to the data
* organization of the destination manager. Using no manager and cloning later will use
* fixed-size types.
*
* @see #getGhidraDataType(DataTypeManager)
*/
public default CompletableFuture<DataType> getGhidraDataType() {
return getGhidraDataType((DataTypeManager) null);
}
/**
* Determine whether the symbol has a constant value
*
* Constant symbols include but are not limited to C enumeration constants. Otherwise, the
* symbol's value refers to an address, which stores a presumably non-constant value.
*
* @return true if constant, or false if not or unspecified
*/
public default boolean isConstant() {
return getValue().isConstantAddress();
}
/**
* Get the value of the symbol
*
* If the symbol is a constant, then the returned address will be in the constant space.
*
* @return the address or constant value of the symbol, or {@link Address#NO_ADDRESS} if
* unspecified
*/
public default Address getValue() {
return getTypedAttributeNowByName(VALUE_ATTRIBUTE_NAME, Address.class, Address.NO_ADDRESS);
}
/**
* If known, get the size of the symbol in bytes
*
* The size of a symbol is usually not required at runtime, so a user should be grateful if this
* is known. If it is not known, or the symbol does not have a size, this method returns 0.
*
* @return the size of the symbol, or 0 if unspecified
*/
public default long getSize() {
return getTypedAttributeNowByName(SIZE_ATTRIBUTE_NAME, Long.class, 0L);
}
/**
* Get the namespace for this symbol.
*
* While it is most common for a symbol to be an immediate child of its namespace, that is not
* necessarily the case. This method is a reliable and type-safe means of obtaining that
* namespace.
*
* @return a reference to the namespace
*/
public default TypedTargetObjectRef<? extends TargetSymbolNamespace<?>> getNamespace() {
return getTypedRefAttributeNowByName(NAMESPACE_ATTRIBUTE_NAME, TargetSymbolNamespace.tclass,
null);
}
}

View file

@ -0,0 +1,56 @@
/* ###
* 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.Collection;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebuggerTargetObjectIface;
/**
* A container of symbols
*
* The debugger should present these in as granular of unit as possible. Consider a desktop
* application, for example. The debugger should present each module as a namespace rather than the
* entire target (or worse, the entire session) as a single namespace.
*/
@DebuggerTargetObjectIface("SymbolNamespace")
public interface TargetSymbolNamespace<T extends TargetSymbolNamespace<T>>
extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetSymbolNamespace<Cls> {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetSymbolNamespace.class;
/**
* Get the symbols in this namespace.
*
* While it is most common for symbols to be immediate children of the namespace, that is not
* necessarily the case.
*
* @implNote By default, this method collects all successor symbols ordered by path. Overriding
* that behavior is not yet supported.
* @return the types
*/
default CompletableFuture<? extends Collection<? extends TargetSymbol<?>>> getSymbols() {
return DebugModelConventions.collectSuccessors(this, TargetSymbol.tclass);
}
}

View file

@ -0,0 +1,38 @@
/* ###
* 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 ghidra.dbg.DebuggerTargetObjectIface;
/**
* A marker interface which indicates a thread, usually within a process
*
* This object must be associated with a suitable {@link TargetExecutionStateful}. In most cases,
* the object should just implement it.
*/
@DebuggerTargetObjectIface("Thread")
public interface TargetThread<T extends TargetThread<T>> extends TypedTargetObject<T> {
enum Private {
;
private abstract class Cls implements TargetThread<Cls> {
}
}
String TID_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "tid";
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<Private.Cls> tclass = (Class) TargetThread.class;
}

View file

@ -0,0 +1,29 @@
/* ###
* 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.attributes.TypedTargetObjectRef;
public interface TypedTargetObject<T extends TypedTargetObject<T>>
extends TargetObject, TypedTargetObjectRef<T> {
@Override
@SuppressWarnings("unchecked")
default CompletableFuture<? extends T> fetch() {
return (CompletableFuture<? extends T>) CompletableFuture.completedFuture(this);
}
}

View file

@ -0,0 +1,371 @@
/* ###
* 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.util;
import java.util.*;
import java.util.function.*;
public enum CollectionUtils {
;
public abstract static class AbstractImmutableList<T> extends AbstractList<T> {
@Override
public void add(int index, T element) {
throw new UnsupportedOperationException();
}
@Override
public boolean add(T e) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection<? extends T> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(int index, Collection<? extends T> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public T remove(int index) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeIf(Predicate<? super T> filter) {
throw new UnsupportedOperationException();
}
@Override
protected void removeRange(int fromIndex, int toIndex) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public void replaceAll(UnaryOperator<T> operator) {
throw new UnsupportedOperationException();
}
@Override
public T set(int index, T element) {
throw new UnsupportedOperationException();
}
@Override
public void sort(Comparator<? super T> c) {
throw new UnsupportedOperationException();
}
}
public abstract static class AbstractImmutableSet<T> extends AbstractSet<T> {
@Override
public boolean add(T e) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection<? extends T> c) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
}
public abstract static class AbstractImmutableMap<K, V> extends AbstractMap<K, V> {
}
public static class AbstractEmptyMap<K, V> extends AbstractMap<K, V> {
@Override
public Set<Entry<K, V>> entrySet() {
return Set.of();
}
}
public static class AbstractEmptyList<T> extends AbstractList<T> {
@Override
public T get(int index) {
throw new ArrayIndexOutOfBoundsException(index);
}
@Override
public int size() {
return 0;
}
}
public static class AbstractEmptySet<T> extends AbstractImmutableSet<T> {
@Override
public Iterator<T> iterator() {
return Collections.emptyIterator();
}
@Override
public int size() {
return 0;
}
}
public static class AbstractNList<T> extends AbstractImmutableList<T> {
protected final List<T> wrapped;
@SafeVarargs
public AbstractNList(T... elems) {
this.wrapped = List.of(elems);
}
public AbstractNList(Collection<T> col) {
this.wrapped = List.copyOf(col);
}
@Override
public T get(int index) {
return wrapped.get(index);
}
@Override
public int size() {
return wrapped.size();
}
}
public static class AbstractNSet<T> extends AbstractImmutableSet<T> {
protected final Set<T> wrapped;
@SafeVarargs
public AbstractNSet(T... elems) {
this.wrapped = Set.of(elems);
}
public AbstractNSet(Collection<T> col) {
this.wrapped = Set.copyOf(col);
}
@Override
public Iterator<T> iterator() {
return wrapped.iterator();
}
@Override
public int size() {
return wrapped.size();
}
}
public static class AbstractNMap<K, V> extends AbstractImmutableMap<K, V> {
protected final Map<K, V> wrapped;
public AbstractNMap(Map<K, V> map) {
this.wrapped = Map.copyOf(map);
}
@Override
public Set<Entry<K, V>> entrySet() {
return wrapped.entrySet();
}
}
public static <K, V> Collection<V> getAllExisting(Map<K, V> map, Collection<K> keys) {
List<V> result = new ArrayList<>();
for (K k : keys) {
if (map.containsKey(k)) {
result.add(map.get(k));
}
}
return result;
}
public static class Delta<T, U extends T> {
public static final Delta<?, ?> EMPTY = new Delta<>(Map.of(), Map.of());
public static final BiPredicate<Object, Object> SAME = (a, b) -> a == b;
public static final BiPredicate<Object, Object> EQUAL = Objects::equals;
@SuppressWarnings("unchecked")
public static final <T, U extends T> Delta<T, U> empty() {
return (Delta<T, U>) EMPTY;
}
public static final <T, U extends T> Delta<T, U> create(Map<String, T> removed,
Map<String, U> added) {
return new Delta<>(removed, added);
}
public static final <T, U extends T> Delta<T, U> create(Collection<String> removedKeys,
Map<String, U> added) {
Map<String, T> removedNull = new HashMap<>();
for (String key : removedKeys) {
removedNull.put(key, null);
}
return new Delta<>(removedNull, added);
}
protected static final <T> void retainKeys(Map<String, T> mutable, Collection<String> keys,
Map<String, T> removed) {
for (Iterator<Map.Entry<String, T>> eit = mutable.entrySet().iterator(); eit
.hasNext();) {
Map.Entry<String, T> oldEnt = eit.next();
if (!keys.contains(oldEnt.getKey())) {
removed.put(oldEnt.getKey(), oldEnt.getValue());
eit.remove();
}
}
}
protected static final <T> void removeKeys(Map<String, T> mutable, Collection<String> keys,
Map<String, T> removed) {
for (String r : keys) {
if (mutable.containsKey(r)) {
removed.put(r, mutable.remove(r));
}
}
}
protected static final <T, U extends T> void putEntries(Map<String, T> mutable,
Map<String, U> entries, Map<String, T> removed, Map<String, U> added,
BiPredicate<? super T, ? super U> equals) {
for (Map.Entry<String, U> e : entries.entrySet()) {
String key = e.getKey();
U newVal = e.getValue();
if (!mutable.containsKey(key)) {
mutable.put(key, newVal);
added.put(key, newVal);
continue;
}
T oldVal = mutable.get(key);
if (!equals.test(oldVal, newVal)) {
mutable.put(key, newVal);
removed.put(key, oldVal);
added.put(key, newVal);
}
}
}
public static final <T, U extends T> Delta<T, U> computeAndSet(Map<String, T> mutable,
Map<String, U> desired, BiPredicate<? super T, ? super U> equals) {
Map<String, T> removed = new LinkedHashMap<>();
Map<String, U> added = new LinkedHashMap<>();
retainKeys(mutable, desired.keySet(), removed);
putEntries(mutable, desired, removed, added, equals);
return create(removed, added);
}
/*public static final <T, U extends T> Delta<T, U> computeAndSet(Map<String, T> mutable,
Map<String, U> desired) {
return computeAndSet(mutable, desired, SAME);
}*/
public static final <T, U extends T> Delta<T, U> apply(Map<String, T> mutable,
Collection<String> removed, Map<String, U> added,
BiPredicate<? super T, ? super U> equals) {
if (removed.isEmpty() && added.isEmpty()) {
return empty();
}
Map<String, T> fRemoved = new LinkedHashMap<>();
Map<String, U> fAdded = new LinkedHashMap<>();
removeKeys(mutable, removed, fRemoved);
putEntries(mutable, added, fRemoved, fAdded, equals);
return create(fRemoved, fAdded);
}
/*public static final <T, U extends T> Delta<T, U> apply(Map<String, T> mutable,
Collection<String> removed, Map<String, U> added) {
return apply(mutable, removed, added, SAME);
}*/
public static final void applyToKeys(Set<String> mutable, Collection<String> removed,
Map<String, ?> added) {
mutable.removeAll(removed);
mutable.addAll(added.keySet());
}
public final Map<String, T> removed;
public final Map<String, U> added;
private volatile Set<String> keysRemoved;
// TODO: Moved?
protected Delta(Map<String, T> removed, Map<String, U> added) {
this.removed = removed;
this.added = added;
}
@Override
public String toString() {
return "<Delta removed=" + removed + ", added=" + added + ">";
}
public boolean isEmpty() {
return removed.isEmpty() && added.isEmpty();
}
public Delta<T, U> apply(Map<String, T> mutable, BiPredicate<Object, Object> equals) {
return apply(mutable, removed.keySet(), added, equals);
}
public Delta<T, U> apply(Map<String, T> mutable) {
return apply(mutable, SAME);
}
public void applyToKeys(Set<String> mutable) {
applyToKeys(mutable, removed.keySet(), added);
}
public Set<String> getKeysRemoved() {
if (keysRemoved != null) {
return keysRemoved;
}
Set<String> temp = new LinkedHashSet<>(removed.keySet());
temp.removeAll(added.keySet());
keysRemoved = temp;
return keysRemoved;
}
}
}

View file

@ -0,0 +1,164 @@
/* ###
* 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.util;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Supplier;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.AutoConfigState.ConfigFieldCodec;
import ghidra.framework.plugintool.AutoConfigState.ConfigStateField;
import ghidra.util.Msg;
public interface ConfigurableFactory<T> {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FactoryDescription {
String brief();
String htmlDetails();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FactoryOption {
/**
* The text to display next to the option
*
* @return
*/
String value();
}
public interface Property<T> {
Class<T> getValueClass();
T getValue();
void setValue(T value);
boolean isEnabled();
void setEnabled(boolean enabled);
static <T> Property<T> fromAccessors(Class<T> cls, Supplier<T> getter, Consumer<T> setter) {
return new Property<T>() {
boolean enabled = true;
@Override
public Class<T> getValueClass() {
return cls;
}
@Override
public T getValue() {
return getter.get();
}
@Override
public void setValue(T value) {
setter.accept(value);
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
};
}
}
/**
* Build the object
*
* Note, if the object requires some initialization, esp., if that such methods are not exposed
* via {@code T}, then this method should invoke them. Preferably, the returned future should
* not complete until initialization is complete.
*
* @return a future which completes with the built and initialized object.
*/
CompletableFuture<? extends T> build();
default String getBrief() {
FactoryDescription annot = getClass().getAnnotation(FactoryDescription.class);
if (annot == null) {
return "Class: " + getClass().getSimpleName();
}
return annot.brief();
}
default String getHtmlDetails() {
FactoryDescription annot = getClass().getAnnotation(FactoryDescription.class);
if (annot == null) {
return "Un-described factory: " + getClass().getName();
}
return annot.htmlDetails();
}
default Map<String, Property<?>> getOptions() {
Map<String, Property<?>> result = new LinkedHashMap<>();
for (Field f : getClass().getFields()) {
FactoryOption annot = f.getAnnotation(FactoryOption.class);
if (annot == null) {
continue;
}
try {
result.put(annot.value(), (Property<?>) f.get(this));
}
catch (Throwable e) {
Msg.error(this, "Could not process option: " + f.getName(), e);
}
}
return result;
}
default void writeConfigState(SaveState saveState) {
for (Entry<String, Property<?>> opt : getOptions().entrySet()) {
Property<?> property = opt.getValue();
@SuppressWarnings({ "unchecked", "rawtypes" })
ConfigFieldCodec<Object> codec = (ConfigFieldCodec) ConfigStateField
.getCodecByType(property.getValueClass());
if (codec == null) {
continue;
}
codec.write(saveState, opt.getKey(), property.getValue());
}
}
default void readConfigState(SaveState saveState) {
for (Entry<String, Property<?>> opt : getOptions().entrySet()) {
@SuppressWarnings({ "unchecked", "rawtypes" })
Property<Object> property = (Property) opt.getValue();
ConfigFieldCodec<?> codec = ConfigStateField
.getCodecByType(property.getValueClass());
if (codec == null) {
continue;
}
property.setValue(codec.read(saveState, opt.getKey(), null));
}
}
}

View file

@ -0,0 +1,71 @@
/* ###
* 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.util;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.stream.Collectors;
import ghidra.dbg.attributes.TargetStringList;
import ghidra.dbg.attributes.TargetStringList.MutableTargetStringList;
import ghidra.dbg.target.TargetMemory;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.address.AddressSpace;
public enum ConversionUtils {
;
/**
* Converts a given big integer into a 2's-complement big-endian byte array
*
* If the value requires fewer than {@code length} bytes, the more-significant bytes of the
* result are filled according to the sign of the value. If the value requires more than
* {@code length} bytes, the more-significant bytes of the value are truncated.
*
* @param length the number of bytes in the output byte array
* @param value the input value to convert
* @return the resulting byte array
*/
public static byte[] bigIntegerToBytes(int length, BigInteger value) {
byte[] bytes = value.toByteArray();
if (length == bytes.length) {
return bytes;
}
byte[] result = new byte[length];
if (value.signum() < 0) {
Arrays.fill(result, (byte) -1);
}
if (length < bytes.length) {
System.arraycopy(bytes, bytes.length - length, result, 0, length);
}
else {
System.arraycopy(bytes, 0, result, length - bytes.length, bytes.length);
}
return result;
}
/**
* @deprecated Since removing getAddressFactory from {@link TargetMemory}, I don't think this is
* needed anymore.
*/
@Deprecated(forRemoval = true)
public static TargetStringList addressFactoryToSpaceNameSet(AddressFactory factory) {
return Arrays.asList(factory.getAddressSpaces())
.stream()
// TODO .filter
.map(AddressSpace::getName)
.collect(Collectors.toCollection(MutableTargetStringList::new));
}
}

View file

@ -0,0 +1,94 @@
/* ###
* 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.util;
import java.util.LinkedHashMap;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
/**
* A means of dynamically binding method calls to one of many registered handlers by sub-type
*
* A handler is a method accepting a sub-type and a custom argument. The handler can be assigned to
* handle any sub-type of its first argument. When the {@link #handle(Object, Object)} method of
* this map is called, it invokes the mapped handler, or throws {@link IllegalArgumentException} if
* there is not a handler for the given object type. The passed type must exactly match the type of
* the registered handler. This abstraction is a useful replacement for {@code instanceof} checks in
* an {@code if-else-if} series.
*
* @param <T> the root type for all handlers and passed objects
* @param <A> the type of custom additional argument
* @param <R> the type of result returned by the handler
*/
public class HandlerMap<T, A, R> {
private LinkedHashMap<Class<? extends T>, BiFunction<?, ? super A, ? extends R>> map =
new LinkedHashMap<>();
/**
* Add a handler to the map
*
* @param cls the type assigned to this handler
* @param handler the handler
* @return the previous handler, if any, otherwise {@code null}
*/
@SuppressWarnings("unchecked")
public <U extends T> BiFunction<? super U, ? super A, ? extends R> put(Class<U> cls,
BiFunction<? super U, ? super A, ? extends R> handler) {
return (BiFunction<? super U, ? super A, ? extends R>) map.put(cls, handler);
}
/**
* Add a void handler to the map
*
* Note that this wraps the {@link BiConsumer} in a {@link BiFunction}. If the handler is
* replaced, the {@link BiFunction} is returned instead of the {@link BiConsumer}.
*
* @param cls the type assigned to this handler
* @param handler the handler
* @return the previous handler, if any, otherwise {@code null}
*/
public <U extends T> BiFunction<? super U, ? super A, ? extends R> putVoid(Class<U> cls,
BiConsumer<? super U, ? super A> handler) {
return put(cls, (u, a) -> {
handler.accept(u, a);
return null;
});
}
/**
* Invoke the a handler for the given object
*
* The given object's type is reflected to determine the appropriate handler to call. The
* object's type must exactly match one of the handlers' assigned types. Being a subclass of an
* assigned type does not constitute a match. The type and the custom argument are then passed
* to the handler. If there is no match, an {@link IllegalArgumentException} is thrown.
*
* @param t the object to handle
* @param a the custom additional argument, often {@code null}
* @return a future
* @throws IllegalArgumentException if no handler is assigned to the given object's type
*/
public R handle(T t, A a) {
@SuppressWarnings("unchecked")
BiFunction<T, A, ? extends R> function =
(BiFunction<T, A, ? extends R>) map.get(t.getClass());
if (function != null) {
//Msg.debug(this, "Handling: " + t);
return function.apply(t, a);
}
throw new IllegalArgumentException("No handler for " + t);
}
}

View file

@ -0,0 +1,54 @@
/* ###
* 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.util;
import java.util.*;
import java.util.function.Predicate;
public class PathMatcher implements PathPredicates {
protected final Set<PathPattern> patterns = new HashSet<>();
public void addPattern(List<String> pattern) {
patterns.add(new PathPattern(pattern));
}
/**
* TODO: We could probably do a lot better, esp. for many patterns, by using a trie.
*/
protected boolean anyPattern(Predicate<PathPattern> pred) {
for (PathPattern p : patterns) {
if (pred.test(p)) {
return true;
}
}
return false;
}
@Override
public boolean matches(List<String> path) {
return anyPattern(p -> p.matches(path));
}
@Override
public boolean successorCouldMatch(List<String> path) {
return anyPattern(p -> p.successorCouldMatch(path));
}
@Override
public boolean ancestorMatches(List<String> path) {
return anyPattern(p -> p.ancestorMatches(path));
}
}

View file

@ -0,0 +1,103 @@
/* ###
* 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.util;
import java.util.List;
import java.util.Objects;
public class PathPattern implements PathPredicates {
private final List<String> pattern;
/**
* TODO: This can get more sophisticated if needed, but for now, I don't think we even need
* regular expressions. Either we care about a path element, or we don't.
*
* This takes a list of path keys as a means of matching paths. The empty key serves as a
* wildcard accepting all keys in that position, e.g., the following matches all elements within
* {@code Processes}:
*
* {@code List.of("Processes", "[]")}
*
* This should still be compatible with {@link PathUtils#parse(String)} and
* {@link PathUtils#toString(List)} allowing the last example to be expressed as
* {@code PathUtils.parse("Processes[]")}.
*
* @param pattern a list of path elements
*/
public PathPattern(List<String> pattern) {
this.pattern = List.copyOf(pattern);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PathPattern)) {
return false;
}
PathPattern that = (PathPattern) obj;
return Objects.equals(this.pattern, that.pattern);
}
@Override
public int hashCode() {
return pattern.hashCode();
}
protected boolean keyMatches(String pat, String key) {
if (key.equals(pat)) {
return true;
}
if ("[]".equals(pat) && PathUtils.isIndex(key)) {
return true;
}
if ("".equals(pat) && PathUtils.isName(pat)) {
return true;
}
return false;
}
protected boolean matchesUpTo(List<String> path, int length) {
for (int i = 0; i < length; i++) {
if (!keyMatches(pattern.get(i), path.get(i))) {
return false;
}
}
return true;
}
@Override
public boolean matches(List<String> path) {
if (path.size() != pattern.size()) {
return false;
}
return matchesUpTo(path, path.size());
}
@Override
public boolean successorCouldMatch(List<String> path) {
if (path.size() > pattern.size()) {
return false;
}
return matchesUpTo(path, path.size());
}
@Override
public boolean ancestorMatches(List<String> path) {
if (path.size() < pattern.size()) {
return false;
}
return matchesUpTo(path, pattern.size());
}
}

View file

@ -0,0 +1,52 @@
/* ###
* 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.util;
import java.util.List;
public interface PathPredicates {
/**
* Check if the entire path passes
*
* @param path the path to check
* @return true if it matches, false otherwise
*/
boolean matches(List<String> path);
/**
* Check if the given path <em>could</em> have a matching successor
*
* This essentially checks if the given path is a viable prefix to the matcher.
*
* @implNote this method could become impractical for culling queries if we allow too
* sophisticated of patterns. Notably, to allow an "any number of keys" pattern, e.g.,
* akin to {@code /src/**{@literal /}*.c} in file system path matchers. Anything
* starting with "src" could have a successor that matches.
*
*
* @param path the path (prefix) to check
* @return true if a successor could match, false otherwise
*/
boolean successorCouldMatch(List<String> path);
/**
* Check if the given path has an ancestor that matches
*
* @param path the path to check
* @return true if an ancestor matches, false otherwise
*/
boolean ancestorMatches(List<String> path);
}

View file

@ -0,0 +1,563 @@
/* ###
* 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.util;
import java.nio.CharBuffer;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.dbg.target.TargetObject;
/**
* A collection of utilities for examining and manipulating debug object paths
*
* <p>
* Paths are merely lists of strings where each part indicates an attribute name or element index.
* Element indices are enclosed in brackets {@code []}, may be multidimensional, and may be any type
* encoded as a string. Method invocations contain {@code (...)}. The root object has the empty
* path.
*/
public enum PathUtils {
;
/**
* Comparators for keys, i.e., strings in a path
*/
public enum TargetObjectKeyComparator implements Comparator<String> {
/**
* Sort keys by attribute name, lexicographically.
*/
ATTRIBUTE {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
},
/**
* Sort keys by element index.
*
* <p>
* Element indices may be multidimensional, in which case the dimensions are separated by
* commas, and sorting prioritizes the left-most dimensions. Where indices (or dimensions
* thereof) appear to be numeric, they are sorted as such. Otherwise, they are sorted
* lexicographically. Numeric types can be encoded in hexadecimal. While decimal is typical
* you may run into difficulties if those numbers are too large, as the implementation must
* assume numeric types are hexadecimal.
*
* @implNote The only way I can think to resolve the numeric encoding issue is to examine
* all keys before even selecting a comparator. As is, a comparator can only see
* two keys at a time, and has no context to what it's actually sorting.
*/
ELEMENT {
@Override
public int compare(String o1, String o2) {
String[] p1 = o1.split(",");
String[] p2 = o2.split(",");
int min = Math.min(p1.length, p2.length);
for (int i = 0; i < min; i++) {
int c = ELEMENT_DIM.compare(p1[i], p2[i]);
if (c != 0) {
return c;
}
}
return Integer.compare(p1.length, p2.length);
}
},
/**
* Sort keys by element index, allowing only one dimension.
*
* <p>
* Please use {@link #ELEMENT}, unless you really know you need this instead.
*/
ELEMENT_DIM {
@Override
public int compare(String o1, String o2) {
Long l1 = null;
Long l2 = null;
try {
l1 = Long.parseLong(o1, 16);
}
catch (NumberFormatException e) {
}
try {
l2 = Long.parseLong(o2, 16);
}
catch (NumberFormatException e) {
}
if (l1 != null && l2 != null) {
return l1.compareTo(l2);
}
if (l1 != null) { // Want numbers first, so l1 < l2
return -1;
}
if (l2 != null) {
return 1;
}
return o1.compareTo(o2);
}
},
/**
* Sort keys by element or index as appropriate, placing elements first.
*/
CHILD {
@Override
public int compare(String o1, String o2) {
boolean ii1 = o1.startsWith("[") && o1.endsWith("]");
boolean ii2 = o2.startsWith("[") && o2.endsWith("]");
if (ii1 && ii2) {
return ELEMENT.compare(o1.substring(1, o1.length() - 1),
o2.substring(1, o2.length() - 1));
}
if (ii1) {
return -1;
}
if (ii2) {
return 1;
}
return ATTRIBUTE.compare(o1, o2);
}
}
}
/**
* Comparators for paths
*/
public enum PathComparator implements Comparator<List<String>> {
/**
* Sort paths by key, prioritizing the left-most, i.e., top-most, keys.
*
* <p>
* If one path is a prefix to the other, the prefix is "less than" the other.
*/
KEYED {
@Override
public int compare(List<String> o1, List<String> o2) {
int min = Math.min(o1.size(), o2.size());
for (int i = 0; i < min; i++) {
String e1 = o1.get(i);
String e2 = o2.get(i);
int c = e1.compareTo(e2);
if (c != 0) {
return c;
}
}
return Integer.compare(o1.size(), o2.size());
}
},
/**
* Sort paths by length, longest first, then as in {@link #KEYED}.
*/
LONGEST_FIRST {
@Override
public int compare(List<String> o1, List<String> o2) {
int c;
c = Integer.compare(o2.size(), o1.size());
if (c != 0) {
return c;
}
return KEYED.compare(o1, o2);
}
}
}
protected static class PathParser {
protected final static Pattern LBRACKET = Pattern.compile("\\[");
protected final static Pattern RBRACKET = Pattern.compile("\\]");
protected final static Pattern BRACKETED = Pattern.compile("\\[.*?\\]");
protected final static Pattern LPAREN = Pattern.compile("\\(");
protected final static Pattern RPAREN = Pattern.compile("\\)");
protected final CharBuffer buf;
protected final Pattern sep;
protected final List<String> result = new ArrayList<>();
protected PathParser(CharSequence path, String sepRE) {
buf = CharBuffer.wrap(path);
sep = Pattern.compile(sepRE);
}
protected String match(Pattern pat) {
Matcher mat = pat.matcher(buf);
if (!mat.lookingAt()) {
throw new IllegalArgumentException("Expecting " + pat + ", but had " + buf);
}
String tok = mat.group();
int length = mat.end() - mat.start();
buf.position(buf.position() + length);
return tok;
}
protected void advanceParenthesized() {
while (buf.hasRemaining()) {
if (RPAREN.matcher(buf).lookingAt()) {
buf.get();
break;
}
else if (LPAREN.matcher(buf).lookingAt()) {
buf.get();
advanceParenthesized();
}
else {
buf.get();
}
}
}
protected String parseName() {
int p = buf.position();
while (buf.hasRemaining()) {
if (sep.matcher(buf).lookingAt()) {
break;
}
else if (LBRACKET.matcher(buf).lookingAt()) {
break;
}
else if (LPAREN.matcher(buf).lookingAt()) {
buf.get();
advanceParenthesized();
}
else {
buf.get();
}
}
int e = buf.position();
buf.position(p);
String tok = buf.subSequence(0, e - p).toString();
buf.position(e);
return tok;
}
protected String parseNext() {
if (sep.matcher(buf).lookingAt()) {
match(sep);
return parseName();
}
if (LBRACKET.matcher(buf).lookingAt()) {
return match(BRACKETED);
}
throw new IllegalArgumentException(
"Expected " + sep + " or " + LBRACKET + ", but had " + buf);
}
protected List<String> parse() {
String first = parseName();
if (first.length() != 0) {
result.add(first);
}
while (buf.hasRemaining()) {
result.add(parseNext());
}
return result;
}
}
public static List<String> parse(String path, String sepRE) {
return new PathParser(path, sepRE).parse();
}
public static List<String> parse(String path) {
return parse(path, "\\.");
}
public static String toString(List<String> path, String sep) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (String e : path) {
if (!isIndex(e) && !first) {
sb.append(sep);
}
first = false;
sb.append(e);
}
return sb.toString();
}
public static String toString(List<String> path) {
return toString(path, ".");
}
/**
* Extend a path with a given key, usually attribute name
*
* @param path the parent path
* @param key the key to append
* @return the resulting extended path
*/
public static List<String> extend(List<String> path, String key) {
List<String> result = new ArrayList<>(path.size() + 1);
result.addAll(path);
result.add(key);
return List.copyOf(result);
}
/**
* Extend a path with another sub-path
*
* @param path the parent path
* @param sub the sub-path to the successor
* @return the resulting extended path
*/
public static List<String> extend(List<String> path, List<String> sub) {
List<String> result = new ArrayList<>(path.size() + sub.size());
result.addAll(path);
result.addAll(sub);
return List.copyOf(result);
}
/**
* Encode the given index in decimal, without brackets
*
* @param i the numeric index
* @return the encoded index
*/
public static String makeIndex(long i) {
return Long.toString(i);
}
/**
* Encode the given index in decimal, without brackets
*
* @param i the numeric index
* @return the encoded index
*/
public static String makeIndex(int i) {
return Integer.toString(i);
}
/**
* Encode the given index as a key
*
* <p>
* When indexing elements, no brackets are needed. The brackets become necessary when used as a
* key, e.g., when specifying an index within a path, or as keys in a map of all children.
*
* @param index the index
* @return the key, specifying an element.
*/
public static String makeKey(String index) {
return "[" + index + "]";
}
/**
* Extend a path with a given index
*
* <p>
* This is equivalent to calling {@code extend(path, makeKey(index))}.
*
* @param path the parent path
* @param index the index to append
* @return the resulting extended path
*/
public static List<String> index(List<String> path, String index) {
return extend(path, makeKey(index));
}
/**
* Obtain the parent path of this path
*
* <p>
* This merely removes the last element of the path. If the given path refers to the root,
* {@code null} is returned.
*
* @param path the child path
* @return the parent path or {@code null}
*/
public static List<String> parent(List<String> path) {
if (path.isEmpty()) {
return null;
}
return path.subList(0, path.size() - 1);
}
/**
* Obtain the key of the object to which the given path refers
*
* <p>
* This is merely the final right-most key in the path. If the given path refers to the root,
* {@code null} is returned.
*
* @param path the object's path
* @return the key of the object
*/
public static String getKey(List<String> path) {
if (path.isEmpty()) {
return null;
}
return path.get(path.size() - 1);
}
/**
* Parse an index value from a key
*
* <p>
* Where key is the form {@code [index]}, this merely returns {@code index}.
*
* @param key the key
* @return the index
* @throws IllegalArgumentException if key is not of the required form
*/
public static String parseIndex(String key) {
if (isIndex(key)) {
return key.substring(1, key.length() - 1);
}
throw new IllegalArgumentException("Index keys must be of the form '[index]'. Got " + key);
}
/**
* Obtain the index of the object to which the given path refers
*
* <p>
* This merely parses the index from the final right-most key in the path. It is roughly
* equivalent to calling {@code parseIndex(getKey(path))}.
*
* @see #getKey(List)
* @see #parseIndex(String)
* @see #index(List, String)
* @param path the object's path
* @return the index of the object
* @throws IllegalArgumentException if key is not of the required form
*/
public static String getIndex(List<String> path) {
String key = getKey(path);
return key == null ? null : parseIndex(key);
}
/**
* Check if the given key is a bracketed index
*
* @param key the key to check
* @return true if it is an index
*/
public static boolean isIndex(String key) {
return key == null ? false : key.startsWith("[") && key.endsWith("]");
}
/**
* Check if the final right-most key of the given path is a bracketed index
*
* @param path the path to check
* @return true if the final key is an index
*/
public static boolean isIndex(List<String> path) {
return isIndex(getKey(path));
}
/**
* Check if the given key is an attribute name, i.e., not an index
*
* @param key the key to check
* @return true if it is an attribute name
*/
public static boolean isName(String key) {
return key == null ? false : !isIndex(key);
}
/**
* Check if the final right-most key of the given path is an attribute name
*
* @param path the path to check
* @return true if the final key is an attribute name
*/
public static boolean isName(List<String> path) {
return isName(getKey(path));
}
/**
* Check if the first path refers to an ancestor of the second path
*
* <p>
* Equivalently, check if the second path refers to a successor of the second path
*
* <p>
* This effectively checks that ancestor is a prefix of successor. By this definition, every
* path is technically an ancestor and successor of itself. If you do not desire that behavior,
* check that the two paths are not equal first.
*
* @param ancestor the first path
* @param successor the second path
* @return true if ancestor is, in fact, an ancestor of successor
*/
public static boolean isAncestor(List<String> ancestor, List<String> successor) {
if (ancestor.size() > successor.size()) {
return false;
}
return Objects.equals(ancestor, successor.subList(0, ancestor.size()));
}
/**
* Check whether a given object-valued attribute is a link.
*
* <p>
* Consider an object {@code O} with an object-valued attribute {@code a}. {@code a}'s value is
* a link, iff its path does <em>not</em> match that generated by extending {@code O}'s path
* with {@code a}'s name.
*
* @param parentPath the path of the parent object of the given attribute
* @param name the name of the given attribute
* @param attributePath the canonical path of the attribute's object value
* @return true if the value is a link (i.e., it's object has a different path)
*/
public static boolean isLink(List<String> parentPath, String name, List<String> attributePath) {
return !Objects.equals(extend(parentPath, name), attributePath);
}
/**
* Check whether a given attribute should be displayed.
*
* @param key the key of the given attribute
*/
public static boolean isHidden(String key) {
return key.startsWith(TargetObject.PREFIX_INVISIBLE);
}
/**
* Check whether a given path key represents a method invocation.
*
* <p>
* This really just checks if the key ends in {@code )}.
*
* @param key the key
* @return true if an invocation, false if not
*/
public static boolean isInvocation(String key) {
return key.endsWith(")");
}
/**
* Get the name and parameters expression of the method from an invocation.
*
* <p>
* TODO: We need a more formal model for method invocation in paths. Probably shouldn't return a
* map entry once we have that specified, either.
*
* @param name the name, which must be in the form {@code method(params)}
* @return the method name
*/
public static Map.Entry<String, String> parseInvocation(String name) {
if (!isInvocation(name)) {
throw new IllegalArgumentException(
"Invocation keys must be of the form 'method(params)'. Got " + name);
}
int i = name.indexOf('(');
if (i == -1) {
throw new IllegalArgumentException(
"Invocation keys must be of the form 'method(params)'. Got " + name);
}
return Map.entry(name.substring(0, i), name.substring(i + 1, name.length() - 1));
}
}

View file

@ -0,0 +1,65 @@
/* ###
* 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.util;
import java.util.LinkedHashMap;
import java.util.Map;
import utility.function.ExceptionalFunction;
/**
* A utility for parsing strings into objects
*
* This utility is suited to controlling a command-line application via its input and output
* streams. The application is expected to display command results, statuses, events, etc., by
* printing one line per item. This utility provides a straightforward mechanism for selecting a
* line parser by examining the first characters.
*
* The prefix-to-constructor map is populated by calling {@link #put(Object, ExceptionalFunction)}
* and other methods inherited from {@link Map}. The function is typically a constructor reference.
* When parsing a line, the prefixes are examined in the order added. If a matching prefix is found,
* it is removed from the line and the tail is passed to the corresponding constructor. If the tail
* starts with a comma, it is also removed. The constructed instance is then returned to the caller.
*
* @param <T> the base class for the parser types
* @param <E> the base class for checked exceptions that must be handled by callers
*/
public class PrefixMap<T, E extends Exception>
extends LinkedHashMap<String, ExceptionalFunction<? super String, ? extends T, E>> {
/**
* Construct a representation of the given line by selecting and invoking a constructor
*
* @param line the line to parse
* @return the object of the mapped type as parsed by its constructor or {@code null} if the
* line does not match a prefix
* @throws E if the constructor throws an exception
*/
public T construct(String line) throws E {
for (java.util.Map.Entry<String, ExceptionalFunction<? super String, ? extends T, E>> ent : entrySet()) {
String prefix = ent.getKey();
if (line.startsWith(prefix)) {
String tail = line.substring(prefix.length());
if (tail.startsWith(",")) {
tail = tail.substring(1);
}
ExceptionalFunction<? super String, ? extends T, E> cons = ent.getValue();
return cons.apply(tail);
}
}
return null;
}
}

View file

@ -0,0 +1,141 @@
/* ###
* 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.util;
import java.util.ArrayList;
import java.util.List;
public class ShellUtils {
enum State {
NORMAL, NORMAL_ESCAPE, DQUOTE, DQUOTE_ESCAPE, SQUOTE, SQUOTE_ESCAPE;
}
public static List<String> parseArgs(String args) {
List<String> argsList = new ArrayList<>();
StringBuilder curArg = new StringBuilder();
State state = State.NORMAL;
for (int i = 0; i < args.length(); i++) {
char c = args.charAt(i);
switch (state) {
case NORMAL:
switch (c) {
case '\\':
state = State.NORMAL_ESCAPE;
break;
case '"':
state = State.DQUOTE;
break;
case '\'':
state = State.SQUOTE;
break;
case ' ':
argsList.add(curArg.toString());
curArg.setLength(0);
break;
default:
curArg.append(c);
}
break;
case NORMAL_ESCAPE:
curArg.append(c);
state = State.NORMAL;
break;
case DQUOTE:
switch (c) {
case '\\':
state = State.DQUOTE_ESCAPE;
break;
case '"':
state = State.NORMAL;
break;
default:
curArg.append(c);
}
break;
case DQUOTE_ESCAPE:
curArg.append(c);
state = State.DQUOTE;
break;
case SQUOTE:
switch (c) {
case '\\':
state = State.SQUOTE_ESCAPE;
break;
case '\'':
state = State.NORMAL;
break;
default:
curArg.append(c);
}
case SQUOTE_ESCAPE:
curArg.append(c);
state = State.SQUOTE;
break;
default:
throw new AssertionError("Shouldn't be here!");
}
}
switch (state) {
case NORMAL:
if (curArg.length() != 0) {
argsList.add(curArg.toString());
}
break;
case DQUOTE:
case SQUOTE:
throw new IllegalArgumentException("Unterminated string");
case NORMAL_ESCAPE:
case DQUOTE_ESCAPE:
case SQUOTE_ESCAPE:
throw new IllegalArgumentException("Incomplete escaped character");
default:
throw new AssertionError("Shouldn't be here!");
}
return argsList;
}
public static String generateLine(List<String> args) {
if (args.isEmpty()) {
return "";
}
StringBuilder line = new StringBuilder(args.get(0));
for (int i = 1; i < args.size(); i++) {
String a = args.get(i);
if (a.contains(" ")) {
if (a.contains("\"")) {
if (a.contains("'")) {
line.append(" \"");
line.append(a.replace("\"", "\\\""));
line.append("\"");
continue;
}
line.append(" '");
line.append(a);
line.append("'");
continue;
}
line.append(" \"");
line.append(a);
line.append("\"");
continue;
}
line.append(" ");
line.append(a);
}
return line.toString();
}
}

View file

@ -0,0 +1,519 @@
/* ###
* 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.util;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import ghidra.async.AsyncFence;
import ghidra.dbg.attributes.*;
import ghidra.dbg.target.TargetDataTypeMember;
import ghidra.dbg.target.TargetNamedDataType;
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
public class TargetDataTypeConverter {
protected static class ConvertedMember {
protected final TargetDataTypeMember<?> member;
protected final DataType type;
public ConvertedMember(TargetDataTypeMember<?> member, DataType type) {
this.member = member;
this.type = type;
}
}
protected abstract static class TwoPhased<T> {
public static <T> TwoPhased<T> completedTwo(T val) {
TwoPhased<T> result = new TwoPhased<>() {
@Override
protected void doStart() {
completeOne(val);
completeTwo();
}
};
result.start();
return result;
}
protected final CompletableFuture<T> one = new CompletableFuture<>();
protected final CompletableFuture<T> two = new CompletableFuture<>();
protected final Set<TwoPhased<?>> deps = new HashSet<>();
protected boolean started = false;
public void start() {
synchronized (this) {
if (started) {
return;
}
started = true;
}
doStart();
}
protected abstract void doStart();
protected void chainExc(CompletableFuture<?> chain) {
chain.exceptionally(ex -> {
completeExceptionally(ex);
return null;
});
}
protected void chainExc(TwoPhased<?> chain) {
chainExc(chain.one);
chainExc(chain.two);
}
public void completeOne(T val) {
one.complete(val);
}
public void completeTwo() {
if (!one.isDone()) {
throw new IllegalStateException("Phase one hasn't completed");
}
try {
two.complete(one.get());
}
catch (InterruptedException e) {
throw new AssertionError(e);
}
catch (ExecutionException e) {
two.completeExceptionally(e.getCause());
}
}
public void completeExceptionally(Throwable ex) {
one.completeExceptionally(ex);
two.completeExceptionally(ex);
}
public <U> TwoPhased<U> thenApply(Function<? super T, ? extends U> func) {
TwoPhased<T> tp = this;
return new TwoPhased<>() {
@Override
protected void doStart() {
deps.add(tp);
chainExc(tp.one.thenAccept(t -> completeOne(func.apply(t))));
chainExc(tp.two.thenAccept(__ -> completeTwo()));
}
};
}
private void collectDeps(Set<TwoPhased<?>> allDeps) {
for (TwoPhased<?> d : deps) {
if (allDeps.add(d)) {
d.collectDeps(allDeps);
}
}
}
protected CompletableFuture<T> depTwos() {
Set<TwoPhased<?>> allDeps = new HashSet<>();
collectDeps(allDeps);
return CompletableFuture
.allOf(allDeps.stream().map(tp -> tp.two).toArray(CompletableFuture[]::new))
.thenCompose(__ -> two);
}
}
protected final DataTypeManager dtm;
protected final Map<TargetDataType, TwoPhased<? extends DataType>> types = new HashMap<>();
protected boolean explainedOffsetDisagreement = false;
protected class TwoPhasedComposite<T extends CompositeDataTypeImpl>
extends TwoPhased<T> {
protected final T type;
protected final TargetNamedDataType<?> tNamed;
protected final Map<String, ConvertedMember> subs =
new TreeMap<>(TargetObjectKeyComparator.ELEMENT);
public TwoPhasedComposite(T type, TargetNamedDataType<?> tNamed) {
this.type = type;
this.tNamed = tNamed;
}
@Override
protected synchronized void doStart() {
one.complete(type);
try {
chainExc(tNamed.getMembers().thenAccept(this::procMembers));
}
catch (Throwable e) {
completeExceptionally(e);
}
}
private void procMembers(Collection<? extends TargetDataTypeMember<?>> members) {
// TODO: Figure out attributes
AsyncFence fence = new AsyncFence();
for (TargetDataTypeMember<?> f : members) {
TwoPhased<? extends DataType> dep = convertTwoPhased(f.getDataType());
deps.add(dep);
fence.include(dep.two.thenAccept(tField -> {
subs.put(f.getIndex(), new ConvertedMember(f, tField));
}));
}
chainExc(fence.ready().thenAccept(this::procSubs));
}
protected void procSubs(Void __) {
for (Map.Entry<String, ConvertedMember> s : subs.entrySet()) {
ConvertedMember conv = s.getValue();
DataTypeComponent component =
type.add(conv.type, -1, conv.member.getMemberName(), null);
long fOff = conv.member.getOffset();
int cOff = component.getOffset();
if (fOff != -1 && fOff != cOff) {
Msg.warn(this, "Offset disagreement during conversion of " +
conv.member + ". " + fOff + " != " + cOff);
explainOffsetDisagreement();
}
}
completeTwo();
}
}
public TargetDataTypeConverter() {
this(null);
}
public TargetDataTypeConverter(DataTypeManager dtm) {
this.dtm = dtm;
}
protected synchronized void explainOffsetDisagreement() {
if (!explainedOffsetDisagreement) {
explainedOffsetDisagreement = true;
Msg.warn(this, "Offset disagreement happens likely because the destination " +
"data type manager has a pointer size different than the source target.");
}
}
public CompletableFuture<? extends DataType> convertTargetDataType(TargetDataType type) {
return convertTwoPhased(type).depTwos();
}
protected TwoPhased<? extends DataType> convertTwoPhased(TargetDataType type) {
TwoPhased<? extends DataType> conv;
synchronized (types) {
conv = types.get(type);
if (conv != null) {
return conv;
}
conv = doConvertTargetDataType(type);
types.put(type, conv);
}
conv.start();
return conv;
}
/**
* TODO
*
* Diagnostic only, please.
*
* @return
*/
public Set<TargetDataType> getPendingOne() {
synchronized (types) {
return types.entrySet()
.stream()
.filter(e -> !e.getValue().one.isDone())
.map(e -> e.getKey())
.collect(Collectors.toSet());
}
}
/**
* TODO
*
* Diagnostic only, please.
*
* @return
*/
public Set<TargetDataType> getPendingTwo() {
synchronized (types) {
return types.entrySet()
.stream()
.filter(e -> !e.getValue().two.isDone())
.map(e -> e.getKey())
.collect(Collectors.toSet());
}
}
protected TwoPhased<? extends DataType> doConvertTargetDataType(TargetDataType type) {
if (type instanceof TargetNamedDataTypeRef<?>) {
TargetNamedDataTypeRef<?> ref = (TargetNamedDataTypeRef<?>) type;
return convertTargetNamedDataTypeRef(ref);
}
if (type instanceof TargetArrayDataType) {
TargetArrayDataType tArray = (TargetArrayDataType) type;
return convertTargetArrayDataType(tArray);
}
if (type instanceof TargetBitfieldDataType) {
TargetBitfieldDataType tBitfield = (TargetBitfieldDataType) type;
return convertTargetBitfieldDataType(tBitfield);
}
if (type instanceof TargetPointerDataType) {
TargetPointerDataType tPointer = (TargetPointerDataType) type;
return convertTargetPointerDataType(tPointer);
}
if (type instanceof TargetPrimitiveDataType) {
TargetPrimitiveDataType tPrimitive = (TargetPrimitiveDataType) type;
return convertTargetPrimitiveDataType(tPrimitive);
}
throw new AssertionError("Do not know how to convert " + type);
}
protected TwoPhased<? extends DataType> convertTargetNamedDataTypeRef(
TargetNamedDataTypeRef<?> ref) {
return new TwoPhased<>() {
final Consumer<TargetNamedDataType<?>> PROC_REF = this::procRef;
@Override
protected void doStart() {
chainExc(ref.fetch().thenAccept(PROC_REF));
}
private void procRef(TargetNamedDataType<?> type) {
TwoPhased<? extends DataType> dep = convertNamedDataType(type);
dep.start();
deps.add(dep);
chainExc(dep.one.thenAccept(this::procOne));
chainExc(dep.two.thenAccept(this::procTwo));
}
private void procOne(DataType type) {
completeOne(type);
}
private void procTwo(DataType type) {
completeTwo();
}
};
}
protected TwoPhased<? extends DataType> convertNamedDataType(
TargetNamedDataType<?> type) {
/**
* NOTE: Convention is named data types are each indexed (in the parent namespace) with its
* defining keyword (or something close), e.g., "struct myStruct", not just "myStruct".
* Whatever the case, the actual type name cannot contain spaces and should be the final
* space-separated token in its index. It can be the sole token, but the index must be
* unique within the namespace.
*/
String parts[] = type.getIndex().split("\\s+");
String name = parts[parts.length - 1];
switch (type.getKind()) {
case ENUM:
return convertTargetEnumDataType(name, type);
case FUNCTION:
return convertTargetFunctionDataType(name, type);
case STRUCT:
return convertTargetStructDataType(name, type);
case TYPEDEF:
return convertTargetTypedefDataType(name, type);
case UNION:
return convertTargetUnionDataType(name, type);
default:
throw new AssertionError("Do not know how to convert " + type);
}
}
protected TwoPhased<EnumDataType> convertTargetEnumDataType(String name,
TargetNamedDataType<?> tEnum) {
return new TwoPhased<>() {
final EnumDataType type =
new EnumDataType(CategoryPath.ROOT, name, tEnum.getTypedAttributeNowByName(
TargetNamedDataType.ENUM_BYTE_LENGTH_ATTRIBUTE_NAME, Integer.class, 4), dtm);
@Override
protected void doStart() {
completeOne(type);
chainExc(tEnum.getMembers().thenAccept(this::procMembers));
}
private void procMembers(Collection<? extends TargetDataTypeMember<?>> members) {
for (TargetDataTypeMember<?> c : members) {
type.add(c.getMemberName(), c.getPosition());
}
completeTwo();
}
};
}
protected TwoPhased<FunctionDefinitionDataType> convertTargetFunctionDataType(String name,
TargetNamedDataType<?> tFunction) {
return new TwoPhased<>() {
final Map<String, ParameterDefinitionImpl> args =
new TreeMap<>(TargetObjectKeyComparator.ELEMENT);
final FunctionDefinitionDataType type =
new FunctionDefinitionDataType(name, dtm);
@Override
protected void doStart() {
completeOne(type);
chainExc(tFunction.getMembers().thenAccept(this::procMembers));
}
private void procMembers(Collection<? extends TargetDataTypeMember<?>> members) {
AsyncFence fence = new AsyncFence();
for (TargetDataTypeMember<?> p : members) {
TwoPhased<? extends DataType> dep = convertTwoPhased(p.getDataType());
deps.add(dep);
fence.include(dep.two.thenAccept(t -> {
if (TargetNamedDataType.FUNCTION_RETURN_INDEX.equals(p.getIndex())) {
type.setReturnType(t);
}
else {
args.put(p.getIndex(),
new ParameterDefinitionImpl(p.getMemberName(), t, null));
}
}));
}
chainExc(fence.ready().thenAccept(this::procArgs));
}
private void procArgs(Void __) {
type.setArguments(args.values().toArray(new ParameterDefinitionImpl[args.size()]));
completeTwo();
}
};
}
protected TwoPhased<StructureDataType> convertTargetStructDataType(String name,
TargetNamedDataType<?> tStruct) {
return new TwoPhasedComposite<>(new StructureDataType(name, 0, dtm), tStruct);
}
protected TwoPhased<UnionDataType> convertTargetUnionDataType(String name,
TargetNamedDataType<?> tUnion) {
return new TwoPhasedComposite<>(new UnionDataType(CategoryPath.ROOT, name, dtm), tUnion);
}
protected TwoPhased<TypedefDataType> convertTargetTypedefDataType(String name,
TargetNamedDataType<?> tTypedef) {
return new TwoPhased<>() {
@Override
protected void doStart() {
chainExc(tTypedef.getMembers().thenAccept(this::procMembers));
}
private void procMembers(Collection<? extends TargetDataTypeMember<?>> members) {
if (members.isEmpty()) {
Msg.warn(this, "Typedef did not provide definition. Defaulting.");
procDef(DataType.DEFAULT);
procTwo(null);
return;
}
if (members.size() != 1) {
Msg.warn(this, "Typedef provided multiple definitions. Taking first.");
}
TargetDataTypeMember<?> d = members.iterator().next();
TwoPhased<? extends DataType> dep = convertTwoPhased(d.getDataType());
deps.add(dep);
chainExc(dep.one.thenAccept(this::procDef));
chainExc(dep.two.thenAccept(this::procTwo));
}
private void procDef(DataType cDef) {
completeOne(new TypedefDataType(CategoryPath.ROOT, name, cDef, dtm));
}
private void procTwo(DataType __) {
completeTwo();
}
};
}
protected TwoPhased<ArrayDataType> convertTargetArrayDataType(
TargetArrayDataType tArray) {
return convertTwoPhased(tArray.getElementType()).thenApply(cElem -> {
return new ArrayDataType(cElem, tArray.getElementCount(), cElem.getLength(), dtm);
});
}
protected static class ConvertedTargetBitfieldDataType extends BitFieldDataType {
protected ConvertedTargetBitfieldDataType(DataType baseDataType, int bitSize, int bitOffset)
throws InvalidDataTypeException {
super(baseDataType, bitSize, bitOffset);
}
}
protected TwoPhased<ConvertedTargetBitfieldDataType> convertTargetBitfieldDataType(
TargetBitfieldDataType tBitfield) {
return convertTwoPhased(tBitfield.getFieldType()).thenApply(cField -> {
// TODO: Test these on the reference SCTL implementation.
// There's probably corrections on both sides of this interface
try {
return new ConvertedTargetBitfieldDataType(cField, tBitfield.getBitLength(),
tBitfield.getLeastBitPosition());
}
catch (InvalidDataTypeException e) {
throw new AssertionError(e);
}
});
}
protected TwoPhased<PointerDataType> convertTargetPointerDataType(
TargetPointerDataType tPointer) {
TwoPhased<PointerDataType> cPointer =
convertTwoPhased(tPointer.getReferentType()).thenApply(cRef -> {
return new PointerDataType(cRef, dtm);
});
// The pointer can complete even though the referent is incomplete
cPointer.one.thenAccept(__ -> cPointer.completeTwo());
return cPointer;
}
protected TwoPhased<DataType> convertTargetPrimitiveDataType(
TargetPrimitiveDataType tPrimitive) {
return TwoPhased.completedTwo(doConvertTargetPrimitiveDataType(tPrimitive));
}
protected DataType doConvertTargetPrimitiveDataType(TargetPrimitiveDataType tPrimitive) {
switch (tPrimitive.getKind()) {
case UNDEFINED:
return Undefined.getUndefinedDataType(tPrimitive.getLength());
case VOID:
if (tPrimitive.getLength() != 0) {
Msg.warn(this, "Ignoring non-zero length for void data type");
}
return VoidDataType.dataType;
case UINT:
return AbstractIntegerDataType.getUnsignedDataType(tPrimitive.getLength(), dtm);
case SINT:
return AbstractIntegerDataType.getSignedDataType(tPrimitive.getLength(), dtm);
case FLOAT:
return AbstractFloatDataType.getFloatDataType(tPrimitive.getLength(), dtm);
case COMPLEX:
return AbstractComplexDataType.getComplexDataType(tPrimitive.getLength(), dtm);
}
throw new IllegalArgumentException("Do not know how to convert " + tPrimitive);
}
}

Some files were not shown because too many files have changed in this diff Show more