Merge tag 'Ghidra_11.1_build' into stable

This commit is contained in:
Ryan Kurtz 2024-06-07 16:10:00 -04:00
commit 9a13473062
3339 changed files with 187159 additions and 82078 deletions

View File

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

View File

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

View File

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

View File

@ -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.
*********************************************************************************/

View File

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

View File

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

View File

@ -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">&lt;CTRL&gt; Tab</span> will
move focus to the next focusable component and <span class="gtitle">&lt;SHIFT&gt; Tab</span> and <span class="gtitle">&lt;CTRL&gt;&lt;SHIFT&gt; Tab</span> will move to the
previous focusable component. <span class="gtitle">Tab</span> and <span class="gtitle">&lt;SHIFT&gt; Tab</span> do not always work as some components use those keys internally, but
<span class="gtitle">&lt;CTRL&gt; Tab,</span> and <span class="gtitle">&lt;SHIFT&gt;&lt;CTRL&gt; Tab</span> should work universally.</LI>
<LI>Ghidra now provides some convenient keyboard shortcut actions for transferring focus:</LI>
<UL>
<LI><span class="gtitle">&lt;CTRL&gt; F3</span> - Transfers focus to the next window or dialog.</LI>
<LI><span class="gtitle">&lt;CTRL&gt;&lt;SHIFT&gt; F3</span> - Transfers focus to the previous window or dialog.</LI>
<LI><span class="gtitle">&lt;CTRL&gt; J</span> - Transfers focus to the next titled dockable component (titled windows).</LI>
<LI><span class="gtitle">&lt;CTRL&gt;&lt;SHIFT&gt; 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">&lt;CTRL&gt; 3</span> will bring up the actions dialog with the local toolbar, popup and keyboard actions.</LI>
<LI>Pressing <span class="gtitle">&lt;CTRL&gt; 3</span> a second time will add in all the global actions. </LI>
<LI>Pressing <span class="gtitle">&lt;CTRL&gt; 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
graft src

View File

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

View File

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

View File

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

View File

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

View File

@ -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()")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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"

View 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"

View 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"

View 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'"

View 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"

View 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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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):
"""

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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"

View 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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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