mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-10 14:11:59 +00:00
Merge tag 'Ghidra_11.1_build' into stable
This commit is contained in:
commit
9a13473062
206
DevGuide.md
206
DevGuide.md
@ -62,6 +62,11 @@ Build Javadoc:
|
||||
gradle createJavadocs
|
||||
```
|
||||
|
||||
Build Python3 packages for the Debugger:
|
||||
```
|
||||
gradle buildPyPackage
|
||||
```
|
||||
|
||||
Build Ghidra to `build/dist` in an uncompressed form. This will be a distribution intended only to
|
||||
run on the platform on which it was built.
|
||||
```
|
||||
@ -182,13 +187,18 @@ If you'd like some details of our fine tuning, take a look at [building_fid.txt]
|
||||
|
||||
## Debugger Development
|
||||
|
||||
We have recently changed the Debugger's back-end architecture.
|
||||
We no longer user JNA to access native Debugger APIs.
|
||||
We only use it for pseudo-terminal access.
|
||||
Instead, we use Python3 and a protobuf-based TCP connection for back-end integration.
|
||||
|
||||
### Additional 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
|
||||
* LLDB 13.0 for macOS
|
||||
* GDB 13 or later for Linux
|
||||
* LLDB 10 or later for macOS
|
||||
|
||||
The others (e.g., JNA) are handled by Gradle via Maven Central.
|
||||
|
||||
@ -199,121 +209,137 @@ These all currently reside in the `Ghidra/Debug` directory, but will likely be r
|
||||
`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
|
||||
* 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
|
||||
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.
|
||||
conveniences. This is mostly deprecated.
|
||||
* Debugger - the collection of Ghidra plugins and services comprising the Debugger UI.
|
||||
* Debugger-rmi-trace - the wire protocol, client, services, and UI components for Trace RMI, the new back-end architecture.
|
||||
* 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-swig-lldb - the Java language bindings for LLDB's SBDebugger, also proposed upstream.
|
||||
* Debugger-agent-lldb - the connector for LLDB (13.0 required) on macOS, UNIX, and Windows.
|
||||
dbgmodel.dll) on Windows x64. This is deprecated, as most of these features are implemented in Debugger-agent-dbgeng for the new architecture.
|
||||
* Debugger-agent-dbgmodel-traceloader - an experimental "importer" for WinDbg trace files. This is deprecated.
|
||||
* Debugger-agent-gdb - the connector for GDB (13 or later recommended) on UNIX.
|
||||
* Debugger-swig-lldb - the Java language bindings for LLDB's SBDebugger, also proposed upstream. This is deprecated. We now use the Python3 language bindings for LLDB.
|
||||
* Debugger-agent-lldb - the connector for LLDB (10 or later recommended) on macOS, UNIX, and Windows.
|
||||
* 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).
|
||||
Protocol. This is deprecated. It's replaced by Debugger-rmi-trace.
|
||||
* Debugger-jpda - an in-development connector for Java and Dalvik debugging via JDI (i.e., JDWP). This is deprecated and not yet replaced.
|
||||
|
||||
The Trace Modeling schema records machine state and markup over time.
|
||||
It rests on the same database framework 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
|
||||
modues, breakpoints, etc.
|
||||
It rests on the same database framework 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.
|
||||
The back-end connector has full discretion over what is recorded by using Trace RMI.
|
||||
Typically, 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 modues, 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. In both cases, the
|
||||
implementations inherit from the same interfaces.
|
||||
Every back end (or "adapter" or "connector" or "agent") employs the Trace RMI client to populate a trace database.
|
||||
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 Trace RMI protocol.
|
||||
This also allows us to better bridge the language gap between Java and Python, which is supported by most native debuggers.
|
||||
This protocol is loosely coupled to Framework-TraceModeling, essentially exposing its methods via RMI, as well as some methods for controlling the UI.
|
||||
The protocol is built using Google's Protobuf library, providing a potential path for back-end implementations in alternative languages.
|
||||
We provide the Trace RMI server as a Ghidra component implemented in Java and the Trace RMI client as a Python3 package.
|
||||
A back-end implementation may be a stand-alone executable or script that accesses the native debugger's API, or a script or plugin for the native debugger.
|
||||
It then connects to Ghidra via Trace RMI to populate the trace database with information gleaned from that API.
|
||||
It should provide a set of diagnostic commands to control and monitor that connection.
|
||||
It should also use the native API to detect session and target changes so that Ghidra's UI consistently reflects the debugging session.
|
||||
|
||||
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.
|
||||
The old system relied on a "recorder" to discover targets and map them to traces in the proper Ghidra language.
|
||||
That responsibility is now delegated to the back end.
|
||||
Typically, it examines the target's architecture and immediately creates a trace upon connection.
|
||||
|
||||
### 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 you're 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.
|
||||
We believe the new system is much less daunting than the previous.
|
||||
Still, 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 some flux.
|
||||
When things go wrong, it could be because of, without limitation:
|
||||
|
||||
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. We've also codified (or
|
||||
attempted to codify) these requirements in a suite of abstract test cases. See the `ghidra.dbg.test`
|
||||
package of Framework-Debugging, and again, look at existing implementations.
|
||||
1. A bug on your part
|
||||
2. A bug on our part
|
||||
3. A design flaw in the interfaces
|
||||
4. A bug in the debugger/API you're adapting
|
||||
|
||||
We are still (yes, still) in the process of writing up this documentation.
|
||||
In the meantime, we recommend using the GDB and dbgeng agents as examples.
|
||||
Be sure to look at the Python code `src/main/py`!
|
||||
The deprecated Java code `src/main/java` is still included as we transition.
|
||||
|
||||
You'll also need to provide launcher(s) so that Ghidra knows how to configure and start your connector.
|
||||
These are just shell scripts.
|
||||
We use bash scripts on Linux and macOS, and we use batch files on Windows.
|
||||
Try to include as many common use cases as makes sense for the debugger.
|
||||
This provides the most flexibility to users and examples to power users who might create derivative launchers.
|
||||
Look at the existing launchers for examples.
|
||||
|
||||
For testing, please follow the examples for GDB.
|
||||
We no longer provide abstract classes that prescribe requirements.
|
||||
Instead, we just provide GDB as an example.
|
||||
Usually, we split our tests into three categories:
|
||||
|
||||
* Commands
|
||||
* Methods
|
||||
* Hooks
|
||||
|
||||
The Commands tests check that the user CLI commands, conventionally implemented in `commands.py`, work correctly.
|
||||
In general, do the minimum connection setup, execute the command, and check that it produces the expected output and causes the expected effects.
|
||||
|
||||
The Methods tests check that the remote methods, conventionally implemented in `methods.py`, work correctly.
|
||||
Many methods are just wrappers around CLI commands, some provided by the native debugger and some provided by `commands.py`.
|
||||
These work similarly to the commands test, except that they invoke methods instead of executing commands.
|
||||
Again, check the return value (rarely applicable) and that it causes the expected effects.
|
||||
|
||||
The Hooks tests check that the back end is able to listen for session and target changes, e.g., knowing when the target stops.
|
||||
*The test should not "cheat" by executing commands or invoking methods that should instead be triggered by the listener.*
|
||||
It should execute the minimal commands to setup the test, then trigger an event.
|
||||
It should then check that the event in turn triggered the expected effects, e.g., updating PC upon the target stopping.
|
||||
|
||||
Whenever you make a change to the Python code, you'll need to re-assemble the package's source.
|
||||
|
||||
```
|
||||
gradle assemblePyPackage
|
||||
```
|
||||
|
||||
This is required in case your package includes generated source, as is the case for Debugger-rmi-trace.
|
||||
If you want to create a new Ghidra module for your connector (recommended) use an existing one's `build.gradle` as a template.
|
||||
A key part is applying the `hasPythonPackage.gradle` script.
|
||||
|
||||
### 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 (and several others) 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.
|
||||
If a connector already exists for a suitable debugger on the desired platform, then adding it may be very simple.
|
||||
For example, many platforms are supported by GDB, so even though we're currently focused on x86-64 (and to some extent arm64) support, we've provided the mappings for many.
|
||||
These mappings are conventionally kept in each connector's `arch.py` file.
|
||||
|
||||
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.
|
||||
In general, to update `arch.py`, 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 the included mappers.
|
||||
Naturally, you'll want to test the special cases, preferably in automated tests.
|
||||
|
||||
### Emulation
|
||||
|
||||
The most obvious integration path for 3rd-party emulators is to write a "connector." However, p-code
|
||||
emulation is now an integral feature of the Ghidra UI, and it has a fairly accessible API. Namely,
|
||||
for interpolation between machines states recorded in a trace, and extrapolation into future machine
|
||||
states. Integration of such emulators may still be useful to you, but we recommend trying the p-code
|
||||
emulator to see if it suits your needs for emulation in Ghidra before pursuing integration of
|
||||
another emulator.
|
||||
The most obvious integration path for 3rd-party emulators is to write a "connector."
|
||||
However, p-code emulation is an integral feature of the Ghidra UI, and it has a fairly accessible API.
|
||||
Namely, for interpolation between machines states recorded in a trace, and extrapolation into future machine states.
|
||||
Integration of such emulators may still be useful to you, but we recommend trying the p-code emulator to see if it suits your needs for emulation in Ghidra before pursuing integration of another emulator.
|
||||
We also provide out-of-the-box QEMU integration via GDB.
|
||||
|
||||
### 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.
|
||||
When submitting help tickets and pull requests, please tag those related to the debugger with "Debugger" so that we can triage them more quickly.
|
||||
|
||||
|
||||
[java]: https://dev.java
|
||||
|
@ -81,6 +81,8 @@ model {
|
||||
targetPlatform "linux_arm_64"
|
||||
targetPlatform "mac_x86_64"
|
||||
targetPlatform "mac_arm_64"
|
||||
targetPlatform "freebsd_x86_64"
|
||||
targetPlatform "freebsd_arm_64"
|
||||
sources {
|
||||
c {
|
||||
source {
|
||||
@ -102,6 +104,8 @@ model {
|
||||
targetPlatform "linux_arm_64"
|
||||
targetPlatform "mac_x86_64"
|
||||
targetPlatform "mac_arm_64"
|
||||
targetPlatform "freebsd_x86_64"
|
||||
targetPlatform "freebsd_arm_64"
|
||||
sources {
|
||||
c {
|
||||
source {
|
||||
|
@ -52,6 +52,16 @@ model {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isCurrentFreeBSD()) {
|
||||
gcc(Gcc) {
|
||||
if (isCurrentArm_64()) {
|
||||
target("freebsd_arm_64")
|
||||
}
|
||||
else {
|
||||
target("freebsd_x86_64")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isCurrentWindows() && VISUAL_STUDIO_INSTALL_DIR) {
|
||||
// specify installDir because Gradle doesn't find VS Build Tools.
|
||||
// See https://github.com/gradle/gradle-native/issues/617#issuecomment-575735288
|
||||
|
@ -24,7 +24,9 @@ project.ext.PLATFORMS = [
|
||||
[name: "linux_x86_64", os: "linux", arch: "x86_64"],
|
||||
[name: "linux_arm_64", os: "linux", arch: "arm64"],
|
||||
[name: "mac_x86_64", os: "osx", arch: "x86_64"],
|
||||
[name: "mac_arm_64", os: "osx", arch: "arm64"]
|
||||
[name: "mac_arm_64", os: "osx", arch: "arm64"],
|
||||
[name: "freebsd_x86_64", os: "freebsd", arch: "x86_64"],
|
||||
[name: "freebsd_arm_64", os: "freebsd", arch: "arm64"]
|
||||
]
|
||||
|
||||
/*********************************************************************************
|
||||
@ -51,6 +53,9 @@ ext.getCurrentPlatformName = {
|
||||
case ~/Mac OS X.*/:
|
||||
os = "mac"
|
||||
break
|
||||
case ~/FreeBSD.*/:
|
||||
os = "freebsd"
|
||||
break
|
||||
default:
|
||||
throw new GradleException("Unrecognized platform operating system: $os")
|
||||
}
|
||||
@ -112,6 +117,20 @@ ext.isCurrentMac = {
|
||||
return isMac(getCurrentPlatformName())
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Returns true if the given platform is FreeBSD.
|
||||
*********************************************************************************/
|
||||
ext.isFreeBSD = { platform_name ->
|
||||
return platform_name.startsWith("freebsd")
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Returns true if the current platform is FreeBSD.
|
||||
*********************************************************************************/
|
||||
ext.isCurrentFreeBSD = {
|
||||
return isFreeBSD(getCurrentPlatformName())
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Returns true if the given platform is Windows.
|
||||
*********************************************************************************/
|
||||
|
@ -1,6 +1,3 @@
|
||||
Internet|https://msdl.microsoft.com/download/symbols/|WARNING: Check your organization's security policy before downloading files from the internet.
|
||||
Internet|https://chromium-browser-symsrv.commondatastorage.googleapis.com|WARNING: Check your organization's security policy before downloading files from the internet.
|
||||
Internet|https://symbols.mozilla.org/|WARNING: Check your organization's security policy before downloading files from the internet.
|
||||
Internet|https://software.intel.com/sites/downloads/symbols/|WARNING: Check your organization's security policy before downloading files from the internet.
|
||||
Internet|https://driver-symbols.nvidia.com/|WARNING: Check your organization's security policy before downloading files from the internet.
|
||||
Internet|https://download.amd.com/dir/bin|WARNING: Check your organization's security policy before downloading files from the internet.
|
||||
|
@ -22,6 +22,178 @@
|
||||
|
||||
<BODY>
|
||||
|
||||
<H1 align="center">Ghidra 11.1 Change History (June 2024)</H1>
|
||||
<blockquote><p><u><B>New Features</B></u></p>
|
||||
<ul>
|
||||
<li><I>Assembler</I>. Added the <span class="gcode">WildcardAssembler</span> module and API for allowing the masking of operands. (GP-4287, Issue #6118)</li>
|
||||
<li><I>Basic Infrastructure</I>. Replaced the <span class="gtitle">Show VM Memory</span> dialog with an upgraded <span class="gtitle">Runtime Information</span> dialog. The dialog contains more information which can aid in debugging, including version information, classpath, defined properties, environment variables, and more. (GP-3844, Issue #5760)</li>
|
||||
<li><I>Debugger</I>. Added a Trace RMI launcher for Windows targets on Linux using Wine. (GP-3891)</li>
|
||||
<li><I>Debugger</I>. Added Trace RMI connector for Microsoft Time-Travel Debugging. (GP-4182)</li>
|
||||
<li><I>Debugger:Agents</I>. Added TraceRMI protocol to provide access to the trace database API over protobufs. This is a simpler alternative to GADP. Implemented TraceRMI client for Python3; added TraceRMI plugins for GDB, LLDB, and WinDbg (dbgeng). Added services, APIs and GUI components for managing TraceRMI connections. (GP-3816)</li>
|
||||
<li><I>Debugger:dbgeng.dll</I>. Extended dbgeng Trace RMI connector to support dbgmodel. (GP-4290)</li>
|
||||
<li><I>Debugger:GDB</I>. Added Trace RMI connector for QEMU with GDB. (GP-3838)</li>
|
||||
<li><I>Debugger:GDB</I>. Added <span class="gcode">raw</span> GDB and Python 3 connectors. (GP-4439)</li>
|
||||
<li><I>DWARF</I>. Added DWARF5 support. (GP-2798, Issue #4088)</li>
|
||||
<li><I>Eclipse Integration</I>. The GhidraDev Eclipse plugin has a new wizard for importing an existing Ghidra module source directory. This will work best with Ghidra module projects created against Ghidra 11.1 or later. (GP-707, Issue #284)</li>
|
||||
<li><I>FileSystems</I>. Added a new <span class="gcode">GFileSystem</span> for Mach-O file sets (i.e., the kernelcache). (GP-3770, Issue #4827)</li>
|
||||
<li><I>FileSystems</I>. Added support for SquashFS FileSystems. (GP-3946)</li>
|
||||
<li><I>FileSystems</I>. Added support for decompressing LZFSE files (iOS/macOS kernelcache) to the File System Browser. (GP-4391)</li>
|
||||
<li><I>GhidraGo</I>. Remote and local GhidraURL's that locate DomainFolders are now acceptable input for GhidraGo. Both result in opening the read-only view for the project and selecting the DomainFolder in the FrontEnd. If the local URL refers to the currently active project, the domain folder will be selected within that. (GP-4433)</li>
|
||||
<li><I>Languages</I>. Improved Swift support by adding Swift Demangler, Swift cspecs, Swift opinions, and by applying type metadata where possible. (GP-3535)</li>
|
||||
<li><I>Version Tracking</I>. Added ability for Version Tracking Session files to be checked into a version control repository. Versioned use is restricted to exclusive checkout use only since there no ability to merge VT Session data. (GP-4085)</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
<blockquote><p><u><B>Improvements</B></u></p>
|
||||
<ul>
|
||||
<li><I>Accessibility</I>. Improved filter options dialog for accessibility. (GP-2264)</li>
|
||||
<li><I>Accessibility</I>. Fixed several focus traversal issues. Also, added convenience actions for moving to next/previous window (<span class="gcode">Ctrl-F3</span>/<span class="gcode">Shift-Ctrl-F3</span>) and next/previous component provider (<span class="gcode">Ctrl-J</span>/<span class="gcode">Shift-Ctrl-J</span>). (GP-4227)</li>
|
||||
<li><I>Accessibility</I>. Added quick action dialog to make actions accessible and more convenient for users who prefer to use keyboard more than mouse. <span class="gcode">Ctrl-3</span> will bring up the dialog. (GP-4267)</li>
|
||||
<li><I>Accessibility</I>. Added accessible names to most main provider components. (GP-4275)</li>
|
||||
<li><I>Accessibility</I>. Improved analyzer enablement so that checkboxes can be changed via the keyboard using the spacebar. (GP-4375, Issue #6261)</li>
|
||||
<li><I>Accessibility</I>. Improved export options window to allow changing checkboxes via keyboard using the spacebar. (GP-4414, Issue #6279)</li>
|
||||
<li><I>Accessibility</I>. Improved keyboard navigation in <span class="gtitle">Add Reference</span> dialog. (GP-4491, Issue #5761)</li>
|
||||
<li><I>Accessibility</I>. Improved Accessibility for assorted <span class="gtitle">Memory Map</span> dialogs. (GP-4511)</li>
|
||||
<li><I>Analysis</I>. Sped up switch recovery analysis on AArch64 objectiveC binaries where <span class="gcode">BRK</span> instruction is used throughout the code for exceptions. (GP-4364)</li>
|
||||
<li><I>Analysis</I>. Added pattern to recognize PPC <span class="gcode">get_pc_thunk_lr</span> position-independent-code-related function. (GP-4474)</li>
|
||||
<li><I>Analysis</I>. The speed of the <span class="gtitle">Create Address Tables</span> analyzer has been improved for large runs of addresses. (GP-4477)</li>
|
||||
<li><I>API</I>. Created builder for <span class="gcode">DomainObjectListeners</span> to express even-handling logic more concisely. (GP-4222)</li>
|
||||
<li><I>API</I>. Revised <span class="gcode">DomainObject</span> java interface while eliminating separate <span class="gcode">UndoableDomainObject</span> and <span class="gcode">Undoable</span> java interface classes. Revised tool-based foreground <span class="gcode">Command</span>-processing to defer event-flushing into a background task. Additional <span class="gcode">execute</span> methods were added to <span class="gcode">PluginTool</span> which allow lambda functions to be used in the place of a <span class="gcode">Command</span> object. (GP-4390)</li>
|
||||
<li><I>Assembler</I>. Added context hints to <span class="gtitle">Patch Instruction</span> (Assembler) results. (GP-3993)</li>
|
||||
<li><I>Assembler</I>. Context is re-flowed and instructions re-disassembled following the <span class="gtitle">Patch Instruction</span> action. (GP-4014)</li>
|
||||
<li><I>Assembler</I>. By virtue of API changes, the Assembler is now more extensible by plugins for advanced use cases. (GP-4185)</li>
|
||||
<li><I>Basic Infrastructure</I>. Ghidra now supports the XDG Base Directory Specification. Default locations of the user settings, cache, and temporary directories have moved to more standardized platform-specific locations. See comments in the <span class="gcode">support/launch.properties</span> file for more detailed information on how these directories are determined and overridden. (GP-1164, Issue #908)</li>
|
||||
<li><I>Basic Infrastructure</I>. Improved the start up time of Ghidra by only loading <span class="gcode">ExtensionPoints</span> when they are first requested. (GP-4515)</li>
|
||||
<li><I>Build</I>. Fixed compilation of <span class="gcode">TreeValueSortedMap</span> for Java 21. (GP-3923, Issue #6083)</li>
|
||||
<li><I>Build</I>. Ghidra will now run on FreeBSD with user-built native components using the <span class="gcode">support/buildNatives</span> script. See the Installation Guide for more information on building native components. NOTE: <span class="gcode">bash</span> is required to be installed in order for Ghidra to launch on FreeBSD. Additionally, the Debugger is not currently supported on FreeBSD. (GP-4235, Issue #6117)</li>
|
||||
<li><I>Byte Viewer</I>. Added the ability to resize the <span class="gtitle">Address</span> column in the ByteViewer window. (GP-2147)</li>
|
||||
<li><I>Data</I>. Added <span class="gcode">writable</span> Mutability data setting to allow chosen data within a read-only memory block to deviate from the block setting. Decompiler was updated to respect this setting. (GP-4505)</li>
|
||||
<li><I>Data Types</I>. Improved datatype resolution and conflict handling as well as datatype name sorting. (GP-3632)</li>
|
||||
<li><I>Debugger</I>. Domain Object event ID numbers have been refactored to be enums. (GP-2076)</li>
|
||||
<li><I>Debugger</I>. Removed TargetRecorder-dependent methods from FlatDebuggerAPI. Moved them to deprecated FlatDebuggerRecorderAPI. Ported FlatDebuggerAPI to Target interface. Added FlatDebuggerRmiAPI. (GP-3872)</li>
|
||||
<li><I>Debugger</I>. Moved launch and control command progress monitors to the <span class="gtitle">Debug Console</span>. (GP-3997)</li>
|
||||
<li><I>Debugger</I>. The <span class="gtitle">Sections Table</span> pane can now be toggled on and off in the <span class="gtitle">Modules</span> window. (GP-4156)</li>
|
||||
<li><I>Debugger</I>. Upgraded to most current release of llvm/lldb (17.x). (GP-4385)</li>
|
||||
<li><I>Debugger</I>. Added Trace RMI connector for GDB <span class="gcode">target remote</span>. (GP-4437)</li>
|
||||
<li><I>Debugger</I>. Made modifications to accommodate remote embedded targets. (GP-4441)</li>
|
||||
<li><I>Debugger</I>. Released Trace RMI as the default Debugger back end. (GP-4485)</li>
|
||||
<li><I>Debugger</I>. Made modifications to enable traceRMI dialog-driven methods. (GP-4527)</li>
|
||||
<li><I>Debugger:Agents</I>. Released version 11.1 of Trace RMI python packages and protocol. (GP-4487)</li>
|
||||
<li><I>Debugger:Agents</I>. The launch failure dialog now previews the last two lines of each Terminal. Selecting <span class="gtitle">Keep</span> will automatically bring those terminals to the front. (GP-4637)</li>
|
||||
<li><I>Debugger:Emulator</I>. Pure emulation use cases can now involve thread creation and destruction and they can be recorded linearly in a trace. (GP-4374)</li>
|
||||
<li><I>Debugger:Listing</I>. Added context menu to location label in <span class="gtitle">Dynamic Listing</span> and <span class="gtitle">Memory Bytes</span> windows. (GP-4311)</li>
|
||||
<li><I>Debugger:Listing</I>. Progress for memory reads now displays in the <span class="gtitle">Debug Console</span> window. (GP-4399)</li>
|
||||
<li><I>Debugger:Mappings</I>. Moved initial mapping failure at launch to the <span class="gtitle">Debug Console</span>, rather than popping the launch failure dialog. (GP-4636)</li>
|
||||
<li><I>Debugger:Memory</I>. Made <span class="gtitle">Memory</span> window more consistent with <span class="gtitle">Dynamic Listing</span> window in terms of appearance and operation. (GP-1625)</li>
|
||||
<li><I>Debugger:Stack</I>. Added <span class="gtitle">Module</span> column to <span class="gtitle">Stack</span> window. (GP-4093)</li>
|
||||
<li><I>Debugger:Threads</I>. Moved trace selection tabs to <span class="gtitle">Dynamic Listing</span>. (GP-1608)</li>
|
||||
<li><I>Debugger:Threads</I>. Added <span class="gtitle">PC</span>, <span class="gtitle">Function</span>, <span class="gtitle">Module</span>, and <span class="gtitle">SP</span> columns to the <span class="gtitle">Threads</span> table. (GP-4236)</li>
|
||||
<li><I>Debugger:Threads</I>. Removed <span class="gtitle">Synchronize Target Activation</span> toggle. Time navigation is now prohibited in Target control mode, especially using the Plot column header. (GP-4334)</li>
|
||||
<li><I>Decompiler</I>. Added <span class="gtitle">Edit Signature Override</span> action to Decompiler. Improved cleanup of unused override signature datatypes. (GP-4263, Issue #6000)</li>
|
||||
<li><I>Decompiler</I>. The Decompiler is now able to simplify additional forms of 64-bit optimized integer division. (GP-4300, Issue #5733)</li>
|
||||
<li><I>Decompiler</I>. Added additional Decompiler support for recovering the addresses of calls and other global symbols for MIPS binaries compiled with Position-Independent Code (PIC) options. (GP-4370)</li>
|
||||
<li><I>Decompiler</I>. The Decompiler now displays array index constants using the configured <span class="gcode">Integer format</span>, rather than always using base 10. (GP-4394, Issue #6019)</li>
|
||||
<li><I>Disassembly</I>. Created <span class="gcode">FixOffcutInstructionScript</span> that attempts to automatically fix an offcut instruction and its references in a restricted fashion. This script can be bound to a hotkey for a user to quickly attempt fixups throughout a program. Also, updated the set instruction length override action to automatically suggest a reasonable length, based on offcut flows, and to disassemble these flows if used. (GP-4034, Issue #5928)</li>
|
||||
<li><I>Disassembly</I>. Fixed storage of default disassembly context to the program database. Programs with no stored context, which is most, will disassemble faster. (GP-4535)</li>
|
||||
<li><I>Disassembly</I>. Improved disassembly speed and use of instructions for any purpose by delaying check of instruction overrides until needed. (GP-4536)</li>
|
||||
<li><I>Documentation</I>. Updated Debugger training materials for Trace RMI. (GP-3887)</li>
|
||||
<li><I>Eclipse Integration</I>. Upgraded the GhidraDev Eclipse plugin to make it compatible with Ghidra 11.1. (GP-4176)</li>
|
||||
<li><I>Eclipse Integration</I>. The Ghidra Eclipse preferences and formatter files are now included in the release under <span class="gcode">support/eclipse/</span>. (GP-4233, Issue #5999)</li>
|
||||
<li><I>Framework</I>. Updated system actions to allow for user-defined key bindings. (GP-4317)</li>
|
||||
<li><I>GUI</I>. Added tool option to remove quotes from strings before putting into clipboard. (GP-3871, Issue #1155)</li>
|
||||
<li><I>GUI</I>. Simplified the dialog for switching themes. (GP-4172, Issue #6024)</li>
|
||||
<li><I>GUI</I>. Updated the Help Info keybinding to <span class="gcode">Ctrl-Shift-F1</span> in order for components to allow <span class="gcode">Ctrl-F1</span> to work for showing tooltips. (GP-4304)</li>
|
||||
<li><I>GUI</I>. Added a <span class="gtitle">Copy Special</span> action to the Listing to copy the byte source offset of the selected address. (GP-4318, Issue #6195)</li>
|
||||
<li><I>GUI</I>. Updated Key Binding options for actions to allow users to set mouse bindings. (GP-4436, Issue #208)</li>
|
||||
<li><I>Importer</I>. Added ability to import/export multiple program trees using SARIF. (GP-4079)</li>
|
||||
<li><I>Importer</I>. The MzLoader can now load binaries whose file size is less than the size that is reported in its header. (GP-4260, Issue #6029)</li>
|
||||
<li><I>Importer</I>. The Importer post-load message log is now echoed to the application.log file. This behavior can be disabled in the <span class="gcode">support/launch.properties</span> file by uncommenting the <span class="gcode">VMARGS=-Ddisable.loader.logging=true</span> line. (GP-4313)</li>
|
||||
<li><I>Importer</I>. The PeLoader now pads memory blocks with zeros instead of creating an uninitialized block with the same name. (GP-4347, Issue #6238)</li>
|
||||
<li><I>Importer</I>. Fixed an issue in the MzLoader where the default Program Tree was out of sync with the Memory Map. (GP-4432, Issue #6277)</li>
|
||||
<li><I>Importer:COFF</I>. COFF headers are now marked up. (GP-4184)</li>
|
||||
<li><I>Importer:ELF</I>. Added relocation handlers for the TI_MSP430 and TI_MSP430X processors. (GP-4152)</li>
|
||||
<li><I>Importer:ELF</I>. Added ELF Import option to enable/disable the creation of undefined data for data symbols with known sizes. This option is enabled by default. (GP-4178)</li>
|
||||
<li><I>Importer:ELF</I>. Transitioned to new <span class="gcode">AbstractElfRelocationHandler</span> implementation which uses <span class="gcode">ElfRelocationType</span> enums specific to each handler. (GP-4239)</li>
|
||||
<li><I>Importer:ELF</I>. Relaxed ELF <span class="gcode">PT_DYNAMIC</span> restriction to allow it to be processed when not covered by a <span class="gcode">PT_LOAD</span>. (GP-4291, Issue #5784)</li>
|
||||
<li><I>Importer:Mach-O</I>. Improved handling of Mach-O <span class="gcode">DYLD_CHAINED_PTR_ARM64E_KERNEL</span> chained pointer fixups. (GP-4259, Issue #6144, #6145)</li>
|
||||
<li><I>Importer:Mach-O</I>. The <span class="gcode">dyld_shared_cache</span> loader now implements pointer fixups for newer versions that use <span class="gcode">dyld_cache_slide_info5</span>. (GP-4380)</li>
|
||||
<li><I>Importer:Mach-O</I>. The MachoLoader now does a better job at importing binaries with corrupted load commands. (GP-4561, Issue #6271)</li>
|
||||
<li><I>Languages</I>. Added support for structured data-type parameters for x86 64-bit System V ABI. (GP-4031)</li>
|
||||
<li><I>Languages</I>. Added Golang 1.21 support. (GP-4183, Issue #6072)</li>
|
||||
<li><I>Languages</I>. Added support for Apple Silicon and AARCH64 Golang binaries. (GP-4465)</li>
|
||||
<li><I>Languages</I>. Added support for Golang 1.22. Versions supported are now 1.17-1.22. (GP-4579)</li>
|
||||
<li><I>Listing</I>. Added options to wrap operand fields on semicolons. This is to better support processors that have more than one instruction at an address. (GP-4289)</li>
|
||||
<li><I>Memory</I>. Added <span class="gcode">Artificial</span> memory block flag intended to identify those blocks that the Debugger should not map into a running target. (GP-4125)</li>
|
||||
<li><I>Memory</I>. Removed lock contention on reading and update of memory AddressSet cache. (GP-4534)</li>
|
||||
<li><I>Multi-User</I>. Significantly improved shared project directory performance when directories contain a very large number of files. (GP-4456)</li>
|
||||
<li><I>PDB</I>. Modified <span class="gcode">LoadPdbTask</span> to schedule <span class="gcode">EntryPointAnalyzer</span>. (GP-4244)</li>
|
||||
<li><I>PDB</I>. Modified <span class="gcode">PdbUniversalAnalyzer</span> to do work into multiple phases so that this work can benefit from work done in interim analyzers. (GP-4245)</li>
|
||||
<li><I>PDB</I>. Reduced number of data type conflicts by delaying the resolve step in the multi-phased resolve process. Also refactored the multi-phased resolve, removing placeholder types. (GP-4246)</li>
|
||||
<li><I>PDB</I>. Stubbed additional larger-than-64-bit pointers to ensure they do not cause problem when used. (GP-4264)</li>
|
||||
<li><I>PDB</I>. Improved mechanism for setting primary symbols; reduced memory footprint by removing primary symbol map and using the now-more-performant ghidra primary symbol methods. (GP-4335, Issue #3497)</li>
|
||||
<li><I>Processors</I>. Added subset of Tricore relocations as well as function start patterns. (GP-3110, Issue #1449)</li>
|
||||
<li><I>Processors</I>. Corrected errors in the MSP430 SLEIGH specification. (GP-4401, Issue #4120)</li>
|
||||
<li><I>Processors</I>. Corrected implementation of <span class="gcode">ZR</span> (aka <span class="gcode">R0</span>) register access for MCS-96 processor. (GP-4407, Issue #6181)</li>
|
||||
<li><I>Processors</I>. Added callfixup for <span class="gcode">__chkstk()</span> found in windows AARCH64 binaries. (GP-4513)</li>
|
||||
<li><I>ProgramDB</I>. Improved locking behavior of Instructions and Data when retrieving bytes from memory. (GP-4568)</li>
|
||||
<li><I>Project</I>. Added an abstract <span class="gcode">GhidraURLQueryTask</span> and related <span class="gcode">GhidraURLQuery</span> utlity class to failitate proper GhidraURL queries and to avoid replication of code. (GP-4447)</li>
|
||||
<li><I>Scripting</I>. Python scripts now have access to the <span class="gcode">this</span> variable, which is a reference to its parent GhidraScript object. It may be necessary to refer to <span class="gcode">this</span> in certain scenarios, such as when releasing the consumer of a Program object returned by <span class="gcode">askProgram()</span>. (GP-4157)</li>
|
||||
<li><I>Scripting</I>. Updated <span class="gcode">RecoverClassesFromRTTIScript</span>'s GCC class recovery to handle copy relocations. (GP-4396)</li>
|
||||
<li><I>Scripting</I>. Added script to paste address/bytes copied as text from a Ghidra Listing. (GP-4480)</li>
|
||||
<li><I>Scripting</I>. Upgraded OSGi-related jars. (GP-4550)</li>
|
||||
<li><I>Sleigh</I>. Compiled SLEIGH (<span class="gcode">.sla</span>) files are now stored in a compressed format to save disk space and shorten language load times. (GP-4285)</li>
|
||||
<li><I>Testing</I>. Upgraded jacoco to version 0.8.11. (GP-4262)</li>
|
||||
<li><I>Version Tracking</I>. Added <span class="gtitle">Function Compare</span> action to the <span class="gtitle">Version Tracking</span> main match table and associated match tables. (GP-4251, Issue #6010)</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
<blockquote><p><u><B>Bugs</B></u></p>
|
||||
<ul>
|
||||
<li><I>Analysis</I>. Improved recovery of additional windows resource references in certain cases by handling the decompiler produced <span class="gcode">MULTI_EQUAL</span> pcode operation. (GP-7)</li>
|
||||
<li><I>Analysis</I>. Exported symbols are now checked that they are not symbols internal to a function before creating a function. (GP-4506)</li>
|
||||
<li><I>Data</I>. Corrected improper Data pointer stacking behavior when applying a pointer data type onto an existing pointer. (GP-4181)</li>
|
||||
<li><I>Data Types</I>. Corrected various Data settings issues where Listing display failed to update properly with settings change. (GP-4212, Issue #5922)</li>
|
||||
<li><I>Data Types</I>. Corrected transaction error when disassociating a datatype from an archive not open for update. (GP-4524, Issue #6424)</li>
|
||||
<li><I>Data Types</I>. Fixed searching for references to structure fields when the field is referenced in a local structure that is then passed to an external function. This has a major effect on Windows programs. (GP-4592, Issue #5652)</li>
|
||||
<li><I>Data Types</I>. Corrected data type source archive transaction error when performing bulk archive revert, update, and disassociate actions. (GP-4615, Issue #6503)</li>
|
||||
<li><I>Debugger</I>. Fixed module map to ignore artificial blocks, especially <span class="gcode">tdb</span> on Windows. (GP-4072, Issue #5994)</li>
|
||||
<li><I>Debugger</I>. Fixed thread-specific stepping in dbgmodel. (GP-4279)</li>
|
||||
<li><I>Debugger:Emulator</I>. Fixed issue where step command is ignored after the emulator encounters an error; e.g., undefined userop. (GP-4248, Issue #6086)</li>
|
||||
<li><I>Debugger:Listing</I>. <span class="gtitle">GoTo</span> in Dynamic Listing can now find symbols with external linkage; e.g., IAT entries. (GP-3408)</li>
|
||||
<li><I>Debugger:Listing</I>. Fixed issue with incorrect byte values in Debugger's snapshot comparison listing. (GP-4528)</li>
|
||||
<li><I>Debugger:Memory</I>. Fixed error message regarding closed programs in the navigation history when using the <span class="gtitle">Memory</span> (dynamic hex) viewer. (GP-4100)</li>
|
||||
<li><I>Decompiler</I>. Corrected issue with Decompiler return/param commit which could cause return details to revert to default state. (GP-4434, Issue #6318)</li>
|
||||
<li><I>Decompiler</I>. Fixed Decompiler bug causing erroneous <span class="gcode">case</span> labels for some switches contained in an <span class="gcode">if</span> block. (GP-4514, Issue #6128)</li>
|
||||
<li><I>Demangler</I>. Fixed out-of-memory issue in MDMang due to infinite loop. (GP-4641, Issue #6586)</li>
|
||||
<li><I>FID</I>. Corrected FID error caused by Functions defined where no memory block resides. (GP-4584, Issue #6453)</li>
|
||||
<li><I>Graphing</I>. Fixed <span class="gcode">NullPointerException</span> in the <span class="gcode">ChkDominanceAlgorithm</span>. (GP-4530)</li>
|
||||
<li><I>GUI</I>. Fixed Listing to navigate to requested address when opening from a URL. (GP-4281, Issue #6166)</li>
|
||||
<li><I>GUI</I>. Fixed Memory Search results to select all matched address when making a selection from the results table. (GP-4538, Issue #6415)</li>
|
||||
<li><I>GUI</I>. Fixed stack overflow in Bundle Manager window when trying to remove all bundles. (GP-4604)</li>
|
||||
<li><I>Importer</I>. Fixed the handling of non-default address spaces, specific to SARIF. (GP-4097)</li>
|
||||
<li><I>Importer</I>. Fixed <span class="gcode">NullPointerException</span>s in the SARIF handlers. (GP-4510)</li>
|
||||
<li><I>Importer</I>. Fixed an issue in the MzLoader that would prevent some 16-bit MZ binaries from loading correctly. (GP-4575, Issue #5970)</li>
|
||||
<li><I>Importer</I>. Fixed a regression that prevented library search paths from getting saved. (GP-4594)</li>
|
||||
<li><I>Importer:Mach-O</I>. The MachoLoader no longer throws an exception when importing DWARF dSYM companion files. (GP-4417, Issue #6302)</li>
|
||||
<li><I>Importer:PE</I>. Fixed an <span class="gcode">EOFException</span> in the PeLoader that could occur when data directories point to section padding bytes. (GP-4496, Issue #6380)</li>
|
||||
<li><I>Importer:PE</I>. Fixed an issue with the provided <span class="gcode">.exports</span> files not getting properly used in some scenarios. (GP-4628)</li>
|
||||
<li><I>Languages</I>. Corrected handling of operand-size override prefix with x86 <span class="gcode">MOVSX</span>/<span class="gcode">MOVZX</span> instructions. (GP-4629, Issue #6525)</li>
|
||||
<li><I>Multi-User</I>. Corrected potential deadlock condition within Ghidra Server. (GP-4531)</li>
|
||||
<li><I>PDB</I>. Removed PDB symbol server URLs from default list that don't publish PDBs. (GP-4266, Issue #3109, #6152)</li>
|
||||
<li><I>PDB</I>. Fixed issue preventing VS6 PDB from being processed due to unexpected unavailable <span class="gcode">DebugData</span> streams. (GP-4571, Issue #6464)</li>
|
||||
<li><I>Processors</I>. Added support for x86 AVX512 instructions (GP-1561, Issue #2209, #4704, #6458)</li>
|
||||
<li><I>Processors</I>. Added PIC16F <span class="gcode">movlb</span> variant instruction form to processor module. (GP-3723)</li>
|
||||
<li><I>Processors</I>. Fixed Xtensa <span class="gcode">bany</span> semantics and added simplifying cases for <span class="gcode">sext</span> instruction. (GP-4254, Issue #6113)</li>
|
||||
<li><I>Processors</I>. Corrected register sizing for the x86 <span class="gcode">str</span> instruction. (GP-4272, Issue #6156)</li>
|
||||
<li><I>Processors</I>. Fixed bug in the M68000 processor with instructions referencing immediate byte values displaying erroneous two-byte values. (GP-4377, Issue #4191, #6260)</li>
|
||||
<li><I>Processors</I>. Fixed operand ordering in x86 <span class="gcode">FDIVP</span> instruction. (GP-4381, Issue #6266)</li>
|
||||
<li><I>Processors</I>. Made several bug fixes for SuperH processor module. (GP-4498, Issue #5967, #6013)</li>
|
||||
<li><I>Processors</I>. Fixed AARCH64 <span class="gcode">ldst</span> instruction to properly support register writeback. (GP-4499)</li>
|
||||
<li><I>Processors</I>. Fixed Tricore <span class="gcode">st.da</span> instruction writing half-words instead of words. (GP-4552, Issue #6456)</li>
|
||||
<li><I>Processors</I>. Updated x86-64 <span class="gcode">RCL</span> and <span class="gcode">RCR</span> instructions to set <span class="gcode">CF</span> correctly. (GP-4576, Issue #6423)</li>
|
||||
<li><I>References</I>. Updated <span class="gcode">EditMemoryReferencePanel</span> to enable inclusion of <span class="gcode">OTHER</span> overlay spaces for address specification. (GP-4345, Issue #6245)</li>
|
||||
<li><I>Version Tracking</I>. Improved Version Tracking <span class="gtitle">Implied Match</span> determination to make sure the destination location is a function if the source location is a function. (GP-4283)</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
|
||||
<H1 align="center">Ghidra 11.0.3 Change History (April 2024)</H1>
|
||||
<blockquote><p><u><B>Improvements</B></u></p>
|
||||
<ul>
|
||||
@ -535,7 +707,7 @@
|
||||
<li><I>Build</I>. Added support for building with Gradle 8. (GP-2476, Issue #3527, #5003)</li>
|
||||
<li><I>Build</I>. The build now enforces a maximum-supported Gradle version. The current supported versions are Gradle 7.3 or later. (GP-3111)</li>
|
||||
<li><I>Build</I>. Ghidra can now run from development/repository mode using Gradle's compiled jars, instead of just relying on Eclipse's compilation output. (GP-3140)</li>
|
||||
<li><I>C Parsing</I>. Provided GDT archives have been updated to include new ProgramArchitecture settings for processor, data organization, and endianess. (GP-1377)</li>
|
||||
<li><I>C Parsing</I>. Provided GDT archives have been updated to include new ProgramArchitecture settings for processor, data organization, and endianness. (GP-1377)</li>
|
||||
<li><I>CParser</I>. Removed unnecessary <span class="gcode">-D</span> defines related to wchar_t from CParser <span class="gcode">prf</span> files and GDT parsing scripts. (GP-3294, Issue #5196)</li>
|
||||
<li><I>Data Types</I>. Function definitions can now be applied from selected Category instead of only from an entire Archive. (GP-199)</li>
|
||||
<li><I>Data Types</I>. Changed Structure/Union editor to show numbers in hex format by default. Also added <span class="gcode">Shift-H</span> keybinding action for toggling hex/decimal view. (GP-2943)</li>
|
||||
@ -1828,7 +2000,7 @@
|
||||
<li><I>Data Types</I>. Fixed a NullPointerException that occurred when trying to edit a function datatype in a datatype archive when there was no open program in the tool. (GP-356, Issue #2407)</li>
|
||||
<li><I>Data Types</I>. Corrected the retention of datatype archive search paths, which did not properly remember disabled paths. (GP-639)</li>
|
||||
<li><I>Data Types</I>. Fixed potential deadlock encountered when working with the DataTypes tree. (GP-774, Issue #2832)</li>
|
||||
<li><I>Decompiler</I>. Fixed endianess issue for joined, two-register returns of <code>longlong</code> values for MIPS 32-bit little endian variants. (GP-513)</li>
|
||||
<li><I>Decompiler</I>. Fixed endianness issue for joined, two-register returns of <code>longlong</code> values for MIPS 32-bit little endian variants. (GP-513)</li>
|
||||
<li><I>Decompiler</I>. The Decompiler no longer emits comments in the middle of conditional expressions. (GP-621, Issue #1670)</li>
|
||||
<li><I>Decompiler</I>. Fixed <code>Redefinition of structure...</code> exceptions in the Decompiler caused by a PNG Image and other opaque datatypes. (GP-820, Issue #2734)</li>
|
||||
<li><I>Decompiler</I>. Fixed infinite loop in the Decompiler when analyzing return values. (GP-821, Issue #2851)</li>
|
||||
@ -1989,7 +2161,7 @@
|
||||
<li><I>Decompiler</I>. Fixed issue with the Auto Create/Fill Structure command that caused it to silently miss some pointer accesses. (GP-344)</li>
|
||||
<li><I>Decompiler</I>. Jump table recovery now takes into account encoded bits, like ARM/THUMB mode transition, that may be present in address tables. (GP-387, Issue #2420)</li>
|
||||
<li><I>Decompiler</I>. Fixed a bug in the Decompiler <B>renaming</B> action when applied to function references. (GP-477, Issue #2415)</li>
|
||||
<li><I>Decompiler</I>. Corrected 8-byte return value storage specification in compiler-spec affecting <code>longlong</code> and <code>double</code> return values. Endianess ordering of <code>r0</code>/<code>r1</code> was incorrect. (GP-512, Issue #2547)</li>
|
||||
<li><I>Decompiler</I>. Corrected 8-byte return value storage specification in compiler-spec affecting <code>longlong</code> and <code>double</code> return values. Endianness ordering of <code>r0</code>/<code>r1</code> was incorrect. (GP-512, Issue #2547)</li>
|
||||
<li><I>Graphing</I>. Fixed the Function Graph's <B>drag-to-select-nodes</B> feature. (GP-430)</li>
|
||||
<li><I>Graphing</I>. Fixed issue where the graph in the satellite view is sometimes truncated. (GP-469)</li>
|
||||
<li><I>Graphing</I>. Fixed a stack trace issue caused by reusing a graph display window to show a graph that is larger than is allowed. (GP-492)</li>
|
||||
|
@ -4,15 +4,17 @@
|
||||
<HEAD>
|
||||
<TITLE> Ghidra What's New</TITLE>
|
||||
<STYLE type="text/css" name="text/css">
|
||||
li { font-family:times new roman; font-size:14pt; font-family:times new roman; font-size:14pt; margin-bottom: 8px; }
|
||||
h1 { color:#000080; font-family:times new roman; font-size:28pt; font-style:italic; font-weight:bold; text-align:center; color:#000080; font-family:times new roman; }
|
||||
h2 { padding-top:10px; color:#984c4c; font-family:times new roman; color:#984c4c; font-family:times new roman; font-size:18pt; font-weight:bold; }
|
||||
h3 { margin-left:40px; padding-top:10px; font-family:times new roman; font-family:times new roman; font-size:14pt; font-weight:bold; }
|
||||
h4 { margin-left:40px; padding-top:10px; font-family:times new roman; font-family:times new roman; font-size:14pt; font-weight:bold; }
|
||||
p { margin-left:40px; font-family:times new roman; font-size:14pt; }
|
||||
table, th, td { border: 1px solid black; border-collapse: collapse; font-size:10pt; }
|
||||
td { font-family:times new roman; font-size:14pt; padding-left:10px; padding-right:10px; text-align:left; vertical-align:top; }
|
||||
th { font-family:times new roman; font-size:14pt; font-weight:bold; padding-left:10px; padding-right:10px; text-align:left; }
|
||||
body { margin-bottom: 50px; margin-left: 10px; margin-right: 10px; margin-top: 10px; } /* some padding to improve readability */
|
||||
li { font-family:times new roman; font-size:14pt; margin-bottom: 8px; }
|
||||
h1 { color:#000080; font-family:times new roman; font-size:36pt; font-style:italic; font-weight:bold; text-align:center; }
|
||||
h2 { margin: 10px; margin-top: 20px; color:#984c4c; font-family:times new roman; font-size:18pt; font-weight:bold; }
|
||||
h3 { margin-left: 10px; margin-top: 20px; color:#0000ff; font-family:times new roman; `font-size:14pt; font-weight:bold; }
|
||||
h4 { margin-left: 10px; margin-top: 20px; font-family:times new roman; font-size:14pt; font-style:italic; }
|
||||
p { margin-left: 40px; font-family:times new roman; font-size:14pt; }
|
||||
blockquote p { margin-left: 10px; }
|
||||
table { margin-left: 20px; margin-top: 10px; width: 80%;}
|
||||
td { font-family:times new roman; font-size:14pt; vertical-align: top; }
|
||||
th { font-family:times new roman; font-size:14pt; font-weight:bold; background-color: #EDF3FE; }
|
||||
code { color:black; font-family:courier new; font-size: 12pt; }
|
||||
span.code { font-family:courier new font-size: 14pt; color:#000000; }
|
||||
.gcode { font-family: courier new; font-weight: bold; font-size: 85%; }
|
||||
@ -45,155 +47,188 @@
|
||||
</P>
|
||||
<hr>
|
||||
|
||||
<H1>What's New in Ghidra 11.0</H1>
|
||||
<H1>What's New in Ghidra 11.1</H1>
|
||||
|
||||
<H2>The not-so-fine print: Please Read!</H2>
|
||||
|
||||
<P>This release includes new features, enhancements, performance improvements, quite a few bug fixes, and many pull-request
|
||||
contributions. Thanks to all those who have contributed their time, thoughts, and code. The Ghidra user community thanks you too!</P>
|
||||
|
||||
<P>Ghidra 11.0 is fully backward compatible with project data from previous releases.
|
||||
However, programs and data type archives which are created or modified in 11.0 will not be useable by an earlier Ghidra version. </P>
|
||||
<P>Ghidra 11.x is fully backward compatible with project data from previous releases.
|
||||
However, programs and data type archives which are created or modified in 11.x will not be usable by an earlier Ghidra version. </P>
|
||||
|
||||
<P>This distribution requires JDK 17 to run, and provides Linux x86-64, Windows x86-64, and macOS x86-64 native components.
|
||||
If you have another platform or wish to use a newer JDK, please see the
|
||||
<a href="InstallationGuide.html">Ghidra Installation Guide</a> for additional information.</P>
|
||||
<P>This distribution requires at minimum JDK 17 to run, but can also run under JDK 21.</P>
|
||||
|
||||
<P>NOTE: Ghidra Server: The Ghidra 11.0 server is compatible with Ghidra 9.2 and later Ghidra clients. Ghidra 11.0
|
||||
<P>NOTE: Each build distribution will include native components (e.g., decompiler) for at least one platform (e.g., Windows x86-64).
|
||||
If you have another platform that is not included in the build distribution, you can build
|
||||
native components for your platform directly from the distribution.
|
||||
See the <a href="InstallationGuide.html">Ghidra Installation Guide</a> for additional information.
|
||||
Users running with older shared libraries and operating systems (e.g., CentOS 7.x) may also run into
|
||||
compatibility errors when launching native executables such as the Decompiler and GNU Demangler which
|
||||
may necessitate a rebuild of native components.</P>
|
||||
|
||||
<P>IMPORTANT: To use the Debugger, you will need Python3 (3.7 to 3.12 supported) installed on your system.</P>
|
||||
|
||||
<P>NOTE: Ghidra Server: The Ghidra 11.x server is compatible with Ghidra 9.2 and later Ghidra clients. Ghidra 11.x
|
||||
clients are compatible with all 10.x and 9.x servers. Although, due to potential Java version differences, it is recommended
|
||||
that Ghidra Server installations older than 10.2 be upgraded. Those using 10.2 and newer should not need a server upgrade.</P>
|
||||
|
||||
<P>NOTE: Any programs imported with a Ghidra beta version or code built directly from source code outside of a release tag may not be compatible,
|
||||
and may have flaws that won't be corrected by using this new release. Any programs analyzed from a beta or other local master source build should be considered
|
||||
experimental and re-imported and analyzed with a release version. Programs imported with previous release versions should upgrade correctly through various
|
||||
automatic upgrade mechanisms. Any program you will continue to reverse engineer should be imported fresh with a release version or a build you trust with the
|
||||
latest code fixes.</P>
|
||||
|
||||
|
||||
|
||||
<H2>BSim </H2>
|
||||
experimental and re-imported and analyzed with a release version.</P>
|
||||
|
||||
<P>A major new feature called BSim has been added. BSim can find structurally similar functions in (potentially large) collections of binaries or object files.
|
||||
BSim is based on Ghidra's decompiler and can find matches across compilers used, architectures, and/or small changes to source code.</P>
|
||||
<P>As you've reverse engineered software, you've likely asked the following questions:</P>
|
||||
<P>Programs imported with previous release versions should upgrade correctly through various
|
||||
automatic upgrade mechanisms. However, there may be improvements or bug fixes in the import and analysis process that will provide better results than prior
|
||||
Ghidra versions. You might consider comparing a fresh import of any program you will continue to reverse engineer to see if the latest Ghidra
|
||||
provides better results.</P>
|
||||
|
||||
<UL style="padding-left:100px">
|
||||
<LI>Which libraries were statically linked into this executable, and possibly what version of the library?</LI>
|
||||
<LI>Does this executable share some code with another executable that I've analyzed?</LI>
|
||||
<LI>What are the differences between version 1 and version 2 of a given executable?</LI>
|
||||
<LI>Does this executable share code with another executable in a large collection of binaries?</LI>
|
||||
<LI>Was this function pulled from an open-source library?</LI>
|
||||
<H2>Debugger </H2>
|
||||
|
||||
<P><span class="gtitle">ATTENTION:</span> Please delete and re-import the default Debugger tool!</P>
|
||||
|
||||
<P> We are introducing a new debugger connection system called Trace RMI. This is replacing the older system,
|
||||
which we are calling the Recorder system.</P>
|
||||
|
||||
<P>The most noticeable difference will be a new menu for launching targets. It is very similar to the previous system, but with some key differences:</P>
|
||||
<BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI>Connection and launching are no longer separated into two different configuration panels. There is one panel to launch your target.</LI>
|
||||
<LI>Ghidra will no longer attempt to launch blindly with defaults. The first time you launch a program, you must select a launcher and configure it.</LI>
|
||||
<LI>After the initial launch you can re-launch with a previous configuration, without requiring a prompt.</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P>BSim is intended to help with these questions (and others) by providing a way to search collections of binaries for similar, but not necessarily identical, functions.</P>
|
||||
<P>The next most noticeable difference will be the replacement of the Interpreter window with the Terminal window. This is a proper VT-100
|
||||
terminal emulator, so the experience will be much like, if not identical to, how you'd debug in a plain terminal, except embedded into and integrated with Ghidra.
|
||||
Some notable improvements that brings:</P>
|
||||
<BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI>Tab completion, history, etc., should all work as implemented by the connected debugger's command-line interface.</LI>
|
||||
<LI>When the target is running, it has proper I/O in that terminal.</LI>
|
||||
<LI>If connecting goes poorly for some reason, the debugger's command-line interface is likely still operational.</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P>BSim can compare functions within a binary, within a collection of binaries or object files in a project on a local system, or within a large collection of binaries
|
||||
utilizing a PostgreSQL or an Elasticsearch server. Using BSim locally does not require setting up a PostgreSQL or Elastic server or having administrator access.</P>
|
||||
<P>You may also notice the replacement of the Debugger Targets window with the Connection Manager window, and the replacement
|
||||
of the Objects window with the Model window. These are operationally very similar to their previous counterparts.</P>
|
||||
|
||||
<P><span class="gtitle">For Power Users:</span> The launchers are just shell scripts on Linux and macOS, and batch files on Windows. We have provided plugins
|
||||
for integrating with GDB, LLDB, and the Windows Debugger. So long as your target works with one of these debuggers, orchestrating
|
||||
another kind of target is mostly a matter of creating a new shell script. This is usually accomplished by using the most similar
|
||||
one as a template and then trying it out in Ghidra. When errors occur, Ghidra will inform you of what progress it made before it
|
||||
failed, and the Terminal should display any error messages produced by your script.</P>
|
||||
|
||||
<P><span class="gtitle">For Developers:</span> Developers may notice that debugger integration is now all done using Python 3.
|
||||
We have specified a new protocol we call Trace RMI, which provides client access to Ghidra's trace databases over TCP.
|
||||
It uses protobuf and is substantially simpler than the previous GADP protocol. We have provided the client implementation in
|
||||
Python 3. Existing integrations can be fairly easily extended, if necessary. For example, see the support for Wine we included in our GDB plugin.</P>
|
||||
|
||||
<P>If you wish to integrate a completely new debugger, and it has a Python 3 API, then things are relatively straightforward, so long as
|
||||
the debugger provides the events and information that Ghidra expects. Use an existing plugin as a template or reference and have fun.
|
||||
If the new debugger does not have Python 3 bindings, the protobuf specification is available, so the client can be ported, if necessary.</P>
|
||||
|
||||
<P><span class="gtitle">IMPORTANT:</span> To use the new Trace RMI system, you will need Python3 (3.7 to 3.12 supported) installed on your system.
|
||||
Additional setup may be required for each type of debug connection. Press <span class="gtitle">F1</span> in the debug connector's launch dialog
|
||||
for more information.</P>
|
||||
|
||||
<P>There is a BSim tutorial that walks through use of BSim locally. Using BSim locally and the tutorial is the best way to try out BSim before deciding if you need to set up a server.</P>
|
||||
<P>Overall, we believe this a substantially more approachable system than our previous DebuggerObjectModel SPI used in the Recorder system.</P>
|
||||
|
||||
<H2>GhidraGo </H2>
|
||||
|
||||
<P>GhidraGo is an experimental feature that adds integration support for Ghidra URL's and Ghidra Tools. The main use of GhidraGo is embedding hyperlinks within web pages
|
||||
to pre-ingested programs within a Ghidra multi-user repository. Clicking on the hyperlink causes Ghidra to display the previously ingested program.
|
||||
No data other than the Ghidra URL is transferred to Ghidra, and no socket is open within Ghidra listening for commands. GhidraGo must be enabled by
|
||||
installing a plugin in the Ghidra project manager, and must also be configured as a protocol handler in your web browser. GhidraGo is not setup or enabled by default.
|
||||
For details on setting up GhidraGo, please see the included <span class="gtitle">GhidraGoREADME.html</span> or search for GhidraGo within help.
|
||||
<H2>GhidraGo </H2>
|
||||
|
||||
<P>GhidraGo is an experimental feature that adds integration support for Ghidra URL's and Ghidra Tools. GhidraGO can now process GhidraURL's that
|
||||
locate folders within a project instead of only programs. For example a remote GhidraURL locating a project folder will open a read only view of
|
||||
the repository in the front end tool and select the folder from the URL. If the GhidraURL refers to a folder in the currently open
|
||||
active project, then the folder is selected within the active project's view instead of a read only view.
|
||||
</P>
|
||||
|
||||
|
||||
<H2>PDB </H2>
|
||||
<P>The PDB data type processing changes from release 11.0 have been further enhanced, simplifying the processing model and reducing the number of datatype
|
||||
conflicts. The algorithm for choosing the primary symbol at an address has been improved to provide the richest possible information. The PDB Universal
|
||||
Analyzer has been split into multiple analyzers so that PDB function processing can follow interim analyzers that specialize in finding code.
|
||||
Lastly, the Load PDB Task has been improved to schedule appropriate follow-on analyzers that are selected in the Analysis Options.</P>
|
||||
|
||||
<H2>Version Tracking </H2>
|
||||
|
||||
<P>Auto Version Tracking has been sped up, made customizable, and improved to find more matches. The mechanism to identify good matches from duplicate match
|
||||
sets has been improved and sped up. Implied matches are now created and will be applied if the vote minimum and conflict maximum limits are met. In addition,
|
||||
the ability to choose which correlators are run as well as setting the options of most correlators has been added. The Auto Version Tracking script has been
|
||||
updated to prompt for all options in a single dialog. The script now works in headless mode and an example prescript to allow
|
||||
setting of options in headless mode is included.</P>
|
||||
|
||||
<P>Version Tracking can also use the new BSim function matching capability in a new correlator called the BSim Correlator. Auto Version Tracking does not use the new BSim Correlator yet.</P>
|
||||
<P> Version Tracking Session files may now be added to a shared project repository. Once a version tracking file has been checked in to a project,
|
||||
it must be checked out for exclusive access. For more information, see help found in the Version Tracking's
|
||||
Session Wizard help for more information.</P>
|
||||
|
||||
<H2>Function Comparison Window</H2>
|
||||
<P>The function comparison window, used by Version Tracking and BSim, has been overhauled a bit:
|
||||
|
||||
<UL style="padding-left:100px">
|
||||
|
||||
<LI>A help topic for Function Comparison has been added.</LI>
|
||||
<LI>Token matching, scrolling to matches, and difference highlighting is much improved using
|
||||
an algorithm based on BSim function matching. The colors used for the token matching highlights are configurable.</LI>
|
||||
|
||||
<LI>From the comparison window, users can click on called functions that have corresponding matches to bring up a new function comparison window showing those functions.
|
||||
The action is currently not available within version tracking.</LI>
|
||||
|
||||
<LI>The functions can be displayed side by side vertically or horizontally.</LI>
|
||||
|
||||
<LI>The function signature from a function in the function comparison window can be applied to include name only, a skeleton signature, or the entire signature
|
||||
including all referenced data types. Applying only the skeleton function signature can be useful if there is any question of differences in the
|
||||
data structure composition used by the function signature between the two programs.</LI>
|
||||
</UL>
|
||||
|
||||
<H2>Scripting</H2>
|
||||
|
||||
<P>A new multi-option script dialog has been added to the scripting API that can present and get all user inputs a script needs in one dialog.
|
||||
The <span class="gcode">askValues()</span> scripting method replaces the cumbersome process of prompting the user for each input separately.</P>
|
||||
|
||||
<H2>Rust</H2>
|
||||
<P>NOTE: Prior to adding a pre-existing VT Session to a shared project repository, it is highly recommended that it first be re-opened
|
||||
and saved. This will upgrade the VT Session internal version to prevent its use with older versions of Ghidra which will not respect
|
||||
the exclusive checkout requirement.</P>
|
||||
|
||||
<P>Initial support for Rust compiled binaries, mainly demangling of Rust method names and Rust in DWARF information, has been added. In addition, Rust strings are marked up so that the
|
||||
decompiler will display Rust strings correctly. There is more work to do, especially with mapping Rust parameter passing. Custom storage may be required in some instances.</P>
|
||||
<H2>Mach-O Improvements</H2>
|
||||
<P>Mach-O support continues to improve, adding support for new features as well as filling in some gaps that existed for several years.
|
||||
The latest dyld_shared_cache files use a new format for pointer fixups, which Ghidra now supports. A new GFileSystem has also been
|
||||
implemented to import and/or extract individual Mach-O binaries from Mach-O "file sets" (i.e., kernelcache). A second new GFileSystem
|
||||
has been added which can extract Apple LZFSE-compressed files. Other improvements have also been made to provide more complete markup of Mach-O load commands.</P>
|
||||
|
||||
<H2>Golang</H2>
|
||||
<P>Golang reverse engineering within Ghidra is much improved by:
|
||||
<UL style="padding-left:100px">
|
||||
<LI>A new <span class="gtitle">Golang String Analyzer</span> which finds and marks up Golang strings so they display correctly in the decompiler</LI>
|
||||
<LI>Type and interface method markup improvements</LI>
|
||||
<LI>Better function parameter recovery</LI>
|
||||
<LI>Using package information to organize type and symbol elements into namespaces</LI>
|
||||
<LI>Using run time type information to override the types of objects that are created by calls to malloc-like built-in functions</LI>
|
||||
<H2>Swift </H2>
|
||||
<P>Initial support for binaries written in the Swift Programming Language has been added. The new support relies on the native Swift demangler being
|
||||
present on the user's system. Swift is automatically bundled with XCode on macOS, and can be optionally installed on Windows and Linux.
|
||||
See the "Demangler Swift" analyzer options for more information. Type information gathered from the demangled Swift symbol names is used to
|
||||
create corresponding Ghidra data types. This currently works for Swift primitives and structures, but more work needs to be done to include
|
||||
classes and other advanced data types. Swift-specific calling conventions are also applied to demangled Swift functions.</P>
|
||||
|
||||
<H2>Usability </H2>
|
||||
|
||||
<P>There have been many improvements to keyboard only actions and navigation in Ghidra. These changes will be welcome for those who
|
||||
prefer to use the keyboard as much as possible and those needing better accessibility. Improvements include:</P>
|
||||
<BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI>Standard keyboard navigation should now work in most component windows and dialogs. In general, <span class="gtitle">Tab</span> and <span class="gtitle"><CTRL> Tab</span> will
|
||||
move focus to the next focusable component and <span class="gtitle"><SHIFT> Tab</span> and <span class="gtitle"><CTRL><SHIFT> Tab</span> will move to the
|
||||
previous focusable component. <span class="gtitle">Tab</span> and <span class="gtitle"><SHIFT> Tab</span> do not always work as some components use those keys internally, but
|
||||
<span class="gtitle"><CTRL> Tab,</span> and <span class="gtitle"><SHIFT><CTRL> Tab</span> should work universally.</LI>
|
||||
<LI>Ghidra now provides some convenient keyboard shortcut actions for transferring focus:</LI>
|
||||
<UL>
|
||||
<LI><span class="gtitle"><CTRL> F3</span> - Transfers focus to the next window or dialog.</LI>
|
||||
<LI><span class="gtitle"><CTRL><SHIFT> F3</span> - Transfers focus to the previous window or dialog.</LI>
|
||||
<LI><span class="gtitle"><CTRL> J</span> - Transfers focus to the next titled dockable component (titled windows).</LI>
|
||||
<LI><span class="gtitle"><CTRL><SHIFT> J</span> - Transfers focus to the previous titled dockable component.</LI>
|
||||
</UL>
|
||||
<LI>All actions can now be accessed via a searchable dialog.</LI>
|
||||
<UL>
|
||||
<LI>Pressing <span class="gtitle"><CTRL> 3</span> will bring up the actions dialog with the local toolbar, popup and keyboard actions.</LI>
|
||||
<LI>Pressing <span class="gtitle"><CTRL> 3</span> a second time will add in all the global actions. </LI>
|
||||
<LI>Pressing <span class="gtitle"><CTRL> 3</span> a third time will add in the disabled actions as well.</LI>
|
||||
<LI>The actions dialog was specifically designed to be easy to use without a mouse. Typing will filter the actions list and the
|
||||
arrow keys allow you to select an action and enter will invoke the selected action </LI>
|
||||
</UL>
|
||||
</UL>
|
||||
</P>
|
||||
|
||||
<H2>Search for Encoded Strings</H2>
|
||||
|
||||
<P>A new action in the Search menu, <span class="gtitle">Search -> For Encoded Strings...</span>, can help find and create strings encoded in alternate character sets and alphabets.
|
||||
Valid strings are based on runs of bytes that would be valid in a particular character set and alphabet. There are currently no additional models for defining valid words within other languages.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Import / Export</H2>
|
||||
<P>The <span class="gtitle">CaRT</span> file format is now supported. The <span class="gtitle">CaRT</span> format is used to store and transfer malware along with metadata about
|
||||
the malware in a neutered form such that it cannot be executed. It is encrypted so anti-virus software will not flag the file under analysis.</P>
|
||||
|
||||
<P>Headless importing of binaries from container files, such as .zip files, with multiple embedded files is now possible. This includes loading referenced .dll and .so files also
|
||||
found within the container file.</P>
|
||||
|
||||
<P>The Headless Analyzer can now recurse into supported GFileSystem container files when a recursion depth of one or more is specified on the command line.</P>
|
||||
|
||||
<H2>Mach-O Improvements</H2>
|
||||
|
||||
<P>Support for the Mach-O binary file format has continued to receive updates. Improvements have been made to library linking and loading, as well as thunk creation. Additionally, dyld_shared_cache
|
||||
components extracted from Ghidra's DyldCacheFileSystem can now be added together on-demand with the Add To Program feature. Broken references can be automatically resolved by right-clicking on them
|
||||
and clicking <span class="gtitle">References -> Add To Program</span>.</P>
|
||||
|
||||
<H2>PDB</H2>
|
||||
<H2>Other Improvements </H2>
|
||||
|
||||
<P>The PDB data types processing has been changed to use a resolve-as-you-go model. The change eliminates the dependency graph and reduces the memory footprint required to load all
|
||||
PDB types. The change allows larger PDB's to load successfully and improves the accuracy of some data types.</P>
|
||||
|
||||
<H2>Overlays with Multiple Memory Blocks</H2>
|
||||
|
||||
<P> Overlay spaces now support multiple memory blocks in the same overlay. After creating the initial memory
|
||||
block as an overlay, the new overlay memory space will become available when adding new memory blocks. All overlay memory blocks can
|
||||
be manipulated in the same way as normal memory blocks. The new feature is useful when analyzing binaries
|
||||
meant to run on multiple processors with tasks running on each processor in their own overlapped virtual memory space such as an RTOS.</P>
|
||||
|
||||
<H2>Processors</H2>
|
||||
<P>Support for the Loongson processor architecture has been added. All known instructions should disassemble. However semantics for a large number of instructions use pseudoOp calls currently.</P>
|
||||
|
||||
<P>Support for the <span class="gtitle">SquashFS</span> filesystem has been added.</P>
|
||||
|
||||
<P>A new wildcard assembler API has been added that can generate all possible variants of an instruction with a variety of wildcards for operands.
|
||||
Two new scripts, <span class="gtitle">FindInstructionWithWildcard</span> and <span class="gtitle">WildSleighAssemblerInfo</span>, demonstrate how to use the API.
|
||||
For more information, see help and search for <span class="gtitle">Wildcard Assembler</span>.
|
||||
|
||||
<P>A new <span class="gtitle">Runtime Information</span> dialog has replaced the Show VM Memory dialog. The dialog contains more information
|
||||
which can aid in debugging, including version information, classpath, defined properties, environment variables, and more.</P>
|
||||
|
||||
<P>The GhidraDev Eclipse plugin has a new wizard for importing an existing Ghidra module source directory. This will work best with Ghidra module projects
|
||||
created against Ghidra 11.1 or later.</P>
|
||||
|
||||
<P>Finding references to fields within a structure has been greatly improved. Previously many references to the field would be missed if they occurred within
|
||||
functions calling external functions using the structure, or when the field was used only in local variables dynamically generated by
|
||||
the decompiler.</P>
|
||||
|
||||
<P>Golang versions 17 thru 22 are now supported.</P>
|
||||
|
||||
<P>DWARF5 debug format is now supported. In addition, DWARF line number information processing has been incorporated into the base DWARF analyzer and the
|
||||
separate DWARF line number analyzer has been removed.</P>
|
||||
|
||||
<H2>Additional Bug Fixes and Enhancements</H2>
|
||||
<P> Numerous other new features, improvements, and bug fixes are fully listed in the <a href="ChangeHistory.html">ChangeHistory</a> file.</P>
|
||||
|
||||
<BR>
|
||||
<P align="center">
|
||||
<B><a href="https://www.nsa.gov/ghidra"> https://www.nsa.gov/ghidra</a></B>
|
||||
</P>
|
||||
|
||||
<div align="center">
|
||||
<B><a href="https://www.nsa.gov/ghidra"> https://www.nsa.gov/ghidra</a></B>
|
||||
<BR><BR>
|
||||
</div>
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
@ -0,0 +1,5 @@
|
||||
MODULE FILE LICENSE: pypkg/dist/capstone-5.0.1-py3-none-win_amd64.whl BSD-3-CAPSTONE
|
||||
MODULE FILE LICENSE: pypkg/dist/comtypes-1.4.1-py3-none-any.whl MIT
|
||||
MODULE FILE LICENSE: pypkg/dist/Pybag-2.2.10-py3-none-any.whl MIT
|
||||
MODULE FILE LICENSE: pypkg/dist/pywin32-306-cp312-cp312-win_amd64.whl Python Software Foundation License
|
||||
|
@ -36,6 +36,69 @@ dependencies {
|
||||
testImplementation project(path: ":Debugger-gadp", configuration: 'testArtifacts')
|
||||
}
|
||||
|
||||
ext.tlb = file("build/os/win_x86_64/dbgmodel.tlb")
|
||||
|
||||
if ("win_x86_64".equals(getCurrentPlatformName())) {
|
||||
|
||||
String makeName = "win_x86_64DbgmodelTlbMake"
|
||||
task(type: Exec, makeName) {
|
||||
ext.tmpBatch = file("build/buildTlb.bat")
|
||||
ext.idl = file("src/main/py/src/ghidradbg/dbgmodel/DbgModel.idl")
|
||||
inputs.file(idl)
|
||||
outputs.file(tlb)
|
||||
|
||||
doFirst {
|
||||
file(tlb).parentFile.mkdirs()
|
||||
def midlCmd = "midl /tlb ${tlb} ${idl}"
|
||||
println "Executing: " + midlCmd
|
||||
|
||||
tmpBatch.withWriter { out ->
|
||||
out.println "call " + VISUAL_STUDIO_VCVARS_CMD
|
||||
out.println midlCmd
|
||||
}
|
||||
}
|
||||
doLast {
|
||||
assert file(tlb).exists() : "Failed to build dbgmodel.tlb"
|
||||
}
|
||||
|
||||
commandLine "cmd", "/c", tmpBatch
|
||||
}
|
||||
|
||||
tasks.assemblePyPackage {
|
||||
from(tasks."$makeName") {
|
||||
into("src/ghidradbg/dbgmodel/tlb")
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (file(tlb).exists()) {
|
||||
// required for multi-platform build
|
||||
tasks.assemblePyPackage {
|
||||
from(tlb) {
|
||||
println "Copying existing tlb build artifact: " + tlb
|
||||
into("src/ghidradbg/dbgmodel/tlb")
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
def prebuiltTlb = new File(rootProject.BIN_REPO + '/' + getGhidraRelativePath(project) + "/os/win_x86_64/dbgmodel.tlb")
|
||||
if (prebuiltTlb.exists()) {
|
||||
println "Copying prebuilt dbgmodel.tlb"
|
||||
tasks.assemblePyPackage {
|
||||
from(rootProject.BIN_REPO + '/' + getGhidraRelativePath(project) + "/os/win_x86_64/dbgmodel.tlb") {
|
||||
into("src/ghidradbg/dbgmodel/tlb")
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
println "WARNING: dbgmodel.tlb omitted from ghidradbg python package"
|
||||
}
|
||||
}
|
||||
|
||||
distributePyDep("Pybag-2.2.10-py3-none-any.whl")
|
||||
distributePyDep("capstone-5.0.1-py3-none-win_amd64.whl")
|
||||
distributePyDep("comtypes-1.4.1-py3-none-any.whl")
|
||||
distributePyDep("pywin32-306-cp312-cp312-win_amd64.whl")
|
||||
|
||||
tasks.nodepJar {
|
||||
manifest {
|
||||
attributes['Main-Class'] = 'agent.dbgeng.gadp.DbgEngGadpServer'
|
||||
|
@ -1,8 +1,13 @@
|
||||
##VERSION: 2.0
|
||||
##MODULE IP: Apache License 2.0
|
||||
##MODULE IP: MIT
|
||||
Module.manifest||GHIDRA||||END|
|
||||
data/debugger-launchers/local-dbgeng.bat||GHIDRA||||END|
|
||||
data/debugger-launchers/local-ttd.bat||GHIDRA||||END|
|
||||
src/main/py/LICENSE||GHIDRA||||END|
|
||||
src/main/py/MANIFEST.in||GHIDRA||||END|
|
||||
src/main/py/README.md||GHIDRA||||END|
|
||||
src/main/py/pyproject.toml||GHIDRA||||END|
|
||||
src/main/py/src/ghidradbg/dbgmodel/DbgModel.idl||GHIDRA||||END|
|
||||
src/main/py/src/ghidradbg/schema.xml||GHIDRA||||END|
|
||||
src/main/py/src/ghidrattd/schema.xml||GHIDRA||||END|
|
||||
|
@ -1,34 +1,21 @@
|
||||
::@title dbgeng
|
||||
::@desc <html><body width="300px">
|
||||
::@desc <h3>Launch with <tt>dbgeng</tt> (in a Python interpreter)</h3>
|
||||
::@desc <p>This will launch the target on the local machine using <tt>dbgeng.dll</tt>. Typically,
|
||||
::@desc Windows systems have this library pre-installed, but it may have limitations, e.g., you
|
||||
::@desc cannot use <tt>.server</tt>. For the full capabilities, you must install WinDbg.</p>
|
||||
::@desc <p>Furthermore, you must have Python 3 installed on your system, and it must have the
|
||||
::@desc <tt>pybag</tt> and <tt>protobuf</tt> packages installed.</p>
|
||||
::@desc <p>
|
||||
::@desc This will launch the target on the local machine using <tt>dbgeng.dll</tt>.
|
||||
::@desc For setup instructions, press <b>F1</b>.
|
||||
::@desc </p>
|
||||
::@desc </body></html>
|
||||
::@menu-group local
|
||||
::@icon icon.debugger
|
||||
::@help TraceRmiLauncherServicePlugin#dbgeng
|
||||
::@env OPT_PYTHON_EXE:str="python" "Path to python" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH."
|
||||
::@env OPT_PYTHON_EXE:file="python" "Python command" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH."
|
||||
:: Use env instead of args, because "all args except first" is terrible to implement in batch
|
||||
::@env OPT_TARGET_IMG:str="" "Image" "The target binary executable image"
|
||||
::@env OPT_TARGET_IMG:file="" "Image" "The target binary executable image"
|
||||
::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
|
||||
::@env OPT_USE_DBGMODEL:bool=true "Use dbgmodel" "Load and use dbgmodel.dll if it is available."
|
||||
::@env WINDBG_DIR:dir="" "Path to dbgeng.dll directory" "Path containing dbgeng and associated DLLS (if not Windows Kits)."
|
||||
|
||||
@echo off
|
||||
|
||||
if exist "%GHIDRA_HOME%\ghidra\.git\" (
|
||||
set PYTHONPATH=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-agent-dbgeng\build\pypkg\src;%PYTHONPATH%
|
||||
set PYTHONPATH=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src;%PYTHONPATH%
|
||||
) else if exist "%GHIDRA_HOME%\.git\" (
|
||||
set PYTHONPATH=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-dbgeng\build\pypkg\src;%PYTHONPATH%
|
||||
set PYTHONPATH=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src;%PYTHONPATH%
|
||||
) else (
|
||||
set PYTHONPATH=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-dbgeng\pypkg\src;%PYTHONPATH%
|
||||
set PYTHONPATH=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src;%PYTHONPATH%
|
||||
)
|
||||
|
||||
echo PYTHONPATH is %PYTHONPATH%
|
||||
echo OPT_TARGET_IMG is [%OPT_TARGET_IMG%]
|
||||
|
||||
"%OPT_PYTHON_EXE%" -i ..\support\local-dbgeng.py
|
||||
|
@ -0,0 +1,21 @@
|
||||
::@title ttd
|
||||
::@desc <html><body width="300px">
|
||||
::@desc <h3>Launch with <tt>ttd</tt> (in a Python interpreter)</h3>
|
||||
::@desc <p>
|
||||
::@desc This will launch the target on the local machine for time-travel debugging.
|
||||
::@desc For setup instructions, press <b>F1</b>.
|
||||
::@desc </p>
|
||||
::@desc </body></html>
|
||||
::@menu-group local
|
||||
::@icon icon.debugger
|
||||
::@help TraceRmiLauncherServicePlugin#dbgeng_ttd
|
||||
::@env OPT_PYTHON_EXE:file="python" "Python command" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH."
|
||||
:: Use env instead of args, because "all args except first" is terrible to implement in batch
|
||||
::@env OPT_TARGET_IMG:file="" "Trace (.run)" "A trace associated with the target binary executable"
|
||||
::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
|
||||
::@env OPT_USE_DBGMODEL:bool=true "Use dbgmodel" "Load and use dbgmodel.dll if it is available."
|
||||
::@env OPT_DBGMODEL_PATH:dir="" "Path to dbgeng.dll & \\ttd" "Path containing dbgeng and associated DLLS (if not Windows Kits)."
|
||||
|
||||
@echo off
|
||||
|
||||
"%OPT_PYTHON_EXE%" -i ..\support\local-ttd.py
|
@ -13,18 +13,58 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
|
||||
import os
|
||||
from ghidradbg.commands import *
|
||||
import sys
|
||||
|
||||
ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR'))
|
||||
args = os.getenv('OPT_TARGET_ARGS')
|
||||
if args:
|
||||
args = ' ' + args
|
||||
ghidra_trace_create(os.getenv('OPT_TARGET_IMG') + args, start_trace=False)
|
||||
ghidra_trace_start(os.getenv('OPT_TARGET_IMG'))
|
||||
ghidra_trace_sync_enable()
|
||||
|
||||
# TODO: HACK
|
||||
dbg().wait()
|
||||
home = os.getenv('GHIDRA_HOME')
|
||||
|
||||
repl()
|
||||
if os.path.isdir(f'{home}\\ghidra\\.git'):
|
||||
sys.path.append(
|
||||
f'{home}\\ghidra\\Ghidra\\Debug\\Debugger-agent-dbgeng\\build\\pypkg\\src')
|
||||
sys.path.append(
|
||||
f'{home}\\ghidra\\Ghidra\\Debug\\Debugger-rmi-trace\\build\\pypkg\\src')
|
||||
elif os.path.isdir(f'{home}\\.git'):
|
||||
sys.path.append(
|
||||
f'{home}\\Ghidra\\Debug\\Debugger-agent-dbgeng\\build\\pypkg\\src')
|
||||
sys.path.append(
|
||||
f'{home}\\Ghidra\\Debug\\Debugger-rmi-trace\\build\\pypkg\\src')
|
||||
else:
|
||||
sys.path.append(
|
||||
f'{home}\\Ghidra\\Debug\\Debugger-agent-dbgeng\\pypkg\\src')
|
||||
sys.path.append(f'{home}\\Ghidra\\Debug\\Debugger-rmi-trace\\pypkg\\src')
|
||||
|
||||
|
||||
def main():
|
||||
# Delay these imports until sys.path is patched
|
||||
from ghidradbg import commands as cmd
|
||||
from ghidradbg.hooks import on_stop
|
||||
from ghidradbg.util import dbg
|
||||
|
||||
# So that the user can re-enter by typing repl()
|
||||
global repl
|
||||
repl = cmd.repl
|
||||
|
||||
cmd.ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR'))
|
||||
args = os.getenv('OPT_TARGET_ARGS')
|
||||
if args:
|
||||
args = ' ' + args
|
||||
cmd.ghidra_trace_create(
|
||||
os.getenv('OPT_TARGET_IMG') + args, start_trace=False)
|
||||
|
||||
# TODO: HACK
|
||||
try:
|
||||
dbg.wait()
|
||||
except KeyboardInterrupt as ki:
|
||||
dbg.interrupt()
|
||||
|
||||
cmd.ghidra_trace_start(os.getenv('OPT_TARGET_IMG'))
|
||||
cmd.ghidra_trace_sync_enable()
|
||||
|
||||
on_stop()
|
||||
cmd.repl()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
58
Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-ttd.py
Normal file
58
Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-ttd.py
Normal 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.
|
||||
##
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
home = os.getenv('GHIDRA_HOME')
|
||||
|
||||
if os.path.isdir(f'{home}\\ghidra\\.git'):
|
||||
sys.path.append(
|
||||
f'{home}\\ghidra\\Ghidra\\Debug\\Debugger-agent-dbgeng\\build\\pypkg\\src')
|
||||
sys.path.append(
|
||||
f'{home}\\ghidra\\Ghidra\\Debug\\Debugger-rmi-trace\\build\\pypkg\\src')
|
||||
elif os.path.isdir(f'{home}\\.git'):
|
||||
sys.path.append(
|
||||
f'{home}\\Ghidra\\Debug\\Debugger-agent-dbgeng\\build\\pypkg\\src')
|
||||
sys.path.append(
|
||||
f'{home}\\Ghidra\\Debug\\Debugger-rmi-trace\\build\\pypkg\\src')
|
||||
else:
|
||||
sys.path.append(
|
||||
f'{home}\\Ghidra\\Debug\\Debugger-agent-dbgeng\\pypkg\\src')
|
||||
sys.path.append(f'{home}\\Ghidra\\Debug\\Debugger-rmi-trace\\pypkg\\src')
|
||||
|
||||
|
||||
def main():
|
||||
# Delay these imports until sys.path is patched
|
||||
from ghidrattd import commands as cmd
|
||||
from ghidrattd import hooks
|
||||
###from ghidrattd.util import dbg
|
||||
|
||||
cmd.ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR'))
|
||||
args = os.getenv('OPT_TARGET_ARGS')
|
||||
if args:
|
||||
args = ' ' + args
|
||||
cmd.ghidra_trace_create(
|
||||
os.getenv('OPT_TARGET_IMG') + args, start_trace=True)
|
||||
cmd.ghidra_trace_sync_enable()
|
||||
hooks.on_stop()
|
||||
|
||||
cmd.repl()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -17,23 +17,12 @@ package agent.dbgeng.model.iface2;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.dbgeng.dbgeng.DebugThreadId;
|
||||
import agent.dbgeng.dbgeng.DebugThreadRecord;
|
||||
import agent.dbgeng.manager.DbgEventsListenerAdapter;
|
||||
import agent.dbgeng.manager.DbgReason;
|
||||
import agent.dbgeng.manager.DbgState;
|
||||
import agent.dbgeng.manager.DbgThread;
|
||||
import agent.dbgeng.manager.impl.DbgManagerImpl;
|
||||
import agent.dbgeng.manager.impl.DbgProcessImpl;
|
||||
import agent.dbgeng.manager.impl.DbgThreadImpl;
|
||||
import agent.dbgeng.model.iface1.DbgModelSelectableObject;
|
||||
import agent.dbgeng.model.iface1.DbgModelTargetAccessConditioned;
|
||||
import agent.dbgeng.model.iface1.DbgModelTargetExecutionStateful;
|
||||
import agent.dbgeng.model.iface1.DbgModelTargetSteppable;
|
||||
import agent.dbgeng.dbgeng.*;
|
||||
import agent.dbgeng.manager.*;
|
||||
import agent.dbgeng.manager.impl.*;
|
||||
import agent.dbgeng.model.iface1.*;
|
||||
import agent.dbgeng.model.impl.DbgModelTargetStackImpl;
|
||||
import ghidra.dbg.target.TargetAggregate;
|
||||
import ghidra.dbg.target.TargetMethod;
|
||||
import ghidra.dbg.target.TargetThread;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
@ -57,7 +46,12 @@ public interface DbgModelTargetThread extends //
|
||||
DbgProcessImpl process = parentProcess == null ? null : (DbgProcessImpl) parentProcess.getProcess();
|
||||
String index = PathUtils.parseIndex(getName());
|
||||
Long tid = Long.decode(index);
|
||||
DebugThreadId id = new DebugThreadRecord(tid);
|
||||
|
||||
DebugSystemObjects so = manager.getSystemObjects();
|
||||
DebugThreadId id = so.getThreadIdBySystemId(tid.intValue());
|
||||
if (id == null) {
|
||||
id = so.getCurrentThreadId();
|
||||
}
|
||||
DbgThreadImpl thread = manager.getThreadComputeIfAbsent(id, process, tid, fire);
|
||||
return thread;
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import ghidra.dbg.target.TargetRegisterBank;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.ResyncMode;
|
||||
import ghidra.dbg.util.ConversionUtils;
|
||||
import ghidra.docking.settings.FormatSettingsDefinition;
|
||||
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "RegisterContainer",
|
||||
@ -170,7 +171,13 @@ public class DbgModelTargetRegisterContainerImpl extends DbgModelTargetObjectImp
|
||||
|
||||
private void changeAttrs(DbgModelTargetRegister reg, BigInteger value) {
|
||||
String oldval = (String) reg.getCachedAttributes().get(VALUE_ATTRIBUTE_NAME);
|
||||
String valstr = Long.toUnsignedString(value.longValue(), 16); //value.toString(16);
|
||||
int bitLength = reg.getBitLength();
|
||||
boolean negative = value.signum() < 0;
|
||||
if (negative) {
|
||||
// force use of unsigned value
|
||||
value = value.add(BigInteger.valueOf(2).pow(bitLength));
|
||||
}
|
||||
String valstr = value.toString(16);
|
||||
String newval = (value.longValue() == 0) ? reg.getName()
|
||||
: reg.getName() + " : " + valstr;
|
||||
reg.changeAttributes(List.of(), Map.of( //
|
||||
|
@ -0,0 +1 @@
|
||||
graft src
|
@ -1,8 +1,3 @@
|
||||
# Ghidra Trace RMI
|
||||
|
||||
Package for connecting dbgeng to Ghidra via Trace RMI.
|
||||
|
||||
This connector requires Pybag 2.2.8 or better:
|
||||
|
||||
https://pypi.org/project/Pybag
|
||||
https://github.com/dshikashio/Pybag
|
@ -1,10 +1,10 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "ghidradbg"
|
||||
version = "10.4"
|
||||
version = "11.1"
|
||||
authors = [
|
||||
{ name="Ghidra Development Team" },
|
||||
]
|
||||
@ -17,10 +17,16 @@ classifiers = [
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
dependencies = [
|
||||
"ghidratrace==10.4",
|
||||
"pybag>=2.2.8"
|
||||
"ghidratrace==11.1",
|
||||
"pybag>=2.2.10"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
"Homepage" = "https://github.com/NationalSecurityAgency/ghidra"
|
||||
"Bug Tracker" = "https://github.com/NationalSecurityAgency/ghidra/issues"
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
ghidradbg = ["*.tlb"]
|
||||
|
||||
[tool.setuptools]
|
||||
include-package-data = true
|
||||
|
@ -13,7 +13,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
import ctypes
|
||||
ctypes.windll.kernel32.SetErrorMode(0x0001|0x0002|0x8000)
|
||||
|
||||
from . import util, commands, methods, hooks
|
||||
# NOTE: libraries must precede EVERYTHING, esp pybag and DbgMod
|
||||
|
||||
from . import libraries, util, commands, methods, hooks
|
||||
|
@ -14,13 +14,14 @@
|
||||
# limitations under the License.
|
||||
##
|
||||
from ghidratrace.client import Address, RegVal
|
||||
|
||||
from pybag import pydbg
|
||||
|
||||
from . import util
|
||||
|
||||
|
||||
language_map = {
|
||||
'ARM': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A', 'ARM:BE:64:v8', 'ARM:LE:64:v8'],
|
||||
'AARCH64': ['AARCH64:LE:64:AppleSilicon'],
|
||||
'ARM': ['ARM:LE:32:v8'],
|
||||
'Itanium': [],
|
||||
'x86': ['x86:LE:32:default'],
|
||||
'x86_64': ['x86:LE:64:default'],
|
||||
@ -36,6 +37,10 @@ x86_compiler_map = {
|
||||
'Cygwin': 'windows',
|
||||
}
|
||||
|
||||
aarch64_compiler_map = {
|
||||
'windows': 'default',
|
||||
}
|
||||
|
||||
arm_compiler_map = {
|
||||
'windows': 'windows',
|
||||
}
|
||||
@ -45,23 +50,23 @@ compiler_map = {
|
||||
'DATA:LE:64:default': data64_compiler_map,
|
||||
'x86:LE:32:default': x86_compiler_map,
|
||||
'x86:LE:64:default': x86_compiler_map,
|
||||
'AARCH64:BE:64:v8A': arm_compiler_map,
|
||||
'AARCH64:LE:64:AppleSilicon': arm_compiler_map,
|
||||
'AARCH64:LE:64:v8A': arm_compiler_map,
|
||||
'ARM:BE:64:v8': arm_compiler_map,
|
||||
'ARM:LE:64:v8': arm_compiler_map,
|
||||
'AARCH64:LE:64:AppleSilicon': aarch64_compiler_map,
|
||||
'ARM:LE:32:v8': arm_compiler_map,
|
||||
}
|
||||
|
||||
|
||||
def get_arch():
|
||||
try:
|
||||
type = util.get_debugger()._control.GetActualProcessorType()
|
||||
type = util.dbg.get_actual_processor_type()
|
||||
except Exception:
|
||||
print("Error getting actual processor type.")
|
||||
return "Unknown"
|
||||
if type is None:
|
||||
return "x86_64"
|
||||
if type == 0x8664:
|
||||
return "x86_64"
|
||||
if type == 0xAA64:
|
||||
return "AARCH64"
|
||||
if type == 0x014c:
|
||||
return "x86"
|
||||
if type == 0x01c0:
|
||||
@ -85,10 +90,11 @@ def get_osabi():
|
||||
if not parm in ['auto', 'default']:
|
||||
return parm
|
||||
try:
|
||||
os = util.get_debugger().cmd("vertarget")
|
||||
if "Windows" not in os:
|
||||
os = util.dbg.cmd("vertarget")
|
||||
if "Windows" not in os:
|
||||
return "default"
|
||||
except Exception:
|
||||
print("Error getting target OS/ABI")
|
||||
pass
|
||||
return "windows"
|
||||
|
||||
@ -154,7 +160,8 @@ class DefaultMemoryMapper(object):
|
||||
def map_back(self, proc: int, address: Address) -> int:
|
||||
if address.space == self.defaultSpace:
|
||||
return address.offset
|
||||
raise ValueError(f"Address {address} is not in process {proc.GetProcessID()}")
|
||||
raise ValueError(
|
||||
f"Address {address} is not in process {proc.GetProcessID()}")
|
||||
|
||||
|
||||
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
|
||||
@ -179,14 +186,13 @@ class DefaultRegisterMapper(object):
|
||||
def map_name(self, proc, name):
|
||||
return name
|
||||
|
||||
|
||||
def map_value(self, proc, name, value):
|
||||
try:
|
||||
### TODO: this seems half-baked
|
||||
# TODO: this seems half-baked
|
||||
av = value.to_bytes(8, "big")
|
||||
except Exception:
|
||||
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
|
||||
.format(name, value, type(value)))
|
||||
.format(name, value, type(value)))
|
||||
return RegVal(self.map_name(proc, name), av)
|
||||
|
||||
def map_name_back(self, proc, name):
|
||||
@ -237,4 +243,3 @@ def compute_register_mapper(lang):
|
||||
if ':LE:' in lang:
|
||||
return DEFAULT_LE_REGISTER_MAPPER
|
||||
return register_mappers[lang]
|
||||
|
||||
|
@ -13,43 +13,46 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
import code
|
||||
from contextlib import contextmanager
|
||||
import inspect
|
||||
import os.path
|
||||
import socket
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
from ghidratrace import sch
|
||||
from ghidratrace.client import Client, Address, AddressRange, TraceObject
|
||||
|
||||
from pybag import pydbg, userdbg, kerneldbg
|
||||
from pybag.dbgeng import core as DbgEng
|
||||
from pybag.dbgeng import exception
|
||||
from pybag.dbgeng.win32.kernel32 import STILL_ACTIVE
|
||||
|
||||
from . import util, arch, methods, hooks
|
||||
import code
|
||||
from .dbgmodel.imodelobject import ModelObjectKind
|
||||
|
||||
|
||||
PAGE_SIZE = 4096
|
||||
|
||||
AVAILABLES_PATH = 'Available'
|
||||
AVAILABLE_KEY_PATTERN = '[{pid}]'
|
||||
AVAILABLE_PATTERN = AVAILABLES_PATH + AVAILABLE_KEY_PATTERN
|
||||
PROCESSES_PATH = 'Processes'
|
||||
PROCESSES_PATH = 'Sessions[0].Processes'
|
||||
PROCESS_KEY_PATTERN = '[{procnum}]'
|
||||
PROCESS_PATTERN = PROCESSES_PATH + PROCESS_KEY_PATTERN
|
||||
PROC_BREAKS_PATTERN = PROCESS_PATTERN + '.Breakpoints'
|
||||
PROC_BREAKS_PATTERN = PROCESS_PATTERN + '.Debug.Breakpoints'
|
||||
PROC_BREAK_KEY_PATTERN = '[{breaknum}]'
|
||||
PROC_BREAK_PATTERN = PROC_BREAKS_PATTERN + PROC_BREAK_KEY_PATTERN
|
||||
ENV_PATTERN = PROCESS_PATTERN + '.Environment'
|
||||
THREADS_PATTERN = PROCESS_PATTERN + '.Threads'
|
||||
THREAD_KEY_PATTERN = '[{tnum}]'
|
||||
THREAD_PATTERN = THREADS_PATTERN + THREAD_KEY_PATTERN
|
||||
STACK_PATTERN = THREAD_PATTERN + '.Stack'
|
||||
STACK_PATTERN = THREAD_PATTERN + '.Stack.Frames'
|
||||
FRAME_KEY_PATTERN = '[{level}]'
|
||||
FRAME_PATTERN = STACK_PATTERN + FRAME_KEY_PATTERN
|
||||
REGS_PATTERN = THREAD_PATTERN + '.Registers'
|
||||
USER_REGS_PATTERN = THREAD_PATTERN + '.Registers.User'
|
||||
MEMORY_PATTERN = PROCESS_PATTERN + '.Memory'
|
||||
REGION_KEY_PATTERN = '[{start:08x}]'
|
||||
REGION_PATTERN = MEMORY_PATTERN + REGION_KEY_PATTERN
|
||||
@ -59,6 +62,7 @@ MODULE_PATTERN = MODULES_PATTERN + MODULE_KEY_PATTERN
|
||||
SECTIONS_ADD_PATTERN = '.Sections'
|
||||
SECTION_KEY_PATTERN = '[{secname}]'
|
||||
SECTION_ADD_PATTERN = SECTIONS_ADD_PATTERN + SECTION_KEY_PATTERN
|
||||
GENERIC_KEY_PATTERN = '[{key}]'
|
||||
|
||||
# TODO: Symbols
|
||||
|
||||
@ -170,10 +174,10 @@ def ghidra_trace_listen(address='0.0.0.0:0'):
|
||||
s.bind((host, int(port)))
|
||||
host, port = s.getsockname()
|
||||
s.listen(1)
|
||||
print("Listening at {}:{}...\n".format(host, port))
|
||||
print("Listening at {}:{}...".format(host, port))
|
||||
c, (chost, cport) = s.accept()
|
||||
s.close()
|
||||
print("Connection from {}:{}\n".format(chost, cport))
|
||||
print("Connection from {}:{}".format(chost, cport))
|
||||
STATE.client = Client(c, "dbgeng.dll", methods.REGISTRY)
|
||||
except ValueError:
|
||||
raise RuntimeError("port must be numeric")
|
||||
@ -207,9 +211,11 @@ def start_trace(name):
|
||||
schema_fn = os.path.join(parent, 'schema.xml')
|
||||
with open(schema_fn, 'r') as schema_file:
|
||||
schema_xml = schema_file.read()
|
||||
using_dbgmodel = os.getenv('OPT_USE_DBGMODEL') == "true"
|
||||
variant = " (dbgmodel)" if using_dbgmodel else " (dbgeng)"
|
||||
with STATE.trace.open_tx("Create Root Object"):
|
||||
root = STATE.trace.create_root_object(schema_xml, 'Session')
|
||||
root.set_value('_display', 'pydbg(dbgeng) ' + util.DBG_VERSION.full)
|
||||
root = STATE.trace.create_root_object(schema_xml, 'DbgRoot')
|
||||
root.set_value('_display', util.DBG_VERSION.full + ' via pybag' + variant)
|
||||
util.set_convenience_variable('_ghidra_tracing', "true")
|
||||
|
||||
|
||||
@ -240,47 +246,48 @@ def ghidra_trace_restart(name=None):
|
||||
start_trace(name)
|
||||
|
||||
|
||||
def ghidra_trace_create(command=None, initial_break=True, timeout=None, start_trace=True):
|
||||
@util.dbg.eng_thread
|
||||
def ghidra_trace_create(command=None, initial_break=True, timeout=DbgEng.WAIT_INFINITE, start_trace=True):
|
||||
"""
|
||||
Create a session.
|
||||
"""
|
||||
|
||||
util.base = userdbg.UserDbg()
|
||||
dbg = util.dbg._base
|
||||
if command != None:
|
||||
if timeout != None:
|
||||
util.base._client.CreateProcess(command, DbgEng.DEBUG_PROCESS)
|
||||
if initial_break:
|
||||
util.base._control.AddEngineOptions(
|
||||
DbgEng.DEBUG_ENGINITIAL_BREAK)
|
||||
util.base.wait(timeout)
|
||||
else:
|
||||
util.base.create(command, initial_break)
|
||||
dbg._client.CreateProcess(command, DbgEng.DEBUG_PROCESS)
|
||||
if initial_break:
|
||||
dbg._control.AddEngineOptions(DbgEng.DEBUG_ENGINITIAL_BREAK)
|
||||
if start_trace:
|
||||
ghidra_trace_start(command)
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def ghidra_trace_kill():
|
||||
"""
|
||||
Kill a session.
|
||||
"""
|
||||
|
||||
dbg()._client.TerminateCurrentProcess()
|
||||
dbg = util.dbg._base
|
||||
dbg._client.TerminateCurrentProcess()
|
||||
try:
|
||||
dbg.wait()
|
||||
except exception.E_UNEXPECTED_Error:
|
||||
# Expect the unexpected, I guess.
|
||||
pass
|
||||
|
||||
|
||||
def ghidra_trace_info():
|
||||
"""Get info about the Ghidra connection"""
|
||||
|
||||
result = {}
|
||||
if STATE.client is None:
|
||||
print("Not connected to Ghidra\n")
|
||||
print("Not connected to Ghidra")
|
||||
return
|
||||
host, port = STATE.client.s.getpeername()
|
||||
print(f"Connected to {STATE.client.description} at {host}:{port}\n")
|
||||
print(f"Connected to {STATE.client.description} at {host}:{port}")
|
||||
if STATE.trace is None:
|
||||
print("No trace\n")
|
||||
print("No trace")
|
||||
return
|
||||
print("Trace active\n")
|
||||
return result
|
||||
print("Trace active")
|
||||
|
||||
|
||||
def ghidra_trace_info_lcsp():
|
||||
@ -289,8 +296,8 @@ def ghidra_trace_info_lcsp():
|
||||
"""
|
||||
|
||||
language, compiler = arch.compute_ghidra_lcsp()
|
||||
print("Selected Ghidra language: {}\n".format(language))
|
||||
print("Selected Ghidra compiler: {}\n".format(compiler))
|
||||
print("Selected Ghidra language: {}".format(language))
|
||||
print("Selected Ghidra compiler: {}".format(compiler))
|
||||
|
||||
|
||||
def ghidra_trace_txstart(description="tx"):
|
||||
@ -319,7 +326,7 @@ def ghidra_trace_txabort():
|
||||
"""
|
||||
|
||||
tx = STATE.require_tx()
|
||||
print("Aborting trace transaction!\n")
|
||||
print("Aborting trace transaction!")
|
||||
tx.abort()
|
||||
STATE.reset_tx()
|
||||
|
||||
@ -362,15 +369,22 @@ def ghidra_trace_set_snap(snap=None):
|
||||
STATE.require_trace().set_snap(int(snap))
|
||||
|
||||
|
||||
def quantize_pages(start, end):
|
||||
return (start // PAGE_SIZE * PAGE_SIZE, (end+PAGE_SIZE-1) // PAGE_SIZE*PAGE_SIZE)
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def put_bytes(start, end, pages, display_result):
|
||||
trace = STATE.require_trace()
|
||||
if pages:
|
||||
start = start // PAGE_SIZE * PAGE_SIZE
|
||||
end = (end + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE
|
||||
start, end = quantize_pages(start, end)
|
||||
nproc = util.selected_process()
|
||||
if end - start <= 0:
|
||||
return {'count': 0}
|
||||
buf = dbg().read(start, end - start)
|
||||
try:
|
||||
buf = util.dbg._base.read(start, end - start)
|
||||
except OSError:
|
||||
return {'count': 0}
|
||||
|
||||
count = 0
|
||||
if buf != None:
|
||||
@ -379,7 +393,7 @@ def put_bytes(start, end, pages, display_result):
|
||||
trace.create_overlay_space(base, addr.space)
|
||||
count = trace.put_bytes(addr, buf)
|
||||
if display_result:
|
||||
print("Wrote {} bytes\n".format(count))
|
||||
print("Wrote {} bytes".format(count))
|
||||
return {'count': count}
|
||||
|
||||
|
||||
@ -404,16 +418,11 @@ def putmem(address, length, pages=True, display_result=True):
|
||||
return put_bytes(start, end, pages, display_result)
|
||||
|
||||
|
||||
def ghidra_trace_putmem(items):
|
||||
def ghidra_trace_putmem(address, length, pages=True):
|
||||
"""
|
||||
Record the given block of memory into the Ghidra trace.
|
||||
"""
|
||||
|
||||
items = items.split(" ")
|
||||
address = items[0]
|
||||
length = items[1]
|
||||
pages = items[2] if len(items) > 2 else True
|
||||
|
||||
STATE.require_tx()
|
||||
return putmem(address, length, pages, True)
|
||||
|
||||
@ -436,27 +445,28 @@ def ghidra_trace_putval(items):
|
||||
return put_bytes(start, end, pages, True)
|
||||
|
||||
|
||||
def ghidra_trace_putmem_state(items):
|
||||
"""
|
||||
Set the state of the given range of memory in the Ghidra trace.
|
||||
"""
|
||||
|
||||
items = items.split(" ")
|
||||
address = items[0]
|
||||
length = items[1]
|
||||
state = items[2]
|
||||
|
||||
STATE.require_tx()
|
||||
def putmem_state(address, length, state, pages=True):
|
||||
STATE.trace.validate_state(state)
|
||||
start, end = eval_range(address, length)
|
||||
if pages:
|
||||
start, end = quantize_pages(start, end)
|
||||
nproc = util.selected_process()
|
||||
base, addr = STATE.trace.memory_mapper.map(nproc, start)
|
||||
if base != addr.space:
|
||||
if base != addr.space and state != 'unknown':
|
||||
trace.create_overlay_space(base, addr.space)
|
||||
STATE.trace.set_memory_state(addr.extend(end - start), state)
|
||||
|
||||
|
||||
def ghidra_trace_delmem(items):
|
||||
def ghidra_trace_putmem_state(address, length, state, pages=True):
|
||||
"""
|
||||
Set the state of the given range of memory in the Ghidra trace.
|
||||
"""
|
||||
|
||||
STATE.require_tx()
|
||||
return putmem_state(address, length, state, pages)
|
||||
|
||||
|
||||
def ghidra_trace_delmem(address, length):
|
||||
"""
|
||||
Delete the given range of memory from the Ghidra trace.
|
||||
|
||||
@ -465,10 +475,6 @@ def ghidra_trace_delmem(items):
|
||||
not quantize. You must do that yourself, if necessary.
|
||||
"""
|
||||
|
||||
items = items.split(" ")
|
||||
address = items[0]
|
||||
length = items[1]
|
||||
|
||||
STATE.require_tx()
|
||||
start, end = eval_range(address, length)
|
||||
nproc = util.selected_process()
|
||||
@ -477,7 +483,20 @@ def ghidra_trace_delmem(items):
|
||||
STATE.trace.delete_bytes(addr.extend(end - start))
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def putreg():
|
||||
if util.dbg.use_generics:
|
||||
nproc = util.selected_process()
|
||||
if nproc < 0:
|
||||
return
|
||||
nthrd = util.selected_thread()
|
||||
rpath = REGS_PATTERN.format(procnum=nproc, tnum=nthrd)
|
||||
create_generic(rpath)
|
||||
STATE.trace.create_overlay_space('register', rpath)
|
||||
path = USER_REGS_PATTERN.format(procnum=nproc, tnum=nthrd)
|
||||
(values, keys) = create_generic(path)
|
||||
return {'missing': STATE.trace.put_registers(rpath, values)}
|
||||
|
||||
nproc = util.selected_process()
|
||||
if nproc < 0:
|
||||
return
|
||||
@ -488,13 +507,13 @@ def putreg():
|
||||
robj.insert()
|
||||
mapper = STATE.trace.register_mapper
|
||||
values = []
|
||||
regs = dbg().reg
|
||||
regs = util.dbg._base.reg
|
||||
for i in range(0, len(regs)):
|
||||
name = regs._reg.GetDescription(i)[0]
|
||||
value = regs._get_register_by_index(i)
|
||||
try:
|
||||
values.append(mapper.map_value(nproc, name, value))
|
||||
robj.set_value(name, value)
|
||||
robj.set_value(name, hex(value))
|
||||
except Exception:
|
||||
pass
|
||||
return {'missing': STATE.trace.put_registers(space, values)}
|
||||
@ -511,6 +530,7 @@ def ghidra_trace_putreg():
|
||||
putreg()
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def ghidra_trace_delreg(group='all'):
|
||||
"""
|
||||
Delete the given register group for the curent frame from the Ghidra trace.
|
||||
@ -524,11 +544,11 @@ def ghidra_trace_delreg(group='all'):
|
||||
space = REGS_PATTERN.format(procnum=nproc, tnum=nthrd)
|
||||
mapper = STATE.trace.register_mapper
|
||||
names = []
|
||||
regs = dbg().reg
|
||||
regs = util.dbg._base.reg
|
||||
for i in range(0, len(regs)):
|
||||
name = regs._reg.GetDescription(i)[0]
|
||||
names.append(mapper.map_name(nproc, name))
|
||||
return STATE.trace.delete_registers(space, names)
|
||||
STATE.trace.delete_registers(space, names)
|
||||
|
||||
|
||||
def ghidra_trace_create_obj(path=None):
|
||||
@ -543,8 +563,7 @@ def ghidra_trace_create_obj(path=None):
|
||||
STATE.require_tx()
|
||||
obj = STATE.trace.create_object(path)
|
||||
obj.insert()
|
||||
print("Created object: id={}, path='{}'\n".format(obj.id, obj.path))
|
||||
return {'id': obj.id, 'path': obj.path}
|
||||
print("Created object: id={}, path='{}'".format(obj.id, obj.path))
|
||||
|
||||
|
||||
def ghidra_trace_insert_obj(path):
|
||||
@ -556,8 +575,7 @@ def ghidra_trace_insert_obj(path):
|
||||
# humans.
|
||||
STATE.require_tx()
|
||||
span = STATE.trace.proxy_object_path(path).insert()
|
||||
print("Inserted object: lifespan={}\n".format(span))
|
||||
return {'lifespan': span}
|
||||
print("Inserted object: lifespan={}".format(span))
|
||||
|
||||
|
||||
def ghidra_trace_remove_obj(path):
|
||||
@ -600,10 +618,10 @@ def to_string_list(value, encoding):
|
||||
|
||||
def eval_value(value, schema=None):
|
||||
if schema == sch.CHAR or schema == sch.BYTE or schema == sch.SHORT or schema == sch.INT or schema == sch.LONG or schema == None:
|
||||
value = util.get_eval(value)
|
||||
value = util.parse_and_eval(value)
|
||||
return value, schema
|
||||
if schema == sch.ADDRESS:
|
||||
value = util.get_eval(value)
|
||||
value = util.parse_and_eval(value)
|
||||
nproc = util.selected_process()
|
||||
base, addr = STATE.trace.memory_mapper.map(nproc, value)
|
||||
return (base, addr), sch.ADDRESS
|
||||
@ -696,8 +714,7 @@ def ghidra_trace_get_obj(path):
|
||||
|
||||
trace = STATE.require_trace()
|
||||
object = trace.get_object(path)
|
||||
print("{}\t{}\n".format(object.id, object.path))
|
||||
return object
|
||||
print("{}\t{}".format(object.id, object.path))
|
||||
|
||||
|
||||
class TableColumn(object):
|
||||
@ -734,7 +751,7 @@ class Tabular(object):
|
||||
for rn in range(self.num_rows):
|
||||
for c in self.columns:
|
||||
c.print_cell(rn)
|
||||
print('\n')
|
||||
print('')
|
||||
|
||||
|
||||
def val_repr(value):
|
||||
@ -761,18 +778,13 @@ def ghidra_trace_get_values(pattern):
|
||||
trace = STATE.require_trace()
|
||||
values = trace.get_values(pattern)
|
||||
print_values(values)
|
||||
return values
|
||||
|
||||
|
||||
def ghidra_trace_get_values_rng(items):
|
||||
def ghidra_trace_get_values_rng(address, length):
|
||||
"""
|
||||
List all values intersecting a given address range.
|
||||
"""
|
||||
|
||||
items = items.split(" ")
|
||||
address = items[0]
|
||||
length = items[1]
|
||||
|
||||
trace = STATE.require_trace()
|
||||
start, end = eval_range(address, length)
|
||||
nproc = util.selected_process()
|
||||
@ -780,7 +792,6 @@ def ghidra_trace_get_values_rng(items):
|
||||
# Do not create the space. We're querying. No tx.
|
||||
values = trace.get_values_intersecting(addr.extend(end - start))
|
||||
print_values(values)
|
||||
return values
|
||||
|
||||
|
||||
def activate(path=None):
|
||||
@ -825,34 +836,47 @@ def ghidra_trace_disassemble(address):
|
||||
trace.create_overlay_space(base, addr.space)
|
||||
|
||||
length = STATE.trace.disassemble(addr)
|
||||
print("Disassembled {} bytes\n".format(length))
|
||||
return {'length': length}
|
||||
print("Disassembled {} bytes".format(length))
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def compute_proc_state(nproc=None):
|
||||
status = dbg()._control.GetExecutionStatus()
|
||||
exit_code = util.GetExitCode()
|
||||
if exit_code is not None and exit_code != STILL_ACTIVE:
|
||||
return 'TERMINATED'
|
||||
status = util.dbg._base._control.GetExecutionStatus()
|
||||
if status == DbgEng.DEBUG_STATUS_BREAK:
|
||||
return 'STOPPED'
|
||||
return 'RUNNING'
|
||||
|
||||
|
||||
def put_processes(running=False):
|
||||
radix = util.get_convenience_variable('output-radix')
|
||||
if radix == 'auto':
|
||||
radix = 16
|
||||
# | always displays PID in hex
|
||||
# TODO: I'm not sure about the engine id
|
||||
|
||||
# NB: This speeds things up, but desirable?
|
||||
if running:
|
||||
return
|
||||
|
||||
if util.dbg.use_generics and not running:
|
||||
ppath = PROCESSES_PATH
|
||||
(values, keys) = create_generic(ppath)
|
||||
STATE.trace.proxy_object_path(PROCESSES_PATH).retain_values(keys)
|
||||
return
|
||||
|
||||
keys = []
|
||||
for i, p in enumerate(util.process_list(running)):
|
||||
# Set running=True to avoid process changes, even while stopped
|
||||
for i, p in enumerate(util.process_list(running=True)):
|
||||
ipath = PROCESS_PATTERN.format(procnum=i)
|
||||
keys.append(PROCESS_KEY_PATTERN.format(procnum=i))
|
||||
procobj = STATE.trace.create_object(ipath)
|
||||
|
||||
istate = compute_proc_state(i)
|
||||
procobj.set_value('_state', istate)
|
||||
if running == False:
|
||||
procobj.set_value('_pid', p[0])
|
||||
pidstr = ('0x{:x}' if radix ==
|
||||
16 else '0{:o}' if radix == 8 else '{}').format(p[0])
|
||||
procobj.set_value('_display', pidstr)
|
||||
procobj.set_value('State', istate)
|
||||
pid = p[0]
|
||||
procobj.set_value('PID', pid)
|
||||
procobj.set_value('_display', '{:x} {:x}'.format(i, pid))
|
||||
if len(p) > 1:
|
||||
procobj.set_value('Name', str(p[1]))
|
||||
procobj.set_value('PEB', hex(p[2]))
|
||||
procobj.insert()
|
||||
@ -860,15 +884,17 @@ def put_processes(running=False):
|
||||
|
||||
|
||||
def put_state(event_process):
|
||||
STATE.require_no_tx()
|
||||
STATE.tx = STATE.require_trace().start_tx("state", undoable=False)
|
||||
ipath = PROCESS_PATTERN.format(procnum=event_process)
|
||||
procobj = STATE.trace.create_object(ipath)
|
||||
state = compute_proc_state(event_process)
|
||||
procobj.set_value('_state', state)
|
||||
procobj.set_value('State', state)
|
||||
procobj.insert()
|
||||
STATE.require_tx().commit()
|
||||
STATE.reset_tx()
|
||||
tnum = util.selected_thread()
|
||||
if tnum is not None:
|
||||
ipath = THREAD_PATTERN.format(procnum=event_process, tnum=tnum)
|
||||
threadobj = STATE.trace.create_object(ipath)
|
||||
threadobj.set_value('State', state)
|
||||
threadobj.insert()
|
||||
|
||||
|
||||
def ghidra_trace_put_processes():
|
||||
@ -881,10 +907,11 @@ def ghidra_trace_put_processes():
|
||||
put_processes()
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def put_available():
|
||||
radix = util.get_convenience_variable('output-radix')
|
||||
keys = []
|
||||
result = dbg().cmd(".tlist")
|
||||
result = util.dbg._base.cmd(".tlist")
|
||||
lines = result.split("\n")
|
||||
for i in lines:
|
||||
i = i.strip()
|
||||
@ -900,7 +927,7 @@ def put_available():
|
||||
keys.append(AVAILABLE_KEY_PATTERN.format(pid=id))
|
||||
pidstr = ('0x{:x}' if radix ==
|
||||
16 else '0{:o}' if radix == 8 else '{}').format(id)
|
||||
procobj.set_value('_pid', id)
|
||||
procobj.set_value('PID', id)
|
||||
procobj.set_value('Name', name)
|
||||
procobj.set_value('_display', '{} {}'.format(pidstr, name))
|
||||
procobj.insert()
|
||||
@ -917,6 +944,7 @@ def ghidra_trace_put_available():
|
||||
put_available()
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
||||
mapper = STATE.trace.memory_mapper
|
||||
bpath = PROC_BREAK_PATTERN.format(procnum=nproc, breaknum=bp.GetId())
|
||||
@ -931,7 +959,7 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
||||
else:
|
||||
address = bp.GetOffset()
|
||||
offset = "%016x" % address
|
||||
expr = dbg().get_name_by_offset(address)
|
||||
expr = util.dbg._base.get_name_by_offset(address)
|
||||
try:
|
||||
tid = bp.GetMatchThreadId()
|
||||
tid = "%04x" % tid
|
||||
@ -950,22 +978,22 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
||||
base, addr = mapper.map(nproc, address)
|
||||
if base != addr.space:
|
||||
STATE.trace.create_overlay_space(base, addr.space)
|
||||
brkobj.set_value('_range', addr.extend(1))
|
||||
brkobj.set_value('Range', addr.extend(1))
|
||||
elif expr is not None: # Implies watchpoint
|
||||
try:
|
||||
address = int(util.parse_and_eval('&({})'.format(expr)))
|
||||
base, addr = mapper.map(inf, address)
|
||||
if base != addr.space:
|
||||
STATE.trace.create_overlay_space(base, addr.space)
|
||||
brkobj.set_value('_range', addr.extend(width))
|
||||
brkobj.set_value('Range', addr.extend(width))
|
||||
except Exception as e:
|
||||
print("Error: Could not get range for breakpoint: {}\n".format(e))
|
||||
print("Error: Could not get range for breakpoint: {}".format(e))
|
||||
else: # I guess it's a catchpoint
|
||||
pass
|
||||
|
||||
brkobj.set_value('_expression', expr)
|
||||
brkobj.set_value('_range', addr.extend(1))
|
||||
brkobj.set_value('_kinds', prot)
|
||||
brkobj.set_value('Expression', expr)
|
||||
brkobj.set_value('Range', addr.extend(1))
|
||||
brkobj.set_value('Kinds', prot)
|
||||
brkobj.set_value('Pass Count', bp.GetPassCount())
|
||||
brkobj.set_value('Current Pass Count', bp.GetCurrentPassCount())
|
||||
brkobj.set_value('Enabled', status)
|
||||
@ -979,19 +1007,30 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
||||
ikeys.append(k)
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def put_breakpoints():
|
||||
target = util.get_target()
|
||||
nproc = util.selected_process()
|
||||
|
||||
# NB: Am leaving this code here in case we change our minds, but the cost
|
||||
# of using put_generic here outweighs the advantage of uniformity
|
||||
#
|
||||
# if util.dbg.use_generics:
|
||||
# path = PROC_BREAKS_PATTERN.format(procnum=nproc)
|
||||
# (values, keys) = create_generic(path)
|
||||
# STATE.trace.proxy_object_path(path).retain_values(keys)
|
||||
# return
|
||||
|
||||
target = util.get_target()
|
||||
ibpath = PROC_BREAKS_PATTERN.format(procnum=nproc)
|
||||
ibobj = STATE.trace.create_object(ibpath)
|
||||
keys = []
|
||||
ikeys = []
|
||||
ids = [bpid for bpid in dbg().breakpoints]
|
||||
ids = [bpid for bpid in util.dbg._base.breakpoints]
|
||||
for bpid in ids:
|
||||
try:
|
||||
bp = dbg()._control.GetBreakpointById(bpid)
|
||||
bp = util.dbg._base._control.GetBreakpointById(bpid)
|
||||
except exception.E_NOINTERFACE_Error:
|
||||
dbg().breakpoints._remove_stale(bpid)
|
||||
util.dbg._base.breakpoints._remove_stale(bpid)
|
||||
continue
|
||||
keys.append(PROC_BREAK_KEY_PATTERN.format(breaknum=bpid))
|
||||
put_single_breakpoint(bp, ibobj, nproc, ikeys)
|
||||
@ -1011,12 +1050,13 @@ def ghidra_trace_put_breakpoints():
|
||||
|
||||
|
||||
def put_environment():
|
||||
epath = ENV_PATTERN.format(procnum=util.selected_process())
|
||||
nproc = util.selected_process()
|
||||
epath = ENV_PATTERN.format(procnum=nproc)
|
||||
envobj = STATE.trace.create_object(epath)
|
||||
envobj.set_value('_debugger', 'pydbg')
|
||||
envobj.set_value('_arch', arch.get_arch())
|
||||
envobj.set_value('_os', arch.get_osabi())
|
||||
envobj.set_value('_endian', arch.get_endian())
|
||||
envobj.set_value('Debugger', 'pydbg')
|
||||
envobj.set_value('Arch', arch.get_arch())
|
||||
envobj.set_value('OS', arch.get_osabi())
|
||||
envobj.set_value('Endian', arch.get_endian())
|
||||
envobj.insert()
|
||||
|
||||
|
||||
@ -1030,14 +1070,14 @@ def ghidra_trace_put_environment():
|
||||
put_environment()
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def put_regions():
|
||||
nproc = util.selected_process()
|
||||
try:
|
||||
regions = dbg().memory_list()
|
||||
regions = util.dbg._base.memory_list()
|
||||
except Exception:
|
||||
regions = []
|
||||
if len(regions) == 0 and util.selected_thread() != None:
|
||||
regions = [util.REGION_INFO_READER.full_mem()]
|
||||
|
||||
mapper = STATE.trace.memory_mapper
|
||||
keys = []
|
||||
# r : MEMORY_BASIC_INFORMATION64
|
||||
@ -1045,19 +1085,14 @@ def put_regions():
|
||||
rpath = REGION_PATTERN.format(procnum=nproc, start=r.BaseAddress)
|
||||
keys.append(REGION_KEY_PATTERN.format(start=r.BaseAddress))
|
||||
regobj = STATE.trace.create_object(rpath)
|
||||
start_base, start_addr = mapper.map(nproc, r.BaseAddress)
|
||||
if start_base != start_addr.space:
|
||||
STATE.trace.create_overlay_space(start_base, start_addr.space)
|
||||
regobj.set_value('_range', start_addr.extend(r.RegionSize))
|
||||
(start_base, start_addr) = map_address(r.BaseAddress)
|
||||
regobj.set_value('Range', start_addr.extend(r.RegionSize))
|
||||
regobj.set_value('_readable', r.Protect ==
|
||||
None or r.Protect & 0x66 != 0)
|
||||
regobj.set_value('_writable', r.Protect ==
|
||||
None or r.Protect & 0xCC != 0)
|
||||
regobj.set_value('_executable', r.Protect ==
|
||||
None or r.Protect & 0xF0 != 0)
|
||||
regobj.set_value('_offset', hex(r.BaseAddress))
|
||||
regobj.set_value('Base', hex(r.BaseAddress))
|
||||
regobj.set_value('Size', hex(r.RegionSize))
|
||||
regobj.set_value('AllocationBase', hex(r.AllocationBase))
|
||||
regobj.set_value('Protect', hex(r.Protect))
|
||||
regobj.set_value('Type', hex(r.Type))
|
||||
@ -1076,10 +1111,18 @@ def ghidra_trace_put_regions():
|
||||
put_regions()
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def put_modules():
|
||||
target = util.get_target()
|
||||
nproc = util.selected_process()
|
||||
modules = dbg().module_list()
|
||||
if util.dbg.use_generics:
|
||||
mpath = MODULES_PATTERN.format(procnum=nproc)
|
||||
(values, keys) = create_generic(mpath)
|
||||
STATE.trace.proxy_object_path(
|
||||
MODULES_PATTERN.format(procnum=nproc)).retain_values(keys)
|
||||
return
|
||||
|
||||
target = util.get_target()
|
||||
modules = util.dbg._base.module_list()
|
||||
mapper = STATE.trace.memory_mapper
|
||||
mod_keys = []
|
||||
for m in modules:
|
||||
@ -1092,14 +1135,11 @@ def put_modules():
|
||||
mpath = MODULE_PATTERN.format(procnum=nproc, modpath=hbase)
|
||||
modobj = STATE.trace.create_object(mpath)
|
||||
mod_keys.append(MODULE_KEY_PATTERN.format(modpath=hbase))
|
||||
modobj.set_value('_module_name', name)
|
||||
base_base, base_addr = mapper.map(nproc, base)
|
||||
if base_base != base_addr.space:
|
||||
STATE.trace.create_overlay_space(base_base, base_addr.space)
|
||||
modobj.set_value('_range', base_addr.extend(size))
|
||||
modobj.set_value('Range', base_addr.extend(size))
|
||||
modobj.set_value('Name', name)
|
||||
modobj.set_value('Base', hbase)
|
||||
modobj.set_value('Size', hex(size))
|
||||
modobj.set_value('Flags', hex(size))
|
||||
modobj.insert()
|
||||
|
||||
@ -1131,38 +1171,45 @@ def convert_state(t):
|
||||
return 'RUNNING'
|
||||
|
||||
|
||||
def convert_tid(t):
|
||||
if t[1] == 0:
|
||||
return t[2]
|
||||
return t[1]
|
||||
|
||||
|
||||
def compute_thread_display(tidstr, t):
|
||||
return '[{} {}]'.format(tidstr, t[2])
|
||||
def compute_thread_display(i, pid, tid, t):
|
||||
if len(t) > 1:
|
||||
return '{:x} {:x}:{:x} {}'.format(i, pid, tid, t[2])
|
||||
return '{:x} {:x}:{:x}'.format(i, pid, tid)
|
||||
|
||||
|
||||
def put_threads(running=False):
|
||||
radix = util.get_convenience_variable('output-radix')
|
||||
if radix == 'auto':
|
||||
radix = 16
|
||||
nproc = util.selected_process()
|
||||
if nproc == None:
|
||||
# ~ always displays PID:TID in hex
|
||||
# TODO: I'm not sure about the engine id
|
||||
|
||||
# NB: This speeds things up, but desirable?
|
||||
if running:
|
||||
return
|
||||
|
||||
nproc = util.selected_process()
|
||||
if nproc is None:
|
||||
return
|
||||
if util.dbg.use_generics and not running:
|
||||
tpath = THREADS_PATTERN.format(procnum=nproc)
|
||||
(values, keys) = create_generic(tpath)
|
||||
STATE.trace.proxy_object_path(
|
||||
THREADS_PATTERN.format(procnum=nproc)).retain_values(keys)
|
||||
return
|
||||
|
||||
pid = util.dbg.pid
|
||||
|
||||
keys = []
|
||||
for i, t in enumerate(util.thread_list(running)):
|
||||
# Set running=True to avoid thread changes, even while stopped
|
||||
for i, t in enumerate(util.thread_list(running=True)):
|
||||
tpath = THREAD_PATTERN.format(procnum=nproc, tnum=i)
|
||||
tobj = STATE.trace.create_object(tpath)
|
||||
keys.append(THREAD_KEY_PATTERN.format(tnum=i))
|
||||
#tobj.set_value('_state', convert_state(t))
|
||||
if running == False:
|
||||
tobj.set_value('_name', t[2])
|
||||
tid = t[0]
|
||||
tobj.set_value('_tid', tid)
|
||||
tidstr = ('0x{:x}' if radix ==
|
||||
16 else '0{:o}' if radix == 8 else '{}').format(tid)
|
||||
tobj.set_value('_short_display', '[{}.{}:{}]'.format(
|
||||
nproc, i, tidstr))
|
||||
tobj.set_value('_display', compute_thread_display(tidstr, t))
|
||||
|
||||
tid = t[0]
|
||||
tobj.set_value('TID', tid)
|
||||
tobj.set_value('_short_display',
|
||||
'{:x} {:x}:{:x}'.format(i, pid, tid))
|
||||
tobj.set_value('_display', compute_thread_display(i, pid, tid, t))
|
||||
if len(t) > 1:
|
||||
tobj.set_value('TEB', hex(t[1]))
|
||||
tobj.set_value('Name', t[2])
|
||||
tobj.insert()
|
||||
@ -1193,29 +1240,47 @@ def ghidra_trace_put_threads():
|
||||
put_threads()
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def put_frames():
|
||||
nproc = util.selected_process()
|
||||
mapper = STATE.trace.memory_mapper
|
||||
if nproc < 0:
|
||||
return
|
||||
nthrd = util.selected_thread()
|
||||
if nthrd is None:
|
||||
return
|
||||
|
||||
if util.dbg.use_generics:
|
||||
path = STACK_PATTERN.format(procnum=nproc, tnum=nthrd)
|
||||
(values, keys) = create_generic(path)
|
||||
STATE.trace.proxy_object_path(path).retain_values(keys)
|
||||
return
|
||||
|
||||
mapper = STATE.trace.memory_mapper
|
||||
keys = []
|
||||
# f : _DEBUG_STACK_FRAME
|
||||
for f in dbg().backtrace_list():
|
||||
for f in util.dbg._base.backtrace_list():
|
||||
fpath = FRAME_PATTERN.format(
|
||||
procnum=nproc, tnum=nthrd, level=f.FrameNumber)
|
||||
fobj = STATE.trace.create_object(fpath)
|
||||
keys.append(FRAME_KEY_PATTERN.format(level=f.FrameNumber))
|
||||
base, pc = mapper.map(nproc, f.InstructionOffset)
|
||||
if base != pc.space:
|
||||
STATE.trace.create_overlay_space(base, pc.space)
|
||||
fobj.set_value('_pc', pc)
|
||||
fobj.set_value('InstructionOffset', hex(f.InstructionOffset))
|
||||
fobj.set_value('StackOffset', hex(f.StackOffset))
|
||||
fobj.set_value('ReturnOffset', hex(f.ReturnOffset))
|
||||
fobj.set_value('FrameOffset', hex(f.FrameOffset))
|
||||
base, offset_inst = mapper.map(nproc, f.InstructionOffset)
|
||||
if base != offset_inst.space:
|
||||
STATE.trace.create_overlay_space(base, offset_inst.space)
|
||||
base, offset_stack = mapper.map(nproc, f.StackOffset)
|
||||
if base != offset_stack.space:
|
||||
STATE.trace.create_overlay_space(base, offset_stack.space)
|
||||
base, offset_ret = mapper.map(nproc, f.ReturnOffset)
|
||||
if base != offset_ret.space:
|
||||
STATE.trace.create_overlay_space(base, offset_ret.space)
|
||||
base, offset_frame = mapper.map(nproc, f.FrameOffset)
|
||||
if base != offset_frame.space:
|
||||
STATE.trace.create_overlay_space(base, offset_frame.space)
|
||||
fobj.set_value('Instruction Offset', offset_inst)
|
||||
fobj.set_value('Stack Offset', offset_stack)
|
||||
fobj.set_value('Return Offset', offset_ret)
|
||||
fobj.set_value('Frame Offset', offset_frame)
|
||||
fobj.set_value('_display', "#{} {}".format(
|
||||
f.FrameNumber, hex(f.InstructionOffset)))
|
||||
f.FrameNumber, offset_inst.offset))
|
||||
fobj.insert()
|
||||
STATE.trace.proxy_object_path(STACK_PATTERN.format(
|
||||
procnum=nproc, tnum=nthrd)).retain_values(keys)
|
||||
@ -1231,6 +1296,129 @@ def ghidra_trace_put_frames():
|
||||
put_frames()
|
||||
|
||||
|
||||
def update_by_container(np, index, obj):
|
||||
if np.endswith("Processes") or np.endswith("Threads"):
|
||||
istate = compute_proc_state(index)
|
||||
obj.set_value('State', istate)
|
||||
if np.endswith("Processes"):
|
||||
create_generic(obj.path)
|
||||
id = util.get_proc_id(index)
|
||||
obj.set_value('PID', index)
|
||||
obj.set_value('_display', '{:x} {:x}'.format(id, index))
|
||||
if np.endswith("Breakpoints"):
|
||||
create_generic(obj.path)
|
||||
#id = util.get_thread_id(index)
|
||||
#obj.set_value('TID', index)
|
||||
#obj.set_value('_display','{:x} {:x}'.format(id, index))
|
||||
if np.endswith("Threads"):
|
||||
create_generic(obj.path)
|
||||
id = util.get_thread_id(index)
|
||||
obj.set_value('TID', index)
|
||||
obj.set_value('_display', '{:x} {:x}'.format(id, index))
|
||||
if np.endswith("Frames"):
|
||||
mo = util.get_object(obj.path)
|
||||
map = util.get_attributes(mo)
|
||||
attr = map["Attributes"]
|
||||
if attr is None:
|
||||
return
|
||||
create_generic(obj.path+".Attributes")
|
||||
map = util.get_attributes(attr)
|
||||
pc = util.get_value(map["InstructionOffset"])
|
||||
(pc_base, pc_addr) = map_address(pc)
|
||||
obj.set_value('Instruction Offset', pc_addr)
|
||||
obj.set_value('_display', '#{:x} 0x{:x}'.format(index, pc))
|
||||
if np.endswith("Modules"):
|
||||
create_generic(obj.path)
|
||||
mo = util.get_object(obj.path)
|
||||
map = util.get_attributes(mo)
|
||||
base = util.get_value(map["BaseAddress"])
|
||||
size = util.get_value(map["Size"])
|
||||
name = util.get_value(map["Name"])
|
||||
obj.set_value('Name', '{}'.format(name))
|
||||
(base_base, base_addr) = map_address(base)
|
||||
obj.set_value('Range', base_addr.extend(size))
|
||||
obj.set_value('_display', '{:x} {:x} {}'.format(index, base, name))
|
||||
|
||||
|
||||
def create_generic(path):
|
||||
obj = STATE.trace.create_object(path)
|
||||
obj.insert()
|
||||
result = put_generic(obj)
|
||||
return result
|
||||
|
||||
|
||||
def put_generic(node):
|
||||
#print(f"put_generic: {node}")
|
||||
nproc = util.selected_process()
|
||||
if nproc is None:
|
||||
return
|
||||
nthrd = util.selected_thread()
|
||||
|
||||
mapper = STATE.trace.memory_mapper
|
||||
mo = util.get_object(node.path)
|
||||
kind = util.get_kind(mo)
|
||||
type = util.get_type(mo)
|
||||
vstr = util.get_value(mo)
|
||||
# print(f"MO={mo}")
|
||||
attributes = util.get_attributes(mo)
|
||||
# print(f"ATTR={attributes}")
|
||||
mapper = STATE.trace.register_mapper
|
||||
values = []
|
||||
for key, value in attributes.items():
|
||||
if value is None:
|
||||
continue
|
||||
kind = util.get_kind(value)
|
||||
vstr = util.get_value(value)
|
||||
#print(f"key={key} kind={kind} value={vstr} type={type}")
|
||||
if kind == ModelObjectKind.PROPERTY_ACCESSOR.value or \
|
||||
kind == ModelObjectKind.SYNTHETIC.value or \
|
||||
kind == ModelObjectKind.METHOD.value:
|
||||
if vstr is not None:
|
||||
key += " : " + vstr
|
||||
apath = node.path+'.'+key
|
||||
aobj = STATE.trace.create_object(apath)
|
||||
aobj.insert()
|
||||
else:
|
||||
try:
|
||||
if node.path.endswith('.User'):
|
||||
values.append(mapper.map_value(nproc, key, vstr))
|
||||
node.set_value(key, hex(vstr))
|
||||
except Exception as e:
|
||||
pass # Error is printed by another mechanism
|
||||
elements = util.get_elements(mo)
|
||||
# print(f"ELEM={elements}")
|
||||
keys = []
|
||||
for el in elements:
|
||||
index = el[0]
|
||||
key = GENERIC_KEY_PATTERN.format(key=index)
|
||||
lpath = node.path+key
|
||||
lobj = STATE.trace.create_object(lpath)
|
||||
update_by_container(node.path, index, lobj)
|
||||
lobj.insert()
|
||||
keys.append(key)
|
||||
node.retain_values(keys)
|
||||
return (values, keys)
|
||||
|
||||
|
||||
def map_address(address):
|
||||
nproc = util.selected_process()
|
||||
mapper = STATE.trace.memory_mapper
|
||||
base, addr = mapper.map(nproc, address)
|
||||
if base != addr.space:
|
||||
STATE.trace.create_overlay_space(base, addr.space)
|
||||
return (base, addr)
|
||||
|
||||
|
||||
def ghidra_trace_put_generic(node):
|
||||
"""
|
||||
Put the current thread's frames into the Ghidra trace
|
||||
"""
|
||||
|
||||
STATE.require_tx()
|
||||
with STATE.client.batch() as b:
|
||||
put_generic(node)
|
||||
|
||||
|
||||
def ghidra_trace_put_all():
|
||||
"""
|
||||
Put everything currently selected into the Ghidra trace
|
||||
@ -1248,8 +1436,8 @@ def ghidra_trace_put_all():
|
||||
put_breakpoints()
|
||||
put_available()
|
||||
ghidra_trace_putreg()
|
||||
ghidra_trace_putmem("$pc 1")
|
||||
ghidra_trace_putmem("$sp 1")
|
||||
ghidra_trace_putmem(util.get_pc(), 1)
|
||||
ghidra_trace_putmem(util.get_sp(), 1)
|
||||
|
||||
|
||||
def ghidra_trace_install_hooks():
|
||||
@ -1318,34 +1506,42 @@ def ghidra_util_wait_stopped(timeout=1):
|
||||
raise RuntimeError('Timed out waiting for thread to stop')
|
||||
|
||||
|
||||
def dbg():
|
||||
return util.get_debugger()
|
||||
def get_prompt_text():
|
||||
try:
|
||||
return util.dbg.get_prompt_text()
|
||||
except util.DebuggeeRunningException:
|
||||
return 'Running>'
|
||||
|
||||
|
||||
SHOULD_WAIT = ['GO', 'STEP_BRANCH', 'STEP_INTO', 'STEP_OVER']
|
||||
@util.dbg.eng_thread
|
||||
def exec_cmd(cmd):
|
||||
dbg = util.dbg
|
||||
dbg.cmd(cmd, quiet=False)
|
||||
stat = dbg.exec_status()
|
||||
if stat != 'BREAK':
|
||||
dbg.wait()
|
||||
|
||||
|
||||
def repl():
|
||||
print("This is the dbgeng.dll (WinDbg) REPL. To drop to Python3, press Ctrl-C.")
|
||||
print("")
|
||||
print("This is the Windows Debugger REPL. To drop to Python, type .exit")
|
||||
while True:
|
||||
# TODO: Implement prompt retrieval in PR to pybag?
|
||||
print('dbg> ', end='')
|
||||
print(get_prompt_text(), end=' ')
|
||||
try:
|
||||
cmd = input().strip()
|
||||
if not cmd:
|
||||
if cmd == '':
|
||||
continue
|
||||
dbg().cmd(cmd, quiet=False)
|
||||
stat = dbg().exec_status()
|
||||
if stat != 'BREAK':
|
||||
dbg().wait()
|
||||
else:
|
||||
pass
|
||||
# dbg().dispatch_events()
|
||||
elif cmd == '.exit':
|
||||
break
|
||||
exec_cmd(cmd)
|
||||
except KeyboardInterrupt as e:
|
||||
util.dbg.interrupt()
|
||||
except util.DebuggeeRunningException as e:
|
||||
print("")
|
||||
print("You have left the dbgeng REPL and are now at the Python3 interpreter.")
|
||||
print("use repl() to re-enter.")
|
||||
return
|
||||
except:
|
||||
# Assume cmd() has already output the error
|
||||
pass
|
||||
print("Debuggee is Running. Use Ctrl-C to interrupt.")
|
||||
except BaseException as e:
|
||||
pass # Error is printed by another mechanism
|
||||
print("")
|
||||
print("You have left the Windows Debugger REPL and are now at the Python "
|
||||
"interpreter.")
|
||||
print("To re-enter, type repl()")
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,20 @@
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
import os
|
||||
|
||||
|
||||
def module_locator():
|
||||
return os.path.dirname(os.path.realpath(__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.
|
||||
##
|
||||
from ctypes import *
|
||||
|
||||
from comtypes.gen import DbgMod
|
||||
from comtypes.hresult import S_OK, S_FALSE
|
||||
from pybag.dbgeng import exception
|
||||
|
||||
from . import imodelobject as mo
|
||||
|
||||
|
||||
class DataModelManager(object):
|
||||
def __init__(self, mgr):
|
||||
self._mgr = mgr
|
||||
exception.wrap_comclass(self._mgr)
|
||||
|
||||
def Release(self):
|
||||
cnt = self._mgr.Release()
|
||||
if cnt == 0:
|
||||
self._mgr = None
|
||||
return cnt
|
||||
|
||||
# DataModelManager
|
||||
|
||||
def GetRootNamespace(self):
|
||||
root = POINTER(DbgMod.IModelObject)()
|
||||
hr = self._mgr.GetRootNamespace(byref(root))
|
||||
exception.check_err(hr)
|
||||
return mo.ModelObject(root)
|
||||
|
||||
def AcquireNamedModel(self, modelName, modelObject):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def Close(self):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def CreateNoValue(self, object):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def CreateErrorObject(self, error, message, object):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def CreateTypedObject(self, context, objectLocation, objectType, object):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def CreateTypedObjectByReference(self, context, objectLocation, objectType, object):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def CreateSyntheticObject(self, context, object):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def CreateDataModelObject(self, dataModel, object):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def CreateTypedIntrinsicObject(self, intrinsicData, type, object):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def CreateIntrinsicObject(self, objectKind, intrinsicData, object):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetModelForTypeSignature(self, typeSignature, dataModel):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetModelForType(self, type, dataModel, typeSignature, wildcardMatches):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def RegisterExtensionForTypeSignature(self, typeSignature, dataModel):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def RegisterModelForTypeSignature(self, typeSignature, dataModel):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def RegisterNamedModel(self, modelName, modelObject):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def UnregisterExtensionForTypeSignature(self, dataModel, typeSignature):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def UnregisterModelForTypeSignature(self, dataModel, typeSignature):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def UnregisterNamedModel(self, modelName):
|
||||
raise exception.E_NOTIMPL_Error
|
@ -0,0 +1,44 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
from ctypes import *
|
||||
|
||||
from comtypes.gen import DbgMod
|
||||
from comtypes.hresult import S_OK, S_FALSE
|
||||
from pybag.dbgeng import exception
|
||||
from pybag.dbgeng import win32
|
||||
|
||||
|
||||
class DebugHost(object):
|
||||
def __init__(self, host):
|
||||
self._host = host
|
||||
exception.wrap_comclass(self._host)
|
||||
|
||||
def Release(self):
|
||||
cnt = self._host.Release()
|
||||
if cnt == 0:
|
||||
self._host = None
|
||||
return cnt
|
||||
|
||||
# DebugHost
|
||||
|
||||
def GetCurrentContext(self, context):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetDefaultMetadata(self, metadata):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetHostDefinedInterface(self, hostUnk):
|
||||
raise exception.E_NOTIMPL_Error
|
@ -0,0 +1,44 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
from ctypes import *
|
||||
|
||||
from comtypes.gen import DbgMod
|
||||
from comtypes.hresult import S_OK, S_FALSE
|
||||
from pybag.dbgeng import exception
|
||||
|
||||
from .idatamodelmanager import DataModelManager
|
||||
from .idebughost import DebugHost
|
||||
|
||||
|
||||
class HostDataModelAccess(object):
|
||||
def __init__(self, hdma):
|
||||
self._hdma = hdma
|
||||
exception.wrap_comclass(self._hdma)
|
||||
|
||||
def Release(self):
|
||||
cnt = self._hdma.Release()
|
||||
if cnt == 0:
|
||||
self._hdma = None
|
||||
return cnt
|
||||
|
||||
# HostDataModelAccess
|
||||
|
||||
def GetDataModel(self):
|
||||
manager = POINTER(DbgMod.IDataModelManager)()
|
||||
host = POINTER(DbgMod.IDebugHost)()
|
||||
hr = self._hdma.GetDataModel(byref(manager), byref(host))
|
||||
exception.check_err(hr)
|
||||
return (DataModelManager(manager), DebugHost(host))
|
@ -0,0 +1,42 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
from ctypes import *
|
||||
|
||||
from comtypes import COMError
|
||||
from comtypes.gen import DbgMod
|
||||
from comtypes.hresult import S_OK, S_FALSE
|
||||
from pybag.dbgeng import exception
|
||||
|
||||
from .imodeliterator import ModelIterator
|
||||
|
||||
|
||||
class IterableConcept(object):
|
||||
def __init__(self, concept):
|
||||
self._concept = concept
|
||||
concept.AddRef()
|
||||
|
||||
# IterableConcept
|
||||
|
||||
def GetDefaultIndexDimensionality(self, context, dimensionality):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetIterator(self, context):
|
||||
iterator = POINTER(DbgMod.IModelIterator)()
|
||||
try:
|
||||
self._concept.GetIterator(context._obj, byref(iterator))
|
||||
except COMError as ce:
|
||||
return None
|
||||
return ModelIterator(iterator)
|
@ -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.
|
||||
##
|
||||
from ctypes import *
|
||||
|
||||
from comtypes import BSTR
|
||||
from comtypes.gen import DbgMod
|
||||
from comtypes.hresult import S_OK, S_FALSE
|
||||
from pybag.dbgeng import exception
|
||||
|
||||
from . import imodelobject as mo
|
||||
|
||||
|
||||
class KeyEnumerator(object):
|
||||
def __init__(self, keys):
|
||||
self._keys = keys
|
||||
exception.wrap_comclass(self._keys)
|
||||
|
||||
def Release(self):
|
||||
cnt = self._keys.Release()
|
||||
if cnt == 0:
|
||||
self._keys = None
|
||||
return cnt
|
||||
|
||||
# KeyEnumerator
|
||||
|
||||
def GetNext(self):
|
||||
key = BSTR()
|
||||
value = POINTER(DbgMod.IModelObject)()
|
||||
store = POINTER(DbgMod.IKeyStore)()
|
||||
hr = self._keys.GetNext(byref(key), byref(value), byref(store))
|
||||
if hr != S_OK:
|
||||
return (None, None)
|
||||
return (key, mo.ModelObject(value))
|
||||
|
||||
def Reset(self):
|
||||
hr = self._keys.Reset()
|
||||
exception.check_err(hr)
|
@ -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.
|
||||
##
|
||||
from ctypes import *
|
||||
|
||||
from comtypes import COMError
|
||||
from comtypes.gen import DbgMod
|
||||
from comtypes.hresult import S_OK, S_FALSE
|
||||
from pybag.dbgeng import exception
|
||||
|
||||
from . import imodelobject as mo
|
||||
|
||||
|
||||
class ModelIterator(object):
|
||||
def __init__(self, iter):
|
||||
self._iter = iter
|
||||
iter.AddRef()
|
||||
|
||||
# ModelIterator
|
||||
|
||||
def GetNext(self, dimensions):
|
||||
object = POINTER(DbgMod.IModelObject)()
|
||||
indexer = POINTER(DbgMod.IModelObject)()
|
||||
metadata = POINTER(DbgMod.IKeyStore)()
|
||||
try:
|
||||
self._iter.GetNext(byref(object), dimensions,
|
||||
byref(indexer), byref(metadata))
|
||||
except COMError as ce:
|
||||
return None
|
||||
index = mo.ModelObject(indexer)
|
||||
id = index.GetIntrinsicValue().value
|
||||
return (id, mo.ModelObject(object))
|
||||
|
||||
def Reset(self):
|
||||
hr = self._keys.Reset()
|
||||
exception.check_err(hr)
|
@ -0,0 +1,274 @@
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
from ctypes import *
|
||||
from enum import Enum
|
||||
|
||||
from comtypes import IUnknown, COMError
|
||||
from comtypes.automation import IID, VARIANT
|
||||
from comtypes.gen import DbgMod
|
||||
from comtypes.hresult import S_OK, S_FALSE
|
||||
from pybag.dbgeng import exception
|
||||
|
||||
from comtypes.gen.DbgMod import *
|
||||
|
||||
from .iiterableconcept import IterableConcept
|
||||
from .ikeyenumerator import KeyEnumerator
|
||||
|
||||
|
||||
class ModelObjectKind(Enum):
|
||||
PROPERTY_ACCESSOR = 0
|
||||
CONTEXT = 1
|
||||
TARGET_OBJECT = 2
|
||||
TARGET_OBJECT_REFERENCE = 3
|
||||
SYNTHETIC = 4
|
||||
NO_VALUE = 5
|
||||
ERROR = 6
|
||||
INTRINSIC = 7
|
||||
METHOD = 8
|
||||
KEY_REFERENCE = 9
|
||||
|
||||
|
||||
class ModelObject(object):
|
||||
def __init__(self, obj):
|
||||
self._obj = obj
|
||||
self.concept = None
|
||||
exception.wrap_comclass(self._obj)
|
||||
|
||||
def Release(self):
|
||||
print("RELEASE ModelObject")
|
||||
breakpoint()
|
||||
cnt = self._obj.Release()
|
||||
if cnt == 0:
|
||||
self._obj = None
|
||||
return cnt
|
||||
|
||||
# ModelObject
|
||||
|
||||
def AddParentModel(self, model, contextObject, override):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def ClearConcepts(self):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def ClearKeys(self):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def Compare(self, other, equal):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def Dereference(self, object):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def EnumerateKeyReferences(self):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def EnumerateKeys(self):
|
||||
keys = POINTER(DbgMod.IKeyEnumerator)()
|
||||
hr = self._obj.EnumerateKeys(byref(keys))
|
||||
if hr != S_OK:
|
||||
return None
|
||||
return KeyEnumerator(keys)
|
||||
|
||||
def EnumerateKeyValues(self):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def EnumerateRawReferences(self, kind, searchFlags):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def EnumerateRawValues(self, kind, searchFlag):
|
||||
keys = POINTER(DbgMod.IRawEnumerator)()
|
||||
hr = self._obj.EnumerateRawValues(kind, searchFlag, byref(keys))
|
||||
if hr != S_OK:
|
||||
return None
|
||||
return RawEnumerator(keys, kind)
|
||||
|
||||
def GetConcept(self, ref):
|
||||
ifc = POINTER(IUnknown)()
|
||||
metadata = POINTER(DbgMod.IKeyStore)()
|
||||
hr = self._obj.GetConcept(ref._iid_, byref(ifc), byref(metadata))
|
||||
if hr != S_OK:
|
||||
return None
|
||||
return cast(ifc, POINTER(ref))
|
||||
|
||||
def GetContext(self, context):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetContextForDataModel(self, dataModelObject, context):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetIntrinsicValue(self):
|
||||
var = VARIANT()
|
||||
hr = self._obj.GetIntrinsicValue(var)
|
||||
if hr != S_OK:
|
||||
return None
|
||||
return var
|
||||
|
||||
def GetIntrinsicValueAs(self, vt):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetKey(self, key, object, metadata):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetKeyReference(self, key, objectReference, metadata):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetKeyValue(self, key):
|
||||
kbuf = cast(c_wchar_p(key), POINTER(c_ushort))
|
||||
value = POINTER(DbgMod.IModelObject)()
|
||||
store = POINTER(DbgMod.IKeyStore)()
|
||||
hr = self._obj.GetKeyValue(kbuf, byref(value), byref(store))
|
||||
if hr != S_OK:
|
||||
return None
|
||||
return ModelObject(value)
|
||||
|
||||
def GetKind(self):
|
||||
kind = c_long()
|
||||
hr = self._obj.GetKind(kind)
|
||||
exception.check_err(hr)
|
||||
return kind
|
||||
|
||||
def GetLocation(self, location):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetNumberOfParentModels(self, numModels):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetParentModel(self, i, model, context):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetRawReference(self, kind, name, searchFlags, object):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetRawValue(self, kind, name, searchFlags, object):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def GetTargetInfo(self):
|
||||
location = POINTER(DbgMod._Location)()
|
||||
type = POINTER(DbgMod.IDebugHostType)()
|
||||
hr = self._obj.GetTargetInfo(location, byref(type))
|
||||
exception.check_err(hr)
|
||||
return type
|
||||
|
||||
def GetTypeInfo(self, type):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def IsEqualTo(self, other, equal):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def RemoveParentModel(self, model):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def SetConcept(self, ref, interface, metadata):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def SetContextForDataModel(self, modelObject, context):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def SetKey(self, key, object, metadata):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def SetKeyValue(self, key, object):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
def TryCastToRuntimeType(self, runtimeTypedObject):
|
||||
raise exception.E_NOTIMPL_Error
|
||||
|
||||
# Auxiliary
|
||||
|
||||
def GetKeyValueMap(self):
|
||||
map = {}
|
||||
keys = self.EnumerateKeys()
|
||||
(k, v) = keys.GetNext()
|
||||
while k is not None:
|
||||
map[k.value] = self.GetKeyValue(k.value)
|
||||
(k, v) = keys.GetNext()
|
||||
return map
|
||||
|
||||
def GetRawValueMap(self):
|
||||
map = {}
|
||||
kind = self.GetKind()
|
||||
keys = self.EnumerateRawValues(kind, c_long(0))
|
||||
(k, v) = keys.GetNext()
|
||||
while k is not None:
|
||||
map[k.value] = v
|
||||
(k, v) = keys.GetNext()
|
||||
return map
|
||||
|
||||
def GetAttributes(self):
|
||||
map = {}
|
||||
kind = self.GetKind()
|
||||
if kind == ModelObjectKind.ERROR:
|
||||
return map
|
||||
if kind == ModelObjectKind.INTRINSIC or \
|
||||
kind == ModelObjectKind.TARGET_OBJECT or \
|
||||
kind == ModelObjectKind.TARGET_OBJECT_REFERENCE:
|
||||
return self.GetRawValueMap()
|
||||
return self.GetKeyValueMap()
|
||||
|
||||
def GetElements(self):
|
||||
list = []
|
||||
if self.concept is None:
|
||||
iconcept = self.GetConcept(DbgMod.IIterableConcept)
|
||||
if iconcept is None:
|
||||
return list
|
||||
self.concept = IterableConcept(iconcept)
|
||||
iter = self.concept.GetIterator(self)
|
||||
if iter is None:
|
||||
print("WARNING: iter is None")
|
||||
return list
|
||||
next = iter.GetNext(1)
|
||||
while next is not None:
|
||||
list.append(next)
|
||||
next = iter.GetNext(1)
|
||||
return list
|
||||
|
||||
def GetElement(self, key):
|
||||
list = self.GetElements()
|
||||
for k, v in list:
|
||||
if k == key:
|
||||
return v
|
||||
return None
|
||||
|
||||
def GetOffspring(self, path):
|
||||
next = self
|
||||
for element in path:
|
||||
if element.startswith("["):
|
||||
idx = element[1:len(element)-1]
|
||||
if "x" not in idx:
|
||||
idx = int(idx)
|
||||
else:
|
||||
idx = int(idx, 16)
|
||||
next = next.GetElement(idx)
|
||||
else:
|
||||
next = next.GetKeyValue(element)
|
||||
if next is None:
|
||||
print(f"{element} not found")
|
||||
return next
|
||||
|
||||
def GetValue(self):
|
||||
value = self.GetIntrinsicValue()
|
||||
if value is None:
|
||||
return None
|
||||
if value.vt == 0xd:
|
||||
return None
|
||||
return value.value
|
||||
|
||||
def GetTypeKind(self):
|
||||
kind = self.GetKind()
|
||||
if kind == ModelObjectKind.TARGET_OBJECT or \
|
||||
kind == ModelObjectKind.INTRINSIC:
|
||||
return self.GetTargetInfo()
|
||||
return None
|
@ -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.
|
||||
##
|
||||
from ctypes import *
|
||||
|
||||
from comtypes import BSTR
|
||||
from comtypes.gen import DbgMod
|
||||
from comtypes.hresult import S_OK, S_FALSE
|
||||
from pybag.dbgeng import exception
|
||||
|
||||
from . import imodelobject as mo
|
||||
|
||||
|
||||
class RawEnumerator(object):
|
||||
def __init__(self, keys, kind):
|
||||
self._keys = keys
|
||||
self._kind = kind
|
||||
exception.wrap_comclass(self._keys)
|
||||
|
||||
def Release(self):
|
||||
cnt = self._keys.Release()
|
||||
if cnt == 0:
|
||||
self._keys = None
|
||||
return cnt
|
||||
|
||||
# KeyEnumerator
|
||||
|
||||
def GetNext(self):
|
||||
key = BSTR()
|
||||
value = POINTER(DbgMod.IModelObject)()
|
||||
hr = self._keys.GetNext(byref(key), byref(self._kind), byref(value))
|
||||
if hr != S_OK:
|
||||
return (None, None)
|
||||
return (key, mo.ModelObject(value))
|
||||
|
||||
def Reset(self):
|
||||
hr = self._keys.Reset()
|
||||
exception.check_err(hr)
|
@ -13,20 +13,26 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
from _ctypes_test import func
|
||||
import functools
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from comtypes.hresult import S_OK
|
||||
from pybag import pydbg
|
||||
from pybag.dbgeng.callbacks import EventHandler
|
||||
from pybag.dbgeng import core as DbgEng
|
||||
from pybag.dbgeng import exception
|
||||
from pybag.dbgeng.callbacks import EventHandler
|
||||
from pybag.dbgeng.idebugbreakpoint import DebugBreakpoint
|
||||
|
||||
from . import commands, util
|
||||
|
||||
|
||||
ALL_EVENTS = 0xFFFF
|
||||
|
||||
|
||||
class HookState(object):
|
||||
__slots__ = ('installed', 'mem_catchpoint')
|
||||
|
||||
@ -36,7 +42,8 @@ class HookState(object):
|
||||
|
||||
|
||||
class ProcessState(object):
|
||||
__slots__ = ('first', 'regions', 'modules', 'threads', 'breaks', 'watches', 'visited')
|
||||
__slots__ = ('first', 'regions', 'modules', 'threads',
|
||||
'breaks', 'watches', 'visited', 'waiting')
|
||||
|
||||
def __init__(self):
|
||||
self.first = True
|
||||
@ -48,8 +55,10 @@ class ProcessState(object):
|
||||
self.watches = False
|
||||
# For frames and threads that have already been synced since last stop
|
||||
self.visited = set()
|
||||
self.waiting = False
|
||||
|
||||
def record(self, description=None):
|
||||
# print("RECORDING")
|
||||
first = self.first
|
||||
self.first = False
|
||||
if description is not None:
|
||||
@ -64,15 +73,17 @@ class ProcessState(object):
|
||||
if thread is not None:
|
||||
if first or thread not in self.visited:
|
||||
commands.putreg()
|
||||
commands.putmem("$pc", "1", display_result=False)
|
||||
commands.putmem("$sp", "1", display_result=False)
|
||||
commands.putmem('0x{:x}'.format(util.get_pc()),
|
||||
"1", display_result=False)
|
||||
commands.putmem('0x{:x}'.format(util.get_sp()),
|
||||
"1", display_result=False)
|
||||
commands.put_frames()
|
||||
self.visited.add(thread)
|
||||
frame = util.selected_frame()
|
||||
hashable_frame = (thread, frame)
|
||||
if first or hashable_frame not in self.visited:
|
||||
self.visited.add(hashable_frame)
|
||||
if first or self.regions or self.threads or self.modules:
|
||||
if first or self.regions:
|
||||
commands.put_regions()
|
||||
self.regions = False
|
||||
if first or self.modules:
|
||||
@ -91,8 +102,9 @@ class ProcessState(object):
|
||||
commands.STATE.trace.snapshot(description)
|
||||
proc = util.selected_process()
|
||||
ipath = commands.PROCESS_PATTERN.format(procnum=proc)
|
||||
commands.STATE.trace.proxy_object_path(
|
||||
ipath).set_value('_exit_code', exit_code)
|
||||
procobj = commands.STATE.trace.proxy_object_path(ipath)
|
||||
procobj.set_value('Exit Code', exit_code)
|
||||
procobj.set_value('State', 'TERMINATED')
|
||||
|
||||
|
||||
class BrkState(object):
|
||||
@ -120,52 +132,92 @@ BRK_STATE = BrkState()
|
||||
PROC_STATE = {}
|
||||
|
||||
|
||||
def log_errors(func):
|
||||
'''
|
||||
Wrap a function in a try-except that prints and reraises the
|
||||
exception.
|
||||
|
||||
This is needed because pybag and/or the COM wrappers do not print
|
||||
exceptions that occur during event callbacks.
|
||||
'''
|
||||
@functools.wraps(func)
|
||||
def _func(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
raise
|
||||
return _func
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_state_changed(*args):
|
||||
#print("ON_STATE_CHANGED")
|
||||
#print(args[0])
|
||||
# print("ON_STATE_CHANGED")
|
||||
# print(args)
|
||||
if args[0] == DbgEng.DEBUG_CES_CURRENT_THREAD:
|
||||
return on_thread_selected(args)
|
||||
elif args[0] == DbgEng.DEBUG_CES_BREAKPOINTS:
|
||||
return on_breakpoint_modified(args)
|
||||
elif args[0] == DbgEng.DEBUG_CES_RADIX:
|
||||
util.set_convenience_variable('output-radix', args[1])
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
return S_OK
|
||||
elif args[0] == DbgEng.DEBUG_CES_EXECUTION_STATUS:
|
||||
util.dbg._ces_exec_status(args[1])
|
||||
proc = util.selected_process()
|
||||
if args[1] & DbgEng.DEBUG_STATUS_INSIDE_WAIT:
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
if proc in PROC_STATE:
|
||||
# Process may have exited (so deleted) first
|
||||
PROC_STATE[proc].waiting = True
|
||||
return S_OK
|
||||
if proc in PROC_STATE:
|
||||
# Process may have exited (so deleted) first.
|
||||
PROC_STATE[proc].waiting = False
|
||||
trace = commands.STATE.trace
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("State changed proc {}".format(proc)):
|
||||
commands.put_state(proc)
|
||||
if args[1] == DbgEng.DEBUG_STATUS_BREAK:
|
||||
return on_stop(args)
|
||||
elif args[1] == DbgEng.DEBUG_STATUS_NO_DEBUGGEE:
|
||||
return on_exited(proc)
|
||||
else:
|
||||
return on_cont(args)
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
return S_OK
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_debuggee_changed(*args):
|
||||
#print("ON_DEBUGGEE_CHANGED")
|
||||
# print("ON_DEBUGGEE_CHANGED: args={}".format(args))
|
||||
# sys.stdout.flush()
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
if args[1] == DbgEng.DEBUG_CDS_REGISTERS:
|
||||
on_register_changed(args[0][1])
|
||||
#if args[1] == DbgEng.DEBUG_CDS_DATA:
|
||||
# on_memory_changed(args[0][1])
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
return S_OK
|
||||
if args[0] == DbgEng.DEBUG_CDS_REGISTERS:
|
||||
on_register_changed(args[1])
|
||||
if args[0] == DbgEng.DEBUG_CDS_DATA:
|
||||
on_memory_changed(args[1])
|
||||
return S_OK
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_session_status_changed(*args):
|
||||
#print("ON_STATUS_CHANGED")
|
||||
# print("ON_STATUS_CHANGED: args={}".format(args))
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
if args[0] == DbgEng.DEBUG_SESSION_ACTIVE or args[0] == DbgEng.DEBUG_SSESION_REBOOT:
|
||||
if args[0] == DbgEng.DEBUG_SESSION_ACTIVE or args[0] == DbgEng.DEBUG_SESSION_REBOOT:
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
with trace.open_tx("New Session {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_symbol_state_changed(*args):
|
||||
#print("ON_SYMBOL_STATE_CHANGED")
|
||||
# print("ON_SYMBOL_STATE_CHANGED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
@ -174,31 +226,33 @@ def on_symbol_state_changed(*args):
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_system_error(*args):
|
||||
print("ON_SYSTEM_ERROR")
|
||||
print(hex(args[0]))
|
||||
print("ON_SYSTEM_ERROR: args={}".format(args))
|
||||
# print(hex(args[0]))
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
with trace.open_tx("System Error {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_BREAK
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_new_process(*args):
|
||||
#print("ON_NEW_PROCESS")
|
||||
# print("ON_NEW_PROCESS")
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_BREAK
|
||||
|
||||
|
||||
def on_process_selected():
|
||||
#print("PROCESS_SELECTED")
|
||||
# print("PROCESS_SELECTED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
@ -211,9 +265,11 @@ def on_process_selected():
|
||||
commands.activate()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_process_deleted(*args):
|
||||
#print("ON_PROCESS_DELETED")
|
||||
proc = args[0]
|
||||
# print("ON_PROCESS_DELETED")
|
||||
exit_code = args[0]
|
||||
proc = util.selected_process()
|
||||
on_exited(proc)
|
||||
if proc in PROC_STATE:
|
||||
del PROC_STATE[proc]
|
||||
@ -226,8 +282,9 @@ def on_process_deleted(*args):
|
||||
return DbgEng.DEBUG_STATUS_BREAK
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_threads_changed(*args):
|
||||
#print("ON_THREADS_CHANGED")
|
||||
# print("ON_THREADS_CHANGED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
@ -236,7 +293,8 @@ def on_threads_changed(*args):
|
||||
|
||||
|
||||
def on_thread_selected(*args):
|
||||
#print("THREAD_SELECTED")
|
||||
# print("THREAD_SELECTED: args={}".format(args))
|
||||
# sys.stdout.flush()
|
||||
nthrd = args[0][1]
|
||||
nproc = util.selected_process()
|
||||
if nproc not in PROC_STATE:
|
||||
@ -246,12 +304,17 @@ def on_thread_selected(*args):
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Thread {}.{} selected".format(nproc, nthrd)):
|
||||
PROC_STATE[nproc].record()
|
||||
commands.activate()
|
||||
commands.put_state(nproc)
|
||||
state = PROC_STATE[nproc]
|
||||
if state.waiting:
|
||||
state.record_continued()
|
||||
else:
|
||||
state.record()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_register_changed(regnum):
|
||||
#print("REGISTER_CHANGED")
|
||||
# print("REGISTER_CHANGED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
@ -264,7 +327,25 @@ def on_register_changed(regnum):
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_memory_changed(space):
|
||||
if space != DbgEng.DEBUG_DATA_SPACE_VIRTUAL:
|
||||
return
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
# Not great, but invalidate the whole space
|
||||
# UI will only re-fetch what it needs
|
||||
# But, some observations will not be recovered
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Memory changed"):
|
||||
commands.putmem_state(0, 2**64, 'unknown')
|
||||
|
||||
|
||||
def on_cont(*args):
|
||||
# print("ON CONT")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
@ -279,13 +360,14 @@ def on_cont(*args):
|
||||
|
||||
|
||||
def on_stop(*args):
|
||||
# print("ON STOP")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
print("not in state")
|
||||
# print("not in state")
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
print("no trace")
|
||||
# print("no trace")
|
||||
return
|
||||
state = PROC_STATE[proc]
|
||||
state.visited.clear()
|
||||
@ -297,8 +379,9 @@ def on_stop(*args):
|
||||
|
||||
|
||||
def on_exited(proc):
|
||||
# print("ON EXITED")
|
||||
if proc not in PROC_STATE:
|
||||
print("not in state")
|
||||
# print("not in state")
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
@ -313,8 +396,9 @@ def on_exited(proc):
|
||||
commands.activate()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_modules_changed(*args):
|
||||
#print("ON_MODULES_CHANGED")
|
||||
# print("ON_MODULES_CHANGED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
@ -323,6 +407,7 @@ def on_modules_changed(*args):
|
||||
|
||||
|
||||
def on_breakpoint_created(bp):
|
||||
# print("ON_BREAKPOINT_CREATED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
@ -340,7 +425,7 @@ def on_breakpoint_created(bp):
|
||||
|
||||
|
||||
def on_breakpoint_modified(*args):
|
||||
#print("BREAKPOINT_MODIFIED")
|
||||
# print("BREAKPOINT_MODIFIED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
@ -352,9 +437,9 @@ def on_breakpoint_modified(*args):
|
||||
ibobj = trace.create_object(ibpath)
|
||||
bpid = args[0][1]
|
||||
try:
|
||||
bp = dbg()._control.GetBreakpointById(bpid)
|
||||
bp = util.dbg._base._control.GetBreakpointById(bpid)
|
||||
except exception.E_NOINTERFACE_Error:
|
||||
dbg().breakpoints._remove_stale(bpid)
|
||||
util.dbg._base.breakpoints._remove_stale(bpid)
|
||||
return on_breakpoint_deleted(bpid)
|
||||
return on_breakpoint_created(bp)
|
||||
|
||||
@ -373,33 +458,27 @@ def on_breakpoint_deleted(bpid):
|
||||
trace.proxy_object_path(bpath).remove(tree=True)
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_breakpoint_hit(*args):
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
# print("ON_BREAKPOINT_HIT: args={}".format(args))
|
||||
return DbgEng.DEBUG_STATUS_BREAK
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_exception(*args):
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
# print("ON_EXCEPTION: args={}".format(args))
|
||||
return DbgEng.DEBUG_STATUS_BREAK
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def install_hooks():
|
||||
# print("Installing hooks")
|
||||
if HOOK_STATE.installed:
|
||||
return
|
||||
HOOK_STATE.installed = True
|
||||
|
||||
events = dbg().events
|
||||
|
||||
events = util.dbg._base.events
|
||||
|
||||
events.engine_state(handler=on_state_changed)
|
||||
events.debuggee_state(handler=on_debuggee_changed)
|
||||
events.session_status(handler=on_session_status_changed)
|
||||
@ -412,20 +491,24 @@ def install_hooks():
|
||||
events.exit_thread(handler=on_threads_changed)
|
||||
events.module_load(handler=on_modules_changed)
|
||||
events.unload_module(handler=on_modules_changed)
|
||||
|
||||
#events.breakpoint(handler=on_breakpoint_hit)
|
||||
#events.exception(handler=on_exception)
|
||||
|
||||
events.breakpoint(handler=on_breakpoint_hit)
|
||||
events.exception(handler=on_exception)
|
||||
|
||||
|
||||
@util.dbg.eng_thread
|
||||
def remove_hooks():
|
||||
# print("Removing hooks")
|
||||
if not HOOK_STATE.installed:
|
||||
return
|
||||
HOOK_STATE.installed = False
|
||||
dbg()._reset_callbacks()
|
||||
util.dbg._base._reset_callbacks()
|
||||
|
||||
|
||||
def enable_current_process():
|
||||
# print("Enable current process")
|
||||
proc = util.selected_process()
|
||||
# print("proc: {}".format(proc))
|
||||
PROC_STATE[proc] = ProcessState()
|
||||
|
||||
|
||||
@ -434,6 +517,3 @@ def disable_current_process():
|
||||
if proc in PROC_STATE:
|
||||
# Silently ignore already disabled
|
||||
del PROC_STATE[proc]
|
||||
|
||||
def dbg():
|
||||
return util.get_debugger()
|
||||
|
@ -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.
|
||||
##
|
||||
import ctypes
|
||||
import os
|
||||
import platform
|
||||
|
||||
import comtypes
|
||||
import comtypes.client
|
||||
from ghidradbg import dbgmodel
|
||||
|
||||
|
||||
ctypes.windll.kernel32.SetErrorMode(0x0001 | 0x0002 | 0x8000)
|
||||
|
||||
|
||||
try:
|
||||
from comtypes.gen import DbgMod
|
||||
except:
|
||||
tlb = os.path.join(dbgmodel.module_locator(), 'tlb', 'dbgmodel.tlb')
|
||||
print(f"Loading TLB: {tlb}")
|
||||
comtypes.client.GetModule(tlb)
|
||||
from comtypes.gen import DbgMod
|
@ -14,21 +14,21 @@
|
||||
# limitations under the License.
|
||||
##
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
from contextlib import redirect_stdout
|
||||
from io import StringIO
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ghidratrace import sch
|
||||
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
||||
|
||||
from pybag import pydbg
|
||||
from pybag.dbgeng import core as DbgEng
|
||||
from pybag.dbgeng import core as DbgEng, exception
|
||||
|
||||
from . import util, commands
|
||||
from contextlib import redirect_stdout
|
||||
from io import StringIO
|
||||
|
||||
|
||||
REGISTRY = MethodRegistry(ThreadPoolExecutor(max_workers=1))
|
||||
REGISTRY = MethodRegistry(ThreadPoolExecutor(
|
||||
max_workers=1, thread_name_prefix='MethodRegistry'))
|
||||
|
||||
|
||||
def extre(base, ext):
|
||||
@ -39,13 +39,16 @@ AVAILABLE_PATTERN = re.compile('Available\[(?P<pid>\\d*)\]')
|
||||
WATCHPOINT_PATTERN = re.compile('Watchpoints\[(?P<watchnum>\\d*)\]')
|
||||
BREAKPOINT_PATTERN = re.compile('Breakpoints\[(?P<breaknum>\\d*)\]')
|
||||
BREAK_LOC_PATTERN = extre(BREAKPOINT_PATTERN, '\[(?P<locnum>\\d*)\]')
|
||||
PROCESS_PATTERN = re.compile('Processes\[(?P<procnum>\\d*)\]')
|
||||
PROC_BREAKS_PATTERN = extre(PROCESS_PATTERN, '\.Breakpoints')
|
||||
SESSIONS_PATTERN = re.compile('Sessions')
|
||||
SESSION_PATTERN = extre(SESSIONS_PATTERN, '\[(?P<snum>\\d*)\]')
|
||||
PROCESSES_PATTERN = extre(SESSION_PATTERN, '\.Processes')
|
||||
PROCESS_PATTERN = extre(PROCESSES_PATTERN, '\[(?P<procnum>\\d*)\]')
|
||||
PROC_BREAKS_PATTERN = extre(PROCESS_PATTERN, '\.Debug.Breakpoints')
|
||||
PROC_BREAKBPT_PATTERN = extre(PROC_BREAKS_PATTERN, '\[(?P<breaknum>\\d*)\]')
|
||||
ENV_PATTERN = extre(PROCESS_PATTERN, '\.Environment')
|
||||
THREADS_PATTERN = extre(PROCESS_PATTERN, '\.Threads')
|
||||
THREAD_PATTERN = extre(THREADS_PATTERN, '\[(?P<tnum>\\d*)\]')
|
||||
STACK_PATTERN = extre(THREAD_PATTERN, '\.Stack')
|
||||
STACK_PATTERN = extre(THREAD_PATTERN, '\.Stack.Frames')
|
||||
FRAME_PATTERN = extre(STACK_PATTERN, '\[(?P<level>\\d*)\]')
|
||||
REGS_PATTERN0 = extre(THREAD_PATTERN, '.Registers')
|
||||
REGS_PATTERN = extre(FRAME_PATTERN, '.Registers')
|
||||
@ -85,11 +88,12 @@ def find_proc_by_obj(object):
|
||||
|
||||
def find_proc_by_procbreak_obj(object):
|
||||
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
|
||||
"a BreakpointLocationContainer")
|
||||
"a BreakpointLocationContainer")
|
||||
|
||||
|
||||
def find_proc_by_procwatch_obj(object):
|
||||
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
|
||||
"a WatchpointContainer")
|
||||
"a WatchpointContainer")
|
||||
|
||||
|
||||
def find_proc_by_env_obj(object):
|
||||
@ -178,35 +182,45 @@ def find_bpt_by_obj(object):
|
||||
|
||||
shared_globals = dict()
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
# @util.dbg.eng_thread
|
||||
def execute(cmd: str, to_string: bool=False):
|
||||
"""Execute a CLI command."""
|
||||
#print("***{}***".format(cmd))
|
||||
#sys.stderr.flush()
|
||||
#sys.stdout.flush()
|
||||
"""Execute a Python3 command or script."""
|
||||
# print("***{}***".format(cmd))
|
||||
# sys.stderr.flush()
|
||||
# sys.stdout.flush()
|
||||
if to_string:
|
||||
data = StringIO()
|
||||
with redirect_stdout(data):
|
||||
exec("{}".format(cmd), shared_globals)
|
||||
exec(cmd, shared_globals)
|
||||
return data.getvalue()
|
||||
else:
|
||||
exec("{}".format(cmd), shared_globals)
|
||||
exec(cmd, shared_globals)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
# @util.dbg.eng_thread
|
||||
def evaluate(expr: str):
|
||||
"""Execute a CLI command."""
|
||||
return str(eval("{}".format(expr), shared_globals))
|
||||
"""Evaluate a Python3 expression."""
|
||||
return str(eval(expr, shared_globals))
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh", condition=util.dbg.use_generics)
|
||||
def refresh_generic(node: sch.OBJECT):
|
||||
"""List processes on pydbg's host system."""
|
||||
with commands.open_tracked_tx('Refresh Generic'):
|
||||
commands.ghidra_trace_put_generic(node)
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh', display='Refresh Available')
|
||||
def refresh_available(node: sch.Schema('AvailableContainer')):
|
||||
"""List processes on pydbg's host system."""
|
||||
with commands.open_tracked_tx('Refresh Available'):
|
||||
commands.ghidra_trace_put_available()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Breakpoints')
|
||||
def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
|
||||
"""
|
||||
Refresh the list of breakpoints (including locations for the current
|
||||
@ -216,14 +230,14 @@ def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
|
||||
commands.ghidra_trace_put_breakpoints()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Processes')
|
||||
def refresh_processes(node: sch.Schema('ProcessContainer')):
|
||||
"""Refresh the list of processes."""
|
||||
with commands.open_tracked_tx('Refresh Processes'):
|
||||
commands.ghidra_trace_put_threads()
|
||||
commands.ghidra_trace_put_processes()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Breakpoint Locations')
|
||||
def refresh_proc_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
||||
"""
|
||||
Refresh the breakpoint locations for the process.
|
||||
@ -235,20 +249,21 @@ def refresh_proc_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
||||
commands.ghidra_trace_put_breakpoints()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Environment')
|
||||
def refresh_environment(node: sch.Schema('Environment')):
|
||||
"""Refresh the environment descriptors (arch, os, endian)."""
|
||||
with commands.open_tracked_tx('Refresh Environment'):
|
||||
commands.ghidra_trace_put_environment()
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
|
||||
@REGISTRY.method(action='refresh', display='Refresh Threads')
|
||||
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
||||
"""Refresh the list of threads in the process."""
|
||||
with commands.open_tracked_tx('Refresh Threads'):
|
||||
commands.ghidra_trace_put_threads()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Stack')
|
||||
def refresh_stack(node: sch.Schema('Stack')):
|
||||
"""Refresh the backtrace for the thread."""
|
||||
tnum = find_thread_by_stack_obj(node)
|
||||
@ -256,7 +271,7 @@ def refresh_stack(node: sch.Schema('Stack')):
|
||||
commands.ghidra_trace_put_frames()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Registers')
|
||||
def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
||||
"""Refresh the register values for the frame."""
|
||||
tnum = find_thread_by_regs_obj(node)
|
||||
@ -264,14 +279,14 @@ def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
||||
commands.ghidra_trace_putreg()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Memory')
|
||||
def refresh_mappings(node: sch.Schema('Memory')):
|
||||
"""Refresh the list of memory regions for the process."""
|
||||
with commands.open_tracked_tx('Refresh Memory Regions'):
|
||||
commands.ghidra_trace_put_regions()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Modules')
|
||||
def refresh_modules(node: sch.Schema('ModuleContainer')):
|
||||
"""
|
||||
Refresh the modules and sections list for the process.
|
||||
@ -287,6 +302,7 @@ def activate_process(process: sch.Schema('Process')):
|
||||
"""Switch to the process."""
|
||||
find_proc_by_obj(process)
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_thread(thread: sch.Schema('Thread')):
|
||||
"""Switch to the thread."""
|
||||
@ -300,46 +316,54 @@ def activate_frame(frame: sch.Schema('StackFrame')):
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
@util.dbg.eng_thread
|
||||
def remove_process(process: sch.Schema('Process')):
|
||||
"""Remove the process."""
|
||||
find_proc_by_obj(process)
|
||||
dbg().detach()
|
||||
dbg().detach_proc()
|
||||
|
||||
|
||||
@REGISTRY.method(action='connect')
|
||||
@util.dbg.eng_thread
|
||||
def target(process: sch.Schema('Process'), spec: str):
|
||||
"""Connect to a target machine or process."""
|
||||
find_proc_by_obj(process)
|
||||
dbg().attach(spec)
|
||||
dbg().attach_kernel(spec)
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
@util.dbg.eng_thread
|
||||
def attach_obj(target: sch.Schema('Attachable')):
|
||||
"""Attach the process to the given target."""
|
||||
pid = find_availpid_by_obj(target)
|
||||
dbg().attach(pid)
|
||||
dbg().attach_proc(pid)
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
@util.dbg.eng_thread
|
||||
def attach_pid(pid: int):
|
||||
"""Attach the process to the given target."""
|
||||
dbg().attach(pid)
|
||||
dbg().attach_proc(pid)
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
@util.dbg.eng_thread
|
||||
def attach_name(process: sch.Schema('Process'), name: str):
|
||||
"""Attach the process to the given target."""
|
||||
dbg().atach(name)
|
||||
dbg().attach_proc(name)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@util.dbg.eng_thread
|
||||
def detach(process: sch.Schema('Process')):
|
||||
"""Detach the process's target."""
|
||||
dbg().detach()
|
||||
dbg().detach_proc()
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
def launch_loader(
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
"""
|
||||
Start a native process with the given command line, stopping at the ntdll initial breakpoint.
|
||||
"""
|
||||
@ -351,65 +375,72 @@ def launch_loader(
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
def launch(
|
||||
timeout: ParamDesc(int, display='Timeout'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
args: ParamDesc(str, display='Arguments')='',
|
||||
initial_break: ParamDesc(bool, display='Initial Break')=True,
|
||||
timeout: ParamDesc(int, display='Timeout')=-1):
|
||||
"""
|
||||
Run a native process with the given command line.
|
||||
"""
|
||||
command = file
|
||||
if args != None:
|
||||
command += " "+args
|
||||
commands.ghidra_trace_create(command, initial_break=False, timeout=timeout, start_trace=False)
|
||||
commands.ghidra_trace_create(
|
||||
command, initial_break=initial_break, timeout=timeout, start_trace=False)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@util.dbg.eng_thread
|
||||
def kill(process: sch.Schema('Process')):
|
||||
"""Kill execution of the process."""
|
||||
dbg().terminate()
|
||||
commands.ghidra_trace_kill()
|
||||
|
||||
|
||||
@REGISTRY.method(name='continue', action='resume')
|
||||
def _continue(process: sch.Schema('Process')):
|
||||
@REGISTRY.method(action='resume')
|
||||
def go(process: sch.Schema('Process')):
|
||||
"""Continue execution of the process."""
|
||||
dbg().go()
|
||||
util.dbg.run_async(lambda: dbg().go())
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def interrupt():
|
||||
def interrupt(process: sch.Schema('Process')):
|
||||
"""Interrupt the execution of the debugged program."""
|
||||
dbg()._control.SetInterrupt(DbgEng.DEBUG_INTERRUPT_ACTIVE)
|
||||
# SetInterrupt is reentrant, so bypass the thread checks
|
||||
util.dbg._protected_base._control.SetInterrupt(
|
||||
DbgEng.DEBUG_INTERRUPT_ACTIVE)
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_into')
|
||||
def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||
"""Step one instruction exactly."""
|
||||
find_thread_by_obj(thread)
|
||||
dbg().stepi(n)
|
||||
util.dbg.run_async(lambda: dbg().stepi(n))
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_over')
|
||||
def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||
"""Step one instruction, but proceed through subroutine calls."""
|
||||
find_thread_by_obj(thread)
|
||||
dbg().stepo(n)
|
||||
util.dbg.run_async(lambda: dbg().stepo(n))
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_out')
|
||||
def step_out(thread: sch.Schema('Thread')):
|
||||
"""Execute until the current stack frame returns."""
|
||||
find_thread_by_obj(thread)
|
||||
dbg().stepout()
|
||||
util.dbg.run_async(lambda: dbg().stepout())
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_to')
|
||||
def step_to(thread: sch.Schema('Thread'), address: Address, max=None):
|
||||
"""Continue execution up to the given address."""
|
||||
find_thread_by_obj(thread)
|
||||
return dbg().stepto(address.offset, max)
|
||||
# TODO: The address may need mapping.
|
||||
util.dbg.run_async(lambda: dbg().stepto(address.offset, max))
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_sw_execute')
|
||||
@util.dbg.eng_thread
|
||||
def break_address(process: sch.Schema('Process'), address: Address):
|
||||
"""Set a breakpoint."""
|
||||
find_proc_by_obj(process)
|
||||
@ -417,6 +448,7 @@ def break_address(process: sch.Schema('Process'), address: Address):
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_sw_execute')
|
||||
@util.dbg.eng_thread
|
||||
def break_expression(expression: str):
|
||||
"""Set a breakpoint."""
|
||||
# TODO: Escape?
|
||||
@ -424,6 +456,7 @@ def break_expression(expression: str):
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_hw_execute')
|
||||
@util.dbg.eng_thread
|
||||
def break_hw_address(process: sch.Schema('Process'), address: Address):
|
||||
"""Set a hardware-assisted breakpoint."""
|
||||
find_proc_by_obj(process)
|
||||
@ -431,12 +464,14 @@ def break_hw_address(process: sch.Schema('Process'), address: Address):
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_hw_execute')
|
||||
@util.dbg.eng_thread
|
||||
def break_hw_expression(expression: str):
|
||||
"""Set a hardware-assisted breakpoint."""
|
||||
dbg().ba(expr=expression)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_read')
|
||||
@util.dbg.eng_thread
|
||||
def break_read_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
"""Set a read watchpoint."""
|
||||
find_proc_by_obj(process)
|
||||
@ -444,12 +479,14 @@ def break_read_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_read')
|
||||
@util.dbg.eng_thread
|
||||
def break_read_expression(expression: str):
|
||||
"""Set a read watchpoint."""
|
||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_write')
|
||||
@util.dbg.eng_thread
|
||||
def break_write_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
"""Set a watchpoint."""
|
||||
find_proc_by_obj(process)
|
||||
@ -457,25 +494,30 @@ def break_write_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_write')
|
||||
@util.dbg.eng_thread
|
||||
def break_write_expression(expression: str):
|
||||
"""Set a watchpoint."""
|
||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_WRITE)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_access')
|
||||
@util.dbg.eng_thread
|
||||
def break_access_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
"""Set an access watchpoint."""
|
||||
find_proc_by_obj(process)
|
||||
dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_READ|DbgEng.DEBUG_BREAK_WRITE)
|
||||
dbg().ba(expr=range.min, size=range.length(),
|
||||
access=DbgEng.DEBUG_BREAK_READ | DbgEng.DEBUG_BREAK_WRITE)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_access')
|
||||
@util.dbg.eng_thread
|
||||
def break_access_expression(expression: str):
|
||||
"""Set an access watchpoint."""
|
||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ|DbgEng.DEBUG_BREAK_WRITE)
|
||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ | DbgEng.DEBUG_BREAK_WRITE)
|
||||
|
||||
|
||||
@REGISTRY.method(action='toggle')
|
||||
@util.dbg.eng_thread
|
||||
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
||||
"""Toggle a breakpoint."""
|
||||
bpt = find_bpt_by_obj(breakpoint)
|
||||
@ -486,6 +528,7 @@ def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
@util.dbg.eng_thread
|
||||
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
||||
"""Delete a breakpoint."""
|
||||
bpt = find_bpt_by_obj(breakpoint)
|
||||
@ -495,14 +538,20 @@ def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
||||
@REGISTRY.method
|
||||
def read_mem(process: sch.Schema('Process'), range: AddressRange):
|
||||
"""Read memory."""
|
||||
# print("READ_MEM: process={}, range={}".format(process, range))
|
||||
nproc = find_proc_by_obj(process)
|
||||
offset_start = process.trace.memory_mapper.map_back(
|
||||
nproc, Address(range.space, range.min))
|
||||
with commands.open_tracked_tx('Read Memory'):
|
||||
dbg().read(range.min, range.length())
|
||||
result = commands.put_bytes(
|
||||
offset_start, offset_start + range.length() - 1, pages=True, display_result=False)
|
||||
if result['count'] == 0:
|
||||
commands.putmem_state(
|
||||
offset_start, offset_start+range.length() - 1, 'error')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@util.dbg.eng_thread
|
||||
def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
|
||||
"""Write memory."""
|
||||
nproc = find_proc_by_obj(process)
|
||||
@ -511,12 +560,13 @@ def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@util.dbg.eng_thread
|
||||
def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
|
||||
"""Write a register."""
|
||||
util.select_frame()
|
||||
nproc = pydbg.selected_process()
|
||||
dbg().reg._set_register(name, value)
|
||||
|
||||
|
||||
|
||||
|
||||
def dbg():
|
||||
return util.get_debugger()
|
||||
return util.dbg._base
|
||||
|
@ -1,5 +1,23 @@
|
||||
<context>
|
||||
<schema name="DbgRoot" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<attribute name="Sessions" schema="SessionContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Settings" schema="ANY" />
|
||||
<attribute name="State" schema="ANY" />
|
||||
<attribute-alias from="_state" to="State" />
|
||||
<attribute name="Utility" schema="ANY" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="ANY" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="SessionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Session" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="ANY" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="Session" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="Access" />
|
||||
<interface name="Attacher" />
|
||||
<interface name="Interpreter" />
|
||||
@ -12,71 +30,42 @@
|
||||
<element schema="VOID" />
|
||||
<attribute name="Processes" schema="ProcessContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Available" schema="AvailableContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_accessible" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_attach_kinds" schema="SET_ATTACH_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="_prompt" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
|
||||
<attribute name="_event_thread" schema="OBJECT" hidden="yes" />
|
||||
<attribute name="_focus" schema="Selectable" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
<attribute schema="ANY" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="Selectable" elementResync="NEVER" attributeResync="NEVER">
|
||||
<element schema="OBJECT" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="DebugBreakpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointLocationContainer" />
|
||||
<interface name="BreakpointSpecContainer" />
|
||||
<element schema="BreakpointSpec" />
|
||||
<attribute name="_supported_breakpoint_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
<attribute schema="ANY" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="AvailableContainer" canonical="yes" elementResync="ALWAYS" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Attachable" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_base" schema="INT" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="ProcessContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Process" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_base" schema="INT" />
|
||||
<attribute schema="VOID" />
|
||||
<attribute schema="ANY" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="BreakpointSpec" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointSpec" />
|
||||
@ -84,19 +73,15 @@
|
||||
<interface name="Deletable" />
|
||||
<interface name="Togglable" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_container" schema="BreakpointContainer" required="yes" hidden="yes" />
|
||||
<attribute name="_expression" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute name="_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="Expression" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_expression" to="Expression" />
|
||||
<attribute name="Kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_kinds" to="Kinds" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_spec" schema="BreakpointSpec" />
|
||||
<attribute name="_range" schema="RANGE" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="Enabled" schema="BOOL" required="yes" />
|
||||
<attribute-alias from="_enabled" to="Enabled" />
|
||||
<attribute name="Commands" schema="STRING" />
|
||||
<attribute name="Condition" schema="STRING" />
|
||||
<attribute name="Hit Count" schema="INT" />
|
||||
@ -109,17 +94,14 @@
|
||||
<schema name="Attachable" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Attachable" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_pid" schema="LONG" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="PID" schema="LONG" />
|
||||
<attribute-alias from="_pid" to="PID" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Process" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="Process" />
|
||||
<interface name="Aggregate" />
|
||||
<interface name="ExecutionStateful" />
|
||||
@ -133,108 +115,70 @@
|
||||
<interface name="Interruptible" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Debug" schema="DebugBreakpointContainer" required="yes" fixed="yes" />
|
||||
<!-- attribute name="Breakpoints" schema="BreakpointLocationContainer" required="yes" fixed="yes" /-->
|
||||
<attribute name="_exit_code" schema="LONG" />
|
||||
<attribute name="Exit Code" schema="LONG" />
|
||||
<attribute-alias from="_exit_code" to="Exit Code" />
|
||||
<attribute name="Environment" schema="Environment" required="yes" fixed="yes" />
|
||||
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
|
||||
<attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_pid" schema="LONG" hidden="yes" />
|
||||
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_attach_kinds" schema="SET_ATTACH_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="Handle" schema="STRING" fixed="yes" />
|
||||
<attribute name="Id" schema="STRING" fixed="yes" />
|
||||
<attribute name="PID" schema="LONG" hidden="yes" />
|
||||
<attribute-alias from="_pid" to="PID" />
|
||||
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_state" to="State" />
|
||||
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
<attribute schema="ANY" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="Environment" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Environment" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="arch" schema="STRING" />
|
||||
<attribute name="os" schema="STRING" />
|
||||
<attribute name="endian" schema="STRING" />
|
||||
<attribute name="_arch" schema="STRING" hidden="yes" />
|
||||
<attribute name="_debugger" schema="STRING" hidden="yes" />
|
||||
<attribute name="_os" schema="STRING" hidden="yes" />
|
||||
<attribute name="_endian" schema="STRING" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="OS" schema="STRING" />
|
||||
<attribute name="Arch" schema="STRING" />
|
||||
<attribute name="Endian" schema="STRING" />
|
||||
<attribute name="Debugger" schema="STRING" />
|
||||
<attribute-alias from="_os" to="OS" />
|
||||
<attribute-alias from="_arch" to="Arch" />
|
||||
<attribute-alias from="_endian" to="Endian" />
|
||||
<attribute-alias from="_debugger" to="Debugger" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="ModuleContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="ModuleContainer" />
|
||||
<element schema="Module" />
|
||||
<attribute name="_supports_synthetic_modules" schema="BOOL" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
<attribute schema="ANY" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="Memory" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Memory" />
|
||||
<element schema="MemoryRegion" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointLocation" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointLocation" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_range" schema="RANGE" hidden="yes" />
|
||||
<attribute name="_spec" schema="BreakpointSpec" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointLocationContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointLocationContainer" />
|
||||
<element schema="BreakpointLocation" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="ThreadContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Thread" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_base" schema="INT" />
|
||||
<attribute schema="VOID" />
|
||||
<attribute schema="ANY" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="Method" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Method" />
|
||||
@ -245,190 +189,129 @@
|
||||
<attribute schema="VOID" fixed="yes" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="Thread" />
|
||||
<interface name="ExecutionStateful" />
|
||||
<interface name="Steppable" />
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Stack" schema="Stack" required="yes" fixed="yes" />
|
||||
<attribute name="Stack" schema="StackFramesContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_tid" schema="LONG" hidden="yes" />
|
||||
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Environment" schema="ANY" fixed="yes" />
|
||||
<attribute name="Id" schema="STRING" fixed="yes" />
|
||||
<attribute name="TID" schema="LONG" />
|
||||
<attribute-alias from="_tid" to="TID" />
|
||||
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_state" to="State" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="Advance" schema="Method" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
<attribute schema="ANY" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="Module" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Module" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Sections" schema="SectionContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Symbols" schema="SymbolContainer" required="yes" fixed="yes" />
|
||||
<attribute name="range" schema="RANGE" />
|
||||
<attribute name="module name" schema="STRING" />
|
||||
<attribute name="_module_name" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute name="Name" schema="STRING" />
|
||||
<attribute-alias from="_module_name" to="Name" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="ToDisplayString" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="MemoryRegion" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="MemoryRegion" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_offset" schema="LONG" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_objfile" schema="STRING" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="Base" schema="LONG" required="yes" fixed="yes" />
|
||||
<attribute name="Object File" schema="STRING" fixed="yes" />
|
||||
<attribute name="_readable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_writable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_executable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
|
||||
<attribute name="_memory" schema="Memory" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="SectionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="SectionContainer" />
|
||||
<element schema="Section" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="StackFramesContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Frames" schema="Stack" required="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Stack" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Stack" />
|
||||
<element schema="StackFrame" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
<attribute schema="ANY" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="SymbolContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="SymbolNamespace" />
|
||||
<element schema="Symbol" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Symbol" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Symbol" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_size" schema="LONG" fixed="yes" hidden="yes" />
|
||||
<attribute name="_namespace" schema="SymbolContainer" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_data_type" schema="DATA_TYPE" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ADDRESS" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_bpt" schema="STRING" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="StackFrame" />
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_function" schema="STRING" hidden="yes" />
|
||||
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_pc" schema="ADDRESS" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Function" schema="STRING" hidden="yes" />
|
||||
<attribute-alias from="_function" to="Function" />
|
||||
<attribute name="Instruction Offset" schema="ADDRESS" required="yes" />
|
||||
<attribute-alias from="_pc" to="Instruction Offset" />
|
||||
<attribute name="Stack Offset" schema="ADDRESS" />
|
||||
<attribute name="Return Offset" schema="ADDRESS" />
|
||||
<attribute name="Frame Offset" schema="ADDRESS" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
<attribute schema="ANY" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="Section" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Section" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="range" schema="RANGE" />
|
||||
<attribute name="_module" schema="Module" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" required="yes" fixed="yes" />
|
||||
<attribute name="_offset" schema="INT" required="no" fixed="yes" />
|
||||
<attribute name="_objfile" schema="STRING" required="no" fixed="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="Offset" schema="STRING" fixed="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterValueContainer" canonical="yes" elementResync="ONCE" attributeResync="ONCE">
|
||||
<schema name="RegisterValueContainer" attributeResync="ONCE">
|
||||
<interface name="RegisterContainer" />
|
||||
<interface name="RegisterBank" />
|
||||
<element schema="RegisterValue" />
|
||||
<attribute name="General Purpose Registers" schema="RegisterBank" />
|
||||
<attribute name="Floating Point Registers" schema="RegisterBank" />
|
||||
<attribute name="Advanced Vector Extensions" schema="RegisterBank" />
|
||||
<attribute name="Memory Protection Extensions" schema="RegisterBank" />
|
||||
<attribute name="_descriptions" schema="RegisterValueContainer" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="FloatingPoint" schema="RegisterBank" />
|
||||
<attribute name="SIMD" schema="RegisterBank" />
|
||||
<attribute name="User" schema="RegisterBank" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="RegisterBank" />
|
||||
<element schema="RegisterValue" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterValue" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Register" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_container" schema="OBJECT" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_length" schema="INT" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
</context>
|
@ -14,49 +14,401 @@
|
||||
# limitations under the License.
|
||||
##
|
||||
from collections import namedtuple
|
||||
from concurrent.futures import Future
|
||||
import concurrent.futures
|
||||
from ctypes import *
|
||||
import functools
|
||||
import io
|
||||
import os
|
||||
import queue
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
from ctypes import *
|
||||
from pybag import pydbg
|
||||
from comtypes import CoClass, GUID
|
||||
import comtypes
|
||||
from comtypes.gen import DbgMod
|
||||
from comtypes.hresult import S_OK
|
||||
from pybag import pydbg, userdbg, kerneldbg, crashdbg
|
||||
from pybag.dbgeng import core as DbgEng
|
||||
from pybag.dbgeng import exception
|
||||
from pybag.dbgeng import util as DbgUtil
|
||||
from pybag.dbgeng.callbacks import DbgEngCallbacks
|
||||
|
||||
base = pydbg.DebuggerBase()
|
||||
DbgVersion = namedtuple('DbgVersion', ['full', 'major', 'minor'])
|
||||
from ghidradbg.dbgmodel.ihostdatamodelaccess import HostDataModelAccess
|
||||
|
||||
|
||||
def _compute_pydbg_ver():
|
||||
blurb = "" #base._control.GetActualProcessorType()
|
||||
full = ""
|
||||
major = 0
|
||||
minor = 0
|
||||
return DbgVersion(full, int(major), int(minor))
|
||||
DbgVersion = namedtuple('DbgVersion', ['full', 'name', 'dotted', 'arch'])
|
||||
|
||||
|
||||
DBG_VERSION = _compute_pydbg_ver()
|
||||
class StdInputCallbacks(CoClass):
|
||||
# This is the UUID listed for IDebugInputCallbacks in DbgEng.h
|
||||
# See https://github.com/tpn/winsdk-10/blob/master/Include/10.0.10240.0/um/DbgEng.h
|
||||
# Accessed 9 Jan 2024
|
||||
_reg_clsid_ = GUID("{9f50e42c-f136-499e-9a97-73036c94ed2d}")
|
||||
_reg_threading_ = "Both"
|
||||
_reg_progid_ = "dbgeng.DbgEngInputCallbacks.1"
|
||||
_reg_novers_progid_ = "dbgeng.DbgEngInputCallbacks"
|
||||
_reg_desc_ = "InputCallbacks"
|
||||
_reg_clsctx_ = comtypes.CLSCTX_INPROC_SERVER
|
||||
|
||||
_com_interfaces_ = [DbgEng.IDebugInputCallbacks,
|
||||
comtypes.typeinfo.IProvideClassInfo2,
|
||||
comtypes.errorinfo.ISupportErrorInfo,
|
||||
comtypes.connectionpoints.IConnectionPointContainer]
|
||||
|
||||
def __init__(self, ghidra_dbg):
|
||||
self.ghidra_dbg = ghidra_dbg
|
||||
self.expecting_input = False
|
||||
|
||||
def IDebugInputCallbacks_StartInput(self, buffer_size):
|
||||
try:
|
||||
self.expecting_input = True
|
||||
self.buffer_size = buffer_size
|
||||
print('Input>', end=' ')
|
||||
line = input()
|
||||
self.ghidra_dbg.return_input(line)
|
||||
return S_OK
|
||||
except:
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
def IDebugInputCallbacks_EndInput(self):
|
||||
self.expecting_input = False
|
||||
|
||||
|
||||
def get_debugger():
|
||||
return base
|
||||
class _Worker(threading.Thread):
|
||||
def __init__(self, new_base, work_queue, dispatch):
|
||||
super().__init__(name='DbgWorker', daemon=True)
|
||||
self.new_base = new_base
|
||||
self.work_queue = work_queue
|
||||
self.dispatch = dispatch
|
||||
|
||||
def run(self):
|
||||
self.new_base()
|
||||
while True:
|
||||
try:
|
||||
work_item = self.work_queue.get_nowait()
|
||||
except queue.Empty:
|
||||
work_item = None
|
||||
if work_item is None:
|
||||
# HACK to avoid lockup on race condition
|
||||
try:
|
||||
self.dispatch(100)
|
||||
except exception.DbgEngTimeout:
|
||||
# This is routine :/
|
||||
pass
|
||||
else:
|
||||
work_item.run()
|
||||
|
||||
|
||||
# Derived from Python core library
|
||||
# https://github.com/python/cpython/blob/main/Lib/concurrent/futures/thread.py
|
||||
# accessed 9 Jan 2024
|
||||
class _WorkItem(object):
|
||||
def __init__(self, future, fn, args, kwargs):
|
||||
self.future = future
|
||||
self.fn = fn
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
result = self.fn(*self.args, **self.kwargs)
|
||||
except BaseException as exc:
|
||||
self.future.set_exception(exc)
|
||||
# Python core lib does this, I presume for good reason
|
||||
self = None
|
||||
else:
|
||||
self.future.set_result(result)
|
||||
|
||||
|
||||
class DebuggeeRunningException(BaseException):
|
||||
pass
|
||||
|
||||
|
||||
class DbgExecutor(object):
|
||||
def __init__(self, ghidra_dbg):
|
||||
self._ghidra_dbg = ghidra_dbg
|
||||
self._work_queue = queue.SimpleQueue()
|
||||
self._thread = _Worker(ghidra_dbg._new_base,
|
||||
self._work_queue, ghidra_dbg._dispatch_events)
|
||||
self._thread.start()
|
||||
self._executing = False
|
||||
|
||||
def submit(self, fn, /, *args, **kwargs):
|
||||
f = self._submit_no_exit(fn, *args, **kwargs)
|
||||
self._ghidra_dbg.exit_dispatch()
|
||||
return f
|
||||
|
||||
def _submit_no_exit(self, fn, /, *args, **kwargs):
|
||||
f = Future()
|
||||
if self._executing:
|
||||
f.set_exception(DebuggeeRunningException("Debuggee is Running"))
|
||||
return f
|
||||
w = _WorkItem(f, fn, args, kwargs)
|
||||
self._work_queue.put(w)
|
||||
return f
|
||||
|
||||
def _clear_queue(self):
|
||||
while True:
|
||||
try:
|
||||
work_item = self._work_queue.get_nowait()
|
||||
except queue.Empty:
|
||||
return
|
||||
work_item.future.set_exception(
|
||||
DebuggeeRunningException("Debuggee is Running"))
|
||||
|
||||
def _state_execute(self):
|
||||
self._executing = True
|
||||
self._clear_queue()
|
||||
|
||||
def _state_break(self):
|
||||
self._executing = False
|
||||
|
||||
|
||||
class WrongThreadException(BaseException):
|
||||
pass
|
||||
|
||||
|
||||
class AllDbg(pydbg.DebuggerBase):
|
||||
# Steal user-mode methods
|
||||
proc_list = userdbg.UserDbg.proc_list
|
||||
ps = userdbg.UserDbg.ps
|
||||
pids_by_name = userdbg.UserDbg.pids_by_name
|
||||
create_proc = userdbg.UserDbg.create
|
||||
attach_proc = userdbg.UserDbg.attach
|
||||
reattach_proc = userdbg.UserDbg.reattach
|
||||
detach_proc = userdbg.UserDbg.detach
|
||||
abandon_proc = userdbg.UserDbg.abandon
|
||||
terminate_proc = userdbg.UserDbg.terminate
|
||||
handoff_proc = userdbg.UserDbg.handoff
|
||||
connect_proc = userdbg.UserDbg.connect
|
||||
disconnect_proc = userdbg.UserDbg.disconnect
|
||||
|
||||
# Steal kernel-mode methods
|
||||
attach_kernel = kerneldbg.KernelDbg.attach
|
||||
detach_kernel = kerneldbg.KernelDbg.detach
|
||||
|
||||
# Steal crash methods
|
||||
load_dump = crashdbg.CrashDbg.load_dump
|
||||
|
||||
|
||||
class GhidraDbg(object):
|
||||
def __init__(self):
|
||||
self._queue = DbgExecutor(self)
|
||||
self._thread = self._queue._thread
|
||||
# Wait for the executor to be operational before getting base
|
||||
self._queue._submit_no_exit(lambda: None).result()
|
||||
self._install_stdin()
|
||||
self.use_generics = os.getenv('OPT_USE_DBGMODEL') == "true"
|
||||
|
||||
base = self._protected_base
|
||||
for name in ['set_output_mask', 'get_output_mask',
|
||||
'exec_status', 'go', 'goto', 'go_handled', 'go_nothandled',
|
||||
'stepi', 'stepo', 'stepbr', 'stepto', 'stepout',
|
||||
'trace', 'traceto',
|
||||
'wait',
|
||||
'bitness',
|
||||
'read', 'readstr', 'readptr', 'poi',
|
||||
'write', 'writeptr',
|
||||
'memory_list', 'address',
|
||||
'instruction_at', 'disasm',
|
||||
'pc', 'r', 'registers',
|
||||
'get_name_by_offset', 'symbol', 'find_symbol',
|
||||
'whereami',
|
||||
'dd', 'dp', 'ds',
|
||||
'bl', 'bc', 'bd', 'be', 'bp', 'ba',
|
||||
'handle_list', 'handles',
|
||||
'get_thread', 'set_thread', 'apply_threads', 'thread_list', 'threads',
|
||||
'teb_addr', 'teb', 'peb_addr', 'peb',
|
||||
'backtrace_list', 'backtrace',
|
||||
'module_list', 'lm', 'exports', 'imports',
|
||||
# User-mode
|
||||
'proc_list', 'ps', 'pids_by_name',
|
||||
'create_proc', 'attach_proc', 'reattach_proc',
|
||||
'detach_proc', 'abandon_proc', 'terminate_proc', 'handoff_proc',
|
||||
'connect_proc', 'disconnect_proc',
|
||||
# Kernel-model
|
||||
'attach_kernel', 'detach_kernel',
|
||||
# Crash dump
|
||||
'load_dump'
|
||||
]:
|
||||
setattr(self, name, self.eng_thread(getattr(base, name)))
|
||||
|
||||
def _new_base(self):
|
||||
self._protected_base = AllDbg()
|
||||
|
||||
@property
|
||||
def _base(self):
|
||||
if threading.current_thread() is not self._thread:
|
||||
raise WrongThreadException("Was {}. Want {}".format(
|
||||
threading.current_thread(), self._thread))
|
||||
return self._protected_base
|
||||
|
||||
def run(self, fn, *args, **kwargs):
|
||||
# TODO: Remove this check?
|
||||
if hasattr(self, '_thread') and threading.current_thread() is self._thread:
|
||||
raise WrongThreadException()
|
||||
future = self._queue.submit(fn, *args, **kwargs)
|
||||
# https://stackoverflow.com/questions/72621731/is-there-any-graceful-way-to-interrupt-a-python-concurrent-future-result-call gives an alternative
|
||||
while True:
|
||||
try:
|
||||
return future.result(0.5)
|
||||
except concurrent.futures.TimeoutError:
|
||||
pass
|
||||
|
||||
def run_async(self, fn, *args, **kwargs):
|
||||
return self._queue.submit(fn, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def check_thread(func):
|
||||
'''
|
||||
For methods inside of GhidraDbg, ensure it runs on the dbgeng
|
||||
thread
|
||||
'''
|
||||
@functools.wraps(func)
|
||||
def _func(self, *args, **kwargs):
|
||||
if threading.current_thread() is self._thread:
|
||||
return func(self, *args, **kwargs)
|
||||
else:
|
||||
return self.run(func, self, *args, **kwargs)
|
||||
return _func
|
||||
|
||||
def eng_thread(self, func):
|
||||
'''
|
||||
For methods and functions outside of GhidraDbg, ensure it
|
||||
runs on this GhidraDbg's dbgeng thread
|
||||
'''
|
||||
@functools.wraps(func)
|
||||
def _func(*args, **kwargs):
|
||||
if threading.current_thread() is self._thread:
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
return self.run(func, *args, **kwargs)
|
||||
return _func
|
||||
|
||||
def _ces_exec_status(self, argument):
|
||||
if argument & 0xfffffff == DbgEng.DEBUG_STATUS_BREAK:
|
||||
self._queue._state_break()
|
||||
else:
|
||||
self._queue._state_execute()
|
||||
|
||||
@check_thread
|
||||
def _install_stdin(self):
|
||||
self.input = StdInputCallbacks(self)
|
||||
self._base._client.SetInputCallbacks(self.input)
|
||||
|
||||
# Manually decorated to preserve undecorated
|
||||
def _dispatch_events(self, timeout=DbgEng.WAIT_INFINITE):
|
||||
# NB: pybag's impl doesn't heed standalone
|
||||
self._protected_base._client.DispatchCallbacks(timeout)
|
||||
dispatch_events = check_thread(_dispatch_events)
|
||||
|
||||
# no check_thread. Must allow reentry
|
||||
def exit_dispatch(self):
|
||||
self._protected_base._client.ExitDispatch()
|
||||
|
||||
@check_thread
|
||||
def cmd(self, cmdline, quiet=True):
|
||||
# NB: pybag's impl always captures.
|
||||
# Here, we let it print without capture if quiet is False
|
||||
if quiet:
|
||||
try:
|
||||
buffer = io.StringIO()
|
||||
self._base.callbacks.stdout = buffer
|
||||
self._base._control.Execute(cmdline)
|
||||
buffer.seek(0)
|
||||
return buffer.read()
|
||||
finally:
|
||||
self._base.callbacks.reset_stdout()
|
||||
else:
|
||||
return self._base._control.Execute(cmdline)
|
||||
|
||||
@check_thread
|
||||
def return_input(self, input):
|
||||
# TODO: Contribute fix upstream (check_hr -> check_err)
|
||||
# return self._base._control.ReturnInput(input.encode())
|
||||
hr = self._base._control._ctrl.ReturnInput(input.encode())
|
||||
exception.check_err(hr)
|
||||
|
||||
def interrupt(self):
|
||||
# Contribute upstream?
|
||||
# NOTE: This can be called from any thread
|
||||
self._protected_base._control.SetInterrupt(
|
||||
DbgEng.DEBUG_INTERRUPT_ACTIVE)
|
||||
|
||||
@check_thread
|
||||
def get_current_system_id(self):
|
||||
# TODO: upstream?
|
||||
sys_id = c_ulong()
|
||||
hr = self._base._systems._sys.GetCurrentSystemId(byref(sys_id))
|
||||
exception.check_err(hr)
|
||||
return sys_id.value
|
||||
|
||||
@check_thread
|
||||
def get_prompt_text(self):
|
||||
# TODO: upstream?
|
||||
size = c_ulong()
|
||||
hr = self._base._control._ctrl.GetPromptText(None, 0, byref(size))
|
||||
prompt_buf = create_string_buffer(size.value)
|
||||
hr = self._base._control._ctrl.GetPromptText(prompt_buf, size, None)
|
||||
return prompt_buf.value.decode()
|
||||
|
||||
@check_thread
|
||||
def get_actual_processor_type(self):
|
||||
return self._base._control.GetActualProcessorType()
|
||||
|
||||
@property
|
||||
@check_thread
|
||||
def pid(self):
|
||||
try:
|
||||
return self._base._systems.GetCurrentProcessSystemId()
|
||||
except exception.E_UNEXPECTED_Error:
|
||||
# There is no process
|
||||
return None
|
||||
|
||||
|
||||
dbg = GhidraDbg()
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def compute_pydbg_ver():
|
||||
pat = re.compile(
|
||||
'(?P<name>.*Debugger.*) Version (?P<dotted>[\\d\\.]*) (?P<arch>\\w*)')
|
||||
blurb = dbg.cmd('version')
|
||||
matches = [pat.match(l) for l in blurb.splitlines() if pat.match(l)]
|
||||
if len(matches) == 0:
|
||||
return DbgVersion('Unknown', 'Unknown', '0', 'Unknown')
|
||||
m = matches[0]
|
||||
return DbgVersion(full=m.group(), **m.groupdict())
|
||||
name, dotted_and_arch = full.split(' Version ')
|
||||
|
||||
|
||||
DBG_VERSION = compute_pydbg_ver()
|
||||
|
||||
|
||||
def get_target():
|
||||
return 0 #get_debugger()._systems.GetCurrentSystemId()
|
||||
return dbg.get_current_system_id()
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def disassemble1(addr):
|
||||
return DbgUtil.disassemble_instruction(dbg._base.bitness(), addr, dbg.read(addr, 15))
|
||||
|
||||
|
||||
def get_inst(addr):
|
||||
dbg = get_debugger()
|
||||
ins = DbgUtil.disassemble_instruction(dbg.bitness(), addr, dbg.read(addr, 15))
|
||||
return str(ins)
|
||||
return str(disassemble1(addr))
|
||||
|
||||
|
||||
def get_inst_sz(addr):
|
||||
dbg = get_debugger()
|
||||
ins = DbgUtil.disassemble_instruction(dbg.bitness(), addr, dbg.read(addr, 15))
|
||||
return str(ins.size)
|
||||
return int(disassemble1(addr).size)
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def get_breakpoints():
|
||||
ids = [bpid for bpid in get_debugger().breakpoints]
|
||||
ids = [bpid for bpid in dbg._base.breakpoints]
|
||||
offset_set = []
|
||||
expr_set = []
|
||||
prot_set = []
|
||||
@ -64,30 +416,30 @@ def get_breakpoints():
|
||||
stat_set = []
|
||||
for bpid in ids:
|
||||
try:
|
||||
bp = get_debugger()._control.GetBreakpointById(bpid)
|
||||
bp = dbg._base._control.GetBreakpointById(bpid)
|
||||
except exception.E_NOINTERFACE_Error:
|
||||
continue
|
||||
|
||||
|
||||
if bp.GetFlags() & DbgEng.DEBUG_BREAKPOINT_DEFERRED:
|
||||
offset = "[Deferred]"
|
||||
expr = bp.GetOffsetExpression()
|
||||
else:
|
||||
offset = "%016x" % bp.GetOffset()
|
||||
expr = get_debugger().get_name_by_offset(bp.GetOffset())
|
||||
|
||||
expr = dbg._base.get_name_by_offset(bp.GetOffset())
|
||||
|
||||
if bp.GetType()[0] == DbgEng.DEBUG_BREAKPOINT_DATA:
|
||||
width, prot = bp.GetDataParameters()
|
||||
width = ' sz={}'.format(str(width))
|
||||
prot = {4: 'type=x', 3: 'type=rw', 2: 'type=w', 1: 'type=r'}[prot]
|
||||
prot = {4: 'type=x', 3: 'type=rw', 2: 'type=w', 1: 'type=r'}[prot]
|
||||
else:
|
||||
width = ''
|
||||
prot = ''
|
||||
prot = ''
|
||||
|
||||
if bp.GetFlags() & DbgEng.DEBUG_BREAKPOINT_ENABLED:
|
||||
status = 'enabled'
|
||||
else:
|
||||
status = 'disabled'
|
||||
|
||||
|
||||
offset_set.append(offset)
|
||||
expr_set.append(expr)
|
||||
prot_set.append(prot)
|
||||
@ -95,48 +447,74 @@ def get_breakpoints():
|
||||
stat_set.append(status)
|
||||
return zip(offset_set, expr_set, prot_set, width_set, stat_set)
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def selected_process():
|
||||
try:
|
||||
return get_debugger()._systems.GetCurrentProcessId()
|
||||
#return current_process
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def selected_thread():
|
||||
if dbg.use_generics:
|
||||
return dbg._base._systems.GetCurrentProcessSystemId()
|
||||
return dbg._base._systems.GetCurrentProcessId()
|
||||
except exception.E_UNEXPECTED_Error:
|
||||
# NB: we're intentionally returning 0 instead of None
|
||||
return 0
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def selected_thread():
|
||||
try:
|
||||
return get_debugger()._systems.GetCurrentThreadId()
|
||||
except Exception:
|
||||
if dbg.use_generics:
|
||||
return dbg._base._systems.GetCurrentThreadSystemId()
|
||||
return dbg._base._systems.GetCurrentThreadId()
|
||||
except exception.E_UNEXPECTED_Error:
|
||||
return None
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def selected_frame():
|
||||
return 0 #selected_thread().GetSelectedFrame()
|
||||
try:
|
||||
line = dbg.cmd('.frame').strip()
|
||||
if not line:
|
||||
return None
|
||||
num_str = line.split(sep=None, maxsplit=1)[0]
|
||||
return int(num_str, 16)
|
||||
except OSError:
|
||||
return None
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def select_process(id: int):
|
||||
return get_debugger()._systems.SetCurrentProcessId(id)
|
||||
if dbg.use_generics:
|
||||
id = get_proc_id(id)
|
||||
return dbg._base._systems.SetCurrentProcessId(id)
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def select_thread(id: int):
|
||||
return get_debugger()._systems.SetCurrentThreadId(id)
|
||||
if dbg.use_generics:
|
||||
id = get_thread_id(id)
|
||||
return dbg._base._systems.SetCurrentThreadId(id)
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def select_frame(id: int):
|
||||
#TODO: this needs to be fixed
|
||||
return id
|
||||
return dbg.cmd('.frame 0x{:x}'.format(id))
|
||||
|
||||
def parse_and_eval(expr):
|
||||
regs = get_debugger().reg
|
||||
if expr == "$pc":
|
||||
return regs.get_pc()
|
||||
if expr == "$sp":
|
||||
return regs.get_sp()
|
||||
return get_eval(expr)
|
||||
|
||||
def get_eval(expr, type=None):
|
||||
ctrl = get_debugger()._control._ctrl
|
||||
@dbg.eng_thread
|
||||
def parse_and_eval(expr, type=None):
|
||||
if isinstance(expr, int):
|
||||
return expr
|
||||
# TODO: This could be contributed upstream
|
||||
ctrl = dbg._base._control._ctrl
|
||||
ctrl.SetExpressionSyntax(1)
|
||||
value = DbgEng._DEBUG_VALUE()
|
||||
index = c_ulong()
|
||||
if type == None:
|
||||
type = DbgEng.DEBUG_VALUE_INT64
|
||||
hr = ctrl.Evaluate(Expression="{}".format(expr).encode(),DesiredType=type,Value=byref(value),RemainderIndex=byref(index))
|
||||
hr = ctrl.Evaluate(Expression=expr.encode(), DesiredType=type,
|
||||
Value=byref(value), RemainderIndex=byref(index))
|
||||
exception.check_err(hr)
|
||||
if type == DbgEng.DEBUG_VALUE_INT8:
|
||||
return value.u.I8
|
||||
@ -157,96 +535,223 @@ def get_eval(expr, type=None):
|
||||
if type == DbgEng.DEBUG_VALUE_FLOAT128:
|
||||
return value.u.F128Bytes
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def get_pc():
|
||||
return dbg._base.reg.get_pc()
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def get_sp():
|
||||
return dbg._base.reg.get_sp()
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def GetProcessIdsByIndex(count=0):
|
||||
# TODO: This could be contributed upstream?
|
||||
if count == 0:
|
||||
try :
|
||||
count = get_debugger()._systems.GetNumberProcesses()
|
||||
try:
|
||||
count = dbg._base._systems.GetNumberProcesses()
|
||||
except Exception:
|
||||
count = 0
|
||||
ids = (c_ulong * count)()
|
||||
sysids = (c_ulong * count)()
|
||||
if count != 0:
|
||||
hr = get_debugger()._systems._sys.GetProcessIdsByIndex(0, count, ids, sysids)
|
||||
hr = dbg._base._systems._sys.GetProcessIdsByIndex(
|
||||
0, count, ids, sysids)
|
||||
exception.check_err(hr)
|
||||
return (tuple(ids), tuple(sysids))
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def GetCurrentProcessExecutableName():
|
||||
dbg = get_debugger()
|
||||
# TODO: upstream?
|
||||
_dbg = dbg._base
|
||||
size = c_ulong()
|
||||
exesize = c_ulong()
|
||||
hr = dbg._systems._sys.GetCurrentProcessExecutableName(None, size, byref(exesize))
|
||||
hr = _dbg._systems._sys.GetCurrentProcessExecutableName(
|
||||
None, size, byref(exesize))
|
||||
exception.check_err(hr)
|
||||
buffer = create_string_buffer(exesize.value)
|
||||
size = exesize
|
||||
hr = dbg._systems._sys.GetCurrentProcessExecutableName(buffer, size, None)
|
||||
hr = _dbg._systems._sys.GetCurrentProcessExecutableName(buffer, size, None)
|
||||
exception.check_err(hr)
|
||||
buffer = buffer[:size.value]
|
||||
buffer = buffer.rstrip(b'\x00')
|
||||
return buffer
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def GetCurrentProcessPeb():
|
||||
dbg = get_debugger()
|
||||
# TODO: upstream?
|
||||
_dbg = dbg._base
|
||||
offset = c_ulonglong()
|
||||
hr = dbg._systems._sys.GetCurrentProcessPeb(byref(offset))
|
||||
hr = _dbg._systems._sys.GetCurrentProcessPeb(byref(offset))
|
||||
exception.check_err(hr)
|
||||
return offset.value
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def GetExitCode():
|
||||
# TODO: upstream?
|
||||
exit_code = c_ulong()
|
||||
hr = get_debugger()._client._cli.GetExitCode(byref(exit_code))
|
||||
hr = dbg._base._client._cli.GetExitCode(byref(exit_code))
|
||||
return exit_code.value
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def process_list(running=False):
|
||||
"""process_list() -> list of all processes"""
|
||||
dbg = get_debugger()
|
||||
"""Get the list of all processes"""
|
||||
_dbg = dbg._base
|
||||
ids, sysids = GetProcessIdsByIndex()
|
||||
pebs = []
|
||||
names = []
|
||||
|
||||
try :
|
||||
curid = dbg._systems.GetCurrentProcessId()
|
||||
if running == False:
|
||||
|
||||
curid = selected_process()
|
||||
try:
|
||||
if running:
|
||||
return zip(sysids)
|
||||
else:
|
||||
for id in ids:
|
||||
dbg._systems.SetCurrentProcessId(id)
|
||||
_dbg._systems.SetCurrentProcessId(id)
|
||||
names.append(GetCurrentProcessExecutableName())
|
||||
pebs.append(GetCurrentProcessPeb())
|
||||
if running == False:
|
||||
dbg._systems.SetCurrentProcessId(curid)
|
||||
return zip(sysids, names, pebs)
|
||||
except Exception:
|
||||
pass
|
||||
return zip(sysids)
|
||||
return zip(sysids)
|
||||
finally:
|
||||
if not running and curid is not None:
|
||||
_dbg._systems.SetCurrentProcessId(curid)
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def thread_list(running=False):
|
||||
"""thread_list() -> list of all threads"""
|
||||
dbg = get_debugger()
|
||||
try :
|
||||
ids, sysids = dbg._systems.GetThreadIdsByIndex()
|
||||
"""Get the list of all threads"""
|
||||
_dbg = dbg._base
|
||||
try:
|
||||
ids, sysids = _dbg._systems.GetThreadIdsByIndex()
|
||||
except Exception:
|
||||
return zip([])
|
||||
tebs = []
|
||||
syms = []
|
||||
|
||||
curid = dbg._systems.GetCurrentThreadId()
|
||||
if running == False:
|
||||
for id in ids:
|
||||
dbg._systems.SetCurrentThreadId(id)
|
||||
tebs.append(dbg._systems.GetCurrentThreadTeb())
|
||||
addr = dbg.reg.get_pc()
|
||||
syms.append(dbg.get_name_by_offset(addr))
|
||||
if running == False:
|
||||
dbg._systems.SetCurrentThreadId(curid)
|
||||
return zip(sysids, tebs, syms)
|
||||
return zip(sysids)
|
||||
|
||||
curid = selected_thread()
|
||||
try:
|
||||
if running:
|
||||
return zip(sysids)
|
||||
else:
|
||||
for id in ids:
|
||||
_dbg._systems.SetCurrentThreadId(id)
|
||||
tebs.append(_dbg._systems.GetCurrentThreadTeb())
|
||||
addr = _dbg.reg.get_pc()
|
||||
syms.append(_dbg.get_name_by_offset(addr))
|
||||
return zip(sysids, tebs, syms)
|
||||
except Exception:
|
||||
return zip(sysids)
|
||||
finally:
|
||||
if not running and curid is not None:
|
||||
_dbg._systems.SetCurrentThreadId(curid)
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def get_proc_id(pid):
|
||||
"""Get the list of all processes"""
|
||||
# TODO: Implement GetProcessIdBySystemId and replace this logic
|
||||
_dbg = dbg._base
|
||||
map = {}
|
||||
try:
|
||||
x = _dbg._systems.GetProcessIdsByIndex()
|
||||
for i in range(0, len(x[0])):
|
||||
map[x[1][i]] = x[0][i]
|
||||
return map[pid]
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def get_thread_id(tid):
|
||||
"""Get the list of all threads"""
|
||||
# TODO: Implement GetThreadIdBySystemId and replace this logic
|
||||
_dbg = dbg._base
|
||||
map = {}
|
||||
try:
|
||||
x = _dbg._systems.GetThreadIdsByIndex()
|
||||
for i in range(0, len(x[0])):
|
||||
map[x[1][i]] = x[0][i]
|
||||
return map[tid]
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def split_path(pathString):
|
||||
list = []
|
||||
segs = pathString.split(".")
|
||||
for s in segs:
|
||||
if s.endswith("]"):
|
||||
index = s.index("[")
|
||||
list.append(s[:index])
|
||||
list.append(s[index:])
|
||||
else:
|
||||
list.append(s)
|
||||
return list
|
||||
|
||||
|
||||
def IHostDataModelAccess():
|
||||
return HostDataModelAccess(
|
||||
dbg._base._client._cli.QueryInterface(interface=DbgMod.IHostDataModelAccess))
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def get_object(relpath):
|
||||
"""Get the list of all threads"""
|
||||
_cli = dbg._base._client._cli
|
||||
access = HostDataModelAccess(_cli.QueryInterface(
|
||||
interface=DbgMod.IHostDataModelAccess))
|
||||
(mgr, host) = access.GetDataModel()
|
||||
root = mgr.GetRootNamespace()
|
||||
pathstr = "Debugger"
|
||||
if relpath != '':
|
||||
pathstr += "."+relpath
|
||||
path = split_path(pathstr)
|
||||
return root.GetOffspring(path)
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def get_attributes(obj):
|
||||
"""Get the list of attributes"""
|
||||
return obj.GetAttributes()
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def get_elements(obj):
|
||||
"""Get the list of all threads"""
|
||||
return obj.GetElements()
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def get_kind(obj):
|
||||
"""Get the list of all threads"""
|
||||
return obj.GetKind().value
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def get_type(obj):
|
||||
"""Get the list of all threads"""
|
||||
return obj.GetTypeKind()
|
||||
|
||||
|
||||
@dbg.eng_thread
|
||||
def get_value(obj):
|
||||
"""Get the list of all threads"""
|
||||
return obj.GetValue()
|
||||
|
||||
|
||||
conv_map = {}
|
||||
|
||||
|
||||
def get_convenience_variable(id):
|
||||
#val = get_target().GetEnvironment().Get(id)
|
||||
if id not in conv_map:
|
||||
return "auto"
|
||||
val = conv_map[id]
|
||||
@ -254,7 +759,6 @@ def get_convenience_variable(id):
|
||||
return "auto"
|
||||
return val
|
||||
|
||||
|
||||
def set_convenience_variable(id, value):
|
||||
#env = get_target().GetEnvironment()
|
||||
#return env.Set(id, value, True)
|
||||
conv_map[id] = value
|
||||
|
@ -0,0 +1,19 @@
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
|
||||
# NOTE: libraries must precede EVERYTHING, esp pybag and DbgMod
|
||||
|
||||
from . import libraries, util, commands, methods, hooks
|
@ -0,0 +1,212 @@
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
from ghidratrace.client import Address, RegVal
|
||||
|
||||
from pybag import pydbg
|
||||
|
||||
from . import util
|
||||
|
||||
language_map = {
|
||||
'ARM': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A', 'ARM:BE:64:v8', 'ARM:LE:64:v8'],
|
||||
'Itanium': [],
|
||||
'x86': ['x86:LE:32:default'],
|
||||
'x86_64': ['x86:LE:64:default'],
|
||||
'EFI': ['x86:LE:64:default'],
|
||||
}
|
||||
|
||||
data64_compiler_map = {
|
||||
None: 'pointer64',
|
||||
}
|
||||
|
||||
x86_compiler_map = {
|
||||
'windows': 'windows',
|
||||
'Cygwin': 'windows',
|
||||
}
|
||||
|
||||
arm_compiler_map = {
|
||||
'windows': 'windows',
|
||||
}
|
||||
|
||||
compiler_map = {
|
||||
'DATA:BE:64:default': data64_compiler_map,
|
||||
'DATA:LE:64:default': data64_compiler_map,
|
||||
'x86:LE:32:default': x86_compiler_map,
|
||||
'x86:LE:64:default': x86_compiler_map,
|
||||
'AARCH64:BE:64:v8A': arm_compiler_map,
|
||||
'AARCH64:LE:64:AppleSilicon': arm_compiler_map,
|
||||
'AARCH64:LE:64:v8A': arm_compiler_map,
|
||||
'ARM:BE:64:v8': arm_compiler_map,
|
||||
'ARM:LE:64:v8': arm_compiler_map,
|
||||
}
|
||||
|
||||
|
||||
def get_arch():
|
||||
return "x86_64"
|
||||
|
||||
|
||||
def get_endian():
|
||||
return 'little'
|
||||
|
||||
|
||||
def get_osabi():
|
||||
return "windows"
|
||||
|
||||
|
||||
def compute_ghidra_language():
|
||||
# First, check if the parameter is set
|
||||
lang = util.get_convenience_variable('ghidra-language')
|
||||
if lang != 'auto':
|
||||
return lang
|
||||
|
||||
# Get the list of possible languages for the arch. We'll need to sift
|
||||
# through them by endian and probably prefer default/simpler variants. The
|
||||
# heuristic for "simpler" will be 'default' then shortest variant id.
|
||||
arch = get_arch()
|
||||
endian = get_endian()
|
||||
lebe = ':BE:' if endian == 'big' else ':LE:'
|
||||
if not arch in language_map:
|
||||
return 'DATA' + lebe + '64:default'
|
||||
langs = language_map[arch]
|
||||
matched_endian = sorted(
|
||||
(l for l in langs if lebe in l),
|
||||
key=lambda l: 0 if l.endswith(':default') else len(l)
|
||||
)
|
||||
if len(matched_endian) > 0:
|
||||
return matched_endian[0]
|
||||
# NOTE: I'm disinclined to fall back to a language match with wrong endian.
|
||||
return 'DATA' + lebe + '64:default'
|
||||
|
||||
|
||||
def compute_ghidra_compiler(lang):
|
||||
# First, check if the parameter is set
|
||||
comp = util.get_convenience_variable('ghidra-compiler')
|
||||
if comp != 'auto':
|
||||
return comp
|
||||
|
||||
# Check if the selected lang has specific compiler recommendations
|
||||
if not lang in compiler_map:
|
||||
return 'default'
|
||||
comp_map = compiler_map[lang]
|
||||
osabi = get_osabi()
|
||||
if osabi in comp_map:
|
||||
return comp_map[osabi]
|
||||
if None in comp_map:
|
||||
return comp_map[None]
|
||||
return 'default'
|
||||
|
||||
|
||||
def compute_ghidra_lcsp():
|
||||
lang = compute_ghidra_language()
|
||||
comp = compute_ghidra_compiler(lang)
|
||||
return lang, comp
|
||||
|
||||
|
||||
class DefaultMemoryMapper(object):
|
||||
|
||||
def __init__(self, defaultSpace):
|
||||
self.defaultSpace = defaultSpace
|
||||
|
||||
def map(self, proc: int, offset: int):
|
||||
space = self.defaultSpace
|
||||
return self.defaultSpace, Address(space, offset)
|
||||
|
||||
def map_back(self, proc: int, address: Address) -> int:
|
||||
if address.space == self.defaultSpace:
|
||||
return address.offset
|
||||
raise ValueError(f"Address {address} is not in process {proc.GetProcessID()}")
|
||||
|
||||
|
||||
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
|
||||
|
||||
memory_mappers = {}
|
||||
|
||||
|
||||
def compute_memory_mapper(lang):
|
||||
if not lang in memory_mappers:
|
||||
return DEFAULT_MEMORY_MAPPER
|
||||
return memory_mappers[lang]
|
||||
|
||||
|
||||
class DefaultRegisterMapper(object):
|
||||
|
||||
def __init__(self, byte_order):
|
||||
if not byte_order in ['big', 'little']:
|
||||
raise ValueError("Invalid byte_order: {}".format(byte_order))
|
||||
self.byte_order = byte_order
|
||||
self.union_winners = {}
|
||||
|
||||
def map_name(self, proc, name):
|
||||
return name
|
||||
|
||||
|
||||
def map_value(self, proc, name, value):
|
||||
try:
|
||||
### TODO: this seems half-baked
|
||||
av = value.to_bytes(8, "big")
|
||||
except Exception:
|
||||
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
|
||||
.format(name, value, type(value)))
|
||||
return RegVal(self.map_name(proc, name), av)
|
||||
|
||||
def map_name_back(self, proc, name):
|
||||
return name
|
||||
|
||||
def map_value_back(self, proc, name, value):
|
||||
return RegVal(self.map_name_back(proc, name), value)
|
||||
|
||||
|
||||
class Intel_x86_64_RegisterMapper(DefaultRegisterMapper):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('little')
|
||||
|
||||
def map_name(self, proc, name):
|
||||
if name is None:
|
||||
return 'UNKNOWN'
|
||||
if name == 'efl':
|
||||
return 'rflags'
|
||||
if name.startswith('zmm'):
|
||||
# Ghidra only goes up to ymm, right now
|
||||
return 'ymm' + name[3:]
|
||||
return super().map_name(proc, name)
|
||||
|
||||
def map_value(self, proc, name, value):
|
||||
rv = super().map_value(proc, name, value)
|
||||
if rv.name.startswith('ymm') and len(rv.value) > 32:
|
||||
return RegVal(rv.name, rv.value[-32:])
|
||||
return rv
|
||||
|
||||
def map_name_back(self, proc, name):
|
||||
if name == 'rflags':
|
||||
return 'eflags'
|
||||
|
||||
|
||||
DEFAULT_BE_REGISTER_MAPPER = DefaultRegisterMapper('big')
|
||||
DEFAULT_LE_REGISTER_MAPPER = DefaultRegisterMapper('little')
|
||||
|
||||
register_mappers = {
|
||||
'x86:LE:64:default': Intel_x86_64_RegisterMapper()
|
||||
}
|
||||
|
||||
|
||||
def compute_register_mapper(lang):
|
||||
if not lang in register_mappers:
|
||||
if ':BE:' in lang:
|
||||
return DEFAULT_BE_REGISTER_MAPPER
|
||||
if ':LE:' in lang:
|
||||
return DEFAULT_LE_REGISTER_MAPPER
|
||||
return register_mappers[lang]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,441 @@
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
|
||||
from pybag import pydbg
|
||||
from pybag.dbgeng.callbacks import EventHandler
|
||||
from pybag.dbgeng import core as DbgEng
|
||||
from pybag.dbgeng import exception
|
||||
from pybag.dbgeng.idebugbreakpoint import DebugBreakpoint
|
||||
|
||||
from . import commands, util
|
||||
|
||||
ALL_EVENTS = 0xFFFF
|
||||
|
||||
class HookState(object):
|
||||
__slots__ = ('installed', 'mem_catchpoint')
|
||||
|
||||
def __init__(self):
|
||||
self.installed = False
|
||||
self.mem_catchpoint = None
|
||||
|
||||
|
||||
class ProcessState(object):
|
||||
__slots__ = ('first', 'regions', 'modules', 'threads', 'breaks', 'watches', 'visited', 'waiting')
|
||||
|
||||
def __init__(self):
|
||||
self.first = True
|
||||
# For things we can detect changes to between stops
|
||||
self.regions = False
|
||||
self.modules = False
|
||||
self.threads = False
|
||||
self.breaks = False
|
||||
self.watches = False
|
||||
# For frames and threads that have already been synced since last stop
|
||||
self.visited = set()
|
||||
self.waiting = True
|
||||
|
||||
def record(self, description=None, snap=None):
|
||||
first = self.first
|
||||
self.first = False
|
||||
if description is not None:
|
||||
commands.STATE.trace.snapshot(description, snap=snap)
|
||||
if first:
|
||||
commands.put_processes()
|
||||
commands.put_environment()
|
||||
if self.threads:
|
||||
commands.put_threads()
|
||||
self.threads = False
|
||||
thread = util.selected_thread()
|
||||
if thread is not None:
|
||||
if first or thread not in self.visited:
|
||||
commands.putreg()
|
||||
commands.putmem("$pc", "1", display_result=False)
|
||||
commands.putmem("$sp", "1", display_result=False)
|
||||
#commands.put_frames()
|
||||
self.visited.add(thread)
|
||||
#frame = util.selected_frame()
|
||||
#hashable_frame = (thread, frame)
|
||||
#if first or hashable_frame not in self.visited:
|
||||
# self.visited.add(hashable_frame)
|
||||
if first or self.regions:
|
||||
commands.put_regions()
|
||||
self.regions = False
|
||||
if first or self.modules:
|
||||
commands.put_modules()
|
||||
self.modules = False
|
||||
if first or self.breaks:
|
||||
commands.put_breakpoints()
|
||||
self.breaks = False
|
||||
|
||||
def record_continued(self):
|
||||
commands.put_processes(running=True)
|
||||
commands.put_threads(running=True)
|
||||
|
||||
def record_exited(self, exit_code, description=None, snap=None):
|
||||
if description is not None:
|
||||
commands.STATE.trace.snapshot(description, snap)
|
||||
proc = util.selected_process()
|
||||
ipath = commands.PROCESS_PATTERN.format(procnum=proc)
|
||||
commands.STATE.trace.proxy_object_path(
|
||||
ipath).set_value('Exit Code', exit_code)
|
||||
|
||||
|
||||
class BrkState(object):
|
||||
__slots__ = ('break_loc_counts',)
|
||||
|
||||
def __init__(self):
|
||||
self.break_loc_counts = {}
|
||||
|
||||
def update_brkloc_count(self, b, count):
|
||||
self.break_loc_counts[b.GetID()] = count
|
||||
|
||||
def get_brkloc_count(self, b):
|
||||
return self.break_loc_counts.get(b.GetID(), 0)
|
||||
|
||||
def del_brkloc_count(self, b):
|
||||
if b not in self.break_loc_counts:
|
||||
return 0 # TODO: Print a warning?
|
||||
count = self.break_loc_counts[b.GetID()]
|
||||
del self.break_loc_counts[b.GetID()]
|
||||
return count
|
||||
|
||||
|
||||
HOOK_STATE = HookState()
|
||||
BRK_STATE = BrkState()
|
||||
PROC_STATE = {}
|
||||
|
||||
|
||||
def on_state_changed(*args):
|
||||
#print("ON_STATE_CHANGED")
|
||||
if args[0] == DbgEng.DEBUG_CES_CURRENT_THREAD:
|
||||
return on_thread_selected(args)
|
||||
elif args[0] == DbgEng.DEBUG_CES_BREAKPOINTS:
|
||||
return on_breakpoint_modified(args)
|
||||
elif args[0] == DbgEng.DEBUG_CES_RADIX:
|
||||
util.set_convenience_variable('output-radix', args[1])
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
elif args[0] == DbgEng.DEBUG_CES_EXECUTION_STATUS:
|
||||
proc = util.selected_process()
|
||||
if args[1] & DbgEng.DEBUG_STATUS_INSIDE_WAIT:
|
||||
PROC_STATE[proc].waiting = True
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
PROC_STATE[proc].waiting = False
|
||||
commands.put_state(proc)
|
||||
if args[1] == DbgEng.DEBUG_STATUS_BREAK:
|
||||
return on_stop(args)
|
||||
else:
|
||||
return on_cont(args)
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_debuggee_changed(*args):
|
||||
#print("ON_DEBUGGEE_CHANGED")
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
if args[1] == DbgEng.DEBUG_CDS_REGISTERS:
|
||||
on_register_changed(args[0][1])
|
||||
#if args[1] == DbgEng.DEBUG_CDS_DATA:
|
||||
# on_memory_changed(args[0][1])
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_session_status_changed(*args):
|
||||
#print("ON_STATUS_CHANGED")
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
if args[0] == DbgEng.DEBUG_SESSION_ACTIVE or args[0] == DbgEng.DEBUG_SSESION_REBOOT:
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_symbol_state_changed(*args):
|
||||
#print("ON_SYMBOL_STATE_CHANGED")
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
if args[0] == 1 or args[0] == 2:
|
||||
PROC_STATE[proc].modules = True
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_system_error(*args):
|
||||
print("ON_SYSTEM_ERROR")
|
||||
print(hex(args[0]))
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_BREAK
|
||||
|
||||
|
||||
def on_new_process(*args):
|
||||
#print("ON_NEW_PROCESS")
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_BREAK
|
||||
|
||||
|
||||
def on_process_selected():
|
||||
#print("PROCESS_SELECTED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Process {} selected".format(proc)):
|
||||
PROC_STATE[proc].record()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_process_deleted(*args):
|
||||
#print("ON_PROCESS_DELETED")
|
||||
proc = args[0]
|
||||
on_exited(proc)
|
||||
if proc in PROC_STATE:
|
||||
del PROC_STATE[proc]
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Process {} deleted".format(proc)):
|
||||
commands.put_processes() # TODO: Could just delete the one....
|
||||
return DbgEng.DEBUG_STATUS_BREAK
|
||||
|
||||
|
||||
def on_threads_changed(*args):
|
||||
#print("ON_THREADS_CHANGED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
PROC_STATE[proc].threads = True
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_thread_selected(*args):
|
||||
#print("THREAD_SELECTED")
|
||||
nthrd = args[0][1]
|
||||
nproc = util.selected_process()
|
||||
if nproc not in PROC_STATE:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Thread {}.{} selected".format(nproc, nthrd)):
|
||||
commands.put_state(nproc)
|
||||
state = PROC_STATE[nproc]
|
||||
if state.waiting:
|
||||
state.record_continued()
|
||||
else:
|
||||
state.record()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_register_changed(regnum):
|
||||
#print("REGISTER_CHANGED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Register {} changed".format(regnum)):
|
||||
commands.putreg()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_cont(*args):
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
state = PROC_STATE[proc]
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Continued"):
|
||||
state.record_continued()
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_stop(*args):
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
print("not in state")
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
print("no trace")
|
||||
return
|
||||
state = PROC_STATE[proc]
|
||||
state.visited.clear()
|
||||
pos = dbg().get_position()
|
||||
rng = range(pos.major, util.lastpos.major)
|
||||
if pos.major > util.lastpos.major:
|
||||
rng = range(util.lastpos.major, pos.major)
|
||||
for i in rng:
|
||||
if util.evttypes.__contains__(i):
|
||||
type = util.evttypes[i]
|
||||
if type == "modload" or type == "modunload":
|
||||
on_modules_changed()
|
||||
if type == "threadcreated" or type == "threadterm":
|
||||
on_threads_changed()
|
||||
util.lastpos = pos
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Stopped"):
|
||||
state.record("Stopped", util.pos2snap(pos))
|
||||
commands.put_state(proc)
|
||||
commands.put_event_thread()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_exited(proc):
|
||||
if proc not in PROC_STATE:
|
||||
print("not in state")
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
state = PROC_STATE[proc]
|
||||
state.visited.clear()
|
||||
exit_code = util.GetExitCode()
|
||||
description = "Exited with code {}".format(exit_code)
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx(description):
|
||||
state.record_exited(exit_code, description)
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_modules_changed(*args):
|
||||
#print("ON_MODULES_CHANGED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
PROC_STATE[proc].modules = True
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_breakpoint_created(bp):
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
PROC_STATE[proc].breaks = True
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
ibpath = commands.PROC_BREAKS_PATTERN.format(procnum=proc)
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Breakpoint {} created".format(bp.id)):
|
||||
ibobj = trace.create_object(ibpath)
|
||||
# Do not use retain_values or it'll remove other locs
|
||||
commands.put_single_breakpoint(bp, ibobj, proc, [])
|
||||
ibobj.insert()
|
||||
|
||||
|
||||
def on_breakpoint_modified(*args):
|
||||
#print("BREAKPOINT_MODIFIED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
PROC_STATE[proc].breaks = True
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
ibpath = commands.PROC_BREAKS_PATTERN.format(procnum=proc)
|
||||
ibobj = trace.create_object(ibpath)
|
||||
bpid = args[0][1]
|
||||
try:
|
||||
bp = dbg()._control.GetBreakpointById(bpid)
|
||||
except exception.E_NOINTERFACE_Error:
|
||||
dbg().breakpoints._remove_stale(bpid)
|
||||
return on_breakpoint_deleted(bpid)
|
||||
return on_breakpoint_created(bp)
|
||||
|
||||
|
||||
def on_breakpoint_deleted(bpt):
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
PROC_STATE[proc].breaks = True
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
bpath = commands.PROC_BREAK_PATTERN.format(procnum=proc, breaknum=bpt.id)
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Breakpoint {} deleted".format(bpt.id)):
|
||||
trace.proxy_object_path(bpath).remove(tree=True)
|
||||
|
||||
|
||||
def on_breakpoint_hit(*args):
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_exception(*args):
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def install_hooks():
|
||||
if HOOK_STATE.installed:
|
||||
return
|
||||
HOOK_STATE.installed = True
|
||||
|
||||
def remove_hooks():
|
||||
if not HOOK_STATE.installed:
|
||||
return
|
||||
HOOK_STATE.installed = False
|
||||
|
||||
|
||||
def enable_current_process():
|
||||
proc = util.selected_process()
|
||||
PROC_STATE[proc] = ProcessState()
|
||||
|
||||
|
||||
def disable_current_process():
|
||||
proc = util.selected_process()
|
||||
if proc in PROC_STATE:
|
||||
# Silently ignore already disabled
|
||||
del PROC_STATE[proc]
|
||||
|
||||
def dbg():
|
||||
return util.get_debugger()
|
@ -0,0 +1,78 @@
|
||||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
import ctypes
|
||||
import os
|
||||
import platform
|
||||
|
||||
import comtypes
|
||||
import comtypes.client
|
||||
|
||||
|
||||
ctypes.windll.kernel32.SetErrorMode(0x0001 | 0x0002 | 0x8000)
|
||||
|
||||
if platform.architecture()[0] == '64bit':
|
||||
dbgdirs = [os.getenv('OPT_DBGMODEL_PATH'),
|
||||
r'C:\Program Files\Windows Kits\10\Debuggers\x64',
|
||||
r'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64']
|
||||
else:
|
||||
dbgdirs = [os.getenv('OPT_DBGMODEL_PATH'),
|
||||
r'C:\Program Files\Windows Kits\10\Debuggers\x86',
|
||||
r'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86']
|
||||
dbgdir = None
|
||||
for _dir in dbgdirs:
|
||||
if _dir is not None and os.path.exists(_dir):
|
||||
dbgdir = _dir
|
||||
break
|
||||
|
||||
if not dbgdir:
|
||||
raise RuntimeError("Windbg install directory not found!")
|
||||
|
||||
print(f"Loading dbgeng and friends from {dbgdir}")
|
||||
|
||||
# preload these to get correct DLLs loaded
|
||||
try:
|
||||
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'dbghelp.dll'))
|
||||
except Exception as exc:
|
||||
print(fr"LoadLibrary failed: {dbgdir}\dbghelp.dll {exc}")
|
||||
pass
|
||||
try:
|
||||
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'dbgeng.dll'))
|
||||
except Exception as exc:
|
||||
print(fr"LoadLibrary failed: {dbgdir}\dbgeng.dll {exc}")
|
||||
pass
|
||||
try:
|
||||
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'DbgModel.dll'))
|
||||
except Exception as exc:
|
||||
print(fr"LoadLibrary failed: {dbgdir}\dbgmodel.dll {exc}")
|
||||
pass
|
||||
try:
|
||||
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'ttd/TTDReplay.dll'))
|
||||
except Exception as exc:
|
||||
print(fr"LoadLibrary failed: {dbgdir}\ttd\TTDReplay.dll {exc}")
|
||||
pass
|
||||
try:
|
||||
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'ttd/TTDReplayCPU.dll'))
|
||||
except Exception as exc:
|
||||
print(fr"LoadLibrary failed: {dbgdir}\ttd\TTDReplayCPU.dll {exc}")
|
||||
pass
|
||||
|
||||
try:
|
||||
from comtypes.gen import DbgMod
|
||||
except:
|
||||
tlb = os.path.join(dbgmodel.module_locator(), 'tlb', 'dbgmodel.tlb')
|
||||
print(f"Loading TLB: {tlb}")
|
||||
comtypes.client.GetModule(tlb)
|
||||
from comtypes.gen import DbgMod
|
@ -0,0 +1,536 @@
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ghidratrace import sch
|
||||
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
||||
|
||||
from pyttd import pyTTD
|
||||
#from pybag import pydbg
|
||||
#from pybag.dbgeng import core as DbgEng
|
||||
|
||||
from . import util, commands, hooks
|
||||
from contextlib import redirect_stdout
|
||||
from io import StringIO
|
||||
|
||||
|
||||
REGISTRY = MethodRegistry(ThreadPoolExecutor(max_workers=1))
|
||||
|
||||
|
||||
def extre(base, ext):
|
||||
return re.compile(base.pattern + ext)
|
||||
|
||||
|
||||
AVAILABLE_PATTERN = re.compile('Available\[(?P<pid>\\d*)\]')
|
||||
WATCHPOINT_PATTERN = re.compile('Watchpoints\[(?P<watchnum>\\d*)\]')
|
||||
BREAKPOINT_PATTERN = re.compile('Breakpoints\[(?P<breaknum>\\d*)\]')
|
||||
BREAK_LOC_PATTERN = extre(BREAKPOINT_PATTERN, '\[(?P<locnum>\\d*)\]')
|
||||
PROCESS_PATTERN = re.compile('Processes\[(?P<procnum>\\d*)\]')
|
||||
PROC_BREAKS_PATTERN = extre(PROCESS_PATTERN, '\.Breakpoints')
|
||||
PROC_BREAKBPT_PATTERN = extre(PROC_BREAKS_PATTERN, '\[(?P<breaknum>\\d*)\]')
|
||||
ENV_PATTERN = extre(PROCESS_PATTERN, '\.Environment')
|
||||
THREADS_PATTERN = extre(PROCESS_PATTERN, '\.Threads')
|
||||
THREAD_PATTERN = extre(THREADS_PATTERN, '\[(?P<tnum>\\d*)\]')
|
||||
STACK_PATTERN = extre(THREAD_PATTERN, '\.Stack')
|
||||
FRAME_PATTERN = extre(STACK_PATTERN, '\[(?P<level>\\d*)\]')
|
||||
REGS_PATTERN0 = extre(THREAD_PATTERN, '.Registers')
|
||||
REGS_PATTERN = extre(FRAME_PATTERN, '.Registers')
|
||||
MEMORY_PATTERN = extre(PROCESS_PATTERN, '\.Memory')
|
||||
MODULES_PATTERN = extre(PROCESS_PATTERN, '\.Modules')
|
||||
|
||||
|
||||
def find_availpid_by_pattern(pattern, object, err_msg):
|
||||
mat = pattern.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
pid = int(mat['pid'])
|
||||
return pid
|
||||
|
||||
|
||||
def find_availpid_by_obj(object):
|
||||
return find_availpid_by_pattern(AVAILABLE_PATTERN, object, "an Available")
|
||||
|
||||
|
||||
def find_proc_by_num(id):
|
||||
if id != util.selected_process():
|
||||
util.select_process(id)
|
||||
return util.selected_process()
|
||||
|
||||
|
||||
def find_proc_by_pattern(object, pattern, err_msg):
|
||||
mat = pattern.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
procnum = int(mat['procnum'])
|
||||
return find_proc_by_num(procnum)
|
||||
|
||||
|
||||
def find_proc_by_obj(object):
|
||||
return find_proc_by_pattern(object, PROCESS_PATTERN, "an Process")
|
||||
|
||||
|
||||
def find_proc_by_procbreak_obj(object):
|
||||
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
|
||||
"a BreakpointLocationContainer")
|
||||
|
||||
|
||||
def find_proc_by_procwatch_obj(object):
|
||||
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
|
||||
"a WatchpointContainer")
|
||||
|
||||
|
||||
def find_proc_by_env_obj(object):
|
||||
return find_proc_by_pattern(object, ENV_PATTERN, "an Environment")
|
||||
|
||||
|
||||
def find_proc_by_threads_obj(object):
|
||||
return find_proc_by_pattern(object, THREADS_PATTERN, "a ThreadContainer")
|
||||
|
||||
|
||||
def find_proc_by_mem_obj(object):
|
||||
return find_proc_by_pattern(object, MEMORY_PATTERN, "a Memory")
|
||||
|
||||
|
||||
def find_proc_by_modules_obj(object):
|
||||
return find_proc_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")
|
||||
|
||||
|
||||
def find_thread_by_num(id):
|
||||
if id != util.selected_thread():
|
||||
util.select_thread(id)
|
||||
return util.selected_thread()
|
||||
|
||||
|
||||
def find_thread_by_pattern(pattern, object, err_msg):
|
||||
mat = pattern.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
pnum = int(mat['procnum'])
|
||||
tnum = int(mat['tnum'])
|
||||
find_proc_by_num(pnum)
|
||||
return find_thread_by_num(tnum)
|
||||
|
||||
|
||||
def find_thread_by_obj(object):
|
||||
return find_thread_by_pattern(THREAD_PATTERN, object, "a Thread")
|
||||
|
||||
|
||||
def find_thread_by_stack_obj(object):
|
||||
return find_thread_by_pattern(STACK_PATTERN, object, "a Stack")
|
||||
|
||||
|
||||
def find_thread_by_regs_obj(object):
|
||||
return find_thread_by_pattern(REGS_PATTERN0, object, "a RegisterValueContainer")
|
||||
|
||||
|
||||
def find_frame_by_level(level):
|
||||
return dbg().backtrace_list()[level]
|
||||
|
||||
|
||||
def find_frame_by_pattern(pattern, object, err_msg):
|
||||
mat = pattern.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
pnum = int(mat['procnum'])
|
||||
tnum = int(mat['tnum'])
|
||||
level = int(mat['level'])
|
||||
find_proc_by_num(pnum)
|
||||
find_thread_by_num(tnum)
|
||||
return find_frame_by_level(level)
|
||||
|
||||
|
||||
def find_frame_by_obj(object):
|
||||
return find_frame_by_pattern(FRAME_PATTERN, object, "a StackFrame")
|
||||
|
||||
|
||||
def find_bpt_by_number(breaknum):
|
||||
try:
|
||||
bp = util.breakpoints[breaknum]
|
||||
return bp
|
||||
except exception.E_NOINTERFACE_Error:
|
||||
raise KeyError(f"Breakpoints[{breaknum}] does not exist")
|
||||
|
||||
|
||||
def find_bpt_by_pattern(pattern, object, err_msg):
|
||||
mat = pattern.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
breaknum = int(mat['breaknum'])
|
||||
return find_bpt_by_number(breaknum)
|
||||
|
||||
|
||||
def find_bpt_by_obj(object):
|
||||
return find_bpt_by_pattern(PROC_BREAKBPT_PATTERN, object, "a BreakpointSpec")
|
||||
|
||||
|
||||
shared_globals = dict()
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def execute(cmd: str, to_string: bool=False):
|
||||
"""Execute a CLI command."""
|
||||
# print("***{}***".format(cmd))
|
||||
# sys.stderr.flush()
|
||||
# sys.stdout.flush()
|
||||
if to_string:
|
||||
data = StringIO()
|
||||
with redirect_stdout(data):
|
||||
exec("{}".format(cmd), shared_globals)
|
||||
return data.getvalue()
|
||||
else:
|
||||
exec("{}".format(cmd), shared_globals)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def evaluate(expr: str):
|
||||
"""Execute a CLI command."""
|
||||
return str(eval("{}".format(expr), shared_globals))
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_available(node: sch.Schema('AvailableContainer')):
|
||||
"""List processes on pydbg's host system."""
|
||||
with commands.open_tracked_tx('Refresh Available'):
|
||||
commands.ghidra_trace_put_available()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
|
||||
"""
|
||||
Refresh the list of breakpoints (including locations for the current
|
||||
process).
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Breakpoints'):
|
||||
commands.ghidra_trace_put_breakpoints()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_processes(node: sch.Schema('ProcessContainer')):
|
||||
"""Refresh the list of processes."""
|
||||
with commands.open_tracked_tx('Refresh Processes'):
|
||||
commands.ghidra_trace_put_threads()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_proc_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
||||
"""
|
||||
Refresh the breakpoint locations for the process.
|
||||
|
||||
In the course of refreshing the locations, the breakpoint list will also be
|
||||
refreshed.
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Breakpoint Locations'):
|
||||
commands.ghidra_trace_put_breakpoints()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_environment(node: sch.Schema('Environment')):
|
||||
"""Refresh the environment descriptors (arch, os, endian)."""
|
||||
with commands.open_tracked_tx('Refresh Environment'):
|
||||
commands.ghidra_trace_put_environment()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
||||
"""Refresh the list of threads in the process."""
|
||||
with commands.open_tracked_tx('Refresh Threads'):
|
||||
commands.ghidra_trace_put_threads()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_stack(node: sch.Schema('Stack')):
|
||||
"""Refresh the backtrace for the thread."""
|
||||
tnum = find_thread_by_stack_obj(node)
|
||||
with commands.open_tracked_tx('Refresh Stack'):
|
||||
commands.ghidra_trace_put_frames()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
||||
"""Refresh the register values for the frame."""
|
||||
tnum = find_thread_by_regs_obj(node)
|
||||
with commands.open_tracked_tx('Refresh Registers'):
|
||||
commands.ghidra_trace_putreg()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_mappings(node: sch.Schema('Memory')):
|
||||
"""Refresh the list of memory regions for the process."""
|
||||
with commands.open_tracked_tx('Refresh Memory Regions'):
|
||||
commands.ghidra_trace_put_regions()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_modules(node: sch.Schema('ModuleContainer')):
|
||||
"""
|
||||
Refresh the modules and sections list for the process.
|
||||
|
||||
This will refresh the sections for all modules, not just the selected one.
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Modules'):
|
||||
commands.ghidra_trace_put_modules()
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_process(process: sch.Schema('Process')):
|
||||
"""Switch to the process."""
|
||||
find_proc_by_obj(process)
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_thread(thread: sch.Schema('Thread')):
|
||||
"""Switch to the thread."""
|
||||
find_thread_by_obj(thread)
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_frame(frame: sch.Schema('StackFrame')):
|
||||
"""Select the frame."""
|
||||
find_frame_by_obj(frame)
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
def remove_process(process: sch.Schema('Process')):
|
||||
"""Remove the process."""
|
||||
find_proc_by_obj(process)
|
||||
dbg().detach()
|
||||
|
||||
|
||||
@REGISTRY.method(action='connect')
|
||||
def target(process: sch.Schema('Process'), spec: str):
|
||||
"""Connect to a target machine or process."""
|
||||
find_proc_by_obj(process)
|
||||
dbg().attach(spec)
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
def attach_obj(target: sch.Schema('Attachable')):
|
||||
"""Attach the process to the given target."""
|
||||
pid = find_availpid_by_obj(target)
|
||||
dbg().attach(pid)
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
def attach_pid(pid: int):
|
||||
"""Attach the process to the given target."""
|
||||
dbg().attach(pid)
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
def attach_name(process: sch.Schema('Process'), name: str):
|
||||
"""Attach the process to the given target."""
|
||||
dbg().atach(name)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def detach(process: sch.Schema('Process')):
|
||||
"""Detach the process's target."""
|
||||
dbg().detach()
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
def launch_loader(
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
"""
|
||||
Start a native process with the given command line, stopping at the ntdll initial breakpoint.
|
||||
"""
|
||||
command = file
|
||||
if args != None:
|
||||
command += " "+args
|
||||
commands.ghidra_trace_create(command=file, start_trace=False)
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
def launch(
|
||||
timeout: ParamDesc(int, display='Timeout'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
"""
|
||||
Run a native process with the given command line.
|
||||
"""
|
||||
command = file
|
||||
if args != None:
|
||||
command += " "+args
|
||||
commands.ghidra_trace_create(
|
||||
command, initial_break=False, timeout=timeout, start_trace=False)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def kill(process: sch.Schema('Process')):
|
||||
"""Kill execution of the process."""
|
||||
dbg().terminate()
|
||||
|
||||
|
||||
@REGISTRY.method(name='continue', action='resume')
|
||||
def _continue(process: sch.Schema('Process')):
|
||||
"""Continue execution of the process."""
|
||||
dbg().replay_forward(pyTTD.MAX_STEP, util.last)
|
||||
hooks.on_stop()
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def interrupt(process: sch.Schema('Process')):
|
||||
"""Interrupt the execution of the debugged program."""
|
||||
print("'interrupt' is unsupported for TTD")
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_into')
|
||||
def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||
"""Step one instruction exactly."""
|
||||
# find_thread_by_obj(thread)
|
||||
dbg().replay_forward(n, util.last)
|
||||
hooks.on_stop()
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_over')
|
||||
def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||
"""Step one instruction, but proceed through subroutine calls."""
|
||||
# find_thread_by_obj(thread)
|
||||
dbg().replay_backward(n, util.first)
|
||||
hooks.on_stop()
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_out')
|
||||
def step_out(thread: sch.Schema('Thread')):
|
||||
"""Execute until the current stack frame returns."""
|
||||
dbg().replay_backward(pyTTD.MAX_STEP, util.first)
|
||||
hooks.on_stop()
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_to')
|
||||
def step_to(thread: sch.Schema('Thread'), address: Address, max=None):
|
||||
"""Continue execution up to the given address."""
|
||||
find_thread_by_obj(thread)
|
||||
return dbg().stepto(address.offset, max)
|
||||
|
||||
|
||||
def gen_bpt(offset: int, size: int, flags: int):
|
||||
bp = pyTTD.MemoryWatchpointData(addr=offset, size=size, flags=flags)
|
||||
dbg().add_memory_watchpoint(bp)
|
||||
bpt = util.Watchpoint(offset, size, flags, len(util.breakpoints), bp)
|
||||
util.breakpoints.append(bpt)
|
||||
hooks.on_breakpoint_created(bpt)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_sw_execute')
|
||||
def break_address(process: sch.Schema('Process'), address: Address):
|
||||
"""Set a breakpoint."""
|
||||
gen_bpt(offset=address.offset, size=4, flags=pyTTD.BP_FLAGS.EXEC)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_sw_execute')
|
||||
def break_expression(expression: str):
|
||||
"""Set a breakpoint."""
|
||||
# TODO: Escape?
|
||||
dbg().bp(expr=expression)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_hw_execute')
|
||||
def break_hw_address(process: sch.Schema('Process'), address: Address):
|
||||
"""Set a hardware-assisted breakpoint."""
|
||||
gen_bpt(offset=address.offset, size=4, flags=pyTTD.BP_FLAGS.EXEC)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_hw_execute')
|
||||
def break_hw_expression(expression: str):
|
||||
"""Set a hardware-assisted breakpoint."""
|
||||
dbg().ba(expr=expression)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_read')
|
||||
def break_read_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
"""Set a read watchpoint."""
|
||||
gen_bpt(offset=range.min, size=range.length(), flags=pyTTD.BP_FLAGS.READ)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_read')
|
||||
def break_read_expression(expression: str):
|
||||
"""Set a read watchpoint."""
|
||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_write')
|
||||
def break_write_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
"""Set a watchpoint."""
|
||||
gen_bpt(offset=range.min, size=range.length(), flags=pyTTD.BP_FLAGS.WRITE)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_write')
|
||||
def break_write_expression(expression: str):
|
||||
"""Set a watchpoint."""
|
||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_WRITE)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_access')
|
||||
def break_access_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
"""Set an access watchpoint."""
|
||||
find_proc_by_obj(process)
|
||||
break_read_range(process, range)
|
||||
break_write_range(process, range)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_access')
|
||||
def break_access_expression(expression: str):
|
||||
"""Set an access watchpoint."""
|
||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ | DbgEng.DEBUG_BREAK_WRITE)
|
||||
|
||||
|
||||
@REGISTRY.method(action='toggle')
|
||||
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
||||
"""Toggle a breakpoint."""
|
||||
bpt = find_bpt_by_obj(breakpoint)
|
||||
if enabled:
|
||||
dbg().be(bpt.GetId())
|
||||
else:
|
||||
dbg().bd(bpt.GetId())
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
||||
"""Delete a breakpoint."""
|
||||
bpt = find_bpt_by_obj(breakpoint)
|
||||
dbg().remove_memory_watchpoint(bpt.bp)
|
||||
util.breakpoints.remove(bpt)
|
||||
hooks.on_breakpoint_deleted(bpt)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def read_mem(process: sch.Schema('Process'), range: AddressRange):
|
||||
"""Read memory."""
|
||||
nproc = find_proc_by_obj(process)
|
||||
offset_start = process.trace.memory_mapper.map_back(
|
||||
nproc, Address(range.space, range.min))
|
||||
with commands.open_tracked_tx('Read Memory'):
|
||||
dbg().read_mem(range.min, range.length())
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
|
||||
"""Write memory."""
|
||||
print("'write_mem' is unsupported for TTD")
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
|
||||
"""Write a register."""
|
||||
print("'write_reg' is unsupported for TTD")
|
||||
|
||||
|
||||
def dbg():
|
||||
return util.get_debugger()
|
@ -0,0 +1,278 @@
|
||||
<context>
|
||||
<schema name="TTDSession" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Access" />
|
||||
<interface name="Attacher" />
|
||||
<interface name="Interpreter" />
|
||||
<interface name="Interruptible" />
|
||||
<interface name="Launcher" />
|
||||
<interface name="ActiveScope" />
|
||||
<interface name="EventScope" />
|
||||
<interface name="FocusScope" />
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Processes" schema="ProcessContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Available" schema="AvailableContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_event_thread" schema="OBJECT" hidden="yes" />
|
||||
<attribute name="_focus" schema="Selectable" required="yes" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Selectable" elementResync="NEVER" attributeResync="NEVER">
|
||||
<element schema="OBJECT" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointLocationContainer" />
|
||||
<interface name="BreakpointSpecContainer" />
|
||||
<element schema="BreakpointSpec" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="AvailableContainer" canonical="yes" elementResync="ALWAYS" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Attachable" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="ProcessContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Process" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointSpec" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointSpec" />
|
||||
<interface name="BreakpointLocation" />
|
||||
<interface name="Deletable" />
|
||||
<interface name="Togglable" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Expression" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_expression" to="Expression" />
|
||||
<attribute name="Kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_kinds" to="Kinds" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="Commands" schema="STRING" />
|
||||
<attribute name="Condition" schema="STRING" />
|
||||
<attribute name="Hit Count" schema="INT" />
|
||||
<attribute name="Ignore Count" schema="INT" />
|
||||
<attribute name="Pending" schema="BOOL" />
|
||||
<attribute name="Silent" schema="BOOL" />
|
||||
<attribute name="Temporary" schema="BOOL" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Attachable" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Attachable" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="PID" schema="LONG" />
|
||||
<attribute-alias from="_pid" to="PID" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Process" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="Process" />
|
||||
<interface name="Aggregate" />
|
||||
<interface name="ExecutionStateful" />
|
||||
<interface name="Attacher" />
|
||||
<interface name="Deletable" />
|
||||
<interface name="Detachable" />
|
||||
<interface name="Killable" />
|
||||
<interface name="Launcher" />
|
||||
<interface name="Resumable" />
|
||||
<interface name="Steppable" />
|
||||
<interface name="Interruptible" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
|
||||
<!-- attribute name="Breakpoints" schema="BreakpointLocationContainer" required="yes" fixed="yes" /-->
|
||||
<attribute name="Exit Code" schema="LONG" />
|
||||
<attribute-alias from="_exit_code" to="Exit Code" />
|
||||
<attribute name="Environment" schema="Environment" required="yes" fixed="yes" />
|
||||
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
|
||||
<attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" />
|
||||
<attribute name="PID" schema="LONG" hidden="yes" />
|
||||
<attribute-alias from="_pid" to="PID" />
|
||||
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_state" to="State" />
|
||||
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Environment" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Environment" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="OS" schema="STRING" />
|
||||
<attribute name="Arch" schema="STRING" />
|
||||
<attribute name="Endian" schema="STRING" />
|
||||
<attribute name="Debugger" schema="STRING" />
|
||||
<attribute-alias from="_os" to="OS" />
|
||||
<attribute-alias from="_arch" to="Arch" />
|
||||
<attribute-alias from="_endian" to="Endian" />
|
||||
<attribute-alias from="_debugger" to="Debugger" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="ModuleContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="ModuleContainer" />
|
||||
<element schema="Module" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Memory" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Memory" />
|
||||
<element schema="MemoryRegion" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointLocation" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointLocation" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointLocationContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointLocationContainer" />
|
||||
<element schema="BreakpointLocation" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="ThreadContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Thread" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Method" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Method" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_display" schema="STRING" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_return_type" schema="TYPE" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute schema="VOID" fixed="yes" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="Thread" />
|
||||
<interface name="ExecutionStateful" />
|
||||
<interface name="Steppable" />
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Stack" schema="Stack" required="yes" fixed="yes" />
|
||||
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
|
||||
<attribute name="TID" schema="LONG" />
|
||||
<attribute-alias from="_tid" to="TID" />
|
||||
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_state" to="State" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="Advance" schema="Method" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Module" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Module" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Sections" schema="SectionContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Symbols" schema="SymbolContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute name="Name" schema="STRING" />
|
||||
<attribute-alias from="_module_name" to="Name" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="MemoryRegion" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="MemoryRegion" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Base" schema="LONG" required="yes" fixed="yes" />
|
||||
<attribute name="Object File" schema="STRING" fixed="yes" />
|
||||
<attribute name="_readable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_writable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_executable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="SectionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="SectionContainer" />
|
||||
<element schema="Section" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Stack" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Stack" />
|
||||
<element schema="StackFrame" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="SymbolContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="SymbolNamespace" />
|
||||
<element schema="Symbol" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Symbol" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Symbol" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="StackFrame" />
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Function" schema="STRING" hidden="yes" />
|
||||
<attribute-alias from="_function" to="Function" />
|
||||
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Instruction Offset" schema="ADDRESS" required="yes" />
|
||||
<attribute-alias from="_pc" to="Instruction Offset" />
|
||||
<attribute name="Stack Offset" schema="ADDRESS" />
|
||||
<attribute name="Return Offset" schema="ADDRESS" />
|
||||
<attribute name="Frame Offset" schema="ADDRESS" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="ANY" />
|
||||
</schema>
|
||||
<schema name="Section" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Section" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="Offset" schema="STRING" fixed="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterValueContainer" attributeResync="ONCE">
|
||||
<interface name="RegisterContainer" />
|
||||
<interface name="RegisterBank" />
|
||||
<attribute name="General Purpose Registers" schema="RegisterBank" />
|
||||
<attribute name="Floating Point Registers" schema="RegisterBank" />
|
||||
<attribute name="Advanced Vector Extensions" schema="RegisterBank" />
|
||||
<attribute name="Memory Protection Extensions" schema="RegisterBank" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="RegisterBank" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
</context>
|
@ -0,0 +1,204 @@
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
from collections import namedtuple
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ctypes import *
|
||||
|
||||
from pyttd import pyTTD
|
||||
|
||||
#from pybag import pydbg
|
||||
#from pybag.dbgeng import core as DbgEng
|
||||
#from pybag.dbgeng import exception
|
||||
#from pybag.dbgeng import util as DbgUtil
|
||||
|
||||
base = False
|
||||
eng = False
|
||||
first = False
|
||||
last = False
|
||||
breakpoints = []
|
||||
events = {}
|
||||
evttypes = {}
|
||||
starts = {}
|
||||
stops = {}
|
||||
lastpos = False
|
||||
DbgVersion = namedtuple('DbgVersion', ['full', 'major', 'minor'])
|
||||
|
||||
|
||||
class Watchpoint(object):
|
||||
def __init__(self, addr, size, flags, id, bp):
|
||||
self.addr = addr
|
||||
self.size = size
|
||||
self.flags = flags
|
||||
self.id = id
|
||||
self.bp = bp
|
||||
self.expr = None
|
||||
|
||||
|
||||
def _compute_pydbg_ver():
|
||||
blurb = "" #get_debugger()._control.GetActualProcessorType()
|
||||
full = ""
|
||||
major = 0
|
||||
minor = 0
|
||||
return DbgVersion(full, int(major), int(minor))
|
||||
|
||||
|
||||
DBG_VERSION = _compute_pydbg_ver()
|
||||
|
||||
|
||||
def get_debugger():
|
||||
return base
|
||||
|
||||
|
||||
def get_target():
|
||||
return 0 # get_debugger()._systems.GetCurrentSystemId()
|
||||
|
||||
|
||||
def get_inst(addr):
|
||||
dbg = get_debugger()
|
||||
ins = DbgUtil.disassemble_instruction(
|
||||
dbg.bitness(), addr, dbg.read_mem(addr, 15))
|
||||
return str(ins)
|
||||
|
||||
|
||||
def get_inst_sz(addr):
|
||||
dbg = get_debugger()
|
||||
ins = DbgUtil.disassemble_instruction(
|
||||
dbg.bitness(), addr, dbg.read_mem(addr, 15))
|
||||
return str(ins.size)
|
||||
|
||||
|
||||
def get_breakpoints():
|
||||
return None
|
||||
|
||||
|
||||
def selected_process():
|
||||
try:
|
||||
return 0
|
||||
# return current_process
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def selected_thread():
|
||||
try:
|
||||
dbg = get_debugger()
|
||||
current = dbg.get_thread_info()
|
||||
return current.threadid
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def selected_frame():
|
||||
return 0 # selected_thread().GetSelectedFrame()
|
||||
|
||||
|
||||
def select_process(id: int):
|
||||
return None
|
||||
|
||||
|
||||
def select_thread(id: int):
|
||||
return None
|
||||
|
||||
|
||||
def select_frame(id: int):
|
||||
# TODO: this needs to be fixed
|
||||
return None
|
||||
|
||||
|
||||
def parse_and_eval(expr):
|
||||
dbg = get_debugger()
|
||||
if expr == "$pc":
|
||||
return dbg.get_program_counter()
|
||||
if expr == "$sp":
|
||||
return dbg.get_context_x86_64().rsp
|
||||
return int(expr)
|
||||
|
||||
|
||||
def get_eval(expr, type=None):
|
||||
ctrl = get_debugger()._control._ctrl
|
||||
ctrl.SetExpressionSyntax(1)
|
||||
value = DbgEng._DEBUG_VALUE()
|
||||
index = c_ulong()
|
||||
if type == None:
|
||||
type = DbgEng.DEBUG_VALUE_INT64
|
||||
hr = ctrl.Evaluate(Expression="{}".format(expr).encode(
|
||||
), DesiredType=type, Value=byref(value), RemainderIndex=byref(index))
|
||||
exception.check_err(hr)
|
||||
if type == DbgEng.DEBUG_VALUE_INT8:
|
||||
return value.u.I8
|
||||
if type == DbgEng.DEBUG_VALUE_INT16:
|
||||
return value.u.I16
|
||||
if type == DbgEng.DEBUG_VALUE_INT32:
|
||||
return value.u.I32
|
||||
if type == DbgEng.DEBUG_VALUE_INT64:
|
||||
return value.u.I64.I64
|
||||
if type == DbgEng.DEBUG_VALUE_FLOAT32:
|
||||
return value.u.F32
|
||||
if type == DbgEng.DEBUG_VALUE_FLOAT64:
|
||||
return value.u.F64
|
||||
if type == DbgEng.DEBUG_VALUE_FLOAT80:
|
||||
return value.u.F80Bytes
|
||||
if type == DbgEng.DEBUG_VALUE_FLOAT82:
|
||||
return value.u.F82Bytes
|
||||
if type == DbgEng.DEBUG_VALUE_FLOAT128:
|
||||
return value.u.F128Bytes
|
||||
|
||||
|
||||
def process_list(running=False):
|
||||
"""process_list() -> list of all processes"""
|
||||
sysids = [0]
|
||||
return sysids
|
||||
|
||||
|
||||
def thread_list():
|
||||
"""thread_list() -> list of all threads"""
|
||||
dbg = get_debugger()
|
||||
return dbg.get_thread_list()
|
||||
|
||||
|
||||
def module_list():
|
||||
"""thread_list() -> list of all threads"""
|
||||
dbg = get_debugger()
|
||||
return dbg.get_module_list()
|
||||
|
||||
|
||||
conv_map = {}
|
||||
|
||||
|
||||
def get_convenience_variable(id):
|
||||
#val = get_target().GetEnvironment().Get(id)
|
||||
if id not in conv_map:
|
||||
return "auto"
|
||||
val = conv_map[id]
|
||||
if val is None:
|
||||
return "auto"
|
||||
return val
|
||||
|
||||
|
||||
def set_convenience_variable(id, value):
|
||||
#env = get_target().GetEnvironment()
|
||||
# return env.Set(id, value, True)
|
||||
conv_map[id] = value
|
||||
|
||||
|
||||
def pos2snap(pos: int):
|
||||
index = int(pos.major)
|
||||
if index < 0 or index >= pyTTD.MAX_STEP:
|
||||
return int(last.major)*1000
|
||||
return index*1000+int(pos.minor)
|
@ -408,6 +408,9 @@ public class DbgEngTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
System.out.print(Long.toHexString(offset) + ": ");
|
||||
DebugMemoryBasicInformation info = client.getDataSpaces().queryVirtual(offset);
|
||||
//System.out.println(info);
|
||||
if (info == null) {
|
||||
break;
|
||||
}
|
||||
System.out.println(Long.toHexString(info.baseAddress) + "-" +
|
||||
Long.toHexString(info.regionSize) + ": " + info.state);
|
||||
if (info.baseAddress != offset) {
|
||||
@ -563,7 +566,7 @@ public class DbgEngTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
//@Test - NOT WORKING, addresses in RW memory still not necessarily writeable (why?)
|
||||
public void testWriteMemory() {
|
||||
try (ProcMaker maker = new ProcMaker("notepad")) {
|
||||
maker.start();
|
||||
@ -833,7 +836,7 @@ public class DbgEngTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
//@Test - This test is way broken; the while(true) loop is unexitable
|
||||
public void testAttachLaunch() throws Exception {
|
||||
final String specimenX = "C:\\windows\\write.exe";
|
||||
final String specimenA = "C:\\windows\\notepad.exe";
|
||||
@ -848,7 +851,7 @@ public class DbgEngTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
|
||||
//System.out.println("Attaching...");
|
||||
//client.attachProcess(client.getLocalServer(), proc.pid, BitmaskSet.of());
|
||||
client.createProcess(client.getLocalServer(), specimenA, "", "",
|
||||
client.createProcess(client.getLocalServer(), specimenA, null, null,
|
||||
BitmaskSet.of(DebugCreateFlags.DEBUG_PROCESS),
|
||||
BitmaskSet.of(DebugEngCreateFlags.DEBUG_ECREATE_PROCESS_DEFAULT),
|
||||
BitmaskSet.of(DebugVerifierFlags.DEBUG_VERIFIER_DEFAULT));
|
||||
|
@ -17,19 +17,21 @@
|
||||
#@title gdb
|
||||
#@desc <html><body width="300px">
|
||||
#@desc <h3>Launch with <tt>gdb</tt></h3>
|
||||
#@desc <p>This will launch the target on the local machine using <tt>gdb</tt>. GDB must already
|
||||
#@desc be installed on your system, and it must embed the Python 3 interpreter. You will also
|
||||
#@desc need <tt>protobuf</tt> and <tt>psutil</tt> installed for Python 3.
|
||||
#@desc <p>
|
||||
#@desc This will launch the target on the local machine using <tt>gdb</tt>.
|
||||
#@desc For setup instructions, press <b>F1</b>.
|
||||
#@desc </p>
|
||||
#@desc </body></html>
|
||||
#@menu-group local
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb
|
||||
#@enum StartCmd:str run start starti
|
||||
#@env OPT_GDB_PATH:str="gdb" "Path to gdb" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_START_CMD:StartCmd="start" "Run command" "The gdb command to actually run the target."
|
||||
#@arg :str "Image" "The target binary executable image"
|
||||
#@arg :file "Image" "The target binary executable image"
|
||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||
#@tty TTY_TARGET
|
||||
#@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_START_CMD:StartCmd="starti" "Run command" "The gdb command to actually run the target."
|
||||
#@env OPT_EXTRA_TTY:bool=false "Inferior TTY" "Provide a separate terminal emulator for the target."
|
||||
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
@ -48,6 +50,8 @@ target_image="$1"
|
||||
shift
|
||||
target_args="$@"
|
||||
|
||||
# NOTE: Ghidra will leave TTY_TARGET empty, which gdb takes for the same terminal.
|
||||
|
||||
"$OPT_GDB_PATH" \
|
||||
-q \
|
||||
-ex "set pagination off" \
|
||||
|
77
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/qemu-gdb.sh
Executable file
77
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/qemu-gdb.sh
Executable file
@ -0,0 +1,77 @@
|
||||
#!/usr/bin/bash
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
#@title qemu + gdb
|
||||
#@desc <html><body width="300px">
|
||||
#@desc <h3>Launch with <tt>qemu</tt> and connect with <tt>gdb</tt></h3>
|
||||
#@desc <p>
|
||||
#@desc This will launch the target on the local machine using <tt>qemu</tt>.
|
||||
#@desc Then in a second terminal, it will connect <tt>gdb</tt> to QEMU's GDBstub.
|
||||
#@desc For setup instructions, press <b>F1</b>.
|
||||
#@desc </p>
|
||||
#@desc </body></html>
|
||||
#@menu-group cross
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb_qemu
|
||||
#@arg :file "Image" "The target binary executable image"
|
||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||
#@env GHIDRA_LANG_EXTTOOL_qemu:file="" "QEMU command" "The path to qemu for the target architecture."
|
||||
#@env QEMU_GDB:int=12345 "QEMU Port" "Port for gdb connection to qemu"
|
||||
#@env OPT_EXTRA_QEMU_ARGS:str="" "Extra qemu arguments" "Extra arguments to pass to qemu. Use with care."
|
||||
#@env OPT_GDB_PATH:file="gdb-multiarch" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_EXTRA_TTY:bool=false "QEMU TTY" "Provide a separate terminal emulator for the target."
|
||||
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
elif [ -d ${GHIDRA_HOME}/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
else
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/pypkg/src:$PYTHONPATH
|
||||
fi
|
||||
|
||||
target_image="$1"
|
||||
|
||||
if [ -z "$TTY_TARGET" ]
|
||||
then
|
||||
"$GHIDRA_LANG_EXTTOOL_qemu" $OPT_EXTRA_QEMU_ARGS $@ &
|
||||
else
|
||||
"$GHIDRA_LANG_EXTTOOL_qemu" $OPT_EXTRA_QEMU_ARGS $@ <$TTY_TARGET >$TTY_TARGET 2>&1 &
|
||||
fi
|
||||
|
||||
# Give QEMU a moment to open the socket
|
||||
sleep 0.1
|
||||
|
||||
"$OPT_GDB_PATH" \
|
||||
-q \
|
||||
-ex "set pagination off" \
|
||||
-ex "set confirm off" \
|
||||
-ex "show version" \
|
||||
-ex "python import ghidragdb" \
|
||||
-ex "file \"$target_image\"" \
|
||||
-ex "set args $target_args" \
|
||||
-ex "set inferior-tty $TTY_TARGET" \
|
||||
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
|
||||
-ex "ghidra trace start" \
|
||||
-ex "ghidra trace sync-enable" \
|
||||
-ex "target remote localhost:$QEMU_GDB" \
|
||||
-ex "set confirm on" \
|
||||
-ex "set pagination on"
|
57
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/raw-gdb.sh
Executable file
57
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/raw-gdb.sh
Executable file
@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
#@title raw gdb
|
||||
#@no-image
|
||||
#@desc <html><body width="300px">
|
||||
#@desc <h3>Start <tt>gdb</tt></h3>
|
||||
#@desc <p>
|
||||
#@desc This will start <tt>gdb</tt> and connect to it.
|
||||
#@desc It will not launch a target, so you can (must) set up your target manually.
|
||||
#@desc For setup instructions, press <b>F1</b>.
|
||||
#@desc </p>
|
||||
#@desc </body></html>
|
||||
#@menu-group raw
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb_raw
|
||||
#@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_ARCH:str="i386:x86-64" "Architecture" "Target architecture"
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
elif [ -d ${GHIDRA_HOME}/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
else
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/pypkg/src:$PYTHONPATH
|
||||
fi
|
||||
|
||||
"$OPT_GDB_PATH" \
|
||||
-q \
|
||||
-ex "set pagination off" \
|
||||
-ex "set confirm off" \
|
||||
-ex "show version" \
|
||||
-ex "python import ghidragdb" \
|
||||
-ex "set architecture $OPT_ARCH" \
|
||||
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
|
||||
-ex "ghidra trace start" \
|
||||
-ex "ghidra trace sync-enable" \
|
||||
-ex "set confirm on" \
|
||||
-ex "set pagination on"
|
69
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/remote-gdb.sh
Executable file
69
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/remote-gdb.sh
Executable file
@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
#@title remote gdb
|
||||
#@no-image
|
||||
#@desc <html><body width="300px">
|
||||
#@desc <h3>Launch with local <tt>gdb</tt> and connect to a stub (e.g., <tt>gdbserver</tt>)</h3>
|
||||
#@desc <p>
|
||||
#@desc This will start <tt>gdb</tt> on the local system and then use it to connect to the remote system.
|
||||
#@desc For setup instructions, press <b>F1</b>.
|
||||
#@desc </p>
|
||||
#@desc </body></html>
|
||||
#@menu-group remote
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb_remote
|
||||
#@enum TargetType:str remote extended-remote
|
||||
#@env OPT_TARGET_TYPE:TargetType="remote" "Target" "The type of remote target"
|
||||
#@env OPT_HOST:str="localhost" "Host" "The hostname of the target"
|
||||
#@env OPT_PORT:str="9999" "Port" "The host's listening port"
|
||||
#@env OPT_ARCH:str="" "Architecture (optional)" "Target architecture override"
|
||||
#@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb on the local system. Omit the full path to resolve using the system PATH."
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
elif [ -d ${GHIDRA_HOME}/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
else
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/pypkg/src:$PYTHONPATH
|
||||
fi
|
||||
|
||||
if [ -z "$OPT_ARCH" ]
|
||||
then
|
||||
archcmd=
|
||||
else
|
||||
archcmd=-ex "set arch $OPT_ARCH"
|
||||
fi
|
||||
|
||||
"$OPT_GDB_PATH" \
|
||||
-q \
|
||||
-ex "set pagination off" \
|
||||
-ex "set confirm off" \
|
||||
-ex "show version" \
|
||||
-ex "python import ghidragdb" \
|
||||
$archcmd \
|
||||
-ex "target $OPT_TARGET_TYPE $OPT_HOST:$OPT_PORT" \
|
||||
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
|
||||
-ex "ghidra trace start" \
|
||||
-ex "ghidra trace sync-enable" \
|
||||
-ex "ghidra trace sync-synth-stopped" \
|
||||
-ex "set confirm on" \
|
||||
-ex "set pagination on"
|
55
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdb.sh
Executable file
55
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdb.sh
Executable file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/bash
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
#@timeout 60000
|
||||
#@title gdb via ssh
|
||||
#@desc <html><body width="300px">
|
||||
#@desc <h3>Launch with <tt>gdb</tt> via <tt>ssh</tt></h3>
|
||||
#@desc <p>
|
||||
#@desc This will launch the target on a remote machine using <tt>gdb</tt> via <tt>ssh</tt>.
|
||||
#@desc For setup instructions, press <b>F1</b>.
|
||||
#@desc </p>
|
||||
#@desc </body></html>
|
||||
#@menu-group remote
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb_ssh
|
||||
#@enum StartCmd:str run start starti
|
||||
#@arg :str "Image" "The target binary executable image on the remote system"
|
||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||
#@env OPT_HOST:str="localhost" "[User@]Host" "The hostname or user@host"
|
||||
#@env OPT_REMOTE_PORT:int=12345 "Remote Trace RMI Port" "A free port on the remote end to receive and forward the Trace RMI connection."
|
||||
#@env OPT_EXTRA_SSH_ARGS:str="" "Extra ssh arguments" "Extra arguments to pass to ssh. Use with care."
|
||||
#@env OPT_GDB_PATH:str="gdb" "gdb command" "The path to gdb on the remote system. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_START_CMD:StartCmd="starti" "Run command" "The gdb command to actually run the target."
|
||||
|
||||
target_image="$1"
|
||||
shift
|
||||
target_args="$@"
|
||||
|
||||
ssh "-R$OPT_REMOTE_PORT:$GHIDRA_TRACE_RMI_ADDR" -t $OPT_EXTRA_SSH_ARGS "$OPT_HOST" "TERM='$TERM' '$OPT_GDB_PATH' \
|
||||
-q \
|
||||
-ex 'set pagination off' \
|
||||
-ex 'set confirm off' \
|
||||
-ex 'show version' \
|
||||
-ex 'python import ghidragdb' \
|
||||
-ex 'file \"$target_image\"' \
|
||||
-ex 'set args $target_args' \
|
||||
-ex 'ghidra trace connect \"localhost:$OPT_REMOTE_PORT\"' \
|
||||
-ex 'ghidra trace start' \
|
||||
-ex 'ghidra trace sync-enable' \
|
||||
-ex '$OPT_START_CMD' \
|
||||
-ex 'set confirm on' \
|
||||
-ex 'set pagination on'"
|
63
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdbserver.sh
Executable file
63
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdbserver.sh
Executable file
@ -0,0 +1,63 @@
|
||||
#!/usr/bin/bash
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
#@timeout 60000
|
||||
#@title gdb + gdbserver via ssh
|
||||
#@desc <html><body width="300px">
|
||||
#@desc <h3>Launch with local <tt>gdb</tt> and <tt>gdbserver</tt> via <tt>ssh</tt></h3>
|
||||
#@desc <p>
|
||||
#@desc This will start <tt>gdb</tt> on the local system and then use it to connect and launch the target in <tt>gdbserver</tt> on the remote system via <tt>ssh</tt>.
|
||||
#@desc For setup instructions, press <b>F1</b>.
|
||||
#@desc </p>
|
||||
#@desc </body></html>
|
||||
#@menu-group remote
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb_gdbserver_ssh
|
||||
#@arg :str "Image" "The target binary executable image on the remote system"
|
||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||
#@env OPT_HOST:str="localhost" "[User@]Host" "The hostname or user@host"
|
||||
#@env OPT_EXTRA_SSH_ARGS:str="" "Extra ssh arguments" "Extra arguments to pass to ssh. Use with care."
|
||||
#@env OPT_GDBSERVER_PATH:str="gdbserver" "gdbserver command (remote)" "The path to gdbserver on the remote system. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_EXTRA_GDBSERVER_ARGS:str="" "Extra gdbserver arguments" "Extra arguments to pass to gdbserver. Use with care."
|
||||
#@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb on the local system. Omit the full path to resolve using the system PATH."
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
elif [ -d ${GHIDRA_HOME}/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
else
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/pypkg/src:$PYTHONPATH
|
||||
fi
|
||||
|
||||
"$OPT_GDB_PATH" \
|
||||
-q \
|
||||
-ex "set pagination off" \
|
||||
-ex "set confirm off" \
|
||||
-ex "show version" \
|
||||
-ex "python import ghidragdb" \
|
||||
-ex "set inferior-tty $TTY_TARGET" \
|
||||
-ex "target remote | ssh $OPT_EXTRA_SSH_ARGS '$OPT_HOST' '$OPT_GDBSERVER_PATH' $OPT_EXTRA_GDBSERVER_ARGS - $@" \
|
||||
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
|
||||
-ex "ghidra trace start" \
|
||||
-ex "ghidra trace sync-enable" \
|
||||
-ex "ghidra trace sync-synth-stopped" \
|
||||
-ex "set confirm on" \
|
||||
-ex "set pagination on"
|
66
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/wine-gdb.sh
Executable file
66
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/wine-gdb.sh
Executable file
@ -0,0 +1,66 @@
|
||||
#!/usr/bin/bash
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
#@title wine + gdb
|
||||
#@desc <html><body width="300px">
|
||||
#@desc <h3>Launch with <tt>gdb</tt> and <tt>wine</tt></h3>
|
||||
#@desc <p>
|
||||
#@desc This will launch the target on the local machine using <tt>gdb</tt> and <tt>wine</tt>.
|
||||
#@desc For setup instructions, press <b>F1</b>.
|
||||
#@desc </p>
|
||||
#@desc </body></html>
|
||||
#@menu-group cross
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb_wine
|
||||
#@arg :file "Image" "The target binary executable image"
|
||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||
#@env OPT_WINE_PATH:file="/usr/lib/wine/wine64" "Path to wine binary" "The path to the wine executable for your target architecture."
|
||||
#@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_EXTRA_TTY:bool=false "Inferior TTY" "Provide a separate terminal emulator for the target."
|
||||
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
elif [ -d ${GHIDRA_HOME}/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
else
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/pypkg/src:$PYTHONPATH
|
||||
fi
|
||||
|
||||
# NOTE: Ghidra will leave TTY_TARGET empty, which gdb takes for the same terminal.
|
||||
|
||||
"$OPT_GDB_PATH" \
|
||||
-q \
|
||||
-ex "set pagination off" \
|
||||
-ex "set confirm off" \
|
||||
-ex "show version" \
|
||||
-ex "python import ghidragdb.wine" \
|
||||
-ex "file \"$OPT_WINE_PATH\"" \
|
||||
-ex "set args $@" \
|
||||
-ex "set inferior-tty $TTY_TARGET" \
|
||||
-ex "starti" \
|
||||
-ex "ghidra wine run-to-image \"$1\"" \
|
||||
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
|
||||
-ex "ghidra trace start \"$1\"" \
|
||||
-ex "ghidra trace sync-enable" \
|
||||
-ex "ghidra trace sync-synth-stopped" \
|
||||
-ex "set confirm on" \
|
||||
-ex "set pagination on"
|
@ -25,7 +25,7 @@ import agent.gdb.manager.breakpoint.GdbBreakpointInfo;
|
||||
import agent.gdb.manager.breakpoint.GdbBreakpointInsertions;
|
||||
import agent.gdb.manager.impl.GdbManagerImpl;
|
||||
import ghidra.pty.PtyFactory;
|
||||
import ghidra.pty.linux.LinuxPty;
|
||||
import ghidra.pty.unix.UnixPty;
|
||||
|
||||
/**
|
||||
* The controlling side of a GDB session, using GDB/MI, usually via a pseudo-terminal
|
||||
@ -232,7 +232,7 @@ public interface GdbManager extends AutoCloseable, GdbConsoleOperations, GdbBrea
|
||||
* Note: depending on the target, its output may not be communicated via this listener. Local
|
||||
* targets, e.g., tend to just print output to GDB's controlling TTY. See
|
||||
* {@link GdbInferior#setTty(String)} for a means to more reliably interact with a target's
|
||||
* input and output. See also {@link LinuxPty} for a means to easily acquire a new TTY from
|
||||
* input and output. See also {@link UnixPty} for a means to easily acquire a new TTY from
|
||||
* Java.
|
||||
*
|
||||
* @param listener the listener to add
|
||||
|
@ -40,7 +40,7 @@ import ghidra.program.model.address.*;
|
||||
public class GdbModelTargetModule extends
|
||||
DefaultTargetObject<TargetObject, GdbModelTargetModuleContainer> implements TargetModule {
|
||||
|
||||
public static final String VISIBLE_RANGE_ATTRIBUTE_NAME = "range";
|
||||
public static final String VISIBLE_RANGE_ATTRIBUTE_NAME = "Range";
|
||||
public static final String VISIBLE_MODULE_NAME_ATTRIBUTE_NAME = "module name";
|
||||
|
||||
protected static String indexModule(GdbModule module) {
|
||||
|
@ -35,7 +35,7 @@ import ghidra.program.model.address.*;
|
||||
public class GdbModelTargetSection extends
|
||||
DefaultTargetObject<TargetObject, GdbModelTargetSectionContainer> implements TargetSection {
|
||||
|
||||
public static final String VISIBLE_RANGE_ATTRIBUTE_NAME = "range";
|
||||
public static final String VISIBLE_RANGE_ATTRIBUTE_NAME = "Range";
|
||||
|
||||
protected static String indexSection(GdbModuleSection section) {
|
||||
return section.getName();
|
||||
|
@ -1,10 +1,10 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "ghidragdb"
|
||||
version = "10.4"
|
||||
version = "11.1"
|
||||
authors = [
|
||||
{ name="Ghidra Development Team" },
|
||||
]
|
||||
@ -17,7 +17,7 @@ classifiers = [
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
dependencies = [
|
||||
"ghidratrace==10.4",
|
||||
"ghidratrace==11.1",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
@ -18,6 +18,7 @@ from ghidratrace.client import Address, RegVal
|
||||
import gdb
|
||||
|
||||
# NOTE: This map is derived from the ldefs using a script
|
||||
# i386 is hand-patched
|
||||
language_map = {
|
||||
'aarch64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
|
||||
'aarch64:ilp32': ['AARCH64:BE:32:ilp32', 'AARCH64:LE:32:ilp32', 'AARCH64:LE:64:AppleSilicon'],
|
||||
@ -48,6 +49,7 @@ language_map = {
|
||||
'avr:51': ['avr8:LE:16:atmega256'],
|
||||
'avr:6': ['avr8:LE:16:atmega256'],
|
||||
'hppa2.0w': ['pa-risc:BE:32:default'],
|
||||
'i386': ['x86:LE:32:default'],
|
||||
'i386:intel': ['x86:LE:32:default'],
|
||||
'i386:x86-64': ['x86:LE:64:default'],
|
||||
'i386:x86-64:intel': ['x86:LE:64:default'],
|
||||
@ -115,7 +117,7 @@ def get_endian():
|
||||
|
||||
def get_osabi():
|
||||
parm = gdb.parameter('osabi')
|
||||
if not parm in ['auto', 'default']:
|
||||
if not parm in ['', 'auto', 'default']:
|
||||
return parm
|
||||
# We have to hack around the fact the GDB won't give us the current OS ABI
|
||||
# via the API if it is "auto" or "default". Using "show", we can get it, but
|
||||
|
@ -50,8 +50,6 @@ STACK_PATTERN = THREAD_PATTERN + '.Stack'
|
||||
FRAME_KEY_PATTERN = '[{level}]'
|
||||
FRAME_PATTERN = STACK_PATTERN + FRAME_KEY_PATTERN
|
||||
REGS_PATTERN = FRAME_PATTERN + '.Registers'
|
||||
REG_KEY_PATTERN = '[{regname}]'
|
||||
REG_PATTERN = REGS_PATTERN + REG_KEY_PATTERN
|
||||
MEMORY_PATTERN = INFERIOR_PATTERN + '.Memory'
|
||||
REGION_KEY_PATTERN = '[{start:08x}]'
|
||||
REGION_PATTERN = MEMORY_PATTERN + REGION_KEY_PATTERN
|
||||
@ -165,21 +163,22 @@ def cmd(cli_name, mi_name, cli_class, cli_repeat):
|
||||
_CLICmd.__doc__ = func.__doc__
|
||||
_CLICmd()
|
||||
|
||||
class _MICmd(gdb.MICommand):
|
||||
if hasattr(gdb, 'MICommand'):
|
||||
class _MICmd(gdb.MICommand):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(mi_name)
|
||||
def __init__(self):
|
||||
super().__init__(mi_name)
|
||||
|
||||
def invoke(self, argv):
|
||||
try:
|
||||
return func(*argv, is_mi=True)
|
||||
except TypeError as e:
|
||||
raise gdb.GdbError(e.args[0].replace(func.__name__ + "()",
|
||||
mi_name))
|
||||
def invoke(self, argv):
|
||||
try:
|
||||
return func(*argv, is_mi=True)
|
||||
except TypeError as e:
|
||||
raise gdb.GdbError(e.args[0].replace(func.__name__ + "()",
|
||||
mi_name))
|
||||
|
||||
_MICmd.__doc__ = func.__doc__
|
||||
_MICmd()
|
||||
return func
|
||||
_MICmd.__doc__ = func.__doc__
|
||||
_MICmd()
|
||||
return func
|
||||
|
||||
return _cmd
|
||||
|
||||
@ -275,8 +274,11 @@ def start_trace(name):
|
||||
with open(schema_fn, 'r') as schema_file:
|
||||
schema_xml = schema_file.read()
|
||||
with STATE.trace.open_tx("Create Root Object"):
|
||||
root = STATE.trace.create_root_object(schema_xml, 'Session')
|
||||
root = STATE.trace.create_root_object(schema_xml, 'GdbSession')
|
||||
root.set_value('_display', 'GNU gdb ' + util.GDB_VERSION.full)
|
||||
STATE.trace.create_object(AVAILABLES_PATH).insert()
|
||||
STATE.trace.create_object(BREAKPOINTS_PATH).insert()
|
||||
STATE.trace.create_object(INFERIORS_PATH).insert()
|
||||
gdb.set_convenience_variable('_ghidra_tracing', True)
|
||||
|
||||
|
||||
@ -467,11 +469,14 @@ def ghidra_trace_set_snap(snap, *, is_mi, **kwargs):
|
||||
STATE.require_trace().set_snap(int(gdb.parse_and_eval(snap)))
|
||||
|
||||
|
||||
def quantize_pages(start, end):
|
||||
return (start // PAGE_SIZE * PAGE_SIZE, (end+PAGE_SIZE-1) // PAGE_SIZE*PAGE_SIZE)
|
||||
|
||||
|
||||
def put_bytes(start, end, pages, is_mi, from_tty):
|
||||
trace = STATE.require_trace()
|
||||
if pages:
|
||||
start = start // PAGE_SIZE * PAGE_SIZE
|
||||
end = (end + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE
|
||||
start, end = quantize_pages(start, end)
|
||||
inf = gdb.selected_inferior()
|
||||
buf = bytes(inf.read_memory(start, end - start))
|
||||
|
||||
@ -486,6 +491,8 @@ def put_bytes(start, end, pages, is_mi, from_tty):
|
||||
|
||||
|
||||
def eval_address(address):
|
||||
if isinstance(address, int):
|
||||
return address
|
||||
try:
|
||||
return int(gdb.parse_and_eval(address))
|
||||
except gdb.error as e:
|
||||
@ -494,10 +501,13 @@ def eval_address(address):
|
||||
|
||||
def eval_range(address, length):
|
||||
start = eval_address(address)
|
||||
try:
|
||||
end = start + int(gdb.parse_and_eval(length))
|
||||
except gdb.error as e:
|
||||
raise gdb.GdbError("Cannot convert '{}' to length".format(length))
|
||||
if isinstance(length, int):
|
||||
end = start + length
|
||||
else:
|
||||
try:
|
||||
end = start + int(gdb.parse_and_eval(length))
|
||||
except gdb.error as e:
|
||||
raise gdb.GdbError("Cannot convert '{}' to length".format(length))
|
||||
return start, end
|
||||
|
||||
|
||||
@ -532,6 +542,18 @@ def ghidra_trace_putval(value, pages=True, *, is_mi, from_tty=True, **kwargs):
|
||||
return put_bytes(start, end, pages, is_mi, from_tty)
|
||||
|
||||
|
||||
def putmem_state(address, length, state, pages=True):
|
||||
STATE.trace.validate_state(state)
|
||||
start, end = eval_range(address, length)
|
||||
if pages:
|
||||
start, end = quantize_pages(start, end)
|
||||
inf = gdb.selected_inferior()
|
||||
base, addr = STATE.trace.memory_mapper.map(inf, start)
|
||||
if base != addr.space:
|
||||
STATE.trace.create_overlay_space(base, addr.space)
|
||||
STATE.trace.set_memory_state(addr.extend(end - start), state)
|
||||
|
||||
|
||||
@cmd('ghidra trace putmem-state', '-ghidra-trace-putmem-state', gdb.COMMAND_DATA, True)
|
||||
def ghidra_trace_putmem_state(address, length, state, *, is_mi, **kwargs):
|
||||
"""
|
||||
@ -539,13 +561,7 @@ def ghidra_trace_putmem_state(address, length, state, *, is_mi, **kwargs):
|
||||
"""
|
||||
|
||||
STATE.require_tx()
|
||||
STATE.trace.validate_state(state)
|
||||
start, end = eval_range(address, length)
|
||||
inf = gdb.selected_inferior()
|
||||
base, addr = STATE.trace.memory_mapper.map(inf, start)
|
||||
if base != addr.space:
|
||||
trace.create_overlay_space(base, addr.space)
|
||||
STATE.trace.set_memory_state(addr.extend(end - start), state)
|
||||
putmem_state(address, length, state, True)
|
||||
|
||||
|
||||
@cmd('ghidra trace delmem', '-ghidra-trace-delmem', gdb.COMMAND_DATA, True)
|
||||
@ -569,25 +585,19 @@ def ghidra_trace_delmem(address, length, *, is_mi, **kwargs):
|
||||
def putreg(frame, reg_descs):
|
||||
inf = gdb.selected_inferior()
|
||||
space = REGS_PATTERN.format(infnum=inf.num, tnum=gdb.selected_thread().num,
|
||||
level=frame.level())
|
||||
level=util.get_level(frame))
|
||||
STATE.trace.create_overlay_space('register', space)
|
||||
cobj = STATE.trace.create_object(space)
|
||||
cobj.insert()
|
||||
mapper = STATE.trace.register_mapper
|
||||
keys = []
|
||||
values = []
|
||||
endian = arch.get_endian()
|
||||
for desc in reg_descs:
|
||||
v = frame.read_register(desc)
|
||||
v = frame.read_register(desc.name)
|
||||
rv = mapper.map_value(inf, desc.name, v)
|
||||
values.append(rv)
|
||||
# TODO: Key by gdb's name or mapped name? I think gdb's.
|
||||
rpath = REG_PATTERN.format(infnum=inf.num, tnum=gdb.selected_thread(
|
||||
).num, level=frame.level(), regname=desc.name)
|
||||
keys.append(REG_KEY_PATTERN.format(regname=desc.name))
|
||||
robj = STATE.trace.create_object(rpath)
|
||||
robj.set_value('_value', rv.value)
|
||||
robj.insert()
|
||||
cobj.retain_values(keys)
|
||||
value = hex(int.from_bytes(rv.value, endian))
|
||||
cobj.set_value(desc.name, str(value))
|
||||
# TODO: Memorize registers that failed for this arch, and omit later.
|
||||
missing = STATE.trace.put_registers(space, values)
|
||||
return {'missing': missing}
|
||||
@ -604,7 +614,7 @@ def ghidra_trace_putreg(group='all', *, is_mi, **kwargs):
|
||||
STATE.require_tx()
|
||||
frame = gdb.selected_frame()
|
||||
with STATE.client.batch() as b:
|
||||
return putreg(frame, frame.architecture().registers(group))
|
||||
return putreg(frame, util.get_register_descs(frame.architecture(), group))
|
||||
|
||||
|
||||
@cmd('ghidra trace delreg', '-ghidra-trace-delreg', gdb.COMMAND_DATA, True)
|
||||
@ -619,11 +629,11 @@ def ghidra_trace_delreg(group='all', *, is_mi, **kwargs):
|
||||
inf = gdb.selected_inferior()
|
||||
frame = gdb.selected_frame()
|
||||
space = 'Inferiors[{}].Threads[{}].Stack[{}].Registers'.format(
|
||||
inf.num, gdb.selected_thread().num, frame.level()
|
||||
inf.num, gdb.selected_thread().num, util.get_level(frame)
|
||||
)
|
||||
mapper = STATE.trace.register_mapper
|
||||
names = []
|
||||
for desc in frame.architecture().registers(group):
|
||||
for desc in util.get_register_descs(frame.architecture(), group):
|
||||
names.append(mapper.map_name(inf, desc.name))
|
||||
return STATE.trace.delete_registers(space, names)
|
||||
|
||||
@ -945,7 +955,7 @@ def activate(path=None):
|
||||
else:
|
||||
frame = gdb.selected_frame()
|
||||
path = FRAME_PATTERN.format(
|
||||
infnum=inf.num, tnum=t.num, level=frame.level())
|
||||
infnum=inf.num, tnum=t.num, level=util.get_level(frame))
|
||||
trace.proxy_object_path(path).activate()
|
||||
|
||||
|
||||
@ -1000,11 +1010,11 @@ def put_inferior_state(inf):
|
||||
ipath = INFERIOR_PATTERN.format(infnum=inf.num)
|
||||
infobj = STATE.trace.proxy_object_path(ipath)
|
||||
istate = compute_inf_state(inf)
|
||||
infobj.set_value('_state', istate)
|
||||
infobj.set_value('State', istate)
|
||||
for t in inf.threads():
|
||||
tpath = THREAD_PATTERN.format(infnum=inf.num, tnum=t.num)
|
||||
tobj = STATE.trace.proxy_object_path(tpath)
|
||||
tobj.set_value('_state', convert_state(t))
|
||||
tobj.set_value('State', convert_state(t))
|
||||
|
||||
|
||||
def put_inferiors():
|
||||
@ -1016,7 +1026,7 @@ def put_inferiors():
|
||||
keys.append(INFERIOR_KEY_PATTERN.format(infnum=inf.num))
|
||||
infobj = STATE.trace.create_object(ipath)
|
||||
istate = compute_inf_state(inf)
|
||||
infobj.set_value('_state', istate)
|
||||
infobj.set_value('State', istate)
|
||||
infobj.insert()
|
||||
STATE.trace.proxy_object_path(INFERIORS_PATH).retain_values(keys)
|
||||
|
||||
@ -1042,8 +1052,8 @@ def put_available():
|
||||
ppath = AVAILABLE_PATTERN.format(pid=proc.pid)
|
||||
procobj = STATE.trace.create_object(ppath)
|
||||
keys.append(AVAILABLE_KEY_PATTERN.format(pid=proc.pid))
|
||||
procobj.set_value('_pid', proc.pid)
|
||||
procobj.set_value('_display', '{} {}'.format(proc.pid, proc.name))
|
||||
procobj.set_value('PID', proc.pid)
|
||||
procobj.set_value('_display', '{} {}'.format(proc.pid, proc.name()))
|
||||
procobj.insert()
|
||||
STATE.trace.proxy_object_path(AVAILABLES_PATH).retain_values(keys)
|
||||
|
||||
@ -1064,28 +1074,28 @@ def put_single_breakpoint(b, ibobj, inf, ikeys):
|
||||
mapper = STATE.trace.memory_mapper
|
||||
bpath = BREAKPOINT_PATTERN.format(breaknum=b.number)
|
||||
brkobj = STATE.trace.create_object(bpath)
|
||||
brkobj.set_value('_enabled', b.enabled)
|
||||
brkobj.set_value('Enabled', b.enabled)
|
||||
if b.type == gdb.BP_BREAKPOINT:
|
||||
brkobj.set_value('_expression', b.location)
|
||||
brkobj.set_value('_kinds', 'SW_EXECUTE')
|
||||
brkobj.set_value('Expression', b.location)
|
||||
brkobj.set_value('Kinds', 'SW_EXECUTE')
|
||||
elif b.type == gdb.BP_HARDWARE_BREAKPOINT:
|
||||
brkobj.set_value('_expression', b.location)
|
||||
brkobj.set_value('_kinds', 'HW_EXECUTE')
|
||||
brkobj.set_value('Expression', b.location)
|
||||
brkobj.set_value('Kinds', 'HW_EXECUTE')
|
||||
elif b.type == gdb.BP_WATCHPOINT:
|
||||
brkobj.set_value('_expression', b.expression)
|
||||
brkobj.set_value('_kinds', 'WRITE')
|
||||
brkobj.set_value('Expression', b.expression)
|
||||
brkobj.set_value('Kinds', 'WRITE')
|
||||
elif b.type == gdb.BP_HARDWARE_WATCHPOINT:
|
||||
brkobj.set_value('_expression', b.expression)
|
||||
brkobj.set_value('_kinds', 'WRITE')
|
||||
brkobj.set_value('Expression', b.expression)
|
||||
brkobj.set_value('Kinds', 'WRITE')
|
||||
elif b.type == gdb.BP_READ_WATCHPOINT:
|
||||
brkobj.set_value('_expression', b.expression)
|
||||
brkobj.set_value('_kinds', 'READ')
|
||||
brkobj.set_value('Expression', b.expression)
|
||||
brkobj.set_value('Kinds', 'READ')
|
||||
elif b.type == gdb.BP_ACCESS_WATCHPOINT:
|
||||
brkobj.set_value('_expression', b.expression)
|
||||
brkobj.set_value('_kinds', 'READ,WRITE')
|
||||
brkobj.set_value('Expression', b.expression)
|
||||
brkobj.set_value('Kinds', 'READ,WRITE')
|
||||
else:
|
||||
brkobj.set_value('_expression', '(unknown)')
|
||||
brkobj.set_value('_kinds', '')
|
||||
brkobj.set_value('Expression', '(unknown)')
|
||||
brkobj.set_value('Kinds', '')
|
||||
brkobj.set_value('Commands', b.commands)
|
||||
brkobj.set_value('Condition', b.condition)
|
||||
brkobj.set_value('Hit Count', b.hit_count)
|
||||
@ -1104,14 +1114,14 @@ def put_single_breakpoint(b, ibobj, inf, ikeys):
|
||||
if inf.num not in l.thread_groups:
|
||||
continue
|
||||
locobj = STATE.trace.create_object(bpath + k)
|
||||
locobj.set_value('_enabled', l.enabled)
|
||||
locobj.set_value('Enabled', l.enabled)
|
||||
ik = INF_BREAK_KEY_PATTERN.format(breaknum=b.number, locnum=i+1)
|
||||
ikeys.append(ik)
|
||||
if b.location is not None: # Implies execution break
|
||||
base, addr = mapper.map(inf, l.address)
|
||||
if base != addr.space:
|
||||
STATE.trace.create_overlay_space(base, addr.space)
|
||||
locobj.set_value('_range', addr.extend(1))
|
||||
locobj.set_value('Range', addr.extend(1))
|
||||
elif b.expression is not None: # Implies watchpoint
|
||||
expr = b.expression
|
||||
if expr.startswith('-location '):
|
||||
@ -1123,7 +1133,7 @@ def put_single_breakpoint(b, ibobj, inf, ikeys):
|
||||
STATE.trace.create_overlay_space(base, addr.space)
|
||||
size = int(gdb.parse_and_eval(
|
||||
'sizeof({})'.format(expr)))
|
||||
locobj.set_value('_range', addr.extend(size))
|
||||
locobj.set_value('Range', addr.extend(size))
|
||||
except Exception as e:
|
||||
gdb.write("Error: Could not get range for breakpoint {}: {}\n".format(
|
||||
ik, e), stream=gdb.STDERR)
|
||||
@ -1165,10 +1175,11 @@ def put_environment():
|
||||
inf = gdb.selected_inferior()
|
||||
epath = ENV_PATTERN.format(infnum=inf.num)
|
||||
envobj = STATE.trace.create_object(epath)
|
||||
envobj.set_value('_debugger', 'gdb')
|
||||
envobj.set_value('_arch', arch.get_arch())
|
||||
envobj.set_value('_os', arch.get_osabi())
|
||||
envobj.set_value('_endian', arch.get_endian())
|
||||
envobj.set_value('Debugger', 'gdb')
|
||||
envobj.set_value('Arch', arch.get_arch())
|
||||
envobj.set_value('OS', arch.get_osabi())
|
||||
envobj.set_value('Endian', arch.get_endian())
|
||||
envobj.insert()
|
||||
|
||||
|
||||
@cmd('ghidra trace put-environment', '-ghidra-trace-put-environment',
|
||||
@ -1200,12 +1211,15 @@ def put_regions():
|
||||
start_base, start_addr = mapper.map(inf, r.start)
|
||||
if start_base != start_addr.space:
|
||||
STATE.trace.create_overlay_space(start_base, start_addr.space)
|
||||
regobj.set_value('_range', start_addr.extend(r.end - r.start))
|
||||
regobj.set_value('Range', start_addr.extend(r.end - r.start))
|
||||
if r.perms != None:
|
||||
regobj.set_value('Permissions', r.perms)
|
||||
regobj.set_value('_readable', r.perms == None or 'r' in r.perms)
|
||||
regobj.set_value('_writable', r.perms == None or 'w' in r.perms)
|
||||
regobj.set_value('_executable', r.perms == None or 'x' in r.perms)
|
||||
regobj.set_value('_offset', r.offset)
|
||||
regobj.set_value('_objfile', r.objfile)
|
||||
regobj.set_value('Offset', hex(r.offset))
|
||||
regobj.set_value('Object File', r.objfile)
|
||||
regobj.set_value('_display', f'{r.objfile} (0x{r.start:x}-0x{r.end:x})')
|
||||
regobj.insert()
|
||||
STATE.trace.proxy_object_path(
|
||||
MEMORY_PATTERN.format(infnum=inf.num)).retain_values(keys)
|
||||
@ -1223,43 +1237,47 @@ def ghidra_trace_put_regions(*, is_mi, **kwargs):
|
||||
put_regions()
|
||||
|
||||
|
||||
def put_modules():
|
||||
def put_modules(modules=None, sections=False):
|
||||
inf = gdb.selected_inferior()
|
||||
modules = util.MODULE_INFO_READER.get_modules()
|
||||
if modules is None:
|
||||
modules = util.MODULE_INFO_READER.get_modules()
|
||||
mapper = STATE.trace.memory_mapper
|
||||
mod_keys = []
|
||||
for mk, m in modules.items():
|
||||
mpath = MODULE_PATTERN.format(infnum=inf.num, modpath=mk)
|
||||
modobj = STATE.trace.create_object(mpath)
|
||||
mod_keys.append(MODULE_KEY_PATTERN.format(modpath=mk))
|
||||
modobj.set_value('_module_name', m.name)
|
||||
modobj.set_value('Name', m.name)
|
||||
base_base, base_addr = mapper.map(inf, m.base)
|
||||
if base_base != base_addr.space:
|
||||
STATE.trace.create_overlay_space(base_base, base_addr.space)
|
||||
modobj.set_value('_range', base_addr.extend(m.max - m.base))
|
||||
sec_keys = []
|
||||
for sk, s in m.sections.items():
|
||||
spath = mpath + SECTION_ADD_PATTERN.format(secname=sk)
|
||||
secobj = STATE.trace.create_object(spath)
|
||||
sec_keys.append(SECTION_KEY_PATTERN.format(secname=sk))
|
||||
start_base, start_addr = mapper.map(inf, s.start)
|
||||
if start_base != start_addr.space:
|
||||
STATE.trace.create_overlay_space(
|
||||
start_base, start_addr.space)
|
||||
secobj.set_value('_range', start_addr.extend(s.end - s.start))
|
||||
secobj.set_value('_offset', s.offset)
|
||||
secobj.set_value('_attrs', s.attrs, schema=sch.STRING_ARR)
|
||||
secobj.insert()
|
||||
# In case there are no sections, we must still insert the module
|
||||
modobj.insert()
|
||||
STATE.trace.proxy_object_path(
|
||||
mpath + SECTIONS_ADD_PATTERN).retain_values(sec_keys)
|
||||
STATE.trace.proxy_object_path(MODULES_PATTERN.format(
|
||||
infnum=inf.num)).retain_values(mod_keys)
|
||||
modobj.set_value('Range', base_addr.extend(m.max - m.base))
|
||||
if sections:
|
||||
sec_keys = []
|
||||
for sk, s in m.sections.items():
|
||||
spath = mpath + SECTION_ADD_PATTERN.format(secname=sk)
|
||||
secobj = STATE.trace.create_object(spath)
|
||||
sec_keys.append(SECTION_KEY_PATTERN.format(secname=sk))
|
||||
start_base, start_addr = mapper.map(inf, s.start)
|
||||
if start_base != start_addr.space:
|
||||
STATE.trace.create_overlay_space(
|
||||
start_base, start_addr.space)
|
||||
secobj.set_value('Range', start_addr.extend(s.end - s.start))
|
||||
secobj.set_value('Offset', hex(s.offset))
|
||||
secobj.set_value('Attrs', s.attrs, schema=sch.STRING_ARR)
|
||||
secobj.insert()
|
||||
STATE.trace.proxy_object_path(
|
||||
mpath + SECTIONS_ADD_PATTERN).retain_values(sec_keys)
|
||||
|
||||
scpath = mpath + SECTIONS_ADD_PATTERN
|
||||
sec_container_obj = STATE.trace.create_object(scpath)
|
||||
sec_container_obj.insert()
|
||||
if not sections:
|
||||
STATE.trace.proxy_object_path(MODULES_PATTERN.format(
|
||||
infnum=inf.num)).retain_values(mod_keys)
|
||||
|
||||
|
||||
@cmd('ghidra trace put-modules', '-ghidra-trace-put-modules', gdb.COMMAND_DATA,
|
||||
True)
|
||||
@cmd('ghidra trace put-modules', '-ghidra-trace-put-modules', gdb.COMMAND_DATA, True)
|
||||
def ghidra_trace_put_modules(*, is_mi, **kwargs):
|
||||
"""
|
||||
Gather object files, if applicable, and write to the trace's Modules.
|
||||
@ -1270,6 +1288,25 @@ def ghidra_trace_put_modules(*, is_mi, **kwargs):
|
||||
put_modules()
|
||||
|
||||
|
||||
@cmd('ghidra trace put-sections', '-ghidra-trace-put-sections', gdb.COMMAND_DATA, True)
|
||||
def ghidra_trace_put_sections(module_name, *, is_mi, **kwargs):
|
||||
"""
|
||||
Write the sections of the given module or all modules
|
||||
"""
|
||||
|
||||
modules = None
|
||||
if module_name != '-all-objects':
|
||||
modules = {mk: m for mk, m in util.MODULE_INFO_READER.get_modules(
|
||||
).items() if mk == module_name}
|
||||
if len(modules) == 0:
|
||||
raise gdb.GdbError(
|
||||
"No module / object named {}".format(module_name))
|
||||
|
||||
STATE.require_tx()
|
||||
with STATE.client.batch() as b:
|
||||
put_modules(modules, True)
|
||||
|
||||
|
||||
def convert_state(t):
|
||||
if t.is_exited():
|
||||
return 'TERMINATED'
|
||||
@ -1315,10 +1352,10 @@ def put_threads():
|
||||
tpath = THREAD_PATTERN.format(infnum=inf.num, tnum=t.num)
|
||||
tobj = STATE.trace.create_object(tpath)
|
||||
keys.append(THREAD_KEY_PATTERN.format(tnum=t.num))
|
||||
tobj.set_value('_state', convert_state(t))
|
||||
tobj.set_value('_name', t.name)
|
||||
tobj.set_value('State', convert_state(t))
|
||||
tobj.set_value('Name', t.name)
|
||||
tid = convert_tid(t.ptid)
|
||||
tobj.set_value('_tid', tid)
|
||||
tobj.set_value('TID', tid)
|
||||
tidstr = ('0x{:x}' if radix ==
|
||||
16 else '0{:o}' if radix == 8 else '{}').format(tid)
|
||||
tobj.set_value('_short_display', '[{}.{}:{}]'.format(
|
||||
@ -1362,19 +1399,21 @@ def put_frames():
|
||||
bt = gdb.execute('bt', to_string=True).strip().split('\n')
|
||||
f = newest_frame(gdb.selected_frame())
|
||||
keys = []
|
||||
level = 0
|
||||
while f is not None:
|
||||
fpath = FRAME_PATTERN.format(
|
||||
infnum=inf.num, tnum=t.num, level=f.level())
|
||||
infnum=inf.num, tnum=t.num, level=level)
|
||||
fobj = STATE.trace.create_object(fpath)
|
||||
keys.append(FRAME_KEY_PATTERN.format(level=f.level()))
|
||||
keys.append(FRAME_KEY_PATTERN.format(level=level))
|
||||
base, pc = mapper.map(inf, f.pc())
|
||||
if base != pc.space:
|
||||
STATE.trace.create_overlay_space(base, pc.space)
|
||||
fobj.set_value('_pc', pc)
|
||||
fobj.set_value('_func', str(f.function()))
|
||||
fobj.set_value('PC', pc)
|
||||
fobj.set_value('Function', str(f.function()))
|
||||
fobj.set_value(
|
||||
'_display', bt[f.level()].strip().replace('\\s+', ' '))
|
||||
'_display', bt[level].strip().replace('\\s+', ' '))
|
||||
f = f.older()
|
||||
level += 1
|
||||
fobj.insert()
|
||||
STATE.trace.proxy_object_path(STACK_PATTERN.format(
|
||||
infnum=inf.num, tnum=t.num)).retain_values(keys)
|
||||
@ -1405,8 +1444,8 @@ def ghidra_trace_put_all(*, is_mi, **kwargs):
|
||||
ghidra_trace_putmem("$sp", "1", is_mi=is_mi)
|
||||
put_inferiors()
|
||||
put_environment()
|
||||
put_regions()
|
||||
put_modules()
|
||||
put_regions()
|
||||
put_threads()
|
||||
put_frames()
|
||||
put_breakpoints()
|
||||
@ -1463,13 +1502,26 @@ def ghidra_trace_sync_disable(*, is_mi, **kwargs):
|
||||
"""
|
||||
Cease synchronizing the current inferior with the Ghidra trace.
|
||||
|
||||
This is the opposite of 'ghidra trace sync-disable', except it will not
|
||||
This is the opposite of 'ghidra trace sync-enable', except it will not
|
||||
automatically remove hooks.
|
||||
"""
|
||||
|
||||
hooks.disable_current_inferior()
|
||||
|
||||
|
||||
@cmd('ghidra trace sync-synth-stopped', '-ghidra-trace-sync-synth-stopped',
|
||||
gdb.COMMAND_SUPPORT, False)
|
||||
def ghidra_trace_sync_synth_stopped(*, is_mi, **kwargs):
|
||||
"""
|
||||
Act as though the target has just stopped.
|
||||
|
||||
This may need to be invoked immediately after 'ghidra trace sync-enable',
|
||||
to ensure the first snapshot displays the initial/current target state.
|
||||
"""
|
||||
|
||||
hooks.on_stop(object()) # Pass a fake event
|
||||
|
||||
|
||||
@cmd('ghidra util wait-stopped', '-ghidra-util-wait-stopped', gdb.COMMAND_NONE, False)
|
||||
def ghidra_util_wait_stopped(timeout='1', *, is_mi, **kwargs):
|
||||
"""
|
||||
|
@ -13,11 +13,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
import functools
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import gdb
|
||||
|
||||
from . import commands
|
||||
from . import commands, util
|
||||
|
||||
|
||||
class GhidraHookPrefix(gdb.Command):
|
||||
@ -46,8 +48,8 @@ class HookState(object):
|
||||
def end_batch(self):
|
||||
if self.batch is None:
|
||||
return
|
||||
commands.STATE.client.end_batch()
|
||||
self.batch = None
|
||||
commands.STATE.client.end_batch()
|
||||
|
||||
def check_skip_continue(self):
|
||||
skip = self.skip_continue
|
||||
@ -87,18 +89,20 @@ class InferiorState(object):
|
||||
commands.put_frames()
|
||||
self.visited.add(thread)
|
||||
frame = gdb.selected_frame()
|
||||
hashable_frame = (thread, frame.level())
|
||||
hashable_frame = (thread, util.get_level(frame))
|
||||
if first or hashable_frame not in self.visited:
|
||||
commands.putreg(
|
||||
frame, frame.architecture().registers('general'))
|
||||
frame, util.get_register_descs(frame.architecture(), 'general'))
|
||||
commands.putmem("$pc", "1", from_tty=False)
|
||||
commands.putmem("$sp", "1", from_tty=False)
|
||||
self.visited.add(hashable_frame)
|
||||
if first or self.regions or self.threads or self.modules:
|
||||
# Sections, memory syscalls, or stack allocations
|
||||
commands.put_modules()
|
||||
self.modules = False
|
||||
commands.put_regions()
|
||||
self.regions = False
|
||||
if first or self.modules:
|
||||
elif first or self.modules:
|
||||
commands.put_modules()
|
||||
self.modules = False
|
||||
if first or self.breaks:
|
||||
@ -113,8 +117,8 @@ class InferiorState(object):
|
||||
inf = gdb.selected_inferior()
|
||||
ipath = commands.INFERIOR_PATTERN.format(infnum=inf.num)
|
||||
infobj = commands.STATE.trace.proxy_object_path(ipath)
|
||||
infobj.set_value('_exit_code', exit_code)
|
||||
infobj.set_value('_state', 'TERMINATED')
|
||||
infobj.set_value('Exit Code', exit_code)
|
||||
infobj.set_value('State', 'TERMINATED')
|
||||
|
||||
|
||||
class BrkState(object):
|
||||
@ -142,6 +146,27 @@ BRK_STATE = BrkState()
|
||||
INF_STATES = {}
|
||||
|
||||
|
||||
def log_errors(func):
|
||||
'''
|
||||
Wrap a function in a try-except that prints and reraises the
|
||||
exception.
|
||||
|
||||
This is needed because pybag and/or the COM wrappers do not print
|
||||
exceptions that occur during event callbacks.
|
||||
'''
|
||||
|
||||
@functools.wraps(func)
|
||||
def _func(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
return _func
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_new_inferior(event):
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
@ -164,6 +189,7 @@ def on_inferior_selected():
|
||||
commands.activate()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_inferior_deleted(event):
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
@ -175,6 +201,7 @@ def on_inferior_deleted(event):
|
||||
commands.put_inferiors() # TODO: Could just delete the one....
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_new_thread(event):
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
@ -207,11 +234,12 @@ def on_frame_selected():
|
||||
t = gdb.selected_thread()
|
||||
f = gdb.selected_frame()
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Frame {}.{}.{} selected".format(inf.num, t.num, f.level())):
|
||||
with trace.open_tx("Frame {}.{}.{} selected".format(inf.num, t.num, util.get_level(f))):
|
||||
INF_STATES[inf.num].record()
|
||||
commands.activate()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_syscall_memory():
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
@ -219,6 +247,7 @@ def on_syscall_memory():
|
||||
INF_STATES[inf.num].regions = True
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_memory_changed(event):
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
@ -232,6 +261,7 @@ def on_memory_changed(event):
|
||||
pages=False, is_mi=False, from_tty=False)
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_register_changed(event):
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
@ -244,9 +274,10 @@ def on_register_changed(event):
|
||||
# For now, just record the lot
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Register {} changed".format(event.regnum)):
|
||||
commands.putreg(event.frame, event.frame.architecture().registers())
|
||||
commands.putreg(event.frame, util.get_register_descs(event.frame.architecture()))
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_cont(event):
|
||||
if (HOOK_STATE.check_skip_continue()):
|
||||
return
|
||||
@ -262,6 +293,7 @@ def on_cont(event):
|
||||
state.record_continued()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_stop(event):
|
||||
if hasattr(event, 'breakpoints') and HOOK_STATE.mem_catchpoint in event.breakpoints:
|
||||
HOOK_STATE.skip_continue = True
|
||||
@ -282,6 +314,7 @@ def on_stop(event):
|
||||
HOOK_STATE.end_batch()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_exited(event):
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
@ -318,18 +351,22 @@ def modules_changed():
|
||||
INF_STATES[inf.num].modules = True
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_clear_objfiles(event):
|
||||
modules_changed()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_new_objfile(event):
|
||||
modules_changed()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_free_objfile(event):
|
||||
modules_changed()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_breakpoint_created(b):
|
||||
inf = gdb.selected_inferior()
|
||||
notify_others_breaks(inf)
|
||||
@ -347,6 +384,7 @@ def on_breakpoint_created(b):
|
||||
ibobj.insert()
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_breakpoint_modified(b):
|
||||
if b == HOOK_STATE.mem_catchpoint:
|
||||
return
|
||||
@ -367,10 +405,11 @@ def on_breakpoint_modified(b):
|
||||
# NOTE: Location may not apply to inferior, but whatever.
|
||||
for i in range(new_count, old_count):
|
||||
ikey = commands.INF_BREAK_KEY_PATTERN.format(
|
||||
breaknum=b.number, locnum=i+1)
|
||||
breaknum=b.number, locnum=i + 1)
|
||||
ibobj.set_value(ikey, None)
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_breakpoint_deleted(b):
|
||||
inf = gdb.selected_inferior()
|
||||
notify_others_breaks(inf)
|
||||
@ -388,16 +427,18 @@ def on_breakpoint_deleted(b):
|
||||
trace.proxy_object_path(bpath).remove(tree=True)
|
||||
for i in range(old_count):
|
||||
ikey = commands.INF_BREAK_KEY_PATTERN.format(
|
||||
breaknum=b.number, locnum=i+1)
|
||||
breaknum=b.number, locnum=i + 1)
|
||||
ibobj.set_value(ikey, None)
|
||||
|
||||
|
||||
@log_errors
|
||||
def on_before_prompt():
|
||||
HOOK_STATE.end_batch()
|
||||
|
||||
|
||||
# This will be called by a catchpoint
|
||||
class GhidraTraceEventMemoryCommand(gdb.Command):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('hooks-ghidra event-memory', gdb.COMMAND_NONE)
|
||||
|
||||
@ -410,8 +451,11 @@ GhidraTraceEventMemoryCommand()
|
||||
|
||||
|
||||
def cmd_hook(name):
|
||||
|
||||
def _cmd_hook(func):
|
||||
|
||||
class _ActiveCommand(gdb.Command):
|
||||
|
||||
def __init__(self):
|
||||
# It seems we can't hook commands using the Python API....
|
||||
super().__init__(f"hooks-ghidra def-{name}", gdb.COMMAND_USER)
|
||||
@ -430,9 +474,11 @@ def cmd_hook(name):
|
||||
define {name}
|
||||
end
|
||||
""")
|
||||
|
||||
func.hook = _ActiveCommand
|
||||
func.unhook = _unhook_command
|
||||
return func
|
||||
|
||||
return _cmd_hook
|
||||
|
||||
|
||||
@ -461,7 +507,7 @@ def hook_frame_down():
|
||||
on_frame_selected()
|
||||
|
||||
|
||||
# TODO: Checks and workarounds for events missing in gdb 8
|
||||
# TODO: Checks and workarounds for events missing in gdb 9
|
||||
def install_hooks():
|
||||
if HOOK_STATE.installed:
|
||||
return
|
||||
@ -495,23 +541,26 @@ def install_hooks():
|
||||
HOOK_STATE.mem_catchpoint.enabled = True
|
||||
else:
|
||||
breaks_before = set(gdb.breakpoints())
|
||||
gdb.execute("""
|
||||
catch syscall group:memory
|
||||
commands
|
||||
silent
|
||||
hooks-ghidra event-memory
|
||||
cont
|
||||
end
|
||||
""")
|
||||
HOOK_STATE.mem_catchpoint = (
|
||||
set(gdb.breakpoints()) - breaks_before).pop()
|
||||
try:
|
||||
gdb.execute("""
|
||||
catch syscall group:memory
|
||||
commands
|
||||
silent
|
||||
hooks-ghidra event-memory
|
||||
cont
|
||||
end
|
||||
""")
|
||||
HOOK_STATE.mem_catchpoint = (set(gdb.breakpoints()) - breaks_before).pop()
|
||||
except Exception as e:
|
||||
print(f"Error setting memory catchpoint: {e}")
|
||||
|
||||
gdb.events.cont.connect(on_cont)
|
||||
gdb.events.stop.connect(on_stop)
|
||||
gdb.events.exited.connect(on_exited) # Inferior exited
|
||||
|
||||
gdb.events.clear_objfiles.connect(on_clear_objfiles)
|
||||
gdb.events.free_objfile.connect(on_free_objfile)
|
||||
if hasattr(gdb.events, 'free_objfile'):
|
||||
gdb.events.free_objfile.connect(on_free_objfile)
|
||||
gdb.events.new_objfile.connect(on_new_objfile)
|
||||
|
||||
gdb.events.breakpoint_created.connect(on_breakpoint_created)
|
||||
@ -545,7 +594,8 @@ def remove_hooks():
|
||||
gdb.events.exited.disconnect(on_exited) # Inferior exited
|
||||
|
||||
gdb.events.clear_objfiles.disconnect(on_clear_objfiles)
|
||||
gdb.events.free_objfile.disconnect(on_free_objfile)
|
||||
if hasattr(gdb.events, 'free_objfile'):
|
||||
gdb.events.free_objfile.disconnect(on_free_objfile)
|
||||
gdb.events.new_objfile.disconnect(on_new_objfile)
|
||||
|
||||
gdb.events.breakpoint_created.disconnect(on_breakpoint_created)
|
||||
|
@ -17,28 +17,27 @@ from concurrent.futures import Future, Executor
|
||||
from contextlib import contextmanager
|
||||
import re
|
||||
|
||||
import gdb
|
||||
from ghidratrace import sch
|
||||
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
||||
|
||||
import gdb
|
||||
|
||||
from . import commands, hooks, util
|
||||
|
||||
|
||||
@contextmanager
|
||||
def no_pagination():
|
||||
before = gdb.parameter('pagination')
|
||||
gdb.set_parameter('pagination', False)
|
||||
util.set_bool_param('pagination', False)
|
||||
yield
|
||||
gdb.set_parameter('pagination', before)
|
||||
util.set_bool_param('pagination', before)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def no_confirm():
|
||||
before = gdb.parameter('confirm')
|
||||
gdb.set_parameter('confirm', False)
|
||||
util.set_bool_param('confirm', False)
|
||||
yield
|
||||
gdb.set_parameter('confirm', before)
|
||||
util.set_bool_param('confirm', before)
|
||||
|
||||
|
||||
class GdbExecutor(Executor):
|
||||
@ -75,9 +74,10 @@ THREADS_PATTERN = extre(INFERIOR_PATTERN, '\.Threads')
|
||||
THREAD_PATTERN = extre(THREADS_PATTERN, '\[(?P<tnum>\\d*)\]')
|
||||
STACK_PATTERN = extre(THREAD_PATTERN, '\.Stack')
|
||||
FRAME_PATTERN = extre(STACK_PATTERN, '\[(?P<level>\\d*)\]')
|
||||
REGS_PATTERN = extre(FRAME_PATTERN, '.Registers')
|
||||
REGS_PATTERN = extre(FRAME_PATTERN, '\.Registers')
|
||||
MEMORY_PATTERN = extre(INFERIOR_PATTERN, '\.Memory')
|
||||
MODULES_PATTERN = extre(INFERIOR_PATTERN, '\.Modules')
|
||||
MODULE_PATTERN = extre(MODULES_PATTERN, '\[(?P<modname>.*)\]')
|
||||
|
||||
|
||||
def find_availpid_by_pattern(pattern, object, err_msg):
|
||||
@ -132,6 +132,17 @@ def find_inf_by_modules_obj(object):
|
||||
return find_inf_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")
|
||||
|
||||
|
||||
def find_inf_by_mod_obj(object):
|
||||
return find_inf_by_pattern(object, MODULE_PATTERN, "a Module")
|
||||
|
||||
|
||||
def find_module_name_by_mod_obj(object):
|
||||
mat = MODULE_PATTERN.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not a Module")
|
||||
return mat['modname']
|
||||
|
||||
|
||||
def find_thread_by_num(inf, tnum):
|
||||
for t in inf.threads():
|
||||
if t.num == tnum:
|
||||
@ -163,7 +174,7 @@ def find_frame_by_level(thread, level):
|
||||
f = gdb.selected_frame()
|
||||
|
||||
# Navigate up or down, because I can't just get by level
|
||||
down = level - f.level()
|
||||
down = level - util.get_level(f)
|
||||
while down > 0:
|
||||
f = f.older()
|
||||
if f is None:
|
||||
@ -176,7 +187,6 @@ def find_frame_by_level(thread, level):
|
||||
raise KeyError(
|
||||
f"Inferiors[{thread.inferior.num}].Threads[{thread.num}].Stack[{level}] does not exist")
|
||||
down += 1
|
||||
assert f.level() == level
|
||||
return f
|
||||
|
||||
|
||||
@ -203,7 +213,7 @@ def find_frame_by_regs_obj(object):
|
||||
|
||||
# Because there's no method to get a register by name....
|
||||
def find_reg_by_name(f, name):
|
||||
for reg in f.architecture().registers():
|
||||
for reg in util.get_register_descs(f.architecture()):
|
||||
# TODO: gdb appears to be case sensitive, but until we encounter a
|
||||
# situation where case matters, we'll be insensitive
|
||||
if reg.name.lower() == name.lower():
|
||||
@ -257,7 +267,7 @@ def find_bpt_loc_by_obj(object):
|
||||
def switch_inferior(inferior):
|
||||
if gdb.selected_inferior().num == inferior.num:
|
||||
return
|
||||
gdb.execute("inferior {}".format(inferior.num))
|
||||
gdb.execute(f'inferior {inferior.num}')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@ -266,14 +276,14 @@ def execute(cmd: str, to_string: bool=False):
|
||||
return gdb.execute(cmd, to_string=to_string)
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Available')
|
||||
def refresh_available(node: sch.Schema('AvailableContainer')):
|
||||
"""List processes on gdb's host system."""
|
||||
with commands.open_tracked_tx('Refresh Available'):
|
||||
gdb.execute('ghidra trace put-available')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Breakpoints')
|
||||
def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
|
||||
"""
|
||||
Refresh the list of breakpoints (including locations for the current
|
||||
@ -283,14 +293,14 @@ def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
|
||||
gdb.execute('ghidra trace put-breakpoints')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Inferiors')
|
||||
def refresh_inferiors(node: sch.Schema('InferiorContainer')):
|
||||
"""Refresh the list of inferiors."""
|
||||
with commands.open_tracked_tx('Refresh Inferiors'):
|
||||
gdb.execute('ghidra trace put-inferiors')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Breakpoint Locations')
|
||||
def refresh_inf_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
||||
"""
|
||||
Refresh the breakpoint locations for the inferior.
|
||||
@ -303,7 +313,7 @@ def refresh_inf_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
||||
gdb.execute('ghidra trace put-breakpoints')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Environment')
|
||||
def refresh_environment(node: sch.Schema('Environment')):
|
||||
"""Refresh the environment descriptors (arch, os, endian)."""
|
||||
switch_inferior(find_inf_by_env_obj(node))
|
||||
@ -311,7 +321,7 @@ def refresh_environment(node: sch.Schema('Environment')):
|
||||
gdb.execute('ghidra trace put-environment')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Threads')
|
||||
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
||||
"""Refresh the list of threads in the inferior."""
|
||||
switch_inferior(find_inf_by_threads_obj(node))
|
||||
@ -319,7 +329,7 @@ def refresh_threads(node: sch.Schema('ThreadContainer')):
|
||||
gdb.execute('ghidra trace put-threads')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Stack')
|
||||
def refresh_stack(node: sch.Schema('Stack')):
|
||||
"""Refresh the backtrace for the thread."""
|
||||
find_thread_by_stack_obj(node).switch()
|
||||
@ -327,7 +337,7 @@ def refresh_stack(node: sch.Schema('Stack')):
|
||||
gdb.execute('ghidra trace put-frames')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Registers')
|
||||
def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
||||
"""Refresh the register values for the frame."""
|
||||
find_frame_by_regs_obj(node).select()
|
||||
@ -336,7 +346,7 @@ def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
||||
gdb.execute('ghidra trace putreg')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Memory')
|
||||
def refresh_mappings(node: sch.Schema('Memory')):
|
||||
"""Refresh the list of memory regions for the inferior."""
|
||||
switch_inferior(find_inf_by_mem_obj(node))
|
||||
@ -344,18 +354,38 @@ def refresh_mappings(node: sch.Schema('Memory')):
|
||||
gdb.execute('ghidra trace put-regions')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Modules")
|
||||
def refresh_modules(node: sch.Schema('ModuleContainer')):
|
||||
"""
|
||||
Refresh the modules and sections list for the inferior.
|
||||
|
||||
This will refresh the sections for all modules, not just the selected one.
|
||||
Refresh the modules list for the inferior.
|
||||
"""
|
||||
switch_inferior(find_inf_by_modules_obj(node))
|
||||
with commands.open_tracked_tx('Refresh Modules'):
|
||||
gdb.execute('ghidra trace put-modules')
|
||||
|
||||
|
||||
# node is Module so this appears in Modules panel
|
||||
@REGISTRY.method(display='Refresh all Modules and all Sections')
|
||||
def load_all_sections(node: sch.Schema('Module')):
|
||||
"""
|
||||
Load/refresh all modules and all sections.
|
||||
"""
|
||||
switch_inferior(find_inf_by_mod_obj(node))
|
||||
with commands.open_tracked_tx('Refresh all Modules and all Sections'):
|
||||
gdb.execute('ghidra trace put-sections -all-objects')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh', display="Refresh Module and Sections")
|
||||
def refresh_sections(node: sch.Schema('Module')):
|
||||
"""
|
||||
Load/refresh the module and its sections.
|
||||
"""
|
||||
switch_inferior(find_inf_by_mod_obj(node))
|
||||
with commands.open_tracked_tx('Refresh Module and Sections'):
|
||||
modname = find_module_name_by_mod_obj(node)
|
||||
gdb.execute(f'ghidra trace put-sections {modname}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_inferior(inferior: sch.Schema('Inferior')):
|
||||
"""Switch to the inferior."""
|
||||
@ -374,7 +404,7 @@ def activate_frame(frame: sch.Schema('StackFrame')):
|
||||
find_frame_by_obj(frame).select()
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@REGISTRY.method(display='Add Inferior')
|
||||
def add_inferior(container: sch.Schema('InferiorContainer')):
|
||||
"""Add a new inferior."""
|
||||
gdb.execute('add-inferior')
|
||||
@ -388,36 +418,36 @@ def delete_inferior(inferior: sch.Schema('Inferior')):
|
||||
|
||||
|
||||
# TODO: Separate method for each of core, exec, remote, etc...?
|
||||
@REGISTRY.method
|
||||
@REGISTRY.method(display='Connect Target')
|
||||
def connect(inferior: sch.Schema('Inferior'), spec: str):
|
||||
"""Connect to a target machine or process."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute(f'target {spec}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
def attach_obj(inferior: sch.Schema('Inferior'), target: sch.Schema('Attachable')):
|
||||
@REGISTRY.method(action='attach', display='Attach')
|
||||
def attach_obj(target: sch.Schema('Attachable')):
|
||||
"""Attach the inferior to the given target."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
#switch_inferior(find_inf_by_obj(inferior))
|
||||
pid = find_availpid_by_obj(target)
|
||||
gdb.execute(f'attach {pid}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
@REGISTRY.method(action='attach', display='Attach by PID')
|
||||
def attach_pid(inferior: sch.Schema('Inferior'), pid: int):
|
||||
"""Attach the inferior to the given target."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute(f'attach {pid}')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@REGISTRY.method(display='Detach')
|
||||
def detach(inferior: sch.Schema('Inferior')):
|
||||
"""Detach the inferior's target."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute('detach')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
@REGISTRY.method(action='launch', display='Launch at main')
|
||||
def launch_main(inferior: sch.Schema('Inferior'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
@ -435,7 +465,8 @@ def launch_main(inferior: sch.Schema('Inferior'),
|
||||
''')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch', condition=util.GDB_VERSION.major >= 9)
|
||||
@REGISTRY.method(action='launch', display='Launch at Loader',
|
||||
condition=util.GDB_VERSION.major >= 9)
|
||||
def launch_loader(inferior: sch.Schema('Inferior'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
@ -451,7 +482,7 @@ def launch_loader(inferior: sch.Schema('Inferior'),
|
||||
''')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
@REGISTRY.method(action='launch', display='Launch and Run')
|
||||
def launch_run(inferior: sch.Schema('Inferior'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
@ -514,7 +545,7 @@ def step_out(thread: sch.Schema('Thread')):
|
||||
gdb.execute('finish')
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_ext', name='Advance')
|
||||
@REGISTRY.method(action='step_ext', display='Advance')
|
||||
def step_advance(thread: sch.Schema('Thread'), address: Address):
|
||||
"""Continue execution up to the given address (advance)."""
|
||||
t = find_thread_by_obj(thread)
|
||||
@ -523,7 +554,7 @@ def step_advance(thread: sch.Schema('Thread'), address: Address):
|
||||
gdb.execute(f'advance *0x{offset:x}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_ext', name='Return')
|
||||
@REGISTRY.method(action='step_ext', display='Return')
|
||||
def step_return(thread: sch.Schema('Thread'), value: int=None):
|
||||
"""Skip the remainder of the current function (return)."""
|
||||
find_thread_by_obj(thread).switch()
|
||||
@ -611,8 +642,8 @@ def break_access_expression(expression: str):
|
||||
gdb.execute(f'awatch {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_ext')
|
||||
def break_event(spec: str):
|
||||
@REGISTRY.method(action='break_ext', display='Catch Event')
|
||||
def break_event(inferior: sch.Schema('Inferior'), spec: str):
|
||||
"""Set a catchpoint (catch)."""
|
||||
gdb.execute(f'catch {spec}')
|
||||
|
||||
@ -653,7 +684,12 @@ def read_mem(inferior: sch.Schema('Inferior'), range: AddressRange):
|
||||
offset_start = inferior.trace.memory_mapper.map_back(
|
||||
inf, Address(range.space, range.min))
|
||||
with commands.open_tracked_tx('Read Memory'):
|
||||
gdb.execute(f'ghidra trace putmem 0x{offset_start:x} {range.length()}')
|
||||
try:
|
||||
gdb.execute(
|
||||
f'ghidra trace putmem 0x{offset_start:x} {range.length()}')
|
||||
except:
|
||||
gdb.execute(
|
||||
f'ghidra trace putmem-state 0x{offset_start:x} {range.length()} error')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
|
@ -1,5 +1,5 @@
|
||||
<context>
|
||||
<schema name="Session" elementResync="NEVER" attributeResync="NEVER">
|
||||
<schema name="GdbSession" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Access" />
|
||||
<interface name="Attacher" />
|
||||
<interface name="Interpreter" />
|
||||
@ -13,69 +13,33 @@
|
||||
<attribute name="Inferiors" schema="InferiorContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Available" schema="AvailableContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_accessible" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_attach_kinds" schema="SET_ATTACH_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="_prompt" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
|
||||
<attribute name="_event_thread" schema="OBJECT" hidden="yes" />
|
||||
<attribute name="_focus" schema="Selectable" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Selectable" elementResync="NEVER" attributeResync="NEVER">
|
||||
<element schema="OBJECT" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointSpecContainer" />
|
||||
<element schema="BreakpointSpec" />
|
||||
<attribute name="_supported_breakpoint_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="AvailableContainer" canonical="yes" elementResync="ALWAYS" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Attachable" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_base" schema="INT" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="InferiorContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Inferior" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_base" schema="INT" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointSpec" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
@ -83,17 +47,14 @@
|
||||
<interface name="Deletable" />
|
||||
<interface name="Togglable" />
|
||||
<element schema="BreakpointLocation" />
|
||||
<attribute name="_container" schema="BreakpointContainer" required="yes" hidden="yes" />
|
||||
<attribute name="_expression" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute name="_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Expression" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_expression" to="Expression" />
|
||||
<attribute name="Kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_kinds" to="Kinds" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="Enabled" schema="BOOL" />
|
||||
<attribute-alias from="_enabled" to="Enabled" />
|
||||
<attribute name="Commands" schema="STRING" />
|
||||
<attribute name="Condition" schema="STRING" />
|
||||
<attribute name="Hit Count" schema="INT" />
|
||||
@ -106,17 +67,14 @@
|
||||
<schema name="Attachable" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Attachable" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_pid" schema="LONG" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="PID" schema="LONG" />
|
||||
<attribute-alias from="_pid" to="PID" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Inferior" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="Process" />
|
||||
<interface name="Aggregate" />
|
||||
<interface name="ExecutionStateful" />
|
||||
@ -131,105 +89,68 @@
|
||||
<element schema="VOID" />
|
||||
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Breakpoints" schema="BreakpointLocationContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_exit_code" schema="LONG" />
|
||||
<attribute name="Exit Code" schema="LONG" />
|
||||
<attribute-alias from="_exit_code" to="Exit Code" />
|
||||
<attribute name="Environment" schema="Environment" required="yes" fixed="yes" />
|
||||
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
|
||||
<attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_pid" schema="LONG" hidden="yes" />
|
||||
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_attach_kinds" schema="SET_ATTACH_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="PID" schema="LONG" hidden="yes" />
|
||||
<attribute-alias from="_pid" to="PID" />
|
||||
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_state" to="State" />
|
||||
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Environment" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Environment" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="arch" schema="STRING" />
|
||||
<attribute name="os" schema="STRING" />
|
||||
<attribute name="endian" schema="STRING" />
|
||||
<attribute name="_arch" schema="STRING" hidden="yes" />
|
||||
<attribute name="_debugger" schema="STRING" hidden="yes" />
|
||||
<attribute name="_os" schema="STRING" hidden="yes" />
|
||||
<attribute name="_endian" schema="STRING" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="OS" schema="STRING" />
|
||||
<attribute name="Arch" schema="STRING" />
|
||||
<attribute name="Endian" schema="STRING" />
|
||||
<attribute name="Debugger" schema="STRING" />
|
||||
<attribute-alias from="_os" to="OS" />
|
||||
<attribute-alias from="_arch" to="Arch" />
|
||||
<attribute-alias from="_endian" to="Endian" />
|
||||
<attribute-alias from="_debugger" to="Debugger" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="ModuleContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="ModuleContainer" />
|
||||
<element schema="Module" />
|
||||
<attribute name="_supports_synthetic_modules" schema="BOOL" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Memory" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Memory" />
|
||||
<element schema="MemoryRegion" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointLocation" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointLocation" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_range" schema="RANGE" hidden="yes" />
|
||||
<attribute name="_spec" schema="BreakpointSpec" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Enabled" schema="BOOL" />
|
||||
<attribute-alias from="_enabled" to="Enabled" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointLocationContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointLocationContainer" />
|
||||
<element schema="BreakpointLocation" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="ThreadContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Thread" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_base" schema="INT" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Method" elementResync="NEVER" attributeResync="NEVER">
|
||||
@ -241,23 +162,20 @@
|
||||
<attribute schema="VOID" fixed="yes" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="Thread" />
|
||||
<interface name="ExecutionStateful" />
|
||||
<interface name="Steppable" />
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Stack" schema="Stack" required="yes" fixed="yes" />
|
||||
<attribute name="_tid" schema="LONG" hidden="yes" />
|
||||
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="TID" schema="LONG" />
|
||||
<attribute-alias from="_tid" to="TID" />
|
||||
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_state" to="State" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="Advance" schema="Method" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Module" elementResync="NEVER" attributeResync="NEVER">
|
||||
@ -265,149 +183,84 @@
|
||||
<element schema="VOID" />
|
||||
<attribute name="Sections" schema="SectionContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Symbols" schema="SymbolContainer" required="yes" fixed="yes" />
|
||||
<attribute name="range" schema="RANGE" />
|
||||
<attribute name="module name" schema="STRING" />
|
||||
<attribute name="_module_name" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute name="Name" schema="STRING" />
|
||||
<attribute-alias from="_module_name" to="Name" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="MemoryRegion" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="MemoryRegion" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_offset" schema="LONG" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_objfile" schema="STRING" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="Offset" schema="STRING" fixed="yes" />
|
||||
<attribute-alias from="_offset" to="Offset" />
|
||||
<attribute name="Object File" schema="STRING" fixed="yes" />
|
||||
<attribute-alias from="_objfile" to="Object File" />
|
||||
<attribute name="_readable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_writable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_executable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
|
||||
<attribute name="_memory" schema="Memory" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="SectionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="SectionContainer" />
|
||||
<element schema="Section" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Stack" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Stack" />
|
||||
<element schema="StackFrame" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="SymbolContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="SymbolNamespace" />
|
||||
<element schema="Symbol" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Symbol" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Symbol" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_size" schema="LONG" fixed="yes" hidden="yes" />
|
||||
<attribute name="_namespace" schema="SymbolContainer" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_data_type" schema="DATA_TYPE" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ADDRESS" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_bpt" schema="STRING" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="StackFrame" />
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_function" schema="STRING" hidden="yes" />
|
||||
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_pc" schema="ADDRESS" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="PC" schema="ADDRESS" required="yes" />
|
||||
<attribute-alias from="_pc" to="PC" />
|
||||
<attribute name="Function" schema="STRING" hidden="yes" />
|
||||
<attribute-alias from="_function" to="Function" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Section" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Section" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="range" schema="RANGE" />
|
||||
<attribute name="_module" schema="Module" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" required="yes" fixed="yes" />
|
||||
<attribute name="_offset" schema="INT" required="no" fixed="yes" />
|
||||
<attribute name="_objfile" schema="STRING" required="no" fixed="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute name="Module" schema="Module" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="Offset" schema="STRING" fixed="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterValueContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<schema name="RegisterValueContainer" attributeResync="NEVER">
|
||||
<interface name="RegisterContainer" />
|
||||
<interface name="RegisterBank" />
|
||||
<element schema="RegisterValue" />
|
||||
<attribute name="_descriptions" schema="RegisterValueContainer" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterValue" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Register" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_container" schema="OBJECT" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_length" schema="INT" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
</context>
|
@ -57,6 +57,7 @@ GNU_DEBUGDATA_PREFIX = ".gnu_debugdata for "
|
||||
class Module(namedtuple('BaseModule', ['name', 'base', 'max', 'sections'])):
|
||||
pass
|
||||
|
||||
|
||||
class Index:
|
||||
def __init__(self, regions):
|
||||
self.regions = {}
|
||||
@ -64,6 +65,7 @@ class Index:
|
||||
for r in regions:
|
||||
self.regions[r.start] = r
|
||||
self.bases.append(r.start)
|
||||
|
||||
def compute_base(self, address):
|
||||
index = bisect.bisect_right(self.bases, address) - 1
|
||||
if index == -1:
|
||||
@ -78,6 +80,7 @@ class Index:
|
||||
else:
|
||||
return region.start
|
||||
|
||||
|
||||
class Section(namedtuple('BaseSection', ['name', 'start', 'end', 'offset', 'attrs'])):
|
||||
def better(self, other):
|
||||
start = self.start if self.start != 0 else other.start
|
||||
@ -103,8 +106,6 @@ class ModuleInfoReader(object):
|
||||
if mat is None:
|
||||
return None
|
||||
n = mat['name']
|
||||
if n.startswith(GNU_DEBUGDATA_PREFIX):
|
||||
return None
|
||||
return None if mat is None else mat['name']
|
||||
|
||||
def section_from_line(self, line):
|
||||
@ -135,7 +136,7 @@ class ModuleInfoReader(object):
|
||||
for line in out.split('\n'):
|
||||
n = self.name_from_line(line)
|
||||
if n is not None:
|
||||
if name is not None:
|
||||
if name is not None and not name.startswith(GNU_DEBUGDATA_PREFIX):
|
||||
modules[name] = self.finish_module(name, sections, index)
|
||||
name = n
|
||||
sections = {}
|
||||
@ -148,7 +149,7 @@ class ModuleInfoReader(object):
|
||||
if s.name in sections:
|
||||
s = s.better(sections[s.name])
|
||||
sections[s.name] = s
|
||||
if name is not None:
|
||||
if name is not None and not name.startswith(GNU_DEBUGDATA_PREFIX):
|
||||
modules[name] = self.finish_module(name, sections, index)
|
||||
return modules
|
||||
|
||||
@ -289,8 +290,37 @@ class BreakpointLocationInfoReaderV8(object):
|
||||
pass
|
||||
|
||||
def get_locations(self, breakpoint):
|
||||
inf = gdb.selected_inferior()
|
||||
thread_groups = [inf.num]
|
||||
if breakpoint.location is not None and breakpoint.location.startswith("*0x"):
|
||||
address = int(breakpoint.location[1:], 16)
|
||||
loc = BreakpointLocation(
|
||||
address, breakpoint.enabled, thread_groups)
|
||||
return [loc]
|
||||
return []
|
||||
|
||||
|
||||
class BreakpointLocationInfoReaderV9(object):
|
||||
def breakpoint_from_line(self, line):
|
||||
pass
|
||||
|
||||
def location_from_line(self, line):
|
||||
pass
|
||||
|
||||
def get_locations(self, breakpoint):
|
||||
inf = gdb.selected_inferior()
|
||||
thread_groups = [inf.num]
|
||||
if breakpoint.location is None:
|
||||
return []
|
||||
try:
|
||||
address = gdb.parse_and_eval(breakpoint.location).address
|
||||
loc = BreakpointLocation(
|
||||
address, breakpoint.enabled, thread_groups)
|
||||
return [loc]
|
||||
except Exception as e:
|
||||
print(f"Error parsing bpt location = {breakpoint.location}")
|
||||
return []
|
||||
|
||||
|
||||
class BreakpointLocationInfoReaderV13(object):
|
||||
def get_locations(self, breakpoint):
|
||||
@ -298,13 +328,64 @@ class BreakpointLocationInfoReaderV13(object):
|
||||
|
||||
|
||||
def _choose_breakpoint_location_info_reader():
|
||||
if 8 <= GDB_VERSION.major < 13:
|
||||
return BreakpointLocationInfoReaderV8()
|
||||
elif GDB_VERSION.major >= 13:
|
||||
if GDB_VERSION.major >= 13:
|
||||
return BreakpointLocationInfoReaderV13()
|
||||
if GDB_VERSION.major >= 9:
|
||||
return BreakpointLocationInfoReaderV9()
|
||||
if GDB_VERSION.major >= 8:
|
||||
return BreakpointLocationInfoReaderV8()
|
||||
else:
|
||||
raise gdb.GdbError(
|
||||
"GDB version not recognized by ghidragdb: " + GDB_VERSION.full)
|
||||
|
||||
|
||||
BREAKPOINT_LOCATION_INFO_READER = _choose_breakpoint_location_info_reader()
|
||||
|
||||
|
||||
def set_bool_param_by_api(name, value):
|
||||
gdb.set_parameter(name, value)
|
||||
|
||||
|
||||
def set_bool_param_by_cmd(name, value):
|
||||
val = 'on' if value else 'off'
|
||||
gdb.execute(f'set {name} {val}')
|
||||
|
||||
|
||||
def choose_set_parameter():
|
||||
if GDB_VERSION.major >= 13:
|
||||
return set_bool_param_by_api
|
||||
else:
|
||||
return set_bool_param_by_cmd
|
||||
|
||||
|
||||
set_bool_param = choose_set_parameter()
|
||||
|
||||
|
||||
def get_level(frame):
|
||||
if hasattr(frame, "level"):
|
||||
return frame.level()
|
||||
else:
|
||||
level = -1
|
||||
f = frame
|
||||
while f is not None:
|
||||
level += 1
|
||||
f = f.newer()
|
||||
return level
|
||||
|
||||
|
||||
class RegisterDesc(namedtuple('BaseRegisterDesc', ['name'])):
|
||||
pass
|
||||
|
||||
|
||||
def get_register_descs(arch, group='all'):
|
||||
if hasattr(arch, "registers"):
|
||||
return arch.registers(group)
|
||||
else:
|
||||
descs = []
|
||||
regset = gdb.execute(
|
||||
f"info registers {group}", to_string=True).strip().split('\n')
|
||||
for line in regset:
|
||||
if not line.startswith(" "):
|
||||
tokens = line.strip().split()
|
||||
descs.append(RegisterDesc(tokens[0]))
|
||||
return descs
|
||||
|
@ -0,0 +1,91 @@
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
import gdb
|
||||
|
||||
from . import util
|
||||
from .commands import install, cmd
|
||||
|
||||
|
||||
@install
|
||||
class GhidraWinePrefix(gdb.Command):
|
||||
"""Commands for tracing Wine processes"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('ghidra wine', gdb.COMMAND_SUPPORT, prefix=True)
|
||||
|
||||
|
||||
def is_mapped(pe_file):
|
||||
return pe_file in gdb.execute("info proc mappings", to_string=True)
|
||||
|
||||
|
||||
def set_break(command):
|
||||
breaks_before = set(gdb.breakpoints())
|
||||
gdb.execute(command)
|
||||
return (set(gdb.breakpoints()) - breaks_before).pop()
|
||||
|
||||
|
||||
@cmd('ghidra wine run-to-image', '-ghidra-wine-run-to-image', gdb.COMMAND_SUPPORT, False)
|
||||
def ghidra_wine_run_to_image(pe_file, *, is_mi, **kwargs):
|
||||
mprot_catchpoint = set_break("""
|
||||
catch syscall mprotect
|
||||
commands
|
||||
silent
|
||||
end
|
||||
""".strip())
|
||||
while not is_mapped(pe_file):
|
||||
gdb.execute("continue")
|
||||
mprot_catchpoint.delete()
|
||||
|
||||
|
||||
ORIG_MODULE_INFO_READER = util.MODULE_INFO_READER
|
||||
|
||||
|
||||
class Range(object):
|
||||
|
||||
def expand(self, region):
|
||||
if not hasattr(self, 'min'):
|
||||
self.min = region.start
|
||||
self.max = region.end
|
||||
else:
|
||||
self.min = min(self.min, region.start)
|
||||
self.max = max(self.max, region.end)
|
||||
return self
|
||||
|
||||
|
||||
# There are more, but user can monkey patch this
|
||||
MODULE_SUFFIXES = (".exe", ".dll")
|
||||
|
||||
|
||||
class WineModuleInfoReader(object):
|
||||
|
||||
def get_modules(self):
|
||||
modules = ORIG_MODULE_INFO_READER.get_modules()
|
||||
ranges = dict()
|
||||
for region in util.REGION_INFO_READER.get_regions():
|
||||
if not region.objfile in ranges:
|
||||
ranges[region.objfile] = Range().expand(region)
|
||||
else:
|
||||
ranges[region.objfile].expand(region)
|
||||
for k, v in ranges.items():
|
||||
if k in modules:
|
||||
continue
|
||||
if not k.lower().endswith(MODULE_SUFFIXES):
|
||||
continue
|
||||
modules[k] = util.Module(k, v.min, v.max, {})
|
||||
return modules
|
||||
|
||||
|
||||
util.MODULE_INFO_READER = WineModuleInfoReader()
|
@ -23,7 +23,8 @@ import org.junit.Ignore;
|
||||
|
||||
import agent.gdb.manager.GdbManager;
|
||||
import ghidra.pty.PtySession;
|
||||
import ghidra.pty.linux.LinuxPty;
|
||||
import ghidra.pty.linux.LinuxIoctls;
|
||||
import ghidra.pty.unix.UnixPty;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@Ignore("Need compatible GDB version for CI")
|
||||
@ -45,13 +46,13 @@ public class JoinedGdbManagerTest extends AbstractGdbManagerTest {
|
||||
}
|
||||
}
|
||||
|
||||
protected LinuxPty ptyUserGdb;
|
||||
protected UnixPty ptyUserGdb;
|
||||
protected PtySession gdb;
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<Void> startManager(GdbManager manager) {
|
||||
try {
|
||||
ptyUserGdb = LinuxPty.openpty();
|
||||
ptyUserGdb = UnixPty.openpty(LinuxIoctls.INSTANCE);
|
||||
manager.start(null);
|
||||
Msg.debug(this, "Starting GDB and invoking new-ui mi2 " + manager.getMi2PtyName());
|
||||
|
||||
|
76
Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/local-lldb.sh
Executable file
76
Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/local-lldb.sh
Executable file
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
#@title lldb
|
||||
#@desc <html><body width="300px">
|
||||
#@desc <h3>Launch with <tt>lldb</tt></h3>
|
||||
#@desc <p>
|
||||
#@desc This will launch the target on the local machine using <tt>lldb</tt>.
|
||||
#@desc For setup instructions, press <b>F1</b>.
|
||||
#@desc </p>
|
||||
#@desc </body></html>
|
||||
#@menu-group local
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#lldb
|
||||
#@enum StartCmd:str "process launch" "process launch --stop-at-entry"
|
||||
#@arg :file "Image" "The target binary executable image"
|
||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||
#@env OPT_LLDB_PATH:file="lldb" "lldb command" "The path to lldb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_START_CMD:StartCmd="process launch" "Run command" "The lldb command to actually run the target."
|
||||
#@env OPT_EXTRA_TTY:bool=false "Target TTY" "Provide a separate terminal emulator for the target."
|
||||
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-agent-lldb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
elif [ -d ${GHIDRA_HOME}/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-lldb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
else
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-lldb/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/pypkg/src:$PYTHONPATH
|
||||
fi
|
||||
|
||||
target_image="$1"
|
||||
shift
|
||||
target_args="$@"
|
||||
|
||||
if [ -z "$target_args" ]
|
||||
then
|
||||
argspart=
|
||||
else
|
||||
argspart=-o "settings set target.run-args $target_args"
|
||||
fi
|
||||
|
||||
if [ -z "$TARGET_TTY" ]
|
||||
then
|
||||
ttypart=
|
||||
else
|
||||
ttypart=-o "settings set target.output-path $TTY_TARGET" -o "settings set target.input-path $TTY_TARGET"
|
||||
fi
|
||||
|
||||
"$OPT_LLDB_PATH" \
|
||||
-o "version" \
|
||||
-o "script import ghidralldb" \
|
||||
-o "target create \"$target_image\"" \
|
||||
$argspart \
|
||||
$ttypart \
|
||||
-o "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
|
||||
-o "ghidra trace start" \
|
||||
-o "ghidra trace sync-enable" \
|
||||
-o "$OPT_START_CMD"
|
63
Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/remote-lldb.sh
Executable file
63
Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/remote-lldb.sh
Executable file
@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
#@title remote lldb
|
||||
#@no-image
|
||||
#@desc <html><body width="300px">
|
||||
#@desc <h3>Launch with local <tt>lldb</tt> and connect to a stub (e.g., <tt>gdbserver</tt>)</h3>
|
||||
#@desc <p>
|
||||
#@desc This will start <tt>lldb</tt> on the local system and then use it to connect to the remote system.
|
||||
#@desc For setup instructions, press <b>F1</b>.
|
||||
#@desc </p>
|
||||
#@desc </body></html>
|
||||
#@menu-group remote
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#lldb_remote
|
||||
#@env OPT_HOST:str="localhost" "Host" "The hostname of the target"
|
||||
#@env OPT_PORT:str="9999" "Port" "The host's listening port"
|
||||
#@env OPT_ARCH:str="" "Architecture" "Target architecture override"
|
||||
#@env OPT_LLDB_PATH:file="lldb" "lldb command" "The path to lldb on the local system. Omit the full path to resolve using the system PATH."
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-agent-lldb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
elif [ -d ${GHIDRA_HOME}/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-lldb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
else
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-lldb/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/pypkg/src:$PYTHONPATH
|
||||
fi
|
||||
|
||||
if [ -z "$OPT_ARCH" ]
|
||||
then
|
||||
archcmd=
|
||||
else
|
||||
archcmd=-o "settings set target.default-arch $OPT_ARCH"
|
||||
fi
|
||||
|
||||
"$OPT_LLDB_PATH" \
|
||||
-o "version" \
|
||||
-o "script import ghidralldb" \
|
||||
$archcmd \
|
||||
-o "gdb-remote $OPT_HOST:$OPT_PORT" \
|
||||
-o "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
|
||||
-o "ghidra trace start" \
|
||||
-o "ghidra trace sync-enable" \
|
||||
-o "ghidra trace sync-synth-stopped"
|
||||
|
@ -1,10 +1,10 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "ghidralldb"
|
||||
version = "10.4"
|
||||
version = "11.1"
|
||||
authors = [
|
||||
{ name="Ghidra Development Team" },
|
||||
]
|
||||
@ -17,7 +17,7 @@ classifiers = [
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
dependencies = [
|
||||
"ghidratrace==10.4",
|
||||
"ghidratrace==11.1",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
@ -14,58 +14,151 @@
|
||||
# limitations under the License.
|
||||
##
|
||||
from ghidratrace.client import Address, RegVal
|
||||
|
||||
import lldb
|
||||
|
||||
from . import util
|
||||
|
||||
|
||||
# NOTE: This map is derived from the ldefs using a script
|
||||
language_map = {
|
||||
'aarch64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
|
||||
'arm': ['ARM:BE:32:v8', 'ARM:BE:32:v8T', 'ARM:LE:32:v8', 'ARM:LE:32:v8T'],
|
||||
'armv4': ['ARM:BE:32:v4', 'ARM:LE:32:v4'],
|
||||
'armv4t': ['ARM:BE:32:v4t', 'ARM:LE:32:v4t'],
|
||||
'armv5': ['ARM:BE:32:v5', 'ARM:LE:32:v5'],
|
||||
'armv5e': ['ARM:BE:32:v5t', 'ARM:LE:32:v5t'],
|
||||
'armv5t': ['ARM:BE:32:v5t', 'ARM:LE:32:v5t'],
|
||||
'armv6': ['ARM:BE:32:v6', 'ARM:LE:32:v6'],
|
||||
'armv6m': ['ARM:BE:32:Cortex', 'ARM:LE:32:Cortex'],
|
||||
'armv7': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'armv7k': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'armv7l': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'armv7f': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'armv7s': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'arm64': ['ARM:BE:64:v8', 'ARM:LE:64:v8'],
|
||||
'arm64_32': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
|
||||
'arm64e': ['ARM:BE:64:v8', 'ARM:LE:64:v8'],
|
||||
'i386': ['x86:LE:32:default'],
|
||||
'armv7k': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'armv7m': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'armv7em': ['ARM:BE:32:Cortex', 'ARM:LE:32:Cortex'],
|
||||
'xscale': ['ARM:BE:32:v6', 'ARM:LE:32:v6'],
|
||||
'thumbv5': ['ARM:BE:32:v5', 'ARM:LE:32:v5'],
|
||||
'thumbv5e': ['ARM:BE:32:v5', 'ARM:LE:32:v5'],
|
||||
'thumbv6': ['ARM:BE:32:v6', 'ARM:LE:32:v6'],
|
||||
'thumbv6m': ['ARM:BE:32:Cortex', 'ARM:LE:32:Cortex'],
|
||||
'thumbv7': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'thumbv7k': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'thumbv7f': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'thumbv7s': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'thumbv7k': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'thumbv7m': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
|
||||
'thumbv7em': ['ARM:BE:32:Cortex', 'ARM:LE:32:Cortex'],
|
||||
'armv8': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
|
||||
'armv8l': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
|
||||
'arm64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
|
||||
'arm64e': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
|
||||
'arm64_32': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
|
||||
'mips': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
|
||||
'mipsr2': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
|
||||
'mipsr3': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
|
||||
'mipsr5': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
|
||||
'mipsr6': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
|
||||
'mipsel': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
|
||||
'mipsr2el': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
|
||||
'mipsr3el': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
|
||||
'mipsr5el': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
|
||||
'mipsr6el': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
|
||||
'mips64': ['MIPS:BE:3264:default', 'MIPS:LE:64:default'],
|
||||
'mips64r2': ['MIPS:BE:64:default', 'MIPS:LE:64:default'],
|
||||
'mips64r3': ['MIPS:BE:64:default', 'MIPS:LE:64:default'],
|
||||
'mips64r5': ['MIPS:BE:64:default', 'MIPS:LE:64:default'],
|
||||
'mips64r6': ['MIPS:BE:64:default', 'MIPS:LE:64:default'],
|
||||
'mips64el': ['MIPS:BE:64:default', 'MIPS:LE:64:default'],
|
||||
'mips64r2el': ['MIPS:BE:64:default', 'MIPS:LE:64:default'],
|
||||
'mips64r3el': ['MIPS:BE:64:default', 'MIPS:LE:64:default'],
|
||||
'mips64r5el': ['MIPS:BE:64:default', 'MIPS:LE:64:default'],
|
||||
'mips64r6el': ['MIPS:BE:64:default', 'MIPS:LE:64:default'],
|
||||
'msp:430X': ['TI_MSP430:LE:16:default'],
|
||||
'powerpc': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'ppc601': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'ppc602': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'ppc603': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'ppc603e': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'ppc603ev': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'ppc604': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'ppc604e': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'ppc620': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'ppc750': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'ppc7400': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'ppc7450': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'ppc970': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
|
||||
'powerpc64': ['PowerPC:BE:64:4xx', 'PowerPC:LE:64:4xx'],
|
||||
'powerpc64le': ['PowerPC:BE:64:4xx', 'PowerPC:LE:64:4xx'],
|
||||
'ppc970-64': ['PowerPC:BE:64:4xx', 'PowerPC:LE:64:4xx'],
|
||||
's390x': [],
|
||||
'sparc': ['sparc:BE:32:default', 'sparc:BE:64:default'],
|
||||
'sparcv9': ['sparc:BE:32:default', 'sparc:BE:64:default'],
|
||||
'i386': ['x86:LE:32:default'],
|
||||
'i486': ['x86:LE:32:default'],
|
||||
'i486sx': ['x86:LE:32:default'],
|
||||
'i686': ['x86:LE:64:default'],
|
||||
'x86_64': ['x86:LE:64:default'],
|
||||
'wasm32': ['x86:LE:64:default'],
|
||||
'x86_64h': ['x86:LE:64:default'],
|
||||
'hexagon': [],
|
||||
'hexagonv4': [],
|
||||
'hexagonv5': [],
|
||||
'riscv32': ['RISCV:LE:32:RV32G', 'RISCV:LE:32:RV32GC', 'RISCV:LE:32:RV32I', 'RISCV:LE:32:RV32IC', 'RISCV:LE:32:RV32IMC', 'RISCV:LE:32:default'],
|
||||
'riscv64': ['RISCV:LE:64:RV64G', 'RISCV:LE:64:RV64GC', 'RISCV:LE:64:RV64I', 'RISCV:LE:64:RV64IC', 'RISCV:LE:64:default'],
|
||||
'unknown-mach-32': ['DATA:LE:32:default', 'DATA:LE:32:default'],
|
||||
'unknown-mach-64': ['DATA:LE:64:default', 'DATA:LE:64:default'],
|
||||
'arc': [],
|
||||
'avr': ['avr8:LE:24:xmega'],
|
||||
'wasm32': ['x86:LE:32:default'],
|
||||
}
|
||||
|
||||
data64_compiler_map = {
|
||||
None: 'pointer64',
|
||||
}
|
||||
|
||||
x86_compiler_map = {
|
||||
default_compiler_map = {
|
||||
'freebsd': 'gcc',
|
||||
'linux': 'gcc',
|
||||
'netbsd': 'gcc',
|
||||
'ps4': 'gcc',
|
||||
'ios': 'clang',
|
||||
'macosx': 'clang',
|
||||
'tvos': 'clang',
|
||||
'watchos': 'clang',
|
||||
'ios': 'gcc',
|
||||
'macosx': 'gcc',
|
||||
'tvos': 'gcc',
|
||||
'watchos': 'gcc',
|
||||
'windows': 'Visual Studio',
|
||||
# This may seem wrong, but Ghidra cspecs really describe the ABI
|
||||
'Cygwin': 'Visual Studio',
|
||||
'default': 'default',
|
||||
'unknown': 'gcc',
|
||||
}
|
||||
|
||||
compiler_map = {
|
||||
'DATA:BE:64:default': data64_compiler_map,
|
||||
'DATA:LE:64:default': data64_compiler_map,
|
||||
'x86:LE:32:default': x86_compiler_map,
|
||||
'x86:LE:64:default': x86_compiler_map,
|
||||
'DATA:BE:64:': data64_compiler_map,
|
||||
'DATA:LE:64:': data64_compiler_map,
|
||||
'x86:LE:32:': default_compiler_map,
|
||||
'x86:LE:64:': default_compiler_map,
|
||||
'ARM:LE:32:': default_compiler_map,
|
||||
'ARM:LE:64:': default_compiler_map,
|
||||
}
|
||||
|
||||
|
||||
def get_arch():
|
||||
def find_host_triple():
|
||||
dbg = util.get_debugger()
|
||||
for i in range(dbg.GetNumPlatforms()):
|
||||
platform = dbg.GetPlatformAtIndex(i)
|
||||
if platform.GetName() == 'host':
|
||||
return platform.GetTriple()
|
||||
return 'unrecognized'
|
||||
|
||||
|
||||
def find_triple():
|
||||
triple = util.get_target().triple
|
||||
if triple is None:
|
||||
return "x86_64"
|
||||
if triple is not None:
|
||||
return triple
|
||||
return find_host_triple()
|
||||
|
||||
|
||||
def get_arch():
|
||||
triple = find_triple()
|
||||
return triple.split('-')[0]
|
||||
|
||||
|
||||
@ -73,7 +166,6 @@ def get_endian():
|
||||
parm = util.get_convenience_variable('endian')
|
||||
if parm != 'auto':
|
||||
return parm
|
||||
# Once again, we have to hack using the human-readable 'show'
|
||||
order = util.get_target().GetByteOrder()
|
||||
if order is lldb.eByteOrderLittle:
|
||||
return 'little'
|
||||
@ -88,15 +180,11 @@ def get_osabi():
|
||||
parm = util.get_convenience_variable('osabi')
|
||||
if not parm in ['auto', 'default']:
|
||||
return parm
|
||||
# We have to hack around the fact the LLDB won't give us the current OS ABI
|
||||
# via the API if it is "auto" or "default". Using "show", we can get it, but
|
||||
# we have to parse output meant for a human. The current value will be on
|
||||
# the top line, delimited by double quotes. It will be the last delimited
|
||||
# thing on that line. ("auto" may appear earlier on the line.)
|
||||
triple = util.get_target().triple
|
||||
triple = find_triple()
|
||||
# this is an unfortunate feature of the tests
|
||||
if triple is None:
|
||||
return "linux"
|
||||
if triple is None or '-' not in triple:
|
||||
return "default"
|
||||
triple = find_triple()
|
||||
return triple.split('-')[2]
|
||||
|
||||
|
||||
@ -132,12 +220,20 @@ def compute_ghidra_compiler(lang):
|
||||
return comp
|
||||
|
||||
# Check if the selected lang has specific compiler recommendations
|
||||
if not lang in compiler_map:
|
||||
matched_lang = sorted(
|
||||
(l for l in compiler_map if l in lang),
|
||||
key=lambda l: compiler_map[l]
|
||||
)
|
||||
if len(matched_lang) == 0:
|
||||
return 'default'
|
||||
comp_map = compiler_map[lang]
|
||||
comp_map = compiler_map[matched_lang[0]]
|
||||
osabi = get_osabi()
|
||||
if osabi in comp_map:
|
||||
return comp_map[osabi]
|
||||
matched_osabi = sorted(
|
||||
(l for l in comp_map if l in osabi),
|
||||
key=lambda l: comp_map[l]
|
||||
)
|
||||
if len(matched_osabi) > 0:
|
||||
return comp_map[matched_osabi[0]]
|
||||
if None in comp_map:
|
||||
return comp_map[None]
|
||||
return 'default'
|
||||
@ -161,7 +257,8 @@ class DefaultMemoryMapper(object):
|
||||
def map_back(self, proc: lldb.SBProcess, address: Address) -> int:
|
||||
if address.space == self.defaultSpace:
|
||||
return address.offset
|
||||
raise ValueError(f"Address {address} is not in process {proc.GetProcessID()}")
|
||||
raise ValueError(
|
||||
f"Address {address} is not in process {proc.GetProcessID()}")
|
||||
|
||||
|
||||
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
|
||||
@ -186,29 +283,8 @@ class DefaultRegisterMapper(object):
|
||||
def map_name(self, proc, name):
|
||||
return name
|
||||
|
||||
"""
|
||||
def convert_value(self, value, type=None):
|
||||
if type is None:
|
||||
type = value.dynamic_type.strip_typedefs()
|
||||
l = type.sizeof
|
||||
# l - 1 because array() takes the max index, inclusive
|
||||
# NOTE: Might like to pre-lookup 'unsigned char', but it depends on the
|
||||
# architecture *at the time of lookup*.
|
||||
cv = value.cast(lldb.lookup_type('unsigned char').array(l - 1))
|
||||
rng = range(l)
|
||||
if self.byte_order == 'little':
|
||||
rng = reversed(rng)
|
||||
return bytes(cv[i] for i in rng)
|
||||
"""
|
||||
|
||||
def map_value(self, proc, name, value):
|
||||
try:
|
||||
### TODO: this seems half-baked
|
||||
av = value.to_bytes(8, "big")
|
||||
except e:
|
||||
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
|
||||
.format(name, value, value.type))
|
||||
return RegVal(self.map_name(proc, name), av)
|
||||
return RegVal(self.map_name(proc, name), value)
|
||||
|
||||
def map_name_back(self, proc, name):
|
||||
return name
|
||||
@ -258,4 +334,3 @@ def compute_register_mapper(lang):
|
||||
if ':LE:' in lang:
|
||||
return DEFAULT_LE_REGISTER_MAPPER
|
||||
return register_mappers[lang]
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,15 +13,17 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
import time
|
||||
import threading
|
||||
import time
|
||||
|
||||
import lldb
|
||||
|
||||
from . import commands, util
|
||||
|
||||
|
||||
ALL_EVENTS = 0xFFFF
|
||||
|
||||
|
||||
class HookState(object):
|
||||
__slots__ = ('installed', 'mem_catchpoint')
|
||||
|
||||
@ -31,7 +33,8 @@ class HookState(object):
|
||||
|
||||
|
||||
class ProcessState(object):
|
||||
__slots__ = ('first', 'regions', 'modules', 'threads', 'breaks', 'watches', 'visited')
|
||||
__slots__ = ('first', 'regions', 'modules', 'threads',
|
||||
'breaks', 'watches', 'visited')
|
||||
|
||||
def __init__(self):
|
||||
self.first = True
|
||||
@ -64,11 +67,24 @@ class ProcessState(object):
|
||||
hashable_frame = (thread.GetThreadID(), frame.GetFrameID())
|
||||
if first or hashable_frame not in self.visited:
|
||||
banks = frame.GetRegisters()
|
||||
commands.putreg(frame, banks.GetFirstValueByName(commands.DEFAULT_REGISTER_BANK))
|
||||
commands.putmem("$pc", "1", from_tty=False)
|
||||
commands.putmem("$sp", "1", from_tty=False)
|
||||
primary = banks.GetFirstValueByName(
|
||||
commands.DEFAULT_REGISTER_BANK)
|
||||
if primary.value is None:
|
||||
primary = banks[0]
|
||||
if primary is not None:
|
||||
commands.DEFAULT_REGISTER_BANK = primary.name
|
||||
if primary is not None:
|
||||
commands.putreg(frame, primary)
|
||||
try:
|
||||
commands.putmem("$pc", "1", result=None)
|
||||
except BaseException as e:
|
||||
print(f"Couldn't record page with PC: {e}")
|
||||
try:
|
||||
commands.putmem("$sp", "1", result=None)
|
||||
except BaseException as e:
|
||||
print(f"Couldn't record page with SP: {e}")
|
||||
self.visited.add(hashable_frame)
|
||||
if first or self.regions or self.threads or self.modules:
|
||||
if first or self.regions or self.modules:
|
||||
# Sections, memory syscalls, or stack allocations
|
||||
commands.put_regions()
|
||||
self.regions = False
|
||||
@ -89,8 +105,9 @@ class ProcessState(object):
|
||||
def record_exited(self, exit_code):
|
||||
proc = util.get_process()
|
||||
ipath = commands.PROCESS_PATTERN.format(procnum=proc.GetProcessID())
|
||||
commands.STATE.trace.proxy_object_path(
|
||||
ipath).set_value('_exit_code', exit_code)
|
||||
procobj = commands.STATE.trace.proxy_object_path(ipath)
|
||||
procobj.set_value('Exit Code', exit_code)
|
||||
procobj.set_value('State', 'TERMINATED')
|
||||
|
||||
|
||||
class BrkState(object):
|
||||
@ -106,7 +123,7 @@ class BrkState(object):
|
||||
return self.break_loc_counts.get(b.GetID(), 0)
|
||||
|
||||
def del_brkloc_count(self, b):
|
||||
if b not in self.break_loc_counts:
|
||||
if b.GetID() not in self.break_loc_counts:
|
||||
return 0 # TODO: Print a warning?
|
||||
count = self.break_loc_counts[b.GetID()]
|
||||
del self.break_loc_counts[b.GetID()]
|
||||
@ -117,46 +134,56 @@ HOOK_STATE = HookState()
|
||||
BRK_STATE = BrkState()
|
||||
PROC_STATE = {}
|
||||
|
||||
|
||||
class QuitSentinel(object):
|
||||
pass
|
||||
|
||||
|
||||
QUIT = QuitSentinel()
|
||||
|
||||
|
||||
def process_event(self, listener, event):
|
||||
try:
|
||||
desc = util.get_description(event)
|
||||
#event_process = lldb.SBProcess_GetProcessFromEvent(event)
|
||||
# print(f"Event: {desc}")
|
||||
target = util.get_target()
|
||||
if not target.IsValid():
|
||||
# LLDB may crash on event.GetBroadcasterClass, otherwise
|
||||
# All the checks below, e.g. SBTarget.EventIsTargetEvent, call this
|
||||
print(f"Ignoring {desc} because target is invalid")
|
||||
return
|
||||
event_process = util.get_process()
|
||||
if event_process not in PROC_STATE:
|
||||
if event_process.IsValid() and event_process.GetProcessID() not in PROC_STATE:
|
||||
PROC_STATE[event_process.GetProcessID()] = ProcessState()
|
||||
rc = event_process.GetBroadcaster().AddListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
if not rc:
|
||||
print("add listener for process failed")
|
||||
|
||||
commands.put_state(event_process)
|
||||
# NB: Calling put_state on running leaves an open transaction
|
||||
if not event_process.is_running:
|
||||
commands.put_state(event_process)
|
||||
type = event.GetType()
|
||||
if lldb.SBTarget.EventIsTargetEvent(event):
|
||||
print('Event:', desc)
|
||||
if (type & lldb.SBTarget.eBroadcastBitBreakpointChanged) != 0:
|
||||
print("eBroadcastBitBreakpointChanged")
|
||||
return on_breakpoint_modified(event)
|
||||
if (type & lldb.SBTarget.eBroadcastBitWatchpointChanged) != 0:
|
||||
print("eBroadcastBitWatchpointChanged")
|
||||
return on_watchpoint_modified(event)
|
||||
if (type & lldb.SBTarget.eBroadcastBitModulesLoaded) != 0:
|
||||
print("eBroadcastBitModulesLoaded")
|
||||
return on_new_objfile(event)
|
||||
if (type & lldb.SBTarget.eBroadcastBitModulesUnloaded) != 0:
|
||||
print("eBroadcastBitModulesUnloaded")
|
||||
return on_free_objfile(event)
|
||||
if (type & lldb.SBTarget.eBroadcastBitSymbolsLoaded) != 0:
|
||||
print("eBroadcastBitSymbolsLoaded")
|
||||
return True
|
||||
if lldb.SBProcess.EventIsProcessEvent(event):
|
||||
if (type & lldb.SBProcess.eBroadcastBitStateChanged) != 0:
|
||||
print("eBroadcastBitStateChanged")
|
||||
if not event_process.is_alive:
|
||||
return on_exited(event)
|
||||
if event_process.is_stopped:
|
||||
return on_stop(event)
|
||||
if event_process.is_running:
|
||||
return on_cont(event)
|
||||
return True
|
||||
if (type & lldb.SBProcess.eBroadcastBitInterrupt) != 0:
|
||||
print("eBroadcastBitInterrupt")
|
||||
if event_process.is_stopped:
|
||||
return on_stop(event)
|
||||
if (type & lldb.SBProcess.eBroadcastBitSTDOUT) != 0:
|
||||
@ -164,138 +191,103 @@ def process_event(self, listener, event):
|
||||
if (type & lldb.SBProcess.eBroadcastBitSTDERR) != 0:
|
||||
return True
|
||||
if (type & lldb.SBProcess.eBroadcastBitProfileData) != 0:
|
||||
print("eBroadcastBitProfileData")
|
||||
return True
|
||||
if (type & lldb.SBProcess.eBroadcastBitStructuredData) != 0:
|
||||
print("eBroadcastBitStructuredData")
|
||||
return True
|
||||
# NB: Thread events not currently processes
|
||||
if lldb.SBThread.EventIsThreadEvent(event):
|
||||
print('Event:', desc)
|
||||
if (type & lldb.SBThread.eBroadcastBitStackChanged) != 0:
|
||||
print("eBroadcastBitStackChanged")
|
||||
return on_frame_selected()
|
||||
if (type & lldb.SBThread.eBroadcastBitThreadSuspended) != 0:
|
||||
print("eBroadcastBitThreadSuspended")
|
||||
if event_process.is_stopped:
|
||||
return on_stop(event)
|
||||
if (type & lldb.SBThread.eBroadcastBitThreadResumed) != 0:
|
||||
print("eBroadcastBitThreadResumed")
|
||||
return on_cont(event)
|
||||
if (type & lldb.SBThread.eBroadcastBitSelectedFrameChanged) != 0:
|
||||
print("eBroadcastBitSelectedFrameChanged")
|
||||
return on_frame_selected()
|
||||
if (type & lldb.SBThread.eBroadcastBitThreadSelected) != 0:
|
||||
print("eBroadcastBitThreadSelected")
|
||||
return on_thread_selected()
|
||||
if lldb.SBBreakpoint.EventIsBreakpointEvent(event):
|
||||
print('Event:', desc)
|
||||
btype = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event);
|
||||
bpt = lldb.SBBreakpoint.GetBreakpointFromEvent(event);
|
||||
btype = lldb.SBBreakpoint.GetBreakpointEventTypeFromEvent(event)
|
||||
bpt = lldb.SBBreakpoint.GetBreakpointFromEvent(event)
|
||||
if btype is lldb.eBreakpointEventTypeAdded:
|
||||
print("eBreakpointEventTypeAdded")
|
||||
return on_breakpoint_created(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeAutoContinueChanged:
|
||||
print("elldb.BreakpointEventTypeAutoContinueChanged")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeCommandChanged:
|
||||
print("eBreakpointEventTypeCommandChanged")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeConditionChanged:
|
||||
print("eBreakpointEventTypeConditionChanged")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeDisabled:
|
||||
print("eBreakpointEventTypeDisabled")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeEnabled:
|
||||
print("eBreakpointEventTypeEnabled")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeIgnoreChanged:
|
||||
print("eBreakpointEventTypeIgnoreChanged")
|
||||
return True
|
||||
if btype is lldb.eBreakpointEventTypeInvalidType:
|
||||
print("eBreakpointEventTypeInvalidType")
|
||||
return True
|
||||
if btype is lldb.eBreakpointEventTypeLocationsAdded:
|
||||
print("eBreakpointEventTypeLocationsAdded")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeLocationsRemoved:
|
||||
print("eBreakpointEventTypeLocationsRemoved")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeLocationsResolved:
|
||||
print("eBreakpointEventTypeLocationsResolved")
|
||||
return on_breakpoint_modified(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeRemoved:
|
||||
print("eBreakpointEventTypeRemoved")
|
||||
return on_breakpoint_deleted(bpt)
|
||||
if btype is lldb.eBreakpointEventTypeThreadChanged:
|
||||
print("eBreakpointEventTypeThreadChanged")
|
||||
return on_breakpoint_modified(bpt)
|
||||
print("UNKNOWN BREAKPOINT EVENT")
|
||||
return True
|
||||
if lldb.SBWatchpoint.EventIsWatchpointEvent(event):
|
||||
print('Event:', desc)
|
||||
btype = lldb.SBWatchpoint.GetWatchpointEventTypeFromEvent(event);
|
||||
bpt = lldb.SBWatchpoint.GetWatchpointFromEvent(eventt);
|
||||
btype = lldb.SBWatchpoint.GetWatchpointEventTypeFromEvent(event)
|
||||
bpt = lldb.SBWatchpoint.GetWatchpointFromEvent(eventt)
|
||||
if btype is lldb.eWatchpointEventTypeAdded:
|
||||
print("eWatchpointEventTypeAdded")
|
||||
return on_watchpoint_added(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeCommandChanged:
|
||||
print("eWatchpointEventTypeCommandChanged")
|
||||
return on_watchpoint_modified(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeConditionChanged:
|
||||
print("eWatchpointEventTypeConditionChanged")
|
||||
return on_watchpoint_modified(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeDisabled:
|
||||
print("eWatchpointEventTypeDisabled")
|
||||
return on_watchpoint_modified(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeEnabled:
|
||||
print("eWatchpointEventTypeEnabled")
|
||||
return on_watchpoint_modified(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeIgnoreChanged:
|
||||
print("eWatchpointEventTypeIgnoreChanged")
|
||||
return True
|
||||
if btype is lldb.eWatchpointEventTypeInvalidType:
|
||||
print("eWatchpointEventTypeInvalidType")
|
||||
return True
|
||||
if btype is lldb.eWatchpointEventTypeRemoved:
|
||||
print("eWatchpointEventTypeRemoved")
|
||||
return on_watchpoint_deleted(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeThreadChanged:
|
||||
print("eWatchpointEventTypeThreadChanged")
|
||||
return on_watchpoint_modified(bpt)
|
||||
if btype is lldb.eWatchpointEventTypeTypeChanged:
|
||||
print("eWatchpointEventTypeTypeChanged")
|
||||
return on_watchpoint_modified(bpt)
|
||||
print("UNKNOWN WATCHPOINT EVENT")
|
||||
return True
|
||||
if lldb.SBCommandInterpreter.EventIsCommandInterpreterEvent(event):
|
||||
print('Event:', desc)
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitAsynchronousErrorData) != 0:
|
||||
print("eBroadcastBitAsynchronousErrorData")
|
||||
return True
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitAsynchronousOutputData) != 0:
|
||||
print("eBroadcastBitAsynchronousOutputData")
|
||||
return True
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitQuitCommandReceived) != 0:
|
||||
print("eBroadcastBitQuitCommandReceived")
|
||||
# DO NOT return QUIT here.
|
||||
# For some reason, this event comes just after launch.
|
||||
# Maybe need to figure out *which* interpreter?
|
||||
return True
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitResetPrompt) != 0:
|
||||
print("eBroadcastBitResetPrompt")
|
||||
return True
|
||||
if (type & lldb.SBCommandInterpreter.eBroadcastBitThreadShouldExit) != 0:
|
||||
print("eBroadcastBitThreadShouldExit")
|
||||
return True
|
||||
print("UNKNOWN EVENT")
|
||||
return True
|
||||
except RuntimeError as e:
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
|
||||
|
||||
|
||||
class EventThread(threading.Thread):
|
||||
func = process_event
|
||||
event = lldb.SBEvent()
|
||||
|
||||
def run(self):
|
||||
|
||||
def run(self):
|
||||
# Let's only try at most 4 times to retrieve any kind of event.
|
||||
# After that, the thread exits.
|
||||
listener = lldb.SBListener('eventlistener')
|
||||
@ -303,54 +295,63 @@ class EventThread(threading.Thread):
|
||||
target = util.get_target()
|
||||
proc = util.get_process()
|
||||
rc = cli.GetBroadcaster().AddListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
if not rc:
|
||||
print("add listener for cli failed")
|
||||
return
|
||||
# return
|
||||
rc = target.GetBroadcaster().AddListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
if not rc:
|
||||
print("add listener for target failed")
|
||||
return
|
||||
# return
|
||||
rc = proc.GetBroadcaster().AddListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
if not rc:
|
||||
print("add listener for process failed")
|
||||
return
|
||||
|
||||
# return
|
||||
|
||||
# Not sure what effect this logic has
|
||||
rc = cli.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
print("add listener for cli failed")
|
||||
return
|
||||
if not rc:
|
||||
print("add initial events for cli failed")
|
||||
# return
|
||||
rc = target.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
print("add listener for target failed")
|
||||
return
|
||||
if not rc:
|
||||
print("add initial events for target failed")
|
||||
# return
|
||||
rc = proc.GetBroadcaster().AddInitialEventsToListener(listener, ALL_EVENTS)
|
||||
if rc is False:
|
||||
print("add listener for process failed")
|
||||
return
|
||||
if not rc:
|
||||
print("add initial events for process failed")
|
||||
# return
|
||||
|
||||
rc = listener.StartListeningForEventClass(util.get_debugger(), lldb.SBThread.GetBroadcasterClassName(), ALL_EVENTS)
|
||||
if rc is False:
|
||||
rc = listener.StartListeningForEventClass(
|
||||
util.get_debugger(), lldb.SBThread.GetBroadcasterClassName(), ALL_EVENTS)
|
||||
if not rc:
|
||||
print("add listener for threads failed")
|
||||
return
|
||||
# THIS WILL NOT WORK: listener = util.get_debugger().GetListener()
|
||||
|
||||
# return
|
||||
# THIS WILL NOT WORK: listener = util.get_debugger().GetListener()
|
||||
|
||||
while True:
|
||||
event_recvd = False
|
||||
while event_recvd is False:
|
||||
while not event_recvd:
|
||||
if listener.WaitForEvent(lldb.UINT32_MAX, self.event):
|
||||
try:
|
||||
self.func(listener, self.event)
|
||||
while listener.GetNextEvent(self.event):
|
||||
self.func(listener, self.event)
|
||||
event_recvd = True
|
||||
except Exception as e:
|
||||
result = self.func(listener, self.event)
|
||||
if result is QUIT:
|
||||
return
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
while listener.GetNextEvent(self.event):
|
||||
try:
|
||||
result = self.func(listener, self.event)
|
||||
if result is QUIT:
|
||||
return
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
event_recvd = True
|
||||
proc = util.get_process()
|
||||
if proc is not None and not proc.is_alive:
|
||||
break
|
||||
return
|
||||
|
||||
|
||||
"""
|
||||
# Not sure if this is possible in LLDB...
|
||||
|
||||
@ -475,11 +476,11 @@ def on_memory_changed(event):
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Memory *0x{:08x} changed".format(event.address)):
|
||||
commands.put_bytes(event.address, event.address + event.length,
|
||||
pages=False, is_mi=False, from_tty=False)
|
||||
pages=False, is_mi=False, result=None)
|
||||
|
||||
|
||||
def on_register_changed(event):
|
||||
print("Register changed: {}".format(dir(event)))
|
||||
# print("Register changed: {}".format(dir(event)))
|
||||
proc = util.get_process()
|
||||
if proc.GetProcessID() not in PROC_STATE:
|
||||
return
|
||||
@ -510,7 +511,8 @@ def on_cont(event):
|
||||
|
||||
|
||||
def on_stop(event):
|
||||
proc = lldb.SBProcess.GetProcessFromEvent(event)
|
||||
proc = lldb.SBProcess.GetProcessFromEvent(
|
||||
event) if event is not None else util.get_process()
|
||||
if proc.GetProcessID() not in PROC_STATE:
|
||||
print("not in state")
|
||||
return
|
||||
@ -547,11 +549,13 @@ def on_exited(event):
|
||||
commands.put_event_thread()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def notify_others_breaks(proc):
|
||||
for num, state in PROC_STATE.items():
|
||||
if num != proc.GetProcessID():
|
||||
state.breaks = True
|
||||
|
||||
|
||||
def notify_others_watches(proc):
|
||||
for num, state in PROC_STATE.items():
|
||||
if num != proc.GetProcessID():
|
||||
@ -618,7 +622,7 @@ def on_breakpoint_deleted(b):
|
||||
notify_others_breaks(proc)
|
||||
if proc.GetProcessID() not in PROC_STATE:
|
||||
return
|
||||
old_count = BRK_STATE.del_brkloc_count(b.GetID())
|
||||
old_count = BRK_STATE.del_brkloc_count(b)
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
@ -697,6 +701,7 @@ def remove_hooks():
|
||||
return
|
||||
HOOK_STATE.installed = False
|
||||
|
||||
|
||||
def enable_current_process():
|
||||
proc = util.get_process()
|
||||
PROC_STATE[proc.GetProcessID()] = ProcessState()
|
||||
|
@ -15,6 +15,7 @@
|
||||
##
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ghidratrace import sch
|
||||
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
||||
@ -66,9 +67,7 @@ def find_proc_by_num(procnum):
|
||||
|
||||
|
||||
def find_proc_by_pattern(object, pattern, err_msg):
|
||||
print(object.path)
|
||||
mat = pattern.fullmatch(object.path)
|
||||
print(mat)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
procnum = int(mat['procnum'])
|
||||
@ -76,16 +75,17 @@ def find_proc_by_pattern(object, pattern, err_msg):
|
||||
|
||||
|
||||
def find_proc_by_obj(object):
|
||||
return find_proc_by_pattern(object, PROCESS_PATTERN, "an Process")
|
||||
return find_proc_by_pattern(object, PROCESS_PATTERN, "a Process")
|
||||
|
||||
|
||||
def find_proc_by_procbreak_obj(object):
|
||||
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
|
||||
"a BreakpointLocationContainer")
|
||||
"a BreakpointLocationContainer")
|
||||
|
||||
|
||||
def find_proc_by_procwatch_obj(object):
|
||||
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
|
||||
"a WatchpointContainer")
|
||||
"a WatchpointContainer")
|
||||
|
||||
|
||||
def find_proc_by_env_obj(object):
|
||||
@ -108,7 +108,8 @@ def find_thread_by_num(proc, tnum):
|
||||
for t in proc.threads:
|
||||
if t.GetThreadID() == tnum:
|
||||
return t
|
||||
raise KeyError(f"Processes[{proc.GetProcessID()}].Threads[{tnum}] does not exist")
|
||||
raise KeyError(
|
||||
f"Processes[{proc.GetProcessID()}].Threads[{tnum}] does not exist")
|
||||
|
||||
|
||||
def find_thread_by_pattern(pattern, object, err_msg):
|
||||
@ -166,7 +167,7 @@ def find_reg_by_name(f, name):
|
||||
# I could keep my own cache in a dict, but why?
|
||||
def find_bpt_by_number(breaknum):
|
||||
# TODO: If len exceeds some threshold, use binary search?
|
||||
for i in range(0,util.get_target().GetNumBreakpoints()):
|
||||
for i in range(0, util.get_target().GetNumBreakpoints()):
|
||||
b = util.get_target().GetBreakpointAtIndex(i)
|
||||
if b.GetID() == breaknum:
|
||||
return b
|
||||
@ -189,7 +190,7 @@ def find_bpt_by_obj(object):
|
||||
# I could keep my own cache in a dict, but why?
|
||||
def find_wpt_by_number(watchnum):
|
||||
# TODO: If len exceeds some threshold, use binary search?
|
||||
for i in range(0,util.get_target().GetNumWatchpoints()):
|
||||
for i in range(0, util.get_target().GetNumWatchpoints()):
|
||||
w = util.get_target().GetWatchpointAtIndex(i)
|
||||
if w.GetID() == watchnum:
|
||||
return w
|
||||
@ -203,6 +204,7 @@ def find_wpt_by_pattern(pattern, object, err_msg):
|
||||
watchnum = int(mat['watchnum'])
|
||||
return find_wpt_by_number(watchnum)
|
||||
|
||||
|
||||
def find_wpt_by_obj(object):
|
||||
return find_wpt_by_pattern(PROC_WATCHLOC_PATTERN, object, "a WatchpointSpec")
|
||||
|
||||
@ -228,106 +230,122 @@ def find_bpt_loc_by_obj(object):
|
||||
return bpt.locations[locnum - 1] # Display is 1-up
|
||||
|
||||
|
||||
def exec_convert_errors(cmd, to_string=False):
|
||||
res = lldb.SBCommandReturnObject()
|
||||
util.get_debugger().GetCommandInterpreter().HandleCommand(cmd, res)
|
||||
if not res.Succeeded():
|
||||
if not to_string:
|
||||
print(res.GetError(), file=sys.stderr)
|
||||
raise RuntimeError(res.GetError())
|
||||
if to_string:
|
||||
return res.GetOutput()
|
||||
print(res.GetOutput(), end="")
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def execute(cmd: str, to_string: bool=False):
|
||||
"""Execute a CLI command."""
|
||||
res = lldb.SBCommandReturnObject()
|
||||
util.get_debugger().GetCommandInterpreter().HandleCommand(cmd, res)
|
||||
if to_string:
|
||||
if res.Succeeded():
|
||||
return res.GetOutput()
|
||||
else:
|
||||
return res.GetError()
|
||||
# TODO: Check for eCommandInterpreterResultQuitRequested?
|
||||
return exec_convert_errors(cmd, to_string)
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method
|
||||
def evaluate(expr: str):
|
||||
"""Evaluate an expression."""
|
||||
value = util.get_target().EvaluateExpression(expr)
|
||||
if value.GetError().Fail():
|
||||
raise RuntimeError(value.GetError().GetCString())
|
||||
return commands.convert_value(value)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def pyeval(expr: str):
|
||||
return eval(expr)
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh', display="Refresh Available")
|
||||
def refresh_available(node: sch.Schema('AvailableContainer')):
|
||||
"""List processes on lldb's host system."""
|
||||
with commands.open_tracked_tx('Refresh Available'):
|
||||
util.get_debugger().HandleCommand('ghidra_trace_put_available')
|
||||
exec_convert_errors('ghidra trace put-available')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Breakpoints")
|
||||
def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
|
||||
"""
|
||||
Refresh the list of breakpoints (including locations for the current
|
||||
process).
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Breakpoints'):
|
||||
util.get_debugger().HandleCommand('ghidra_trace_put_breakpoints')
|
||||
exec_convert_errors('ghidra trace put-breakpoints')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Processes")
|
||||
def refresh_processes(node: sch.Schema('ProcessContainer')):
|
||||
"""Refresh the list of processes."""
|
||||
with commands.open_tracked_tx('Refresh Processes'):
|
||||
util.get_debugger().HandleCommand('ghidra_trace_put_threads')
|
||||
exec_convert_errors('ghidra trace put-threads')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Breakpoints")
|
||||
def refresh_proc_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
||||
"""
|
||||
Refresh the breakpoint locations for the process.
|
||||
|
||||
In the course of refreshing the locations, the breakpoint list will also be
|
||||
refreshed.
|
||||
Refresh the breakpoints for the process.
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Breakpoint Locations'):
|
||||
util.get_debugger().HandleCommand('ghidra_trace_put_breakpoints');
|
||||
exec_convert_errors('ghidra trace put-breakpoints')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Watchpoints")
|
||||
def refresh_proc_watchpoints(node: sch.Schema('WatchpointContainer')):
|
||||
"""
|
||||
Refresh the watchpoint locations for the process.
|
||||
|
||||
In the course of refreshing the locations, the watchpoint list will also be
|
||||
refreshed.
|
||||
Refresh the watchpoints for the process.
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Watchpoint Locations'):
|
||||
util.get_debugger().HandleCommand('ghidra_trace_put_watchpoints');
|
||||
exec_convert_errors('ghidra trace put-watchpoints')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Environment")
|
||||
def refresh_environment(node: sch.Schema('Environment')):
|
||||
"""Refresh the environment descriptors (arch, os, endian)."""
|
||||
with commands.open_tracked_tx('Refresh Environment'):
|
||||
util.get_debugger().HandleCommand('ghidra_trace_put_environment')
|
||||
exec_convert_errors('ghidra trace put-environment')
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
|
||||
@REGISTRY.method(action='refresh', display="Refresh Threads")
|
||||
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
||||
"""Refresh the list of threads in the process."""
|
||||
with commands.open_tracked_tx('Refresh Threads'):
|
||||
util.get_debugger().HandleCommand('ghidra_trace_put_threads')
|
||||
exec_convert_errors('ghidra trace put-threads')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Stack")
|
||||
def refresh_stack(node: sch.Schema('Stack')):
|
||||
"""Refresh the backtrace for the thread."""
|
||||
t = find_thread_by_stack_obj(node)
|
||||
t.process.SetSelectedThread(t)
|
||||
with commands.open_tracked_tx('Refresh Stack'):
|
||||
util.get_debugger().HandleCommand('ghidra_trace_put_frames');
|
||||
exec_convert_errors('ghidra trace put-frames')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Registers")
|
||||
def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
||||
"""Refresh the register values for the frame."""
|
||||
f = find_frame_by_regs_obj(node)
|
||||
f.thread.SetSelectedFrame(f.GetFrameID())
|
||||
# TODO: Groups?
|
||||
with commands.open_tracked_tx('Refresh Registers'):
|
||||
util.get_debugger().HandleCommand('ghidra_trace_putreg');
|
||||
exec_convert_errors('ghidra trace putreg')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Memory")
|
||||
def refresh_mappings(node: sch.Schema('Memory')):
|
||||
"""Refresh the list of memory regions for the process."""
|
||||
with commands.open_tracked_tx('Refresh Memory Regions'):
|
||||
util.get_debugger().HandleCommand('ghidra_trace_put_regions');
|
||||
exec_convert_errors('ghidra trace put-regions')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Modules")
|
||||
def refresh_modules(node: sch.Schema('ModuleContainer')):
|
||||
"""
|
||||
Refresh the modules and sections list for the process.
|
||||
@ -335,14 +353,16 @@ def refresh_modules(node: sch.Schema('ModuleContainer')):
|
||||
This will refresh the sections for all modules, not just the selected one.
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Modules'):
|
||||
util.get_debugger().HandleCommand('ghidra_trace_put_modules');
|
||||
exec_convert_errors('ghidra trace put-modules')
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_process(process: sch.Schema('Process')):
|
||||
"""Switch to the process."""
|
||||
# TODO
|
||||
return
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_thread(thread: sch.Schema('Thread')):
|
||||
"""Switch to the thread."""
|
||||
@ -361,88 +381,91 @@ def activate_frame(frame: sch.Schema('StackFrame')):
|
||||
def remove_process(process: sch.Schema('Process')):
|
||||
"""Remove the process."""
|
||||
proc = find_proc_by_obj(process)
|
||||
util.get_debugger().HandleCommand(f'target delete 0')
|
||||
exec_convert_errors(f'target delete 0')
|
||||
|
||||
|
||||
@REGISTRY.method(action='connect')
|
||||
@REGISTRY.method(action='connect', display="Connect Target")
|
||||
def target(process: sch.Schema('Process'), spec: str):
|
||||
"""Connect to a target machine or process."""
|
||||
util.get_debugger().HandleCommand(f'target select {spec}')
|
||||
exec_convert_errors(f'target select {spec}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
@REGISTRY.method(action='attach', display="Attach by Attachable")
|
||||
def attach_obj(process: sch.Schema('Process'), target: sch.Schema('Attachable')):
|
||||
"""Attach the process to the given target."""
|
||||
pid = find_availpid_by_obj(target)
|
||||
util.get_debugger().HandleCommand(f'process attach -p {pid}')
|
||||
exec_convert_errors(f'process attach -p {pid}')
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
|
||||
@REGISTRY.method(action='attach', display="Attach by PID")
|
||||
def attach_pid(process: sch.Schema('Process'), pid: int):
|
||||
"""Attach the process to the given target."""
|
||||
util.get_debugger().HandleCommand(f'process attach -p {pid}')
|
||||
exec_convert_errors(f'process attach -p {pid}')
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
|
||||
@REGISTRY.method(action='attach', display="Attach by Name")
|
||||
def attach_name(process: sch.Schema('Process'), name: str):
|
||||
"""Attach the process to the given target."""
|
||||
util.get_debugger().HandleCommand(f'process attach -n {name}')
|
||||
exec_convert_errors(f'process attach -n {name}')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@REGISTRY.method(display="Detach")
|
||||
def detach(process: sch.Schema('Process')):
|
||||
"""Detach the process's target."""
|
||||
util.get_debugger().HandleCommand(f'process detach')
|
||||
exec_convert_errors(f'process detach')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
def do_launch(process, file, args, cmd):
|
||||
exec_convert_errors(f'file {file}')
|
||||
if args != '':
|
||||
exec_convert_errors(f'settings set target.run-args {args}')
|
||||
exec_convert_errors(cmd)
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch', display="Launch at Entry")
|
||||
def launch_loader(process: sch.Schema('Process'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
"""
|
||||
Start a native process with the given command line, stopping at 'main'.
|
||||
|
||||
If 'main' is not defined in the file, this behaves like 'run'.
|
||||
"""
|
||||
util.get_debugger().HandleCommand(f'file {file}')
|
||||
if args is not '':
|
||||
util.get_debugger().HandleCommand(f'settings set target.run-args {args}')
|
||||
util.get_debugger().HandleCommand(f'process launch --stop-at-entry')
|
||||
do_launch(process, file, args, 'process launch --stop-at-entry')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
@REGISTRY.method(action='launch', display="Launch and Run")
|
||||
def launch(process: sch.Schema('Process'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
"""
|
||||
Run a native process with the given command line.
|
||||
|
||||
The process will not stop until it hits one of your breakpoints, or it is
|
||||
signaled.
|
||||
"""
|
||||
util.get_debugger().HandleCommand(f'file {file}')
|
||||
if args is not '':
|
||||
util.get_debugger().HandleCommand(f'settings set target.run-args {args}')
|
||||
util.get_debugger().HandleCommand(f'run')
|
||||
do_launch(process, file, args, 'run')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def kill(process: sch.Schema('Process')):
|
||||
"""Kill execution of the process."""
|
||||
util.get_debugger().HandleCommand('process kill')
|
||||
exec_convert_errors('process kill')
|
||||
|
||||
|
||||
@REGISTRY.method(name='continue', action='resume')
|
||||
def _continue(process: sch.Schema('Process')):
|
||||
"""Continue execution of the process."""
|
||||
util.get_debugger().HandleCommand('process continue')
|
||||
exec_convert_errors('process continue')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def interrupt():
|
||||
def interrupt(process: sch.Schema('Process')):
|
||||
"""Interrupt the execution of the debugged program."""
|
||||
util.get_debugger().HandleCommand('process interrupt')
|
||||
#util.get_process().SendAsyncInterrupt()
|
||||
#util.get_debugger().HandleCommand('^c')
|
||||
#util.get_process().Signal(2)
|
||||
exec_convert_errors('process interrupt')
|
||||
# util.get_process().SendAsyncInterrupt()
|
||||
# exec_convert_errors('^c')
|
||||
# util.get_process().Signal(2)
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_into')
|
||||
@ -450,7 +473,7 @@ def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||
"""Step on instruction exactly."""
|
||||
t = find_thread_by_obj(thread)
|
||||
t.process.SetSelectedThread(t)
|
||||
util.get_debugger().HandleCommand('thread step-inst')
|
||||
exec_convert_errors('thread step-inst')
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_over')
|
||||
@ -458,7 +481,7 @@ def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||
"""Step one instruction, but proceed through subroutine calls."""
|
||||
t = find_thread_by_obj(thread)
|
||||
t.process.SetSelectedThread(t)
|
||||
util.get_debugger().HandleCommand('thread step-inst-over')
|
||||
exec_convert_errors('thread step-inst-over')
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_out')
|
||||
@ -467,27 +490,27 @@ def step_out(thread: sch.Schema('Thread')):
|
||||
if thread is not None:
|
||||
t = find_thread_by_obj(thread)
|
||||
t.process.SetSelectedThread(t)
|
||||
util.get_debugger().HandleCommand('thread step-out')
|
||||
exec_convert_errors('thread step-out')
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_ext')
|
||||
def step_ext(thread: sch.Schema('Thread'), address: Address):
|
||||
@REGISTRY.method(action='step_ext', display="Advance")
|
||||
def step_advance(thread: sch.Schema('Thread'), address: Address):
|
||||
"""Continue execution up to the given address."""
|
||||
t = find_thread_by_obj(thread)
|
||||
t.process.SetSelectedThread(t)
|
||||
offset = thread.trace.memory_mapper.map_back(t.process, address)
|
||||
util.get_debugger().HandleCommand(f'thread until -a {offset}')
|
||||
exec_convert_errors(f'thread until -a {offset}')
|
||||
|
||||
|
||||
@REGISTRY.method(name='return', action='step_ext')
|
||||
def _return(thread: sch.Schema('Thread'), value: int=None):
|
||||
@REGISTRY.method(action='step_ext', display="Return")
|
||||
def step_return(thread: sch.Schema('Thread'), value: int=None):
|
||||
"""Skip the remainder of the current function."""
|
||||
t = find_thread_by_obj(thread)
|
||||
t.process.SetSelectedThread(t)
|
||||
if value is None:
|
||||
util.get_debugger().HandleCommand('thread return')
|
||||
exec_convert_errors('thread return')
|
||||
else:
|
||||
util.get_debugger().HandleCommand(f'thread return {value}')
|
||||
exec_convert_errors(f'thread return {value}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_sw_execute')
|
||||
@ -495,14 +518,14 @@ def break_address(process: sch.Schema('Process'), address: Address):
|
||||
"""Set a breakpoint."""
|
||||
proc = find_proc_by_obj(process)
|
||||
offset = process.trace.memory_mapper.map_back(proc, address)
|
||||
util.get_debugger().HandleCommand(f'breakpoint set -a 0x{offset:x}')
|
||||
exec_convert_errors(f'breakpoint set -a 0x{offset:x}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_sw_execute')
|
||||
def break_expression(expression: str):
|
||||
"""Set a breakpoint."""
|
||||
# TODO: Escape?
|
||||
util.get_debugger().HandleCommand(f'breakpoint set -r {expression}')
|
||||
exec_convert_errors(f'breakpoint set -r {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_hw_execute')
|
||||
@ -510,14 +533,14 @@ def break_hw_address(process: sch.Schema('Process'), address: Address):
|
||||
"""Set a hardware-assisted breakpoint."""
|
||||
proc = find_proc_by_obj(process)
|
||||
offset = process.trace.memory_mapper.map_back(proc, address)
|
||||
util.get_debugger().HandleCommand(f'breakpoint set -H -a 0x{offset:x}')
|
||||
exec_convert_errors(f'breakpoint set -H -a 0x{offset:x}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_hw_execute')
|
||||
def break_hw_expression(expression: str):
|
||||
"""Set a hardware-assisted breakpoint."""
|
||||
# TODO: Escape?
|
||||
util.get_debugger().HandleCommand(f'breakpoint set -H -name {expression}')
|
||||
exec_convert_errors(f'breakpoint set -H -name {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_read')
|
||||
@ -527,13 +550,16 @@ def break_read_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
offset_start = process.trace.memory_mapper.map_back(
|
||||
proc, Address(range.space, range.min))
|
||||
sz = range.length()
|
||||
util.get_debugger().HandleCommand(f'watchpoint set expression -s {sz} -w read -- {offset_start}')
|
||||
exec_convert_errors(
|
||||
f'watchpoint set expression -s {sz} -w read -- {offset_start}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_read')
|
||||
def break_read_expression(expression: str):
|
||||
def break_read_expression(expression: str, size=None):
|
||||
"""Set a read watchpoint."""
|
||||
util.get_debugger().HandleCommand(f'watchpoint set expression -w read -- {expression}')
|
||||
size_part = '' if size is None else f'-s {size}'
|
||||
exec_convert_errors(
|
||||
f'watchpoint set expression {size_part} -w read -- {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_write')
|
||||
@ -543,13 +569,16 @@ def break_write_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
offset_start = process.trace.memory_mapper.map_back(
|
||||
proc, Address(range.space, range.min))
|
||||
sz = range.length()
|
||||
util.get_debugger().HandleCommand(f'watchpoint set expression -s {sz} -- {offset_start}')
|
||||
exec_convert_errors(
|
||||
f'watchpoint set expression -s {sz} -- {offset_start}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_write')
|
||||
def break_write_expression(expression: str):
|
||||
def break_write_expression(expression: str, size=None):
|
||||
"""Set a watchpoint."""
|
||||
util.get_debugger().HandleCommand(f'watchpoint set expression -- {expression}')
|
||||
size_part = '' if size is None else f'-s {size}'
|
||||
exec_convert_errors(
|
||||
f'watchpoint set expression {size_part} -- {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_access')
|
||||
@ -559,19 +588,22 @@ def break_access_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
offset_start = process.trace.memory_mapper.map_back(
|
||||
proc, Address(range.space, range.min))
|
||||
sz = range.length()
|
||||
util.get_debugger().HandleCommand(f'watchpoint set expression -s {sz} -w read_write -- {offset_start}')
|
||||
exec_convert_errors(
|
||||
f'watchpoint set expression -s {sz} -w read_write -- {offset_start}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_access')
|
||||
def break_access_expression(expression: str):
|
||||
def break_access_expression(expression: str, size=None):
|
||||
"""Set an access watchpoint."""
|
||||
util.get_debugger().HandleCommand(f'watchpoint set expression -w read_write -- {expression}')
|
||||
size_part = '' if size is None else f'-s {size}'
|
||||
exec_convert_errors(
|
||||
f'watchpoint set expression {size_part} -w read_write -- {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_ext')
|
||||
@REGISTRY.method(action='break_ext', display="Break on Exception")
|
||||
def break_exception(lang: str):
|
||||
"""Set a catchpoint."""
|
||||
util.get_debugger().HandleCommand(f'breakpoint set -E {lang}')
|
||||
exec_convert_errors(f'breakpoint set -E {lang}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='toggle')
|
||||
@ -580,18 +612,20 @@ def toggle_watchpoint(breakpoint: sch.Schema('WatchpointSpec'), enabled: bool):
|
||||
wpt = find_wpt_by_obj(watchpoint)
|
||||
wpt.enabled = enabled
|
||||
|
||||
|
||||
@REGISTRY.method(action='toggle')
|
||||
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
||||
"""Toggle a breakpoint."""
|
||||
bpt = find_bpt_by_obj(breakpoint)
|
||||
bpt.enabled = enabled
|
||||
|
||||
|
||||
@REGISTRY.method(action='toggle')
|
||||
def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabled: bool):
|
||||
"""Toggle a breakpoint location."""
|
||||
bptnum, locnum = find_bptlocnum_by_obj(location)
|
||||
cmd = 'enable' if enabled else 'disable'
|
||||
util.get_debugger().HandleCommand(f'breakpoint {cmd} {bptnum}.{locnum}')
|
||||
exec_convert_errors(f'breakpoint {cmd} {bptnum}.{locnum}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
@ -599,14 +633,15 @@ def delete_watchpoint(watchpoint: sch.Schema('WatchpointSpec')):
|
||||
"""Delete a watchpoint."""
|
||||
wpt = find_wpt_by_obj(watchpoint)
|
||||
wptnum = wpt.GetID()
|
||||
util.get_debugger().HandleCommand(f'watchpoint delete {wptnum}')
|
||||
exec_convert_errors(f'watchpoint delete {wptnum}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
||||
"""Delete a breakpoint."""
|
||||
bpt = find_bpt_by_obj(breakpoint)
|
||||
bptnum = bpt.GetID()
|
||||
util.get_debugger().HandleCommand(f'breakpoint delete {bptnum}')
|
||||
exec_convert_errors(f'breakpoint delete {bptnum}')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@ -615,8 +650,16 @@ def read_mem(process: sch.Schema('Process'), range: AddressRange):
|
||||
proc = find_proc_by_obj(process)
|
||||
offset_start = process.trace.memory_mapper.map_back(
|
||||
proc, Address(range.space, range.min))
|
||||
ci = util.get_debugger().GetCommandInterpreter()
|
||||
with commands.open_tracked_tx('Read Memory'):
|
||||
util.get_debugger().HandleCommand(f'ghidra_trace_putmem 0x{offset_start:x} {range.length()}')
|
||||
result = lldb.SBCommandReturnObject()
|
||||
ci.HandleCommand(
|
||||
f'ghidra trace putmem 0x{offset_start:x} {range.length()}', result)
|
||||
if result.Succeeded():
|
||||
return
|
||||
#print(f"Could not read 0x{offset_start:x}: {result}")
|
||||
exec_convert_errors(
|
||||
f'ghidra trace putmem-state 0x{offset_start:x} {range.length()} error')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@ -628,7 +671,7 @@ def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def write_reg(frame: sch.Schema('Frame'), name: str, value: bytes):
|
||||
def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
|
||||
"""Write a register."""
|
||||
f = find_frame_by_obj(frame)
|
||||
f.select()
|
||||
@ -637,4 +680,5 @@ def write_reg(frame: sch.Schema('Frame'), name: str, value: bytes):
|
||||
reg = find_reg_by_name(f, mname)
|
||||
size = int(lldb.parse_and_eval(f'sizeof(${mname})'))
|
||||
arr = '{' + ','.join(str(b) for b in mval) + '}'
|
||||
util.get_debugger().HandleCommand(f'expr ((unsigned char[{size}])${mname}) = {arr};')
|
||||
exec_convert_errors(
|
||||
f'expr ((unsigned char[{size}])${mname}) = {arr};')
|
||||
|
@ -1,46 +0,0 @@
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
import lldb
|
||||
|
||||
# TODO: I don't know how to register a custom parameter prefix. I would rather
|
||||
# these were 'ghidra language' and 'ghidra compiler'
|
||||
|
||||
|
||||
class GhidraLanguageParameter(lldb.Parameter):
|
||||
"""
|
||||
The language id for Ghidra traces. Set this to 'auto' to try to derive it
|
||||
from 'show arch' and 'show endian'. Otherwise, set it to a Ghidra
|
||||
LanguageID.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('ghidra-language', lldb.COMMAND_DATA, lldb.PARAM_STRING)
|
||||
self.value = 'auto'
|
||||
GhidraLanguageParameter()
|
||||
|
||||
|
||||
class GhidraCompilerParameter(lldb.Parameter):
|
||||
"""
|
||||
The compiler spec id for Ghidra traces. Set this to 'auto' to try to derive
|
||||
it from 'show osabi'. Otherwise, set it to a Ghidra CompilerSpecID. Note
|
||||
that valid compiler spec ids depend on the language id.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('ghidra-compiler', lldb.COMMAND_DATA, lldb.PARAM_STRING)
|
||||
self.value = 'auto'
|
||||
GhidraCompilerParameter()
|
||||
|
@ -1,5 +1,5 @@
|
||||
<context>
|
||||
<schema name="Session" elementResync="NEVER" attributeResync="NEVER">
|
||||
<schema name="LldbSession" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Access" />
|
||||
<interface name="Attacher" />
|
||||
<interface name="Interpreter" />
|
||||
@ -14,82 +14,38 @@
|
||||
<attribute name="Available" schema="AvailableContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Watchpoints" schema="WatchpointContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_accessible" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_attach_kinds" schema="SET_ATTACH_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="_prompt" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
|
||||
<attribute name="_event_thread" schema="OBJECT" hidden="yes" />
|
||||
<attribute name="_focus" schema="Selectable" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Selectable" elementResync="NEVER" attributeResync="NEVER">
|
||||
<element schema="OBJECT" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointSpecContainer" />
|
||||
<element schema="BreakpointSpec" />
|
||||
<attribute name="_supported_breakpoint_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="WatchpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="WatchpointSpecContainer" />
|
||||
<element schema="WatchpointSpec" />
|
||||
<attribute name="_supported_breakpoint_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="AvailableContainer" canonical="yes" elementResync="ALWAYS" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Attachable" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_base" schema="INT" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="ProcessContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Process" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_base" schema="INT" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointSpec" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
@ -97,17 +53,14 @@
|
||||
<interface name="Deletable" />
|
||||
<interface name="Togglable" />
|
||||
<element schema="BreakpointLocation" />
|
||||
<attribute name="_container" schema="BreakpointContainer" required="yes" hidden="yes" />
|
||||
<attribute name="_expression" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute name="_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Expression" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_expression" to="Expression" />
|
||||
<attribute name="Kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_kinds" to="Kinds" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="Enabled" schema="BOOL" required="yes" />
|
||||
<attribute-alias from="_enabled" to="Enabled" />
|
||||
<attribute name="Commands" schema="STRING" />
|
||||
<attribute name="Condition" schema="STRING" />
|
||||
<attribute name="Hit Count" schema="INT" />
|
||||
@ -121,18 +74,16 @@
|
||||
<interface name="BreakpointSpec" />
|
||||
<interface name="Deletable" />
|
||||
<interface name="Togglable" />
|
||||
<attribute name="_container" schema="WatchpointContainer" required="yes" hidden="yes" />
|
||||
<attribute name="_expression" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute name="_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Expression" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_expression" to="Expression" />
|
||||
<attribute name="Kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_kinds" to="Kinds" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" hidden="yes" />
|
||||
<attribute name="Enabled" schema="BOOL" required="yes" />
|
||||
<attribute-alias from="_enabled" to="Enabled" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="Condition" schema="STRING" />
|
||||
<attribute name="Hit Count" schema="INT" />
|
||||
<attribute name="Ignore Count" schema="INT" />
|
||||
@ -141,17 +92,14 @@
|
||||
<schema name="Attachable" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Attachable" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_pid" schema="LONG" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="PID" schema="LONG" />
|
||||
<attribute-alias from="_pid" to="PID" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Process" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="Process" />
|
||||
<interface name="Aggregate" />
|
||||
<interface name="ExecutionStateful" />
|
||||
@ -167,105 +115,68 @@
|
||||
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Breakpoints" schema="BreakpointLocationContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Watchpoints" schema="WatchpointContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_exit_code" schema="LONG" />
|
||||
<attribute name="Exit Code" schema="LONG" />
|
||||
<attribute-alias from="_exit_code" to="Exit Code" />
|
||||
<attribute name="Environment" schema="Environment" required="yes" fixed="yes" />
|
||||
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
|
||||
<attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_pid" schema="LONG" hidden="yes" />
|
||||
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_attach_kinds" schema="SET_ATTACH_KIND" required="yes" hidden="yes" />
|
||||
<attribute name="PID" schema="LONG" hidden="yes" />
|
||||
<attribute-alias from="_pid" to="PID" />
|
||||
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_state" to="State" />
|
||||
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Environment" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Environment" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="arch" schema="STRING" />
|
||||
<attribute name="os" schema="STRING" />
|
||||
<attribute name="endian" schema="STRING" />
|
||||
<attribute name="_arch" schema="STRING" hidden="yes" />
|
||||
<attribute name="_debugger" schema="STRING" hidden="yes" />
|
||||
<attribute name="_os" schema="STRING" hidden="yes" />
|
||||
<attribute name="_endian" schema="STRING" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="OS" schema="STRING" />
|
||||
<attribute name="Arch" schema="STRING" />
|
||||
<attribute name="Endian" schema="STRING" />
|
||||
<attribute name="Debugger" schema="STRING" />
|
||||
<attribute-alias from="_os" to="OS" />
|
||||
<attribute-alias from="_arch" to="Arch" />
|
||||
<attribute-alias from="_endian" to="Endian" />
|
||||
<attribute-alias from="_debugger" to="Debugger" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="ModuleContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="ModuleContainer" />
|
||||
<element schema="Module" />
|
||||
<attribute name="_supports_synthetic_modules" schema="BOOL" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Memory" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Memory" />
|
||||
<element schema="MemoryRegion" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointLocation" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointLocation" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_range" schema="RANGE" hidden="yes" />
|
||||
<attribute name="_spec" schema="BreakpointSpec" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="Enabled" schema="BOOL" required="yes" />
|
||||
<attribute-alias from="_enabled" to="Enabled" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointLocationContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointLocationContainer" />
|
||||
<element schema="BreakpointLocation" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="ThreadContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Thread" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_base" schema="INT" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Method" elementResync="NEVER" attributeResync="NEVER">
|
||||
@ -277,22 +188,21 @@
|
||||
<attribute schema="VOID" fixed="yes" hidden="yes" />
|
||||
</schema>
|
||||
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="Thread" />
|
||||
<interface name="ExecutionStateful" />
|
||||
<interface name="Steppable" />
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Stack" schema="Stack" required="yes" fixed="yes" />
|
||||
<attribute name="_tid" schema="LONG" hidden="yes" />
|
||||
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="TID" schema="LONG" />
|
||||
<attribute-alias from="_tid" to="TID" />
|
||||
<attribute name="Name" schema="STRING" />
|
||||
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_state" to="State" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="Advance" schema="Method" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
@ -301,165 +211,95 @@
|
||||
<element schema="VOID" />
|
||||
<attribute name="Sections" schema="SectionContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Symbols" schema="SymbolContainer" required="yes" fixed="yes" />
|
||||
<attribute name="range" schema="RANGE" />
|
||||
<attribute name="module name" schema="STRING" />
|
||||
<attribute name="_module_name" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute name="Name" schema="STRING" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute-alias from="_module_name" to="Name" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="MemoryRegion" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="MemoryRegion" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_offset" schema="LONG" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_objfile" schema="STRING" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="Offset" schema="STRING" fixed="yes" />
|
||||
<attribute name="Object File" schema="STRING" fixed="yes" />
|
||||
<attribute name="_readable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_writable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_executable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
|
||||
<attribute name="_memory" schema="Memory" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" required="yes" hidden="yes" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="SectionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="SectionContainer" />
|
||||
<element schema="Section" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Stack" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Stack" />
|
||||
<element schema="StackFrame" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="SymbolContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="SymbolNamespace" />
|
||||
<element schema="Symbol" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Symbol" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Symbol" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_size" schema="LONG" fixed="yes" hidden="yes" />
|
||||
<attribute name="_namespace" schema="SymbolContainer" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_data_type" schema="DATA_TYPE" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ADDRESS" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_bpt" schema="STRING" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Activatable" />
|
||||
<interface name="StackFrame" />
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_function" schema="STRING" hidden="yes" />
|
||||
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_pc" schema="ADDRESS" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="PC" schema="ADDRESS" required="yes" />
|
||||
<attribute-alias from="_pc" to="PC" />
|
||||
<attribute name="Function" schema="STRING" hidden="yes" />
|
||||
<attribute-alias from="_function" to="Function" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Section" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Section" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="range" schema="RANGE" />
|
||||
<attribute name="_module" schema="Module" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" required="yes" fixed="yes" />
|
||||
<attribute name="_offset" schema="INT" required="no" fixed="yes" />
|
||||
<attribute name="_objfile" schema="STRING" required="no" fixed="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="Range" schema="RANGE" />
|
||||
<attribute name="Module" schema="Module" fixed="yes" />
|
||||
<attribute-alias from="_range" to="Range" />
|
||||
<attribute name="Offset" schema="STRING" fixed="yes" />
|
||||
<attribute-alias from="_offset" to="Offset" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterValueContainer" canonical="yes" elementResync="ONCE" attributeResync="ONCE">
|
||||
<schema name="RegisterValueContainer" attributeResync="ONCE">
|
||||
<interface name="RegisterContainer" />
|
||||
<interface name="RegisterBank" />
|
||||
<element schema="RegisterValue" />
|
||||
<attribute name="General Purpose Registers" schema="RegisterBank" />
|
||||
<attribute name="Floating Point Registers" schema="RegisterBank" />
|
||||
<attribute name="Advanced Vector Extensions" schema="RegisterBank" />
|
||||
<attribute name="Memory Protection Extensions" schema="RegisterBank" />
|
||||
<attribute name="_descriptions" schema="RegisterValueContainer" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="float" schema="RegisterBank" />
|
||||
<attribute name="general" schema="RegisterBank" />
|
||||
<attribute name="system" schema="RegisterBank" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="RegisterBank" />
|
||||
<element schema="RegisterValue" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterValue" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Register" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_container" schema="OBJECT" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_length" schema="INT" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
</context>
|
@ -21,21 +21,25 @@ import sys
|
||||
import lldb
|
||||
|
||||
|
||||
LldbVersion = namedtuple('LldbVersion', ['full', 'major', 'minor'])
|
||||
LldbVersion = namedtuple('LldbVersion', ['display', 'full', 'major', 'minor'])
|
||||
|
||||
|
||||
def _compute_lldb_ver():
|
||||
blurb = lldb.debugger.GetVersionString()
|
||||
top = blurb.split('\n')[0]
|
||||
full = top.split(' ')[2]
|
||||
if ' version ' in top:
|
||||
full = top.split(' ')[2] # "lldb version x.y.z"
|
||||
else:
|
||||
full = top.split('-')[1] # "lldb-x.y.z"
|
||||
major, minor = full.split('.')[:2]
|
||||
return LldbVersion(full, int(major), int(minor))
|
||||
return LldbVersion(top, full, int(major), int(minor))
|
||||
|
||||
|
||||
LLDB_VERSION = _compute_lldb_ver()
|
||||
|
||||
GNU_DEBUGDATA_PREFIX = ".gnu_debugdata for "
|
||||
|
||||
|
||||
class Module(namedtuple('BaseModule', ['name', 'base', 'max', 'sections'])):
|
||||
pass
|
||||
|
||||
@ -70,7 +74,7 @@ class ModuleInfoReader(object):
|
||||
name = s.GetName()
|
||||
attrs = s.GetPermissions()
|
||||
return Section(name, start, end, offset, attrs)
|
||||
|
||||
|
||||
def finish_module(self, name, sections):
|
||||
alloc = {k: s for k, s in sections.items()}
|
||||
if len(alloc) == 0:
|
||||
@ -96,7 +100,7 @@ class ModuleInfoReader(object):
|
||||
fspec = module.GetFileSpec()
|
||||
name = debracket(fspec.GetFilename())
|
||||
sections = {}
|
||||
for i in range(0, module.GetNumSections()):
|
||||
for i in range(0, module.GetNumSections()):
|
||||
s = self.section_from_sbsection(module.GetSectionAtIndex(i))
|
||||
sname = debracket(s.name)
|
||||
sections[sname] = s
|
||||
@ -107,8 +111,8 @@ class ModuleInfoReader(object):
|
||||
def _choose_module_info_reader():
|
||||
return ModuleInfoReader()
|
||||
|
||||
MODULE_INFO_READER = _choose_module_info_reader()
|
||||
|
||||
MODULE_INFO_READER = _choose_module_info_reader()
|
||||
|
||||
|
||||
class Region(namedtuple('BaseRegion', ['start', 'end', 'offset', 'perms', 'objfile'])):
|
||||
@ -137,8 +141,8 @@ class RegionInfoReader(object):
|
||||
reglist = get_process().GetMemoryRegions()
|
||||
for i in range(0, reglist.GetSize()):
|
||||
module = get_target().GetModuleAtIndex(i)
|
||||
info = lldb.SBMemoryRegionInfo();
|
||||
success = reglist.GetMemoryRegionAtIndex(i, info);
|
||||
info = lldb.SBMemoryRegionInfo()
|
||||
success = reglist.GetMemoryRegionAtIndex(i, info)
|
||||
if success:
|
||||
r = self.region_from_sbmemreg(info)
|
||||
regions.append(r)
|
||||
@ -146,8 +150,11 @@ class RegionInfoReader(object):
|
||||
|
||||
def full_mem(self):
|
||||
# TODO: This may not work for Harvard architectures
|
||||
sizeptr = int(parse_and_eval('sizeof(void*)')) * 8
|
||||
return Region(0, 1 << sizeptr, 0, None, 'full memory')
|
||||
try:
|
||||
sizeptr = int(parse_and_eval('sizeof(void*)')) * 8
|
||||
return Region(0, 1 << sizeptr, 0, None, 'full memory')
|
||||
except ValueError:
|
||||
return Region(0, 1 << 64, 0, None, 'full memory')
|
||||
|
||||
|
||||
def _choose_region_info_reader():
|
||||
@ -177,28 +184,39 @@ def _choose_breakpoint_location_info_reader():
|
||||
|
||||
BREAKPOINT_LOCATION_INFO_READER = _choose_breakpoint_location_info_reader()
|
||||
|
||||
|
||||
def get_debugger():
|
||||
return lldb.SBDebugger.FindDebuggerWithID(1)
|
||||
|
||||
|
||||
def get_target():
|
||||
return get_debugger().GetTargetAtIndex(0)
|
||||
|
||||
|
||||
def get_process():
|
||||
return get_target().GetProcess()
|
||||
|
||||
|
||||
|
||||
def selected_thread():
|
||||
return get_process().GetSelectedThread()
|
||||
|
||||
|
||||
def selected_frame():
|
||||
return selected_thread().GetSelectedFrame()
|
||||
|
||||
|
||||
|
||||
def parse_and_eval(expr, signed=False):
|
||||
if signed is True:
|
||||
return get_target().EvaluateExpression(expr).GetValueAsSigned()
|
||||
return get_target().EvaluateExpression(expr).GetValueAsUnsigned()
|
||||
return get_eval(expr).GetValueAsSigned()
|
||||
return get_eval(expr).GetValueAsUnsigned()
|
||||
|
||||
|
||||
def get_eval(expr):
|
||||
return get_target().EvaluateExpression(expr)
|
||||
eval = get_target().EvaluateExpression(expr)
|
||||
if eval.GetError().Fail():
|
||||
raise ValueError(eval.GetError().GetCString())
|
||||
return eval
|
||||
|
||||
|
||||
def get_description(object, level=None):
|
||||
stream = lldb.SBStream()
|
||||
@ -208,8 +226,10 @@ def get_description(object, level=None):
|
||||
object.GetDescription(stream, level)
|
||||
return escape_ansi(stream.GetData())
|
||||
|
||||
|
||||
conv_map = {}
|
||||
|
||||
|
||||
def get_convenience_variable(id):
|
||||
#val = get_target().GetEnvironment().Get(id)
|
||||
if id not in conv_map:
|
||||
@ -219,18 +239,20 @@ def get_convenience_variable(id):
|
||||
return "auto"
|
||||
return val
|
||||
|
||||
|
||||
def set_convenience_variable(id, value):
|
||||
#env = get_target().GetEnvironment()
|
||||
#return env.Set(id, value, True)
|
||||
# return env.Set(id, value, True)
|
||||
conv_map[id] = value
|
||||
|
||||
|
||||
|
||||
def escape_ansi(line):
|
||||
ansi_escape =re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
|
||||
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
|
||||
return ansi_escape.sub('', line)
|
||||
|
||||
|
||||
|
||||
def debracket(init):
|
||||
val = init
|
||||
val = val.replace("[","(")
|
||||
val = val.replace("]",")")
|
||||
val = val.replace("[", "(")
|
||||
val = val.replace("]", ")")
|
||||
return val
|
||||
|
@ -35,6 +35,12 @@ public enum MacOSSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUtil
|
||||
return DummyProc.which("expSpin");
|
||||
}
|
||||
},
|
||||
READ {
|
||||
@Override
|
||||
public String getCommandLine() {
|
||||
return DummyProc.which("expRead");
|
||||
}
|
||||
},
|
||||
FORK_EXIT {
|
||||
@Override
|
||||
public String getCommandLine() {
|
||||
|
@ -31,26 +31,51 @@ public interface DebuggerConsoleService {
|
||||
* Log a message to the console
|
||||
*
|
||||
* <p>
|
||||
* <b>WARNING:</b> See {@link #log(Icon, String, ActionContext)} regarding HTML.
|
||||
* <b>WARNING:</b> See {@link #log(Icon, String, Throwable, ActionContext)} regarding HTML.
|
||||
*
|
||||
* @param icon an icon for the message
|
||||
* @param message the HTML-formatted message
|
||||
*/
|
||||
void log(Icon icon, String message);
|
||||
|
||||
/**
|
||||
* Log an error message to the console
|
||||
*
|
||||
* <p>
|
||||
* <b>WARNING:</b> See {@link #log(Icon, String, Throwable, ActionContext)} regarding HTML.
|
||||
*
|
||||
* @param icon an icon for the message
|
||||
* @param message the HTML-formatted message
|
||||
* @param error an exception, if applicable
|
||||
*/
|
||||
void log(Icon icon, String message, Throwable error);
|
||||
|
||||
/**
|
||||
* Log an actionable message to the console
|
||||
*
|
||||
* <p>
|
||||
* <b>WARNING:</b> See {@link #log(Icon, String, Throwable, ActionContext)} regarding HTML.
|
||||
*
|
||||
* @param icon an icon for the message
|
||||
* @param message the HTML-formatted message
|
||||
* @param context an (immutable) context for actions
|
||||
*/
|
||||
void log(Icon icon, String message, ActionContext context);
|
||||
|
||||
/**
|
||||
* Log an actionable error message to the console
|
||||
*
|
||||
* <p>
|
||||
* <b>WARNING:</b> The log accepts and will interpret HTML in its messages, allowing a rich and
|
||||
* flexible display; however, you MUST sanitize any content derived from the user or target. We
|
||||
* recommend using {@link HTMLUtilities#escapeHTML(String)}.
|
||||
*
|
||||
* @param icon an icon for the message
|
||||
* @param message the HTML-formatted message
|
||||
* @param error an exception, if applicable
|
||||
* @param context an (immutable) context for actions
|
||||
*/
|
||||
void log(Icon icon, String message, ActionContext context);
|
||||
void log(Icon icon, String message, Throwable error, ActionContext context);
|
||||
|
||||
/**
|
||||
* Remove an actionable message from the console
|
||||
|
@ -250,6 +250,18 @@ public interface DebuggerTraceManagerService {
|
||||
*/
|
||||
void closeTrace(Trace trace);
|
||||
|
||||
/**
|
||||
* Close the given trace without confirmation
|
||||
*
|
||||
* <p>
|
||||
* Ordinarily, {@link #closeTrace(Trace)} will prompt the user to confirm termination of live
|
||||
* targets associated with traces to be closed. Such prompts can cause issues during automated
|
||||
* tests.
|
||||
*
|
||||
* @param trace the trace to close
|
||||
*/
|
||||
void closeTraceNoConfirm(Trace trace);
|
||||
|
||||
/**
|
||||
* Close all traces
|
||||
*/
|
||||
@ -303,7 +315,7 @@ public interface DebuggerTraceManagerService {
|
||||
*
|
||||
* <p>
|
||||
* If asynchronous notification is needed, use
|
||||
* {@link #activateAndNotify(DebuggerCoordinates, boolean)}.
|
||||
* {@link #activateAndNotify(DebuggerCoordinates, ActivationCause)}.
|
||||
*
|
||||
* @param coordinates the desired coordinates
|
||||
* @param cause the cause of activation
|
||||
@ -458,7 +470,7 @@ public interface DebuggerTraceManagerService {
|
||||
DebuggerCoordinates resolvePath(TraceObjectKeyPath path);
|
||||
|
||||
/**
|
||||
* Activate the given object path
|
||||
* Activate the given canonical object path
|
||||
*
|
||||
* @param path the desired path
|
||||
*/
|
||||
@ -484,34 +496,6 @@ public interface DebuggerTraceManagerService {
|
||||
activate(resolveObject(object));
|
||||
}
|
||||
|
||||
/**
|
||||
* Control whether trace activation is synchronized with debugger activation
|
||||
*
|
||||
* @param enabled true to synchronize, false otherwise
|
||||
*/
|
||||
void setSynchronizeActive(boolean enabled);
|
||||
|
||||
/**
|
||||
* Check whether trace activation is synchronized with debugger activation
|
||||
*
|
||||
* @return true if synchronized, false otherwise
|
||||
*/
|
||||
boolean isSynchronizeActive();
|
||||
|
||||
/**
|
||||
* Add a listener for changes to activation synchronization enablement
|
||||
*
|
||||
* @param listener the listener to receive change notifications
|
||||
*/
|
||||
void addSynchronizeActiveChangeListener(BooleanChangeAdapter listener);
|
||||
|
||||
/**
|
||||
* Remove a listener for changes to activation synchronization enablement
|
||||
*
|
||||
* @param listener the listener receiving change notifications
|
||||
*/
|
||||
void removeSynchronizeActiveChangeListener(BooleanChangeAdapter listener);
|
||||
|
||||
/**
|
||||
* Control whether traces should be saved by default
|
||||
*
|
||||
|
@ -16,12 +16,16 @@
|
||||
package ghidra.app.services;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import ghidra.debug.api.progress.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
@ -99,4 +103,57 @@ public interface ProgressService {
|
||||
* @param listener the listener
|
||||
*/
|
||||
void removeProgressListener(ProgressListener listener);
|
||||
|
||||
/**
|
||||
* A drop-in replacement for {@link PluginTool#execute(Task)} that publishes progress via the
|
||||
* service rather than displaying a dialog.
|
||||
*
|
||||
* <p>
|
||||
* In addition to changing how progress is displayed, this also returns a future so that task
|
||||
* completion can be detected by the caller.
|
||||
*
|
||||
* @param task task to run in a new thread
|
||||
* @return a future which completes when the task is finished
|
||||
*/
|
||||
default CompletableFuture<Void> execute(Task task) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try (CloseableTaskMonitor monitor = publishTask()) {
|
||||
monitor.setCancelEnabled(task.canCancel());
|
||||
try {
|
||||
task.run(monitor);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
throw new CancellationException("User cancelled");
|
||||
}
|
||||
catch (Throwable e) {
|
||||
monitor.reportError(e);
|
||||
return ExceptionUtils.rethrow(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #execute(Task)}, but for asynchronous methods
|
||||
*
|
||||
* @param <T> the type of future result
|
||||
* @param canCancel true if the task can be cancelled
|
||||
* @param hasProgress true if the task displays progress
|
||||
* @param isModal true if the task is modal (ignored)
|
||||
* @param futureSupplier the task which returns a future, given the task monitor
|
||||
* @return the future returned by the supplier
|
||||
*/
|
||||
default <T> CompletableFuture<T> execute(boolean canCancel, boolean hasProgress,
|
||||
boolean isModal, Function<TaskMonitor, CompletableFuture<T>> futureSupplier) {
|
||||
CloseableTaskMonitor monitor = publishTask();
|
||||
monitor.setCancelEnabled(canCancel);
|
||||
CompletableFuture<T> future = futureSupplier.apply(monitor);
|
||||
future.handle((t, ex) -> {
|
||||
monitor.close();
|
||||
return null;
|
||||
});
|
||||
monitor.addCancelledListener(() -> future.cancel(true));
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package ghidra.app.services;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
@ -35,4 +36,12 @@ public interface TraceRmiLauncherService {
|
||||
* @return the offers
|
||||
*/
|
||||
Collection<TraceRmiLaunchOffer> getOffers(Program program);
|
||||
|
||||
/**
|
||||
* Get offers with a saved configuration, ordered by most-recently-saved
|
||||
*
|
||||
* @param program the program
|
||||
* @return the offers
|
||||
*/
|
||||
List<TraceRmiLaunchOffer> getSavedOffers(Program program);
|
||||
}
|
||||
|
@ -15,10 +15,8 @@
|
||||
*/
|
||||
package ghidra.debug.api.action;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
@ -47,22 +45,21 @@ public interface LocationTracker {
|
||||
* The address returned must be in the host platform's language, i.e., please use
|
||||
* {@link TracePlatform#mapGuestToHost(Address)}.
|
||||
*
|
||||
* @param tool the tool containing the provider
|
||||
* @param provider the service provider (usually the tool)
|
||||
* @param coordinates the trace, thread, snap, etc., of the tool
|
||||
* @return the address to navigate to
|
||||
*/
|
||||
CompletableFuture<Address> computeTraceAddress(PluginTool tool,
|
||||
DebuggerCoordinates coordinates);
|
||||
Address computeTraceAddress(ServiceProvider provider, DebuggerCoordinates coordinates);
|
||||
|
||||
/**
|
||||
* Get the suggested input if the user activates "Go To" while this tracker is active
|
||||
*
|
||||
* @param tool the tool containing the provider
|
||||
* @param provider the service provider (usually the tool)
|
||||
* @param coordinates the user's current coordinates
|
||||
* @param location the user's current location
|
||||
* @return the suggested address or Sleigh expression
|
||||
*/
|
||||
GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates,
|
||||
GoToInput getDefaultGoToInput(ServiceProvider provider, DebuggerCoordinates coordinates,
|
||||
ProgramLocation location);
|
||||
|
||||
// TODO: Is there a way to generalize these so that other dependencies need not
|
||||
|
@ -119,7 +119,7 @@ public interface LocationTrackingSpec {
|
||||
/**
|
||||
* Compute a title prefix to indicate this tracking specification
|
||||
*
|
||||
* @param thread the provider's current thread
|
||||
* @param coordinates the current coordinates
|
||||
* @return a prefix, or {@code null} to use a default
|
||||
*/
|
||||
String computeTitle(DebuggerCoordinates coordinates);
|
||||
|
@ -90,14 +90,6 @@ public enum ControlMode {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControlMode modeOnChange(DebuggerCoordinates coordinates) {
|
||||
if (coordinates.isAliveAndPresent()) {
|
||||
return this;
|
||||
}
|
||||
return getAlternative(coordinates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelectable(DebuggerCoordinates coordinates) {
|
||||
return coordinates.isAlive();
|
||||
@ -105,7 +97,7 @@ public enum ControlMode {
|
||||
|
||||
@Override
|
||||
public ControlMode getAlternative(DebuggerCoordinates coordinates) {
|
||||
return RO_TRACE;
|
||||
return RW_EMULATOR;
|
||||
}
|
||||
},
|
||||
/**
|
||||
@ -159,14 +151,6 @@ public enum ControlMode {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControlMode modeOnChange(DebuggerCoordinates coordinates) {
|
||||
if (coordinates.isAliveAndPresent()) {
|
||||
return this;
|
||||
}
|
||||
return getAlternative(coordinates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelectable(DebuggerCoordinates coordinates) {
|
||||
return coordinates.isAlive();
|
||||
@ -391,6 +375,39 @@ public enum ControlMode {
|
||||
*/
|
||||
public abstract boolean followsPresent();
|
||||
|
||||
/**
|
||||
* Validate and/or adjust the given coordinates pre-activation
|
||||
*
|
||||
* <p>
|
||||
* This is called by the trace manager whenever there is a request to activate new coordinates.
|
||||
* The control mode may adjust or reject the request before the trace manager actually performs
|
||||
* and notifies the activation.
|
||||
*
|
||||
* @param tool the tool for displaying status messages
|
||||
* @param coordinates the requested coordinates
|
||||
* @param cause the cause of the activation
|
||||
* @return the effective coordinates or null to reject
|
||||
*/
|
||||
public DebuggerCoordinates validateCoordinates(PluginTool tool,
|
||||
DebuggerCoordinates coordinates, ActivationCause cause) {
|
||||
if (!followsPresent()) {
|
||||
return coordinates;
|
||||
}
|
||||
Target target = coordinates.getTarget();
|
||||
if (target == null) {
|
||||
return coordinates;
|
||||
}
|
||||
if (cause == ActivationCause.USER &&
|
||||
(!coordinates.getTime().isSnapOnly() || coordinates.getSnap() != target.getSnap())) {
|
||||
tool.setStatusInfo(
|
||||
"Cannot navigate time in %s mode. Switch to Trace or Emulate mode first."
|
||||
.formatted(name),
|
||||
true);
|
||||
return null;
|
||||
}
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if (broadly speaking) the mode supports editing the given coordinates
|
||||
*
|
||||
|
@ -24,7 +24,7 @@ import ghidra.trace.model.thread.TraceThread;
|
||||
*
|
||||
* <p>
|
||||
* In addition to the trace "coordinates" encapsulated by {@link PcodeTraceAccess}, this
|
||||
* encapsulates the tool controlling a session and the session's trace recorder. This permits p-code
|
||||
* encapsulates the tool controlling a session and the session's target. This permits p-code
|
||||
* executor/emulator states to access target data and to access session data, e.g., data from mapped
|
||||
* static images. It supports the same method chain pattern as {@link PcodeTraceAccess}, but
|
||||
* starting with {@link DefaultPcodeDebuggerAccess}.
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.model;
|
||||
package ghidra.debug.api.model;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.Collection;
|
@ -0,0 +1,34 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.debug.api.model;
|
||||
|
||||
import docking.DefaultActionContext;
|
||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||
|
||||
/**
|
||||
* Really just used by scripts to get a path into an action context
|
||||
*/
|
||||
public class DebuggerSingleObjectPathActionContext extends DefaultActionContext {
|
||||
private final TraceObjectKeyPath path;
|
||||
|
||||
public DebuggerSingleObjectPathActionContext(TraceObjectKeyPath path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public TraceObjectKeyPath getPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.modules;
|
||||
package ghidra.debug.api.modules;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@ -43,10 +43,9 @@ public class DebuggerMissingModuleActionContext extends DefaultActionContext {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof DebuggerMissingModuleActionContext)) {
|
||||
if (!(obj instanceof DebuggerMissingModuleActionContext that)) {
|
||||
return false;
|
||||
}
|
||||
DebuggerMissingModuleActionContext that = (DebuggerMissingModuleActionContext) obj;
|
||||
if (!this.module.equals(that.module)) {
|
||||
return false;
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/* ###
|
||||
* 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.debug.api.modules;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import docking.DefaultActionContext;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.InstructionIterator;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
public class DebuggerMissingProgramActionContext extends DefaultActionContext {
|
||||
|
||||
public static Address getMappingProbeAddress(Program program) {
|
||||
if (program == null) {
|
||||
return null;
|
||||
}
|
||||
AddressIterator eepi = program.getSymbolTable().getExternalEntryPointIterator();
|
||||
if (eepi.hasNext()) {
|
||||
return eepi.next();
|
||||
}
|
||||
InstructionIterator ii = program.getListing().getInstructions(true);
|
||||
if (ii.hasNext()) {
|
||||
return ii.next().getAddress();
|
||||
}
|
||||
AddressSetView es = program.getMemory().getExecuteSet();
|
||||
if (!es.isEmpty()) {
|
||||
return es.getMinAddress();
|
||||
}
|
||||
if (!program.getMemory().isEmpty()) {
|
||||
return program.getMinAddress();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final Trace trace;
|
||||
private final Program program;
|
||||
private final int hashCode;
|
||||
|
||||
private Address probe;
|
||||
|
||||
public DebuggerMissingProgramActionContext(Trace trace, Program program) {
|
||||
this.trace = Objects.requireNonNull(trace);
|
||||
this.program = Objects.requireNonNull(program);
|
||||
this.hashCode = Objects.hash(getClass(), trace, program);
|
||||
}
|
||||
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
public Program getProgram() {
|
||||
return program;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof DebuggerMissingProgramActionContext that)) {
|
||||
return false;
|
||||
}
|
||||
if (!this.trace.equals(that.trace)) {
|
||||
return false;
|
||||
}
|
||||
if (!this.program.equals(that.program)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Address getMappingProbeAddress() {
|
||||
if (probe == null) {
|
||||
probe = getMappingProbeAddress(program);
|
||||
}
|
||||
return probe;
|
||||
}
|
||||
}
|
@ -17,7 +17,17 @@ package ghidra.debug.api.progress;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A task monitor that can be used in a try-with-resources block.
|
||||
*/
|
||||
public interface CloseableTaskMonitor extends TaskMonitor, AutoCloseable {
|
||||
@Override
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Report an error while working on this task
|
||||
*
|
||||
* @param error the error
|
||||
*/
|
||||
void reportError(Throwable error);
|
||||
}
|
||||
|
@ -15,7 +15,13 @@
|
||||
*/
|
||||
package ghidra.debug.api.progress;
|
||||
|
||||
/**
|
||||
* A listener for events on the progress service, including updates to task progress
|
||||
*/
|
||||
public interface ProgressListener {
|
||||
/**
|
||||
* Describes how or why a task monitor was disposed
|
||||
*/
|
||||
enum Disposal {
|
||||
/**
|
||||
* The monitor was properly closed
|
||||
@ -27,12 +33,52 @@ public interface ProgressListener {
|
||||
CLEANED;
|
||||
}
|
||||
|
||||
/**
|
||||
* A new task monitor has been created
|
||||
*
|
||||
* <p>
|
||||
* The subscriber ought to display the monitor as soon as is reasonable. Optionally, a
|
||||
* subscriber may apply a grace period, e.g., half a second, before displaying it, in case it is
|
||||
* quickly disposed.
|
||||
*
|
||||
* @param monitor a means of retrieving messages and progress about the task
|
||||
*/
|
||||
void monitorCreated(MonitorReceiver monitor);
|
||||
|
||||
/**
|
||||
* A task monitor has been disposed
|
||||
*
|
||||
* @param monitor the receiver for the disposed monitor
|
||||
* @param disposal why it was disposed
|
||||
*/
|
||||
void monitorDisposed(MonitorReceiver monitor, Disposal disposal);
|
||||
|
||||
/**
|
||||
* A task has updated a monitor's message
|
||||
*
|
||||
* @param monitor the receiver whose monitor's message changed
|
||||
* @param message the new message
|
||||
*/
|
||||
void messageUpdated(MonitorReceiver monitor, String message);
|
||||
|
||||
/**
|
||||
* A task has reported an error
|
||||
*
|
||||
* @param monitor the receiver for the task reporting the error
|
||||
* @param error the exception representing the error
|
||||
*/
|
||||
void errorReported(MonitorReceiver monitor, Throwable error);
|
||||
|
||||
/**
|
||||
* A task's progress has updated
|
||||
*
|
||||
* <p>
|
||||
* Note the subscriber may need to use {@link MonitorReceiver#getMaximum()} to properly update
|
||||
* the display.
|
||||
*
|
||||
* @param monitor the receiver whose monitor's progress changed
|
||||
* @param progress the new progress value
|
||||
*/
|
||||
void progressUpdated(MonitorReceiver monitor, long progress);
|
||||
|
||||
/**
|
||||
@ -46,7 +92,7 @@ public interface ProgressListener {
|
||||
* <li>show progress value in percent string</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param monitor the monitor
|
||||
* @param monitor the receiver whose monitor's attribute(s) changed
|
||||
*/
|
||||
void attributeUpdated(MonitorReceiver monitor);
|
||||
}
|
||||
|
@ -33,6 +33,10 @@ import java.util.Map;
|
||||
* list may change over time, but that shouldn't matter much. Each back-end should make its best
|
||||
* effort to match its methods to these stock actions where applicable, but ultimately, it is up to
|
||||
* the UI to decide what is presented where.
|
||||
*
|
||||
* @param name the name of the action (given as the action attribute on method annotations)
|
||||
* @param builtIn true if the action should <em>not</em> be presented in generic contexts, but
|
||||
* reserved for built-in, purpose-specific actions
|
||||
*/
|
||||
public record ActionName(String name, boolean builtIn) {
|
||||
private static final Map<String, ActionName> NAMES = new HashMap<>();
|
||||
@ -63,7 +67,7 @@ public record ActionName(String name, boolean builtIn) {
|
||||
}
|
||||
}
|
||||
|
||||
public static final ActionName REFRESH = builtIn("refresh");
|
||||
public static final ActionName REFRESH = extended("refresh");
|
||||
/**
|
||||
* Activate a given object and optionally a time
|
||||
*
|
||||
|
@ -63,11 +63,14 @@ public interface Target {
|
||||
* @param name the name of a common debugger command this action implements
|
||||
* @param details text providing more details, usually displayed in a tool tip
|
||||
* @param requiresPrompt true if invoking the action requires further user interaction
|
||||
* @param specificity a relative score of specificity. These are only meaningful when compared
|
||||
* among entries returned in the same collection.
|
||||
* @param enabled a supplier to determine whether an associated action in the UI is enabled.
|
||||
* @param action a function for invoking this action asynchronously
|
||||
*/
|
||||
record ActionEntry(String display, ActionName name, String details, boolean requiresPrompt,
|
||||
BooleanSupplier enabled, Function<Boolean, CompletableFuture<?>> action) {
|
||||
long specificity, BooleanSupplier enabled,
|
||||
Function<Boolean, CompletableFuture<?>> action) {
|
||||
|
||||
/**
|
||||
* Check if this action is currently enabled
|
||||
@ -132,6 +135,13 @@ public interface Target {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the target for display in the UI
|
||||
*
|
||||
* @return the description
|
||||
*/
|
||||
String describe();
|
||||
|
||||
/**
|
||||
* Check if the target is still valid
|
||||
*
|
||||
@ -166,6 +176,20 @@ public interface Target {
|
||||
*/
|
||||
Map<String, ActionEntry> collectActions(ActionName name, ActionContext context);
|
||||
|
||||
/**
|
||||
* @see #execute(String, boolean)
|
||||
*/
|
||||
CompletableFuture<String> executeAsync(String command, boolean toString);
|
||||
|
||||
/**
|
||||
* Execute a command as if in the CLI
|
||||
*
|
||||
* @param command the command
|
||||
* @param toString true to capture the output and return it, false to print to the terminal
|
||||
* @return the captured output, or null if {@code toString} is false
|
||||
*/
|
||||
String execute(String command, boolean toString);
|
||||
|
||||
/**
|
||||
* Get the trace thread that contains the given object
|
||||
*
|
||||
|
@ -45,6 +45,16 @@ import ghidra.util.NotOwnerException;
|
||||
|
||||
public class DebuggerCoordinates {
|
||||
|
||||
/**
|
||||
* Coordinates that indicate no trace is active in the Debugger UI.
|
||||
*
|
||||
* <p>
|
||||
* Typically, that only happens when no trace is open. Telling the trace manager to activate
|
||||
* {@code NOWHERE} will cause it to instead activate the most recently active trace, which may
|
||||
* very well be the current trace, resulting in no change. Internally, the trace manager will
|
||||
* activate {@code NOWHERE} whenever the current trace is closed, effectively activating the
|
||||
* most recent trace other than the one just closed.
|
||||
*/
|
||||
public static final DebuggerCoordinates NOWHERE =
|
||||
new DebuggerCoordinates(null, null, null, null, null, null, null, null);
|
||||
|
||||
@ -310,8 +320,14 @@ public class DebuggerCoordinates {
|
||||
if (frameLevel == null) {
|
||||
return objThread.getCanonicalPath();
|
||||
}
|
||||
TraceStack stack =
|
||||
thread.getTrace().getStackManager().getStack(thread, time.getSnap(), false);
|
||||
TraceStack stack;
|
||||
try {
|
||||
stack = thread.getTrace().getStackManager().getStack(thread, time.getSnap(), false);
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// Schema does not specify a stack
|
||||
return objThread.getCanonicalPath();
|
||||
}
|
||||
if (stack == null) {
|
||||
return objThread.getCanonicalPath();
|
||||
}
|
||||
@ -391,7 +407,8 @@ public class DebuggerCoordinates {
|
||||
return NOWHERE;
|
||||
}
|
||||
long snap = newTime.getSnap();
|
||||
TraceThread newThread = thread != null && thread.getLifespan().contains(snap) ? thread
|
||||
Lifespan threadLifespan = thread == null ? null : thread.getLifespan();
|
||||
TraceThread newThread = threadLifespan != null && threadLifespan.contains(snap) ? thread
|
||||
: resolveThread(trace, target, newTime);
|
||||
// This will cause the frame to reset to 0 on every snap change. That's fair....
|
||||
Integer newFrame = resolveFrame(newThread, newTime);
|
||||
@ -492,11 +509,9 @@ public class DebuggerCoordinates {
|
||||
else if (trace == null) {
|
||||
throw new IllegalArgumentException("No trace");
|
||||
}
|
||||
else {
|
||||
if (newPath == null) {
|
||||
return new DebuggerCoordinates(trace, platform, target, thread, view, time, frame,
|
||||
newPath);
|
||||
}
|
||||
else if (newPath == null) {
|
||||
return new DebuggerCoordinates(trace, platform, target, thread, view, time, frame,
|
||||
newPath);
|
||||
}
|
||||
TraceThread newThread = target != null
|
||||
? resolveThread(target, newPath)
|
||||
@ -509,6 +524,31 @@ public class DebuggerCoordinates {
|
||||
newFrame, newPath);
|
||||
}
|
||||
|
||||
public DebuggerCoordinates pathNonCanonical(TraceObjectKeyPath newPath) {
|
||||
if (trace == null && newPath == null) {
|
||||
return NOWHERE;
|
||||
}
|
||||
else if (trace == null) {
|
||||
throw new IllegalArgumentException("No trace");
|
||||
}
|
||||
else if (newPath == null) {
|
||||
return new DebuggerCoordinates(trace, platform, target, thread, view, time, frame,
|
||||
newPath);
|
||||
}
|
||||
TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(newPath);
|
||||
if (object != null) {
|
||||
return path(newPath);
|
||||
}
|
||||
object = trace.getObjectManager()
|
||||
.getObjectsByPath(Lifespan.at(getSnap()), newPath)
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
if (object != null) {
|
||||
return path(object.getCanonicalPath());
|
||||
}
|
||||
throw new IllegalArgumentException("No such object at path " + newPath);
|
||||
}
|
||||
|
||||
protected static TraceThread resolveThread(Target target, TraceObjectKeyPath objectPath) {
|
||||
return target.getThreadForSuccessor(objectPath);
|
||||
}
|
||||
|
@ -56,6 +56,13 @@ public interface RemoteMethod {
|
||||
*/
|
||||
ActionName action();
|
||||
|
||||
/**
|
||||
* A title to display in the UI for this action.
|
||||
*
|
||||
* @return the title
|
||||
*/
|
||||
String display();
|
||||
|
||||
/**
|
||||
* A description of the method.
|
||||
*
|
||||
@ -99,23 +106,28 @@ public interface RemoteMethod {
|
||||
* primitive. We instead need {@link TraceObject}. I'd add the method to the schema, except that
|
||||
* trace stuff is not in its dependencies.
|
||||
*
|
||||
* @param name the name of the parameter
|
||||
* @param paramName the name of the parameter
|
||||
* @param schName the name of the parameter's schema
|
||||
* @param sch the type of the parameter
|
||||
* @param arg the argument
|
||||
*/
|
||||
static void checkType(String name, TargetObjectSchema sch, Object arg) {
|
||||
if (sch.getType() != TargetObject.class) {
|
||||
if (sch.getType().isInstance(arg)) {
|
||||
return;
|
||||
static void checkType(String paramName, SchemaName schName, TargetObjectSchema sch,
|
||||
Object arg) {
|
||||
// if sch is null, it was definitely an object-type schema without context
|
||||
if (sch != null) {
|
||||
if (sch.getType() != TargetObject.class) {
|
||||
if (sch.getType().isInstance(arg)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (arg instanceof TraceObject obj) {
|
||||
if (sch.equals(obj.getTargetSchema())) {
|
||||
return;
|
||||
else if (arg instanceof TraceObject obj) {
|
||||
if (sch.isAssignableFrom(obj.getTargetSchema())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"For parameter %s: argument %s is not a %s".formatted(name, arg, sch));
|
||||
"For parameter %s: argument %s is not a %s".formatted(paramName, arg, schName));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -152,8 +164,9 @@ public interface RemoteMethod {
|
||||
"All TraceObject parameters must come from the same trace");
|
||||
}
|
||||
}
|
||||
TargetObjectSchema sch = ctx.getSchema(ent.getValue().type());
|
||||
checkType(ent.getKey(), sch, arg);
|
||||
SchemaName schName = ent.getValue().type();
|
||||
TargetObjectSchema sch = ctx.getSchemaOrNull(schName);
|
||||
checkType(ent.getKey(), schName, sch, arg);
|
||||
}
|
||||
for (Map.Entry<String, Object> ent : arguments.entrySet()) {
|
||||
if (!parameters().containsKey(ent.getKey())) {
|
||||
@ -191,6 +204,7 @@ public interface RemoteMethod {
|
||||
*
|
||||
* @param arguments the keyword arguments to the remote method
|
||||
* @throws IllegalArgumentException if the arguments are not valid
|
||||
* @return the returned value
|
||||
*/
|
||||
default Object invoke(Map<String, Object> arguments) {
|
||||
try {
|
||||
|
@ -17,15 +17,39 @@ package ghidra.debug.api.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.services.Terminal;
|
||||
|
||||
/**
|
||||
* A terminal with some back-end element attached to it
|
||||
*/
|
||||
public interface TerminalSession extends AutoCloseable {
|
||||
@Override
|
||||
void close() throws IOException;
|
||||
default void close() throws IOException {
|
||||
terminate();
|
||||
terminal().close();
|
||||
}
|
||||
|
||||
/**
|
||||
* The handle to the terminal
|
||||
*
|
||||
* @return the handle
|
||||
*/
|
||||
Terminal terminal();
|
||||
|
||||
/**
|
||||
* Ensure the session is visible
|
||||
*
|
||||
* <p>
|
||||
* The window should be displayed and brought to the front.
|
||||
*/
|
||||
default void show() {
|
||||
terminal().toFront();
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate the session without closing the terminal
|
||||
*
|
||||
* @throws IOException if an I/O issue occurs during termination
|
||||
*/
|
||||
void terminate() throws IOException;
|
||||
|
||||
@ -34,7 +58,9 @@ public interface TerminalSession extends AutoCloseable {
|
||||
*
|
||||
* @return true for terminated, false for active
|
||||
*/
|
||||
boolean isTerminated();
|
||||
default boolean isTerminated() {
|
||||
return terminal().isTerminated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a human-readable description of the session
|
||||
@ -42,4 +68,22 @@ public interface TerminalSession extends AutoCloseable {
|
||||
* @return the description
|
||||
*/
|
||||
String description();
|
||||
|
||||
/**
|
||||
* Get the terminal contents as a string (no attributes)
|
||||
*
|
||||
* @return the content
|
||||
*/
|
||||
default String content() {
|
||||
return terminal().getFullText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current title of the terminal
|
||||
*
|
||||
* @return the title
|
||||
*/
|
||||
default String title() {
|
||||
return terminal().getSubTitle();
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,13 @@ public interface TraceRmiAcceptor {
|
||||
*/
|
||||
TraceRmiConnection accept() throws IOException, CancelledException;
|
||||
|
||||
/**
|
||||
* Check if the acceptor is actually still accepting.
|
||||
*
|
||||
* @return true if not accepting anymore
|
||||
*/
|
||||
boolean isClosed();
|
||||
|
||||
/**
|
||||
* Get the address (and port) where the acceptor is listening
|
||||
*
|
||||
|
@ -52,21 +52,42 @@ public interface TraceRmiLaunchOffer {
|
||||
* @param sessions any terminal sessions created while launching the back-end. If there are more
|
||||
* than one, they are distinguished by launcher-defined keys. If there are no
|
||||
* sessions, then there was likely a catastrophic error in the launcher.
|
||||
* @param acceptor the acceptor if waiting for a connection
|
||||
* @param connection if the target connected back to Ghidra, that connection
|
||||
* @param trace if the connection started a trace, the (first) trace it created
|
||||
* @param exception optional error, if failed
|
||||
*/
|
||||
public record LaunchResult(Program program, Map<String, TerminalSession> sessions,
|
||||
TraceRmiConnection connection, Trace trace, Throwable exception)
|
||||
implements AutoCloseable {
|
||||
TraceRmiAcceptor acceptor, TraceRmiConnection connection, Trace trace,
|
||||
Throwable exception) implements AutoCloseable {
|
||||
public LaunchResult(Program program, Map<String, TerminalSession> sessions,
|
||||
TraceRmiAcceptor acceptor, TraceRmiConnection connection, Trace trace,
|
||||
Throwable exception) {
|
||||
this.program = program;
|
||||
this.sessions = sessions;
|
||||
this.acceptor = acceptor == null || acceptor.isClosed() ? null : acceptor;
|
||||
this.connection = connection;
|
||||
this.trace = trace;
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
public void showTerminals() {
|
||||
for (TerminalSession session : sessions.values()) {
|
||||
session.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
for (TerminalSession s : sessions.values()) {
|
||||
s.close();
|
||||
}
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
if (acceptor != null) {
|
||||
acceptor.cancel();
|
||||
}
|
||||
for (TerminalSession s : sessions.values()) {
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,7 +145,7 @@ public interface TraceRmiLaunchOffer {
|
||||
/**
|
||||
* Re-write the launcher arguments, if desired
|
||||
*
|
||||
* @param launcher the launcher that will create the target
|
||||
* @param offer the offer that will create the target
|
||||
* @param arguments the arguments suggested by the offer or saved settings
|
||||
* @param relPrompt describes the timing of this callback relative to prompting the user
|
||||
* @return the adjusted arguments
|
||||
@ -162,7 +183,7 @@ public interface TraceRmiLaunchOffer {
|
||||
* memorized. The opinion will generate each offer fresh each time, so it's important that the
|
||||
* "same offer" have the same configuration name. Note that the name <em>cannot</em> depend on
|
||||
* the program name, but can depend on the model factory and program language and/or compiler
|
||||
* spec. This name cannot contain semicolons ({@ code ;}).
|
||||
* spec. This name cannot contain semicolons ({@code ;}).
|
||||
*
|
||||
* @return the configuration name
|
||||
*/
|
||||
@ -248,6 +269,8 @@ public interface TraceRmiLaunchOffer {
|
||||
* The order of entries in the quick-launch drop-down menu is always most-recently to
|
||||
* least-recently used. An entry that has never been used does not appear in the quick launch
|
||||
* menu.
|
||||
*
|
||||
* @return the sub-group name for ordering in the menu
|
||||
*/
|
||||
default String getMenuOrder() {
|
||||
return "";
|
||||
@ -271,4 +294,11 @@ public interface TraceRmiLaunchOffer {
|
||||
* @return the parameters
|
||||
*/
|
||||
Map<String, ParameterDescription<?>> getParameters();
|
||||
|
||||
/**
|
||||
* Check if this offer requires an open program
|
||||
*
|
||||
* @return true if required
|
||||
*/
|
||||
boolean requiresImage();
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,656 @@
|
||||
/* ###
|
||||
* 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.debug.flatapi;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.services.DebuggerModelService;
|
||||
import ghidra.dbg.AnnotatedDebuggerAttributeListener;
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.debug.api.model.DebuggerProgramLaunchOffer;
|
||||
import ghidra.debug.api.model.DebuggerProgramLaunchOffer.*;
|
||||
import ghidra.debug.api.model.TraceRecorder;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@Deprecated
|
||||
public interface FlatDebuggerRecorderAPI extends FlatDebuggerAPI {
|
||||
|
||||
/**
|
||||
* Get the model (legacy target) service
|
||||
*
|
||||
* @return the service
|
||||
*/
|
||||
default DebuggerModelService getModelService() {
|
||||
return requireService(DebuggerModelService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target for a given trace
|
||||
*
|
||||
* <p>
|
||||
* WARNING: This method will likely change or be removed in the future.
|
||||
*
|
||||
* @param trace the trace
|
||||
* @return the target, or null if not alive
|
||||
*/
|
||||
default TargetObject getTarget(Trace trace) {
|
||||
TraceRecorder recorder = getModelService().getRecorder(trace);
|
||||
if (recorder == null) {
|
||||
return null;
|
||||
}
|
||||
return recorder.getTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target thread for a given trace thread
|
||||
*
|
||||
* <p>
|
||||
* WARNING: This method will likely change or be removed in the future.
|
||||
*
|
||||
* @param thread the trace thread
|
||||
* @return the target thread, or null if not alive
|
||||
*/
|
||||
default TargetThread getTargetThread(TraceThread thread) {
|
||||
TraceRecorder recorder = getModelService().getRecorder(thread.getTrace());
|
||||
if (recorder == null) {
|
||||
return null;
|
||||
}
|
||||
return recorder.getTargetThread(thread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user focus for a given trace
|
||||
*
|
||||
* <p>
|
||||
* WARNING: This method will likely change or be removed in the future.
|
||||
*
|
||||
* @param trace the trace
|
||||
* @return the target, or null if not alive
|
||||
*/
|
||||
default TargetObject getTargetFocus(Trace trace) {
|
||||
TraceRecorder recorder = getModelService().getRecorder(trace);
|
||||
if (recorder == null) {
|
||||
return null;
|
||||
}
|
||||
TargetObject focus = recorder.getFocus();
|
||||
return focus != null ? focus : recorder.getTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the most suitable object related to the given object implementing the given interface
|
||||
*
|
||||
* <p>
|
||||
* WARNING: This method will likely change or be removed in the future.
|
||||
*
|
||||
* @param <T> the interface type
|
||||
* @param seed the seed object
|
||||
* @param iface the interface class
|
||||
* @return the related interface, or null
|
||||
* @throws ClassCastException if the model violated its schema wrt. the requested interface
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T extends TargetObject> T findInterface(TargetObject seed, Class<T> iface) {
|
||||
DebuggerObjectModel model = seed.getModel();
|
||||
List<String> found = model
|
||||
.getRootSchema()
|
||||
.searchForSuitable(iface, seed.getPath());
|
||||
if (found == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Object value = waitOn(model.fetchModelValue(found));
|
||||
return (T) value;
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the most suitable object related to the given thread implementing the given interface
|
||||
*
|
||||
* @param <T> the interface type
|
||||
* @param thread the thread
|
||||
* @param iface the interface class
|
||||
* @return the related interface, or null
|
||||
* @throws ClassCastException if the model violated its schema wrt. the requested interface
|
||||
*/
|
||||
default <T extends TargetObject> T findInterface(TraceThread thread, Class<T> iface) {
|
||||
TargetThread targetThread = getTargetThread(thread);
|
||||
if (targetThread == null) {
|
||||
return null;
|
||||
}
|
||||
return findInterface(targetThread, iface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the most suitable object related to the given trace's focus implementing the given
|
||||
* interface
|
||||
*
|
||||
* @param <T> the interface type
|
||||
* @param trace the trace
|
||||
* @param iface the interface class
|
||||
* @return the related interface, or null
|
||||
* @throws ClassCastException if the model violated its schema wrt. the requested interface
|
||||
*/
|
||||
default <T extends TargetObject> T findInterface(Trace trace, Class<T> iface) {
|
||||
TargetObject focus = getTargetFocus(trace);
|
||||
if (focus == null) {
|
||||
return null;
|
||||
}
|
||||
return findInterface(focus, iface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the interface related to the current thread or trace
|
||||
*
|
||||
* <p>
|
||||
* This first attempts to find the most suitable object related to the current trace thread. If
|
||||
* that fails, or if there is no current thread, it tries to find the one related to the current
|
||||
* trace (or its focus). If there is no current trace, this throws an exception.
|
||||
*
|
||||
* @param <T> the interface type
|
||||
* @param iface the interface class
|
||||
* @return the related interface, or null
|
||||
* @throws IllegalStateException if there is no current trace
|
||||
*/
|
||||
default <T extends TargetObject> T findInterface(Class<T> iface) {
|
||||
TraceThread thread = getCurrentThread();
|
||||
T t = thread == null ? null : findInterface(thread, iface);
|
||||
if (t != null) {
|
||||
return t;
|
||||
}
|
||||
return findInterface(requireCurrentTrace(), iface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step the given target object
|
||||
*
|
||||
* @param steppable the steppable target object
|
||||
* @param kind the kind of step to take
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
default boolean step(TargetSteppable steppable, TargetStepKind kind) {
|
||||
if (steppable == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
waitOn(steppable.step(kind));
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Step the given thread on target according to the given kind
|
||||
*
|
||||
* @param thread the trace thread
|
||||
* @param kind the kind of step to take
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
default boolean step(TraceThread thread, TargetStepKind kind) {
|
||||
if (thread == null) {
|
||||
return false;
|
||||
}
|
||||
return step(findInterface(thread, TargetSteppable.class), kind);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume execution of the given target object
|
||||
*
|
||||
* @param resumable the resumable target object
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
default boolean resume(TargetResumable resumable) {
|
||||
if (resumable == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
waitOn(resumable.resume());
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interrupt execution of the given target object
|
||||
*
|
||||
* @param interruptible the interruptible target object
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
default boolean interrupt(TargetInterruptible interruptible) {
|
||||
if (interruptible == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
waitOn(interruptible.interrupt());
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate execution of the given target object
|
||||
*
|
||||
* @param interruptible the interruptible target object
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
default boolean kill(TargetKillable killable) {
|
||||
if (killable == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
waitOn(killable.kill());
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state of the given target
|
||||
*
|
||||
* <p>
|
||||
* Any invalidated object is considered {@link TargetExecutionState#TERMINATED}. Otherwise, it's
|
||||
* at least considered {@link TargetExecutionState#ALIVE}. A more specific state may be
|
||||
* determined by searching the model for the conventionally-related object implementing
|
||||
* {@link TargetObjectStateful}. This method applies this convention.
|
||||
*
|
||||
* @param target the target object
|
||||
* @return the target object's execution state
|
||||
*/
|
||||
default TargetExecutionState getExecutionState(TargetObject target) {
|
||||
if (!target.isValid()) {
|
||||
return TargetExecutionState.TERMINATED;
|
||||
}
|
||||
TargetExecutionStateful stateful = findInterface(target, TargetExecutionStateful.class);
|
||||
return stateful == null ? TargetExecutionState.ALIVE : stateful.getExecutionState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given target to exit the {@link TargetExecutionState#RUNNING} state
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> There may be subtleties depending on the target debugger. For the most part, if
|
||||
* the connection is handling a single target, things will work as expected. However, if there
|
||||
* are multiple targets on one connection, it is possible for the given target to break, but for
|
||||
* the target debugger to remain unresponsive to commands. This would happen, e.g., if a second
|
||||
* target on the same connection is still running.
|
||||
*
|
||||
* @param target the target
|
||||
* @param timeout the maximum amount of time to wait
|
||||
* @param unit the units for time
|
||||
* @throws TimeoutException if the timeout expires
|
||||
*/
|
||||
default void waitForBreak(TargetObject target, long timeout, TimeUnit unit)
|
||||
throws TimeoutException {
|
||||
TargetExecutionStateful stateful = findInterface(target, TargetExecutionStateful.class);
|
||||
if (stateful == null) {
|
||||
throw new IllegalArgumentException("Given target is not stateful");
|
||||
}
|
||||
var listener = new AnnotatedDebuggerAttributeListener(MethodHandles.lookup()) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
|
||||
private void stateChanged(TargetObject parent, TargetExecutionState state) {
|
||||
if (parent == stateful && !state.isRunning()) {
|
||||
future.complete(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
target.getModel().addModelListener(listener);
|
||||
try {
|
||||
if (!stateful.getExecutionState().isRunning()) {
|
||||
return;
|
||||
}
|
||||
listener.future.get(timeout, unit);
|
||||
}
|
||||
catch (ExecutionException | InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally {
|
||||
target.getModel().removeModelListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default void waitForBreak(Trace trace, long timeout, TimeUnit unit) throws TimeoutException {
|
||||
TargetObject target = getTarget(trace);
|
||||
if (target == null || !target.isValid()) {
|
||||
return;
|
||||
}
|
||||
waitForBreak(target, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in a connection's interpreter, capturing the output
|
||||
*
|
||||
* <p>
|
||||
* This executes a raw command in the given interpreter. The command could have arbitrary
|
||||
* effects, so it may be necessary to wait for those effects to be handled by the tool's
|
||||
* services and plugins before proceeding.
|
||||
*
|
||||
* @param interpreter the interpreter
|
||||
* @param command the command
|
||||
* @return the output, or null if there is no interpreter
|
||||
*/
|
||||
default String executeCapture(TargetInterpreter interpreter, String command) {
|
||||
if (interpreter == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return waitOn(interpreter.executeCapture(command));
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in a connection's interpreter
|
||||
*
|
||||
* <p>
|
||||
* This executes a raw command in the given interpreter. The command could have arbitrary
|
||||
* effects, so it may be necessary to wait for those effects to be handled by the tool's
|
||||
* services and plugins before proceeding.
|
||||
*
|
||||
* @param interpreter the interpreter
|
||||
* @param command the command
|
||||
* @return true if successful
|
||||
*/
|
||||
default boolean execute(TargetInterpreter interpreter, String command) {
|
||||
if (interpreter == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
waitOn(interpreter.executeCapture(command));
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value at the given path for the given model
|
||||
*
|
||||
* @param model the model
|
||||
* @param path the path
|
||||
* @return the avlue, or null if the trace is not live or if the path does not exist
|
||||
*/
|
||||
default Object getModelValue(DebuggerObjectModel model, String path) {
|
||||
try {
|
||||
return waitOn(model.fetchModelValue(PathUtils.parse(path)));
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value at the given path for the current trace's model
|
||||
*
|
||||
* @param path the path
|
||||
* @return the value, or null if the trace is not live or if the path does not exist
|
||||
*/
|
||||
default Object getModelValue(String path) {
|
||||
TraceRecorder recorder = getModelService().getRecorder(getCurrentTrace());
|
||||
if (recorder == null) {
|
||||
return null;
|
||||
}
|
||||
return getModelValue(recorder.getTarget().getModel(), path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the given objects children (elements and attributes)
|
||||
*
|
||||
* @param object the object
|
||||
* @return the set of children, excluding primitive-valued attributes
|
||||
*/
|
||||
default Set<TargetObject> refreshObjectChildren(TargetObject object) {
|
||||
try {
|
||||
// Refresh both children and memory/register values
|
||||
waitOn(object.invalidateCaches());
|
||||
waitOn(object.resync());
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return null;
|
||||
}
|
||||
Set<TargetObject> result = new LinkedHashSet<>();
|
||||
result.addAll(object.getCachedElements().values());
|
||||
for (Object v : object.getCachedAttributes().values()) {
|
||||
if (v instanceof TargetObject) {
|
||||
result.add((TargetObject) v);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the given object and its children, recursively
|
||||
*
|
||||
* <p>
|
||||
* The objects are traversed in depth-first pre-order. Links are traversed, even if the object
|
||||
* is not part of the specified subtree, but an object is skipped if it has already been
|
||||
* visited.
|
||||
*
|
||||
* @param object the seed object
|
||||
* @return true if the traversal completed successfully
|
||||
*/
|
||||
default boolean refreshSubtree(TargetObject object) {
|
||||
var util = new Object() {
|
||||
Set<TargetObject> visited = new HashSet<>();
|
||||
|
||||
boolean visit(TargetObject object) {
|
||||
if (!visited.add(object)) {
|
||||
return true;
|
||||
}
|
||||
for (TargetObject child : refreshObjectChildren(object)) {
|
||||
if (!visit(child)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return util.visit(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* This override includes flushing the recorder's event and transaction queues.
|
||||
*/
|
||||
@Override
|
||||
default boolean flushAsyncPipelines(Trace trace) {
|
||||
try {
|
||||
TraceRecorder recorder = getModelService().getRecorder(trace);
|
||||
if (recorder != null) {
|
||||
waitOn(recorder.getTarget().getModel().flushEvents());
|
||||
waitOn(recorder.flushTransactions());
|
||||
}
|
||||
trace.flushEvents();
|
||||
waitOn(getMappingService().changesSettled());
|
||||
waitOn(getBreakpointService().changesSettled());
|
||||
Swing.allowSwingToProcessEvents();
|
||||
return true;
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offers for launching the given program
|
||||
*
|
||||
* @param program the program
|
||||
* @return the offers
|
||||
*/
|
||||
default List<DebuggerProgramLaunchOffer> getLaunchOffers(Program program) {
|
||||
return getModelService().getProgramLaunchOffers(program).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offers for launching the current program
|
||||
*
|
||||
* @return the offers
|
||||
*/
|
||||
default List<DebuggerProgramLaunchOffer> getLaunchOffers() {
|
||||
return getLaunchOffers(requireCurrentProgram());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the best launch offer for a program, throwing an exception if there is no offer
|
||||
*
|
||||
* @param program the program
|
||||
* @return the offer
|
||||
* @throws NoSuchElementException if there is no offer
|
||||
*/
|
||||
default DebuggerProgramLaunchOffer requireLaunchOffer(Program program) {
|
||||
Optional<DebuggerProgramLaunchOffer> offer =
|
||||
getModelService().getProgramLaunchOffers(program).findFirst();
|
||||
if (offer.isEmpty()) {
|
||||
throw new NoSuchElementException("No offers to launch " + program);
|
||||
}
|
||||
return offer.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given offer, overriding its command line
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> Most offers take a command line, but not all do. If this is used for an offer
|
||||
* that does not, it's behavior is undefined.
|
||||
*
|
||||
* <p>
|
||||
* Launches are not always successful, and may in fact fail frequently, usually because of
|
||||
* configuration errors or missing components on the target platform. This may leave stale
|
||||
* connections and/or target debuggers, processes, etc., in strange states. Furthermore, even if
|
||||
* launching the target is successful, starting the recorder may not succeed, typically because
|
||||
* Ghidra cannot identify and map the target platform to a Sleigh language. This method makes no
|
||||
* attempt at cleaning up partial pieces. Instead it returns those pieces in the launch result.
|
||||
* If the result includes a recorder, the launch was successful. If not, the script can decide
|
||||
* what to do with the other pieces. That choice depends on what is expected of the user. Can
|
||||
* the user reasonable be expected to intervene and complete the launch manually? How many
|
||||
* targets does the script intend to launch? How big is the mess if left partially completed?
|
||||
*
|
||||
* @param offer the offer (this includes the program given when asking for offers)
|
||||
* @param commandLine the command-line override. If this doesn't refer to the same program as
|
||||
* the offer, there may be unexpected results
|
||||
* @param monitor the monitor for the launch stages
|
||||
* @return the result, possibly partial
|
||||
*/
|
||||
default LaunchResult launch(DebuggerProgramLaunchOffer offer, String commandLine,
|
||||
TaskMonitor monitor) {
|
||||
try {
|
||||
return waitOn(offer.launchProgram(monitor, PromptMode.NEVER, new LaunchConfigurator() {
|
||||
@Override
|
||||
public Map<String, ?> configureLauncher(TargetLauncher launcher,
|
||||
Map<String, ?> arguments, RelPrompt relPrompt) {
|
||||
Map<String, Object> adjusted = new HashMap<>(arguments);
|
||||
adjusted.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, commandLine);
|
||||
return adjusted;
|
||||
}
|
||||
}));
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
// TODO: This is not ideal, since it's likely partially completed
|
||||
return LaunchResult.totalFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given offer with the default/saved arguments
|
||||
*
|
||||
* @see #launch(DebuggerProgramLaunchOffer, String, TaskMonitor)
|
||||
*/
|
||||
default LaunchResult launch(DebuggerProgramLaunchOffer offer, TaskMonitor monitor) {
|
||||
try {
|
||||
return waitOn(offer.launchProgram(monitor, PromptMode.NEVER));
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
// TODO: This is not ideal, since it's likely partially completed
|
||||
return LaunchResult.totalFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given program, overriding its command line
|
||||
*
|
||||
* <p>
|
||||
* This takes the best offer for the given program. The command line should invoke the given
|
||||
* program. If it does not, there may be unexpected results.
|
||||
*
|
||||
* @see #launch(DebuggerProgramLaunchOffer, String, TaskMonitor)
|
||||
*/
|
||||
default LaunchResult launch(Program program, String commandLine, TaskMonitor monitor)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return launch(requireLaunchOffer(program), commandLine, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given program with the default/saved arguments
|
||||
*
|
||||
* <p>
|
||||
* This takes the best offer for the given program.
|
||||
*
|
||||
* @see #launch(DebuggerProgramLaunchOffer, String, TaskMonitor)
|
||||
*/
|
||||
default LaunchResult launch(Program program, TaskMonitor monitor)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return launch(requireLaunchOffer(program), monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the current program, overriding its command line
|
||||
*
|
||||
* @see #launch(Program, String, TaskMonitor)
|
||||
*/
|
||||
default LaunchResult launch(String commandLine, TaskMonitor monitor)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return launch(requireCurrentProgram(), commandLine, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the current program with the default/saved arguments
|
||||
*
|
||||
* @see #launch(Program, TaskMonitor)
|
||||
*/
|
||||
default LaunchResult launch(TaskMonitor monitor)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return launch(requireCurrentProgram(), monitor);
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/* ###
|
||||
* 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.debug.flatapi;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.services.TraceRmiLauncherService;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public interface FlatDebuggerRmiAPI extends FlatDebuggerAPI {
|
||||
|
||||
/**
|
||||
* Get the trace-rmi launcher service
|
||||
*
|
||||
* @return the service
|
||||
*/
|
||||
default TraceRmiLauncherService getTraceRmiLauncherService() {
|
||||
return requireService(TraceRmiLauncherService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offers for launching the given program
|
||||
*
|
||||
* @param program the program, or null for no image
|
||||
* @return the offers
|
||||
*/
|
||||
default Collection<TraceRmiLaunchOffer> getLaunchOffers(Program program) {
|
||||
return getTraceRmiLauncherService().getOffers(program);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offers for launching the current program
|
||||
*
|
||||
* @return the offers
|
||||
*/
|
||||
default Collection<TraceRmiLaunchOffer> getLaunchOffers() {
|
||||
return getLaunchOffers(getCurrentProgram());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saved offers for launching the given program, ordered by most-recently-saved
|
||||
*
|
||||
* @param program the program, or null for no image
|
||||
* @return the offers
|
||||
*/
|
||||
default List<TraceRmiLaunchOffer> getSavedLaunchOffers(Program program) {
|
||||
return getTraceRmiLauncherService().getSavedOffers(program);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saved offers for launching the current program, ordered by most-recently-saved
|
||||
*
|
||||
* @return the offers
|
||||
*/
|
||||
default List<TraceRmiLaunchOffer> getSavedLaunchOffers() {
|
||||
return getSavedLaunchOffers(getCurrentProgram());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most-recently-saved launch offer for the given program
|
||||
*
|
||||
* @param program the program, or null for no image
|
||||
* @return the offer
|
||||
* @throws NoSuchElementException if no offer's configuration has been saved
|
||||
*/
|
||||
default TraceRmiLaunchOffer requireLastLaunchOffer(Program program) {
|
||||
List<TraceRmiLaunchOffer> offers = getSavedLaunchOffers(program);
|
||||
if (offers.isEmpty()) {
|
||||
throw new NoSuchElementException("No saved offers to launch " + program);
|
||||
}
|
||||
return offers.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most-recently-saved launch offer for the current program
|
||||
*
|
||||
* @return the offer
|
||||
* @throws NoSuchElementException if no offer's configuration has been saved
|
||||
*/
|
||||
default TraceRmiLaunchOffer requireLastLaunchOffer() {
|
||||
return requireLastLaunchOffer(getCurrentProgram());
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given offer with the default, saved, and/or overridden arguments
|
||||
*
|
||||
* <p>
|
||||
* If the offer has saved arguments, those will be loaded. Otherwise, the default arguments will
|
||||
* be used. If given, specific arguments can be overridden by the caller. The caller may need to
|
||||
* examine the offer's parameters before overriding any arguments. Conventionally, the argument
|
||||
* displayed as "Image" gives the path to the executable, and "Args" gives the command-line
|
||||
* arguments to pass to the target.
|
||||
*
|
||||
* @param offer the offer to launch
|
||||
* @param monitor a monitor for the launch stages
|
||||
* @param overrideArgs overridden arguments, which may be empty
|
||||
* @return the launch result, which may indicate errors
|
||||
*/
|
||||
default LaunchResult launch(TraceRmiLaunchOffer offer, Map<String, ?> overrideArgs,
|
||||
TaskMonitor monitor) {
|
||||
return offer.launchProgram(monitor, new LaunchConfigurator() {
|
||||
@Override
|
||||
public Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
|
||||
Map<String, ?> arguments, RelPrompt relPrompt) {
|
||||
if (arguments.isEmpty()) {
|
||||
return arguments;
|
||||
}
|
||||
Map<String, Object> args = new HashMap<>(arguments);
|
||||
args.putAll(overrideArgs);
|
||||
return args;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given offer with the default or saved arguments
|
||||
*
|
||||
* @param offer the offer to launch
|
||||
* @param monitor a monitor for the launch stages
|
||||
* @return the launch result, which may indicate errors
|
||||
*/
|
||||
default LaunchResult launch(TraceRmiLaunchOffer offer, TaskMonitor monitor) {
|
||||
return launch(offer, Map.of(), monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the given program with the most-recently-saved offer
|
||||
*
|
||||
* @param program the program to launch
|
||||
* @param monitor a monitor for the launch stages
|
||||
* @return the launch result, which may indicate errors
|
||||
*/
|
||||
default LaunchResult launch(Program program, TaskMonitor monitor) {
|
||||
return launch(requireLastLaunchOffer(program), monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the current program with the most-recently-saved offer
|
||||
*
|
||||
* @param monitor a monitor for the launch stages
|
||||
* @return the launch result, which may indicate errors
|
||||
*/
|
||||
default LaunchResult launch(TaskMonitor monitor) {
|
||||
return launch(requireLastLaunchOffer(), monitor);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user