GP-653 added support for user-defined compiler spec extensions

This commit is contained in:
caheckman 2021-04-29 16:17:25 -04:00 committed by ghidra1
parent cce6807470
commit ed82c2cb34
2915 changed files with 333939 additions and 4451 deletions

76
.gitattributes vendored
View File

@ -3,53 +3,79 @@
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.java text
*.gradle text
*.manifest text
*.bash text
*.c text
*.cc text
*.command text
*.cpp text
*.cspec text
*.css text
*.gradle text
*.groovy text
*.h text
*.hh text
*.htm text
*.html text
*.java text
*.jj text
*.js text
*.json text
*.jsp text
*.jspf text
*.jspx text
*.l text
*.lang text
*.ldefs text
*.manifest text
*.opinion text
*.props text
*.properties text
*.tld text
*.txt text
*.proto text
*.pspec text
*.py text
*.rxg text
*.sh text
*.sla text
*.tag text
*.tld text
*.tool text
*.trans text
*.txt text
*.xml text
*.c text
*.h text
*.cpp text
*.hh text
*.cc text
*.y text
# Declare files that will always have CRLF line endings on checkout.
*.bat text eol=crlf
*.sln text eol=crlf
*.vcproj text eol=crlf
*.vcxproj text eol=crlf
*.bat text eol=crlf
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
*.a binary
*.apk binary
*.bmp binary
*.class binary
*.dll binary
*.dmg binary
*.ear binary
*.gif binary
*.ico binary
*.jar binary
*.jpeg binary
*.so binary
*.war binary
*.pdf binary
*.exe binary
*.lib binary
*.sa binary
*.gdt binary
*.gif binary
*.gz binary
*.gzf binary
*.tgz binary
*.ico binary
*.ipsw binary
*.jar binary
*.jpeg binary
*.jpg binary
*.lib binary
*.o binary
*.obj binary
*.pdf binary
*.png binary
*.sa binary
*.so binary
*.tar binary
*.sh binary
*.tgz binary
*.war binary
*.zip binary

5
.gitignore vendored
View File

@ -3,6 +3,7 @@ ghidra.repos.config
# Misc files produced while executing application
repositories/
dependencies/
flatRepo/
Ghidra/.ghidraSvrKeys
wrapper.log*
@ -84,4 +85,6 @@ gradle/wrapper
gradlew
gradlew.*
# Ignore logs and core dumps
*.log
core.*

107
DebuggerDevGuide.md Normal file
View 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`.

View File

@ -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
@ -33,7 +34,7 @@ You may not need all of these, depending on which portions you are building or d
- https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot
- Amazon Corretto
- https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html
* Gradle 5.0 or later - We use version 5.0, and tested with up to 5.6.3.
* Gradle 5.0 or later - We use version 5.0, and tested with up to 6.8.3.
- https://gradle.org/next-steps/?version=5.0&format=bin
* A C/C++ compiler - We use GCC on Linux, Xcode (Clang) on macOS, and Visual Studio (2017 or later) on Windows.
- https://gcc.gnu.org/
@ -61,9 +62,9 @@ You may not need all of these, depending on which portions you are building or d
- https://sourceforge.net/projects/yajsw/files/yajsw/yajsw-stable-12.12/
* Eclipse PDE - Environment for developing the GhidraDev plugin.
- https://www.eclipse.org/pde/
* Eclipse CDT. We use version 8.6.0 - Build dependency for the GhidraDev plugin.
* Eclipse CDT. We build against version 8.6.0 - Build dependency for the GhidraDev plugin.
- https://www.eclipse.org/cdt/
* PyDev. We use version 6.3.1 - Build dependency for the GhidraDev plugin.
* PyDev. We build against version 6.3.1 - Build dependency for the GhidraDev plugin.
- https://sourceforge.net/projects/pydev/files/pydev/
There are many, many others automatically downloaded by Gradle from Maven Central and Bintray JCenter when building and/or setting up the development environment.
@ -110,64 +111,67 @@ or manually by downloading the required dependencies. Choose one of the two fol
The flat directory-style repository can be setup automatically by running a simple Gradle script.
Navigate to `~/git/ghidra` and run the following:
```
gradle --init-script gradle/support/fetchDependencies.gradle init
gradle -I gradle/support/fetchDependencies.gradle init
```
The Gradle task to be executed, in this case _init_, is unimportant. The point is to have Gradle execute
the `fetchDependencies.gradle` script. If it ran correctly you will have a new `~/git/ghidra/flatRepo/`
directory populated with the following jar files:
* AXMLPrinter2
* csframework
* dex-ir-2.0
* dex-reader-2.0
* dex-reader-api-2.0
* dex-tools-2.0
* dex-translator-2.0
* dex-writer-2.0
* hfsx
* hfsx_dmglib
* iharder-base64
There will also be a new archive files at:
* ~/git/ghidra/Ghidra/Features/GhidraServer/build/`yajsw-stable-12.12.zip`
* ~/git/ghidra/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/`PyDev 6.3.1.zip`
* ~/git/ghidra/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/`cdt-8.6.0.zip`
the `fetchDependencies.gradle` script. If it ran correctly you will have a new `~/git/ghidra/dependencies/`
directory populated with the following files:
* flatRepo/AXMLPrinter2.jar
* flatRepo/csframework.jar
* flatRepo/dex-ir-2.0.jar
* flatRepo/dex-reader-2.0.jar
* flatRepo/dex-reader-api-2.0.jar
* flatRepo/dex-tools-2.0.jar
* flatRepo/dex-translator-2.0.jar
* flatRepo/dex-writer-2.0.jar
* flatRepo/hfsx.jar
* flatRepo/hfsx_dmglib.jar
* flatRepo/iharder-base64.jar
* cdt-8.6.0.zip
* PyDev 6.3.1.zip
* yajsw-stable-12.12.zip
* fidb/*.fidb
If you see these, congrats! Skip to [building](#building-ghidra) or [developing](#developing-ghidra). If not, continue with manual download
instructions below...
### Manual Download Instructions
Create the `~/git/ghidra/flatRepo/` directory to hold the manually-downloaded dependencies:
Create the `~/git/ghidra/dependencies/` directory and required subdirectories to hold the manually-downloaded dependencies:
```bash
mkdir ~/git/ghidra/flatRepo
mkdir ~/git/ghidra/dependencies
mkdir ~/git/ghidra/dependencies/flatRepo
mkdir ~/git/ghidra/dependencies/fidb
mkdir ~/git/ghidra/dependencies/GhidraServer
mkdir ~/git/ghidra/dependencies/GhidraDev
```
#### Get Dependencies for FileFormats:
Download `dex-tools-2.0.zip` from the dex2jar project's releases page on GitHub.
Unpack the `dex-*.jar` files from the `lib` directory to `~/git/ghidra/flatRepo`:
Unpack the `dex-*.jar` files from the `lib` directory to `~/git/ghidra/dependencies/flatRepo`:
```bash
cd ~/Downloads # Or wherever
curl -OL https://github.com/pxb1988/dex2jar/releases/download/2.0/dex-tools-2.0.zip
unzip dex-tools-2.0.zip
cp dex2jar-2.0/lib/dex-*.jar ~/git/ghidra/flatRepo/
cp dex2jar-2.0/lib/dex-*.jar ~/git/ghidra/dependencies/flatRepo/
```
Download `AXMLPrinter2.jar` from the "android4me" archive on code.google.com.
Place it in `~/git/ghidra/flatRepo`:
Place it in `~/git/ghidra/dependencies/flatRepo`:
```bash
cd ~/git/ghidra/flatRepo
cd ~/git/ghidra/dependencies/flatRepo
curl -OL https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/android4me/AXMLPrinter2.jar
```
#### Get Dependencies for DMG:
Download `hfsexplorer-0_21-bin.zip` from www.catacombae.org.
Unpack the `lib` directory to `~/git/ghidra/flatRepo`:
Unpack the `lib` directory to `~/git/ghidra/dependencies/flatRepo`:
```bash
cd ~/Downloads # Or wherever
@ -176,35 +180,33 @@ mkdir hfsx
cd hfsx
unzip ../hfsexplorer-0_21-bin.zip
cd lib
cp csframework.jar hfsx_dmglib.jar hfsx.jar iharder-base64.jar ~/git/ghidra/flatRepo/
cp csframework.jar hfsx_dmglib.jar hfsx.jar iharder-base64.jar ~/git/ghidra/dependencies/flatRepo/
```
#### Get Dependencies for GhidraServer
Building the GhidraServer requires "Yet another Java service wrapper" (yajsw) version 12.12.
Download `yajsw-stable-12.12.zip` from their project on www.sourceforge.net, and place it in:
`~/git/ghidra/Ghidra/Features/GhidraServer/build`:
`~/git/ghidra/dependencies/GhidraServer/`:
```bash
cd ~/Downloads # Or wherever
curl -OL https://sourceforge.net/projects/yajsw/files/yajsw/yajsw-stable-12.12/yajsw-stable-12.12.zip
mkdir -p ~/git/ghidra/Ghidra/Features/GhidraServer/build/
cp ~/Downloads/yajsw-stable-12.12.zip ~/git/ghidra/Ghidra/Features/GhidraServer/build/
cp ~/Downloads/yajsw-stable-12.12.zip ~/git/ghidra/dependencies/GhidraServer/
```
#### Get Dependencies for GhidraDev
Building the GhidraDev plugin for Eclipse requires the CDT and PyDev plugins for Eclipse.
Download `cdt-8.6.0.zip` from The Eclipse Foundation, and place it in:
`~/git/ghidra/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/`:
`~/git/ghidra/dependencies/GhidraDev/`:
```bash
cd ~/Downloads # Or wherever
curl -OL 'https://archive.eclipse.org/tools/cdt/releases/8.6/cdt-8.6.0.zip'
curl -o 'cdt-8.6.0.zip.sha512' -L --retry 3 'https://www.eclipse.org/downloads/sums.php?type=sha512&file=/tools/cdt/releases/8.6/cdt-8.6.0.zip'
shasum -a 512 -c 'cdt-8.6.0.zip.sha512'
mkdir -p ~/git/ghidra/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/
cp ~/Downloads/cdt-8.6.0.zip ~/git/ghidra/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/
cp ~/Downloads/cdt-8.6.0.zip ~/git/ghidra/dependencies/GhidraDev/
```
Download `PyDev 6.3.1.zip` from www.pydev.org, and place it in the same directory:
@ -212,7 +214,27 @@ Download `PyDev 6.3.1.zip` from www.pydev.org, and place it in the same director
```bash
cd ~/Downloads # Or wherever
curl -L -o 'PyDev 6.3.1.zip' https://sourceforge.net/projects/pydev/files/pydev/PyDev%206.3.1/PyDev%206.3.1.zip
cp ~/Downloads/'PyDev 6.3.1.zip' ~/git/ghidra/GhidraBuild/EclipsePlugins/GhidraDev/GhidraDevPlugin/build/
cp ~/Downloads/'PyDev 6.3.1.zip' ~/git/ghidra/dependencies/GhidraDev/
```
#### Get Ghidra Function ID datasets
Download the Ghidra Function ID dataset files from the `ghidra-data` GitHub repository and place them
in `~/git/ghidra/dependencies/fidb`:
```bash
cd ~/Downloads # Or wherever
curl -L -o 'vs2012_x64.fidb' https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2012_x64.fidb
curl -L -o 'vs2012_x86.fidb' https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2012_x86.fidb
curl -L -o 'vs2015_x64.fidb' https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2015_x64.fidb
curl -L -o 'vs2015_x86.fidb' https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2015_x86.fidb
curl -L -o 'vs2017_x64.fidb' https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2017_x64.fidb
curl -L -o 'vs2017_x86.fidb' https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2017_x86.fidb
curl -L -o 'vs2019_x64.fidb' https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2019_x64.fidb
curl -L -o 'vs2019_x86.fidb' https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vs2019_x86.fidb
curl -L -o 'vsOlder_x64.fidb' https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vsOlder_x64.fidb
curl -L -o 'vsOlder_x86.fidb' https://github.com/NationalSecurityAgency/ghidra-data/raw/master/FunctionID/vsOlder_x86.fidb
cp ~/Downloads/*.fidb ~/git/ghidra/dependencies/fidb/
```
## Building Ghidra
@ -376,4 +398,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

View File

@ -1,7 +1,6 @@
##VERSION: 2.0
##MODULE IP: GPL 2
##MODULE IP: Public Domain
.project||GHIDRA||||END|
Module.manifest||Public Domain||||END|
README.txt||Public Domain||||END|
data/arm_test1.s||Public Domain||||END|

View File

@ -6,6 +6,19 @@
</HEAD>
<BODY>
<H1 align="center">Ghidra 9.2.4 Change History (April 2021)</H1>
<blockquote><p><u>Improvements</u></p>
<ul>
<li><I>Basic Infrastructure</I>. Improved support running under JDK 16. Note that Ghidra still only officially supports JDK 11 LTS. (GP-824, Issue #2879, #2888)</li>
</ul>
</blockquote>
<blockquote><p><u>Bugs</u></p>
<ul>
<li><I>API</I>. Corrected error condition which could occur if overlay memory block duplicates another memory space name or overlay block name in a case-insensitive manner. The names are intended to be case-sensitive. (GP-839, Issue #2898)</li>
<li><I>Demangler</I>. Improved handling of mangled names on thunk functions which were previously left unmangled and could prevent name of underlying thunked function from appearing. (GP-809)</li>
</ul>
</blockquote>
<H1 align="center">Ghidra 9.2.3 Change History (March 2021)</H1>
<blockquote><p><u>Improvements</u></p>
<ul>
@ -23,7 +36,7 @@
<blockquote><p><u>Bugs</u></p>
<ul>
<li><I>Analysis</I>. The RTTI analyzer now runs prior to Reference analysis so that references into vftables are not turned into code or data before the vftables are created. (GP-517)</li>
<li><I>API</I>. <code>Funtion.getCalledFunctions(TaskMonitor)</code> and <code>Function.getCallingFunctions(TaskMonitor)</code> now support passing <code>null</code> for the task monitor parameter, which previously would have thrown an exception. (GP-589, Issue #2643)</li>
<li><I>API</I>. <code>Function.getCalledFunctions(TaskMonitor)</code> and <code>Function.getCallingFunctions(TaskMonitor)</code> now support passing <code>null</code> for the task monitor parameter, which previously would have thrown an exception. (GP-589, Issue #2643)</li>
<li><I>Data Types</I>. Corrected segmented 32-bit pointer datatype address generation for 16:16 x86 far pointers. (GP-534, Issue #2548)</li>
<li><I>Decompiler</I>. Fixed Decompiler issue where, when a function name extends beyond the line limit, an end-of-line comment could wrap around to additional lines without including additional <code>//</code> comment indicators. (GP-473)</li>
<li><I>Decompiler</I>. Corrected an exception that could occur when attempting to edit function signature from the Decompiler. (GP-597, Issue #2601)</li>

View File

@ -0,0 +1,60 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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))
}

View File

@ -0,0 +1,3 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
src/main/resources/META-INF/services/javax.annotation.processing.Processor||GHIDRA||||END|

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
ghidra.util.database.annotproc.DBAnnotatedObjectProcessor

View 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

View File

@ -0,0 +1,81 @@
/* ###
* 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.
*/
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")
}
}

View File

@ -0,0 +1,5 @@
##VERSION: 2.0
##MODULE IP: Apache License 2.0
Module.manifest||GHIDRA||||END|
src/javaprovider/def/javaprovider.def||GHIDRA||||END|
src/javaprovider/rc/javaprovider.rc||GHIDRA||||END|

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,125 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.model;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import agent.dbgeng.gadp.DbgEngGadpServer;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.gadp.client.GadpClient;
import ghidra.dbg.gadp.client.GadpClientTestHelper;
import ghidra.dbg.gadp.protocol.Gadp;
import ghidra.util.Msg;
public class GadpForDbgTest extends AbstractModelForDbgTest {
static class DbgGadpModelHost implements ModelHost {
final DbgEngGadpServer server;
final SocketAddress addr;
final AsynchronousSocketChannel socket;
final GadpClient client;
DbgGadpModelHost() throws Exception {
server = DbgEngGadpServer.newInstance(new InetSocketAddress("localhost", 0));
server.startDbgEng(new String[] {})
.get(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
addr = server.getLocalAddress();
socket = AsynchronousSocketChannel.open();
client = new GadpClient("Test", socket);
}
@Override
public CompletableFuture<Void> init() {
return AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
Msg.debug(this, "Connecting...");
AsyncUtils.completable(TypeSpec.VOID, socket::connect, addr).handle(seq::next);
}).then(seq -> {
Msg.debug(this, "Negotiating...");
client.connect().handle(seq::next);
}).finish();
}
@Override
public DebuggerObjectModel getModel() {
return client;
}
@Override
public void close() throws Exception {
// Not too eww
Msg.debug(this, "Disconnecting...");
try {
waitOn(client.close());
}
catch (Exception e) {
throw e;
}
catch (Throwable e) {
throw new AssertionError(e);
}
server.terminate();
}
}
@Override
protected DbgGadpModelHost modelHost() throws Exception {
return new DbgGadpModelHost();
}
@Test
public void testBadRequest() throws Throwable {
try (DbgGadpModelHost m = modelHost()) {
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
m.init().handle(seq::next);
}).then(seq -> {
Msg.debug(this, "Sending bogus message...");
GadpClientTestHelper.sendChecked(m.client, Gadp.ErrorRequest.newBuilder(), null)
.handle(seq::nextIgnore);
}).finish());
fail("Exception expected");
}
catch (AssertionError e) {
assertEquals(
"Client implementation sent an invalid request: " +
"BAD_REQUEST: Unrecognized request: ERROR_REQUEST",
e.getMessage());
}
}
@Test
public void testPing() throws Throwable {
try (DbgGadpModelHost m = modelHost()) {
waitOn(AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
m.init().handle(seq::next);
}).then(seq -> {
Msg.debug(this, "Pinging...");
m.client.ping("Hello, Ghidra Async Debugging!").handle(seq::next);
}).finish());
}
}
}

View File

@ -0,0 +1,54 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.model;
import java.util.concurrent.CompletableFuture;
import agent.dbgeng.model.impl.DbgModelImpl;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.util.Msg;
public class ModelForDbgTest extends AbstractModelForDbgTest {
static class DbgGadpModelHost implements ModelHost {
final DbgModelImpl model;
DbgGadpModelHost() {
model = new DbgModelImpl();
}
@Override
public CompletableFuture<Void> init() {
Msg.debug(this, "Starting dbgeng...");
return model.startDbgEng(new String[] {});
}
@Override
public DebuggerObjectModel getModel() {
return model;
}
@Override
public void close() throws Exception {
model.terminate();
}
}
@Override
protected ModelHost modelHost() throws Exception {
return new DbgGadpModelHost();
}
}

View File

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

View File

@ -0,0 +1,13 @@
EXPORTS
; For ExtCpp
DebugExtensionInitialize
DebugExtensionUninitialize
DebugExtensionNotify
help
; My Commands
java_add_cp
java_set
java_get
java_run

View File

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

View File

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

View File

@ -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.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
/**
* 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" //
)
public class DbgEngInJvmDebuggerModelFactory implements DebuggerModelFactory {
// TODO remoteTransport option?
@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");
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,135 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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);
/**
* Get the location on target that triggers the breakpoint
*
* <p>
* If the breakpoint is deferred, this will return {@code null}. In that case, use
* {@link #getOffsetExpression()}.
*
* @return the offset, or {@code null}
*/
Long getOffset();
void setOffset(long offset);
String getOffsetExpression();
void setOffsetExpression(String expression);
BreakDataParameters getDataParameters();
void setDataParameters(BreakDataParameters params);
void setDataParameters(int size, BitmaskSet<BreakAccess> access);
void setDataParameters(int size, BreakAccess... access);
}

View File

@ -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, ExecutionState.RUNNING, 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);
}

View File

@ -0,0 +1,46 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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();
}

View File

@ -0,0 +1,359 @@
/* ###
* 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.
*
* <p>
* 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);
/**
* Add a (resolved) breakpoint with the given type and desired id
*
* <p>
* This is equivalent, in part, to the {@code bp} command.
*
* @param type the type
* @param desiredId the desired id
* @return the breakpoint, disabled at offset 0
*/
DebugBreakpoint addBreakpoint(BreakType type, int desiredId);
/**
* Add a (resolved) breakpoint with the given type and any id
*
* <p>
* This is equivalent, in part, to the {@code bp} command.
*
* @param type the type
* @return the breakpoint, disable at offset 0
*/
DebugBreakpoint addBreakpoint(BreakType type);
/**
* Add an unresolved breakpoint with the given type and desired id
*
* <p>
* This is equivalent, in part, to the {@code bu} command. See the MSDN for a comparison of
* {@code bu} and {@code bp}.
*
* @param type the type
* @param desiredId the desired id
* @return the breakpoint, disabled at offset 0
*/
DebugBreakpoint addBreakpoint2(BreakType type, int desiredId);
/**
* Add an unresolved breakpoint with the given type and any id
*
* <p>
* This is equivalent, in part, to the {@code bu} command. See the MSDN for a comparison of
* {@code bu} and {@code bp}.
*
* @param desiredId the desired id
* @return the breakpoint, disabled at offset 0
*/
DebugBreakpoint addBreakpoint2(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();
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,41 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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);
}
}

View File

@ -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.
*/
package agent.dbgeng.dbgeng;
/**
* 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 {
public void startInput(long bufferSize);
default void endInput() {
// Optional implementation
}
}

View File

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

View File

@ -0,0 +1,64 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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 int checkSum;
public final int timeDateStamp;
private String moduleName;
private String imageName;
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.setModuleName(moduleName);
this.setImageName(imageName);
this.checkSum = checkSum;
this.timeDateStamp = timeDateStamp; // TODO: Convert to DateTime?
}
public String toString() {
return Long.toHexString(baseOffset);
}
public String getModuleName() {
return moduleName;
}
public void setModuleName(String moduleName) {
this.moduleName = moduleName;
}
public String getImageName() {
return imageName;
}
public void setImageName(String imageName) {
this.imageName = imageName;
}
}

View File

@ -0,0 +1,32 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,37 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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];
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,85 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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);
DebugModuleInfo getModuleParameters(int count, 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);
public int getCurrentScopeFrameIndex();
public void setCurrentScopeFrameIndex(int index);
}

View File

@ -0,0 +1,102 @@
/* ###
* 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;
/**
* A wrapper for {@code IDebugSystemObjects} and its newer variants.
*/
public interface DebugSystemObjects {
DebugThreadId getEventThread();
DebugProcessId getEventProcess();
DebugSessionId getEventSystem();
DebugThreadId getCurrentThreadId();
void setCurrentThreadId(DebugThreadId id);
DebugProcessId getCurrentProcessId();
void setCurrentProcessId(DebugProcessId id);
DebugSessionId getCurrentSystemId();
void setCurrentSystemId(DebugSessionId id);
int getNumberThreads();
int getTotalNumberThreads(); // TODO: LargestProcess?
/**
* Get the threads IDs by index from the current process
*
* @param start the starting index
* @param count the number of threads
* @return the list of thread IDs
*/
List<DebugThreadId> getThreads(int start, int count);
/**
* Get all thread IDs in the current process
*
* @return the list of thread IDs
*/
default List<DebugThreadId> getThreads() {
return getThreads(0, getNumberThreads());
}
DebugThreadId getThreadIdByHandle(long handle);
DebugProcessId getProcessIdByHandle(long handle);
int getNumberSystems();
List<DebugSessionId> getSystems(int start, int count);
default List<DebugSessionId> getSessions() {
int numberSystems = getNumberSystems();
if (numberSystems < 0) {
return new ArrayList<DebugSessionId>();
}
return getSystems(0, numberSystems);
}
int getNumberProcesses();
List<DebugProcessId> getProcesses(int start, int count);
default List<DebugProcessId> getProcesses() {
int numberProcesses = getNumberProcesses();
if (numberProcesses < 0) {
return new ArrayList<DebugProcessId>();
}
return getProcesses(0, numberProcesses);
}
int getCurrentThreadSystemId();
int getCurrentProcessSystemId();
DebugThreadId getThreadIdBySystemId(int systemId);
DebugProcessId getProcessIdBySystemId(int systemId);
}

View File

@ -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 thread.
*
* Note: This is not the same as the "TID." {@code dbgeng.dll} calls that the <em>system</em> ID of
* the thread.
*
* 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 TIDs with system
* TIDs.
*/
public class DebugThreadId implements Comparable<DebugThreadId> {
public final int id;
public DebugThreadId(int id) {
this.id = id;
}
@Override
public int hashCode() {
return Integer.hashCode(id);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof DebugThreadId)) {
return false;
}
DebugThreadId that = (DebugThreadId) obj;
if (this.id != that.id) {
return false;
}
return true;
}
@Override
public int compareTo(DebugThreadId that) {
return Integer.compare(this.id, that.id);
}
@Override
public String toString() {
return "<dbgeng.dll Engine TID " + id + ">";
}
}

View File

@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.dbgeng;
/**
* Information about a thread.
*
* The fields correspond to parameters taken by {@code CreateThread} of
* {@code IDebugEventCallbacks}. They also appear as a subset of parameters taken by
* {@code CreateProcess} of {@code IDebugEventCallbacks}.
*/
public class DebugThreadInfo {
public final long handle;
public final long dataOffset;
public final long startOffset;
public DebugThreadInfo(long handle, long dataOffset, long startOffset) {
this.handle = handle;
this.dataOffset = dataOffset;
this.startOffset = startOffset;
}
}

View File

@ -0,0 +1,457 @@
/* ###
* 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 java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import agent.dbgeng.dbgeng.DebugValue.DebugValueType;
import agent.dbgeng.dbgeng.DebugValue.ForDebugValueType;
import ghidra.util.NumericUtilities;
/**
* Data copied from a {@code DEBUG_VALUE} as defined in {dbgeng.h}.
*/
@ForDebugValueType(DebugValueType.INVALID)
public interface DebugValue {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface ForDebugValueType {
DebugValueType value();
}
public static enum DebugValueType {
INVALID(0), //
INT8(Byte.SIZE), //
INT16(Short.SIZE), //
INT32(Integer.SIZE), //
INT64(Long.SIZE), //
FLOAT32(Float.SIZE), //
FLOAT64(Double.SIZE), //
FLOAT80(80), //
FLOAT82(82), //
FLOAT128(128), //
VECTOR64(64), //
VECTOR128(128), //
;
private static final Class<? extends DebugValue>[] CLASSES;
static {
@SuppressWarnings("unchecked")
Class<? extends DebugValue>[] supressed = new Class[DebugValueType.values().length];
CLASSES = supressed;
for (Class<?> cls : DebugValue.class.getDeclaredClasses()) {
if (!DebugValue.class.isAssignableFrom(cls)) {
continue;
}
Class<? extends DebugValue> dvCls = cls.asSubclass(DebugValue.class);
DebugValueType type = getDebugValueTypeForClass(dvCls);
CLASSES[type.ordinal()] = dvCls;
}
}
public static DebugValueType getDebugValueTypeForClass(Class<? extends DebugValue> cls) {
ForDebugValueType annot = cls.getAnnotation(ForDebugValueType.class);
if (annot == null) {
throw new AssertionError(
"INTERNAL: Missing ForDebugValueType annotation on " + cls);
}
return annot.value();
}
public final int bitLength;
public final int byteLength;
private DebugValueType(int bitLength) {
this.bitLength = bitLength;
this.byteLength = (bitLength + 7) / 8;
}
public Class<? extends DebugValue> getDebugValueClass() {
return CLASSES[ordinal()];
}
public DebugValue decodeBytes(byte[] bytes) throws IllegalArgumentException {
try {
return CLASSES[ordinal()].getConstructor(byte[].class).newInstance(bytes);
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
catch (InvocationTargetException e) {
if (e.getCause() instanceof IllegalArgumentException) {
throw (IllegalArgumentException) e.getCause();
}
throw new AssertionError(e);
}
}
}
@ForDebugValueType(DebugValueType.INT8)
public static class DebugInt8Value implements DebugValue {
private final byte value;
public DebugInt8Value(byte value) {
this.value = value;
}
public DebugInt8Value(byte[] bytes) {
if (bytes.length != 1) {
throw new IllegalArgumentException("Must have exactly 1 byte");
}
this.value = bytes[0];
}
public byte byteValue() {
return value;
}
@Override
public byte[] encodeAsBytes() {
return new byte[] { value };
}
@Override
public String toString() {
return "byte " + Integer.toHexString(value) + "h";
}
}
@ForDebugValueType(DebugValueType.INT16)
public static class DebugInt16Value implements DebugValue {
private final short value;
public DebugInt16Value(short value) {
this.value = value;
}
public DebugInt16Value(byte[] bytes) {
if (bytes.length != Short.BYTES) {
throw new IllegalArgumentException("Must have exactly " + Short.BYTES + " bytes");
}
ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
this.value = buf.getShort();
}
public short shortValue() {
return value;
}
@Override
public byte[] encodeAsBytes() {
ByteBuffer buf = ByteBuffer.allocate(Short.BYTES).order(ByteOrder.BIG_ENDIAN);
buf.putShort(value);
return buf.array();
}
@Override
public String toString() {
return "word " + Integer.toHexString(value) + "h";
}
}
@ForDebugValueType(DebugValueType.INT32)
public static class DebugInt32Value implements DebugValue {
private final int value;
public DebugInt32Value(int value) {
this.value = value;
}
public DebugInt32Value(byte[] bytes) {
if (bytes.length != Integer.BYTES) {
throw new IllegalArgumentException("Must have exactly " + Integer.BYTES + " bytes");
}
ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
this.value = buf.getInt();
}
public int intValue() {
return value;
}
@Override
public byte[] encodeAsBytes() {
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.BIG_ENDIAN);
buf.putInt(value);
return buf.array();
}
@Override
public String toString() {
return "dword " + Integer.toHexString(value) + "h";
}
}
@ForDebugValueType(DebugValueType.INT64)
public static class DebugInt64Value implements DebugValue {
private final long value;
public DebugInt64Value(long value) {
this.value = value;
}
public DebugInt64Value(byte[] bytes) {
if (bytes.length != Long.BYTES) {
throw new IllegalArgumentException("Must have exactly " + Long.BYTES + " bytes");
}
ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
this.value = buf.getLong();
}
public long longValue() {
return value;
}
@Override
public byte[] encodeAsBytes() {
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.BIG_ENDIAN);
buf.putLong(value);
return buf.array();
}
@Override
public String toString() {
return "qword " + Long.toHexString(value) + "h";
}
}
@ForDebugValueType(DebugValueType.FLOAT32)
public static class DebugFloat32Value implements DebugValue {
private final float value;
public DebugFloat32Value(float value) {
this.value = value;
}
public DebugFloat32Value(byte[] bytes) {
if (bytes.length != Float.BYTES) {
throw new IllegalArgumentException("Must have exactly " + Float.BYTES + " bytes");
}
ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
this.value = buf.getFloat();
}
public float floatValue() {
return value;
}
@Override
public byte[] encodeAsBytes() {
ByteBuffer buf = ByteBuffer.allocate(Float.BYTES).order(ByteOrder.BIG_ENDIAN);
buf.putFloat(value);
return buf.array();
}
@Override
public String toString() {
return "f32 " + value;
}
}
@ForDebugValueType(DebugValueType.FLOAT64)
public static class DebugFloat64Value implements DebugValue {
private final double value;
public DebugFloat64Value(double value) {
this.value = value;
}
public DebugFloat64Value(byte[] bytes) {
if (bytes.length != Double.BYTES) {
throw new IllegalArgumentException("Must have exactly " + Double.BYTES + " bytes");
}
ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
this.value = buf.getDouble();
}
public double doubleValue() {
return value;
}
@Override
public byte[] encodeAsBytes() {
ByteBuffer buf = ByteBuffer.allocate(Double.BYTES).order(ByteOrder.BIG_ENDIAN);
buf.putDouble(value);
return buf.array();
}
@Override
public String toString() {
return "f64 " + value;
}
}
/**
* Extended-precision float
*/
@ForDebugValueType(DebugValueType.FLOAT80)
public static class DebugFloat80Value implements DebugValue {
private final byte[] bytes;
public DebugFloat80Value(byte[] bytes) {
if (bytes.length != 10) {
throw new IllegalArgumentException("Must have exactly 10 bytes");
}
this.bytes = Arrays.copyOf(bytes, 10);
}
public byte[] bytes() {
return bytes;
}
@Override
public byte[] encodeAsBytes() {
return bytes;
}
@Override
public String toString() {
return "f80 " + NumericUtilities.convertBytesToString(bytes);
}
}
/**
* Specific to IA-64 (Itanium) floating-point registers
*
* 17-bit exponent, 64-bit fraction. Not sure how it's aligned in memory, though.
*/
@ForDebugValueType(DebugValueType.FLOAT82)
public static class DebugFloat82Value implements DebugValue {
private final byte[] bytes;
public DebugFloat82Value(byte[] bytes) {
if (bytes.length != 11) {
throw new IllegalArgumentException("Must have exactly 11 bytes");
}
this.bytes = Arrays.copyOf(bytes, 11);
}
public byte[] bytes() {
return bytes;
}
@Override
public byte[] encodeAsBytes() {
return bytes;
}
@Override
public String toString() {
return "f82 " + NumericUtilities.convertBytesToString(bytes);
}
}
/**
* Quadruple-precision float
*/
@ForDebugValueType(DebugValueType.FLOAT128)
public static class DebugFloat128Value implements DebugValue {
private final byte[] bytes;
public DebugFloat128Value(byte[] bytes) {
if (bytes.length != 16) {
throw new IllegalArgumentException("Must have exactly 16 bytes");
}
this.bytes = Arrays.copyOf(bytes, 16);
}
public byte[] bytes() {
return bytes;
}
@Override
public byte[] encodeAsBytes() {
return bytes;
}
@Override
public String toString() {
return "f128 " + NumericUtilities.convertBytesToString(bytes);
}
}
@ForDebugValueType(DebugValueType.VECTOR64)
public static class DebugVector64Value implements DebugValue {
private final byte[] bytes;
public DebugVector64Value(byte[] bytes) {
if (bytes.length != 8) {
throw new IllegalArgumentException("Must have exactly 8 bytes");
}
this.bytes = Arrays.copyOf(bytes, 8);
}
public byte[] vi4() {
return bytes;
}
@Override
public byte[] encodeAsBytes() {
return bytes;
}
@Override
public String toString() {
return "vec64 " + NumericUtilities.convertBytesToString(bytes);
}
}
@ForDebugValueType(DebugValueType.VECTOR128)
public static class DebugVector128Value implements DebugValue {
private final byte[] bytes;
public DebugVector128Value(byte[] bytes) {
if (bytes.length != 16) {
throw new IllegalArgumentException(
"Must have exactly 16 bytes. got " + bytes.length);
}
this.bytes = Arrays.copyOf(bytes, 16);
}
public byte[] vi8() {
return bytes;
}
@Override
public byte[] encodeAsBytes() {
return bytes;
}
@Override
public String toString() {
return "vec128 " + NumericUtilities.convertBytesToString(bytes);
}
}
default DebugValueType getValueType() {
return DebugValueType.getDebugValueTypeForClass(getClass());
}
/**
* TODO: Document me
*
* Encodes the value as an array of bytes in big-endian order
*
* @return the encoded value
*/
public byte[] encodeAsBytes();
}

View File

@ -0,0 +1,29 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.dbgeng.err;
/**
* The base exception for checked {@code dbgeng.dll} wrapper-related errors.
*/
public class DbgEngException extends Exception {
public DbgEngException() {
super();
}
public DbgEngException(String message) {
super(message);
}
}

View File

@ -0,0 +1,29 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.dbgeng.err;
/**
* The base exception for unchecked {@code dbgeng.dll} wrapper-related errors.
*/
public class DbgEngRuntimeException extends RuntimeException {
public DbgEngRuntimeException() {
super();
}
public DbgEngRuntimeException(String message) {
super(message);
}
}

View File

@ -0,0 +1,49 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.dbgeng.ext;
import com.sun.jna.platform.win32.COM.COMUtils;
import com.sun.jna.ptr.PointerByReference;
import agent.dbgeng.dbgeng.DebugClient;
import agent.dbgeng.impl.dbgeng.client.DebugClientInternal;
import agent.dbgeng.jna.dbgeng.client.WrapIDebugClient;
import agent.dbgeng.jna.javaprovider.JavaProviderNative;
/**
* Wrapper for "javaprovider" plugin library
*
* @deprecated In one (abandoned) use case, the SCTL server can be loaded as a
* "{@code engext.cpp}-style" plugin, presumably into any {@code dbgeng.dll}-powered
* debugger. This is accomplished by embedding the JVM into the plugin, and then calling
* an alternative entry point. This plugin also provides a utility function for invoking
* {@code CreateClient} on the client provided to the plugin by the host debugger.
*/
@Deprecated
public class JavaProvider {
public static DebugClient createClient() {
PointerByReference pClient = new PointerByReference();
COMUtils.checkRC(JavaProviderNative.INSTANCE.createClient(pClient.getPointer()));
WrapIDebugClient wrap = new WrapIDebugClient(pClient.getValue());
try {
return DebugClientInternal.tryPreferredInterfaces(wrap::QueryInterface);
}
finally {
wrap.Release();
}
}
}

View File

@ -0,0 +1,125 @@
/* ###
* 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.util;
import java.lang.reflect.Method;
import agent.dbgeng.dbgeng.*;
import agent.dbgeng.dbgeng.DebugClient.*;
import ghidra.comm.util.BitmaskSet;
/**
* A convenient abstract implementation of {@link DebugEventCallbacks}
*
* This implementation automatically computes the value for {@link #getInterestMask()} based on the
* overridden methods. The default implementations all return {@link DebugStatus#NO_CHANGE}, should
* they happen to be called.
*/
public class DebugEventCallbacksAdapter implements DebugEventCallbacks {
private BitmaskSet<DebugEvent> interests = new BitmaskSet<>(DebugEvent.class);
public DebugEventCallbacksAdapter() {
try {
// Compute the interest mask based on methods that are overridden
for (Method im : DebugEventCallbacks.class.getDeclaredMethods()) {
Method m = this.getClass().getMethod(im.getName(), im.getParameterTypes());
if (m.getDeclaringClass() == DebugEventCallbacksAdapter.class) {
continue;
}
// The interface method is overridden, grab the annotation from the interface
ForInterest fi = im.getAnnotation(ForInterest.class);
if (fi == null) {
throw new AssertionError("No ForInterest annotation present on " + m);
}
interests.add(fi.value());
}
}
catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
@Override
public BitmaskSet<DebugEvent> getInterestMask() {
return interests;
}
@Override
public DebugStatus breakpoint(DebugBreakpoint bp) {
return DebugStatus.NO_CHANGE;
}
@Override
public DebugStatus exception(DebugExceptionRecord64 exception, boolean firstChance) {
return DebugStatus.NO_CHANGE;
}
@Override
public DebugStatus createThread(DebugThreadInfo debugThreadInfo) {
return DebugStatus.NO_CHANGE;
}
@Override
public DebugStatus exitThread(int exitCode) {
return DebugStatus.NO_CHANGE;
}
@Override
public DebugStatus createProcess(DebugProcessInfo debugProcessInfo) {
return DebugStatus.NO_CHANGE;
}
@Override
public DebugStatus exitProcess(int exitCode) {
return DebugStatus.NO_CHANGE;
}
@Override
public DebugStatus loadModule(DebugModuleInfo debugModuleInfo) {
return DebugStatus.NO_CHANGE;
}
@Override
public DebugStatus unloadModule(String imageBaseName, long baseOffset) {
return DebugStatus.NO_CHANGE;
}
@Override
public DebugStatus systemError(int error, int level) {
return DebugStatus.NO_CHANGE;
}
@Override
public DebugStatus sessionStatus(SessionStatus status) {
return DebugStatus.NO_CHANGE;
}
@Override
public DebugStatus changeDebuggeeState(BitmaskSet<ChangeDebuggeeState> flags, long argument) {
return DebugStatus.NO_CHANGE;
}
@Override
public DebugStatus changeEngineState(BitmaskSet<ChangeEngineState> flags, long argument) {
return DebugStatus.NO_CHANGE;
}
@Override
public DebugStatus changeSymbolState(BitmaskSet<ChangeSymbolState> flags, long argument) {
return DebugStatus.NO_CHANGE;
}
}

View File

@ -0,0 +1,242 @@
/* ###
* 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.gadp;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import agent.dbgeng.gadp.impl.DbgEngGadpServerImpl;
import ghidra.dbg.agent.AgentWindow;
import ghidra.util.Msg;
public interface DbgEngGadpServer extends AutoCloseable {
public static final String DEFAULT_DBGSRV_TRANSPORT = "tcp:port=11200";
/**
* The entry point for the SCTL-DBGENG server in stand-alone mode
*
* Run it to see help.
*
* @param args the command-line arguments
* @throws IOException if an I/O error occurs
* @throws ExecutionException
* @throws InterruptedException
*/
public static void main(String[] args) throws Exception {
new DbgEngRunner().run(args);
}
/**
* Create a new instance of the server
*
* @param addr the address to bind the SCTL server to
* @param busId the client ID the server should use on the bus for synthesized commands
* @param dbgSrvTransport the transport specification for the {@code dbgeng.dll} server
* @return the server instance
* @throws IOException
*/
public static DbgEngGadpServer newInstance(SocketAddress addr) throws IOException {
return new DbgEngGadpServerImpl(addr);
}
/**
* Runs the server from the command line
*/
public class DbgEngRunner {
protected InetSocketAddress bindTo;
protected List<String> dbgengArgs = new ArrayList<>();
protected byte busId = 1;
protected String dbgSrvTransport = DEFAULT_DBGSRV_TRANSPORT;
protected String remote = null;
public DbgEngRunner() {
}
public void run(String args[])
throws IOException, InterruptedException, ExecutionException {
parseArguments(args);
try (DbgEngGadpServer server = newInstance(bindTo)) {
//TODO: fix/test the debugConnect case when args are passed
server.startDbgEng(dbgengArgs.toArray(new String[] {})).exceptionally(e -> {
Msg.error(this, "Error starting dbgeng/GADP", e);
System.exit(-1);
return null;
});
new AgentWindow("dbgeng.dll Agent for Ghidra", server.getLocalAddress());
while (server.isRunning()) {
// TODO: Put consoleLoop back?
Thread.sleep(1000);
}
System.exit(0);
}
}
protected void parseArguments(String[] args) {
String iface = "localhost";
int port = 12345;
// NOTE: Maybe commons-cli or Argparse4j?
Iterator<String> ait = Arrays.asList(args).iterator();
while (ait.hasNext()) {
String a = ait.next();
if ("-h".equals(a) || "--help".equals(a)) {
printUsage();
System.exit(0);
}
else if ("-p".equals(a) || "--port".equals(a)) {
if (!ait.hasNext()) {
System.err.println("Expected PORT");
printUsage();
System.exit(-1);
}
String portStr = ait.next();
try {
port = Integer.parseInt(portStr);
}
catch (NumberFormatException e) {
System.err.println("Integer required. Got " + portStr);
printUsage();
System.exit(-1);
}
}
else if ("-H".equals(a) || "--host".equals(a)) {
if (!ait.hasNext()) {
System.err.println("Expected HOST/ADDR");
printUsage();
System.exit(-1);
}
iface = ait.next();
}
else if ("-i".equals(a) || "--bus-id".equals(a)) {
if (!ait.hasNext()) {
System.err.println("Expected ID");
printUsage();
System.exit(-1);
}
String busIdStr = ait.next();
try {
busId = Byte.parseByte(busIdStr);
//dbgengArgs.add(busIdStr);
}
catch (NumberFormatException e) {
System.err.println("Byte required. Got " + busIdStr);
printUsage();
System.exit(-1);
}
}
else if ("-t".equals(a) || "--transport".equals(a)) {
if (!ait.hasNext()) {
System.err.println("Expected TRANSPORT");
System.err.println("See the MSDN 'Activating a Process Server'");
printUsage();
System.exit(-1);
}
dbgSrvTransport = ait.next();
dbgengArgs.add(dbgSrvTransport);
}
else if ("-r".equals(a) || "--remote".equals(a)) {
if (!ait.hasNext()) {
System.err.println("Expected TRANSPORT:HOST,PORT");
printUsage();
System.exit(-1);
}
remote = ait.next();
dbgengArgs.add(remote);
}
else {
System.err.println("Unknown option: " + a);
printUsage();
System.exit(-1);
}
}
bindTo = new InetSocketAddress(iface, port);
}
protected void printUsage() {
System.out.println("This is the GADP server for Windows dbgeng.dll. Usage:");
System.out.println();
System.out.println(" [-H HOST/ADDR] [-p PORT] [-i ID] [-t TRANSPORT] [-r REMOTE]");
System.out.println();
System.out.println("Options:");
System.out.println(
" --host/-H The address of the interface on which to listen.");
System.out.println(" Default is localhost");
System.out.println(
" --port/-p The TCP port on which to listen for GADP. Default is 12345");
System.out.println(
" --bus-id/-i The numeric client id for synthetic requests. Default is 1");
System.out.println(
" --transport/-t The transport specification for the Process Server.");
System.out.println(" Default is tcp:port=11200");
System.out.println(
" --remote/-r The transport specification for a remote server.");
}
}
/**
* Start the debugging server
*
* @return a future that completes when the server is ready
*/
CompletableFuture<Void> startDbgEng(String[] args);
/**
* Get the local address to which the SCTL server is bound.
*
* @return the local socket address
*/
SocketAddress getLocalAddress();
/**
* Starts the dbgeng manager's console loop
*
* @throws IOException if an I/O error occurs
*/
//public void consoleLoop() throws IOException;
/**
* Close all connections and ports, GADP and Process Server, and terminate the server
*
* @throws IOException if an I/O error occurs
*/
public void terminate() throws IOException;
/**
* Check if the server is running
*
* This will return false: 1) Before the server has been started, 2) After a call to
* {@link #terminate()}, or 3) When an error occurs causing the server to terminate
* unexpectedly. Otherwise, it returns true.
*
* @returns true if the server is currently running.
*/
public boolean isRunning();
/**
* Calls {@link #terminate()}
*
* @throws IOException if an I/O error occurs
*/
@Override
default void close() throws IOException {
terminate();
}
}

View File

@ -0,0 +1,85 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.gadp;
import java.util.List;
import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
import ghidra.util.classfinder.ExtensionPointProperties;
@FactoryDescription( //
brief = "MS dbgeng.dll (WinDbg) local agent via GADP/TCP", //
htmlDetails = "Launch a new agent using the Microsoft Debug Engine." //
)
@ExtensionPointProperties(priority = 100)
public class DbgEngLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory {
protected String remote = "none"; // Require user to start server
@FactoryOption("DebugConnect options (.server)")
public final Property<String> agentRemoteOption =
Property.fromAccessors(String.class, this::getAgentRemote, this::setAgentRemote);
protected String transport = "none"; // Require user to start server
@FactoryOption("Remote process server options (untested)")
public final Property<String> agentTransportOption =
Property.fromAccessors(String.class, this::getAgentTransport, this::setAgentTransport);
@Override
public boolean isCompatible() {
// TODO: Might instead look for the DLL
return System.getProperty("os.name").toLowerCase().contains("windows");
}
public String getAgentTransport() {
return transport;
}
public void setAgentTransport(String transport) {
this.transport = transport;
}
public String getAgentRemote() {
return remote;
}
public void setAgentRemote(String remote) {
this.remote = remote;
}
@Override
protected String getThreadName() {
return "Local dbgeng.dll Agent stdout";
}
protected Class<?> getServerClass() {
return DbgEngGadpServer.class;
}
@Override
protected void completeCommandLine(List<String> cmd) {
cmd.add(getServerClass().getCanonicalName());
cmd.addAll(List.of("-H", host));
cmd.addAll(List.of("-p", Integer.toString(port)));
//cmd.addAll(List.of("-t", transport));
if (!remote.equals("none")) {
cmd.addAll(List.of("-r", remote));
}
if (!transport.equals("none")) {
cmd.addAll(List.of("-t", transport));
}
}
}

View File

@ -0,0 +1,244 @@
/* ###
* 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.gadp.impl;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import com.sun.jna.platform.win32.COM.COMException;
import agent.dbgeng.dbgeng.DebugClient;
import agent.dbgeng.dbgeng.DebugClient.DebugStatus;
import agent.dbgeng.dbgeng.DebugControl;
import agent.dbgeng.manager.DbgManager;
import ghidra.util.Msg;
/**
* A single-threaded executor which creates and exclusively accesses the {@code dbgeng.dll} client.
*
* <p>
* The executor also has a priority mechanism, so that callbacks may register follow-on handlers
* which take precedence over other tasks in the queue (which could trigger additional callbacks).
* This is required since certain operation are not allowed during normal callback processing. For
* example, changing the current process is typically not allowed, but it is necessary to retrieve a
* thread's context.
*/
public abstract class AbstractClientThreadExecutor extends AbstractExecutorService {
private static final int DEFAULT_PRIORITY = 10;
protected DebugClient client;
protected boolean shuttingDown = false;
protected final Queue<Entry> queue = new PriorityQueue<>();
protected Thread thread = new Thread(this::run, "DebugClient");
protected final AtomicBoolean waitRegistered = new AtomicBoolean();
protected abstract void init();
public static class Entry implements Comparable<Entry> {
final int priority;
public final Runnable command;
public Entry(int priority, Runnable command) {
this.priority = priority;
this.command = command;
}
@Override
public int compareTo(Entry that) {
return Integer.compare(this.priority, that.priority);
}
}
/**
* Obtain a reference to the client, only if the calling thread is this executor's thread.
*
* @return the client
*/
public DebugClient getClient() {
if (thread != Thread.currentThread()) {
//TODO: throw new AssertionError("Cannot get client outside owning thread");
}
return client;
}
/**
* Instruct the executor to call {@link DebugClient#dispatchCallbacks()} when it next idles.
*/
public void cancelWait() {
waitRegistered.set(false);
}
/**
* Instruct the executor to call {@link DebugControl#waitForEvent()} when it next idles.
*/
public void registerWait() {
waitRegistered.set(true);
}
private Entry pollQueue() {
synchronized (queue) {
return queue.poll();
}
}
private void run() {
/**
* The general idea is to run indefinitely, taking every precaution to protect this thread's
* life, since only it can access the client. Granted, if it turns out to be too difficult,
* we can always create a new thread and client, using the existing client's reentrant
* methods.
*
* <p>
* As stated in the MSDN, this thread repeatedly calls {@code DispatchEvents} in order to
* receive callbacks regarding events caused by other clients. If, however, an wait is
* registered, or the current engine state indicates that a wait is proper, the thread calls
* {@code WaitForEvent} instead. The thread is occupied until the wait completes, which is
* fine since the engine is inaccessible (except to certain callbacks) until it completes,
* anyway.
*/
try {
init();
while (!shuttingDown) {
Entry next;
while (null != (next = pollQueue())) {
if (shuttingDown) {
return;
}
try {
//System.out.println("Executing: " + next);
next.command.run();
//System.out.println("Done");
}
catch (Throwable t) {
Msg.error(this, "Task in executor threw: " + t);
}
}
DebugStatus status = client.getControl().getExecutionStatus();
if (status.shouldWait && status != DebugStatus.NO_DEBUGGEE ||
waitRegistered.get()) {
waitRegistered.set(false);
try {
getManager().waitForEventEx();
//client.getControl().waitForEvent();
}
catch (COMException e) {
Msg.error(this, "Error during WaitForEvents: " + e);
}
}
else {
try {
client.dispatchCallbacks(100); // TODO: Better synchronization
}
catch (COMException e) {
Msg.error(this, "Error during DispatchCallbacks: " + e);
}
}
}
}
catch (Throwable t) {
Msg.error(this, "Non-respawnable executor terminated unexpectedly", t);
shuttingDown = true;
}
}
@Override
public void shutdown() {
shuttingDown = true;
}
@Override
public List<Runnable> shutdownNow() {
shuttingDown = true;
client.exitDispatch();
thread.interrupt();
List<Runnable> left = new ArrayList<>(queue.size());
for (Entry ent : queue) {
left.add(ent.command);
}
return left;
}
@Override
public boolean isShutdown() {
return shuttingDown;
}
@Override
public boolean isTerminated() {
return !thread.isAlive();
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
long millis = TimeUnit.MILLISECONDS.convert(timeout, unit);
thread.join(millis);
return !thread.isAlive();
}
@Override
public void execute(Runnable command) {
execute(DEFAULT_PRIORITY, command);
}
/**
* Schedule a task with a given priority.
*
* <p>
* Smaller priority values indicate earlier execution. The default priority is
* {@link #DEFAULT_PRIORITY}.
*
* @param priority the priority
* @param command the task
*/
public void execute(int priority, Runnable command) {
if (shuttingDown) {
// TODO: Is this the correct exception?
throw new RejectedExecutionException("Executor is shutting down");
}
if (!thread.isAlive()) {
throw new RejectedExecutionException("Executor has terminated");
}
synchronized (queue) {
queue.add(new Entry(priority, command));
// TODO: Putting this in causes sync/output flushing problems
//client.exitDispatch();
}
}
public boolean isCurrentThread() {
return thread.equals(Thread.currentThread());
}
/**
* Schedule a task with the given priority, taking a reference to the client.
*
* <p>
* This is a convenience which spares a call to {@link #getClient()}. See
* {@link #execute(int, Runnable)} about priority.
*
* @param priority the priority
* @param command the task
*/
public void execute(int priority, Consumer<DebugClient> command) {
execute(priority, () -> command.accept(client));
}
public abstract DbgManager getManager();
public abstract void setManager(DbgManager manager);
}

View File

@ -0,0 +1,63 @@
/* ###
* 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.gadp.impl;
import java.util.function.Supplier;
import agent.dbgeng.dbgeng.DebugClient;
import agent.dbgeng.manager.DbgManager;
/**
* A single-threaded executor which creates and exclusively accesses the {@code dbgeng.dll} client.
*
* The executor also has a priority mechanism, so that callbacks may register follow-on handlers
* which take precedence over other tasks in the queue (which could trigger additional callbacks).
* This is required since certain operation are not allowed during normal callback processing. For
* example, changing the current process is typically not allowed, but it is necessary to retrieve a
* thread's context.
*/
public class DbgEngClientThreadExecutor extends AbstractClientThreadExecutor {
private final Supplier<DebugClient> makeClient;
private DbgManager manager;
/**
* Instantiate a new executor, providing a means of creating the client
*
* @param makeClient the callback to create the client
*/
public DbgEngClientThreadExecutor(Supplier<DebugClient> makeClient) {
this.makeClient = makeClient;
thread.setDaemon(true);
thread.start();
}
@Override
protected void init() {
this.client = makeClient.get();
}
@Override
public DbgManager getManager() {
return manager;
}
@Override
public void setManager(DbgManager manager) {
this.manager = manager;
}
}

View File

@ -0,0 +1,64 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.gadp.impl;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.concurrent.CompletableFuture;
import agent.dbgeng.gadp.DbgEngGadpServer;
import agent.dbgeng.model.AbstractDbgModel;
import agent.dbgeng.model.impl.DbgModelImpl;
import ghidra.dbg.gadp.server.AbstractGadpServer;
public class DbgEngGadpServerImpl implements DbgEngGadpServer {
public class GadpSide extends AbstractGadpServer {
public GadpSide(AbstractDbgModel model, SocketAddress addr)
throws IOException {
super(model, addr);
}
}
protected final AbstractDbgModel model;
protected final GadpSide server;
public DbgEngGadpServerImpl(SocketAddress addr) throws IOException {
super();
this.model = new DbgModelImpl();
this.server = new GadpSide(model, addr);
}
@Override
public CompletableFuture<Void> startDbgEng(String[] args) {
return model.startDbgEng(args).thenCompose(__ -> server.launchAsyncService());
}
@Override
public SocketAddress getLocalAddress() {
return server.getLocalAddress();
}
@Override
public boolean isRunning() {
return model.isRunning();
}
@Override
public void terminate() throws IOException {
model.terminate();
server.terminate();
}
}

View File

@ -0,0 +1,86 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.function.Function;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Guid.REFIID;
import com.sun.jna.platform.win32.WinDef.ULONG;
import com.sun.jna.platform.win32.WinNT.HRESULT;
import com.sun.jna.platform.win32.COM.*;
import com.sun.jna.ptr.PointerByReference;
import agent.dbgeng.dbgeng.err.DbgEngRuntimeException;
import ghidra.util.Msg;
public abstract class DbgEngUtil {
public static final ULONG DEBUG_ANY_ID = new ULONG(-1);
private DbgEngUtil() {
}
public static interface InterfaceSupplier {
HRESULT get(REFIID refiid, PointerByReference pClient);
}
@SuppressWarnings("unchecked")
public static <I> I tryPreferredInterfaces(Class<I> cls,
Map<REFIID, ? extends Class<?>> preferred, InterfaceSupplier supplier) {
PointerByReference ppClient = new PointerByReference();
for (Map.Entry<REFIID, ? extends Class<?>> ent : preferred.entrySet()) {
try {
COMUtils.checkRC(supplier.get(ent.getKey(), ppClient));
if (ppClient.getValue() == null) {
continue;
}
Object impl =
ent.getValue().getConstructor(Pointer.class).newInstance(ppClient.getValue());
Method instanceFor = cls.getMethod("instanceFor", ent.getValue());
Object instance = instanceFor.invoke(null, impl);
return (I) instance;
}
catch (COMException e) {
Msg.debug(DbgEngUtil.class, e + " (" + ent.getValue() + ")");
// TODO: Only try next on E_NOINTERFACE?
// Try next
}
catch (Exception e) {
throw new AssertionError("INTERNAL: Unexpected exception", e);
}
}
throw new DbgEngRuntimeException("None of the preferred interfaces are supported");
}
public static <T extends Unknown, U> U lazyWeakCache(Map<Pointer, U> cache, T unk,
Function<T, U> forNew) {
U present = cache.get(unk.getPointer());
if (present != null) {
unk.Release();
return present;
}
U absent = forNew.apply(unk);
cache.put(unk.getPointer(), absent);
return absent;
}
public static void dbgline() {
System.out.println(new Exception().getStackTrace()[1]);
System.out.flush();
}
}

View File

@ -0,0 +1,66 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng;
import agent.dbgeng.dbgeng.DebugRunningProcess;
import agent.dbgeng.dbgeng.DebugServerId;
import agent.dbgeng.impl.dbgeng.client.DebugClientInternal;
import ghidra.comm.util.BitmaskSet;
public class DebugRunningProcessImpl
implements DebugRunningProcess, Comparable<DebugRunningProcessImpl> {
public DebugRunningProcessImpl(DebugClientInternal client, DebugServerId server, int systemId) {
this.client = client;
this.server = server;
this.systemId = systemId;
}
protected final DebugClientInternal client;
protected final DebugServerId server;
protected final int systemId;
@Override
public int getSystemId() {
return systemId;
}
@Override
public Description getFullDescription(Description.ProcessDescriptionFlags... flags) {
return client.getProcessDescription(server, systemId, BitmaskSet.of(flags));
}
@Override
public String getExecutableName(Description.ProcessDescriptionFlags... flags) {
return getFullDescription(flags).getExecutableName();
}
@Override
public String getDescription(Description.ProcessDescriptionFlags... flags) {
return getFullDescription(flags).getDescription();
}
@Override
public int compareTo(DebugRunningProcessImpl that) {
int result;
result = Integer.compare(this.systemId, that.systemId);
if (result != 0) {
return result;
}
return 0;
}
}

View File

@ -0,0 +1,38 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.advanced;
import agent.dbgeng.dbgeng.DbgEng;
import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.dbgeng.DbgEng.OpaqueCleanable;
import agent.dbgeng.jna.dbgeng.advanced.IDebugAdvanced;
public class DebugAdvancedImpl1 implements DebugAdvancedInternal {
@SuppressWarnings("unused")
private final OpaqueCleanable cleanable;
@SuppressWarnings("unused")
private final IDebugAdvanced jnaAdvanced;
public DebugAdvancedImpl1(IDebugAdvanced jnaAdvanced) {
this.cleanable = DbgEng.releaseWhenPhantom(this, jnaAdvanced);
this.jnaAdvanced = jnaAdvanced;
}
@Override
public DebugThreadBasicInformation getThreadBasicInformation(DebugThreadId tid) {
throw new UnsupportedOperationException("Not supported by this interface");
}
}

View File

@ -0,0 +1,85 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.advanced;
import com.sun.jna.platform.win32.WinDef.ULONG;
import com.sun.jna.platform.win32.WinDef.ULONGLONG;
import agent.dbgeng.dbgeng.DebugThreadId;
import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_THREAD_BASIC_INFORMATION;
import agent.dbgeng.jna.dbgeng.advanced.IDebugAdvanced2;
import com.sun.jna.platform.win32.COM.COMUtils;
import ghidra.comm.util.BitmaskSet;
public class DebugAdvancedImpl2 extends DebugAdvancedImpl1 {
private final IDebugAdvanced2 jnaAdvanced;
public DebugAdvancedImpl2(IDebugAdvanced2 jnaAdvanced) {
super(jnaAdvanced);
this.jnaAdvanced = jnaAdvanced;
}
@Override
public DebugThreadBasicInformation getThreadBasicInformation(DebugThreadId tid) {
ULONG ulWhich = new ULONG(WhichSystemObjectInformation.THREAD_BASIC_INFORMATION.ordinal());
ULONGLONG ullUnused = new ULONGLONG(0);
ULONG ulThreadId = new ULONG(tid.id);
DEBUG_THREAD_BASIC_INFORMATION sInfo = new DEBUG_THREAD_BASIC_INFORMATION();
ULONG ulBufferSize = new ULONG(sInfo.size());
COMUtils.checkRC(jnaAdvanced.GetSystemObjectInformation(ulWhich, ullUnused, ulThreadId,
sInfo.getPointer(), ulBufferSize, null));
sInfo.read();
Integer exitStatus = null;
Integer priorityClass = null;
Integer priority = null;
Long createTime = null;
Long exitTime = null;
Long kernelTime = null;
Long userTime = null;
Long startOffset = null;
Long affinity = null;
BitmaskSet<ThreadBasicInformationValidBits> valid =
new BitmaskSet<>(ThreadBasicInformationValidBits.class, sInfo.Valid.intValue());
if (valid.contains(ThreadBasicInformationValidBits.EXIT_STATUS)) {
exitStatus = sInfo.ExitStatus.intValue();
}
if (valid.contains(ThreadBasicInformationValidBits.PRIORITY_CLASS)) {
priorityClass = sInfo.PriorityClass.intValue();
}
if (valid.contains(ThreadBasicInformationValidBits.PRIORITY)) {
priority = sInfo.Priority.intValue();
}
if (valid.contains(ThreadBasicInformationValidBits.TIMES)) {
createTime = sInfo.CreateTime.longValue();
exitTime = sInfo.ExitTime.longValue();
kernelTime = sInfo.KernelTime.longValue();
userTime = sInfo.UserTime.longValue();
}
if (valid.contains(ThreadBasicInformationValidBits.START_OFFSET)) {
startOffset = sInfo.StartOffset.longValue();
}
if (valid.contains(ThreadBasicInformationValidBits.AFFINITY)) {
affinity = sInfo.Affinity.longValue();
}
return new DebugThreadBasicInformation(exitStatus, priorityClass, priority, createTime,
exitTime, kernelTime, userTime, startOffset, affinity);
}
}

View File

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.advanced;
import agent.dbgeng.jna.dbgeng.advanced.IDebugAdvanced3;
public class DebugAdvancedImpl3 extends DebugAdvancedImpl2 {
@SuppressWarnings("unused")
private final IDebugAdvanced3 jnaAdvanced;
public DebugAdvancedImpl3(IDebugAdvanced3 jnaAdvanced) {
super(jnaAdvanced);
this.jnaAdvanced = jnaAdvanced;
}
}

View File

@ -0,0 +1,88 @@
/* ###
* 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.impl.dbgeng.advanced;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Guid.REFIID;
import agent.dbgeng.dbgeng.DebugAdvanced;
import agent.dbgeng.impl.dbgeng.DbgEngUtil;
import agent.dbgeng.impl.dbgeng.DbgEngUtil.InterfaceSupplier;
import agent.dbgeng.jna.dbgeng.advanced.*;
import ghidra.comm.util.BitmaskUniverse;
import ghidra.util.datastruct.WeakValueHashMap;
public interface DebugAdvancedInternal extends DebugAdvanced {
Map<Pointer, DebugAdvancedInternal> CACHE = new WeakValueHashMap<>();
static DebugAdvancedInternal instanceFor(WrapIDebugAdvanced advanced) {
return DbgEngUtil.lazyWeakCache(CACHE, advanced, DebugAdvancedImpl1::new);
}
static DebugAdvancedInternal instanceFor(WrapIDebugAdvanced2 advanced) {
return DbgEngUtil.lazyWeakCache(CACHE, advanced, DebugAdvancedImpl2::new);
}
static DebugAdvancedInternal instanceFor(WrapIDebugAdvanced3 advanced) {
return DbgEngUtil.lazyWeakCache(CACHE, advanced, DebugAdvancedImpl3::new);
}
ImmutableMap.Builder<REFIID, Class<? extends WrapIDebugAdvanced>> PREFERRED_ADVANCED_IIDS_BUILDER =
ImmutableMap.builder();
Map<REFIID, Class<? extends WrapIDebugAdvanced>> PREFERRED_ADVANCED_IIDS =
PREFERRED_ADVANCED_IIDS_BUILDER //
.put(new REFIID(IDebugAdvanced3.IID_IDEBUG_ADVANCED3), WrapIDebugAdvanced3.class) //
.put(new REFIID(IDebugAdvanced2.IID_IDEBUG_ADVANCED2), WrapIDebugAdvanced2.class) //
.put(new REFIID(IDebugAdvanced.IID_IDEBUG_ADVANCED), WrapIDebugAdvanced.class) //
.build();
static DebugAdvancedInternal tryPreferredInterfaces(InterfaceSupplier supplier) {
return DbgEngUtil.tryPreferredInterfaces(DebugAdvancedInternal.class,
PREFERRED_ADVANCED_IIDS, supplier);
}
public enum WhichSystemObjectInformation {
THREAD_BASIC_INFORMATION, //
THREAD_NAME_WIDE, //
CURRENT_PROCESS_COOKIE, //
;
}
public enum ThreadBasicInformationValidBits implements BitmaskUniverse {
EXIT_STATUS(1 << 0), //
PRIORITY_CLASS(1 << 1), //
PRIORITY(1 << 2), //
TIMES(1 << 3), //
START_OFFSET(1 << 4), //
AFFINITY(1 << 5), //
ALL(0x3f);
;
ThreadBasicInformationValidBits(int mask) {
this.mask = mask;
}
int mask;
@Override
public long getMask() {
return mask;
}
}
}

View File

@ -0,0 +1,187 @@
/* ###
* 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.impl.dbgeng.breakpoint;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinDef.*;
import com.sun.jna.platform.win32.WinNT.HRESULT;
import com.sun.jna.platform.win32.COM.COMUtils;
import com.sun.jna.ptr.PointerByReference;
import agent.dbgeng.dbgeng.DbgEng;
import agent.dbgeng.dbgeng.DbgEng.OpaqueCleanable;
import agent.dbgeng.dbgeng.DebugClient;
import agent.dbgeng.impl.dbgeng.client.DebugClientInternal;
import agent.dbgeng.impl.dbgeng.control.DebugControlInternal;
import agent.dbgeng.jna.dbgeng.WinNTExtra.Machine;
import agent.dbgeng.jna.dbgeng.breakpoint.IDebugBreakpoint;
import agent.dbgeng.jna.dbgeng.client.WrapIDebugClient;
import ghidra.comm.util.BitmaskSet;
public class DebugBreakpointImpl1 implements DebugBreakpointInternal {
@SuppressWarnings("unused")
private final OpaqueCleanable cleanable;
private IDebugBreakpoint jnaBreakpoint;
private DebugControlInternal control;
public DebugBreakpointImpl1(IDebugBreakpoint jnaBreakpoint) {
this.cleanable = DbgEng.releaseWhenPhantom(this, jnaBreakpoint);
this.jnaBreakpoint = jnaBreakpoint;
}
@Override
public void setControl(DebugControlInternal control) {
this.control = control;
}
@Override
public void remove() {
control.removeBreakpoint(jnaBreakpoint);
// Prevent accidental access. Will be released during GC. NPE is better than segfault.
jnaBreakpoint = null;
}
@Override
public int getId() {
ULONGByReference pulId = new ULONGByReference();
COMUtils.checkRC(jnaBreakpoint.GetId(pulId));
return pulId.getValue().intValue();
}
@Override
public BreakFullType getType() {
ULONGByReference pulBreakType = new ULONGByReference();
ULONGByReference pulProcType = new ULONGByReference();
COMUtils.checkRC(jnaBreakpoint.GetType(pulBreakType, pulProcType));
BreakType breakType = BreakType.values()[pulBreakType.getValue().intValue()];
Machine procType = Machine.getByNumber(pulProcType.getValue().intValue());
return new BreakFullType(breakType, procType);
}
@Override
public DebugClient getAdder() {
PointerByReference pClient = new PointerByReference();
COMUtils.checkRC(jnaBreakpoint.GetAdder(pClient.getPointer()));
WrapIDebugClient wrap = new WrapIDebugClient(pClient.getValue());
try {
return DebugClientInternal.tryPreferredInterfaces(wrap::QueryInterface);
}
finally {
wrap.Release();
}
}
@Override
public BitmaskSet<BreakFlags> getFlags() {
ULONGByReference pulFlags = new ULONGByReference();
COMUtils.checkRC(jnaBreakpoint.GetFlags(pulFlags));
return new BitmaskSet<>(BreakFlags.class, pulFlags.getValue().longValue());
}
@Override
public void addFlags(BitmaskSet<BreakFlags> flags) {
ULONG ulFlags = new ULONG(flags.getBitmask());
COMUtils.checkRC(jnaBreakpoint.AddFlags(ulFlags));
}
@Override
public void addFlags(BreakFlags... flags) {
addFlags(BitmaskSet.of(flags));
}
@Override
public void removeFlags(BitmaskSet<BreakFlags> flags) {
ULONG ulFlags = new ULONG(flags.getBitmask());
COMUtils.checkRC(jnaBreakpoint.RemoveFlags(ulFlags));
}
@Override
public void removeFlags(BreakFlags... flags) {
removeFlags(BitmaskSet.of(flags));
}
@Override
public void setFlags(BitmaskSet<BreakFlags> flags) {
ULONG ulFlags = new ULONG(flags.getBitmask());
COMUtils.checkRC(jnaBreakpoint.SetFlags(ulFlags));
}
@Override
public void setFlags(BreakFlags... flags) {
setFlags(BitmaskSet.of(flags));
}
@Override
public Long getOffset() {
ULONGLONGByReference pullOffset = new ULONGLONGByReference();
HRESULT getOffset = jnaBreakpoint.GetOffset(pullOffset);
if (getOffset.longValue() == Kernel32.E_NOINTERFACE) {
// Per MSDN, this means the placement is deferred
return null;
}
COMUtils.checkRC(getOffset);
return pullOffset.getValue().longValue();
}
@Override
public void setOffset(long offset) {
ULONGLONG ullOffset = new ULONGLONG(offset);
COMUtils.checkRC(jnaBreakpoint.SetOffset(ullOffset));
}
@Override
public String getOffsetExpression() {
ULONGByReference pulExpressionSize = new ULONGByReference();
COMUtils.checkRC(jnaBreakpoint.GetOffsetExpression(null, new ULONG(0), pulExpressionSize));
byte[] buffer = new byte[pulExpressionSize.getValue().intValue()];
COMUtils.checkRC(
jnaBreakpoint.GetOffsetExpression(buffer, pulExpressionSize.getValue(), null));
return Native.toString(buffer);
}
@Override
public void setOffsetExpression(String expression) {
COMUtils.checkRC(jnaBreakpoint.SetOffsetExpression(expression));
}
@Override
public BreakDataParameters getDataParameters() {
ULONGByReference pulSize = new ULONGByReference();
ULONGByReference pulAccessType = new ULONGByReference();
COMUtils.checkRC(jnaBreakpoint.GetDataParameters(pulSize, pulAccessType));
return new BreakDataParameters(pulSize.getValue().intValue(),
new BitmaskSet<>(BreakAccess.class, pulAccessType.getValue().intValue()));
}
@Override
public void setDataParameters(BreakDataParameters params) {
setDataParameters(params.size, params.access);
}
@Override
public void setDataParameters(int size, BitmaskSet<BreakAccess> access) {
ULONG ulSize = new ULONG(size);
ULONG ulAccessType = new ULONG(access.getBitmask());
COMUtils.checkRC(jnaBreakpoint.SetDataParameters(ulSize, ulAccessType));
}
@Override
public void setDataParameters(int size, BreakAccess... access) {
setDataParameters(size, BitmaskSet.of(access));
}
}

View File

@ -0,0 +1,50 @@
/* ###
* 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.impl.dbgeng.breakpoint;
import com.sun.jna.Native;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.WinDef.ULONG;
import com.sun.jna.platform.win32.WinDef.ULONGByReference;
import com.sun.jna.platform.win32.COM.COMUtils;
import agent.dbgeng.jna.dbgeng.breakpoint.IDebugBreakpoint2;
public class DebugBreakpointImpl2 extends DebugBreakpointImpl1 {
@SuppressWarnings("unused")
private final IDebugBreakpoint2 jnaBreakpoint;
public DebugBreakpointImpl2(IDebugBreakpoint2 jnaBreakpoint) {
super(jnaBreakpoint);
this.jnaBreakpoint = jnaBreakpoint;
}
@Override
public String getOffsetExpression() {
ULONGByReference pulExpressionSize = new ULONGByReference();
COMUtils.checkRC(
jnaBreakpoint.GetOffsetExpressionWide(null, new ULONG(0), pulExpressionSize));
char[] buffer = new char[pulExpressionSize.getValue().intValue()];
COMUtils.checkRC(
jnaBreakpoint.GetOffsetExpressionWide(buffer, pulExpressionSize.getValue(), null));
return Native.toString(buffer);
}
@Override
public void setOffsetExpression(String expression) {
COMUtils.checkRC(jnaBreakpoint.SetOffsetExpressionWide(new WString(expression)));
}
}

View File

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.breakpoint;
import agent.dbgeng.jna.dbgeng.breakpoint.IDebugBreakpoint3;
public class DebugBreakpointImpl3 extends DebugBreakpointImpl2 {
@SuppressWarnings("unused")
private final IDebugBreakpoint3 jnaBreakpoint;
public DebugBreakpointImpl3(IDebugBreakpoint3 jnaBreakpoint) {
super(jnaBreakpoint);
this.jnaBreakpoint = jnaBreakpoint;
}
}

View File

@ -0,0 +1,66 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.breakpoint;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Guid.REFIID;
import agent.dbgeng.dbgeng.DebugBreakpoint;
import agent.dbgeng.impl.dbgeng.DbgEngUtil;
import agent.dbgeng.impl.dbgeng.DbgEngUtil.InterfaceSupplier;
import agent.dbgeng.impl.dbgeng.control.DebugControlInternal;
import agent.dbgeng.jna.dbgeng.breakpoint.*;
import ghidra.util.datastruct.WeakValueHashMap;
public interface DebugBreakpointInternal extends DebugBreakpoint {
Map<Pointer, DebugBreakpointInternal> CACHE = new WeakValueHashMap<>();
static DebugBreakpointInternal instanceFor(WrapIDebugBreakpoint bp) {
return DbgEngUtil.lazyWeakCache(CACHE, bp, DebugBreakpointImpl1::new);
}
static DebugBreakpointInternal instanceFor(WrapIDebugBreakpoint2 bp) {
return DbgEngUtil.lazyWeakCache(CACHE, bp, DebugBreakpointImpl2::new);
}
static DebugBreakpointInternal instanceFor(WrapIDebugBreakpoint3 bp) {
return DbgEngUtil.lazyWeakCache(CACHE, bp, DebugBreakpointImpl3::new);
}
ImmutableMap.Builder<REFIID, Class<? extends WrapIDebugBreakpoint>> PREFERRED_BREAKPOINT_IIDS_BUILDER =
ImmutableMap.builder();
Map<REFIID, Class<? extends WrapIDebugBreakpoint>> PREFERRED_BREAKPOINT_IIDS =
PREFERRED_BREAKPOINT_IIDS_BUILDER //
.put(new REFIID(IDebugBreakpoint3.IID_IDEBUG_BREAKPOINT3),
WrapIDebugBreakpoint3.class) //
.put(new REFIID(IDebugBreakpoint2.IID_IDEBUG_BREAKPOINT2),
WrapIDebugBreakpoint2.class) //
.put(new REFIID(IDebugBreakpoint.IID_IDEBUG_BREAKPOINT), WrapIDebugBreakpoint.class) //
.build();
static DebugBreakpointInternal tryPreferredInterfaces(DebugControlInternal control,
InterfaceSupplier supplier) {
DebugBreakpointInternal bpt = DbgEngUtil.tryPreferredInterfaces(
DebugBreakpointInternal.class, PREFERRED_BREAKPOINT_IIDS, supplier);
bpt.setControl(control);
return bpt;
}
void setControl(DebugControlInternal control);
}

View File

@ -0,0 +1,323 @@
/* ###
* 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.impl.dbgeng.client;
import java.util.ArrayList;
import java.util.List;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.WinDef.*;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.WinNT.HRESULT;
import com.sun.jna.platform.win32.COM.COMUtils;
import com.sun.jna.ptr.PointerByReference;
import agent.dbgeng.dbgeng.*;
import agent.dbgeng.dbgeng.DbgEng.OpaqueCleanable;
import agent.dbgeng.impl.dbgeng.DebugRunningProcessImpl;
import agent.dbgeng.impl.dbgeng.advanced.DebugAdvancedInternal;
import agent.dbgeng.impl.dbgeng.control.DebugControlInternal;
import agent.dbgeng.impl.dbgeng.dataspaces.DebugDataSpacesInternal;
import agent.dbgeng.impl.dbgeng.event.WrapCallbackIDebugEventCallbacks;
import agent.dbgeng.impl.dbgeng.io.WrapCallbackIDebugInputCallbacks;
import agent.dbgeng.impl.dbgeng.io.WrapCallbackIDebugOutputCallbacks;
import agent.dbgeng.impl.dbgeng.registers.DebugRegistersInternal;
import agent.dbgeng.impl.dbgeng.symbols.DebugSymbolsInternal;
import agent.dbgeng.impl.dbgeng.sysobj.DebugSystemObjectsInternal;
import agent.dbgeng.jna.dbgeng.client.IDebugClient;
import agent.dbgeng.jna.dbgeng.event.ListenerIDebugEventCallbacks;
import agent.dbgeng.jna.dbgeng.event.MarkerEventCallbacks;
import agent.dbgeng.jna.dbgeng.io.*;
import ghidra.comm.util.BitmaskSet;
public class DebugClientImpl1 implements DebugClientInternal {
@SuppressWarnings("unused")
private final OpaqueCleanable cleanable;
private final IDebugClient jnaClient;
private DebugAdvancedInternal advanced;
private DebugControlInternal control;
private DebugDataSpaces data;
private DebugRegisters registers;
private DebugSymbols symbols;
private DebugSystemObjects sysobjs;
// Keep references to callbacks here, since JNA doesn't keep one for things handed to natives.
protected MarkerOutputCallbacks listenerOutput;
protected MarkerInputCallbacks listenerInput;
protected MarkerEventCallbacks listenerEvent;
public DebugClientImpl1(IDebugClient jnaClient) {
// TODO: Debug and verify COM resource management
this.cleanable = DbgEng.releaseWhenPhantom(this, jnaClient);
this.jnaClient = jnaClient;
}
@Override
public IDebugClient getJNAClient() {
return jnaClient;
}
@Override
public DebugAdvanced getAdvanced() {
if (advanced == null) {
advanced = DebugAdvancedInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
}
return advanced;
}
@Override
public DebugControlInternal getControlInternal() {
if (control == null) {
control = DebugControlInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
}
return control;
}
@Override
public DebugControl getControl() {
return getControlInternal();
}
@Override
public DebugDataSpaces getDataSpaces() {
if (data == null) {
data = DebugDataSpacesInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
}
return data;
}
@Override
public DebugRegisters getRegisters() {
if (registers == null) {
registers = DebugRegistersInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
}
return registers;
}
@Override
public DebugSymbols getSymbols() {
if (symbols == null) {
symbols = DebugSymbolsInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
}
return symbols;
}
@Override
public DebugSystemObjects getSystemObjects() {
if (sysobjs == null) {
sysobjs = DebugSystemObjectsInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
}
return sysobjs;
}
@Override
public DebugServerId getLocalServer() {
return new DebugServerId(0);
}
@Override
public void startProcessServer(String options) {
COMUtils.checkRC(jnaClient.StartProcessServer(new ULONG(DebugClass.USER_WINDOWS.ordinal()),
options, null));
}
@Override
public DebugServerId connectProcessServer(String options) {
ULONGLONGByReference pulServer = new ULONGLONGByReference();
COMUtils.checkRC(jnaClient.ConnectProcessServer(options, pulServer));
return new DebugServerId(pulServer.getValue().longValue());
}
@Override
public List<DebugRunningProcess> getRunningProcesses(DebugServerId si) {
ULONGLONG server = new ULONGLONG(si.id);
ULONGByReference actualCount = new ULONGByReference();
COMUtils.checkRC(
jnaClient.GetRunningProcessSystemIds(server, null, new ULONG(0), actualCount));
int[] ids = new int[actualCount.getValue().intValue()];
COMUtils.checkRC(
jnaClient.GetRunningProcessSystemIds(server, ids, actualCount.getValue(), null));
List<DebugRunningProcess> result = new ArrayList<>(ids.length);
for (int id : ids) {
result.add(new DebugRunningProcessImpl(this, si, id));
}
return result;
}
@Override
public DebugRunningProcess.Description getProcessDescription(DebugServerId si, int systemId,
BitmaskSet<DebugRunningProcess.Description.ProcessDescriptionFlags> flags) {
ULONGLONG ullServer = new ULONGLONG(si.id);
ULONG ulId = new ULONG(systemId);
ULONG ulFlags = new ULONG(flags.getBitmask());
ULONGByReference actualExeNameSize = new ULONGByReference();
ULONGByReference actualDescriptionSize = new ULONGByReference();
COMUtils.checkRC(jnaClient.GetRunningProcessDescription(ullServer, ulId, ulFlags, null,
new ULONG(0), actualExeNameSize, null, new ULONG(0), actualDescriptionSize));
byte[] exeName = new byte[actualExeNameSize.getValue().intValue()];
byte[] description = new byte[actualDescriptionSize.getValue().intValue()];
COMUtils.checkRC(jnaClient.GetRunningProcessDescription(ullServer, ulId, ulFlags, exeName,
actualExeNameSize.getValue(), null, description, actualDescriptionSize.getValue(),
null));
return new DebugRunningProcess.Description(systemId, Native.toString(exeName),
Native.toString(description));
}
@Override
public void attachProcess(DebugServerId si, int processId,
BitmaskSet<DebugAttachFlags> attachFlags) {
ULONGLONG ullServer = new ULONGLONG(si.id);
ULONG ulPid = new ULONG(processId);
ULONG ulFlags = new ULONG(attachFlags.getBitmask());
COMUtils.checkRC(jnaClient.AttachProcess(ullServer, ulPid, ulFlags));
}
@Override
public void createProcess(DebugServerId si, String commandLine,
BitmaskSet<DebugCreateFlags> createFlags) {
ULONGLONG ullServer = new ULONGLONG(si.id);
ULONG ulFlags = new ULONG(createFlags.getBitmask());
COMUtils.checkRC(jnaClient.CreateProcess(ullServer, commandLine, ulFlags));
}
@Override
public void createProcessAndAttach(DebugServerId si, String commandLine,
BitmaskSet<DebugCreateFlags> createFlags, int processId,
BitmaskSet<DebugAttachFlags> attachFlags) {
ULONGLONG ullServer = new ULONGLONG(si.id);
ULONG ulFlags1 = new ULONG(createFlags.getBitmask());
ULONG ulPid = new ULONG(processId);
ULONG ulFlags2 = new ULONG(attachFlags.getBitmask());
COMUtils.checkRC(
jnaClient.CreateProcessAndAttach(ullServer, commandLine, ulFlags1, ulPid, ulFlags2));
}
@Override
public void startServer(String options) {
COMUtils.checkRC(jnaClient.StartServer(options));
}
@Override
public boolean dispatchCallbacks(int timeout) {
HRESULT hr = jnaClient.DispatchCallbacks(new ULONG(timeout));
COMUtils.checkRC(hr);
return hr.equals(WinNT.S_OK);
}
@Override
public void flushCallbacks() {
HRESULT hr = jnaClient.FlushCallbacks();
COMUtils.checkRC(hr);
}
@Override
public void exitDispatch(DebugClient client) {
DebugClientInternal ic = (DebugClientInternal) client;
COMUtils.checkRC(jnaClient.ExitDispatch(ic.getJNAClient()));
}
@Override
public DebugClient createClient() {
PointerByReference ppClient = new PointerByReference();
COMUtils.checkRC(jnaClient.CreateClient(ppClient));
return DebugClientInternal.tryPreferredInterfaces(jnaClient::QueryInterface);
}
@Override
public void setInputCallbacks(DebugInputCallbacks cb) {
ListenerIDebugInputCallbacks listener = null;
if (cb != null) {
WrapCallbackIDebugInputCallbacks callback =
new WrapCallbackIDebugInputCallbacks(this, cb);
listener = new ListenerIDebugInputCallbacks(callback);
callback.setListener(listener);
}
COMUtils.checkRC(jnaClient.SetInputCallbacks(listener));
listenerInput = listener;
}
@Override
public void setOutputCallbacks(DebugOutputCallbacks cb) {
ListenerIDebugOutputCallbacks listener = null;
if (cb != null) {
WrapCallbackIDebugOutputCallbacks callback = new WrapCallbackIDebugOutputCallbacks(cb);
listener = new ListenerIDebugOutputCallbacks(callback);
callback.setListener(listener);
}
COMUtils.checkRC(jnaClient.SetOutputCallbacks(listener));
listenerOutput = listener;
}
@Override
public void setEventCallbacks(DebugEventCallbacks cb) {
ListenerIDebugEventCallbacks listener = null;
if (cb != null) {
WrapCallbackIDebugEventCallbacks callback =
new WrapCallbackIDebugEventCallbacks(this, cb);
listener = new ListenerIDebugEventCallbacks(callback);
callback.setListener(listener);
}
COMUtils.checkRC(jnaClient.SetEventCallbacks(listener));
listenerEvent = listener;
}
@Override
public void terminateCurrentProcess() {
throw new UnsupportedOperationException("Not implemented by this interface");
}
@Override
public void detachCurrentProcess() {
throw new UnsupportedOperationException("Not implemented by this interface");
}
@Override
public void abandonCurrentProcess() {
throw new UnsupportedOperationException("Not implemented by this interface");
}
@Override
public void waitForProcessServerEnd(int timeout) {
throw new UnsupportedOperationException("Not implemented by this interface");
}
@Override
public void endSession(DebugEndSessionFlags flags) {
COMUtils.checkRC(jnaClient.EndSession(new ULONG(flags.getValue())));
}
@Override
public void connectSession(int flags) {
COMUtils.checkRC(jnaClient.ConnectSession(new ULONG(flags), new ULONG(10000)));
}
@Override
public void openDumpFileWide(String fileName) {
throw new UnsupportedOperationException("Not implemented by this interface");
}
@Override
public void attachKernel(long flags, String options) {
throw new UnsupportedOperationException("Not implemented by this interface");
}
}

View File

@ -0,0 +1,50 @@
/* ###
* 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.impl.dbgeng.client;
import com.sun.jna.platform.win32.WinDef.ULONG;
import com.sun.jna.platform.win32.COM.COMUtils;
import agent.dbgeng.jna.dbgeng.client.IDebugClient2;
public class DebugClientImpl2 extends DebugClientImpl1 {
private final IDebugClient2 jnaClient;
public DebugClientImpl2(IDebugClient2 jnaClient) {
super(jnaClient);
this.jnaClient = jnaClient;
}
@Override
public void terminateCurrentProcess() {
COMUtils.checkRC(jnaClient.TerminateCurrentProcess());
}
@Override
public void detachCurrentProcess() {
COMUtils.checkRC(jnaClient.DetachCurrentProcess());
}
@Override
public void abandonCurrentProcess() {
COMUtils.checkRC(jnaClient.AbandonCurrentProcess());
}
@Override
public void waitForProcessServerEnd(int timeout) {
COMUtils.checkRC(jnaClient.WaitForProcessServerEnd(new ULONG(timeout)));
}
}

View File

@ -0,0 +1,68 @@
/* ###
* 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.impl.dbgeng.client;
import com.sun.jna.Native;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.WinDef.*;
import agent.dbgeng.dbgeng.DebugRunningProcess;
import agent.dbgeng.dbgeng.DebugServerId;
import agent.dbgeng.dbgeng.DebugRunningProcess.Description.ProcessDescriptionFlags;
import agent.dbgeng.jna.dbgeng.client.IDebugClient3;
import com.sun.jna.platform.win32.COM.COMUtils;
import ghidra.comm.util.BitmaskSet;
public class DebugClientImpl3 extends DebugClientImpl2 {
private final IDebugClient3 jnaClient;
public DebugClientImpl3(IDebugClient3 jnaClient) {
super(jnaClient);
this.jnaClient = jnaClient;
}
@Override
public void createProcess(DebugServerId si, String commandLine,
BitmaskSet<DebugCreateFlags> createFlags) {
ULONGLONG ullServer = new ULONGLONG(si.id);
ULONG ulFlags = new ULONG(createFlags.getBitmask());
COMUtils.checkRC(jnaClient.CreateProcessWide(ullServer, new WString(commandLine), ulFlags));
}
@Override
public DebugRunningProcess.Description getProcessDescription(DebugServerId si, int systemId,
BitmaskSet<ProcessDescriptionFlags> flags) {
ULONGLONG server = new ULONGLONG(si.id);
ULONG id = new ULONG(systemId);
ULONG f = new ULONG(flags.getBitmask());
ULONGByReference actualExeNameSize = new ULONGByReference();
ULONGByReference actualDescriptionSize = new ULONGByReference();
COMUtils.checkRC(jnaClient.GetRunningProcessDescriptionWide(server, id, f, null,
new ULONG(0), actualExeNameSize, null, new ULONG(0), actualDescriptionSize));
char[] exeName = new char[actualExeNameSize.getValue().intValue()];
char[] description = new char[actualDescriptionSize.getValue().intValue()];
COMUtils.checkRC(jnaClient.GetRunningProcessDescriptionWide(server, id, f, exeName,
actualExeNameSize.getValue(), null, description, actualDescriptionSize.getValue(),
null));
return new DebugRunningProcess.Description(systemId, Native.toString(exeName),
Native.toString(description));
}
}

View File

@ -0,0 +1,39 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.client;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.WinDef.ULONGLONG;
import agent.dbgeng.jna.dbgeng.client.IDebugClient4;
import com.sun.jna.platform.win32.COM.COMUtils;
public class DebugClientImpl4 extends DebugClientImpl3 {
@SuppressWarnings("unused")
private final IDebugClient4 jnaClient;
public DebugClientImpl4(IDebugClient4 jnaClient) {
super(jnaClient);
this.jnaClient = jnaClient;
}
@Override
public void openDumpFileWide(String fileName) {
ULONGLONG ullFileHandle = new ULONGLONG(0);
COMUtils.checkRC(jnaClient.OpenDumpFileWide(new WString(fileName), ullFileHandle));
}
}

View File

@ -0,0 +1,82 @@
/* ###
* 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.impl.dbgeng.client;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.WinDef.ULONG;
import com.sun.jna.platform.win32.WinDef.ULONGLONGByReference;
import com.sun.jna.platform.win32.COM.COMUtils;
import agent.dbgeng.dbgeng.*;
import agent.dbgeng.impl.dbgeng.event.WrapCallbackIDebugEventCallbacksWide;
import agent.dbgeng.impl.dbgeng.io.WrapCallbackIDebugOutputCallbacksWide;
import agent.dbgeng.jna.dbgeng.client.IDebugClient5;
import agent.dbgeng.jna.dbgeng.event.ListenerIDebugEventCallbacksWide;
import agent.dbgeng.jna.dbgeng.io.ListenerIDebugOutputCallbacksWide;
public class DebugClientImpl5 extends DebugClientImpl4 {
private final IDebugClient5 jnaClient;
public DebugClientImpl5(IDebugClient5 jnaClient) {
super(jnaClient);
this.jnaClient = jnaClient;
}
@Override
public void attachKernel(long flags, String options) {
ULONG connectFlags = new ULONG(flags);
COMUtils.checkRC(jnaClient.AttachKernelWide(connectFlags, new WString(options)));
}
@Override
public void startProcessServer(String options) {
COMUtils.checkRC(jnaClient.StartProcessServerWide(
new ULONG(DebugClass.USER_WINDOWS.ordinal()), new WString(options), null));
}
@Override
public DebugServerId connectProcessServer(String options) {
ULONGLONGByReference pulServer = new ULONGLONGByReference();
COMUtils.checkRC(jnaClient.ConnectProcessServerWide(new WString(options), pulServer));
return new DebugServerId(pulServer.getValue().longValue());
}
@Override
public void setOutputCallbacks(DebugOutputCallbacks cb) {
ListenerIDebugOutputCallbacksWide listener = null;
if (cb != null) {
WrapCallbackIDebugOutputCallbacksWide callback =
new WrapCallbackIDebugOutputCallbacksWide(cb);
listener = new ListenerIDebugOutputCallbacksWide(callback);
callback.setListener(listener);
}
COMUtils.checkRC(jnaClient.SetOutputCallbacksWide(listener));
listenerOutput = listener;
}
@Override
public void setEventCallbacks(DebugEventCallbacks cb) {
ListenerIDebugEventCallbacksWide listener = null;
if (cb != null) {
WrapCallbackIDebugEventCallbacksWide callback =
new WrapCallbackIDebugEventCallbacksWide(this, cb);
listener = new ListenerIDebugEventCallbacksWide(callback);
callback.setListener(listener);
}
COMUtils.checkRC(jnaClient.SetEventCallbacksWide(listener));
listenerEvent = listener;
}
}

View File

@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.client;
import agent.dbgeng.dbgeng.DebugEventCallbacks;
import agent.dbgeng.jna.dbgeng.client.IDebugClient6;
public class DebugClientImpl6 extends DebugClientImpl5 {
@SuppressWarnings("unused")
private final IDebugClient6 jnaClient;
public DebugClientImpl6(IDebugClient6 jnaClient) {
super(jnaClient);
this.jnaClient = jnaClient;
}
@Override
public void setEventCallbacks(DebugEventCallbacks cb) {
// TODO: Use Context variant. Will require expanding the generic interface.
super.setEventCallbacks(cb);
}
}

View File

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.client;
import agent.dbgeng.jna.dbgeng.client.IDebugClient7;
public class DebugClientImpl7 extends DebugClientImpl6 {
@SuppressWarnings("unused")
private final IDebugClient7 jnaClient;
public DebugClientImpl7(IDebugClient7 jnaClient) {
super(jnaClient);
this.jnaClient = jnaClient;
}
}

View File

@ -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 agent.dbgeng.impl.dbgeng.client;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Guid.REFIID;
import agent.dbgeng.dbgeng.DebugClient;
import agent.dbgeng.impl.dbgeng.DbgEngUtil;
import agent.dbgeng.impl.dbgeng.DbgEngUtil.InterfaceSupplier;
import agent.dbgeng.impl.dbgeng.control.DebugControlInternal;
import agent.dbgeng.jna.dbgeng.client.*;
import ghidra.util.datastruct.WeakValueHashMap;
public interface DebugClientInternal extends DebugClient {
Map<Pointer, DebugClientInternal> CACHE = new WeakValueHashMap<>();
enum DebugClass {
UNINITIALIZED, //
KERNEL, //
USER_WINDOWS, //
IMAGE_FILE, //
;
}
static DebugClientInternal instanceFor(WrapIDebugClient client) {
return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl1::new);
}
static DebugClientInternal instanceFor(WrapIDebugClient2 client) {
return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl2::new);
}
static DebugClientInternal instanceFor(WrapIDebugClient3 client) {
return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl3::new);
}
static DebugClientInternal instanceFor(WrapIDebugClient4 client) {
return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl4::new);
}
static DebugClientInternal instanceFor(WrapIDebugClient5 client) {
return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl5::new);
}
static DebugClientInternal instanceFor(WrapIDebugClient6 client) {
return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl6::new);
}
static DebugClientInternal instanceFor(WrapIDebugClient7 client) {
return DbgEngUtil.lazyWeakCache(CACHE, client, DebugClientImpl7::new);
}
ImmutableMap.Builder<REFIID, Class<? extends WrapIDebugClient>> PREFERRED_CLIENT_IIDS_BUILDER =
ImmutableMap.builder();
Map<REFIID, Class<? extends WrapIDebugClient>> PREFERRED_CLIENT_IIDS =
PREFERRED_CLIENT_IIDS_BUILDER //
.put(new REFIID(IDebugClient7.IID_IDEBUG_CLIENT7), WrapIDebugClient7.class) //
.put(new REFIID(IDebugClient6.IID_IDEBUG_CLIENT6), WrapIDebugClient6.class) //
.put(new REFIID(IDebugClient5.IID_IDEBUG_CLIENT5), WrapIDebugClient5.class) //
.put(new REFIID(IDebugClient4.IID_IDEBUG_CLIENT4), WrapIDebugClient4.class) //
.put(new REFIID(IDebugClient3.IID_IDEBUG_CLIENT3), WrapIDebugClient3.class) //
.put(new REFIID(IDebugClient2.IID_IDEBUG_CLIENT2), WrapIDebugClient2.class) //
.put(new REFIID(IDebugClient.IID_IDEBUG_CLIENT), WrapIDebugClient.class) //
.build();
static DebugClientInternal tryPreferredInterfaces(InterfaceSupplier supplier) {
return DbgEngUtil.tryPreferredInterfaces(DebugClientInternal.class, PREFERRED_CLIENT_IIDS,
supplier);
}
IDebugClient getJNAClient();
DebugControlInternal getControlInternal();
@Override
default void endSessionReentrant() {
endSession(DebugEndSessionFlags.DEBUG_END_REENTRANT);
}
}

View File

@ -0,0 +1,300 @@
/* ###
* 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.impl.dbgeng.control;
import javax.help.UnsupportedOperationException;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.WinDef.*;
import com.sun.jna.platform.win32.WinError;
import com.sun.jna.platform.win32.WinNT.HRESULT;
import com.sun.jna.platform.win32.COM.COMUtils;
import com.sun.jna.ptr.PointerByReference;
import agent.dbgeng.dbgeng.*;
import agent.dbgeng.dbgeng.DbgEng.OpaqueCleanable;
import agent.dbgeng.dbgeng.DebugBreakpoint.BreakType;
import agent.dbgeng.dbgeng.DebugClient.DebugStatus;
import agent.dbgeng.dbgeng.DebugValue.DebugValueType;
import agent.dbgeng.impl.dbgeng.DbgEngUtil;
import agent.dbgeng.impl.dbgeng.breakpoint.DebugBreakpointInternal;
import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_STACK_FRAME;
import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_VALUE;
import agent.dbgeng.jna.dbgeng.breakpoint.IDebugBreakpoint;
import agent.dbgeng.jna.dbgeng.breakpoint.WrapIDebugBreakpoint;
import agent.dbgeng.jna.dbgeng.control.IDebugControl;
import ghidra.comm.util.BitmaskSet;
public class DebugControlImpl1 implements DebugControlInternal {
@SuppressWarnings("unused")
private final OpaqueCleanable cleanable;
private final IDebugControl jnaControl;
public DebugControlImpl1(IDebugControl jnaControl) {
this.cleanable = DbgEng.releaseWhenPhantom(this, jnaControl);
this.jnaControl = jnaControl;
}
@Override
public boolean getInterrupt() {
HRESULT interrupt = jnaControl.GetInterrupt();
if (interrupt.equals(WinError.S_OK)) {
return true;
}
if (interrupt.equals(WinError.S_FALSE)) {
return false;
}
COMUtils.checkRC(interrupt);
throw new AssertionError("Shouldn't get here");
}
@Override
public void setInterrupt(DebugInterrupt interrupt) {
ULONG flags = new ULONG(interrupt.ordinal());
COMUtils.checkRC(jnaControl.SetInterrupt(flags));
}
@Override
public int getInterruptTimeout() {
ULONGByReference pulSeconds = new ULONGByReference();
COMUtils.checkRC(jnaControl.GetInterruptTimeout(pulSeconds));
return pulSeconds.getValue().intValue();
}
@Override
public void setInterruptTimeout(int seconds) {
ULONG ulSeconds = new ULONG(seconds);
COMUtils.checkRC(jnaControl.SetInterruptTimeout(ulSeconds));
}
@Override
public void print(BitmaskSet<DebugOutputLevel> levels, String message) {
ULONG mask = new ULONG(levels.getBitmask());
COMUtils.checkRC(jnaControl.Output(mask, "%s", message));
}
@Override
public void println(BitmaskSet<DebugOutputLevel> levels, String message) {
ULONG mask = new ULONG(levels.getBitmask());
COMUtils.checkRC(jnaControl.Output(mask, "%s", message + "\r\n"));
}
@Override
public void prompt(BitmaskSet<DebugOutputControl> ctl, String message) {
ULONG ctlMask = new ULONG(ctl.getBitmask());
COMUtils.checkRC(jnaControl.OutputPrompt(ctlMask, "%s", message));
}
@Override
public String getPromptText() {
ULONGByReference pulTextSize = new ULONGByReference();
COMUtils.checkRC(jnaControl.GetPromptText(null, new ULONG(0), pulTextSize));
byte[] buffer = new byte[pulTextSize.getValue().intValue()];
COMUtils.checkRC(jnaControl.GetPromptText(buffer, pulTextSize.getValue(), null));
return Native.toString(buffer);
}
protected DEBUG_VALUE doEval(DebugValueType type, String expression) {
DEBUG_VALUE.ByReference value = new DEBUG_VALUE.ByReference();
ULONGByReference pulRemainder = new ULONGByReference();
COMUtils.checkRC(
jnaControl.Evaluate(expression, new ULONG(type.ordinal()), value, pulRemainder));
int remainder = pulRemainder.getValue().intValue();
if (remainder != expression.length()) {
throw new RuntimeException("Failed to parse: " + expression.substring(remainder));
}
return value;
}
@Override
public <T extends DebugValue> T evaluate(Class<T> desiredType, String expression) {
DebugValueType type = DebugValueType.getDebugValueTypeForClass(desiredType);
return doEval(type, expression).convertTo(desiredType);
}
@Override
public void execute(BitmaskSet<DebugOutputControl> ctl, String cmd,
BitmaskSet<DebugExecute> flags) {
ULONG ctlMask = new ULONG(ctl.getBitmask());
ULONG flagMask = new ULONG(flags.getBitmask());
COMUtils.checkRC(jnaControl.Execute(ctlMask, cmd, flagMask));
}
@Override
public void returnInput(String input) {
COMUtils.checkRC(jnaControl.ReturnInput(input));
}
@Override
public DebugStatus getExecutionStatus() {
ULONGByReference pulStatus = new ULONGByReference();
COMUtils.checkRC(jnaControl.GetExecutionStatus(pulStatus));
return DebugStatus.values()[pulStatus.getValue().intValue()];
}
@Override
public void setExecutionStatus(DebugStatus status) {
ULONG ulStatus = new ULONG(status.ordinal());
HRESULT hr = jnaControl.SetExecutionStatus(ulStatus);
if (!hr.equals(COMUtilsExtra.E_ACCESS_DENIED)) {
COMUtils.checkRC(hr);
}
}
public DebugBreakpoint doAddBreakpoint(BreakType type, ULONG ulDesiredId) {
ULONG ulType = new ULONG(type.ordinal());
PointerByReference ppBp = new PointerByReference();
COMUtils.checkRC(jnaControl.AddBreakpoint(ulType, ulDesiredId, ppBp));
IDebugBreakpoint Bp = new WrapIDebugBreakpoint(ppBp.getValue());
DebugBreakpoint bpt =
DebugBreakpointInternal.tryPreferredInterfaces(this, Bp::QueryInterface);
// AddRef or no? Probably not.
return bpt;
}
@Override
public int getNumberBreakpoints() {
ULONGByReference ulNumber = new ULONGByReference();
COMUtils.checkRC(jnaControl.GetNumberBreakpoints(ulNumber));
return ulNumber.getValue().intValue();
}
@Override
public DebugBreakpoint getBreakpointByIndex(int index) {
ULONG ulIndex = new ULONG(index);
PointerByReference ppBp = new PointerByReference();
COMUtils.checkRC(jnaControl.GetBreakpointByIndex(ulIndex, ppBp));
IDebugBreakpoint Bp = new WrapIDebugBreakpoint(ppBp.getValue());
DebugBreakpoint bpt =
DebugBreakpointInternal.tryPreferredInterfaces(this, Bp::QueryInterface);
// NOTE: Do not AddRef. dbgeng manages lifecycle
return bpt;
}
@Override
public DebugBreakpoint getBreakpointById(int id) {
ULONG ulId = new ULONG(id);
PointerByReference ppBp = new PointerByReference();
HRESULT hr = jnaControl.GetBreakpointById(ulId, ppBp);
if (hr.equals(COMUtilsExtra.E_NOINTERFACE)) {
return null;
}
if (hr.equals(COMUtilsExtra.E_UNEXPECTED)) {
return null;
}
COMUtils.checkRC(hr);
IDebugBreakpoint Bp = new WrapIDebugBreakpoint(ppBp.getValue());
DebugBreakpoint bpt =
DebugBreakpointInternal.tryPreferredInterfaces(this, Bp::QueryInterface);
// NOTE: Do not AddRef. dbgeng manages lifecycle
return bpt;
}
@Override
public DebugBreakpoint addBreakpoint(BreakType type, int desiredId) {
return doAddBreakpoint(type, new ULONG(desiredId));
}
@Override
public DebugBreakpoint addBreakpoint(BreakType type) {
return doAddBreakpoint(type, DbgEngUtil.DEBUG_ANY_ID);
}
@Override
public DebugBreakpoint addBreakpoint2(BreakType type) {
throw new UnsupportedOperationException();
}
@Override
public DebugBreakpoint addBreakpoint2(BreakType type, int desiredId) {
throw new UnsupportedOperationException();
}
@Override
public void removeBreakpoint(IDebugBreakpoint comBpt) {
COMUtils.checkRC(jnaControl.RemoveBreakpoint(comBpt));
}
@Override
public void waitForEvent(int timeout) {
COMUtils.checkRC(jnaControl.WaitForEvent(new ULONG(0), new ULONG(timeout)));
}
@Override
public DebugEventInformation getLastEventInformation() {
ULONGByReference pulType = new ULONGByReference();
ULONGByReference pulProcessId = new ULONGByReference();
ULONGByReference pulThreadId = new ULONGByReference();
//PointerByReference pExtraInformation = new PointerByReference();
ULONG ulExtraInformationSize = new ULONG(0);
ULONGByReference pulExtraInformationUsed = new ULONGByReference();
//byte[] pstrDescription = new byte[0];
ULONG ulDescriptionSize = new ULONG(0);
ULONGByReference pulDescriptionUsed = new ULONGByReference();
COMUtils.checkRC(jnaControl.GetLastEventInformation(pulType, pulProcessId, pulThreadId,
null, ulExtraInformationSize, pulExtraInformationUsed, null, ulDescriptionSize,
pulDescriptionUsed));
return new DebugEventInformation(pulType.getValue().intValue(),
pulProcessId.getValue().intValue(), pulThreadId.getValue().intValue());
}
@Override
public DebugStackInformation getStackTrace(long frameOffset, long stackOffset,
long instructionOffset) {
ULONGLONG ullFrameOffset = new ULONGLONG(frameOffset);
ULONGLONG ullStackOffset = new ULONGLONG(stackOffset);
ULONGLONG ullInstructionOffset = new ULONGLONG(instructionOffset);
ULONG ulFrameSize = new ULONG(100);
DEBUG_STACK_FRAME[] pParams = new DEBUG_STACK_FRAME[ulFrameSize.intValue()];
ULONGByReference pulFramesFilled = new ULONGByReference();
COMUtils.checkRC(jnaControl.GetStackTrace(ullFrameOffset, ullStackOffset,
ullInstructionOffset, pParams, ulFrameSize, pulFramesFilled));
return new DebugStackInformation(pulFramesFilled.getValue().intValue(), pParams);
}
@Override
public int getActualProcessorType() {
ULONGByReference ulType = new ULONGByReference();
HRESULT hr = jnaControl.GetActualProcessorType(ulType);
if (hr.equals(COMUtilsExtra.E_UNEXPECTED)) {
return -1;
}
COMUtils.checkRC(hr);
return ulType.getValue().intValue();
}
@Override
public int getEffectiveProcessorType() {
ULONGByReference ulType = new ULONGByReference();
COMUtils.checkRC(jnaControl.GetEffectiveProcessorType(ulType));
return ulType.getValue().intValue();
}
@Override
public int getExecutingProcessorType() {
ULONGByReference ulType = new ULONGByReference();
COMUtils.checkRC(jnaControl.GetExecutingProcessorType(ulType));
return ulType.getValue().intValue();
}
@Override
public int getDebuggeeType() {
ULONGByReference ulClass = new ULONGByReference();
ULONGByReference ulQualifier = new ULONGByReference();
COMUtils.checkRC(jnaControl.GetDebuggeeType(ulClass, ulQualifier));
return ulClass.getValue().intValue();
}
}

View File

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.control;
import agent.dbgeng.jna.dbgeng.control.IDebugControl2;
public class DebugControlImpl2 extends DebugControlImpl1 {
@SuppressWarnings("unused")
private final IDebugControl2 jnaControl;
public DebugControlImpl2(IDebugControl2 jnaControl) {
super(jnaControl);
this.jnaControl = jnaControl;
}
}

View File

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.control;
import agent.dbgeng.jna.dbgeng.control.IDebugControl3;
public class DebugControlImpl3 extends DebugControlImpl2 {
@SuppressWarnings("unused")
private final IDebugControl3 jnaControl;
public DebugControlImpl3(IDebugControl3 jnaControl) {
super(jnaControl);
this.jnaControl = jnaControl;
}
}

View File

@ -0,0 +1,122 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.control;
import com.sun.jna.Native;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.WinDef.ULONG;
import com.sun.jna.platform.win32.WinDef.ULONGByReference;
import com.sun.jna.platform.win32.COM.COMUtils;
import com.sun.jna.ptr.PointerByReference;
import agent.dbgeng.dbgeng.DebugBreakpoint;
import agent.dbgeng.dbgeng.DebugBreakpoint.BreakType;
import agent.dbgeng.dbgeng.DebugValue.DebugValueType;
import agent.dbgeng.impl.dbgeng.DbgEngUtil;
import agent.dbgeng.impl.dbgeng.breakpoint.DebugBreakpointInternal;
import agent.dbgeng.jna.dbgeng.DbgEngNative.DEBUG_VALUE;
import agent.dbgeng.jna.dbgeng.breakpoint.IDebugBreakpoint;
import agent.dbgeng.jna.dbgeng.breakpoint.WrapIDebugBreakpoint;
import agent.dbgeng.jna.dbgeng.control.IDebugControl4;
import ghidra.comm.util.BitmaskSet;
public class DebugControlImpl4 extends DebugControlImpl3 {
private final IDebugControl4 jnaControl;
public DebugControlImpl4(IDebugControl4 jnaControl) {
super(jnaControl);
this.jnaControl = jnaControl;
}
@Override
public void print(BitmaskSet<DebugOutputLevel> levels, String message) {
ULONG mask = new ULONG(levels.getBitmask());
COMUtils.checkRC(jnaControl.OutputWide(mask, new WString("%s"), new WString(message)));
}
@Override
public void println(BitmaskSet<DebugOutputLevel> levels, String message) {
ULONG mask = new ULONG(levels.getBitmask());
COMUtils
.checkRC(
jnaControl.OutputWide(mask, new WString("%s"), new WString(message + "\r\n")));
}
@Override
public void prompt(BitmaskSet<DebugOutputControl> ctl, String message) {
ULONG ctlMask = new ULONG(ctl.getBitmask());
COMUtils
.checkRC(
jnaControl.OutputPromptWide(ctlMask, new WString("%s"), new WString(message)));
}
@Override
public String getPromptText() {
ULONGByReference pulTextSize = new ULONGByReference();
COMUtils.checkRC(jnaControl.GetPromptTextWide(null, new ULONG(0), pulTextSize));
char[] buffer = new char[pulTextSize.getValue().intValue()];
COMUtils.checkRC(jnaControl.GetPromptTextWide(buffer, pulTextSize.getValue(), null));
return Native.toString(buffer);
}
@Override
protected DEBUG_VALUE doEval(DebugValueType type, String expression) {
DEBUG_VALUE.ByReference value = new DEBUG_VALUE.ByReference();
ULONGByReference pulRemainder = new ULONGByReference();
COMUtils.checkRC(jnaControl.EvaluateWide(new WString(expression), new ULONG(type.ordinal()),
value, pulRemainder));
int remainder = pulRemainder.getValue().intValue();
if (remainder != expression.length()) {
throw new RuntimeException("Failed to parse: " + expression.substring(remainder));
}
return value;
}
@Override
public void execute(BitmaskSet<DebugOutputControl> ctl, String cmd,
BitmaskSet<DebugExecute> flags) {
ULONG ctlMask = new ULONG(ctl.getBitmask());
ULONG flagMask = new ULONG(flags.getBitmask());
COMUtils.checkRC(jnaControl.ExecuteWide(ctlMask, new WString(cmd), flagMask));
}
@Override
public void returnInput(String input) {
COMUtils.checkRC(jnaControl.ReturnInputWide(new WString(input)));
}
public DebugBreakpoint doAddBreakpoint2(BreakType type, ULONG ulDesiredId) {
ULONG ulType = new ULONG(type.ordinal());
PointerByReference ppBp = new PointerByReference();
COMUtils.checkRC(jnaControl.AddBreakpoint2(ulType, ulDesiredId, ppBp));
IDebugBreakpoint Bp = new WrapIDebugBreakpoint(ppBp.getValue());
DebugBreakpoint bpt =
DebugBreakpointInternal.tryPreferredInterfaces(this, Bp::QueryInterface);
// AddRef or no? Probably not.
return bpt;
}
@Override
public DebugBreakpoint addBreakpoint2(BreakType type, int desiredId) {
return doAddBreakpoint2(type, new ULONG(desiredId));
}
@Override
public DebugBreakpoint addBreakpoint2(BreakType type) {
return doAddBreakpoint2(type, DbgEngUtil.DEBUG_ANY_ID);
}
}

View File

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.control;
import agent.dbgeng.jna.dbgeng.control.IDebugControl5;
public class DebugControlImpl5 extends DebugControlImpl4 {
@SuppressWarnings("unused")
private final IDebugControl5 jnaControl;
public DebugControlImpl5(IDebugControl5 jnaControl) {
super(jnaControl);
this.jnaControl = jnaControl;
}
}

View File

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.control;
import agent.dbgeng.jna.dbgeng.control.IDebugControl6;
public class DebugControlImpl6 extends DebugControlImpl5 {
@SuppressWarnings("unused")
private final IDebugControl6 jnaControl;
public DebugControlImpl6(IDebugControl6 jnaControl) {
super(jnaControl);
this.jnaControl = jnaControl;
}
}

View File

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.control;
import agent.dbgeng.jna.dbgeng.control.IDebugControl7;
public class DebugControlImpl7 extends DebugControlImpl6 {
@SuppressWarnings("unused")
private final IDebugControl7 jnaControl;
public DebugControlImpl7(IDebugControl7 jnaControl) {
super(jnaControl);
this.jnaControl = jnaControl;
}
}

View File

@ -0,0 +1,81 @@
/* ###
* 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.impl.dbgeng.control;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Guid.REFIID;
import agent.dbgeng.dbgeng.DebugControl;
import agent.dbgeng.impl.dbgeng.DbgEngUtil;
import agent.dbgeng.impl.dbgeng.DbgEngUtil.InterfaceSupplier;
import agent.dbgeng.jna.dbgeng.breakpoint.IDebugBreakpoint;
import agent.dbgeng.jna.dbgeng.control.*;
import ghidra.util.datastruct.WeakValueHashMap;
public interface DebugControlInternal extends DebugControl {
Map<Pointer, DebugControlInternal> CACHE = new WeakValueHashMap<>();
static DebugControlInternal instanceFor(WrapIDebugControl control) {
return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl1::new);
}
static DebugControlInternal instanceFor(WrapIDebugControl2 control) {
return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl2::new);
}
static DebugControlInternal instanceFor(WrapIDebugControl3 control) {
return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl3::new);
}
static DebugControlInternal instanceFor(WrapIDebugControl4 control) {
return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl4::new);
}
static DebugControlInternal instanceFor(WrapIDebugControl5 control) {
return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl5::new);
}
static DebugControlInternal instanceFor(WrapIDebugControl6 control) {
return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl6::new);
}
static DebugControlInternal instanceFor(WrapIDebugControl7 control) {
return DbgEngUtil.lazyWeakCache(CACHE, control, DebugControlImpl7::new);
}
ImmutableMap.Builder<REFIID, Class<? extends WrapIDebugControl>> PREFERRED_CONTROL_IIDS_BUILDER =
ImmutableMap.builder();
Map<REFIID, Class<? extends WrapIDebugControl>> PREFERRED_CONTROL_IIDS =
PREFERRED_CONTROL_IIDS_BUILDER //
.put(new REFIID(IDebugControl7.IID_IDEBUG_CONTROL7), WrapIDebugControl7.class) //
.put(new REFIID(IDebugControl6.IID_IDEBUG_CONTROL6), WrapIDebugControl6.class) //
.put(new REFIID(IDebugControl5.IID_IDEBUG_CONTROL5), WrapIDebugControl5.class) //
.put(new REFIID(IDebugControl4.IID_IDEBUG_CONTROL4), WrapIDebugControl4.class) //
.put(new REFIID(IDebugControl3.IID_IDEBUG_CONTROL3), WrapIDebugControl3.class) //
.put(new REFIID(IDebugControl2.IID_IDEBUG_CONTROL2), WrapIDebugControl2.class) //
.put(new REFIID(IDebugControl.IID_IDEBUG_CONTROL), WrapIDebugControl.class) //
.build();
static DebugControlInternal tryPreferredInterfaces(InterfaceSupplier supplier) {
return DbgEngUtil.tryPreferredInterfaces(DebugControlInternal.class, PREFERRED_CONTROL_IIDS,
supplier);
}
void removeBreakpoint(IDebugBreakpoint comBpt);
}

View File

@ -0,0 +1,271 @@
/* ###
* 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.impl.dbgeng.dataspaces;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import javax.help.UnsupportedOperationException;
import com.sun.jna.platform.win32.WinDef.*;
import com.sun.jna.platform.win32.WinNT.HRESULT;
import com.sun.jna.platform.win32.COM.COMUtils;
import agent.dbgeng.dbgeng.COMUtilsExtra;
import agent.dbgeng.dbgeng.DbgEng;
import agent.dbgeng.dbgeng.DbgEng.OpaqueCleanable;
import agent.dbgeng.jna.dbgeng.dataspaces.IDebugDataSpaces;
public class DebugDataSpacesImpl1 implements DebugDataSpacesInternal {
@SuppressWarnings("unused")
private final OpaqueCleanable cleanble;
private final IDebugDataSpaces jnaData;
public DebugDataSpacesImpl1(IDebugDataSpaces jnaData) {
this.cleanble = DbgEng.releaseWhenPhantom(this, jnaData);
this.jnaData = jnaData;
}
@Override
public DebugMemoryBasicInformation queryVirtual(long offset) {
throw new UnsupportedOperationException("Not implemented in this interface");
}
@Override
public int readVirtual(long offset, ByteBuffer into, int len) {
if (len > into.remaining()) {
throw new BufferOverflowException();
}
ULONGLONG ullOffset = new ULONGLONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesRead = new ULONGByReference();
HRESULT hr = jnaData.ReadVirtual(ullOffset, into, ulLen, pulBytesRead);
if (hr.equals(COMUtilsExtra.E_CANNOT_READ)) {
return 0;
}
COMUtils.checkRC(hr);
int read = pulBytesRead.getValue().intValue();
into.position(read + into.position());
return read;
}
@Override
public int writeVirtual(long offset, ByteBuffer from, int len) {
if (len > from.remaining()) {
throw new BufferOverflowException();
}
ULONGLONG ullOffset = new ULONGLONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesWritten = new ULONGByReference();
COMUtils.checkRC(jnaData.WriteVirtual(ullOffset, from, ulLen, pulBytesWritten));
int written = pulBytesWritten.getValue().intValue();
from.position(written + from.position());
return written;
}
@Override
public int readVirtualUncached(long offset, ByteBuffer into, int len) {
if (len > into.remaining()) {
throw new BufferOverflowException();
}
ULONGLONG ullOffset = new ULONGLONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesRead = new ULONGByReference();
COMUtils.checkRC(jnaData.ReadVirtualUncached(ullOffset, into, ulLen, pulBytesRead));
int read = pulBytesRead.getValue().intValue();
into.position(read + into.position());
return read;
}
@Override
public int writeVirtualUncached(long offset, ByteBuffer from, int len) {
if (len > from.remaining()) {
throw new BufferOverflowException();
}
ULONGLONG ullOffset = new ULONGLONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesWritten = new ULONGByReference();
COMUtils.checkRC(jnaData.WriteVirtualUncached(ullOffset, from, ulLen, pulBytesWritten));
int written = pulBytesWritten.getValue().intValue();
from.position(written + from.position());
return written;
}
@Override
public int readPhysical(long offset, ByteBuffer into, int len) {
if (len > into.remaining()) {
throw new BufferOverflowException();
}
ULONGLONG ullOffset = new ULONGLONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesRead = new ULONGByReference();
COMUtils.checkRC(jnaData.ReadPhysical(ullOffset, into, ulLen, pulBytesRead));
int read = pulBytesRead.getValue().intValue();
into.position(read + into.position());
return read;
}
@Override
public int writePhysical(long offset, ByteBuffer from, int len) {
if (len > from.remaining()) {
throw new BufferOverflowException();
}
ULONGLONG ullOffset = new ULONGLONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesWritten = new ULONGByReference();
COMUtils.checkRC(jnaData.WritePhysical(ullOffset, from, ulLen, pulBytesWritten));
int written = pulBytesWritten.getValue().intValue();
from.position(written + from.position());
return written;
}
@Override
public int readControl(int processor, long offset, ByteBuffer into, int len) {
if (len > into.remaining()) {
throw new BufferOverflowException();
}
ULONG ulProcessor = new ULONG(processor);
ULONGLONG ullOffset = new ULONGLONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesRead = new ULONGByReference();
COMUtils.checkRC(jnaData.ReadControl(ulProcessor, ullOffset, into, ulLen, pulBytesRead));
int read = pulBytesRead.getValue().intValue();
into.position(read + into.position());
return read;
}
@Override
public int writeControl(int processor, long offset, ByteBuffer from, int len) {
if (len > from.remaining()) {
throw new BufferOverflowException();
}
ULONG ulProcessor = new ULONG(processor);
ULONGLONG ullOffset = new ULONGLONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesWritten = new ULONGByReference();
COMUtils.checkRC(
jnaData.WriteControl(ulProcessor, ullOffset, from, ulLen, pulBytesWritten));
int written = pulBytesWritten.getValue().intValue();
from.position(written + from.position());
return written;
}
@Override
public int readBusData(int busDataType, int busNumber, int slotNumber, long offset,
ByteBuffer into, int len) {
if (len > into.remaining()) {
throw new BufferOverflowException();
}
ULONG ulBusDataType = new ULONG(busDataType);
ULONG ulBusNumber = new ULONG(busNumber);
ULONG ulSlotNumber = new ULONG(slotNumber);
ULONGLONG ullOffset = new ULONGLONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesRead = new ULONGByReference();
COMUtils.checkRC(jnaData.ReadBusData(ulBusDataType, ulBusNumber, ulSlotNumber, ullOffset,
into, ulLen, pulBytesRead));
int read = pulBytesRead.getValue().intValue();
into.position(read + into.position());
return read;
}
@Override
public int writeBusData(int busDataType, int busNumber, int slotNumber, long offset,
ByteBuffer from, int len) {
if (len > from.remaining()) {
throw new BufferOverflowException();
}
ULONG ulBusDataType = new ULONG(busDataType);
ULONG ulBusNumber = new ULONG(busNumber);
ULONG ulSlotNumber = new ULONG(slotNumber);
ULONGLONG ullOffset = new ULONGLONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesWritten = new ULONGByReference();
COMUtils.checkRC(jnaData.WriteBusData(ulBusDataType, ulBusNumber, ulSlotNumber, ullOffset,
from, ulLen, pulBytesWritten));
int written = pulBytesWritten.getValue().intValue();
from.position(written + from.position());
return written;
}
@Override
public int readIo(int interfaceType, int busNumber, int addressSpace, long offset,
ByteBuffer into, int len) {
if (len > into.remaining()) {
throw new BufferOverflowException();
}
ULONG ulInterfaceType = new ULONG(interfaceType);
ULONG ulBusNumber = new ULONG(busNumber);
ULONG ulAddressSpace = new ULONG(addressSpace);
ULONGLONG ullOffset = new ULONGLONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesRead = new ULONGByReference();
COMUtils.checkRC(jnaData.ReadIo(ulInterfaceType, ulBusNumber, ulAddressSpace, ullOffset,
into, ulLen, pulBytesRead));
int read = pulBytesRead.getValue().intValue();
into.position(read + into.position());
return read;
}
@Override
public int writeIo(int interfaceType, int busNumber, int addressSpace, long offset,
ByteBuffer from, int len) {
if (len > from.remaining()) {
throw new BufferOverflowException();
}
ULONG ulInterfaceType = new ULONG(interfaceType);
ULONG ulBusNumber = new ULONG(busNumber);
ULONG ulAddressSpace = new ULONG(addressSpace);
ULONGLONG ullOffset = new ULONGLONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesWritten = new ULONGByReference();
COMUtils.checkRC(jnaData.WriteIo(ulInterfaceType, ulBusNumber, ulAddressSpace, ullOffset,
from, ulLen, pulBytesWritten));
int written = pulBytesWritten.getValue().intValue();
from.position(written + from.position());
return written;
}
@Override
public long readMsr(int msr) {
ULONG ulNumber = new ULONG(msr);
ULONGLONGByReference pulValue = new ULONGLONGByReference();
COMUtils.checkRC(jnaData.ReadMsr(ulNumber, pulValue));
return pulValue.getValue().longValue();
}
@Override
public void writeMsr(int msr, long value) {
ULONG ulNumber = new ULONG(msr);
ULONGLONG ullValue = new ULONGLONG(value);
COMUtils.checkRC(jnaData.WriteMsr(ulNumber, ullValue));
}
@Override
public int readDebuggerData(int offset, ByteBuffer into, int len) {
if (len > into.remaining()) {
throw new BufferOverflowException();
}
ULONG ulOffset = new ULONG(offset);
ULONG ulLen = new ULONG(len);
ULONGByReference pulBytesRead = new ULONGByReference();
COMUtils.checkRC(jnaData.ReadDebuggerData(ulOffset, into, ulLen, pulBytesRead));
int read = pulBytesRead.getValue().intValue();
into.position(read + into.position());
return read;
}
}

View File

@ -0,0 +1,52 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.dataspaces;
import com.sun.jna.platform.win32.WinDef.ULONGLONG;
import com.sun.jna.platform.win32.WinNT.HRESULT;
import com.sun.jna.platform.win32.COM.COMUtils;
import agent.dbgeng.dbgeng.COMUtilsExtra;
import agent.dbgeng.jna.dbgeng.WinNTExtra.MEMORY_BASIC_INFORMATION64;
import agent.dbgeng.jna.dbgeng.dataspaces.IDebugDataSpaces2;
import ghidra.comm.util.BitmaskSet;
public class DebugDataSpacesImpl2 extends DebugDataSpacesImpl1 {
private final IDebugDataSpaces2 jnaData;
public DebugDataSpacesImpl2(IDebugDataSpaces2 jnaData) {
super(jnaData);
this.jnaData = jnaData;
}
@Override
public DebugMemoryBasicInformation queryVirtual(long offset) {
ULONGLONG ullOffset = new ULONGLONG(offset);
MEMORY_BASIC_INFORMATION64.ByReference pInfo = new MEMORY_BASIC_INFORMATION64.ByReference();
HRESULT hr = jnaData.QueryVirtual(ullOffset, pInfo);
if (hr.equals(COMUtilsExtra.E_UNEXPECTED)) {
return null;
}
COMUtils.checkRC(hr);
return new DebugMemoryBasicInformation(pInfo.BaseAddress.longValue(),
pInfo.AllocationBase.longValue(),
new BitmaskSet<>(PageProtection.class, pInfo.AllocationProtect.intValue()),
pInfo.RegionSize.longValue(), PageState.byValue(pInfo.State.intValue()),
new BitmaskSet<>(PageProtection.class, pInfo.Protect.intValue()),
PageType.byValue(pInfo.Type.intValue()));
}
}

View File

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.dataspaces;
import agent.dbgeng.jna.dbgeng.dataspaces.IDebugDataSpaces3;
public class DebugDataSpacesImpl3 extends DebugDataSpacesImpl2 {
@SuppressWarnings("unused")
private final IDebugDataSpaces3 jnaData;
public DebugDataSpacesImpl3(IDebugDataSpaces3 jnaData) {
super(jnaData);
this.jnaData = jnaData;
}
}

View File

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package agent.dbgeng.impl.dbgeng.dataspaces;
import agent.dbgeng.jna.dbgeng.dataspaces.IDebugDataSpaces4;
public class DebugDataSpacesImpl4 extends DebugDataSpacesImpl3 {
@SuppressWarnings("unused")
private final IDebugDataSpaces4 jnaData;
public DebugDataSpacesImpl4(IDebugDataSpaces4 jnaData) {
super(jnaData);
this.jnaData = jnaData;
}
}

View File

@ -0,0 +1,67 @@
/* ###
* 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.impl.dbgeng.dataspaces;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Guid.REFIID;
import agent.dbgeng.dbgeng.DebugDataSpaces;
import agent.dbgeng.impl.dbgeng.DbgEngUtil;
import agent.dbgeng.impl.dbgeng.DbgEngUtil.InterfaceSupplier;
import agent.dbgeng.jna.dbgeng.dataspaces.*;
import ghidra.util.datastruct.WeakValueHashMap;
public interface DebugDataSpacesInternal extends DebugDataSpaces {
Map<Pointer, DebugDataSpacesInternal> CACHE = new WeakValueHashMap<>();
static DebugDataSpacesInternal instanceFor(WrapIDebugDataSpaces data) {
return DbgEngUtil.lazyWeakCache(CACHE, data, DebugDataSpacesImpl1::new);
}
static DebugDataSpacesInternal instanceFor(WrapIDebugDataSpaces2 data) {
return DbgEngUtil.lazyWeakCache(CACHE, data, DebugDataSpacesImpl2::new);
}
static DebugDataSpacesInternal instanceFor(WrapIDebugDataSpaces3 data) {
return DbgEngUtil.lazyWeakCache(CACHE, data, DebugDataSpacesImpl3::new);
}
static DebugDataSpacesInternal instanceFor(WrapIDebugDataSpaces4 data) {
return DbgEngUtil.lazyWeakCache(CACHE, data, DebugDataSpacesImpl4::new);
}
ImmutableMap.Builder<REFIID, Class<? extends WrapIDebugDataSpaces>> PREFERRED_DATA_SPACES_IIDS_BUILDER =
ImmutableMap.builder();
Map<REFIID, Class<? extends WrapIDebugDataSpaces>> PREFERRED_DATA_SPACES_IIDS =
PREFERRED_DATA_SPACES_IIDS_BUILDER //
.put(new REFIID(IDebugDataSpaces4.IID_IDEBUG_DATA_SPACES4),
WrapIDebugDataSpaces4.class) //
.put(new REFIID(IDebugDataSpaces3.IID_IDEBUG_DATA_SPACES3),
WrapIDebugDataSpaces3.class) //
.put(new REFIID(IDebugDataSpaces2.IID_IDEBUG_DATA_SPACES2),
WrapIDebugDataSpaces2.class) //
.put(new REFIID(IDebugDataSpaces.IID_IDEBUG_DATA_SPACES),
WrapIDebugDataSpaces.class) //
.build();
static DebugDataSpacesInternal tryPreferredInterfaces(InterfaceSupplier supplier) {
return DbgEngUtil.tryPreferredInterfaces(DebugDataSpacesInternal.class,
PREFERRED_DATA_SPACES_IIDS, supplier);
}
}

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