mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-02-06 03:42:34 +08:00
GP-71: Prepping for source release.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -84,4 +84,6 @@ gradle/wrapper
|
||||
gradlew
|
||||
gradlew.*
|
||||
|
||||
|
||||
# Ignore logs and core dumps
|
||||
*.log
|
||||
core.*
|
||||
|
||||
107
DebuggerDevGuide.md
Normal file
107
DebuggerDevGuide.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Debugger Developer's Guide
|
||||
|
||||
## Catalog of Dependencies
|
||||
|
||||
In addition to Ghidra's normal dependencies, you may want the following:
|
||||
|
||||
* WinDbg for Windows x64
|
||||
* GDB 8.0 or later for Linux amd64/x86_64
|
||||
|
||||
The others (e.g., JNA) are handled by Gradle via Maven Central.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
There are several Eclipse projects each fitting into a larger architectural picture.
|
||||
These all currently reside in the `Ghidra/Debug` directory, but will likely be re-factored into the `Framework` and `Feature` directories later.
|
||||
Each project is listed "bottom up" with a brief description and status.
|
||||
|
||||
* ProposedUtils - a collection of utilities proposed to be moved to other respective projects
|
||||
* AnnotationValidator - an experimental annotation processor for database access objects
|
||||
* Framework-TraceModeling - a database schema and set of interfaces for storing machine state over time
|
||||
* Framework-AsyncComm - a collection of utilities for asynchronous communication (packet formats and completable-future conveniences).
|
||||
* Framework-Debugging - specifies interfaces for debugger models and provides implementation conveniences.
|
||||
* Debugger - the collection of Ghidra plugins and services comprising the Debugger UI.
|
||||
* Debugger-agent-dbgeng - the connector for WinDbg (via dbgeng.dll) on Windows x64.
|
||||
* Debugger-agent-dbgmodel - an experimental connector for WinDbg Preview (with TTD, via dbgmodel.dll) on Windows x64.
|
||||
* Debugger-agent-dbgmodel-traceloader - an experimental "importer" for WinDbg trace files.
|
||||
* Debugger-agent-gdb - the connector for GDB (8.0 or later recommended) on UNIX.
|
||||
* Debugger-gadp - the connector for our custom wire protocol the Ghidra Asynchronous Debugging Protocol.
|
||||
* Debugger-jpda - an in-development connector for Java and Dalvik debugging via JDI (i.e., JDWP).
|
||||
* Debugger-sctl - a deprecated connector for the SCTL stub (cqctworld.org).
|
||||
|
||||
The Trace Modeling schema records machine state and markup over time.
|
||||
It rests on the same database as Programs, allowing trace recordings to be stored in a Ghidra project and shared via a server, if desired.
|
||||
Trace "recording" is a de facto requirement for displaying information in Ghidra's UI.
|
||||
However, only the machine state actually observed by the user (or perhaps a script) is recorded.
|
||||
For most use cases, the Trace is small and ephemeral, serving only to mediate between the UI components and the target's model.
|
||||
It supports many of the same markup (e.g., disassembly, data types) as Programs, in addition to tracking active threads, loaded modules, breakpoints, etc.
|
||||
|
||||
Every model (or "adapter" or "connector" or "agent") implements the API specified in Framework-Debugging. As a general rule in Ghidra, no component is allowed to access a native API and reside in the same JVM as the Ghidra UI.
|
||||
This allows us to contain crashes, preventing data loss.
|
||||
To accommodate this requirement -- given that debugging native applications is almost certainly going to require access to native APIs -- we've developed the Ghidra Asynchronous Debugging Protocol.
|
||||
This protocol is tightly coupled to Framework-Debugging, essentially exposing its methods via RMI.
|
||||
The protocol is built using Google's Protobuf library, providing a potential path for agent implementations in alternative languages.
|
||||
GADP provides both a server and a client implementation.
|
||||
The server can accept any model which adheres to the specification and expose it via TCP; the client does the converse.
|
||||
When a model is instantiated in this way, it is called an "agent," because it is executing in its own JVM.
|
||||
The other connectors, which do not use native APIs, may reside in Ghidra's JVM and typically implement alternative wire protocols, e.g., JDWP and SCTL.
|
||||
In both cases, the implementations inherit from the same interfaces.
|
||||
|
||||
The Debugger services maintain a collection of active connections and inspect each model for potential targets.
|
||||
When a target is found, the service inspects the target environment and attempts to find a suitable opinion.
|
||||
Such an opinion, if found, instructs Ghidra how to map the objects, addresses, registers, etc. from the target namespace into Ghidra's.
|
||||
The target is then handed to a Trace Recorder which begins collecting information needed to populate the UI, e.g., the program counter, stack pointer, and the bytes of memory they refer to.
|
||||
|
||||
## Developing a new connector
|
||||
|
||||
So Ghidra does not yet support your favorite debugger?
|
||||
It is tempting, exciting, but also daunting to develop your own connector.
|
||||
Please finish reading this guide, and look carefully at the ones we have so far, and perhaps ask to see if we are already developing one.
|
||||
Of course, in time you might also search the internet to see if others are developing one.
|
||||
There are quite a few caveats and gotchas, the most notable being that this interface is still in quite a bit of flux.
|
||||
When things go wrong, it could be because of, without limitation: 1) a bug on your part, 2) a bug on our part, 3) a design flaw in the interfaces, or 4) a bug in the debugger/API your adapting.
|
||||
We are still in the process of writing up this documentation.
|
||||
In the meantime, we recommend using the GDB and dbgeng.dll agents as examples.
|
||||
|
||||
You'll also need to provide launcher(s) so that Ghidra knows how to configure and start your connector.
|
||||
Please provide launchers for your model in both configurations: as a connector in Ghidra's JVM, and as a GADP agent.
|
||||
If your model requires native API access, you should only permit launching it as a GADP agent, unless you give ample warning in the launcher's description.
|
||||
Look at the existing launchers for examples.
|
||||
There are many model implementation requirements that cannot be expressed in Java interfaces.
|
||||
Failing to adhere to those requirements may cause different behaviors with and without GADP.
|
||||
Testing with GADP tends to reveal those implementation errors, but also obscures the source of client method calls behind network messages.
|
||||
|
||||
## Adding a new platform
|
||||
|
||||
If an existing connector exists for a suitable debugger on the desired platform, then adding it may be very simple.
|
||||
For example, both the x86 and ARM platforms are supported by GDB, so even though we're currently focused on x86 support, we've provided the opinions needed for Ghidra to debug ARM platforms via GDB.
|
||||
These opinions are kept in the "Debugger" project, not their respective "agent" projects.
|
||||
We imagine there are a number of platforms that could be supported almost out of the box, except that we haven't written the necessary opinions, yet.
|
||||
Take a look at the existing ones for examples.
|
||||
|
||||
In general, to write a new opinion, you need to know: 1) What the platform is called (including variant names) by the debugger, 2) What the processor language is called by Ghidra, 3) If applicable, the mapping of target address spaces into Ghidra's address spaces, 4) If applicable, the mapping of target register names to those in Ghidra's processor language.
|
||||
In most cases (3) and (4) are already implemented by default mappers, so you can use those same mappers in your opinion.
|
||||
Once you have the opinion written, you can try debugging and recording a target.
|
||||
If Ghidra finds your opinion applicable to that target, it will attempt to record, and then you can work out the kinds from there.
|
||||
Again, we have a bit of documentation to do regarding common pitfalls.
|
||||
|
||||
## Emulation
|
||||
|
||||
It may be tempting to write a "connector" for emulation, but we recommend against it.
|
||||
We are exploring the inclusion of emulation as an integral feature of the UI.
|
||||
Namely for interpolation between machines states recorded in a trace, and extrapolation into future machine states.
|
||||
In other words, a connector for emulation is likely to be deprecated by our future work.
|
||||
|
||||
## Contributing
|
||||
|
||||
Whether submitting help tickets and pull requests, please tag those related to the debugger with "Debugger" so that we can triage them more quickly.
|
||||
|
||||
To set up your environment, in addition to the usual Gradle tasks, process the Protobuf specification for GADP:
|
||||
|
||||
```bash
|
||||
gradle generateProto
|
||||
```
|
||||
|
||||
If you already have an environment set up in Eclipse, please re-run `gradle prepDev eclipse` and import the new projects.
|
||||
The Protobuf plugin for Gradle does not seem to export the generated source directory to the Eclipse project.
|
||||
To remedy this, add `build/generated/source/proto/main/java` to the build path, and configure it to output to `bin/main`.
|
||||
@@ -21,6 +21,7 @@
|
||||
- [Building Supporting Data](#building-supporting-data)
|
||||
* [Building Data Type Archives](#building-data-type-archives)
|
||||
* [Building FID Databases](#building-fid-databases)
|
||||
- [Hacking on the Debugger](#hacking-on-the-debugger)
|
||||
|
||||
## Catalog of Dependencies
|
||||
|
||||
@@ -375,4 +376,11 @@ Fill out the options appropriately and click OK.
|
||||
|
||||
If you'd like some details of our fine tuning, take a look at `~/git/ghidra/Ghidra/Features/FunctionID/data/building_fid.txt`.
|
||||
|
||||
### Hacking on the Debugger
|
||||
|
||||
The Debugger consists of multiple modules comprising its own collection of utilities, frameworks, and features.
|
||||
There is plenty of new ground to be broken.
|
||||
Before getting too deep into it, please see our dedicated [Debugger Developer's Guide][DbgGuide].
|
||||
|
||||
[ghidra-data]: https://github.com/NationalSecurityAgency/ghidra-data
|
||||
[DbgGuide]: DebuggerDevGuide.md
|
||||
0
Ghidra/Debug/AnnotationValidator/Module.manifest
Normal file
0
Ghidra/Debug/AnnotationValidator/Module.manifest
Normal file
45
Ghidra/Debug/AnnotationValidator/build.gradle
Normal file
45
Ghidra/Debug/AnnotationValidator/build.gradle
Normal file
@@ -0,0 +1,45 @@
|
||||
apply from: "${rootProject.projectDir}/gradle/javaProject.gradle"
|
||||
apply from: "${rootProject.projectDir}/gradle/jacocoProject.gradle"
|
||||
apply from: "${rootProject.projectDir}/gradle/javaTestProject.gradle"
|
||||
apply from: "${rootProject.projectDir}/gradle/distributableGhidraModule.gradle"
|
||||
|
||||
apply plugin: 'eclipse'
|
||||
eclipse.project.name = 'Debug AnnotationValidator'
|
||||
|
||||
dependencies {
|
||||
compile project(':ProposedUtils')
|
||||
}
|
||||
|
||||
// no-dep jar for experiments in loading annotation processor into Eclipse
|
||||
def boolean filterJar(File jarfile) {
|
||||
if (jarfile.name.contains("gradle-api")) {
|
||||
return false
|
||||
} else if (jarfile.name.contains("groovy-all")) {
|
||||
return false
|
||||
} else if (jarfile.name.contains("gradle-installation-beacon")) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
task configureNodepJar {
|
||||
doLast {
|
||||
configurations.runtime.files.forEach {
|
||||
if (filterJar(it)) {
|
||||
nodepJar.from(zipTree(it)) {
|
||||
exclude("META-INF/**")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task nodepJar(type: Jar) {
|
||||
inputs.file(file(jar.archivePath))
|
||||
dependsOn(configureNodepJar)
|
||||
dependsOn(jar)
|
||||
|
||||
appendix = 'nodep'
|
||||
|
||||
from(zipTree(jar.archivePath))
|
||||
}
|
||||
6
Ghidra/Debug/AnnotationValidator/certification.manifest
Normal file
6
Ghidra/Debug/AnnotationValidator/certification.manifest
Normal file
@@ -0,0 +1,6 @@
|
||||
##VERSION: 2.0
|
||||
.classpath||NONE||reviewed||END|
|
||||
.project||NONE||reviewed||END|
|
||||
Module.manifest||GHIDRA||||END|
|
||||
build.gradle||GHIDRA||||END|
|
||||
src/main/resources/META-INF/services/javax.annotation.processing.Processor||GHIDRA||||END|
|
||||
@@ -0,0 +1,43 @@
|
||||
/* ###
|
||||
* 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.util.database.annotproc;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
|
||||
public class AbstractDBAnnotationValidator {
|
||||
protected final ValidationContext ctx;
|
||||
|
||||
public AbstractDBAnnotationValidator(ValidationContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
protected void checkEnclosingType(Class<? extends Annotation> annotType, VariableElement field,
|
||||
TypeElement type) {
|
||||
if (type.getKind() != ElementKind.CLASS) {
|
||||
ctx.messager.printMessage(Kind.ERROR, String.format(
|
||||
"@%s can only be applied to fields in a class", annotType.getSimpleName()), field);
|
||||
}
|
||||
else if (!ctx.isSubclass(type, ctx.DB_ANNOTATED_OBJECT_ELEM)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s can only be applied within a subclass of %s",
|
||||
annotType.getSimpleName(), ctx.DB_ANNOTATED_OBJECT_ELEM),
|
||||
field);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.util.database.annotproc;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
public enum AccessSpec {
|
||||
PRIVATE(0), PACKAGE(1), PROTECTED(2), PUBLIC(3);
|
||||
|
||||
private final int level;
|
||||
|
||||
private AccessSpec(int level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the second permits the same or more access than the first
|
||||
*
|
||||
* @param first the first
|
||||
* @param second the second
|
||||
* @return true if the second is the same or more permissive
|
||||
*/
|
||||
public static boolean isSameOrMorePermissive(AccessSpec first, AccessSpec second) {
|
||||
// TODO: I'm not sure protected actually includes package...
|
||||
// It might be more diamond shaped
|
||||
return first.level <= second.level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the access specifier derived from the given modifiers
|
||||
*
|
||||
* @param modifiers the element's modifiers
|
||||
* @return the elements access specification
|
||||
*/
|
||||
public static AccessSpec get(Set<Modifier> modifiers) {
|
||||
if (modifiers.contains(Modifier.PRIVATE)) {
|
||||
return PRIVATE;
|
||||
}
|
||||
if (modifiers.contains(Modifier.PROTECTED)) {
|
||||
return PROTECTED;
|
||||
}
|
||||
if (modifiers.contains(Modifier.PUBLIC)) {
|
||||
return PUBLIC;
|
||||
}
|
||||
return PACKAGE;
|
||||
}
|
||||
}
|
||||
@@ -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.util.database.annotproc;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
|
||||
import ghidra.util.database.annot.DBAnnotatedColumn;
|
||||
|
||||
public class DBAnnotatedColumnValidator extends AbstractDBAnnotationValidator {
|
||||
final VariableElement column;
|
||||
|
||||
public DBAnnotatedColumnValidator(ValidationContext ctx, VariableElement column) {
|
||||
super(ctx);
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
if (!ctx.hasType(column, ctx.DB_OBJECT_COLUMN_ELEM)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s can only be applied to fields of type %s",
|
||||
DBAnnotatedColumn.class.getSimpleName(), ctx.DB_OBJECT_COLUMN_ELEM),
|
||||
column);
|
||||
}
|
||||
Set<Modifier> mods = column.getModifiers();
|
||||
if (mods.contains(Modifier.FINAL)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s cannot be applied to a final field",
|
||||
DBAnnotatedColumn.class.getSimpleName()),
|
||||
column);
|
||||
}
|
||||
if (!mods.contains(Modifier.STATIC)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s must be applied to a static field",
|
||||
DBAnnotatedColumn.class.getSimpleName()),
|
||||
column);
|
||||
}
|
||||
TypeElement type = (TypeElement) column.getEnclosingElement();
|
||||
checkEnclosingType(DBAnnotatedColumn.class, column, type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/* ###
|
||||
* 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.util.database.annotproc;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
|
||||
import db.DBHandle;
|
||||
import ghidra.util.database.DBCachedDomainObjectAdapter;
|
||||
import ghidra.util.database.DBOpenMode;
|
||||
import ghidra.util.database.annot.DBAnnotatedField;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DBAnnotatedFieldValidator extends AbstractDBAnnotationValidator {
|
||||
final VariableElement field;
|
||||
final Map<TypeMirror, TypeElement> javaToDBTypeMap;
|
||||
final static String FACTORY_NAME = "ghidra.util.database.DBCachedObjectStoreFactory";
|
||||
final static String BOOLEAN_CODEC_NAME = FACTORY_NAME + ".BooleanDBFieldCodec";
|
||||
final static String BYTE_CODEC_NAME = FACTORY_NAME + ".ByteDBFieldCodec";
|
||||
final static String SHORT_CODEC_NAME = FACTORY_NAME + ".ShortDBFieldCodec";
|
||||
final static String INT_CODEC_NAME = FACTORY_NAME + ".IntDBFieldCodec";
|
||||
final static String LONG_CODEC_NAME = FACTORY_NAME + ".LongDBFieldCodec";
|
||||
final static String STRING_CODEC_NAME = FACTORY_NAME + ".StringDBFieldCodec";
|
||||
final static String BYTE_ARRAY_CODEC_NAME = FACTORY_NAME + ".ByteArrayDBFieldCodec";
|
||||
final static String LONG_ARRAY_CODEC_NAME = FACTORY_NAME + ".LongArrayDBFieldCodec";
|
||||
final static String ENUM_CODEC_NAME = FACTORY_NAME + ".EnumDBByteFieldCodec";
|
||||
|
||||
final TypeElement ENUM_CODEC_ELEM;
|
||||
|
||||
public DBAnnotatedFieldValidator(ValidationContext ctx, VariableElement field) {
|
||||
super(ctx);
|
||||
this.field = field;
|
||||
|
||||
Map<TypeMirror, TypeElement> typeMap = new LinkedHashMap<>();
|
||||
putPrimitiveTypeCodec(typeMap, TypeKind.BOOLEAN, BOOLEAN_CODEC_NAME);
|
||||
putPrimitiveTypeCodec(typeMap, TypeKind.BYTE, BYTE_CODEC_NAME);
|
||||
putPrimitiveTypeCodec(typeMap, TypeKind.SHORT, SHORT_CODEC_NAME);
|
||||
putPrimitiveTypeCodec(typeMap, TypeKind.INT, INT_CODEC_NAME);
|
||||
putPrimitiveTypeCodec(typeMap, TypeKind.LONG, LONG_CODEC_NAME);
|
||||
putTypeCodec(typeMap, String.class, STRING_CODEC_NAME);
|
||||
putPrimitiveArrayTypeCodec(typeMap, TypeKind.BYTE, BYTE_ARRAY_CODEC_NAME);
|
||||
putPrimitiveArrayTypeCodec(typeMap, TypeKind.LONG, LONG_ARRAY_CODEC_NAME);
|
||||
// NOTE: Enum requires subtype check
|
||||
|
||||
javaToDBTypeMap = Map.copyOf(typeMap);
|
||||
|
||||
ENUM_CODEC_ELEM = ctx.elementUtils.getTypeElement(ENUM_CODEC_NAME);
|
||||
}
|
||||
|
||||
protected void putPrimitiveTypeCodec(Map<TypeMirror, TypeElement> map, TypeKind kind,
|
||||
String codecName) {
|
||||
PrimitiveType primitive = ctx.typeUtils.getPrimitiveType(kind);
|
||||
TypeMirror boxed = ctx.typeUtils.boxedClass(primitive).asType();
|
||||
TypeElement codec = ctx.elementUtils.getTypeElement(codecName);
|
||||
map.put(primitive, codec);
|
||||
map.put(boxed, codec);
|
||||
}
|
||||
|
||||
protected void putTypeCodec(Map<TypeMirror, TypeElement> map, Class<?> cls, String codecName) {
|
||||
TypeMirror type = ctx.elementUtils.getTypeElement(cls.getCanonicalName()).asType();
|
||||
TypeElement codec = ctx.elementUtils.getTypeElement(codecName);
|
||||
map.put(type, codec);
|
||||
}
|
||||
|
||||
protected void putPrimitiveArrayTypeCodec(Map<TypeMirror, TypeElement> map, TypeKind kind,
|
||||
String codecName) {
|
||||
PrimitiveType primitive = ctx.typeUtils.getPrimitiveType(kind);
|
||||
ArrayType array = ctx.typeUtils.getArrayType(primitive);
|
||||
TypeElement codec = ctx.elementUtils.getTypeElement(codecName);
|
||||
map.put(array, codec);
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
Set<Modifier> mods = field.getModifiers();
|
||||
if (mods.contains(Modifier.FINAL)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s cannot be applied to a final field",
|
||||
DBAnnotatedField.class.getSimpleName()),
|
||||
field);
|
||||
}
|
||||
if (mods.contains(Modifier.STATIC)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s cannot be applied to a static field",
|
||||
DBAnnotatedField.class.getSimpleName()),
|
||||
field);
|
||||
}
|
||||
TypeElement type = (TypeElement) field.getEnclosingElement();
|
||||
checkEnclosingType(DBAnnotatedField.class, field, type);
|
||||
checkCodecTypes(type);
|
||||
}
|
||||
|
||||
protected TypeElement getDefaultCodecType(TypeMirror javaType) {
|
||||
if (ctx.isEnumType(javaType)) {
|
||||
return ENUM_CODEC_ELEM;
|
||||
}
|
||||
return javaToDBTypeMap.get(javaType);
|
||||
}
|
||||
|
||||
protected TypeElement getCodecTypeElement() {
|
||||
DBAnnotatedField annotation = field.getAnnotation(DBAnnotatedField.class);
|
||||
TypeElement codecElem;
|
||||
try {
|
||||
codecElem = ctx.elementUtils.getTypeElement(annotation.codec().getCanonicalName());
|
||||
}
|
||||
catch (MirroredTypeException e) {
|
||||
codecElem = (TypeElement) ((DeclaredType) e.getTypeMirror()).asElement();
|
||||
}
|
||||
if (codecElem == ctx.DEFAULT_CODEC_ELEM) {
|
||||
return getDefaultCodecType(field.asType());
|
||||
}
|
||||
return codecElem;
|
||||
}
|
||||
|
||||
class A extends DBCachedDomainObjectAdapter {
|
||||
|
||||
protected A(DBHandle dbh, DBOpenMode openMode, TaskMonitor monitor, String name,
|
||||
int timeInterval, int bufSize, Object consumer) {
|
||||
super(dbh, openMode, monitor, name, timeInterval, bufSize, consumer);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChangeable() {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkCodecTypes(TypeElement objectType) {
|
||||
|
||||
//experiment(new Blargh(null, null));
|
||||
|
||||
TypeElement codecType = getCodecTypeElement();
|
||||
if (codecType == null) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("Could not select default codec for %s. @%s.codec must be specified.",
|
||||
field.asType(), DBAnnotatedField.class.getSimpleName()),
|
||||
field);
|
||||
return;
|
||||
}
|
||||
|
||||
// REQUIREMENTS:
|
||||
// 1) ValueType matches the field's type exactly
|
||||
// Cannot be super or extends because it's read/write
|
||||
// 2) ObjectType is super of the containing object
|
||||
// Need to ensure extra interfaces (intersection) are considered
|
||||
// 3) FieldType is non-abstract
|
||||
// 4) The codec has an appropriate constructor
|
||||
|
||||
for (Element enc : codecType.getEnclosedElements()) {
|
||||
if (enc.getKind() == ElementKind.CONSTRUCTOR) {
|
||||
ExecutableElement exe = (ExecutableElement) enc;
|
||||
ExecutableType exeType = (ExecutableType) exe.asType();
|
||||
//throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, TypeMirror> args = ctx.getArguments(codecType, ctx.DB_FIELD_CODEC_ELEM);
|
||||
|
||||
// 1)
|
||||
TypeMirror argVT = args.get("VT");
|
||||
if (!ctx.hasType(field, argVT)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("Codec %s can only be used with fields of type %s", codecType, argVT),
|
||||
field);
|
||||
}
|
||||
|
||||
// 2) (INCOMPLETE)
|
||||
TypeMirror argOT = args.get("OT");
|
||||
if (!ctx.isCapturable(objectType.asType(), argOT)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("Codec %s requires the containing object to conform to %s", codecType,
|
||||
ctx.format(argOT)),
|
||||
field);
|
||||
}
|
||||
|
||||
// 3)
|
||||
TypeMirror argFT = args.get("FT");
|
||||
if (argFT.getKind() != TypeKind.DECLARED) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("Codec %s must have a non-abstract class for its field type, not %s",
|
||||
codecType, argFT),
|
||||
codecType);
|
||||
}
|
||||
else if (((DeclaredType) argFT).asElement().getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("Codec %s must have a non-abstract class for its field type, not %s",
|
||||
codecType, argFT),
|
||||
codecType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/* ###
|
||||
* 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.util.database.annotproc;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.*;
|
||||
|
||||
import ghidra.util.database.DBAnnotatedObject;
|
||||
import ghidra.util.database.annot.*;
|
||||
|
||||
/**
|
||||
* A compile-time annotation processor for {@link DBAnnotatedObject}-related annotations.
|
||||
*
|
||||
* Currently just performs compile-time checks. It does not generate any code, but perhaps one day,
|
||||
* it will.
|
||||
*/
|
||||
//@AutoService(Processor.class) // TODO: Evaluate Google's auto-service as a dependency
|
||||
public class DBAnnotatedObjectProcessor extends AbstractProcessor {
|
||||
static final Set<Class<? extends Annotation>> SUPPORTED_ANNOTATIONS =
|
||||
Set.of(DBAnnotatedColumn.class, DBAnnotatedField.class, DBAnnotatedObjectInfo.class);
|
||||
|
||||
private ValidationContext ctx;
|
||||
|
||||
@Override
|
||||
public synchronized void init(ProcessingEnvironment env) {
|
||||
//System.err.println("HERE4");
|
||||
super.init(env);
|
||||
ctx = new ValidationContext(env);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
Map<TypeElement, DBAnnotatedObjectValidator> types = new LinkedHashMap<>();
|
||||
for (Element element : roundEnv.getElementsAnnotatedWith(DBAnnotatedObjectInfo.class)) {
|
||||
TypeElement type = (TypeElement) element; // Required by annotation Target
|
||||
types.put(type, new DBAnnotatedObjectValidator(ctx, type));
|
||||
}
|
||||
for (Element field : roundEnv.getElementsAnnotatedWith(DBAnnotatedField.class)) {
|
||||
VariableElement varField = (VariableElement) field; // Required by annotation Target
|
||||
// Fields can only be members of types, right?
|
||||
TypeElement type = (TypeElement) field.getEnclosingElement();
|
||||
DBAnnotatedObjectValidator validator =
|
||||
types.computeIfAbsent(type, t -> new DBAnnotatedObjectValidator(ctx, type));
|
||||
validator.addAnnotatedField(varField);
|
||||
}
|
||||
for (Element column : roundEnv.getElementsAnnotatedWith(DBAnnotatedColumn.class)) {
|
||||
VariableElement varColumn = (VariableElement) column; // Required by annotation Target
|
||||
// Fields can only be members of types, right?
|
||||
TypeElement type = (TypeElement) column.getEnclosingElement();
|
||||
DBAnnotatedObjectValidator validator =
|
||||
types.computeIfAbsent(type, t -> new DBAnnotatedObjectValidator(ctx, type));
|
||||
validator.addAnnotatedColumn(varColumn);
|
||||
}
|
||||
|
||||
for (DBAnnotatedObjectValidator ov : types.values()) {
|
||||
ov.validate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends Completion> getCompletions(Element element,
|
||||
AnnotationMirror annotation, ExecutableElement member, String userText) {
|
||||
// TODO Auto-generated method stub
|
||||
return super.getCompletions(element, annotation, member, userText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
return SourceVersion.latestSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSupportedAnnotationTypes() {
|
||||
return SUPPORTED_ANNOTATIONS.stream().map(Class::getCanonicalName).collect(
|
||||
Collectors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/* ###
|
||||
* 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.util.database.annotproc;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
|
||||
import ghidra.util.database.annot.*;
|
||||
|
||||
public class DBAnnotatedObjectValidator {
|
||||
private final ValidationContext ctx;
|
||||
private final TypeElement type;
|
||||
private final Map<String, DBAnnotatedFieldValidator> fieldsByName = new LinkedHashMap<>();
|
||||
private final Map<String, DBAnnotatedColumnValidator> columnsByName = new LinkedHashMap<>();
|
||||
|
||||
public DBAnnotatedObjectValidator(ValidationContext ctx, TypeElement type) {
|
||||
this.ctx = ctx;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void addAnnotatedField(VariableElement field) {
|
||||
DBAnnotatedField annotation = field.getAnnotation(DBAnnotatedField.class);
|
||||
assert annotation != null;
|
||||
fieldsByName.put(annotation.column(), new DBAnnotatedFieldValidator(ctx, field));
|
||||
}
|
||||
|
||||
public void addAnnotatedColumn(VariableElement column) {
|
||||
DBAnnotatedColumn annotation = column.getAnnotation(DBAnnotatedColumn.class);
|
||||
assert annotation != null;
|
||||
columnsByName.put(annotation.value(), new DBAnnotatedColumnValidator(ctx, column));
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
DBAnnotatedObjectInfo annotation = type.getAnnotation(DBAnnotatedObjectInfo.class);
|
||||
if (annotation != null && type.getKind() != ElementKind.CLASS) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s cannot be applied to an interface",
|
||||
DBAnnotatedObjectInfo.class.getSimpleName()),
|
||||
type);
|
||||
}
|
||||
else if (annotation != null && type.getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s cannot be applied to an abstract class",
|
||||
DBAnnotatedObjectInfo.class.getSimpleName()),
|
||||
type);
|
||||
}
|
||||
if (annotation != null && !ctx.isSubclass(type, ctx.DB_ANNOTATED_OBJECT_ELEM)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s can only be applied to subclasses of %s", "DBAnnotatedObject",
|
||||
DBAnnotatedObjectInfo.class.getSimpleName()));
|
||||
}
|
||||
if (annotation == null && !type.getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("Non-abstract subclasses of %s must have @%s annotation",
|
||||
"DBAnnotatedObject", DBAnnotatedObjectInfo.class.getSimpleName()),
|
||||
type);
|
||||
}
|
||||
if (annotation != null && annotation.version() < 0) {
|
||||
ctx.messager.printMessage(Kind.ERROR, String.format("@%s.version cannot be negative",
|
||||
DBAnnotatedObjectInfo.class.getSimpleName()), type);
|
||||
}
|
||||
|
||||
validateFields();
|
||||
validateColumns();
|
||||
|
||||
checkMissing();
|
||||
}
|
||||
|
||||
protected void validateFields() {
|
||||
for (DBAnnotatedFieldValidator fv : fieldsByName.values()) {
|
||||
fv.validate();
|
||||
}
|
||||
}
|
||||
|
||||
protected void validateColumns() {
|
||||
for (DBAnnotatedColumnValidator cv : columnsByName.values()) {
|
||||
cv.validate();
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkMissing() {
|
||||
Set<String> names = new LinkedHashSet<>();
|
||||
names.addAll(fieldsByName.keySet());
|
||||
names.addAll(columnsByName.keySet());
|
||||
for (String n : names) {
|
||||
DBAnnotatedFieldValidator fv = fieldsByName.get(n);
|
||||
DBAnnotatedColumnValidator cv = columnsByName.get(n);
|
||||
if (fv == null && cv != null && !type.getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s is missing corresponding @%s of the same column name: %s",
|
||||
DBAnnotatedColumn.class.getSimpleName(),
|
||||
DBAnnotatedField.class.getSimpleName(), n),
|
||||
cv.column);
|
||||
}
|
||||
if (fv != null && cv == null && !type.getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
ctx.messager.printMessage(Kind.WARNING,
|
||||
String.format("@%s is missing corresponding @%s of the same column name: %s",
|
||||
DBAnnotatedField.class.getSimpleName(),
|
||||
DBAnnotatedColumn.class.getSimpleName(), n),
|
||||
fv.field);
|
||||
}
|
||||
if (fv != null && cv != null) {
|
||||
checkAccess(fv.field, cv.column, n);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void checkAccess(VariableElement field, VariableElement column, String name) {
|
||||
AccessSpec fieldSpec = AccessSpec.get(field.getModifiers());
|
||||
AccessSpec columnSpec = AccessSpec.get(column.getModifiers());
|
||||
if (!AccessSpec.isSameOrMorePermissive(fieldSpec, columnSpec)) {
|
||||
ctx.messager.printMessage(Kind.WARNING,
|
||||
String.format(
|
||||
"field with @%s should have same or greater access than field with" +
|
||||
" corresponding @%s for column name: %s",
|
||||
DBAnnotatedColumn.class.getSimpleName(), DBAnnotatedField.class.getSimpleName(),
|
||||
name),
|
||||
column);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.util.database.annotproc;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
|
||||
import ghidra.util.database.annot.DBAnnotatedField;
|
||||
|
||||
public class ValidationContext {
|
||||
final Types typeUtils;
|
||||
final Elements elementUtils;
|
||||
final Messager messager;
|
||||
|
||||
final TypeElement LIST_ELEM;
|
||||
final TypeElement DB_ANNOTATED_OBJECT_ELEM;
|
||||
final TypeElement DB_OBJECT_COLUMN_ELEM;
|
||||
final TypeElement DB_FIELD_CODEC_ELEM;
|
||||
final TypeElement DEFAULT_CODEC_ELEM;
|
||||
final TypeElement ENUM_ELEM;
|
||||
|
||||
public ValidationContext(ProcessingEnvironment env) {
|
||||
typeUtils = env.getTypeUtils();
|
||||
elementUtils = env.getElementUtils();
|
||||
messager = env.getMessager();
|
||||
|
||||
LIST_ELEM = elementUtils.getTypeElement(List.class.getCanonicalName());
|
||||
DB_ANNOTATED_OBJECT_ELEM =
|
||||
elementUtils.getTypeElement("ghidra.util.database.DBAnnotatedObject");
|
||||
DB_OBJECT_COLUMN_ELEM = elementUtils.getTypeElement("ghidra.util.database.DBObjectColumn");
|
||||
DB_FIELD_CODEC_ELEM = elementUtils.getTypeElement(
|
||||
"ghidra.util.database.DBCachedObjectStoreFactory.DBFieldCodec");
|
||||
DEFAULT_CODEC_ELEM = elementUtils.getTypeElement(
|
||||
DBAnnotatedField.class.getCanonicalName() + ".DefaultCodec");
|
||||
ENUM_ELEM = elementUtils.getTypeElement(Enum.class.getCanonicalName());
|
||||
}
|
||||
|
||||
public boolean isSubclass(TypeElement t1, TypeElement t2) {
|
||||
return typeUtils.isSubtype(typeUtils.erasure(t1.asType()), typeUtils.erasure(t2.asType()));
|
||||
}
|
||||
|
||||
public boolean hasType(VariableElement field, TypeElement type) {
|
||||
return hasType(field, type.asType());
|
||||
}
|
||||
|
||||
public boolean hasType(VariableElement field, TypeMirror type) {
|
||||
TypeMirror fieldType = field.asType();
|
||||
try {
|
||||
PrimitiveType unboxed = typeUtils.unboxedType(type);
|
||||
if (typeUtils.isSameType(fieldType, unboxed)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// Eh, I guess it's not unboxable
|
||||
}
|
||||
|
||||
if (fieldType.getKind() == TypeKind.DECLARED) {
|
||||
DeclaredType declType = (DeclaredType) fieldType;
|
||||
if (isSubclass((TypeElement) declType.asElement(), ENUM_ELEM)) {
|
||||
Map<String, TypeMirror> enumArgs = getArguments(declType, ENUM_ELEM);
|
||||
TypeMirror argE = enumArgs.get("E");
|
||||
if (typeUtils.isSameType(declType, argE)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return typeUtils.isAssignable(fieldType, type);
|
||||
// return typeUtils.isSameType(fieldType, type);
|
||||
}
|
||||
|
||||
public boolean isCapturable(TypeMirror t1, TypeMirror t2) {
|
||||
// TODO: This only works for typevar at top level...
|
||||
// TODO: Need to figure out how to check for capture and check
|
||||
if (t2.getKind() == TypeKind.TYPEVAR) {
|
||||
TypeVariable v2 = (TypeVariable) t2;
|
||||
if (!typeUtils.isSubtype(t1, v2.getUpperBound())) {
|
||||
return false;
|
||||
}
|
||||
if (!typeUtils.isSubtype(v2.getLowerBound(), t1)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return typeUtils.isSubtype(t1, t2);
|
||||
}
|
||||
|
||||
public boolean isEnumType(TypeMirror t) {
|
||||
if (t.getKind() != TypeKind.DECLARED) {
|
||||
return false;
|
||||
}
|
||||
DeclaredType enumType = typeUtils.getDeclaredType(ENUM_ELEM, t);
|
||||
return typeUtils.isSubtype(t, enumType);
|
||||
}
|
||||
|
||||
protected DeclaredType findSupertype(Set<DeclaredType> types, TypeElement superType) {
|
||||
Set<DeclaredType> next;
|
||||
while (!types.isEmpty()) {
|
||||
next = new HashSet<>();
|
||||
for (DeclaredType t : types) {
|
||||
List<? extends TypeMirror> supers = typeUtils.directSupertypes(t);
|
||||
for (TypeMirror s : supers) {
|
||||
DeclaredType ds = (DeclaredType) s;
|
||||
if (superType == ds.asElement()) {
|
||||
return ds;
|
||||
}
|
||||
next.add(ds);
|
||||
}
|
||||
}
|
||||
types = next;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public DeclaredType findSupertype(DeclaredType type, TypeElement superElem) {
|
||||
return findSupertype(Set.of(type), superElem);
|
||||
}
|
||||
|
||||
public DeclaredType findSupertype(TypeElement elem, TypeElement superElem) {
|
||||
return findSupertype((DeclaredType) elem.asType(), superElem);
|
||||
}
|
||||
|
||||
protected Map<String, TypeMirror> toArgsMap(TypeElement superElem, DeclaredType superType) {
|
||||
List<? extends TypeParameterElement> typeParameters = superElem.getTypeParameters();
|
||||
List<? extends TypeMirror> typeArguments = superType.getTypeArguments();
|
||||
assert typeParameters.size() == typeArguments.size();
|
||||
Map<String, TypeMirror> result = new HashMap<>();
|
||||
for (int i = 0; i < typeParameters.size(); i++) {
|
||||
result.put(typeParameters.get(i).getSimpleName().toString(), typeArguments.get(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, TypeMirror> getArguments(DeclaredType type, TypeElement superElem) {
|
||||
return toArgsMap(superElem, findSupertype(type, superElem));
|
||||
}
|
||||
|
||||
public Map<String, TypeMirror> getArguments(TypeElement elem, TypeElement superElem) {
|
||||
return toArgsMap(superElem, findSupertype(elem, superElem));
|
||||
}
|
||||
|
||||
public String format(TypeMirror type) {
|
||||
FormatVisitor vis = new FormatVisitor();
|
||||
type.accept(vis, null);
|
||||
return vis.buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class FormatVisitor implements TypeVisitor<Void, Void> {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
|
||||
@Override
|
||||
public Void visit(TypeMirror t, Void p) {
|
||||
switch (t.getKind()) {
|
||||
case ARRAY:
|
||||
return visitArray((ArrayType) t, p);
|
||||
case BOOLEAN:
|
||||
case BYTE:
|
||||
case CHAR:
|
||||
case DOUBLE:
|
||||
case FLOAT:
|
||||
case INT:
|
||||
case LONG:
|
||||
case SHORT:
|
||||
case VOID:
|
||||
return visitPrimitive((PrimitiveType) t, p);
|
||||
case DECLARED:
|
||||
return visitDeclared((DeclaredType) t, p);
|
||||
case ERROR:
|
||||
return visitError((ErrorType) t, p);
|
||||
case EXECUTABLE:
|
||||
return visitExecutable((ExecutableType) t, p);
|
||||
case INTERSECTION:
|
||||
return visitIntersection((IntersectionType) t, p);
|
||||
case NONE:
|
||||
return visitNoType((NoType) t, p);
|
||||
case NULL:
|
||||
return visitNull((NullType) t, p);
|
||||
case TYPEVAR:
|
||||
return visitTypeVariable((TypeVariable) t, p);
|
||||
case UNION:
|
||||
return visitUnion((UnionType) t, p);
|
||||
case WILDCARD:
|
||||
return visitWildcard((WildcardType) t, p);
|
||||
default:
|
||||
return visitUnknown(t, p);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitPrimitive(PrimitiveType t, Void p) {
|
||||
buf.append(t.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitNull(NullType t, Void p) {
|
||||
buf.append(t.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitArray(ArrayType t, Void p) {
|
||||
visit(t.getComponentType());
|
||||
buf.append("[]");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitDeclared(DeclaredType t, Void p) {
|
||||
buf.append(t.asElement().toString());
|
||||
Iterator<? extends TypeMirror> it = t.getTypeArguments().iterator();
|
||||
if (it.hasNext()) {
|
||||
buf.append("<");
|
||||
visit(it.next());
|
||||
while (it.hasNext()) {
|
||||
buf.append(", ");
|
||||
visit(it.next());
|
||||
}
|
||||
buf.append(">");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitError(ErrorType t, Void p) {
|
||||
buf.append(t.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitTypeVariable(TypeVariable t, Void p) {
|
||||
buf.append(t.toString());
|
||||
TypeMirror lower = t.getLowerBound();
|
||||
if (lower.getKind() != TypeKind.NULL) {
|
||||
buf.append(" super ");
|
||||
visit(lower);
|
||||
}
|
||||
TypeMirror upper = t.getUpperBound();
|
||||
if (!upper.toString().equals("java.lang.Object")) {
|
||||
buf.append(" extends ");
|
||||
visit(upper);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitWildcard(WildcardType t, Void p) {
|
||||
buf.append("?");
|
||||
TypeMirror sup = t.getSuperBound();
|
||||
if (sup != null) {
|
||||
buf.append(" super ");
|
||||
visit(sup);
|
||||
}
|
||||
TypeMirror ext = t.getExtendsBound();
|
||||
if (ext != null) {
|
||||
buf.append(" extends ");
|
||||
visit(ext);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitExecutable(ExecutableType t, Void p) {
|
||||
buf.append(t.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitNoType(NoType t, Void p) {
|
||||
buf.append(t.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitUnknown(TypeMirror t, Void p) {
|
||||
buf.append(t.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitUnion(UnionType t, Void p) {
|
||||
Iterator<? extends TypeMirror> it = t.getAlternatives().iterator();
|
||||
if (it.hasNext()) {
|
||||
visit(it.next());
|
||||
while (it.hasNext()) {
|
||||
buf.append(" | ");
|
||||
visit(it.next());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitIntersection(IntersectionType t, Void p) {
|
||||
Iterator<? extends TypeMirror> it = t.getBounds().iterator();
|
||||
if (it.hasNext()) {
|
||||
visit(it.next());
|
||||
while (it.hasNext()) {
|
||||
buf.append(" & ");
|
||||
visit(it.next());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
ghidra.util.database.annotproc.DBAnnotatedObjectProcessor
|
||||
2
Ghidra/Debug/Debugger-agent-dbgeng/Module.manifest
Normal file
2
Ghidra/Debug/Debugger-agent-dbgeng/Module.manifest
Normal file
@@ -0,0 +1,2 @@
|
||||
MODULE FILE LICENSE: lib/jna-5.4.0.jar Apache License 2.0
|
||||
MODULE FILE LICENSE: lib/jna-platform-5.4.0.jar Apache License 2.0
|
||||
66
Ghidra/Debug/Debugger-agent-dbgeng/build.gradle
Normal file
66
Ghidra/Debug/Debugger-agent-dbgeng/build.gradle
Normal file
@@ -0,0 +1,66 @@
|
||||
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/nativeProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
|
||||
apply plugin: 'eclipse'
|
||||
|
||||
eclipse.project.name = 'Debug Debugger-agent-dbgeng'
|
||||
|
||||
dependencies {
|
||||
compile project(":Framework-AsyncComm")
|
||||
compile project(":Framework-Debugging")
|
||||
compile project(":Debugger-gadp")
|
||||
compile "net.java.dev.jna:jna:5.4.0"
|
||||
compile "net.java.dev.jna:jna-platform:5.4.0"
|
||||
|
||||
//testCompile project(":Base")
|
||||
testCompile project(path: ":Debugger-gadp", configuration: 'testArtifacts')
|
||||
}
|
||||
|
||||
def boolean filterJar(File jarfile) {
|
||||
if (jarfile.name.contains("gradle-api")) {
|
||||
return false
|
||||
} else if (jarfile.name.contains("groovy-all")) {
|
||||
return false
|
||||
} else if (jarfile.name.contains("gradle-installation-beacon")) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes['Main-Class'] = 'agent.dbgeng.gadp.DbgEngGadpServer'
|
||||
}
|
||||
}
|
||||
|
||||
task configureNodepJar {
|
||||
doLast {
|
||||
configurations.runtime.files.forEach {
|
||||
if (filterJar(it)) {
|
||||
nodepJar.from(zipTree(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task nodepJar(type: Jar) {
|
||||
inputs.file(file(jar.archivePath))
|
||||
dependsOn(configureNodepJar)
|
||||
dependsOn(jar)
|
||||
|
||||
appendix = 'nodep'
|
||||
manifest {
|
||||
attributes['Main-Class'] = 'agent.dbgeng.gadp.DbgEngGadpServer'
|
||||
}
|
||||
|
||||
from(zipTree(jar.archivePath))
|
||||
}
|
||||
|
||||
test {
|
||||
jvmArgs('-Xrs') // TODO: Is this needed, or left over from trial-and-error
|
||||
if ("win64".equals(getCurrentPlatformName())) {
|
||||
dependsOn(":Framework-Debugging:testSpecimenWin64")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
##VERSION: 2.0
|
||||
##MODULE IP: Apache License 2.0
|
||||
.classpath||NONE||reviewed||END|
|
||||
.project||NONE||reviewed||END|
|
||||
Module.manifest||GHIDRA||||END|
|
||||
build.gradle||GHIDRA||||END|
|
||||
src/javaprovider/def/javaprovider.def||GHIDRA||||END|
|
||||
src/javaprovider/rc/javaprovider.rc||GHIDRA||||END|
|
||||
@@ -0,0 +1,224 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
#define INITGUID
|
||||
|
||||
#include <engextcpp.hpp>
|
||||
#include <jni.h>
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
#define CHECK_RESULT(x, y) do { \
|
||||
HRESULT hr = (x); \
|
||||
if (hr != S_OK) { \
|
||||
fprintf(stderr, "HRESULT of %s = %x\n", ##x, hr); \
|
||||
return y; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
class EXT_CLASS : public ExtExtension {
|
||||
public:
|
||||
virtual HRESULT Initialize();
|
||||
virtual void Uninitialize();
|
||||
|
||||
//virtual void OnSessionAccessible(ULONG64 Argument);
|
||||
|
||||
EXT_COMMAND_METHOD(java_add_cp);
|
||||
EXT_COMMAND_METHOD(java_set);
|
||||
EXT_COMMAND_METHOD(java_get);
|
||||
EXT_COMMAND_METHOD(java_run);
|
||||
|
||||
void run_command(PCSTR name);
|
||||
};
|
||||
|
||||
EXT_DECLARE_GLOBALS();
|
||||
|
||||
JavaVM* jvm = NULL;
|
||||
JNIEnv* env = NULL;
|
||||
jclass clsCommands = NULL;
|
||||
|
||||
char JDK_JVM_DLL_PATH[] = "\\jre\\bin\\server\\jvm.dll";
|
||||
char JRE_JVM_DLL_PATH[] = "\\bin\\server\\jvm.dll";
|
||||
|
||||
typedef jint (_cdecl *CreateJavaVMFunc)(JavaVM**, void**, void*);
|
||||
|
||||
HRESULT EXT_CLASS::Initialize() {
|
||||
HRESULT result = ExtExtension::Initialize();
|
||||
if (result != S_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
char* env_java_home = getenv("JAVA_HOME");
|
||||
if (env_java_home == NULL) {
|
||||
fprintf(stderr, "JAVA_HOME is not set\n");
|
||||
fflush(stderr);
|
||||
return E_FAIL;
|
||||
}
|
||||
char* java_home = strdup(env_java_home);
|
||||
size_t home_len = strlen(java_home);
|
||||
if (java_home[home_len - 1] == '\\') {
|
||||
java_home[home_len - 1] = '\0';
|
||||
}
|
||||
size_t full_len = home_len + sizeof(JDK_JVM_DLL_PATH);
|
||||
char* full_path = new char[full_len];
|
||||
HMODULE jvmDll = NULL;
|
||||
// Try the JRE path first;
|
||||
strcpy_s(full_path, full_len, java_home);
|
||||
strcat_s(full_path, full_len, JRE_JVM_DLL_PATH);
|
||||
fprintf(stderr, "Trying to find jvm.dll at %s\n", full_path);
|
||||
fflush(stderr);
|
||||
jvmDll = LoadLibraryA(full_path);
|
||||
if (jvmDll == NULL) {
|
||||
// OK, then try the JDK path
|
||||
strcpy_s(full_path, full_len, java_home);
|
||||
strcat_s(full_path, full_len, JDK_JVM_DLL_PATH);
|
||||
fprintf(stderr, "Trying to find jvm.dll at %s\n", full_path);
|
||||
fflush(stderr);
|
||||
jvmDll = LoadLibraryA(full_path);
|
||||
}
|
||||
free(full_path);
|
||||
free(java_home);
|
||||
if (jvmDll == NULL) {
|
||||
fprintf(stderr, "Could not find the jvm.dll\n");
|
||||
fflush(stderr);
|
||||
return E_FAIL;
|
||||
}
|
||||
fprintf(stderr, "Found it!\n");
|
||||
fflush(stderr);
|
||||
|
||||
JavaVMOption options[2];
|
||||
JavaVMInitArgs vm_args = { 0 };
|
||||
vm_args.version = JNI_VERSION_1_8;
|
||||
vm_args.nOptions = sizeof(options)/sizeof(options[0]);
|
||||
vm_args.options = options;
|
||||
options[0].optionString = "-Xrs";
|
||||
options[1].optionString = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005";
|
||||
vm_args.ignoreUnrecognized = false;
|
||||
CreateJavaVMFunc create_jvm = NULL;
|
||||
//create_jvm = JNI_CreateJavaVM;
|
||||
create_jvm = (CreateJavaVMFunc) GetProcAddress(jvmDll, "JNI_CreateJavaVM");
|
||||
jint jni_result = create_jvm(&jvm, (void**)&env, &vm_args);
|
||||
|
||||
if (jni_result != JNI_OK) {
|
||||
jvm = NULL;
|
||||
fprintf(stderr, "Could not initialize JVM: %d: ", jni_result);
|
||||
switch (jni_result) {
|
||||
case JNI_ERR:
|
||||
fprintf(stderr, "unknown error");
|
||||
break;
|
||||
case JNI_EDETACHED:
|
||||
fprintf(stderr, "thread detached from the VM");
|
||||
break;
|
||||
case JNI_EVERSION:
|
||||
fprintf(stderr, "JNI version error");
|
||||
break;
|
||||
case JNI_ENOMEM:
|
||||
fprintf(stderr, "not enough memory");
|
||||
break;
|
||||
case JNI_EEXIST:
|
||||
fprintf(stderr, "VM already created");
|
||||
break;
|
||||
case JNI_EINVAL:
|
||||
fprintf(stderr, "invalid arguments");
|
||||
break;
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
fflush(stderr);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
HMODULE hJavaProviderModule = GetModuleHandle(TEXT("javaprovider"));
|
||||
HRSRC resCommandsClassfile = FindResource(hJavaProviderModule, MAKEINTRESOURCE(IDR_CLASSFILE1), TEXT("Classfile"));
|
||||
HGLOBAL gblCommandsClassfile = LoadResource(hJavaProviderModule, resCommandsClassfile);
|
||||
LPVOID lpCommandsClassfile = LockResource(gblCommandsClassfile);
|
||||
DWORD szCommandsClassfile = SizeofResource(hJavaProviderModule, resCommandsClassfile);
|
||||
|
||||
clsCommands = env->DefineClass(
|
||||
"javaprovider/Commands", NULL, (jbyte*) lpCommandsClassfile, szCommandsClassfile
|
||||
);
|
||||
if (clsCommands == NULL) {
|
||||
fprintf(stderr, "Could not define Commands class\n");
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void EXT_CLASS::Uninitialize() {
|
||||
if (jvm != NULL) {
|
||||
jvm->DestroyJavaVM();
|
||||
}
|
||||
ExtExtension::Uninitialize();
|
||||
}
|
||||
|
||||
void EXT_CLASS::run_command(PCSTR name) {
|
||||
// TODO: Throw an exception during load, then!
|
||||
if (jvm == NULL) {
|
||||
Out("javaprovider extension did not load properly.\n");
|
||||
return;
|
||||
}
|
||||
if (clsCommands == NULL) {
|
||||
Out("javaprovider extension did not load properly.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
PCSTR args = GetRawArgStr();
|
||||
|
||||
jmethodID mthCommand = env->GetStaticMethodID(clsCommands, name, "(Ljava/lang/String;)V");
|
||||
if (mthCommand == NULL) {
|
||||
Out("INTERNAL ERROR: No such command: %s\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
jstring argsStr = env->NewStringUTF(args);
|
||||
if (argsStr == NULL) {
|
||||
Out("Could not create Java string for arguments.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
env->CallStaticVoidMethod(clsCommands, mthCommand, argsStr);
|
||||
env->DeleteLocalRef(argsStr);
|
||||
if (env->ExceptionCheck()) {
|
||||
Out("Exception during javaprovider command:\n");
|
||||
env->ExceptionDescribe(); // TODO: Send this to output callbacks, not console.
|
||||
env->ExceptionClear();
|
||||
}
|
||||
}
|
||||
|
||||
EXT_COMMAND(java_add_cp, "Add an element to the class path", "{{custom}}") {
|
||||
run_command("java_add_cp");
|
||||
}
|
||||
|
||||
EXT_COMMAND(java_set, "Set a Java system property", "{{custom}}") {
|
||||
run_command("java_set");
|
||||
}
|
||||
|
||||
EXT_COMMAND(java_get, "Get a Java system property", "{{custom}}") {
|
||||
run_command("java_get");
|
||||
}
|
||||
|
||||
EXT_COMMAND(java_run, "Execute the named java class", "{{custom}}") {
|
||||
run_command("java_run");
|
||||
}
|
||||
|
||||
#define JNA extern "C" __declspec(dllexport)
|
||||
|
||||
JNA HRESULT createClient(PDEBUG_CLIENT* client) {
|
||||
return g_ExtInstance.m_Client->CreateClient(client);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
EXPORTS
|
||||
|
||||
; For ExtCpp
|
||||
DebugExtensionInitialize
|
||||
DebugExtensionUninitialize
|
||||
DebugExtensionNotify
|
||||
help
|
||||
|
||||
; My Commands
|
||||
java_add_cp
|
||||
java_set
|
||||
java_get
|
||||
java_run
|
||||
@@ -0,0 +1,16 @@
|
||||
/* ###
|
||||
* 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>
|
||||
@@ -0,0 +1,31 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by javaprovider.rc
|
||||
//
|
||||
#define IDR_CLASSFILE1 101
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.dbgeng.model.impl.DbgModelImpl;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.LocalDebuggerModelFactory;
|
||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||
import ghidra.util.classfinder.ExtensionPointProperties;
|
||||
|
||||
/**
|
||||
* Note this is in the testing source because it's not meant to be shipped in the release.... That
|
||||
* may change if it proves stable, though, no?
|
||||
*/
|
||||
@FactoryDescription( //
|
||||
brief = "IN-VM MS dbgeng local debugger", //
|
||||
htmlDetails = "Launch a dbgeng session in this same JVM" //
|
||||
)
|
||||
@ExtensionPointProperties(priority = 80)
|
||||
public class DbgEngInJvmDebuggerModelFactory implements LocalDebuggerModelFactory {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<? extends DebuggerObjectModel> build() {
|
||||
DbgModelImpl model = new DbgModelImpl();
|
||||
return model.startDbgEng(new String[] {}).thenApply(__ -> model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompatible() {
|
||||
return System.getProperty("os.name").toLowerCase().contains("windows");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 agent.dbgeng.dbgeng;
|
||||
|
||||
import com.sun.jna.platform.win32.WinNT.HRESULT;
|
||||
import com.sun.jna.platform.win32.COM.COMException;
|
||||
import com.sun.jna.platform.win32.COM.COMUtils;
|
||||
|
||||
/**
|
||||
* Utilities for interacting with Microsoft COM objects beyond those provided by {@link COMUtils}.
|
||||
*
|
||||
* See the MSDN for details on the meanings of the return codes for the function or method of
|
||||
* interest.
|
||||
*/
|
||||
public interface COMUtilsExtra {
|
||||
|
||||
public static HRESULT E_UNEXPECTED = new HRESULT(0x8000FFFF);
|
||||
public static HRESULT E_BOUNDS = new HRESULT(0x8000000B);
|
||||
public static HRESULT E_NOTIMPLEMENTED = new HRESULT(0x80004001);
|
||||
public static HRESULT E_NOINTERFACE = new HRESULT(0x80004002);
|
||||
public static HRESULT E_COM_EXC = new HRESULT(0x80004003);
|
||||
public static HRESULT E_FAIL = new HRESULT(0x80004005);
|
||||
public static HRESULT E_CANTCALLOUT_INASYNCCALL = new HRESULT(0x80010004);
|
||||
public static HRESULT E_INTERNALEXCEPTION = new HRESULT(0x80040205);
|
||||
public static HRESULT E_ACCESS_DENIED = new HRESULT(0x80070005);
|
||||
public static HRESULT E_CANNOT_READ = new HRESULT(0x8007001E);
|
||||
public static HRESULT E_INVALID_PARAM = new HRESULT(0x80070057);
|
||||
public static HRESULT E_SCOPE_NOT_FOUND = new HRESULT(0x8007013E);
|
||||
|
||||
/**
|
||||
* Check if the given exception represents an {@code E_NOINTERFACE} result
|
||||
*
|
||||
* @param e the exception
|
||||
* @return true if {@code E_NOINTERFACE}
|
||||
*/
|
||||
static boolean isE_NOINTERFACE(COMException e) {
|
||||
return E_NOINTERFACE.equals(e.getHresult());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given exception represents an {@code E_UNEXPECTED} result
|
||||
*
|
||||
* @param e the exception
|
||||
* @return true if {@code E_UNEXPECTED}
|
||||
*/
|
||||
static boolean isE_UNEXPECTED(COMException e) {
|
||||
return E_UNEXPECTED.equals(e.getHresult());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given exception represents an {@code E_INTERNALEXCEPTION} result
|
||||
*
|
||||
* @param e the exception
|
||||
* @return true if {@code E_INTERNALEXCEPTION}
|
||||
*/
|
||||
static boolean isE_INTERNALEXCEPTION(COMException e) {
|
||||
return E_INTERNALEXCEPTION.equals(e.getHresult());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import java.lang.ref.Cleaner;
|
||||
|
||||
import com.sun.jna.WString;
|
||||
import com.sun.jna.platform.win32.Kernel32Util;
|
||||
import com.sun.jna.platform.win32.WinDef.DWORD;
|
||||
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
||||
import com.sun.jna.platform.win32.COM.IUnknown;
|
||||
|
||||
import agent.dbgeng.impl.dbgeng.client.DebugClientInternal;
|
||||
import agent.dbgeng.jna.dbgeng.DbgEngNative;
|
||||
import ghidra.comm.util.BitmaskSet;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* A wrapper for Microsoft's {@code dbgeng.dll} that presents a Java-friendly interface.
|
||||
*
|
||||
* This is the "root interface" from which all other interfaces to {@code dbgeng.dll} are generated.
|
||||
* Not every method listed in the documentation, nor every method present in the header, is
|
||||
* implemented. Only those that were necessary to implement the SCTL adapter. However, the class and
|
||||
* interface hierarchy was designed so that adding the remaining methods should be fairly
|
||||
* straightforward. This wrapper attempts to obtain the most capable COM interfaces for the debug
|
||||
* client that it knows. Again, newer interfaces should be fairly straightforward to add.
|
||||
*
|
||||
* Methods that are "obviously" wrappers for a COM method are left undocumented, unless there is
|
||||
* some nuance to how it has been wrapped. In many cases, a parameter which is an integer in the COM
|
||||
* method may be presented as an {@code enum} or {@link BitmaskSet} by the wrapper. Consult the MSDN
|
||||
* for the meaning of the various values and bit flags.
|
||||
*
|
||||
* Each wrapper interface is implemented by several COM interface wrappers: one for each known COM
|
||||
* interface version. The wrapper is optimistic, in that it declares wrapper methods even for COM
|
||||
* methods that are only available in later versions. The implementations limited to earlier COM
|
||||
* interfaces should either emulate the operation, or throw an
|
||||
* {@link UnsupportedOperationException}. Where a newer method is provided by a newer interface, a
|
||||
* wrapper implementation should prefer the latest. For example, one series of interfaces introduces
|
||||
* {@code *Wide} variants of existing methods. Since Java also uses a UTF-16-like string encoding
|
||||
* internally, JNA permits wide strings to be passed by reference. Thus, the wide variant is always
|
||||
* preferred.
|
||||
*
|
||||
* Pay careful attention to the threading requirements imposed by {@code dbgeng.dll} these can be
|
||||
* found in the MSDN. As a general rule of thumb, if the method is reentrant (i.e., it can be called
|
||||
* from any thread), it is declared in the {@code *Reentrant} variant of the wrapper interface.
|
||||
* There are few of these. Unless the documentation explicitly lists the method as reentrant, do not
|
||||
* declare it there. Many methods appear to execute successfully from the wrong thread, but cause
|
||||
* latent issues. A practice to prevent accidental use of non-reentrant methods outside of the
|
||||
* client's owning thread is to ensure that only the owning thread can see the full interface. All
|
||||
* other threads should only have access to the reentrant interface.
|
||||
*
|
||||
* If you implement methods that introduce a new callback class, use the existing callback type
|
||||
* hierarchies as a model. There are many classes to implement. Furthermore, be sure to keep a
|
||||
* reference to any active callback instances within the wrapper that uses them. The JNA has no way
|
||||
* of knowing whether or not the instance is still being used by the external C/C++ library. If you
|
||||
* do not store a reference, the JVM will think it's garbage and free it, even though COM is still
|
||||
* using it. Drop the reference only when you are certain nothing external has a reference to it.
|
||||
*/
|
||||
public class DbgEng {
|
||||
private static final Cleaner CLEANER = Cleaner.create();
|
||||
|
||||
private static class ReleaseCOMObject implements Runnable {
|
||||
private final IUnknown obj;
|
||||
|
||||
ReleaseCOMObject(IUnknown obj) {
|
||||
this.obj = obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Msg.debug(this, "Releasing COM object: " + obj);
|
||||
obj.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReleaseHANDLE implements Runnable {
|
||||
private final HANDLE handle;
|
||||
|
||||
public ReleaseHANDLE(HANDLE handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Kernel32Util.closeHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
public static class OpaqueCleanable {
|
||||
@SuppressWarnings("unused") // A reference to control GC
|
||||
private final Object state;
|
||||
@SuppressWarnings("unused") // A reference to control GC
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public OpaqueCleanable(Object state, Cleaner.Cleanable cleanable) {
|
||||
this.state = state;
|
||||
this.cleanable = cleanable;
|
||||
}
|
||||
}
|
||||
|
||||
public static OpaqueCleanable releaseWhenPhantom(Object owner, IUnknown obj) {
|
||||
ReleaseCOMObject state = new ReleaseCOMObject(obj);
|
||||
return new OpaqueCleanable(state, CLEANER.register(owner, state));
|
||||
}
|
||||
|
||||
public static OpaqueCleanable releaseWhenPhantom(Object owner, HANDLE handle) {
|
||||
ReleaseHANDLE state = new ReleaseHANDLE(handle);
|
||||
return new OpaqueCleanable(state, CLEANER.register(owner, state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a debug session.
|
||||
*
|
||||
* See {@code DebugConnect} or {@code DebugConnectWide} on the MSDN.
|
||||
*
|
||||
* @param remoteOptions the options, like those given to {@code -remote}
|
||||
* @return a new client connected as specified
|
||||
*/
|
||||
public static DebugClient debugConnect(String remoteOptions) {
|
||||
WString options = new WString(remoteOptions);
|
||||
return DebugClientInternal.tryPreferredInterfaces((refiid,
|
||||
ppClient) -> DbgEngNative.INSTANCE.DebugConnectWide(options, refiid, ppClient));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a debug client.
|
||||
*
|
||||
* Typically, this client is connected to the "local server". See {@code DebugCreate} on the
|
||||
* MSDN.
|
||||
*
|
||||
* @return a new client
|
||||
*/
|
||||
public static DebugClient debugCreate() {
|
||||
return DebugClientInternal.tryPreferredInterfaces(DbgEngNative.INSTANCE::DebugCreate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a debug client with the given options.
|
||||
*
|
||||
* See {@code DebugCreateEx} on the MSDN.
|
||||
*
|
||||
* @param options the options
|
||||
* @return a new client
|
||||
*/
|
||||
public static DebugClient debugCreate(int options) {
|
||||
DWORD dwOpts = new DWORD(options);
|
||||
return DebugClientInternal.tryPreferredInterfaces(
|
||||
(refiid, ppClient) -> DbgEngNative.INSTANCE.DebugCreateEx(refiid, dwOpts, ppClient));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
/**
|
||||
* A wrapper for {@code IDebugAdvanced} and its newer variants.
|
||||
*/
|
||||
public interface DebugAdvanced {
|
||||
public static class DebugThreadBasicInformation {
|
||||
public final Integer exitStatus;
|
||||
public final Integer priorityClass;
|
||||
public final Integer priority;
|
||||
public final Long createTime;
|
||||
public final Long exitTime;
|
||||
public final Long kernelTime;
|
||||
public final Long userTime;
|
||||
public final Long startOffset;
|
||||
public final Long affinity;
|
||||
|
||||
public DebugThreadBasicInformation(Integer exitStatus, Integer priorityClass,
|
||||
Integer priority, Long createTime, Long exitTime, Long kernelTime, Long userTime,
|
||||
Long startOffset, Long affinity) {
|
||||
this.exitStatus = exitStatus;
|
||||
this.priorityClass = priorityClass;
|
||||
this.priority = priority;
|
||||
this.createTime = createTime;
|
||||
this.exitTime = exitTime;
|
||||
this.kernelTime = kernelTime;
|
||||
this.userTime = userTime;
|
||||
this.startOffset = startOffset;
|
||||
this.affinity = affinity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("<DebugThreadBasicInformation:\n");
|
||||
if (exitStatus != null) {
|
||||
sb.append(" exitStatus: " + exitStatus + "\n");
|
||||
}
|
||||
if (priorityClass != null) {
|
||||
sb.append(" priorityClass: " + priorityClass + "\n");
|
||||
}
|
||||
if (priority != null) {
|
||||
sb.append(" priority: " + priority + "\n");
|
||||
}
|
||||
if (createTime != null) {
|
||||
sb.append(" createTime: " + createTime + "\n");
|
||||
}
|
||||
if (exitTime != null) {
|
||||
sb.append(" exitTime: " + exitTime + "\n");
|
||||
}
|
||||
if (kernelTime != null) {
|
||||
sb.append(" kernelTime: " + kernelTime + "\n");
|
||||
}
|
||||
if (userTime != null) {
|
||||
sb.append(" userTime: " + userTime + "\n");
|
||||
}
|
||||
if (startOffset != null) {
|
||||
sb.append(" startOffset: " + startOffset + "\n");
|
||||
}
|
||||
if (affinity != null) {
|
||||
sb.append(" affinity: " + affinity + "\n");
|
||||
}
|
||||
sb.append(">");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
DebugThreadBasicInformation getThreadBasicInformation(DebugThreadId tid);
|
||||
}
|
||||
@@ -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 agent.dbgeng.dbgeng;
|
||||
|
||||
import agent.dbgeng.jna.dbgeng.WinNTExtra.Machine;
|
||||
import ghidra.comm.util.BitmaskSet;
|
||||
import ghidra.comm.util.BitmaskUniverse;
|
||||
|
||||
/**
|
||||
* A wrapper for {@code IDebugBreakpoint} and its newer variants.
|
||||
*/
|
||||
public interface DebugBreakpoint {
|
||||
public static enum BreakType {
|
||||
CODE, DATA, TIME, INLINE;
|
||||
}
|
||||
|
||||
public static class BreakFullType {
|
||||
public final BreakType breakType;
|
||||
public final Machine procType; // TODO: Guessing the values are from WinNT
|
||||
|
||||
public BreakFullType(BreakType breakType, Machine procType) {
|
||||
this.breakType = breakType;
|
||||
this.procType = procType;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum BreakFlags implements BitmaskUniverse {
|
||||
GO_ONLY(1 << 0), //
|
||||
DEFERRED(1 << 1), //
|
||||
ENABLED(1 << 2), //
|
||||
ADDER_ONLY(1 << 3), //
|
||||
ONE_SHOT(1 << 4), //
|
||||
;
|
||||
|
||||
private BreakFlags(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
private final int mask;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum BreakAccess implements BitmaskUniverse {
|
||||
READ(1 << 0), //
|
||||
WRITE(1 << 1), //
|
||||
EXECUTE(1 << 2), //
|
||||
IO(1 << 3), //
|
||||
;
|
||||
|
||||
private BreakAccess(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
private final int mask;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BreakDataParameters {
|
||||
public int size;
|
||||
public BitmaskSet<BreakAccess> access;
|
||||
|
||||
public BreakDataParameters(int size, BitmaskSet<BreakAccess> access) {
|
||||
this.size = size;
|
||||
this.access = access;
|
||||
}
|
||||
}
|
||||
|
||||
void remove();
|
||||
|
||||
int getId();
|
||||
|
||||
BreakFullType getType();
|
||||
|
||||
DebugClient getAdder();
|
||||
|
||||
BitmaskSet<BreakFlags> getFlags();
|
||||
|
||||
void addFlags(BitmaskSet<BreakFlags> flags);
|
||||
|
||||
void addFlags(BreakFlags... flags);
|
||||
|
||||
void removeFlags(BitmaskSet<BreakFlags> flags);
|
||||
|
||||
void removeFlags(BreakFlags... flags);
|
||||
|
||||
void setFlags(BitmaskSet<BreakFlags> flags);
|
||||
|
||||
void setFlags(BreakFlags... flags);
|
||||
|
||||
long getOffset();
|
||||
|
||||
void setOffset(long offset);
|
||||
|
||||
BreakDataParameters getDataParameters();
|
||||
|
||||
void setDataParameters(BreakDataParameters params);
|
||||
|
||||
void setDataParameters(int size, BitmaskSet<BreakAccess> access);
|
||||
|
||||
void setDataParameters(int size, BreakAccess... access);
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.jna.platform.win32.WinBase;
|
||||
|
||||
import agent.dbgeng.dbgeng.DebugRunningProcess.Description;
|
||||
import agent.dbgeng.dbgeng.DebugRunningProcess.Description.ProcessDescriptionFlags;
|
||||
import ghidra.comm.util.BitmaskSet;
|
||||
import ghidra.comm.util.BitmaskUniverse;
|
||||
|
||||
/**
|
||||
* A wrapper for {@code IDebugClient} and its newer variants.
|
||||
*/
|
||||
public interface DebugClient extends DebugClientReentrant {
|
||||
public static enum ExecutionState {
|
||||
RUNNING, STOPPED;
|
||||
}
|
||||
|
||||
public static enum DebugStatus {
|
||||
NO_CHANGE(false, null, 13), //
|
||||
GO(true, ExecutionState.RUNNING, 10), //
|
||||
GO_HANDLED(true, ExecutionState.RUNNING, 9), //
|
||||
GO_NOT_HANDLED(true, ExecutionState.RUNNING, 8), //
|
||||
STEP_OVER(true, ExecutionState.RUNNING, 7), //
|
||||
STEP_INTO(true, ExecutionState.RUNNING, 5), //
|
||||
BREAK(false, ExecutionState.STOPPED, 0), //
|
||||
NO_DEBUGGEE(true, null, 1), // shouldWait is true to handle process creation
|
||||
STEP_BRANCH(true, null, 6), //
|
||||
IGNORE_EVENT(false, null, 11), //
|
||||
RESTART_REQUESTED(true, null, 12), //
|
||||
REVERSE_GO(true, null, 0xff), //
|
||||
REVERSE_STEP_BRANCH(true, null, 0xff), //
|
||||
REVERSE_STEP_OVER(true, null, 0xff), //
|
||||
REVERSE_STEP_INTO(true, null, 0xff), //
|
||||
OUT_OF_SYNC(false, null, 2), //
|
||||
WAIT_INPUT(false, null, 3), //
|
||||
TIMEOUT(false, null, 4), //
|
||||
;
|
||||
|
||||
public static final long MASK = 0xaf;
|
||||
public static final long INSIDE_WAIT = 0x100000000L;
|
||||
public static final long WAIT_TIMEOUT = 0x200000000L;
|
||||
|
||||
DebugStatus(boolean shouldWait, ExecutionState threadState, int precedence) {
|
||||
this.shouldWait = shouldWait;
|
||||
this.threadState = threadState;
|
||||
this.precedence = precedence;
|
||||
}
|
||||
|
||||
public final boolean shouldWait;
|
||||
public final ExecutionState threadState;
|
||||
public final int precedence; // 0 is highest
|
||||
|
||||
public static DebugStatus fromArgument(long argument) {
|
||||
return values()[(int) (argument & MASK)];
|
||||
}
|
||||
|
||||
public static boolean isInsideWait(long argument) {
|
||||
return (argument & INSIDE_WAIT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isWaitTimeout(long argument) {
|
||||
return (argument & WAIT_TIMEOUT) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum SessionStatus {
|
||||
ACTIVE, //
|
||||
END_SESSION_ACTIVE_TERMINATE,//
|
||||
END_SESSION_ACTIVE_DETACH, //
|
||||
END_SESSION_PASSIVE, //
|
||||
END, //
|
||||
REBOOT, //
|
||||
HIBERNATE, //
|
||||
FAILURE, //
|
||||
;
|
||||
}
|
||||
|
||||
public static enum ChangeDebuggeeState implements BitmaskUniverse {
|
||||
ALL(0xffffffff), //
|
||||
REGISTERS(1 << 0), //
|
||||
DATA(1 << 1), //
|
||||
REFRESH(1 << 2), //
|
||||
;
|
||||
|
||||
private ChangeDebuggeeState(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
private final int mask;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum ChangeEngineState implements BitmaskUniverse {
|
||||
ALL(0xffffffff), //
|
||||
CURRENT_THREAD(1 << 0), //
|
||||
EFFECTIVE_PROCESSOR(1 << 1), //
|
||||
BREAKPOINTS(1 << 2), //
|
||||
CODE_LEVEL(1 << 3), //
|
||||
EXECUTION_STATUS(1 << 4), //
|
||||
ENGINE_OPTIONS(1 << 5), //
|
||||
LOG_FILE(1 << 6), //
|
||||
RADIX(1 << 7), //
|
||||
EVENT_FILTERS(1 << 8), //
|
||||
PROCESS_OPTIONS(1 << 9), //
|
||||
EXTENSIONS(1 << 10), //
|
||||
SYSTEMS(1 << 11), //
|
||||
ASSEMBLY_OPTIONS(1 << 12), //
|
||||
EXPRESSION_SYNTAX(1 << 13), //
|
||||
TEXT_REPLACEMENTS(1 << 14), //
|
||||
;
|
||||
|
||||
private ChangeEngineState(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
private final int mask;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum ChangeSymbolState implements BitmaskUniverse {
|
||||
ALL(0xffffffff), //
|
||||
LOADS(1 << 0), //
|
||||
UNLOADS(1 << 1), //
|
||||
SCOPE(1 << 2), //
|
||||
PATHS(1 << 3), //
|
||||
SYMBOL_OPTIONS(1 << 4), //
|
||||
TYPE_OPTIONS(1 << 5), //
|
||||
;
|
||||
|
||||
private ChangeSymbolState(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
private final int mask;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum DebugAttachFlags implements BitmaskUniverse {
|
||||
DEFAULT(0), //
|
||||
NONINVASIVE(1 << 0), //
|
||||
EXISTING(1 << 1), //
|
||||
NONINVASIVE_NO_SUSPEND(1 << 2), //
|
||||
INVASIVE_NO_INITIAL_BREAK(1 << 3), //
|
||||
INVASIVE_RESUME_PROCESS(1 << 4), //
|
||||
NONINVASIVE_ALLOW_PARTIAL(1 << 5), //
|
||||
;
|
||||
|
||||
DebugAttachFlags(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
private final int mask;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum DebugCreateFlags implements BitmaskUniverse {
|
||||
DEBUG_PROCESS(WinBase.DEBUG_PROCESS), //
|
||||
DEBUG_ONLY_THIS_PROCESS(WinBase.DEBUG_ONLY_THIS_PROCESS), //
|
||||
CREATE_SUSPENDED(WinBase.CREATE_SUSPENDED), //
|
||||
DETACHED_PROCESS(WinBase.DETACHED_PROCESS), //
|
||||
|
||||
CREATE_NEW_CONSOLE(WinBase.CREATE_NEW_CONSOLE), //
|
||||
//NORMAL_PRIORITY_CLASS(WinBase.NORMAL_PRIORITY_CLASS), //
|
||||
//IDLE_PRIORITY_CLASS(WinBase.IDLE_PRIORITY_CLASS), //
|
||||
//HIGH_PRIORITY_CLASS(WinBase.HIGH_PRIORITY_CLASS), //
|
||||
|
||||
//REALTIME_PRIORITY_CLASS(WinBase.REALTIME_PRIORITY_CLASS), //
|
||||
CREATE_NEW_PROCESS_GROUP(WinBase.CREATE_NEW_PROCESS_GROUP), //
|
||||
CREATE_UNICODE_ENVIRONMENT(WinBase.CREATE_UNICODE_ENVIRONMENT), //
|
||||
CREATE_SEPARATE_WOW_VDM(WinBase.CREATE_SEPARATE_WOW_VDM), //
|
||||
|
||||
CREATE_SHARED_WOW_VDM(WinBase.CREATE_SHARED_WOW_VDM), //
|
||||
CREATE_FORCEDOS(WinBase.CREATE_FORCEDOS), //
|
||||
//BELOW_NORMAL_PRIORITY_CLASS(WinBase.BELOW_NORMAL_PRIORITY_CLASS), //
|
||||
//ABOVE_NORMAL_PRIORITY_CLASS(WinBase.ABOVE_NORMAL_PRIORITY_CLASS), //
|
||||
|
||||
INHERIT_PARENT_AFFINITY(WinBase.INHERIT_PARENT_AFFINITY), //
|
||||
//INHERIT_CALLER_PRIORITY(WinBase.INHERIT_CALLER_PRIORITY), //
|
||||
CREATE_PROTECTED_PROCESS(WinBase.CREATE_PROTECTED_PROCESS), //
|
||||
EXTENDED_STARTUPINFO_PRESENT(WinBase.EXTENDED_STARTUPINFO_PRESENT), //
|
||||
|
||||
//PROCESS_MODE_BACKGROUND_BEGIN(WinBase.PROCESS_MODE_BACKGROUND_BEGIN), //
|
||||
//PROCESS_MODE_BACKGROUND_END(WinBase.PROCESS_MODE_BACKGROUND_END), //
|
||||
|
||||
CREATE_BREAKAWAY_FROM_JOB(WinBase.CREATE_BREAKAWAY_FROM_JOB), //
|
||||
CREATE_PRESERVE_CODE_AUTHZ_LEVEL(WinBase.CREATE_PRESERVE_CODE_AUTHZ_LEVEL), //
|
||||
CREATE_DEFAULT_ERROR_MODE(WinBase.CREATE_DEFAULT_ERROR_MODE), //
|
||||
CREATE_NO_WINDOW(WinBase.CREATE_NO_WINDOW), //
|
||||
|
||||
//PROFILE_USER(WinBase.PROFILE_USER), //
|
||||
//PROFILE_KERNEL(WinBase.PROFILE_KERNEL), //
|
||||
//PROFILE_SERVER(WinBase.PROFILE_SERVER), //
|
||||
//CREATE_IGNORE_SYSTEM_DEFAULT(WinBase.CREATE_IGNORE_SYSTEM_DEFAULT), //
|
||||
DEBUG_CREATE_NO_DEBUG_HEAP(0x00000400), //
|
||||
DEBUG_CREATE_THROUGH_RTL(0x00010000), //
|
||||
;
|
||||
|
||||
DebugCreateFlags(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
private final int mask;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public enum DebugEndSessionFlags {
|
||||
DEBUG_END_PASSIVE(0x00000000),
|
||||
DEBUG_END_ACTIVE_TERMINATE(0x00000001),
|
||||
DEBUG_END_ACTIVE_DETACH(0x00000002),
|
||||
DEBUG_END_REENTRANT(0x00000003),
|
||||
DEBUG_END_DISCONNECT(0x00000004);
|
||||
|
||||
DebugEndSessionFlags(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private final int value;
|
||||
|
||||
public long getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public enum DebugOutputFlags {
|
||||
DEBUG_OUTPUT_NORMAL(0x1), //
|
||||
DEBUG_OUTPUT_ERROR(0x2), //
|
||||
DEBUG_OUTPUT_WARNING(0x4), //
|
||||
DEBUG_OUTPUT_VERBOSE(0x8), //
|
||||
DEBUG_OUTPUT_PROMPT(0x10), //
|
||||
DEBUG_OUTPUT_PROMPT_REGISTERS(0x20), //
|
||||
DEBUG_OUTPUT_EXTENSION_WARNING(0x40), //
|
||||
DEBUG_OUTPUT_DEBUGGEE(0x80), //
|
||||
DEBUG_OUTPUT_DEBUGGEE_PROMPT(0x100), //
|
||||
DEBUG_OUTPUT_SYMBOLS(0x200);
|
||||
|
||||
DebugOutputFlags(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private final int value;
|
||||
|
||||
public long getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the advanced interface to this client.
|
||||
*
|
||||
* @return the advanced interface
|
||||
*/
|
||||
DebugAdvanced getAdvanced();
|
||||
|
||||
/**
|
||||
* Obtain the control interface to this client
|
||||
*
|
||||
* @return the control interface
|
||||
*/
|
||||
@Override
|
||||
DebugControl getControl();
|
||||
|
||||
/**
|
||||
* Obtain the data spaces interface to this client
|
||||
*
|
||||
* @return the data spaces interface
|
||||
*/
|
||||
DebugDataSpaces getDataSpaces();
|
||||
|
||||
/**
|
||||
* Obtain the registers interface to this client
|
||||
*
|
||||
* @return the registers interface
|
||||
*/
|
||||
DebugRegisters getRegisters();
|
||||
|
||||
/**
|
||||
* Obtain the symbols interface to this client
|
||||
*
|
||||
* @return the symbols interface
|
||||
*/
|
||||
DebugSymbols getSymbols();
|
||||
|
||||
/**
|
||||
* Obtain the system objects interface to this client
|
||||
*
|
||||
* @return the system objects interface
|
||||
*/
|
||||
DebugSystemObjects getSystemObjects();
|
||||
|
||||
/**
|
||||
* The the ID for the local server
|
||||
*
|
||||
* @return the ID
|
||||
*/
|
||||
DebugServerId getLocalServer();
|
||||
|
||||
void attachKernel(long flags, String options);
|
||||
|
||||
void startProcessServer(String options);
|
||||
|
||||
DebugServerId connectProcessServer(String options);
|
||||
|
||||
boolean dispatchCallbacks(int timeout);
|
||||
|
||||
void flushCallbacks();
|
||||
|
||||
default void dispatchCallbacks() {
|
||||
this.dispatchCallbacks(-1);
|
||||
}
|
||||
|
||||
void exitDispatch(DebugClient client);
|
||||
|
||||
default void exitDispatch() {
|
||||
exitDispatch(this);
|
||||
}
|
||||
|
||||
void setInputCallbacks(DebugInputCallbacks cb);
|
||||
|
||||
void setOutputCallbacks(DebugOutputCallbacks cb);
|
||||
|
||||
void setEventCallbacks(DebugEventCallbacks cb);
|
||||
|
||||
List<DebugRunningProcess> getRunningProcesses(DebugServerId server);
|
||||
|
||||
Description getProcessDescription(DebugServerId si, int systemId,
|
||||
BitmaskSet<ProcessDescriptionFlags> flags);
|
||||
|
||||
void attachProcess(DebugServerId si, int processId, BitmaskSet<DebugAttachFlags> attachFlags);
|
||||
|
||||
void createProcess(DebugServerId si, String commandLine,
|
||||
BitmaskSet<DebugCreateFlags> createFlags);
|
||||
|
||||
void createProcessAndAttach(DebugServerId si, String commandLine,
|
||||
BitmaskSet<DebugCreateFlags> createFlags, int processId,
|
||||
BitmaskSet<DebugAttachFlags> attachFlags);
|
||||
|
||||
void startServer(String options);
|
||||
|
||||
// Only in IDebugClient2
|
||||
|
||||
void waitForProcessServerEnd(int timeout);
|
||||
|
||||
default void waitForProcessServerEnd() {
|
||||
waitForProcessServerEnd(-1);
|
||||
}
|
||||
|
||||
void terminateCurrentProcess();
|
||||
|
||||
void detachCurrentProcess();
|
||||
|
||||
void abandonCurrentProcess();
|
||||
|
||||
void connectSession(int flags);
|
||||
|
||||
void endSession(DebugEndSessionFlags flags);
|
||||
|
||||
// Only in IDebugClient4+
|
||||
|
||||
void openDumpFileWide(String fileName);
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
/**
|
||||
* An interface containing the subset of {@link DebugClient} methods which are reentrant.
|
||||
*
|
||||
* All other methods should be called only by the thread which created the client.
|
||||
*/
|
||||
public interface DebugClientReentrant {
|
||||
/**
|
||||
* Create a new client for the calling thread, connected to the same session as this client.
|
||||
*
|
||||
* @return the new client
|
||||
*/
|
||||
DebugClient createClient();
|
||||
|
||||
/**
|
||||
* Get the reentrant control interface to the client
|
||||
*
|
||||
* @return the control interface
|
||||
*/
|
||||
DebugControlReentrant getControl();
|
||||
|
||||
/**
|
||||
* End a session without acquiring locks
|
||||
*
|
||||
* Note. This method calls {@code IDebugClient::EndSession(DEBUG_END_REENTRANT)}. Per the MSDN,
|
||||
* this may leave the engine in an indeterminate state. The engine should no longer be used by
|
||||
* this process. It's really only appropriate to use this method when terminating the debugger.
|
||||
*/
|
||||
void endSessionReentrant();
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.jna.platform.win32.WinBase;
|
||||
import com.sun.jna.platform.win32.COM.COMException;
|
||||
|
||||
import agent.dbgeng.dbgeng.DebugBreakpoint.BreakType;
|
||||
import agent.dbgeng.dbgeng.DebugClient.DebugStatus;
|
||||
import ghidra.comm.util.BitmaskSet;
|
||||
import ghidra.comm.util.BitmaskUniverse;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* A wrapper for {@code IDebugControl} and its newer variants.
|
||||
*/
|
||||
public interface DebugControl extends DebugControlReentrant {
|
||||
public static final BitmaskSet<DebugOutputControl> SET_ALL_CLIENTS =
|
||||
BitmaskSet.of(DebugOutputControl.ALL_CLIENTS);
|
||||
public static final BitmaskSet<DebugExecute> SET_DEFAULT = BitmaskSet.of(DebugExecute.DEFAULT);
|
||||
|
||||
public static enum DebugOutputLevel implements BitmaskUniverse {
|
||||
NORMAL(1 << 0), //
|
||||
ERROR(1 << 1), //
|
||||
WARNING(1 << 2), //
|
||||
VERBOSE(1 << 3), //
|
||||
PROMPT(1 << 4), //
|
||||
PROMPT_REGISTERS(1 << 5), //
|
||||
EXTENSION_WARNING(1 << 6), //
|
||||
OUTPUT_DEBUGEE(1 << 7), //
|
||||
OUTPUT_DEBUGEE_PROMPT(1 << 8), //
|
||||
OUTPUT_SYMBOLS(1 << 9), //
|
||||
OUTPUT_STATUS(1 << 10), //
|
||||
;
|
||||
|
||||
private final int mask;
|
||||
|
||||
DebugOutputLevel(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum DebugOutputControl implements BitmaskUniverse {
|
||||
THIS_CLIENT(0), //
|
||||
ALL_CLIENTS(1), //
|
||||
ALL_OTHER_CLIENTS(2), //
|
||||
IGNORE(3), //
|
||||
LOG_ONLY(4), //
|
||||
SEND_MASK(7), //
|
||||
NOT_LOGGED(1 << 3), //
|
||||
OVERRIDE_MASK(1 << 4), //
|
||||
DML(1 << 5), //
|
||||
AMBIENT_DML(0xfffffffe), //
|
||||
AMBIENT_TEXT(0xffffffff), //
|
||||
AMBIENT(0xffffffff), //
|
||||
;
|
||||
|
||||
private final int mask;
|
||||
|
||||
DebugOutputControl(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum DebugExecute implements BitmaskUniverse {
|
||||
DEFAULT(0), //
|
||||
ECHO(1 << 0), //
|
||||
NOT_LOGGED(1 << 1), //
|
||||
NO_REPEAT(1 << 2), //
|
||||
;
|
||||
|
||||
private final int mask;
|
||||
|
||||
DebugExecute(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum DebugInterrupt {
|
||||
ACTIVE, //
|
||||
PASSIVE, //
|
||||
EXIT, //
|
||||
;
|
||||
}
|
||||
|
||||
boolean getInterrupt();
|
||||
|
||||
int getInterruptTimeout();
|
||||
|
||||
void setInterruptTimeout(int seconds);
|
||||
|
||||
void print(BitmaskSet<DebugOutputLevel> levels, String message);
|
||||
|
||||
/**
|
||||
* A shortcut for {@link #print(BitmaskSet, String)} that includes a newline.
|
||||
*
|
||||
* @param levels the log levels for the message
|
||||
* @param message the message
|
||||
*/
|
||||
void println(BitmaskSet<DebugOutputLevel> levels, String message);
|
||||
|
||||
/**
|
||||
* A shortcut for {@link #print(BitmaskSet, String)} that applies to a single level.
|
||||
*
|
||||
* @param level the log level for the message
|
||||
* @param message the message
|
||||
*/
|
||||
default void print(DebugOutputLevel level, String message) {
|
||||
print(BitmaskSet.of(level), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* A shortcut for {@link #print(BitmaskSet, String)} that includes a newline and applies to a
|
||||
* single level.
|
||||
*
|
||||
* @param level the log level for the message
|
||||
* @param message the message
|
||||
*/
|
||||
default void println(DebugOutputLevel level, String message) {
|
||||
println(BitmaskSet.of(level), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* A shortcut for {@link #print(BitmaskSet, String)} at normal level.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
default void out(String message) {
|
||||
print(DebugOutputLevel.NORMAL, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* A shortcut for {@link #println(BitmaskSet, String)} at normal level.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
default void outln(String message) {
|
||||
println(DebugOutputLevel.NORMAL, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* A shortcut for {@link #print(BitmaskSet, String)} at warning level.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
default void warn(String message) {
|
||||
print(DebugOutputLevel.WARNING, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* A shortcut for {@link #println(BitmaskSet, String)} at warning level.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
default void warnln(String message) {
|
||||
println(DebugOutputLevel.WARNING, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* A shortcut for {@link #print(BitmaskSet, String)} at error level.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
default void err(String message) {
|
||||
print(DebugOutputLevel.ERROR, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* A shortcut for {@link #println(BitmaskSet, String)} at error level.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
default void errln(String message) {
|
||||
println(DebugOutputLevel.ERROR, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* A shortcut for {@link #print(BitmaskSet, String)} at verbose level.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
default void verb(String message) {
|
||||
print(DebugOutputLevel.VERBOSE, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* A shortcut for {@link #println(BitmaskSet, String)} at verbose level.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
default void verbln(String message) {
|
||||
println(DebugOutputLevel.VERBOSE, message);
|
||||
}
|
||||
|
||||
<T extends DebugValue> T evaluate(Class<T> desiredType, String expression);
|
||||
|
||||
void execute(BitmaskSet<DebugOutputControl> ctl, String str, BitmaskSet<DebugExecute> flags);
|
||||
|
||||
/**
|
||||
* A shortcut for {@link #execute(BitmaskSet, String, BitmaskSet)} outputting to all clients
|
||||
* with the default execution flag.
|
||||
*
|
||||
* @param str the command string
|
||||
*/
|
||||
default void execute(String str) {
|
||||
execute(SET_ALL_CLIENTS, str, SET_DEFAULT);
|
||||
}
|
||||
|
||||
void prompt(BitmaskSet<DebugOutputControl> ctl, String message);
|
||||
|
||||
String getPromptText();
|
||||
|
||||
void returnInput(String input);
|
||||
|
||||
DebugStatus getExecutionStatus();
|
||||
|
||||
void setExecutionStatus(DebugStatus status);
|
||||
|
||||
int getNumberBreakpoints();
|
||||
|
||||
DebugBreakpoint getBreakpointByIndex(int index);
|
||||
|
||||
/**
|
||||
* Shortcut to retrieve all breakpoints for the current process.
|
||||
*
|
||||
* Uses {@link #getNumberBreakpoints()} and {@link #getBreakpointByIndex(int)} to enumerate all
|
||||
* breakpoints for the current process.
|
||||
*
|
||||
* @return the list of retrieved breakpoints.
|
||||
*/
|
||||
default List<DebugBreakpoint> getBreakpoints() {
|
||||
int count = getNumberBreakpoints();
|
||||
List<DebugBreakpoint> result = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
try {
|
||||
result.add(getBreakpointByIndex(i));
|
||||
}
|
||||
catch (COMException e) {
|
||||
if (!COMUtilsExtra.isE_NOINTERFACE(e)) {
|
||||
throw e;
|
||||
}
|
||||
Msg.trace(this, "Discarding private breakpoint at index " + i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a breakpoint by ID
|
||||
*
|
||||
* According to the MSDN, though the IDs may be global, this method should only succeed for
|
||||
* breakpoints belonging to the current process.
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
DebugBreakpoint getBreakpointById(int id);
|
||||
|
||||
DebugBreakpoint addBreakpoint(BreakType type, int desiredId);
|
||||
|
||||
DebugBreakpoint addBreakpoint(BreakType type);
|
||||
|
||||
void waitForEvent(int timeout);
|
||||
|
||||
DebugEventInformation getLastEventInformation();
|
||||
|
||||
DebugStackInformation getStackTrace(long frameOffset, long stackOffset, long instructionOffset);
|
||||
|
||||
/**
|
||||
* Shortcut for {@link #waitForEvent(int)} with infinite timeout.
|
||||
*/
|
||||
default void waitForEvent() {
|
||||
waitForEvent(WinBase.INFINITE);
|
||||
}
|
||||
|
||||
int getActualProcessorType();
|
||||
|
||||
int getEffectiveProcessorType();
|
||||
|
||||
int getExecutingProcessorType();
|
||||
|
||||
int getDebuggeeType();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import agent.dbgeng.dbgeng.DebugControl.DebugInterrupt;
|
||||
|
||||
/**
|
||||
* An interface containing the subset of {@link DebugControl} methods which are reentrant.
|
||||
*
|
||||
* All other methods should be called only by the thread which created the client.
|
||||
*/
|
||||
public interface DebugControlReentrant {
|
||||
void setInterrupt(DebugInterrupt interrupt);
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
import com.sun.jna.platform.win32.COM.COMException;
|
||||
|
||||
import ghidra.comm.util.BitmaskSet;
|
||||
import ghidra.comm.util.BitmaskUniverse;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* A wrapper for {@code IDebugDataSpaces} and its newer variants.
|
||||
*/
|
||||
public interface DebugDataSpaces {
|
||||
public enum PageState {
|
||||
COMMIT(0x1000), FREE(0x10000), RESERVE(0x2000);
|
||||
|
||||
private final int val;
|
||||
|
||||
private PageState(int val) {
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
public static PageState byValue(int val) {
|
||||
for (PageState state : values()) {
|
||||
if (state.val == val) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
Msg.warn(PageState.class, "No such value: 0x" + Integer.toHexString(val));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public enum PageProtection implements BitmaskUniverse {
|
||||
NOACCESS(1 << 0, false, false, false), //
|
||||
READONLY(1 << 1, true, false, false), //
|
||||
READWRITE(1 << 2, true, true, false), //
|
||||
WRITE_COPY(1 << 3, true, true, false), // Becomes READWRITE after copy
|
||||
EXECUTE(1 << 4, false, false, true), //
|
||||
EXECUTE_READ(1 << 5, true, false, true), //
|
||||
EXECUTE_READWRITE(1 << 6, true, true, true), //
|
||||
EXECUTE_WRITECOPY(1 << 7, true, true, true), //
|
||||
//
|
||||
GUARD(1 << 8, false, false, false), //
|
||||
NOCACHE(1 << 9, false, false, false), //
|
||||
WRITECOMBINE(1 << 10, false, false, false), //
|
||||
;
|
||||
|
||||
private PageProtection(int mask, boolean isRead, boolean isWrite, boolean isExecute) {
|
||||
this.mask = mask;
|
||||
this.isRead = isRead;
|
||||
this.isWrite = isWrite;
|
||||
this.isExecute = isExecute;
|
||||
}
|
||||
|
||||
final int mask;
|
||||
final boolean isRead;
|
||||
final boolean isWrite;
|
||||
final boolean isExecute;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
|
||||
public boolean isRead() {
|
||||
return isRead;
|
||||
}
|
||||
|
||||
public boolean isWrite() {
|
||||
return isWrite;
|
||||
}
|
||||
|
||||
public boolean isExecute() {
|
||||
return isExecute;
|
||||
}
|
||||
}
|
||||
|
||||
public enum PageType {
|
||||
NONE(0), //
|
||||
IMAGE(0x1000000), //
|
||||
MAPPED(0x40000), //
|
||||
PRIVATE(0x20000), //
|
||||
;
|
||||
|
||||
private final int val;
|
||||
|
||||
private PageType(int val) {
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
public static PageType byValue(int val) {
|
||||
for (PageType type : values()) {
|
||||
if (type.val == val) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
Msg.warn(PageType.class, "No such value: 0x" + Integer.toHexString(val));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DebugMemoryBasicInformation {
|
||||
public final long baseAddress;
|
||||
public final long allocationBase;
|
||||
public final Set<PageProtection> allocationProtect;
|
||||
public final long regionSize;
|
||||
public final PageState state;
|
||||
public final Set<PageProtection> protect;
|
||||
public final PageType type;
|
||||
|
||||
public DebugMemoryBasicInformation(long baseAddress, long allocationBase,
|
||||
BitmaskSet<PageProtection> allocationProtect, long regionSize, PageState state,
|
||||
BitmaskSet<PageProtection> protect, PageType type) {
|
||||
this.baseAddress = baseAddress;
|
||||
this.allocationBase = allocationBase;
|
||||
this.allocationProtect = Collections.unmodifiableSet(allocationProtect);
|
||||
this.regionSize = regionSize;
|
||||
this.state = state;
|
||||
this.protect = Collections.unmodifiableSet(protect);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<DebugMemoryBasicInformation:\n" + //
|
||||
" baseAddress=" + Long.toHexString(baseAddress) + "h,\n" + //
|
||||
" allocationBase=" + Long.toHexString(allocationBase) + "h,\n" + //
|
||||
" allocationProtect=" + allocationProtect + ",\n" + //
|
||||
" regionSize=" + Long.toHexString(regionSize) + "h,\n" + //
|
||||
" state=" + state + ",\n" + //
|
||||
" protect=" + protect + ",\n" + //
|
||||
" type=" + type + "\n" + //
|
||||
">";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(baseAddress, allocationBase, allocationProtect, regionSize, state,
|
||||
protect, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof DebugMemoryBasicInformation)) {
|
||||
return false;
|
||||
}
|
||||
DebugMemoryBasicInformation that = (DebugMemoryBasicInformation) obj;
|
||||
if (this.baseAddress != that.baseAddress) {
|
||||
return false;
|
||||
}
|
||||
if (this.allocationBase != that.allocationBase) {
|
||||
return false;
|
||||
}
|
||||
if (!this.allocationProtect.equals(that.allocationProtect)) {
|
||||
return false;
|
||||
}
|
||||
if (this.regionSize != that.regionSize) {
|
||||
return false;
|
||||
}
|
||||
if (this.state != that.state) {
|
||||
return false;
|
||||
}
|
||||
if (!this.protect.equals(that.protect)) {
|
||||
return false;
|
||||
}
|
||||
if (this.type != that.type) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int readVirtual(long offset, ByteBuffer into, int len);
|
||||
|
||||
int writeVirtual(long offset, ByteBuffer from, int len);
|
||||
|
||||
int readVirtualUncached(long offset, ByteBuffer into, int len);
|
||||
|
||||
int writeVirtualUncached(long offset, ByteBuffer from, int len);
|
||||
|
||||
int readPhysical(long offset, ByteBuffer into, int len);
|
||||
|
||||
int writePhysical(long offset, ByteBuffer from, int len);
|
||||
|
||||
int readControl(int processor, long offset, ByteBuffer into, int len);
|
||||
|
||||
int writeControl(int processor, long offset, ByteBuffer from, int len);
|
||||
|
||||
int readBusData(int busDataType, int busNumber, int slotNumber, long offset, ByteBuffer into,
|
||||
int len);
|
||||
|
||||
int writeBusData(int busDataType, int busNumber, int slotNumber, long offset, ByteBuffer from,
|
||||
int len);
|
||||
|
||||
int readIo(int interfaceType, int busNumber, int addressSpace, long offset, ByteBuffer into,
|
||||
int len);
|
||||
|
||||
int writeIo(int interfaceType, int busNumber, int addressSpace, long offset, ByteBuffer from,
|
||||
int len);
|
||||
|
||||
long readMsr(int msr);
|
||||
|
||||
void writeMsr(int msr, long value);
|
||||
|
||||
int readDebuggerData(int offset, ByteBuffer into, int len);
|
||||
|
||||
DebugMemoryBasicInformation queryVirtual(long offset);
|
||||
|
||||
/**
|
||||
* A shortcut for iterating over virtual memory regions.
|
||||
*
|
||||
* This operates by calling {@link #queryVirtual(long)} to get each next entry, starting at an
|
||||
* offset of -start-, adding the size of the returned region to determine the offset for the
|
||||
* next call.
|
||||
*
|
||||
* @param start the starting offset
|
||||
* @return an iterator over virtual memory regions after the given start
|
||||
*/
|
||||
default Iterable<DebugMemoryBasicInformation> iterateVirtual(long start) {
|
||||
return new Iterable<DebugMemoryBasicInformation>() {
|
||||
@Override
|
||||
public Iterator<DebugMemoryBasicInformation> iterator() {
|
||||
return new Iterator<DebugMemoryBasicInformation>() {
|
||||
private long last = start;
|
||||
private long offset = start;
|
||||
private DebugMemoryBasicInformation next = doGetNext();
|
||||
|
||||
private DebugMemoryBasicInformation getNext() {
|
||||
if (Long.compareUnsigned(last, offset) < 0) {
|
||||
return doGetNext();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private DebugMemoryBasicInformation doGetNext() {
|
||||
try {
|
||||
DebugMemoryBasicInformation info = queryVirtual(offset);
|
||||
last = offset;
|
||||
if (info != null) {
|
||||
offset += info.regionSize;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
catch (COMException e) {
|
||||
if (!COMUtilsExtra.isE_NOINTERFACE(e)) {
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return next != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugMemoryBasicInformation next() {
|
||||
DebugMemoryBasicInformation ret = next;
|
||||
next = getNext();
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
import agent.dbgeng.dbgeng.DebugClient.*;
|
||||
import ghidra.comm.util.BitmaskSet;
|
||||
import ghidra.comm.util.BitmaskUniverse;
|
||||
|
||||
/**
|
||||
* The interface for receiving event callbacks via {@code IDebugEventCallbacks} or a newer variant.
|
||||
*
|
||||
* Note: The wrapper implementation will select the appropriate native interface version.
|
||||
*
|
||||
* Note: Even though {@link #changeDebuggeeState(BitmaskSet, long)},
|
||||
* {@link #changeEngineState(BitmaskSet, long)} and {@link #changeSymbolState(BitmaskSet, long)}
|
||||
* purport to return a {@link DebugStatus}, the returned value is ignored by {@code dbgeng.dll}.
|
||||
*/
|
||||
public interface DebugEventCallbacks {
|
||||
public static enum DebugEvent implements BitmaskUniverse {
|
||||
BREAKPOINT(1 << 0), //
|
||||
EXCEPTION(1 << 1), //
|
||||
CREATE_THREAD(1 << 2), //
|
||||
EXIT_THREAD(1 << 3), //
|
||||
CREATE_PROCESS(1 << 4), //
|
||||
EXIT_PROCESS(1 << 5), //
|
||||
LOAD_MODULE(1 << 6), //
|
||||
UNLOAD_MODULE(1 << 7), //
|
||||
SYSTEM_ERROR(1 << 8), //
|
||||
SESSION_STATUS(1 << 9), //
|
||||
CHANGE_DEBUGEE_STATE(1 << 10), //
|
||||
CHANGE_ENGINE_STATE(1 << 11), //
|
||||
CHANGE_SYMBOL_STATE(1 << 12), //
|
||||
;
|
||||
|
||||
private DebugEvent(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
private final int mask;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An annotation for marking each callback with its interest flag.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface ForInterest {
|
||||
/**
|
||||
* The flag corresponding to the annotated callback method
|
||||
*
|
||||
* @return the flag
|
||||
*/
|
||||
DebugEvent value();
|
||||
}
|
||||
|
||||
BitmaskSet<DebugEvent> getInterestMask();
|
||||
|
||||
@ForInterest(DebugEvent.BREAKPOINT)
|
||||
DebugStatus breakpoint(DebugBreakpoint bp);
|
||||
|
||||
@ForInterest(DebugEvent.EXCEPTION)
|
||||
DebugStatus exception(DebugExceptionRecord64 exception, boolean firstChance);
|
||||
|
||||
@ForInterest(DebugEvent.CREATE_THREAD)
|
||||
DebugStatus createThread(DebugThreadInfo debugThreadInfo);
|
||||
|
||||
@ForInterest(DebugEvent.EXIT_THREAD)
|
||||
DebugStatus exitThread(int exitCode);
|
||||
|
||||
@ForInterest(DebugEvent.CREATE_PROCESS)
|
||||
DebugStatus createProcess(DebugProcessInfo debugProcessInfo);
|
||||
|
||||
@ForInterest(DebugEvent.EXIT_PROCESS)
|
||||
DebugStatus exitProcess(int exitCode);
|
||||
|
||||
@ForInterest(DebugEvent.LOAD_MODULE)
|
||||
DebugStatus loadModule(DebugModuleInfo debugModuleInfo);
|
||||
|
||||
@ForInterest(DebugEvent.UNLOAD_MODULE)
|
||||
DebugStatus unloadModule(String imageBaseName, long baseOffset);
|
||||
|
||||
@ForInterest(DebugEvent.SYSTEM_ERROR)
|
||||
DebugStatus systemError(int error, int level);
|
||||
|
||||
@ForInterest(DebugEvent.SESSION_STATUS)
|
||||
DebugStatus sessionStatus(SessionStatus status);
|
||||
|
||||
@ForInterest(DebugEvent.CHANGE_DEBUGEE_STATE)
|
||||
DebugStatus changeDebuggeeState(BitmaskSet<ChangeDebuggeeState> flags, long argument);
|
||||
|
||||
@ForInterest(DebugEvent.CHANGE_ENGINE_STATE)
|
||||
DebugStatus changeEngineState(BitmaskSet<ChangeEngineState> flags, long argument);
|
||||
|
||||
@ForInterest(DebugEvent.CHANGE_SYMBOL_STATE)
|
||||
DebugStatus changeSymbolState(BitmaskSet<ChangeSymbolState> flags, long argument);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import agent.dbgeng.jna.dbgeng.WinNTExtra;
|
||||
|
||||
public class DebugEventInformation {
|
||||
|
||||
private int type;
|
||||
private DebugProcessId pid;
|
||||
private DebugThreadId tid;
|
||||
private DebugSessionId sid;
|
||||
private int executingProcessorType = WinNTExtra.Machine.IMAGE_FILE_MACHINE_AMD64.val;
|
||||
|
||||
public DebugEventInformation(int type, int pid, int tid) {
|
||||
this.type = type;
|
||||
this.pid = new DebugProcessId(pid);
|
||||
this.tid = new DebugThreadId(tid);
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public DebugSessionId getSessionId() {
|
||||
return sid;
|
||||
}
|
||||
|
||||
public DebugProcessId getProcessId() {
|
||||
return pid;
|
||||
}
|
||||
|
||||
public DebugThreadId getThreadId() {
|
||||
return tid;
|
||||
}
|
||||
|
||||
public void setThread(DebugThreadId tid) {
|
||||
this.tid = tid;
|
||||
}
|
||||
|
||||
public void setProcess(DebugProcessId pid) {
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
public void setSession(DebugSessionId sid) {
|
||||
this.sid = sid;
|
||||
}
|
||||
|
||||
public int getExecutingProcessorType() {
|
||||
return executingProcessorType;
|
||||
}
|
||||
|
||||
public void setExecutingProcessorType(int execType) {
|
||||
this.executingProcessorType = execType;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Data copied from a {@code EXCEPTION_RECORD64} as defined in {@code winnt.h}.
|
||||
*
|
||||
* TODO: Some enums, flags, etc., to help interpret some of the fields.
|
||||
*/
|
||||
public class DebugExceptionRecord64 {
|
||||
public final int code; // TODO: How to interpret
|
||||
public final int flags; // TODO: How to interpret
|
||||
public final long record; // TODO: How to interpret
|
||||
public final long address;
|
||||
public final List<Long> information;
|
||||
|
||||
public DebugExceptionRecord64(int code, int flags, long record, long address,
|
||||
List<Long> information) {
|
||||
this.code = code;
|
||||
this.flags = flags;
|
||||
this.record = record;
|
||||
this.address = address;
|
||||
this.information = Collections.unmodifiableList(information);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* The interface for receiving input callbacks via {@code IDebugInputCallbacks} or a newer variant.
|
||||
*
|
||||
* Note: The wrapper implementation will select the appropriate native interface version.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DebugInputCallbacks {
|
||||
CompletableFuture<String> startInput();
|
||||
|
||||
default void endInput() {
|
||||
// Optional implementation
|
||||
}
|
||||
}
|
||||
@@ -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 agent.dbgeng.dbgeng;
|
||||
|
||||
/**
|
||||
* Handle to a module (program or library image).
|
||||
*/
|
||||
public interface DebugModule {
|
||||
public enum DebugModuleName {
|
||||
IMAGE, MODULE, LOADED_IMAGE, SYMBOL_FILE, MAPPED_IMAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a name for the module.
|
||||
*
|
||||
* @param which identifies which name
|
||||
* @return the requested name, if available
|
||||
*/
|
||||
String getName(DebugModuleName which);
|
||||
|
||||
/**
|
||||
* Get the index assigned to this module.
|
||||
*
|
||||
* @return the index
|
||||
*/
|
||||
int getIndex();
|
||||
|
||||
/**
|
||||
* Get the base address where this module is loaded, if applicable.
|
||||
*
|
||||
* @return the base address
|
||||
*/
|
||||
long getBase();
|
||||
}
|
||||
@@ -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 agent.dbgeng.dbgeng;
|
||||
|
||||
/**
|
||||
* Information about a module (program or library image).
|
||||
*
|
||||
* The fields correspond to the parameters taken by {@code LoadModule} of
|
||||
* {@code IDebugEventCallbacks}. They also appear as a subset of parameters taken by
|
||||
* {@code CreateProcess} of {@code IDebugEventCallbacks}.
|
||||
*/
|
||||
public class DebugModuleInfo {
|
||||
public final long imageFileHandle;
|
||||
public final long baseOffset;
|
||||
public final int moduleSize;
|
||||
public final String moduleName;
|
||||
public final String imageName;
|
||||
public final int checkSum;
|
||||
public final int timeDateStamp;
|
||||
|
||||
public DebugModuleInfo(long imageFileHandle, long baseOffset, int moduleSize, String moduleName,
|
||||
String imageName, int checkSum, int timeDateStamp) {
|
||||
this.imageFileHandle = imageFileHandle;
|
||||
this.baseOffset = baseOffset;
|
||||
this.moduleSize = moduleSize;
|
||||
this.moduleName = moduleName;
|
||||
this.imageName = imageName;
|
||||
this.checkSum = checkSum;
|
||||
this.timeDateStamp = timeDateStamp; // TODO: Convert to DateTime?
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import agent.dbgeng.dbgeng.DebugControl.DebugOutputLevel;
|
||||
|
||||
/**
|
||||
* The interface for receiving output callbacks via {@code IDebugOutputCallbacks} or a newer
|
||||
* variant.
|
||||
*
|
||||
* Note: The wrapper implementation will select the apprirate native interface version.
|
||||
*
|
||||
* TODO: Change {@link #output(int, String)} {@code mask} parameter to use {@link DebugOutputLevel}
|
||||
* flags.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DebugOutputCallbacks {
|
||||
void output(int mask, String text);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
/**
|
||||
* The <em>engine</em> ID assigned to a debugged process.
|
||||
*
|
||||
* Note: This is not the same as the "PID." {@code dbgeng.dll} calls that the <em>system</em> ID of
|
||||
* the process.
|
||||
*
|
||||
* This is essentially just a boxed integer, but having an explicit data type prevents confusion
|
||||
* with other integral values. In particular, this prevents confusion of engine PIDs with system
|
||||
* PIDs.
|
||||
*/
|
||||
public class DebugProcessId implements Comparable<DebugProcessId> {
|
||||
public final int id;
|
||||
|
||||
public DebugProcessId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Integer.hashCode(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof DebugProcessId)) {
|
||||
return false;
|
||||
}
|
||||
DebugProcessId that = (DebugProcessId) obj;
|
||||
if (this.id != that.id) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(DebugProcessId that) {
|
||||
return Integer.compare(this.id, that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<dbgeng.dll Engine PID " + id + ">";
|
||||
}
|
||||
}
|
||||
@@ -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 agent.dbgeng.dbgeng;
|
||||
|
||||
/**
|
||||
* Information about a process.
|
||||
*
|
||||
* The fields correspond to parameters taken by {@code CreateProcess} of
|
||||
* {@code IDebugEventCallbacks}. Note that parameters common to other callbacks have been factored
|
||||
* into types aggregated here.
|
||||
*/
|
||||
public class DebugProcessInfo {
|
||||
public final long handle;
|
||||
public final DebugModuleInfo moduleInfo;
|
||||
public final DebugThreadInfo initialThreadInfo;
|
||||
|
||||
public DebugProcessInfo(long handle, DebugModuleInfo moduleInfo,
|
||||
DebugThreadInfo initialThreadInfo) {
|
||||
this.handle = handle;
|
||||
this.moduleInfo = moduleInfo;
|
||||
this.initialThreadInfo = initialThreadInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import agent.dbgeng.dbgeng.DebugValue.DebugValueType;
|
||||
import ghidra.comm.util.BitmaskSet;
|
||||
import ghidra.comm.util.BitmaskUniverse;
|
||||
|
||||
/**
|
||||
* A wrapper for {@code IDebugRegisters} and its newer variants.
|
||||
*/
|
||||
public interface DebugRegisters {
|
||||
public static enum DebugRegisterSource {
|
||||
DEBUG_REGSRC_DEBUGGEE, //
|
||||
DEBUG_REGSRC_EXPLICIT, //
|
||||
DEBUG_REGSRC_FRAME, //
|
||||
;
|
||||
}
|
||||
|
||||
public static enum DebugRegisterFlags implements BitmaskUniverse {
|
||||
SUB_REGISTER(1 << 0), //
|
||||
;
|
||||
|
||||
private DebugRegisterFlags(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
private final int mask;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DebugRegisterDescription {
|
||||
public final String name;
|
||||
public final int index;
|
||||
public final DebugValueType type;
|
||||
public final Set<DebugRegisterFlags> flags;
|
||||
public final int subregMaster;
|
||||
public final int subregLengthBits;
|
||||
public final long subregMask;
|
||||
public final int subregShift;
|
||||
|
||||
public DebugRegisterDescription(String name, int index, DebugValueType type,
|
||||
BitmaskSet<DebugRegisterFlags> flags, int subregMaster, int subregLengthBits,
|
||||
long subregMask, int subregShift) {
|
||||
this.name = name;
|
||||
this.index = index;
|
||||
this.type = type;
|
||||
this.flags = Collections.unmodifiableSet(flags);
|
||||
this.subregMaster = subregMaster;
|
||||
this.subregLengthBits = subregLengthBits;
|
||||
this.subregMask = subregMask;
|
||||
this.subregShift = subregShift;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"<%s: name='%s' index=%d type=%s flags=%s subregMaster=%d subregLengthBits=%d" +
|
||||
" subregMask=%x subregShift=%d>",
|
||||
getClass().getSimpleName(), name, index, type, flags, subregMaster,
|
||||
subregLengthBits, subregMask, subregShift);
|
||||
}
|
||||
}
|
||||
|
||||
int getNumberRegisters();
|
||||
|
||||
DebugRegisterDescription getDescription(int registerNumber);
|
||||
|
||||
/**
|
||||
* A shortcut to get all register descriptions for the current process.
|
||||
*
|
||||
* Uses {@link #getNumberRegisters()} and {@link #getDescription(int)} to retrieve all
|
||||
* descriptions for the current process.
|
||||
*
|
||||
* @return the list of register descriptions
|
||||
*/
|
||||
default Set<DebugRegisterDescription> getAllDescriptions() {
|
||||
Set<DebugRegisterDescription> result = new LinkedHashSet<>();
|
||||
int count = getNumberRegisters();
|
||||
for (int i = 0; i < count; i++) {
|
||||
result.add(getDescription(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int getIndexByName(String name);
|
||||
|
||||
/**
|
||||
* A shortcut to get many register indices in one call.
|
||||
*
|
||||
* Uses {@link #getIndexByName(String)}.
|
||||
*
|
||||
* @param names the names whose indices to get
|
||||
* @return the indices in respective order to the given names
|
||||
*/
|
||||
default int[] getIndicesByNames(String... names) {
|
||||
int[] indices = new int[names.length];
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
indices[i] = getIndexByName(names[i]);
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
DebugValue getValue(int index);
|
||||
|
||||
Map<Integer, DebugValue> getValues(DebugRegisterSource source, Collection<Integer> indices);
|
||||
|
||||
/**
|
||||
* A shortcut to get a register value by name.
|
||||
*
|
||||
* Uses {@link #getIndexByName(String)} followed by {@link #getValue(int)}.
|
||||
*
|
||||
* @param name the name of the register
|
||||
* @return the value
|
||||
*/
|
||||
default DebugValue getValueByName(String name) {
|
||||
int indexByName = getIndexByName(name);
|
||||
if (indexByName > 0) {
|
||||
return getValue(indexByName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void setValue(int index, DebugValue value);
|
||||
|
||||
void setValues(DebugRegisterSource source, Map<Integer, DebugValue> values);
|
||||
|
||||
/**
|
||||
* A shortcut to set a register value by name.
|
||||
*
|
||||
* Uses {@link #getIndexByName(String)} followed by {@link #setValue(int, DebugValue)}.
|
||||
*
|
||||
* @param name the name of the register
|
||||
* @param value the desired value
|
||||
*/
|
||||
default void setValueByName(String name, DebugValue value) {
|
||||
setValue(getIndexByName(name), value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import agent.dbgeng.dbgeng.DebugRunningProcess.Description.ProcessDescriptionFlags;
|
||||
import ghidra.comm.util.BitmaskUniverse;
|
||||
|
||||
/**
|
||||
* Information about a running process, not necessarily a debugged process.
|
||||
*/
|
||||
public interface DebugRunningProcess {
|
||||
/**
|
||||
* Description of a running process
|
||||
*/
|
||||
public static class Description {
|
||||
public static enum ProcessDescriptionFlags implements BitmaskUniverse {
|
||||
NO_PATHS(1 << 0), //
|
||||
NO_SERVICES(1 << 1), //
|
||||
NO_MTS_PACKAGES(1 << 2), //
|
||||
NO_COMMAND_LINE(1 << 3), //
|
||||
NO_SESSION_ID(1 << 4), //
|
||||
NO_USER_NAME(1 << 5), //
|
||||
;
|
||||
|
||||
ProcessDescriptionFlags(int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
private final int mask;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public Description(int systemId, String exeName, String description) {
|
||||
this.systemId = systemId;
|
||||
this.exeName = exeName;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
private final int systemId;
|
||||
private final String exeName;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* The system ID (PID) for the process.
|
||||
*
|
||||
* @return the PID
|
||||
*/
|
||||
public int getSystemId() {
|
||||
return systemId;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the executable defining the process
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
public String getExecutableName() {
|
||||
return exeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* A textual description of the process.
|
||||
*
|
||||
* @return the description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PID:%d, EXE:%s, Description:%s", systemId, exeName, description);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The system ID (PID) for the process.
|
||||
*
|
||||
* @return the PID
|
||||
*/
|
||||
int getSystemId();
|
||||
|
||||
/**
|
||||
* Get the "full" description of the process.
|
||||
*
|
||||
* @param flags indicate which information to include in the description
|
||||
* @return the description
|
||||
*/
|
||||
Description getFullDescription(ProcessDescriptionFlags... flags);
|
||||
|
||||
/**
|
||||
* The name of the executable defining the process.
|
||||
*
|
||||
* @param flags indicate which information to include in the description
|
||||
* @return the name
|
||||
*/
|
||||
String getExecutableName(ProcessDescriptionFlags... flags);
|
||||
|
||||
/**
|
||||
* A textual description of the process.
|
||||
*
|
||||
* @param flags indicate which information to include in the description
|
||||
* @return the description
|
||||
*/
|
||||
String getDescription(ProcessDescriptionFlags... flags);
|
||||
}
|
||||
@@ -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 agent.dbgeng.dbgeng;
|
||||
|
||||
/**
|
||||
* The ID of a debug server.
|
||||
*
|
||||
* Each server to which a client is connected is assigned a server ID. The local server, to which
|
||||
* every client is connected by default, has the ID 0. This is essentially just a boxed integer, but
|
||||
* having an explicit data type prevents confusion with other integral values.
|
||||
*/
|
||||
public class DebugServerId implements Comparable<DebugServerId> {
|
||||
public final long id;
|
||||
|
||||
public DebugServerId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof DebugServerId)) {
|
||||
return false;
|
||||
}
|
||||
DebugServerId that = (DebugServerId) obj;
|
||||
if (this.id != that.id) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(DebugServerId that) {
|
||||
return Long.compare(this.id, that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<dbgeng.dll Server ID " + id + ">";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
/**
|
||||
* The <em>engine</em> ID assigned to a debugged process.
|
||||
*
|
||||
* Note: This is not the same as the "PID." {@code dbgeng.dll} calls that the <em>system</em> ID of
|
||||
* the process.
|
||||
*
|
||||
* This is essentially just a boxed integer, but having an explicit data type prevents confusion
|
||||
* with other integral values. In particular, this prevents confusion of engine PIDs with system
|
||||
* PIDs.
|
||||
*/
|
||||
public class DebugSessionId implements Comparable<DebugSessionId> {
|
||||
public final int id;
|
||||
|
||||
public DebugSessionId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Integer.hashCode(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof DebugSessionId)) {
|
||||
return false;
|
||||
}
|
||||
DebugSessionId that = (DebugSessionId) obj;
|
||||
if (this.id != that.id) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(DebugSessionId that) {
|
||||
return Integer.compare(this.id, that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<dbgeng.dll Engine SYSID " + id + ">";
|
||||
}
|
||||
}
|
||||
@@ -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 agent.dbgeng.dbgeng;
|
||||
|
||||
import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_STACK_FRAME;
|
||||
|
||||
public class DebugStackInformation {
|
||||
|
||||
private int nFrames;
|
||||
private DEBUG_STACK_FRAME[] stackFrames;
|
||||
|
||||
public DebugStackInformation(int nFrames, DEBUG_STACK_FRAME[] stackFrames) {
|
||||
this.nFrames = nFrames;
|
||||
this.stackFrames = stackFrames;
|
||||
}
|
||||
|
||||
public int getNumberOfFrames() {
|
||||
return nFrames;
|
||||
}
|
||||
|
||||
public DEBUG_STACK_FRAME getFrame(int frameNumber) {
|
||||
return stackFrames[frameNumber];
|
||||
}
|
||||
}
|
||||
@@ -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 agent.dbgeng.dbgeng;
|
||||
|
||||
/**
|
||||
* Data copied from a {@code DEBUG_SYMBOL_ENTRY} as defined in {@code dbgeng.h}.
|
||||
*
|
||||
* TODO: Some enums, flags, etc., to help interpret some of the fields.
|
||||
*/
|
||||
public class DebugSymbolEntry {
|
||||
public final long moduleBase;
|
||||
public final long offset;
|
||||
public final long symbolId;
|
||||
public final long size;
|
||||
public final int flags;
|
||||
public final int typeId;
|
||||
public final String name;
|
||||
public final int tag;
|
||||
|
||||
public DebugSymbolEntry(long moduleBase, long offset, long symbolId, long size, int flags,
|
||||
int typeId, String name, int tag) {
|
||||
this.moduleBase = moduleBase;
|
||||
this.offset = offset;
|
||||
this.symbolId = symbolId;
|
||||
this.size = size;
|
||||
this.flags = flags;
|
||||
this.typeId = typeId;
|
||||
this.name = name;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("<DebugSymbolEntry %016x:%016x\n" + //
|
||||
" offset=%016xh,\n" + //
|
||||
" size=%xh,\n" + //
|
||||
" flags=%xh,\n" + //
|
||||
" typeId=%xh,\n" + //
|
||||
" name='%s',\n" + //
|
||||
" tag=%xh>", //
|
||||
moduleBase, symbolId, offset, size, flags, typeId, name, tag);
|
||||
}
|
||||
}
|
||||
@@ -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 agent.dbgeng.dbgeng;
|
||||
|
||||
/**
|
||||
* Symbol identifier, consisting of module ID and symbol index.
|
||||
*/
|
||||
public class DebugSymbolId {
|
||||
public final long moduleBase;
|
||||
public final long symbolIndex;
|
||||
|
||||
public DebugSymbolId(long moduleBase, long symbolIndex) {
|
||||
this.moduleBase = moduleBase;
|
||||
this.symbolIndex = symbolIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("<DebugSymbolId %016x:%016x>", moduleBase, symbolIndex);
|
||||
}
|
||||
}
|
||||
@@ -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 agent.dbgeng.dbgeng;
|
||||
|
||||
/**
|
||||
* Symbol name, consisting of textual name and offset.
|
||||
*/
|
||||
public class DebugSymbolName {
|
||||
public final String name;
|
||||
public final long offset;
|
||||
|
||||
public DebugSymbolName(String name, long offset) {
|
||||
this.name = name;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("<%016x: %s>", offset, name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.dbgeng;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A wrapper for {@code IDebugSymbols} and its newer variants.
|
||||
*/
|
||||
public interface DebugSymbols {
|
||||
int getNumberLoadedModules();
|
||||
|
||||
int getNumberUnloadedModules();
|
||||
|
||||
DebugModule getModuleByIndex(int index);
|
||||
|
||||
DebugModule getModuleByModuleName(String name, int startIndex);
|
||||
|
||||
DebugModule getModuleByOffset(long offset, int startIndex);
|
||||
|
||||
/**
|
||||
* A shortcut for iterating over all loaded modules, lazily.
|
||||
*
|
||||
* @param startIndex the module index to start at
|
||||
* @return an iterator over modules starting at the given index
|
||||
*/
|
||||
default Iterable<DebugModule> iterateModules(int startIndex) {
|
||||
int count = getNumberLoadedModules(); // TODO: What about unloaded?
|
||||
return new Iterable<DebugModule>() {
|
||||
@Override
|
||||
public Iterator<DebugModule> iterator() {
|
||||
return new Iterator<DebugModule>() {
|
||||
int cur = startIndex;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return cur < count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugModule next() {
|
||||
DebugModule ret = getModuleByIndex(cur);
|
||||
cur++;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Iterable<DebugSymbolName> iterateSymbolMatches(String pattern);
|
||||
|
||||
List<DebugSymbolId> getSymbolIdsByName(String pattern);
|
||||
|
||||
DebugSymbolEntry getSymbolEntry(DebugSymbolId id);
|
||||
|
||||
String getSymbolPath();
|
||||
|
||||
void setSymbolPath(String path);
|
||||
|
||||
int getSymbolOptions();
|
||||
|
||||
void setSymbolOptions(int options);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user