Merge branch 'NationalSecurityAgency:master' into m68k-a-line-trap

This commit is contained in:
Andrew Dunbar 2024-11-05 15:35:19 +07:00 committed by GitHub
commit 280e808b57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
621 changed files with 14466 additions and 6698 deletions

1
GPL/DMG/README.md Normal file
View File

@ -0,0 +1 @@
# DMG

View File

@ -3,6 +3,7 @@
##MODULE IP: LGPL 2.1
##MODULE IP: Public Domain
Module.manifest||Public Domain||||END|
README.md||GHIDRA||||END|
data/lib/csframework.jar||LGPL 2.1||||END|
data/lib/hfsexplorer-0_21-src.zip||GPL 3||||END|
data/lib/hfsx.jar||GPL 3||||END|

View File

@ -0,0 +1 @@
# DemanglerGnu

View File

@ -5,5 +5,5 @@
##MODULE IP: LGPL 3.0
##MODULE IP: Public Domain
Module.manifest||Public Domain||||END|
README.md||GHIDRA||||END|
src/demangler_gnu_v2_24/README.txt||Public Domain||||END|
src/demangler_gnu_v2_33_1/README.txt||Public Domain||||END|

View File

@ -0,0 +1,27 @@
# GnuDisassembler
The GnuDisassembler extension module must be built using gradle prior to its use within Ghidra.
This module provides the ability to leverage the binutils disassembler capabilities
for various processors as a means of verifying Sleigh disassembler output syntax.
To build this extension for Linux or macOS:
1. If building for an installation of Ghidra, copy the appropriate source distribution of binutils
into this module's root directory. If building within a git clone of the full Ghidra source, copy
binutils source distribution file into the `ghidra.bin/GPL/GnuDisassembler` directory.
The supported version and archive format is identified within the build.gradle file. If a
different binutils distribution is used the build.gradle and/or buildGdis.gradle may require
modification.
The build requires the following packages to be installed:
* flex
* bison
* texinfo
* zlib1g-dev
2. Run gradle from the module's root directory (see top of `build.gradle` file for specific
instructions).
This resulting gdis executable will be located in `build/os/<platform>`.

View File

@ -1,26 +0,0 @@
The GnuDisassembler extension module must be built using gradle prior to its' use within Ghidra.
This module provides the ability to leverage the binutils disassembler capabilities
for various processors as a means of verifying Sleigh disassembler output syntax.
To build this extension for Linux or Mac OS X:
1. If building for an installation of Ghidra, copy the appropriate source distribution of
binutils into this module's root directory. If building within a git clone of the full
Ghidra source, copy binutils source distribution file into the ghidra.bin/GPL/GnuDisassembler
directory.
The supported version and archive format is identified within the build.gradle file.
If a different binutils distribution is used the build.gradle and/or buildGdis.gradle
may require modification.
The build requires the following packages to be installed:
* flex
* bison
* texinfo
* zlib1g-dev
2. Run gradle from the module's root directory (see top of build.gradle file for
specific instructions).
This resulting gdis executable will be located in build/os/<platform>.

View File

@ -3,7 +3,7 @@
##MODULE IP: Public Domain
.gitignore||Public Domain||||END|
Module.manifest||Public Domain||||END|
README.txt||Public Domain||||END|
README.md||GHIDRA||||END|
data/arm_test1.s||Public Domain||||END|
data/big.elf||Public Domain||||END|
data/little.elf||Public Domain||||END|

View File

@ -0,0 +1 @@
# Public_Release

View File

@ -1,5 +1,6 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
data/PDB_SYMBOL_SERVER_URLS.pdburl||GHIDRA||||END|
src/global/docs/ChangeHistory.html||GHIDRA||||END|
src/global/docs/UserAgreement.html||GHIDRA||||END|

View File

@ -1,4 +1,7 @@
<HTML>
<HEAD>
<TITLE>Ghidra User Agreement</TITLE>
</HEAD>
<FONT SIZE="5">

View File

@ -0,0 +1 @@
# AnnotationValidator

View File

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

View File

@ -0,0 +1 @@
# Debugger-agent-dbgeng

View File

@ -2,6 +2,7 @@
##MODULE IP: Apache License 2.0
##MODULE IP: MIT
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
data/debugger-launchers/kernel-dbgeng.bat||GHIDRA||||END|
data/debugger-launchers/local-dbgeng-attach.bat||GHIDRA||||END|
data/debugger-launchers/local-dbgeng-ext.bat||GHIDRA||||END|

View File

@ -9,7 +9,7 @@
::@menu-group local
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#dbgeng_kernel
::@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."
::@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_ARGS:str="" "Arguments" "Connection-string arguments (a la .server)"
::@env OPT_USE_DBGMODEL:bool=true "Use dbgmodel" "Load and use dbgmodel.dll if it is available."

View File

@ -9,9 +9,9 @@
::@menu-group local
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#dbgeng_attach
::@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."
::@env OPT_TARGET_PID:str="" "Process id" "The target process id"
::@env OPT_ATTACH_FLAGS:str="0" "Attach flags" "Attach flags"
::@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."
::@env OPT_TARGET_PID:int="" "Process id" "The target process id"
::@env OPT_ATTACH_FLAGS:int="0" "Attach flags" "Attach flags"
::@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)."

View File

@ -1,4 +1,5 @@
::@title dbgeng-ext
::@image-opt env:OPT_TARGET_IMG
::@desc <html><body width="300px">
::@desc <h3>Launch with <tt>dbgeng</tt> (in a Python interpreter)</h3>
::@desc <p>
@ -9,17 +10,17 @@
::@menu-group local
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#dbgeng_ext
::@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."
::@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="" "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)."
::@env OPT_TARGET_DIR:str="" "Dir" "Initial directory"
::@env OPT_TARGET_ENV:str="" "Env" "Environment variables (sep=/0)"
::@env OPT_CREATE_FLAGS:str="1" "Create flags" "Creation flags"
::@env OPT_CREATE_ENGFLAGS:str="0" "Create flags (Engine)" "Engine-specific creation flags"
::@env OPT_VERIFIER_FLAGS:str="0" "Verifier flags" "Verifier flags"
::@env OPT_CREATE_FLAGS:int="1" "Create flags" "Creation flags"
::@env OPT_CREATE_ENGFLAGS:int="0" "Create flags (Engine)" "Engine-specific creation flags"
::@env OPT_VERIFIER_FLAGS:int="0" "Verifier flags" "Verifier flags"
@echo off

View File

@ -1,4 +1,5 @@
::@title dbgeng
::@image-opt env:OPT_TARGET_IMG
::@desc <html><body width="300px">
::@desc <h3>Launch with <tt>dbgeng</tt> (in a Python interpreter)</h3>
::@desc <p>
@ -9,9 +10,9 @@
::@menu-group local
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#dbgeng
::@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."
::@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="" "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)."

View File

@ -9,9 +9,9 @@
::@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."
::@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_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)."

View File

@ -1,4 +1,5 @@
::@title dbgeng-remote
::@image-opt env:OPT_TARGET_IMG
::@desc <html><body width="300px">
::@desc <h3>Launch with <tt>dbgeng</tt> remotely (in a Python interpreter)</h3>
::@desc <p>
@ -9,9 +10,9 @@
::@menu-group local
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#dbgeng_remote
::@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."
::@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="" "Image" "The target binary executable image"
::@env OPT_TARGET_IMG:str!="" "Image" "The target binary executable image"
::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
::@env OPT_CONNECT_STRING:str="" "Connection" "Connection-string arguments (a la dbgsrv args)"
::@env OPT_USE_DBGMODEL:bool=true "Use dbgmodel" "Load and use dbgmodel.dll if it is available."

View File

@ -0,0 +1 @@
# Debugger-agent-dbgmodel-traceloader

View File

@ -1,3 +1,4 @@
##VERSION: 2.0
##MODULE IP: Apache License 2.0
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|

View File

@ -0,0 +1 @@
# Debugger-agent-dbgmodel

View File

@ -1,5 +1,6 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
src/javaprovider/def/javaprovider.def||GHIDRA||||END|
src/javaprovider/rc/javaprovider.rc||GHIDRA||||END|
src/main/resources/agent/dbgmodel/model/impl/dbgmodel_schema.xml||GHIDRA||||END|

View File

@ -1,28 +0,0 @@
Random Notes on the Implementation of Debugger-agent-frida
- Building libfrida-core.so:
You can download libfrida-core.a for Frida by grabbing the latest frida-core-devkit for your OS from
https://github.com/frida/frida/releases
or by downloading the Frida source and running:
python3 devkit.py frida-core linux-x86_64 DEVKIT
from the "releng" directory.
Ghidra needs a dynamically-loadable version of libfrida-core.a which you can generate by something like:
cp ghidra_wrapper.c into the directory with libfrida-core.a and frida-core.h (distro or DEVKIT)
g++ -shared ghidra_wrapper.c ./libfrida-core.a -o libfrida-core.so
Libfrida-core.so should then be added to the jna.library.path or put someplace like /usr/lib/x86_64-linux-gnu, where it will get picked up by Native.load().
- Frida Functionality
The most interesting bits of Frida are available as "methods" from the Objects Tree. For instance, if you select a function and hit "M", you will get a dialog with available methods. Selecting, for example, "intercept" will bring up a second dialog with the relevant parameters. For many of these, you will want to provide your own Javascript "on" functions, e.g. onEnter for the Interceptor. Stalking is available on Threads and the individual thread entries. Scan, protect, and watch functions are available on Memory. You can also redirect the output to GhidraScript, although this relies on a bit of a hack. If your Javascript "Name" parameter is something like "interpreter", prepend "interpreter<=" to the output from your Javascript, and the results will be passed to both the console and the script.
- State in Frida:
Commands in Frida are, generally speaking, not state-dependent, i.e. they do not depend on whether the target is running or not, only on whether the frida-agent thread is running. Many of the gum-based commands do, however, depend on ptrace. If you have a ptrace-based debugger attached to the target, they will time out. You can attach a debugger after Frida, but you will have to detach it to regain the gum-based functionality. "Detach" in most debuggers includes "resume", so it is difficult to get state other than the "initial" state from the frida-agent injection point. It would be nice if "disconnect" worked, but "disconnect" (i.e. detach without resuming) also leaves Frida in a partially disabled state.
- Errors in Frida
The cloaking logic in Frida, e.g. in gum_cloak_add_thread and gum_cloak_index_of_thread, is broken as of the writing of this note. Gum_cloak_add_thread is called for every thread, and gum_cloak_index_of_thread returns a non-negative result for every call but the first. As a result, every thread but one is cloaked, and enumerateThreads returns only a single thread. This is documented in Issue #625 for the frida-gum project. A quick fix is to comment out the cloaking call in frida-gum/gum/gumprocess.c::gum_emit_thread_if_not_cloaked. Obviously, this may have other undesirable effects, but...
The logic in the ordering of exception handlers also appears to be broken (Issue #627). New handlers are appended to the queue, in most cases after gum_exceptor_handle_scope_exception and gum_quick_core_handle_crashed_js. Gum_exceptor_handle_scope_exception almost always returns TRUE, breaking out of the queue and causing any remaining handlers to be ignored. This means any handler added with Process.setExceptionHandler is likely to be ignored. A quick fix is to modify gum_exceptor_add to use g_slist_prepend instead of g_slist_append.
Not really an error, but worth noting: building libfrida-core.so from the source may result in a library with glib2.0 dependencies that are incompatible with the current version of Eclipse. The not-so-simple solution is to build Eclipse on the machine that you used to build libfrida-core.

View File

@ -0,0 +1,58 @@
# Debugger-agent-frida
## Random Notes on the Implementation of Debugger-agent-frida
Building libfrida-core.so:
* You can download libfrida-core.a for Frida by grabbing the latest frida-core-devkit for your OS
from https://github.com/frida/frida/releases or by downloading the Frida source and running:
`python3 devkit.py frida-core linux-x86_64 DEVKIT` from the `releng` directory.
Ghidra needs a dynamically-loadable version of libfrida-core.a which you can generate by something like:
```bash
cp ghidra_wrapper.c into the directory with libfrida-core.a and frida-core.h (distro or DEVKIT)
g++ -shared ghidra_wrapper.c ./libfrida-core.a -o libfrida-core.so
```
Libfrida-core.so should then be added to the `j`na.library.path`or put someplace like
`/usr/lib/x86_64-linux-gnu`, where it will get picked up by `Native.load()`.
### Frida Functionality
The most interesting bits of Frida are available as "methods" from the Objects Tree. For instance,
if you select a function and hit `M`, you will get a dialog with available methods. Selecting,
for example, `intercept` will bring up a second dialog with the relevant parameters. For many of
these, you will want to provide your own Javascript `on` functions, e.g. `onEnter` for the
Interceptor. Stalking is available on Threads and the individual thread entries. Scan, protect, and
watch functions are available on Memory. You can also redirect the output to GhidraScript, although
this relies on a bit of a hack. If your Javascript `Name` parameter is something like
`interpreter`, prepend `interpreter<=` to the output from your Javascript, and the results will be
passed to both the console and the script.
### State in Frida
Commands in Frida are, generally speaking, not state-dependent, i.e. they do not depend on whether
the target is running or not, only on whether the frida-agent thread is running. Many of the
gum-based commands do, however, depend on ptrace. If you have a ptrace-based debugger attached to
the target, they will time out. You can attach a debugger after Frida, but you will have to detach
it to regain the gum-based functionality. "Detach" in most debuggers includes "resume", so it is
difficult to get state other than the "initial" state from the frida-agent injection point. It
would be nice if "disconnect" worked, but "disconnect" (i.e. detach without resuming) also leaves
Frida in a partially disabled state.
### Errors in Frida
The cloaking logic in Frida, e.g. in `gum_cloak_add_thread` and `gum_cloak_index_of_thread`, is
broken as of the writing of this note. `gum_cloak_add_thread` is called for every thread, and
`gum_cloak_index_of_thread` returns a non-negative result for every call but the first. As a
result, every thread but one is cloaked, and `enumerateThreads`returns only a single thread. This is
documented in `Issue #625` for the frida-gum project. A quick fix is to comment out the cloaking
call in `frida-gum/gum/gumprocess.c::gum_emit_thread_if_not_cloaked`. Obviously, this may have
other undesirable effects, but...
The logic in the ordering of exception handlers also appears to be broken (`Issue #627`). New
handlers are appended to the queue, in most cases after `gum_exceptor_handle_scope_exception` and
`gum_quick_core_handle_crashed_js`. `gum_exceptor_handle_scope_exception` almost always returns
`TRUE`, breaking out of the queue and causing any remaining handlers to be ignored. This means any
handler added with `Process.setExceptionHandler` is likely to be ignored. A quick fix is to modify
`gum_exceptor_add` to use `g_slist_prepend instead` of `g_slist_append`.
Not really an error, but worth noting: building `libfrida-core.so` from the source may result in a
library with glib2.0 dependencies that are incompatible with the current version of Eclipse. The
not-so-simple solution is to build Eclipse on the machine that you used to build `libfrida-core`.

View File

@ -1,8 +1,8 @@
##VERSION: 2.0
##MODULE IP: Apache License 2.0
##MODULE IP: Apache License 2.0 with LLVM Exceptions
FridaNotes.txt||GHIDRA||||END|
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
build.gradle||GHIDRA||||END|
data/scripts/onAccess.js||GHIDRA||||END|
data/scripts/onAccessExt.js||GHIDRA||||END|

View File

@ -0,0 +1 @@
# Debugger-agent-gdb

View File

@ -1,7 +1,12 @@
##VERSION: 2.0
##MODULE IP: JSch License
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
data/debugger-launchers/local-gdb.bat||GHIDRA||||END|
data/debugger-launchers/qemu-gdb.bat||GHIDRA||||END|
data/debugger-launchers/remote-gdb.bat||GHIDRA||||END|
data/debugger-launchers/ssh-gdb.bat||GHIDRA||||END|
data/debugger-launchers/ssh-gdbserver.bat||GHIDRA||||END|
data/scripts/fallback_info_proc_mappings.gdb||GHIDRA||||END|
data/scripts/fallback_maintenance_info_sections.gdb||GHIDRA||||END|
data/scripts/getpid-linux-i386.gdb||GHIDRA||||END|

View File

@ -1,4 +1,5 @@
::@title gdb
::@image-opt env:OPT_TARGET_IMG
::@desc <html><body width="300px">
::@desc <h3>Launch with <tt>gdb</tt></h3>
::@desc <p>
@ -10,12 +11,12 @@
::@icon icon.debugger
::@help TraceRmiLauncherServicePlugin#gdb
::@enum StartCmd:str run start starti
::@arg :file "Image" "The target binary executable image"
::@args "Arguments" "Command-line arguments to pass to the target"
::@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_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
::@env OPT_ARCH:str="i386:x86-64" "Architecture" "Target architecture"
@echo off
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\pypkg\src
@ -30,24 +31,31 @@ IF EXIST %GHIDRA_HOME%\ghidra\.git (
)
set PYTHONPATH=%PYTHONPATH1%;%PYTHONPATH0%;%PYTHONPATH%
set target_image=%1
shift
set target_args=%*
"%OPT_GDB_PATH%" ^
-q ^
-ex "set pagination off" ^
-ex "set confirm off" ^
-ex "show version" ^
-ex "python import ghidragdb" ^
-ex "target exec %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 "%OPT_START_CMD%" ^
-ex "set confirm on" ^
-ex "set pagination on" ^
IF "%OPT_TARGET_IMG%"=="" (
"%OPT_GDB_PATH%" ^
-q ^
-ex "set pagination off" ^
-ex "set confirm off" ^
-ex "show version" ^
-ex "python import ghidragdb" ^
-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"
) ELSE (
"%OPT_GDB_PATH%" ^
-q ^
-ex "set pagination off" ^
-ex "set confirm off" ^
-ex "show version" ^
-ex "python import ghidragdb" ^
-ex "target exec %OPT_TARGET_IMG%" ^
-ex "set args %OPT_TARGET_ARGS%" ^
-ex "ghidra trace connect '%GHIDRA_TRACE_RMI_ADDR%'" ^
-ex "ghidra trace start" ^
-ex "ghidra trace sync-enable" ^
-ex "%OPT_START_CMD%" ^
-ex "set confirm on" ^
-ex "set pagination on"
)

View File

@ -1,20 +1,21 @@
#!/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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT 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 gdb
#@image-opt arg:1
#@desc <html><body width="300px">
#@desc <h3>Launch with <tt>gdb</tt></h3>
#@desc <p>
@ -26,10 +27,11 @@
#@icon icon.debugger
#@help TraceRmiLauncherServicePlugin#gdb
#@enum StartCmd:str run start starti
#@arg :file "Image" "The target binary executable image"
#@arg :file "Image" "The target binary executable image, empty for no target"
#@args "Arguments" "Command-line arguments to pass to the 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_ARCH:str="i386:x86-64" "Architecture" "Target architecture"
#@env OPT_EXTRA_TTY:bool=false "Inferior TTY" "Provide a separate terminal emulator for the target."
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
@ -50,20 +52,38 @@ target_image="$1"
shift
target_args="$@"
# NOTE: Ghidra will leave TTY_TARGET empty, which gdb takes for the same terminal.
# Ghidra will leave TTY_TARGET empty when OPT_EXTRA_TTY is false. Gdb takes empty to mean the same terminal.
"$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 "$OPT_START_CMD" \
-ex "set confirm on" \
-ex "set pagination on"
if [ -z "$target_image" ]
then
"$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 "set inferior-tty $TTY_TARGET" \
-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"
else
"$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 "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 "$OPT_START_CMD" \
-ex "set confirm on" \
-ex "set pagination on"
fi

View File

@ -0,0 +1,57 @@
::@title qemu + gdb
::@image-opt env:OPT_TARGET_IMG
::@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
::@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 GHIDRA_LANG_EXTTOOL_qemu:file="" "QEMU command" "The path to qemu for the target architecture."
::@env QEMU_GDB:int=1234 "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."
@echo off
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src
IF EXIST %GHIDRA_HOME%\.git (
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
IF EXIST %GHIDRA_HOME%\ghidra\.git (
set PYTHONPATH0=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
set PYTHONPATH=%PYTHONPATH1%;%PYTHONPATH0%;%PYTHONPATH%
IF "%OPT_EXTRA_TTY%"=="true" (
start "qemu" "%GHIDRA_LANG_EXTTOOL_qemu%" %OPT_EXTRA_QEMU_ARGS% -gdb tcp::%QEMU_GDB% -S "%OPT_TARGET_IMG%" %OPT_TARGET_ARGS%
) ELSE (
start /B "qemu" "%GHIDRA_LANG_EXTTOOL_qemu%" %OPT_EXTRA_QEMU_ARGS% -gdb tcp::%QEMU_GDB% -S "%OPT_TARGET_IMG%" %OPT_TARGET_ARGS%
)
:: Give QEMU a moment to open the socket
powershell -nop -c "& {sleep -m 100}"
"%OPT_GDB_PATH%" ^
-q ^
-ex "set pagination off" ^
-ex "set confirm off" ^
-ex "show version" ^
-ex "python import ghidragdb" ^
-ex "target exec '%OPT_TARGET_IMG%'" ^
-ex "set args %OPT_TARGET_ARGS%" ^
-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

@ -1,20 +1,21 @@
#!/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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT 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
#@image-opt arg:1
#@desc <html><body width="300px">
#@desc <h3>Launch with <tt>qemu</tt> and connect with <tt>gdb</tt></h3>
#@desc <p>
@ -26,10 +27,10 @@
#@menu-group cross
#@icon icon.debugger
#@help TraceRmiLauncherServicePlugin#gdb_qemu
#@arg :file "Image" "The target binary executable image"
#@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 QEMU_GDB:int=1234 "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."
@ -68,7 +69,6 @@ sleep 0.1
-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" \

View File

@ -1,57 +0,0 @@
#!/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,46 @@
::@title remote gdb
::@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:int=9999 "Port" "The host's listening port"
::@env OPT_ARCH:str="auto" "Architecture" "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."
@echo off
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src
IF EXIST %GHIDRA_HOME%\.git (
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
IF EXIST %GHIDRA_HOME%\ghidra\.git (
set PYTHONPATH0=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
set PYTHONPATH=%PYTHONPATH1%;%PYTHONPATH0%;%PYTHONPATH%
"%OPT_GDB_PATH%" ^
-q ^
-ex "set pagination off" ^
-ex "set confirm off" ^
-ex "show version" ^
-ex "python import ghidragdb" ^
-ex "set arch %OPT_ARCH%" ^
-ex "echo Connecting to %OPT_HOST%:%OPT_PORT%... " ^
-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

@ -15,7 +15,6 @@
# 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>
@ -30,7 +29,7 @@
#@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:int=9999 "Port" "The host's listening port"
#@env OPT_ARCH:str="" "Architecture (optional)" "Target architecture override"
#@env OPT_ARCH:str="auto" "Architecture" "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 ]
@ -46,20 +45,13 @@ else
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 "set arch $OPT_ARCH" \
-ex "echo Connecting to $OPT_HOST:$OPT_PORT... " \
-ex "target $OPT_TARGET_TYPE $OPT_HOST:$OPT_PORT" \
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \

View File

@ -0,0 +1,58 @@
::@timeout 60000
::@title gdb via ssh
::@image-opt env:OPT_TARGET_IMG
::@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
::@env OPT_TARGET_IMG:str="" "Image" "The target binary executable image on the remote system"
::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
::@env OPT_SSH_PATH:file="ssh" "ssh command" "The path to ssh on the local system. Omit the full path to resolve using the system PATH."
::@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."
::@env OPT_ARCH:str="i386:x86-64" "Architecture" "Target architecture"
@echo off
IF "%OPT_TARGET_IMG%" == "" (
set cmd=TERM='%TERM%' '%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 \"localhost:%OPT_REMOTE_PORT%\"' ^
-ex 'ghidra trace start' ^
-ex 'ghidra trace sync-enable' ^
-ex 'set confirm on' ^
-ex 'set pagination on'
) ELSE (
set cmd=TERM='%TERM%' '%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 'file \"%OPT_TARGET_IMG%\"' ^
-ex 'set args %OPT_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'
)
"%OPT_SSH_PATH%" "-R%OPT_REMOTE_PORT%:%GHIDRA_TRACE_RMI_ADDR%" -t %OPT_EXTRA_SSH_ARGS% "%OPT_HOST%" "%cmd%"

View File

@ -1,21 +1,22 @@
#!/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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT 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
#@image-opt arg:1
#@desc <html><body width="300px">
#@desc <h3>Launch with <tt>gdb</tt> via <tt>ssh</tt></h3>
#@desc <p>
@ -29,27 +30,46 @@
#@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_SSH_PATH:file!="ssh" "ssh command" "The path to ssh on the local system. Omit the full path to resolve using the system PATH."
#@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."
#@env OPT_ARCH:str="i386:x86-64" "Architecture" "Target architecture"
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'"
if [ -z "$target_image" ]
then
"$OPT_SSH_PATH" "-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 'set architecture $OPT_ARCH' \
-ex 'ghidra trace connect \"localhost:$OPT_REMOTE_PORT\"' \
-ex 'ghidra trace start' \
-ex 'ghidra trace sync-enable' \
-ex 'set confirm on' \
-ex 'set pagination on'"
else
"$OPT_SSH_PATH" "-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 'set architecture $OPT_ARCH' \
-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'"
fi

View File

@ -0,0 +1,48 @@
::@timeout 60000
::@title gdb + gdbserver via ssh
::@image-opt env:OPT_TARGET_IMG
::@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
::@env OPT_TARGET_IMG:str!="" "Image" "The target binary executable image on the remote system"
::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
::@env OPT_SSH_PATH:file="ssh" "ssh command" "The path to ssh on the local system. Omit the full path to resolve using the system PATH."
::@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."
@echo off
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src
IF EXIST %GHIDRA_HOME%\.git (
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
IF EXIST %GHIDRA_HOME%\ghidra\.git (
set PYTHONPATH0=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-agent-gdb\build\pypkg\src
set PYTHONPATH1=%GHIDRA_HOME%\ghidra\Ghidra\Debug\Debugger-rmi-trace\build\pypkg\src
)
set PYTHONPATH=%PYTHONPATH1%;%PYTHONPATH0%;%PYTHONPATH%
"%OPT_GDB_PATH%" ^
-q ^
-ex "set pagination off" ^
-ex "set confirm off" ^
-ex "show version" ^
-ex "python import ghidragdb" ^
-ex "target remote | '%OPT_SSH_PATH%' %OPT_EXTRA_SSH_ARGS% '%OPT_HOST%' '%OPT_GDBSERVER_PATH%' %OPT_EXTRA_GDBSERVER_ARGS% - '%OPT_TARGET_IMG%' %OPT_TARGET_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

@ -1,21 +1,22 @@
#!/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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT 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
#@image-opt arg:1
#@desc <html><body width="300px">
#@desc <h3>Launch with local <tt>gdb</tt> and <tt>gdbserver</tt> via <tt>ssh</tt></h3>
#@desc <p>
@ -26,8 +27,9 @@
#@menu-group remote
#@icon icon.debugger
#@help TraceRmiLauncherServicePlugin#gdb_gdbserver_ssh
#@arg :str "Image" "The target binary executable image on the remote system"
#@arg :str! "Image" "The target binary executable image on the remote system"
#@args "Arguments" "Command-line arguments to pass to the target"
#@env OPT_SSH_PATH:file!="ssh" "ssh command" "The path to ssh on the local system. Omit the full path to resolve using the system PATH."
#@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."
@ -53,8 +55,7 @@ fi
-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 "target remote | '$OPT_SSH_PATH' $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" \

View File

@ -1,20 +1,21 @@
#!/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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT 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
#@image-opt arg:1
#@desc <html><body width="300px">
#@desc <h3>Launch with <tt>gdb</tt> and <tt>wine</tt></h3>
#@desc <p>
@ -25,10 +26,11 @@
#@menu-group cross
#@icon icon.debugger
#@help TraceRmiLauncherServicePlugin#gdb_wine
#@arg :file "Image" "The target binary executable image"
#@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_ARCH:str="i386:x86-64" "Architecture" "Target architecture"
#@env OPT_EXTRA_TTY:bool=false "Inferior TTY" "Provide a separate terminal emulator for the target."
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
@ -53,6 +55,7 @@ fi
-ex "set confirm off" \
-ex "show version" \
-ex "python import ghidragdb.wine" \
-ex "set architecture $OPT_ARCH" \
-ex "file \"$OPT_WINE_PATH\"" \
-ex "set args $@" \
-ex "set inferior-tty $TTY_TARGET" \

View File

@ -0,0 +1 @@
# Debugger-agent-lldb

View File

@ -2,6 +2,7 @@
##MODULE IP: Apache License 2.0
##MODULE IP: Apache License 2.0 with LLVM Exceptions
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
build.gradle||GHIDRA||||END|
data/debugger-launchers/local-lldb.bat||GHIDRA||||END|
src/llvm-project/lldb/bindings/java/java-typemaps.swig||Apache License 2.0 with LLVM Exceptions||||END|

View File

@ -1,4 +1,5 @@
::@title lldb
::@image-opt arg:1
::@desc <html><body width="300px">
::@desc <h3>Launch with <tt>lldb</tt></h3>
::@desc <p>
@ -14,8 +15,6 @@
::@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
@echo off
set PYTHONPATH0=%GHIDRA_HOME%\Ghidra\Debug\Debugger-agent-gdb\pypkg\src
@ -38,17 +37,21 @@ IF DEFINED target_args (
argspart=-o "settings set target.run-args %target_args%"
)
IF DEFINED TARGET_TTY (
ttypart=-o "settings set target.output-path %TTY_TARGET%" -o "settings set target.input-path $TTY_TARGET"
IF "%target_image%"=="" (
"%OPT_LLDB_PATH%" ^
-o "version" ^
-o "script import ghidralldb" ^
-o "ghidra trace connect %GHIDRA_TRACE_RMI_ADDR%" ^
-o "ghidra trace start" ^
-o "ghidra trace sync-enable" ^
) ELSE (
"%OPT_LLDB_PATH%" ^
-o "version" ^
-o "script import ghidralldb" ^
-o "target create %target_image%" ^
%argspart% ^
-o "ghidra trace connect %GHIDRA_TRACE_RMI_ADDR%" ^
-o "ghidra trace start" ^
-o "ghidra trace sync-enable" ^
-o "%OPT_START_CMD%"
)
"%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

@ -1,20 +1,21 @@
#!/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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT 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
#@image-opt arg:1
#@desc <html><body width="300px">
#@desc <h3>Launch with <tt>lldb</tt></h3>
#@desc <p>
@ -64,13 +65,24 @@ 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"
if [ -z "$target_image" ]
then
"$OPT_LLDB_PATH" \
-o "version" \
-o "script import ghidralldb" \
$ttypart \
-o "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
-o "ghidra trace start" \
-o "ghidra trace sync-enable"
else
"$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"
fi

View File

@ -1,21 +1,20 @@
#!/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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT 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>
@ -59,5 +58,4 @@ fi
-o "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
-o "ghidra trace start" \
-o "ghidra trace sync-enable" \
-o "ghidra trace sync-synth-stopped"
-o "ghidra trace sync-synth-stopped"

View File

@ -0,0 +1 @@
# Debugger-api

View File

@ -1,2 +1,3 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|

View File

@ -17,6 +17,7 @@ package ghidra.debug.api;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;
public record ValStr<T>(T val, String str) {
@ -56,4 +57,18 @@ public record ValStr<T>(T val, String str) {
.stream()
.collect(Collectors.toMap(Entry::getKey, e -> e.getValue().val()));
}
public static String normStr(ValStr<?> val) {
if (val == null) {
return "";
}
return val.normStr();
}
public String normStr() {
if (val == null) {
return "";
}
return Objects.toString(val);
}
}

View File

@ -295,10 +295,29 @@ public interface TraceRmiLaunchOffer {
*/
Map<String, LaunchParameter<?>> getParameters();
/**
* If present, get the parameter via which this offer expects to receive the current program
*
* @return the parameter, or null
*/
LaunchParameter<?> imageParameter();
/**
* Check if this offer presents a parameter for the open program
*
* @return true if present
*/
default boolean supportsImage() {
return imageParameter() != null;
}
/**
* Check if this offer requires an open program
*
* @return true if required
*/
boolean requiresImage();
default boolean requiresImage() {
LaunchParameter<?> param = imageParameter();
return param != null && param.required();
}
}

View File

@ -0,0 +1 @@
# Debugger-gadp

View File

@ -1,2 +1,3 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|

View File

@ -0,0 +1 @@
# Debugger-isf

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -31,11 +31,3 @@ dependencies {
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
}
// Include buildable native source in distribution
rootProject.assembleDistribution {
from (this.project.projectDir.toString()) {
include "runISFServer"
into { getZipPath(this.project) }
}
}

View File

@ -1,3 +1,4 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
runISFServer||GHIDRA||||END|
README.md||GHIDRA||||END|
support/runISFServer||GHIDRA||||END|

View File

@ -26,9 +26,9 @@ VMARG_LIST="-XX:ParallelGCThreads=2 -XX:CICompilerCount=2 "
# fallback, which doesn't attempt to do anything with links.
SCRIPT_FILE="$(readlink -f "$0" 2>/dev/null || readlink "$0" 2>/dev/null || echo "$0")"
SCRIPT_DIR="${SCRIPT_FILE%/*}"
SUPPORT_DIR="${SCRIPT_DIR}/../../../support"
SUPPORT_DIR="${SCRIPT_DIR}/../../../../support"
if ! [ -f "${SUPPORT_DIR}/launch.properties" ]; then
SUPPORT_DIR="${SCRIPT_DIR}/../../RuntimeScripts/Linux/support"
SUPPORT_DIR="${SCRIPT_DIR}/../../../RuntimeScripts/Linux/support"
fi
# Launch ISF Server.

View File

@ -0,0 +1 @@
# Debugger-jpda

View File

@ -1,5 +1,6 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
data/debugger-launchers/attach-java.jsh||GHIDRA||||END|
data/debugger-launchers/bypid-java.jsh||GHIDRA||||END|
data/debugger-launchers/local-java.jsh||GHIDRA||||END|

View File

@ -1,5 +1,6 @@
//@title java launch
//@timeout 20000
//@title java
////@image-opt env:OPT_TARGET_CLASS
//@timeout 2000000
//@desc <html><body width="300px">
//@desc <h3>Launch with <tt>java</tt></h3>
//@desc <p>
@ -10,10 +11,11 @@
//@menu-group local
//@icon icon.debugger
//@help TraceRmiLauncherServicePlugin#java
//@env OPT_TARGET_CLASS:str="" "Image" "The Main Class to launch (defaults to current program)."
//@env OPT_TARGET_CLASSPATH:str="" "ClassPath" "The JVM classpath"
//@args "Arguments" "Command-line arguments to pass to the target"
//@enum Arch:str JVM Dalvik
//@env OPT_ARCH:Arch="JVM" "Arch" "Either 'JVM' or 'Dalvik'"
//@env OPT_TARGET_CLASS:str="" "Image" "The Main Class to launch (defaults to current program)."
////@env OPT_SUSPEND:bool=true "Suspend" "Suspend the VM on launch."
//@env OPT_INCLUDE:str=n "Include virtual threads" "Include virtual threads."
//@env OPT_JSHELL_PATH:file="" "JShell cmd (if desired)" "The full path to jshell."

View File

@ -140,6 +140,7 @@ public class JdiEventHandler implements Runnable {
//System.err.println(event + ":" + vm);
return switch (event) {
case ExceptionEvent ev -> processException(ev);
case BreakpointEvent ev -> processBreakpoint(ev);
case AccessWatchpointEvent ev -> processAccessWatchpoint(ev);
case ModificationWatchpointEvent ev -> processWatchpointModification(ev);
case WatchpointEvent ev -> processWatchpoint(ev);

View File

@ -17,7 +17,7 @@ package ghidra.dbg.jdi.manager.impl;
import static ghidra.lifecycle.Unfinished.TODO;
import java.io.IOException;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
@ -29,6 +29,7 @@ import com.sun.jdi.event.Event;
import ghidra.dbg.jdi.manager.*;
import ghidra.dbg.jdi.manager.JdiCause.Causes;
import ghidra.dbg.jdi.rmi.jpda.JdiArguments;
import ghidra.util.Msg;
import ghidra.util.datastruct.ListenerSet;
@ -61,23 +62,40 @@ public class JdiManagerImpl implements JdiManager {
virtualMachineManager = Bootstrap.virtualMachineManager();
}
private static void pumpStream(InputStream in, OutputStream out) {
try {
in.transferTo(out);
}
catch (IOException e) {
// We're done!
}
}
public VirtualMachine connectVM(Connector cx, Map<String, Connector.Argument> arguments)
throws Exception {
if (cx instanceof LaunchingConnector) {
LaunchingConnector lcx = (LaunchingConnector) cx;
return lcx.launch(arguments);
if (cx instanceof LaunchingConnector lcx) {
VirtualMachine vm = lcx.launch(arguments);
new Thread(() -> pumpStream(vm.process().getErrorStream(), System.err)).start();
new Thread(() -> pumpStream(vm.process().getInputStream(), System.out)).start();
return vm;
}
if (cx instanceof AttachingConnector) {
AttachingConnector acx = (AttachingConnector) cx;
if (cx instanceof AttachingConnector acx) {
return acx.attach(arguments);
}
if (cx instanceof ListeningConnector) {
ListeningConnector lcx = (ListeningConnector) cx;
if (cx instanceof ListeningConnector lcx) {
return lcx.accept(arguments);
}
throw new Exception("Unknown connector type");
}
public VirtualMachine createVM(Map<String, String> env) {
JdiArguments args = new JdiArguments(env);
Connector cx = args.getConnector(virtualMachineManager);
Map<String, Connector.Argument> defaultArguments = cx.defaultArguments();
args.putArguments(defaultArguments);
return addVM(cx, defaultArguments);
}
@Override
public void terminate() {
/**

View File

@ -23,7 +23,7 @@ import ghidra.app.plugin.core.debug.client.tracermi.DefaultRegisterMapper;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
public class TraceJdiArch {
public class JdiArch {
private LanguageID langID;
private Language language;

View File

@ -0,0 +1,109 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.jdi.rmi.jpda;
import java.util.Map;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.Connector.Argument;
import ghidra.dbg.util.ShellUtils;
public class JdiArguments {
enum Mode {
ATTACH_PORT, ATTACH_PID, LAUNCH;
}
private final Map<String, String> env;
private final Mode mode;
public JdiArguments(Map<String, String> env) {
this.env = Map.copyOf(env);
this.mode = computeMode();
}
/**
* Compute/detect the launch mode using the environment map.
*
* <p>
* It'd be nice if this were selected/specified in the script body, rather than by what options
* are present in its header. The reason we can't, though, is that the JDI client thread needs
* to also work within Ghidra's JVM, i.e., without launching a jshell subprocess. By far, the
* simplest way to accomplish this is to keep all the logic here, and just pass the environment
* map in. For the jshell-subprocess case, it's the environment map proper. For the
* in-Ghidra's-VM case, it's the map we would have passed when creating the subprocess.
*
* @return the mode.
*/
protected Mode computeMode() {
if (env.containsKey("OPT_PORT")) {
return Mode.ATTACH_PORT;
}
if (env.containsKey("OPT_PID")) {
return Mode.ATTACH_PID;
}
return Mode.LAUNCH;
}
protected AttachingConnector findConnectorByArgKey(VirtualMachineManager vmm, String key) {
return vmm.attachingConnectors()
.stream()
.filter(ac -> ac.defaultArguments().containsKey(key))
.findFirst()
.orElseThrow();
}
public Connector getConnector(VirtualMachineManager vmm) {
return switch (mode) {
case ATTACH_PORT -> findConnectorByArgKey(vmm, "port");
case ATTACH_PID -> findConnectorByArgKey(vmm, "pid");
case LAUNCH -> vmm.defaultConnector();
};
}
public void putArguments(Map<String, Argument> args) {
switch (mode) {
case ATTACH_PORT -> {
args.get("hostname").setValue(env.get("OPT_HOST").toString());
args.get("port").setValue(env.get("OPT_PORT").toString());
args.get("timeout").setValue(env.get("OPT_TIMEOUT").toString());
}
case ATTACH_PID -> {
args.get("pid").setValue(env.get("OPT_PID").toString());
args.get("timeout").setValue(env.get("OPT_TIMEOUT").toString());
}
case LAUNCH -> {
args.get("main").setValue(env.get("OPT_TARGET_CLASS"));
Argument argSuspend = args.get("suspend");
String optSuspend = env.get("OPT_SUSPEND");
if (argSuspend != null && optSuspend != null) {
argSuspend.setValue(optSuspend);
}
Argument argIncludeVirtualThreads = args.get("includevirtualthreads");
String optInclude = env.get("OPT_INCLUDE");
if (argIncludeVirtualThreads != null && optInclude != null) {
argIncludeVirtualThreads.setValue(optInclude);
}
String cp = env.get("OPT_TARGET_CLASSPATH");
if (!cp.isBlank()) {
args.get("options").setValue("-cp " + ShellUtils.generateArgument(cp));
}
}
}
}
}

View File

@ -17,7 +17,6 @@ package ghidra.dbg.jdi.rmi.jpda;
import java.util.Map;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.Connector.Argument;
@ -26,70 +25,31 @@ import ghidra.dbg.jdi.manager.impl.JdiManagerImpl;
import ghidra.util.Msg;
public class JdiClientThread extends Thread {
enum Mode {
ATTACH_PORT, ATTACH_PID, LAUNCH;
}
private final Map<String, String> env;
private final Mode mode;
private final JdiArguments arguments;
private JdiManagerImpl manager;
private TraceJdiManager traceJdiManager;
private JdiManager jdiManager;
public JdiClientThread(Map<String, String> env) {
this.env = env;
this.mode = computeMode();
}
/**
* Compute/detect the launch mode using the environment map.
*
* <p>
* It'd be nice if this were selected/specified in the script body, rather than by what options
* are present in its header. The reason we can't, though, is that the JDI client thread needs
* to also work within Ghidra's JVM, i.e., without launching a jshell subprocess. By far, the
* simplest way to accomplish this is to keep all the logic here, and just pass the environment
* map in. For the jshell-subprocess case, it's the environment map proper. For the
* in-Ghidra's-VM case, it's the map we would have passed when creating the subprocess.
*
* @return the mode.
*/
Mode computeMode() {
if (env.containsKey("OPT_PORT")) {
return Mode.ATTACH_PORT;
}
if (env.containsKey("OPT_PID")) {
return Mode.ATTACH_PID;
}
return Mode.LAUNCH;
}
AttachingConnector findConnectorByArgKey(String key) {
return manager.getVirtualMachineManager()
.attachingConnectors()
.stream()
.filter(ac -> ac.defaultArguments().containsKey(key))
.findFirst()
.orElseThrow();
this.arguments = new JdiArguments(env);
}
@Override
public void run() {
try {
manager = new JdiManagerImpl();
traceJdiManager = new TraceJdiManager(manager, env);
jdiManager = new JdiManager(manager, env);
Connector cx = switch (mode) {
case ATTACH_PORT -> findConnectorByArgKey("port");
case ATTACH_PID -> findConnectorByArgKey("pid");
case LAUNCH -> manager.getVirtualMachineManager().defaultConnector();
};
Connector cx = arguments.getConnector(manager.getVirtualMachineManager());
Map<String, Argument> args = cx.defaultArguments();
putArguments(args);
arguments.putArguments(args);
if (manager.addVM(cx, args) != null) {
traceJdiManager.getCommands().ghidraTraceSyncEnable();
traceJdiManager.getHooks().vmStarted(null, Causes.UNCLAIMED);
jdiManager.getCommands().ghidraTraceSyncEnable();
jdiManager.getHooks().vmStarted(null, Causes.UNCLAIMED);
}
else {
// Nothing. addVM should already have reported the error.
@ -100,30 +60,11 @@ public class JdiClientThread extends Thread {
}
}
protected void putArguments(Map<String, Argument> args) {
switch (mode) {
case ATTACH_PORT -> {
args.get("hostname").setValue(env.get("OPT_HOST").toString());
args.get("port").setValue(env.get("OPT_PORT").toString());
args.get("timeout").setValue(env.get("OPT_TIMEOUT").toString());
}
case ATTACH_PID -> {
args.get("pid").setValue(env.get("OPT_PID").toString());
args.get("timeout").setValue(env.get("OPT_TIMEOUT").toString());
}
case LAUNCH -> {
args.get("main").setValue(env.get("OPT_TARGET_CLASS"));
//args.get("suspend").setValue(env.get("OPT_SUSPEND"));
args.get("includevirtualthreads").setValue(env.get("OPT_INCLUDE"));
}
}
public JdiManager mgr() {
return jdiManager;
}
public TraceJdiManager mgr() {
return traceJdiManager;
}
public TraceJdiCommands cmds() {
return traceJdiManager.getCommands();
public JdiCommands cmds() {
return jdiManager.getCommands();
}
}

View File

@ -15,47 +15,35 @@
*/
package ghidra.dbg.jdi.rmi.jpda;
import static ghidra.dbg.jdi.rmi.jpda.JdiManager.*;
import java.util.*;
import com.sun.jdi.*;
import com.sun.jdi.event.*;
import ghidra.app.plugin.core.debug.client.tracermi.RmiTrace;
import ghidra.app.plugin.core.debug.client.tracermi.RmiTransaction;
import ghidra.app.plugin.core.debug.client.tracermi.*;
import ghidra.dbg.jdi.manager.*;
import ghidra.dbg.jdi.manager.impl.DebugStatus;
import ghidra.dbg.jdi.manager.impl.JdiManagerImpl;
class HookState {
private TraceJdiCommands cmds;
private Object batch;
private JdiCommands cmds;
public HookState(TraceJdiCommands cmds) {
public HookState(JdiCommands cmds) {
this.cmds = cmds;
this.batch = null;
}
public void ensureBatch() {
if (batch == null) {
batch = cmds.state.client.startBatch();
}
public RmiBatch batch() {
return cmds.state.client.startBatch();
}
public void endBatch() {
if (batch == null) {
return;
}
batch = null;
cmds.state.client.endBatch();
}
}
class VmState {
private TraceJdiManager manager;
private TraceJdiCommands cmds;
private JdiManager manager;
private JdiCommands cmds;
private boolean firstPass;
boolean classes;
boolean modules;
@ -65,7 +53,7 @@ class VmState {
boolean events;
Set<Object> visited;
public VmState(TraceJdiManager manager) {
public VmState(JdiManager manager) {
this.manager = manager;
this.cmds = manager.getCommands();
this.firstPass = true;
@ -109,9 +97,14 @@ class VmState {
}
StackFrame frame = manager.getJdi().getCurrentFrame();
if (frame != null) {
if (first || !visited.contains(frame)) {
cmds.putReg(frame);
visited.add(frame);
try {
if (first || !visited.contains(frame)) {
cmds.putReg(frame);
visited.add(frame);
}
}
catch (InvalidStackFrameException e) {
manager.getJdi().setCurrentFrame(null);
}
}
}
@ -154,7 +147,7 @@ class VmState {
Process proc = vm.process();
String path = cmds.getPath(proc);
if (path != null) {
cmds.setValue(path, "Alive", proc.isAlive());
cmds.setValue(path, ATTR_ALIVE, proc.isAlive());
}
setState(vm);
}
@ -168,8 +161,8 @@ class VmState {
if (process != null) {
exitCode = process.exitValue();
String procpath = cmds.getPath(vm.process());
cmds.setValue(procpath, "ExitCode", exitCode);
cmds.setValue(procpath, TraceJdiManager.STATE_ATTRIBUTE_NAME, "TERMINATED");
cmds.setValue(procpath, ATTR_EXIT_CODE, exitCode);
cmds.setValue(procpath, ATTR_STATE, "TERMINATED");
}
}
catch (IllegalThreadStateException e) {
@ -178,25 +171,25 @@ class VmState {
if (description != null) {
cmds.state.trace.snapshot(description, "", null);
}
cmds.setValue(path, "ExitCode", exitCode);
cmds.setValue(path, TraceJdiManager.STATE_ATTRIBUTE_NAME, "TERMINATED");
cmds.setValue(path, ATTR_EXIT_CODE, exitCode);
cmds.setValue(path, ATTR_STATE, "TERMINATED");
}
}
public class TraceJdiHooks implements JdiEventsListenerAdapter {
public class JdiHooks implements JdiEventsListenerAdapter {
private TraceJdiManager manager;
private TraceJdiCommands cmds;
private JdiManager manager;
private JdiCommands cmds;
private HookState hookState;
private Map<VirtualMachine, VmState> vmStates = new HashMap<>();
public TraceJdiHooks(TraceJdiManager manager) {
public JdiHooks(JdiManager manager, JdiCommands cmds) {
this.manager = manager;
this.cmds = manager.getCommands();
this.cmds = cmds;
}
private void setCommands(TraceJdiCommands commands) {
private void setCommands(JdiCommands commands) {
this.cmds = commands;
hookState = new HookState(commands);
}
@ -204,17 +197,19 @@ public class TraceJdiHooks implements JdiEventsListenerAdapter {
@Override
public DebugStatus vmStarted(VMStartEvent event, JdiCause cause) {
setCommands(manager.getCommands());
hookState.ensureBatch();
RmiTrace trace = cmds.state.trace;
JdiManagerImpl jdi = manager.getJdi();
VirtualMachine vm = event == null ? jdi.getCurrentVM() : event.virtualMachine();
try (RmiTransaction tx = trace.openTx("New VM " + vm.description())) {
jdi.setCurrentVM(vm);
jdi.addVM(vm);
jdi.setCurrentVM(vm);
jdi.addVM(vm);
RmiTrace trace = cmds.state.trace;
if (trace == null) {
return DebugStatus.NO_CHANGE;
}
try (RmiBatch batch = hookState.batch();
RmiTransaction tx = trace.openTx("New VM " + vm.description())) {
cmds.putVMs();
enableCurrentVM();
}
hookState.endBatch();
return DebugStatus.NO_CHANGE;
}
@ -417,12 +412,11 @@ public class TraceJdiHooks implements JdiEventsListenerAdapter {
}
VmState state = vmStates.get(vm);
state.visited.clear();
hookState.ensureBatch();
try (RmiTransaction tx = trace.openTx("Stopped")) {
try (RmiBatch batch = hookState.batch();
RmiTransaction tx = trace.openTx("Stopped")) {
state.recordState("Stopped");
cmds.activate(null);
}
hookState.endBatch();
}
private void setCurrent(Event event) {
@ -446,12 +440,11 @@ public class TraceJdiHooks implements JdiEventsListenerAdapter {
void onContinue() {
VirtualMachine currentVM = manager.getJdi().getCurrentVM();
VmState state = vmStates.get(currentVM);
hookState.ensureBatch();
try (RmiTransaction tx = cmds.state.trace.openTx("Continue")) {
try (RmiBatch batch = hookState.batch();
RmiTransaction tx = cmds.state.trace.openTx("Continue")) {
state.recordStateContinued(currentVM);
cmds.activate(null);
}
hookState.endBatch();
}
public void installHooks() {

View File

@ -21,6 +21,7 @@ import java.util.Map;
import com.sun.jdi.*;
import ghidra.app.plugin.core.debug.client.tracermi.*;
import ghidra.app.plugin.core.debug.client.tracermi.RmiMethodRegistry.TraceMethod;
import ghidra.dbg.jdi.manager.impl.DebugStatus;
import ghidra.dbg.jdi.manager.impl.JdiManagerImpl;
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema;
@ -28,65 +29,87 @@ import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.program.model.address.*;
import ghidra.util.Msg;
public class TraceJdiManager {
public class JdiManager {
private static final int STATIC_METHOD_SEPARATION = 3;
public static final long BLOCK_SIZE = 0x1000L;
public static final long DEFAULT_SECTION = 0x0000L;
public static final String PREFIX_INVISIBLE = "_";
public static final String DISPLAY_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "display";
public static final String STATE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "state";
public static final String MODULE_NAME_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "module_name";
public static final String ARCH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "arch";
public static final String DEBUGGER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "debugger";
public static final String OS_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "os";
public static final String ENDIAN_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "endian";
public static final String ACCESSIBLE_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "accessible";
public static final String ATTR_DISPLAY = "_display";
public static final String ATTR_STATE = "_state";
public static final String ATTR_MODULE_NAME = "_module_name";
public static final String ATTR_ARCH = "_arch";
public static final String ATTR_DEBUGGER = "_debugger";
public static final String ATTR_OS = "_os";
public static final String ATTR_ENDIAN = "_endian";
public static final String ATTR_ACCESSIBLE = "_accessible";
public static final String ATTR_ADDRESS = "Address";
public static final String ATTR_ALIVE = "Alive";
public static final String ATTR_CLASS = "Class";
public static final String ATTR_COMMAND_LINE = "CommandLine";
public static final String ATTR_COUNT = "Count";
public static final String ATTR_ENABLED = "Enabled";
public static final String ATTR_EXECUTABLE = "Executable";
public static final String ATTR_EXIT_CODE = "ExitCode";
public static final String ATTR_INDEX = "Index";
public static final String ATTR_INSTANCE = "Instance";
public static final String ATTR_LENGTH = "Length";
public static final String ATTR_LINENO = "LineNo";
public static final String ATTR_LOCATION = "Location";
public static final String ATTR_NAME = "Name";
public static final String ATTR_PC = "PC";
public static final String ATTR_PLATFORM_ONLY = "PlatformOnly";
public static final String ATTR_RANGE = "Range";
public static final String ATTR_RANGE_CP = "RangeCP"; // Constant pool
public static final String ATTR_SIGNATURE = "Signature";
public static final String ATTR_THREAD = "Thread";
public static final String ATTR_TYPE = "Type";
public static final String ATTR_VALUE = "Value";
public static final String ATTR_EXCLUDE = "Exclude";
public static final String ATTR_INCLUDE = "Include";
private JdiManagerImpl manager;
private TraceJdiArch arch;
private TraceJdiHooks hooks;
private TraceJdiMethods methods;
private TraceJdiCommands commands;
private final JdiManagerImpl manager;
private final JdiArch arch;
private final JdiHooks hooks;
private final JdiMethods methods;
private final JdiCommands commands;
Map<String, Object> objectRegistry = new HashMap<>();
Map<Object, String> reverseRegistry = new HashMap<>();
RmiMethodRegistry remoteMethodRegistry = new RmiMethodRegistry();
Map<Object, Boolean> scopeRegistry = new HashMap<>();
final Map<String, Object> objectRegistry = new HashMap<>();
final Map<Object, String> reverseRegistry = new HashMap<>();
final RmiMethodRegistry remoteMethodRegistry = new RmiMethodRegistry();
final Map<Object, Boolean> scopeRegistry = new HashMap<>();
protected final AddressSpace ram = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 0);
protected Long ramIndex = Long.valueOf(BLOCK_SIZE);
protected Long ramIndex = BLOCK_SIZE;
protected final AddressSpace pool =
new GenericAddressSpace("constantPool", 64, AddressSpace.TYPE_RAM, 0);
protected Long poolIndex = Long.valueOf(0x0L);
public AddressRangeImpl defaultRange;
protected Long poolIndex = 0x0L;
public final AddressRangeImpl defaultRange;
private Map<String, AddressRange> addressRangeByMethod = new HashMap<>();
private Map<String, Method> methodsByKey = new HashMap<>();
private Map<ReferenceType, AddressRange> addressRangeByClass = new HashMap<>();
private Map<ReferenceType, AddressRange> cpAddressRangeByClass = new HashMap<>();
private final Map<String, AddressRange> addressRangeByMethod = new HashMap<>();
private final Map<String, Method> methodsByKey = new HashMap<>();
private final Map<ReferenceType, AddressRange> addressRangeByClass = new HashMap<>();
private final Map<ReferenceType, AddressRange> cpAddressRangeByClass = new HashMap<>();
private Map<String, DebugStatus> returnStatusMap = new HashMap<>();
TargetObjectSchema rootSchema;
private final Map<String, DebugStatus> returnStatusMap = new HashMap<>();
final TargetObjectSchema rootSchema;
public TraceJdiManager(JdiManagerImpl manager, Map<String, String> env) {
public JdiManager(JdiManagerImpl manager, Map<String, String> env) {
this(manager);
commands.ghidraTraceConnect(env.get("GHIDRA_TRACE_RMI_ADDR"));
commands.ghidraTraceStart(env.get("OPT_TARGET_CLASS"));
}
// NB: Needed for testing
public TraceJdiManager(JdiManagerImpl manager) {
public JdiManager(JdiManagerImpl manager) {
this.manager = manager;
Address start = ram.getAddress(DEFAULT_SECTION);
defaultRange = new AddressRangeImpl(start, start.add(BLOCK_SIZE - 1));
rootSchema = RmiClient.loadSchema("jdi_schema.xml", "Debugger");
arch = new TraceJdiArch();
commands = new TraceJdiCommands(this); // Must precede methods/hooks
methods = new TraceJdiMethods(this);
hooks = new TraceJdiHooks(this);
arch = new JdiArch();
commands = new JdiCommands(this); // Must precede methods/hooks
methods = new JdiMethods(this, commands);
hooks = new JdiHooks(this, commands);
hooks.installHooks();
}
@ -94,19 +117,19 @@ public class TraceJdiManager {
return manager;
}
public TraceJdiArch getArch() {
public JdiArch getArch() {
return arch;
}
public TraceJdiCommands getCommands() {
public JdiCommands getCommands() {
return commands;
}
public TraceJdiMethods getMethods() {
public JdiMethods getMethods() {
return methods;
}
public TraceJdiHooks getHooks() {
public JdiHooks getHooks() {
return hooks;
}
@ -114,12 +137,11 @@ public class TraceJdiManager {
return commands.state.client;
}
public void registerRemoteMethod(TraceJdiMethods methods, java.lang.reflect.Method m,
String name) {
public void registerRemoteMethod(JdiMethods methods, java.lang.reflect.Method m, String name) {
String action = name;
String display = name;
String description = name;
RmiMethodRegistry.TraceMethod annot = m.getAnnotation(RmiMethodRegistry.TraceMethod.class);
TraceMethod annot = m.getAnnotation(TraceMethod.class);
if (annot == null) {
return;
}
@ -134,6 +156,10 @@ public class TraceJdiManager {
if (pcount < 1) {
return;
}
/**
* TODO: The return type should be reflected from the method; however, none of the parameter
* collection routines currently use the return type, so just use ANY for now.
*/
TargetObjectSchema schema = EnumerableTargetObjectSchema.ANY;
RmiRemoteMethod method = new RmiRemoteMethod(rootSchema.getContext(), name, action, display,
description, schema, methods, m);
@ -152,27 +178,33 @@ public class TraceJdiManager {
}
public AddressRange putAddressRange(ReferenceType cls, AddressSet set) {
if (set.isEmpty()) {
addressRangeByClass.put(cls, defaultRange);
return defaultRange;
synchronized (addressRangeByClass) {
if (set.isEmpty()) {
addressRangeByClass.put(cls, defaultRange);
return defaultRange;
}
AddressRange range = new AddressRangeImpl(set.getMinAddress(), set.getMaxAddress());
addressRangeByClass.put(cls, range);
return range;
}
AddressRange range = new AddressRangeImpl(set.getMinAddress(), set.getMaxAddress());
addressRangeByClass.put(cls, range);
return range;
}
public AddressRange getAddressRange(ReferenceType cls) {
if (cls == null) {
return defaultRange;
}
return addressRangeByClass.get(cls);
synchronized (addressRangeByClass) {
return addressRangeByClass.get(cls);
}
}
public ReferenceType getReferenceTypeForAddress(Address address) {
for (ReferenceType ref : addressRangeByClass.keySet()) {
AddressRange range = addressRangeByClass.get(ref);
if (range.contains(address)) {
return ref;
synchronized (addressRangeByClass) {
for (ReferenceType ref : addressRangeByClass.keySet()) {
AddressRange range = addressRangeByClass.get(ref);
if (range.contains(address)) {
return ref;
}
}
}
return null;
@ -182,19 +214,21 @@ public class TraceJdiManager {
if (cls == null) {
return defaultRange;
}
AddressRange range = cpAddressRangeByClass.get(cls);
if (range == null) {
registerConstantPool(cls, sz);
range = cpAddressRangeByClass.get(cls);
synchronized (cpAddressRangeByClass) {
AddressRange range = cpAddressRangeByClass.get(cls);
if (range == null) {
registerConstantPool(cls, sz);
range = cpAddressRangeByClass.get(cls);
}
return range;
}
return range;
}
public void registerConstantPool(ReferenceType declaringType, int sz) {
if (!cpAddressRangeByClass.containsKey(declaringType)) {
if (manager.getCurrentVM().canGetConstantPool()) {
long length = sz == 0 ? 2 : sz;
synchronized (cpAddressRangeByClass) {
synchronized (cpAddressRangeByClass) {
if (!cpAddressRangeByClass.containsKey(declaringType)) {
if (manager.getCurrentVM().canGetConstantPool()) {
long length = sz == 0 ? 2 : sz;
Address start = pool.getAddress(poolIndex);
AddressRangeImpl range =
new AddressRangeImpl(start, start.add(length - 1));
@ -208,10 +242,12 @@ public class TraceJdiManager {
}
public ReferenceType getReferenceTypeForPoolAddress(Address address) {
for (ReferenceType ref : cpAddressRangeByClass.keySet()) {
AddressRange range = cpAddressRangeByClass.get(ref);
if (range.contains(address)) {
return ref;
synchronized (cpAddressRangeByClass) {
for (ReferenceType ref : cpAddressRangeByClass.keySet()) {
AddressRange range = cpAddressRangeByClass.get(ref);
if (range.contains(address)) {
return ref;
}
}
}
return null;
@ -221,21 +257,25 @@ public class TraceJdiManager {
if (method == null) {
return defaultRange;
}
AddressRange range = addressRangeByMethod.get(methodToKey(method));
if (range == null) {
return defaultRange;
synchronized (addressRangeByMethod) {
AddressRange range = addressRangeByMethod.get(methodToKey(method));
if (range == null) {
return defaultRange;
}
return range;
}
return range;
}
public Method getMethodForAddress(Address address) {
for (String methodName : addressRangeByMethod.keySet()) {
AddressRange range = addressRangeByMethod.get(methodName);
if (range.contains(address)) {
return methodsByKey.get(methodName);
synchronized (addressRangeByMethod) {
for (String methodName : addressRangeByMethod.keySet()) {
AddressRange range = addressRangeByMethod.get(methodName);
if (range.contains(address)) {
return methodsByKey.get(methodName);
}
}
return null;
}
return null;
}
public Address getAddressFromLocation(Location location) {
@ -364,5 +404,4 @@ public class TraceJdiManager {
}
}
}
}

View File

@ -162,7 +162,7 @@
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
<attribute name="Events" schema="EventContainer" required="yes" fixed="yes" />
<attribute name="Classes" schema="ReferenceTypeContainer" required="yes" fixed="yes" />
<attribute name="Classes" schema="CanonicalReferenceTypeContainer" required="yes" fixed="yes" />
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
<attribute name="ModuleRefs" schema="ModuleReferenceContainer" required="yes" fixed="yes" />
<attribute schema="ANY" />
@ -361,7 +361,7 @@
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="Event" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<schema name="Event" elementResync="NEVER" attributeResync="NEVER">
<interface name="Deletable" />
<element schema="OBJECT" />
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
@ -483,13 +483,32 @@
<attribute name="Virtual" schema="OBJECT" />
<attribute schema="ANY" />
</schema>
<schema name="ReferenceTypeContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<schema name="CanonicalReferenceTypeContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<element schema="CanonicalReferenceType" />
<attribute schema="ANY" />
</schema>
<schema name="ReferenceTypeContainer" canonical="no" elementResync="NEVER" attributeResync="NEVER">
<element schema="ReferenceType" />
<attribute schema="ANY" />
</schema>
<schema name="ReferenceTypeProxy" elementResync="NEVER" attributeResync="NEVER">
<element schema="ReferenceType" />
</schema>
<schema name="CanonicalReferenceType" elementResync="NEVER" attributeResync="ALWAYS">
<interface name="Module" />
<interface name="Aggregate" />
<element schema="ANY" />
<attribute name="Fields" schema="CanonicalFieldContainer" fixed="yes" />
<attribute name="Instances" schema="ObjectReferenceContainer" fixed="yes" />
<attribute name="Locations" schema="LocationContainer" fixed="yes" />
<attribute name="Methods" schema="CanonicalMethodContainer" required="no" fixed="yes" />
<attribute name="Relations" schema="ReferenceTypeRelations" required="no" fixed="yes" />
<attribute name="_module_name" schema="STRING" hidden="yes" />
<attribute name="Range" schema="RANGE" />
<attribute name="RangeCP" schema="RANGE" />
<attribute-alias from="_range" to="Range" />
<attribute schema="ANY" />
</schema>
<schema name="ReferenceType" elementResync="NEVER" attributeResync="ALWAYS">
<interface name="Module" />
<interface name="Aggregate" />
@ -519,7 +538,11 @@
<attribute name="SuperInterfaces" schema="ReferenceTypeContainer" fixed="yes" />
<attribute schema="ANY" />
</schema>
<schema name="FieldContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<schema name="CanonicalFieldContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<element schema="Field" />
<attribute schema="ANY" />
</schema>
<schema name="FieldContainer" canonical="no" elementResync="NEVER" attributeResync="NEVER">
<element schema="Field" />
<attribute schema="ANY" />
</schema>
@ -529,7 +552,12 @@
<attribute name="Value" schema="Value" required="no" />
<attribute schema="ANY" />
</schema>
<schema name="MethodContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<schema name="CanonicalMethodContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="SectionContainer" />
<element schema="Method" />
<attribute schema="ANY" />
</schema>
<schema name="MethodContainer" canonical="no" elementResync="NEVER" attributeResync="NEVER">
<interface name="SectionContainer" />
<element schema="Method" />
<attribute schema="ANY" />
@ -547,7 +575,7 @@
<attribute name="Locations" schema="LocationContainer" fixed="yes" />
<attribute name="Range" schema="RANGE" />
<attribute-alias from="_range" to="Range" />
<attribute name="Variables" schema="VariableContainer" fixed="yes" />
<attribute name="Variables" schema="CanonicalVariableContainer" fixed="yes" />
<attribute name="_display" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_return_type" schema="TYPE" fixed="yes" hidden="yes" />
<attribute name="_parameters" schema="MAP_PARAMETERS" fixed="yes" hidden="yes" />
@ -557,7 +585,11 @@
<element schema="Type" />
<attribute schema="ANY" />
</schema>
<schema name="VariableContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<schema name="CanonicalVariableContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<element schema="Variable" />
<attribute schema="ANY" />
</schema>
<schema name="VariableContainer" canonical="no" elementResync="NEVER" attributeResync="NEVER">
<element schema="Variable" />
<attribute schema="ANY" />
</schema>

View File

@ -0,0 +1 @@
# Debugger-rmi-trace

View File

@ -3,6 +3,7 @@
##MODULE IP: BSD-3-PSUTIL
DEVNOTES.txt||GHIDRA||||END|
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
src/main/help/help/topics/TraceRmiConnectionManagerPlugin/TraceRmiConnectionManagerPlugin.html||GHIDRA||||END|

View File

@ -1,22 +1,21 @@
#!/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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT 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 python
#@no-image
#@desc <html><body width="300px">
#@desc <h3>Start <tt>gdb</tt></h3>
#@desc <p>

View File

@ -857,7 +857,7 @@ bdcedit /dbgsettings NET HOSTIP:IP PORT:54321 KEY:1.1.1.1
<P>The following launchers based on the Java Debugger are included out of the box:</P>
<H3><A name="java"></A>java launch</H3>
<H3><A name="java"></A>java</H3>
<P>This launcher uses the native Java Debug Interface (JDI) to launch the current
<TT>.class</TT> file.</P>

View File

@ -77,4 +77,14 @@ public class ProtobufSocket<T extends AbstractMessage> {
Msg.error(this, "Unable to close ProtobufSocket");
}
}
public String getRemoteAddress() {
try {
return channel.getRemoteAddress().toString();
}
catch (IOException e) {
return null;
}
}
}

View File

@ -15,14 +15,21 @@
*/
package ghidra.app.plugin.core.debug.client.tracermi;
import java.util.HashSet;
import java.util.Set;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
public class RmiBatch {
import ghidra.app.plugin.core.debug.client.tracermi.RmiClient.RequestResult;
public class RmiBatch implements AutoCloseable {
private int refCount = 0;
private Set<Object> futures = new HashSet<>();
private final RmiClient client;
private volatile int refCount = 0;
private final List<RequestResult> futures = new ArrayList<>();
public RmiBatch(RmiClient client) {
this.client = client;
}
public void inc() {
refCount++;
@ -31,14 +38,35 @@ public class RmiBatch {
public int dec() {
return --refCount;
}
public void append(Object f) {
futures.add(f);
@Override
public void close() {
try {
client.endBatch(this);
}
catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
public Object results() {
return null;
public void append(RequestResult f) {
synchronized (futures) {
futures.add(f);
}
}
public List<Object> results() throws InterruptedException, ExecutionException {
List<RequestResult> futures = futures();
List<Object> results = new ArrayList<>(futures.size());
for (RequestResult r : futures) {
results.add(r.get());
}
return results;
}
public List<RequestResult> futures() {
synchronized (futures) {
return List.copyOf(futures);
}
}
}

View File

@ -18,9 +18,9 @@ package ghidra.app.plugin.core.debug.client.tracermi;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;
import java.nio.channels.SocketChannel;
import java.util.*;
import java.util.concurrent.*;
import org.jdom.JDOMException;
@ -37,18 +37,51 @@ import ghidra.rmi.trace.TraceRmi.Language;
import ghidra.rmi.trace.TraceRmi.Value.Builder;
import ghidra.trace.model.Lifespan;
import ghidra.util.Msg;
import ghidra.util.Swing;
public class RmiClient {
static class RequestResult extends CompletableFuture<Object> {
public final RootMessage request;
public RequestResult(RootMessage req) {
this.request = req;
}
@Override
public Object get() throws InterruptedException, ExecutionException {
if (Swing.isSwingThread()) {
throw new AssertionError("Refusing indefinite wait on Swing thread");
}
return super.get();
}
@Override
public Object get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (Swing.isSwingThread() && unit.toSeconds(timeout) > 1) {
throw new AssertionError("Refusing a timeout > 1 second on Swing thread");
}
return super.get(timeout, unit);
}
}
public static class RmiException extends RuntimeException {
public RmiException(String message) {
super(message);
}
}
private final ProtobufSocket<RootMessage> socket;
private final String description;
private int nextTraceId = 0;
private RmiBatch currentBatch = null;
private volatile RmiBatch currentBatch = null;
Map<Integer, RmiTrace> traces = new HashMap<>();
private SchemaContext schemaContext;
private RmiMethodHandlerThread handler;
private RmiReplyHandlerThread handler;
private static RmiMethodRegistry methodRegistry;
private Deque<RootMessage> requests = new LinkedList<>();
private Deque<RequestResult> requests = new LinkedList<>();
public static TargetObjectSchema loadSchema(String resourceName, String rootName) {
XmlSchemaContext schemaContext;
@ -64,19 +97,10 @@ public class RmiClient {
}
}
// public static TargetObjectSchema getSchema(String name) {
// try {
// return SCHEMA_CTX.getSchema(new SchemaName(name));
// } catch (NullPointerException e) {
// System.err.println("Possibly non-existent schema: "+name);
// return SCHEMA_CTX.getSchema(new SchemaName("OBJECT"));
// }
// }
public static enum TraceRmiResolution {
RES_ADJUST("adjust", Resolution.CR_ADJUST), //
RES_DENY("deny", Resolution.CR_DENY), //
RES_TRUNCATE("truncate", Resolution.CR_TRUNCATE), //
RES_ADJUST("adjust", Resolution.CR_ADJUST),
RES_DENY("deny", Resolution.CR_DENY),
RES_TRUNCATE("truncate", Resolution.CR_TRUNCATE),
;
TraceRmiResolution(String val, TraceRmi.Resolution description) {
@ -89,9 +113,9 @@ public class RmiClient {
}
public static enum TraceRmiValueKinds {
ATTRIBUTES("attributes", ValueKinds.VK_ATTRIBUTES), //
ELEMENTS("elements", ValueKinds.VK_ELEMENTS), //
BOTH("both", ValueKinds.VK_BOTH), //
ATTRIBUTES("attributes", ValueKinds.VK_ATTRIBUTES),
ELEMENTS("elements", ValueKinds.VK_ELEMENTS),
BOTH("both", ValueKinds.VK_BOTH),
;
TraceRmiValueKinds(String val, TraceRmi.ValueKinds description) {
@ -106,16 +130,12 @@ public class RmiClient {
public RmiClient(SocketChannel channel, String description) {
this.socket = new ProtobufSocket<>(channel, RootMessage::parseFrom);
this.description = description;
this.handler = new RmiMethodHandlerThread(this, socket);
this.handler = new RmiReplyHandlerThread(this, socket);
handler.start();
}
public ProtobufSocket<RootMessage> getSocket() {
return socket;
}
public String getDescription() {
return description;
return description + " at " + socket.getRemoteAddress();
}
public void close() {
@ -123,10 +143,18 @@ public class RmiClient {
socket.close();
}
private void send(RootMessage msg) {
private RequestResult send(RootMessage msg) {
try {
requests.push(msg);
socket.send(msg);
RequestResult result = new RequestResult(msg);
synchronized (requests) {
socket.send(msg);
requests.push(result);
}
RmiBatch cb = currentBatch;
if (cb != null) {
cb.append(result);
}
return result;
}
catch (IOException e) {
throw new RuntimeException(e);
@ -137,12 +165,11 @@ public class RmiClient {
if (compiler == null) {
compiler = new CompilerSpecID("default");
}
RmiTrace trace = new RmiTrace(this, nextTraceId);
traces.put(nextTraceId, trace);
send(RootMessage.newBuilder()
int traceId = nextTraceId++;
RequestResult result = send(RootMessage.newBuilder()
.setRequestCreateTrace(RequestCreateTrace.newBuilder()
.setOid(DomObjId.newBuilder()
.setId(nextTraceId++))
.setId(traceId))
.setLanguage(Language.newBuilder()
.setId(language.getIdAsString()))
.setCompiler(Compiler.newBuilder()
@ -150,6 +177,8 @@ public class RmiClient {
.setPath(FilePath.newBuilder()
.setPath(path)))
.build());
RmiTrace trace = new RmiTrace(this, traceId, result);
traces.put(traceId, trace);
return trace;
}
@ -159,6 +188,7 @@ public class RmiClient {
.setOid(DomObjId.newBuilder()
.setId(id)))
.build());
traces.remove(id);
}
public void saveTrace(int id) {
@ -248,7 +278,7 @@ public class RmiClient {
.setRange(AddrRange.newBuilder()
.setSpace(range.getAddressSpace().getName())
.setOffset(range.getMinAddress().getOffset())
.setExtend(range.getLength())))
.setExtend(range.getLength() - 1)))
.build());
}
@ -279,7 +309,7 @@ public class RmiClient {
.setSpace(ppath);
for (int i = 0; i < names.length; i++) {
String name = names[i];
builder.setNames(i, name);
builder.addNames(name);
}
send(RootMessage.newBuilder()
.setRequestDeleteRegisterValue(builder)
@ -298,9 +328,8 @@ public class RmiClient {
.build());
}
public void createObject(int traceId, String path) {
//System.err.println("createObject:"+path);
send(RootMessage.newBuilder()
RequestResult createObject(int traceId, String path) {
return send(RootMessage.newBuilder()
.setRequestCreateObject(RequestCreateObject.newBuilder()
.setOid(DomObjId.newBuilder()
.setId(traceId))
@ -323,12 +352,13 @@ public class RmiClient {
.build());
}
public void insertObject(int traceId, ObjSpec object, Lifespan span, Resolution r) {
public void insertObject(int traceId, long id, Lifespan span, Resolution r) {
send(RootMessage.newBuilder()
.setRequestInsertObject(RequestInsertObject.newBuilder()
.setOid(DomObjId.newBuilder()
.setId(traceId))
.setObject(object)
.setObject(ObjSpec.newBuilder()
.setId(id))
.setSpan(Span.newBuilder()
.setMin(span.lmin())
.setMax(span.lmax()))
@ -336,12 +366,28 @@ public class RmiClient {
.build());
}
public void removeObject(int traceId, ObjSpec object, Lifespan span, boolean tree) {
public void removeObject(int traceId, String path, Lifespan span, boolean tree) {
send(RootMessage.newBuilder()
.setRequestRemoveObject(RequestRemoveObject.newBuilder()
.setOid(DomObjId.newBuilder()
.setId(traceId))
.setObject(object)
.setObject(ObjSpec.newBuilder()
.setPath(ObjPath.newBuilder()
.setPath(path)))
.setSpan(Span.newBuilder()
.setMin(span.lmin())
.setMax(span.lmax()))
.setTree(tree))
.build());
}
public void removeObject(int traceId, long id, Lifespan span, boolean tree) {
send(RootMessage.newBuilder()
.setRequestRemoveObject(RequestRemoveObject.newBuilder()
.setOid(DomObjId.newBuilder()
.setId(traceId))
.setObject(ObjSpec.newBuilder()
.setId(id))
.setSpan(Span.newBuilder()
.setMin(span.lmin())
.setMax(span.lmax()))
@ -389,19 +435,18 @@ public class RmiClient {
}
public void getObject(int traceId, String path) {
RequestGetObject.Builder builder = RequestGetObject.newBuilder()
.setOid(DomObjId.newBuilder()
.setId(traceId))
.setObject(ObjSpec.newBuilder()
.setPath(ObjPath.newBuilder()
.setPath(path)));
send(RootMessage.newBuilder()
.setRequestGetObject(builder)
.setRequestGetObject(RequestGetObject.newBuilder()
.setOid(DomObjId.newBuilder()
.setId(traceId))
.setObject(ObjSpec.newBuilder()
.setPath(ObjPath.newBuilder()
.setPath(path))))
.build());
}
public void getValues(int traceId, Lifespan span, String pattern) {
send(RootMessage.newBuilder()
RequestResult getValues(int traceId, Lifespan span, String pattern) {
return send(RootMessage.newBuilder()
.setRequestGetValues(RequestGetValues.newBuilder()
.setOid(DomObjId.newBuilder()
.setId(traceId))
@ -412,9 +457,9 @@ public class RmiClient {
.build());
}
public void getValuesIntersecting(int traceId, Lifespan span, AddressRange range,
RequestResult getValuesIntersecting(int traceId, Lifespan span, AddressRange range,
String key) {
send(RootMessage.newBuilder()
return send(RootMessage.newBuilder()
.setRequestGetValuesIntersecting(RequestGetValuesIntersecting.newBuilder()
.setOid(DomObjId.newBuilder()
.setId(traceId))
@ -445,6 +490,9 @@ public class RmiClient {
@SuppressWarnings("unchecked")
private Builder buildValue(Object value) {
Builder builder = Value.newBuilder();
if (value == null) {
return builder.setNullValue(Null.newBuilder());
}
if (value instanceof String str) {
return builder.setStringValue(str);
}
@ -497,7 +545,11 @@ public class RmiClient {
return builder.setBoolArrValue(b.build());
}
if (list.get(0) instanceof Short) {
ShortArr.Builder b = ShortArr.newBuilder().addAllArr((List<Integer>) list);
List<Integer> newList = new ArrayList<>();
for (Object object : list) {
newList.add(((Short) object).intValue());
}
ShortArr.Builder b = ShortArr.newBuilder().addAllArr(newList);
return builder.setShortArrValue(b.build());
}
if (list.get(0) instanceof Integer) {
@ -570,12 +622,12 @@ public class RmiClient {
.setDisplay(param.getDisplay())
.setDescription(param.getDescription())
.setType(param.getType())
.setDefaultValue(param.getDefaultValue())
.setDefaultValue(buildValue(param.getDefaultValue()))
.setRequired(param.isRequired())
.build();
}
public void handleInvokeMethod(int traceId, XRequestInvokeMethod req) {
public XReplyInvokeMethod handleInvokeMethod(int traceId, XRequestInvokeMethod req) {
RmiRemoteMethod rm = getMethod(req.getName());
Object[] arglist = new Object[req.getArgumentsCount()];
java.lang.reflect.Method m = rm.getMethod();
@ -585,57 +637,50 @@ public class RmiClient {
argmap.put(arg.getName(), arg);
}
int i = 0;
for (Parameter p : m.getParameters()) {
for (RmiRemoteMethodParameter p : rm.getParameters()) {
MethodArgument arg = argmap.get(p.getName());
if (arg != null) {
Object obj = argToObject(traceId, arg);
Object obj = argToObject(traceId, arg.getValue());
arglist[i++] = obj;
}
}
try {
Object ret = m.invoke(rm.getContainer(), arglist);
if (ret != null) {
socket.send(RootMessage.newBuilder()
.setXreplyInvokeMethod(XReplyInvokeMethod.newBuilder()
.setReturnValue(buildValue(ret)))
.build());
return XReplyInvokeMethod.newBuilder()
.setReturnValue(buildValue(ret))
.build();
}
return XReplyInvokeMethod.newBuilder()
.setReturnValue(buildValue(true))
.build();
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| IOException e) {
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
String message = e.getMessage();
if (message != null) {
Msg.error(this, message);
try {
socket.send(RootMessage.newBuilder()
.setXreplyInvokeMethod(
XReplyInvokeMethod.newBuilder().setError(message))
.build());
}
catch (IOException e1) {
Msg.error(this, e1.getMessage());
}
Msg.error(this, "Error handling method invocation:" + message);
return XReplyInvokeMethod.newBuilder()
.setError(message)
.build();
}
return XReplyInvokeMethod.newBuilder()
.setError(e.toString())
.build();
}
}
private Object argToObject(int traceId, MethodArgument arg) {
if (arg == null) {
throw new RuntimeException("Null argument passed to argToObject");
}
Value value = arg.getValue();
Object argToObject(int traceId, Value value) {
if (value.hasStringValue()) {
return value.getStringValue();
}
if (value.hasStringArrValue()) {
return value.getStringArrValue();
return value.getStringArrValue().getArrList();
}
if (value.hasBoolValue()) {
return value.getBoolValue();
}
if (value.hasBoolArrValue()) {
return value.getBoolArrValue();
return value.getBoolArrValue().getArrList();
}
if (value.hasCharValue()) {
return value.getCharValue();
@ -647,19 +692,19 @@ public class RmiClient {
return value.getShortValue();
}
if (value.hasShortArrValue()) {
return value.getShortArrValue();
return value.getShortArrValue().getArrList();
}
if (value.hasIntValue()) {
return value.getIntValue();
}
if (value.hasIntArrValue()) {
return value.getIntArrValue();
return value.getIntArrValue().getArrList();
}
if (value.hasLongValue()) {
return value.getLongValue();
}
if (value.hasLongArrValue()) {
return value.getLongArrValue();
return value.getLongArrValue().getArrList();
}
if (value.hasAddressValue()) {
return decodeAddr(traceId, value.getAddressValue());
@ -681,6 +726,61 @@ public class RmiClient {
return proxyObjectPath(traceId, path);
}
String argToType(Value value) {
if (value.hasStringValue()) {
return "STRING";
}
if (value.hasStringArrValue()) {
return "STRING_ARR";
}
if (value.hasBoolValue()) {
return "BOOL";
}
if (value.hasBoolArrValue()) {
return "BOOL_ARR";
}
if (value.hasCharValue()) {
return "CHAR";
}
if (value.hasCharArrValue()) {
return "CHAR_ARR";
}
if (value.hasShortValue()) {
return "SHORT";
}
if (value.hasShortArrValue()) {
return "SHORT_ARR";
}
if (value.hasIntValue()) {
return "INT";
}
if (value.hasIntArrValue()) {
return "INT_ARR";
}
if (value.hasLongValue()) {
return "LONG";
}
if (value.hasLongArrValue()) {
return "LONG_ARR";
}
if (value.hasAddressValue()) {
return "ADDRESS";
}
if (value.hasRangeValue()) {
return "RANGE";
}
if (value.hasByteValue()) {
return "BYTE";
}
if (value.hasBytesValue()) {
return "BYTE_ARR";
}
if (value.hasNullValue()) {
return "NULL";
}
return "OBJECT";
}
private Address decodeAddr(int id, Addr addr) {
RmiTrace trace = traces.get(id);
return trace.memoryMapper.genAddr(addr.getSpace(), addr.getOffset());
@ -700,32 +800,33 @@ public class RmiClient {
return methodRegistry.getMap().get(name);
}
public Object startBatch() {
public RmiBatch startBatch() {
if (currentBatch == null) {
currentBatch = new RmiBatch();
currentBatch = new RmiBatch(this);
}
currentBatch.inc();
return currentBatch;
}
public Object endBatch() {
RmiBatch cb = null;
if (0 == currentBatch.dec()) {
cb = currentBatch;
boolean hasBatch() {
return currentBatch != null;
}
void endBatch(RmiBatch batch) throws InterruptedException, ExecutionException {
if (currentBatch.dec() == 0) {
RmiBatch cb = currentBatch;
currentBatch = null;
cb.results();
}
if (cb != null) {
return cb.results();
}
return null;
}
public TargetObjectSchema getSchema(String schema) {
return schemaContext.getSchema(new SchemaName(schema));
}
public RootMessage getRequestsPoll() {
return requests.poll();
public RequestResult pollRequest() {
synchronized (requests) {
return requests.poll();
}
}
}

View File

@ -1,75 +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.
*/
package ghidra.app.plugin.core.debug.client.tracermi;
import ghidra.rmi.trace.TraceRmi.*;
import ghidra.util.Msg;
public class RmiMethodHandlerThread extends Thread {
private RmiClient client;
private ProtobufSocket<RootMessage> socket;
private boolean terminated = false;
public RmiMethodHandlerThread(RmiClient client, ProtobufSocket<RootMessage> socket) {
this.client = client;
this.socket = socket;
}
@Override
public void run() {
while (!terminated) {
try {
RootMessage msg = socket.recv();
if (msg.hasXrequestInvokeMethod()) {
try {
XRequestInvokeMethod req = msg.getXrequestInvokeMethod();
int id = req.getOid().getId();
RmiTrace trace = client.traces.get(id);
trace.handleInvokeMethod(req);
}
catch (Exception e) {
e.printStackTrace();
}
continue;
}
RootMessage request = client.getRequestsPoll();
if (msg.hasError()) {
Msg.error(this, msg);
}
else if (msg.hasReplyCreateObject()) {
ReplyCreateObject reply = msg.getReplyCreateObject();
RmiTrace trace = client.traces.get(request.getRequestCreateObject().getOid().getId());
trace.handleCreateObject(reply);
}
else if (msg.hasReplyCreateTrace()) {
ReplyCreateTrace reply = msg.getReplyCreateTrace();
RmiTrace trace = client.traces.get(request.getRequestCreateTrace().getOid().getId());
trace.handleCreateTrace(reply);
}
}
catch (Exception e) {
Msg.error(this, e.getMessage());
}
}
Msg.info(this, "Handler exiting");
}
public void close() {
terminated = true;
}
}

View File

@ -27,12 +27,13 @@ public class RmiMethodRegistry {
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public static @interface TraceMethod {
String action();
String display() default "";
String description() default "";
String schema() default "ANY";
String action() default "";
String display() default "";
String description() default "";
}
Map<String, RmiRemoteMethod> map = new HashMap<>();
public RmiRemoteMethod getMethod(String key) {

View File

@ -19,14 +19,12 @@ import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import ghidra.dbg.target.TargetMethod;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.target.schema.*;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.rmi.trace.TraceRmi.Value;
public class RmiRemoteMethod {
private final SchemaContext schemaContext;
private String name;
private String action;
@ -37,7 +35,8 @@ public class RmiRemoteMethod {
private RmiMethods instance;
private Method m;
public RmiRemoteMethod(SchemaContext schemaContext, String name, String action, String display, String description, TargetObjectSchema schema, RmiMethods instance, Method m) {
public RmiRemoteMethod(SchemaContext schemaContext, String name, String action, String display,
String description, TargetObjectSchema schema, RmiMethods instance, Method m) {
this.schemaContext = schemaContext;
this.name = name;
this.action = action;
@ -46,54 +45,23 @@ public class RmiRemoteMethod {
this.params = new RmiRemoteMethodParameter[m.getParameterCount()];
this.schema = schema;
this.instance = instance;
this.m = m;
this.m = m;
int i = 0;
for (Parameter p : m.getParameters()) {
TargetObjectSchema pschema = getSchemaFromParameter(p);
String pname = p.getName(); // NB: don't change this unless yuou resolve the ordering issues
String pdesc = pname;
String pdisp = pname;
if (i == 0) {
RmiMethodRegistry.TraceMethod annot = m.getAnnotation(RmiMethodRegistry.TraceMethod.class);
if (annot != null) {
pschema = schemaContext.getSchema(new SchemaName(annot.schema()));
}
pdisp = "Object";
ParameterDescription<?> desc = TargetMethod.ParameterDescription.annotated(p);
TargetObjectSchema pschema;
if (desc.type != RmiTraceObject.class) {
pschema = EnumerableTargetObjectSchema.schemaForPrimitive(desc.type);
}
Value pdef = null;
TargetMethod.Param pannot = p.getAnnotation(TargetMethod.Param.class);
if (pannot != null) {
pdesc = pannot.description();
pdisp = pannot.display();
else {
pschema = schemaContext.getSchema(new SchemaName(desc.schema));
}
boolean required = i != 0;
params[i++] = new RmiRemoteMethodParameter(pname, pschema, required, pdef, pdisp, pdesc);
params[i++] = new RmiRemoteMethodParameter(desc.name, pschema, desc.required,
desc.defaultValue, desc.display, desc.description);
}
}
private TargetObjectSchema getSchemaFromParameter(Parameter p) {
if (p.getAnnotatedType().getType().equals(String.class)) {
return EnumerableTargetObjectSchema.STRING;
}
if (p.getAnnotatedType().getType().equals(Boolean.class)) {
return EnumerableTargetObjectSchema.BOOL;
}
if (p.getAnnotatedType().getType().equals(Integer.class)) {
return EnumerableTargetObjectSchema.INT;
}
if (p.getAnnotatedType().getType().equals(Long.class)) {
return EnumerableTargetObjectSchema.LONG;
}
if (p.getAnnotatedType().getType().equals(Address.class)) {
return EnumerableTargetObjectSchema.ADDRESS;
}
if (p.getAnnotatedType().getType().equals(AddressRange.class)) {
return EnumerableTargetObjectSchema.RANGE;
}
return EnumerableTargetObjectSchema.ANY;
}
public String getName() {
return name;
}
@ -125,5 +93,4 @@ public class RmiRemoteMethod {
public RmiMethods getContainer() {
return instance;
}
}

View File

@ -19,24 +19,24 @@ import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.rmi.trace.TraceRmi.*;
public class RmiRemoteMethodParameter {
private final String name;
private final TargetObjectSchema schema;
private final boolean required;
private final Value defaultValue;
private final Object defaultValue;
private final String display;
private final String description;
public RmiRemoteMethodParameter(String name, TargetObjectSchema schema, boolean required,
Value defaultValue, String display, String description) {
public RmiRemoteMethodParameter(String name, TargetObjectSchema schema, boolean required,
Object defaultValue, String display, String description) {
this.name = name;
this.schema = schema;
this.required = required;
this.defaultValue = defaultValue;
this.display = display;
this.description = description;
this.description = description;
}
public String getName() {
return name;
}
@ -57,15 +57,11 @@ public class RmiRemoteMethodParameter {
return ValueType.newBuilder().setName(schemaName).build();
}
public Value getDefaultValue() {
if (defaultValue != null) {
return defaultValue;
}
return Value.newBuilder().setNullValue(Null.newBuilder()).build();
public Object getDefaultValue() {
return defaultValue;
}
public boolean isRequired() {
return required;
}
}

View File

@ -0,0 +1,112 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.app.plugin.core.debug.client.tracermi;
import ghidra.app.plugin.core.debug.client.tracermi.RmiClient.RequestResult;
import ghidra.app.plugin.core.debug.client.tracermi.RmiClient.RmiException;
import ghidra.rmi.trace.TraceRmi.*;
import ghidra.util.Msg;
public class RmiReplyHandlerThread extends Thread {
private RmiClient client;
private ProtobufSocket<RootMessage> socket;
private boolean terminated = false;
public RmiReplyHandlerThread(RmiClient client, ProtobufSocket<RootMessage> socket) {
this.client = client;
this.socket = socket;
}
@Override
public void run() {
while (!terminated) {
try {
RootMessage msg = socket.recv();
if (msg.hasXrequestInvokeMethod()) {
try {
XRequestInvokeMethod req = msg.getXrequestInvokeMethod();
int id = req.getOid().getId();
RmiTrace trace = client.traces.get(id);
XReplyInvokeMethod reply = trace.handleInvokeMethod(req);
socket.send(RootMessage.newBuilder().setXreplyInvokeMethod(reply).build());
}
catch (Exception e) {
Msg.error(this, "Error handling method invocation", e);
socket.send(RootMessage.newBuilder()
.setXreplyInvokeMethod(
XReplyInvokeMethod.newBuilder().setError(e.toString()))
.build());
}
continue;
}
RequestResult result = client.pollRequest();
if (result == null) {
System.err.println("REPLY without request: " + msg);
continue;
}
RootMessage request = result.request;
switch (msg.getMsgCase()) {
case ERROR -> {
Msg.error(this, msg.getError().getMessage());
result.completeExceptionally(new RmiException(msg.getError().getMessage()));
}
case REPLY_CREATE_OBJECT -> {
ReplyCreateObject reply = msg.getReplyCreateObject();
RmiTrace trace =
client.traces.get(request.getRequestCreateObject().getOid().getId());
result.complete(trace.handleCreateObject(reply));
}
case REPLY_CREATE_TRACE -> {
ReplyCreateTrace reply = msg.getReplyCreateTrace();
RmiTrace trace =
client.traces.get(request.getRequestCreateTrace().getOid().getId());
result.complete(trace.handleCreateTrace(reply));
}
case REPLY_GET_VALUES -> {
ReplyGetValues reply = msg.getReplyGetValues();
RmiTrace trace =
client.traces.get(request.getRequestGetValues().getOid().getId());
result.complete(trace.handleGetValues(reply));
}
case REPLY_DISASSEMBLE -> {
ReplyDisassemble reply = msg.getReplyDisassemble();
RmiTrace trace =
client.traces.get(request.getRequestDisassemble().getOid().getId());
result.complete(trace.handleDisassemble(reply));
}
default -> result.complete(null);
}
}
catch (Exception e) {
if (e.getMessage() == null) {
Msg.error(this, "Error processing reply", e);
}
else {
Msg.error(this, e.getMessage());
}
}
}
Msg.info(this, "Handler exiting");
}
public void close() {
terminated = true;
}
}

View File

@ -15,14 +15,15 @@
*/
package ghidra.app.plugin.core.debug.client.tracermi;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import ghidra.app.plugin.core.debug.client.tracermi.RmiClient.RequestResult;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.RegisterValue;
import ghidra.rmi.trace.TraceRmi.*;
import ghidra.trace.model.Lifespan;
@ -30,14 +31,15 @@ import ghidra.util.LockHold;
import ghidra.util.Msg;
public class RmiTrace {
final RmiClient client;
private final int id;
private RequestResult createResult;
private int nextTx = 0;
private Object txLock = new Object();
private ReadWriteLock snLock = new ReentrantReadWriteLock();
private Set<String> overlays = new HashSet<>();
private long currentSnap = -1;
private boolean closed = false;
@ -45,11 +47,17 @@ public class RmiTrace {
public MemoryMapper memoryMapper;
public RegisterMapper registerMapper;
public RmiTrace(RmiClient client, int id) {
public RmiTrace(RmiClient client, int id, RequestResult createResult) {
this.client = client;
this.id = id;
this.id = id;
this.createResult = createResult;
}
public void checkResult(long timeoutMs)
throws InterruptedException, ExecutionException, TimeoutException {
createResult.get(timeoutMs, TimeUnit.MILLISECONDS);
}
public void close() {
if (closed) {
return;
@ -63,7 +71,7 @@ public class RmiTrace {
public RmiTransaction startTx(String description, boolean undoable) {
int txid;
synchronized(txLock) {
synchronized (txLock) {
txid = nextTx++;
}
client.startTx(id, description, undoable, txid);
@ -74,17 +82,20 @@ public class RmiTrace {
return startTx(description, false);
}
public void endTx(int txid, boolean abort) {
public void endTx(int txid, boolean abort) {
client.endTx(id, txid, abort);
}
public long nextSnap() {
try (LockHold hold = LockHold.lock(snLock.writeLock())) {
return ++currentSnap;
}
}
public long snapshot(String description, String datatime, Long snap) {
if (datatime == null) {
datatime = "";
}
if (snap == null) {
snap = nextSnap();
}
@ -97,68 +108,90 @@ public class RmiTrace {
return currentSnap;
}
}
public void setSnap(long snap) {
try (LockHold hold = LockHold.lock(snLock.writeLock())) {
this.currentSnap = snap;
}
}
public long snapOrCurrent(Long snap) {
try (LockHold hold = LockHold.lock(snLock.readLock())) {
return snap == null ? this.currentSnap : snap.longValue();
}
}
public void createOverlaySpace(String base, String name) {
if (overlays.contains(name)) {
return;
}
client.createOverlaySpace(id, base, name);
}
public void createOverlaySpace(Address repl, Address orig) {
createOverlaySpace(repl.getAddressSpace().getName(), orig.getAddressSpace().getName());
}
public void putBytes(Address addr, byte[] data, Long snap) {
public void putBytes(Address addr, byte[] data, Long snap) {
client.putBytes(id, snapOrCurrent(snap), addr, data);
}
public void setMemoryState(AddressRange range, MemoryState state, Long snap) {
client.setMemoryState(id, snapOrCurrent(snap), range, state);
}
public void deleteBytes(AddressRange range, Long snap) {
client.deleteBytes(id, snapOrCurrent(snap), range);
}
public void putRegisters(String ppath, RegisterValue[] values, Long snap) {
client.putRegisters(id, snapOrCurrent(snap), ppath, values);
}
public void deleteRegisters(String ppath, String[] names, Long snap) {
client.deleteRegisters(id, snapOrCurrent(snap), ppath, names);
}
public void createRootObject(SchemaContext schemaContext, String schema) {
client.createRootObject(id, schemaContext, schema);
}
public void createObject(String path) {
client.createObject(id, path);
public RmiTraceObject createObject(String path) {
RequestResult result = client.createObject(id, path);
return new RmiTraceObject(this, path, result);
}
public void handleCreateObject(ReplyCreateObject reply) {
RmiTraceObject obj = new RmiTraceObject(this, reply.getObject());
try (RmiTransaction tx = startTx("CreateObject", false); LockHold hold = LockHold.lock(snLock.readLock())) {
obj.insert(currentSnap, null);
}
public RmiTraceObject createAndInsertObject(String path) {
RmiTraceObject object = createObject(path);
object.insert(currentSnap, null);
return object;
}
public void handleCreateTrace(ReplyCreateTrace reply) {
long handleCreateObject(ReplyCreateObject reply) {
return reply.getObject().getId();
}
public Void handleCreateTrace(ReplyCreateTrace reply) {
return null;
}
public List<RmiTraceObjectValue> handleGetValues(ReplyGetValues reply) {
List<RmiTraceObjectValue> result = new ArrayList<>();
for (ValDesc d : reply.getValuesList()) {
RmiTraceObject parent = proxyObject(d.getParent());
Lifespan span = Lifespan.span(d.getSpan().getMin(), d.getSpan().getMax());
Object value = client.argToObject(id, d.getValue());
TargetObjectSchema schema = client.getSchema(client.argToType(d.getValue()));
result.add(new RmiTraceObjectValue(parent, span, d.getKey(), value, schema));
}
return result;
}
public long handleDisassemble(ReplyDisassemble reply) {
Msg.info(this, "Disassembled " + reply.getLength() + " bytes");
return reply.getLength();
}
public void insertObject(String path) {
Lifespan span = getLifespan();
client.insertObject(id, path, span, Resolution.CR_ADJUST);
@ -174,29 +207,71 @@ public class RmiTrace {
Lifespan span = getLifespan();
client.setValue(id, ppath, span, key, value, null);
}
public void retainValues(String ppath, Set<String> keys, ValueKinds kinds) {
Lifespan span = getLifespan();
client.retainValues(id, ppath, span, kinds, keys);
}
@SuppressWarnings("unchecked")
private <T> T doSync(RequestResult r) {
if (client.hasBatch()) {
return null;
}
try {
return (T) r.get();
}
catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
public RequestResult getValuesAsync(String pattern) {
Lifespan span = getLifespan();
return client.getValues(id, span, pattern);
}
public List<RmiTraceObjectValue> getValues(String pattern) {
return doSync(getValuesAsync(pattern));
}
public RequestResult getValuesRngAsync(Address start, long length) {
Lifespan span = getLifespan();
try {
AddressRange range = new AddressRangeImpl(start, length);
return client.getValuesIntersecting(id, span, range, "");
}
catch (AddressOverflowException e) {
throw new RuntimeException(e.getMessage());
}
}
public List<RmiTraceObjectValue> getValuesRng(Address start, long length) {
return doSync(getValuesRngAsync(start, length));
}
public void activate(String path) {
if (path == null) {
Msg.error(this, "Attempt to activate null");
return;
}
client.activate(id, path);
}
public void disassemble(Address start, Long snap) {
public void disassemble(Address start, Long snap) {
client.disassemble(id, snapOrCurrent(snap), start);
}
public void handleInvokeMethod(XRequestInvokeMethod req) {
try (RmiTransaction tx = startTx("InvokeMethod", false)) {
client.handleInvokeMethod(id, req);
}
public XReplyInvokeMethod handleInvokeMethod(XRequestInvokeMethod req) {
try (RmiTransaction tx = startTx("InvokeMethod", false)) {
return client.handleInvokeMethod(id, req);
}
}
private RmiTraceObject proxyObject(ObjDesc desc) {
return client.proxyObjectPath(id, desc.getId(), desc.getPath().getPath());
}
public RmiTraceObject proxyObjectId(Long objectId) {
return client.proxyObjectId(id, objectId);
}

View File

@ -17,45 +17,65 @@ package ghidra.app.plugin.core.debug.client.tracermi;
import java.util.Set;
import ghidra.rmi.trace.TraceRmi.*;
import ghidra.app.plugin.core.debug.client.tracermi.RmiClient.RequestResult;
import ghidra.rmi.trace.TraceRmi.Resolution;
import ghidra.rmi.trace.TraceRmi.ValueKinds;
import ghidra.trace.model.Lifespan;
public class RmiTraceObject {
private RmiTrace trace;
private ObjSpec spec;
private String path;
public RmiTraceObject(RmiTrace trace, ObjSpec spec) {
this.trace = trace;
this.spec = spec;
this.path = spec.getPath().getPath();
}
public RmiTraceObject(RmiTrace trace, Long id, String path) {
private final RmiTrace trace;
private final String path;
private volatile Long id;
public RmiTraceObject(RmiTrace trace, String path) {
this.trace = trace;
this.path = path;
}
RmiTraceObject(RmiTrace trace, String path, RequestResult result) {
this.trace = trace;
this.path = path;
result.thenAccept(id -> this.id = (Long) id);
}
public RmiTraceObject(RmiTrace trace, Long id, String path) {
this.trace = trace;
this.id = id;
this.path = path;
}
public static RmiTraceObject fromId(RmiTrace trace, long id) {
return new RmiTraceObject(trace, id, null);
}
public static RmiTraceObject fromPath(RmiTrace trace, String path) {
return new RmiTraceObject(trace, null, path);
}
public void insert(long snap, Resolution resolution) {
public Lifespan insert(long snap, Resolution resolution) {
if (resolution == null) {
resolution = Resolution.CR_ADJUST;
}
Lifespan span = Lifespan.nowOn(snap);
trace.client.insertObject(trace.getId(), spec, span, resolution);
if (id != null) {
trace.client.insertObject(trace.getId(), id, span, resolution);
}
else {
trace.client.insertObject(trace.getId(), path, span, resolution);
}
return span;
}
public void remove(long snap, boolean tree) {
public Lifespan remove(long snap, boolean tree) {
Lifespan span = Lifespan.nowOn(snap);
trace.client.removeObject(trace.getId(), spec, span, tree);
if (id != null) {
trace.client.removeObject(trace.getId(), id, span, tree);
}
else {
trace.client.removeObject(trace.getId(), path, span, tree);
}
return span;
}
public void setValue(String key, Object value, long snap, String resolution) {
@ -71,7 +91,7 @@ public class RmiTraceObject {
public void activate() {
trace.client.activate(trace.getId(), path);
}
public String getPath() {
return path;
}

View File

@ -0,0 +1,22 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.client.tracermi;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.trace.model.Lifespan;
public record RmiTraceObjectValue(RmiTraceObject parent, Lifespan span, String key, Object value,
TargetObjectSchema schema) {}

View File

@ -57,8 +57,4 @@ public class RmiTransaction implements AutoCloseable {
commit();
}
public RmiTransaction startTx(String description, boolean b) {
// TODO Auto-generated method stub
return null;
}
}

View File

@ -130,10 +130,19 @@ public class RemoteMethodInvocationDialog extends AbstractDebuggerParameterDialo
ConfigStateField.getState(state, parameterType(parameter), key));
}
protected ValStr<?> forMissingDefault(RemoteParameter param) {
Class<?> type = parameterType(param);
if (type == Boolean.class || type == boolean.class) {
return ValStr.from(false);
}
return new ValStr<>(null, "");
}
@Override
protected void setEditorValue(PropertyEditor editor, RemoteParameter param, ValStr<?> val) {
ValStr<?> v = switch (val.val()) {
case Missing __ -> new ValStr<>(null, "");
case null -> forMissingDefault(param);
case Missing __ -> forMissingDefault(param);
case TraceObject obj -> new ValStr<>(obj, obj.getCanonicalPath().toString());
default -> val;
};

View File

@ -124,7 +124,7 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
}
@Override
public boolean requiresImage() {
return !attrs.noImage();
public LaunchParameter<?> imageParameter() {
return attrs.imageOpt();
}
}

View File

@ -57,7 +57,6 @@ import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer {
public static final String PARAM_DISPLAY_IMAGE = "Image";
public static final String PREFIX_PARAM_EXTTOOL = "env:GHIDRA_LANG_EXTTOOL_";
public static final int DEFAULT_TIMEOUT_MILLIS = 10000;
@ -299,15 +298,10 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
protected Map<String, ValStr<?>> generateDefaultLauncherArgs(
Map<String, LaunchParameter<?>> params) {
Map<String, ValStr<?>> map = new LinkedHashMap<>();
ImageParamSetter imageSetter = null;
for (Entry<String, LaunchParameter<?>> entry : params.entrySet()) {
LaunchParameter<?> param = entry.getValue();
map.put(entry.getKey(), ValStr.cast(Object.class, param.defaultValue()));
if (PARAM_DISPLAY_IMAGE.equals(param.display())) {
imageSetter = ImageParamSetter.get(param);
// May still be null if type is not supported
}
else if (param.name().startsWith(PREFIX_PARAM_EXTTOOL)) {
if (param.name().startsWith(PREFIX_PARAM_EXTTOOL)) {
String tool = param.name().substring(PREFIX_PARAM_EXTTOOL.length());
List<String> names =
program.getLanguage().getLanguageDescription().getExternalNames(tool);
@ -328,7 +322,8 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
}
}
}
if (imageSetter != null && program != null) {
if (supportsImage() && program != null) {
ImageParamSetter imageSetter = ImageParamSetter.get(imageParameter());
imageSetter.setImage(map, program);
}
return map;
@ -559,9 +554,18 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
return auto == null ? ByModuleAutoMapSpec.instance() : auto.getAutoMapSpec(trace);
}
protected void initializeMonitor(TaskMonitor monitor) {
protected boolean providesImage(Map<String, ValStr<?>> args) {
LaunchParameter<?> param = imageParameter();
if (param == null) {
return false;
}
return !"".equals(param.get(args).str());
}
protected void updateMonitorMax(TaskMonitor monitor, Map<String, ValStr<?>> args) {
AutoMapSpec spec = getAutoMapSpec();
if (requiresImage() && spec.hasTask()) {
boolean image = args == null ? supportsImage() : providesImage(args);
if (image && spec.hasTask()) {
monitor.setMaximum(6);
}
else {
@ -621,7 +625,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
Trace trace = null;
Throwable lastExc = null;
initializeMonitor(monitor);
updateMonitorMax(monitor, null);
while (true) {
try {
monitor.setMessage("Gathering arguments");
@ -633,6 +637,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
return new LaunchResult(program, sessions, acceptor, connection, trace,
lastExc);
}
updateMonitorMax(monitor, args);
monitor.increment();
acceptor = null;

View File

@ -44,45 +44,46 @@ public abstract class ScriptAttributesParser {
public static final String ENV_GHIDRA_TRACE_RMI_HOST = "GHIDRA_TRACE_RMI_HOST";
public static final String ENV_GHIDRA_TRACE_RMI_PORT = "GHIDRA_TRACE_RMI_PORT";
public static final String AT_TITLE = "@title";
public static final String AT_DESC = "@desc";
public static final String AT_MENU_PATH = "@menu-path";
public static final String AT_MENU_GROUP = "@menu-group";
public static final String AT_MENU_ORDER = "@menu-order";
public static final String AT_ICON = "@icon";
public static final String AT_HELP = "@help";
public static final String AT_ENUM = "@enum";
public static final String AT_ENV = "@env";
public static final String AT_ARG = "@arg";
public static final String AT_ARGS = "@args";
public static final String AT_TTY = "@tty";
public static final String AT_DESC = "@desc";
public static final String AT_ENUM = "@enum";
public static final String AT_ENV = "@env";
public static final String AT_HELP = "@help";
public static final String AT_ICON = "@icon";
public static final String AT_IMAGE_OPT = "@image-opt";
public static final String AT_MENU_GROUP = "@menu-group";
public static final String AT_MENU_ORDER = "@menu-order";
public static final String AT_MENU_PATH = "@menu-path";
public static final String AT_TITLE = "@title";
public static final String AT_TIMEOUT = "@timeout";
public static final String AT_NOIMAGE = "@no-image";
public static final String AT_TTY = "@tty";
public static final String PREFIX_ENV = "env:";
public static final String PREFIX_ARG = "arg:";
public static final String KEY_ARGS = "args";
public static final String PREFIX_ARG = "arg:";
public static final String PREFIX_ENV = "env:";
public static final String MSGPAT_INVALID_HELP_SYNTAX =
"%s: Invalid %s syntax. Use Topic#anchor";
public static final String MSGPAT_INVALID_ENUM_SYNTAX =
"%s: Invalid %s syntax. Use NAME:type Choice1 [ChoiceN...]";
public static final String MSGPAT_INVALID_ENV_SYNTAX =
"%s: Invalid %s syntax. Use NAME:type=default \"Display\" \"Tool Tip\"";
public static final String MSGPAT_DUPLICATE_TAG = "%s: Duplicate %s";
public static final String MSGPAT_INVALID_ARG_SYNTAX =
"%s: Invalid %s syntax. Use :type \"Display\" \"Tool Tip\"";
public static final String MSGPAT_INVALID_ARGS_SYNTAX =
"%s: Invalid %s syntax. Use \"Display\" \"Tool Tip\"";
public static final String MSGPAT_INVALID_TTY_SYNTAX =
"%s: Invalid %s syntax. Use TTY_TARGET [if env:OPT [== VAL]]";
public static final String MSGPAT_INVALID_ENUM_SYNTAX =
"%s: Invalid %s syntax. Use NAME:type Choice1 [ChoiceN...]";
public static final String MSGPAT_INVALID_ENV_SYNTAX =
"%s: Invalid %s syntax. Use NAME:type=default \"Display\" \"Tool Tip\"";
public static final String MSGPAT_INVALID_HELP_SYNTAX =
"%s: Invalid %s syntax. Use Topic#anchor";
public static final String MSGPAT_INVALID_TIMEOUT_SYNTAX = "" +
"%s: Invalid %s syntax. Use [milliseconds]";
public static final String MSGPAT_INVALID_TTY_BAD_VAL =
"%s: In %s: Parameter '%s' has type %s, but '%s' cannot be parsed as such";
public static final String MSGPAT_INVALID_TTY_NO_PARAM =
"%s: In %s: No such parameter '%s'";
public static final String MSGPAT_INVALID_TTY_NOT_BOOL =
"%s: In %s: Parameter '%s' must have bool type";
public static final String MSGPAT_INVALID_TTY_BAD_VAL =
"%s: In %s: Parameter '%s' has type %s, but '%s' cannot be parsed as such";
public static final String MSGPAT_INVALID_TIMEOUT_SYNTAX = "" +
"%s: Invalid %s syntax. Use [milliseconds]";
public static final String MSGPAT_INVALID_TTY_SYNTAX =
"%s: Invalid %s syntax. Use TTY_TARGET [if env:OPT [== VAL]]";
public static class ParseException extends Exception {
private Location loc;
@ -289,8 +290,9 @@ public abstract class ScriptAttributesParser {
return tac.withCastDefault(new ValStr<>(value, defaultString));
}
public LaunchParameter<T> createParameter(String name, String display, String description) {
return type.createParameter(name, display, description, false, defaultValue);
public LaunchParameter<T> createParameter(String name, String display, String description,
boolean required) {
return type.createParameter(name, display, description, required, defaultValue);
}
}
@ -340,7 +342,7 @@ public abstract class ScriptAttributesParser {
public record ScriptAttributes(String title, String description, List<String> menuPath,
String menuGroup, String menuOrder, Icon icon, HelpLocation helpLocation,
Map<String, LaunchParameter<?>> parameters, Map<String, TtyCondition> extraTtys,
int timeoutMillis, boolean noImage) {}
int timeoutMillis, LaunchParameter<?> imageOpt) {}
/**
* Convert an arguments map into a command line and environment variables
@ -371,7 +373,7 @@ public abstract class ScriptAttributesParser {
LaunchParameter<?> param;
for (int i = 1; (param = parameters.get("arg:" + i)) != null; i++) {
// Don't use ValStr.str here. I'd like the script's input normalized
commandLine.add(Objects.toString(param.get(args).val()));
commandLine.add(param.get(args).normStr());
}
param = parameters.get("args");
@ -384,29 +386,24 @@ public abstract class ScriptAttributesParser {
if (key.startsWith(PREFIX_ENV)) {
String varName = key.substring(PREFIX_ENV.length());
ValStr<?> val = ent.getValue().get(args);
if (val == null || val.val() == null) {
env.put(varName, "");
}
else {
env.put(varName, Objects.toString(val.val()));
}
env.put(varName, ValStr.normStr(val));
}
}
}
private int argc = 0;
private int argc;
private String title;
private StringBuilder description;
private List<String> menuPath;
private String menuGroup;
private String menuOrder;
private String iconId;
private HelpLocation helpLocation;
private String menuGroup;
private String menuOrder;
private List<String> menuPath;
private final Map<String, UserType<?>> userTypes = new HashMap<>();
private final Map<String, LaunchParameter<?>> parameters = new LinkedHashMap<>();
private final Map<String, TtyCondition> extraTtys = new LinkedHashMap<>();
private int timeoutMillis = AbstractTraceRmiLaunchOffer.DEFAULT_TIMEOUT_MILLIS;
private boolean noImage = false;
private String imageOptKey;
/**
* Check if a line should just be ignored, e.g., blank lines, or the "shebang" line on UNIX.
@ -489,36 +486,66 @@ public abstract class ScriptAttributesParser {
return;
}
if (parts.length == 1) {
switch (parts[0].trim()) {
case AT_NOIMAGE -> parseNoImage(loc);
default -> parseUnrecognized(loc, comment);
}
parseUnrecognized(loc, comment);
}
else {
switch (parts[0].trim()) {
case AT_TITLE -> parseTitle(loc, parts[1]);
case AT_DESC -> parseDesc(loc, parts[1]);
case AT_MENU_PATH -> parseMenuPath(loc, parts[1]);
case AT_MENU_GROUP -> parseMenuGroup(loc, parts[1]);
case AT_MENU_ORDER -> parseMenuOrder(loc, parts[1]);
case AT_ICON -> parseIcon(loc, parts[1]);
case AT_HELP -> parseHelp(loc, parts[1]);
case AT_ENUM -> parseEnum(loc, parts[1]);
case AT_ENV -> parseEnv(loc, parts[1]);
case AT_ARG -> parseArg(loc, parts[1], ++argc);
case AT_ARGS -> parseArgs(loc, parts[1]);
case AT_TTY -> parseTty(loc, parts[1]);
case AT_DESC -> parseDesc(loc, parts[1]);
case AT_ENUM -> parseEnum(loc, parts[1]);
case AT_ENV -> parseEnv(loc, parts[1]);
case AT_HELP -> parseHelp(loc, parts[1]);
case AT_ICON -> parseIcon(loc, parts[1]);
case AT_IMAGE_OPT -> parseImageOpt(loc, parts[1]);
case AT_MENU_GROUP -> parseMenuGroup(loc, parts[1]);
case AT_MENU_ORDER -> parseMenuOrder(loc, parts[1]);
case AT_MENU_PATH -> parseMenuPath(loc, parts[1]);
case AT_TIMEOUT -> parseTimeout(loc, parts[1]);
case AT_TITLE -> parseTitle(loc, parts[1]);
case AT_TTY -> parseTty(loc, parts[1]);
default -> parseUnrecognized(loc, comment);
}
}
}
protected void parseTitle(Location loc, String str) {
if (title != null) {
reportWarning("%s: Duplicate %s".formatted(loc, AT_TITLE));
protected void parseArg(Location loc, String str, int argNum) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 3) {
reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
return;
}
String colonType = parts.get(0).trim();
if (!colonType.startsWith(":")) {
reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
return;
}
OptType<?> type;
boolean required = colonType.endsWith("!");
int endType = required ? colonType.length() - 1 : colonType.length();
try {
type = OptType.parse(loc, colonType.substring(1, endType), userTypes);
String name = PREFIX_ARG + argNum;
parameters.put(name, type.createParameter(name, parts.get(1), parts.get(2), required,
new ValStr<>(null, "")));
}
catch (ParseException e) {
reportError(e.getMessage());
}
}
protected void parseArgs(Location loc, String str) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 2) {
reportError(MSGPAT_INVALID_ARGS_SYNTAX.formatted(loc, AT_ARGS));
return;
}
LaunchParameter<String> parameter = BaseType.STRING.createParameter(
"args", parts.get(0), parts.get(1), false, ValStr.str(""));
if (parameters.put(KEY_ARGS, parameter) != null) {
reportWarning("%s: Duplicate %s. Replaced".formatted(loc, AT_ARGS));
}
title = str;
}
protected void parseDesc(Location loc, String str) {
@ -529,54 +556,6 @@ public abstract class ScriptAttributesParser {
description.append("\n");
}
protected void parseMenuPath(Location loc, String str) {
if (menuPath != null) {
reportWarning("%s: Duplicate %s".formatted(loc, AT_MENU_PATH));
}
menuPath = List.of(str.trim().split("\\."));
if (menuPath.isEmpty()) {
reportError(
"%s: Empty %s. Ignoring.".formatted(loc, AT_MENU_PATH));
}
}
protected void parseMenuGroup(Location loc, String str) {
if (menuGroup != null) {
reportWarning("%s: Duplicate %s".formatted(loc, AT_MENU_GROUP));
}
menuGroup = str;
}
protected void parseMenuOrder(Location loc, String str) {
if (menuOrder != null) {
reportWarning("%s: Duplicate %s".formatted(loc, AT_MENU_ORDER));
}
menuOrder = str;
}
protected void parseIcon(Location loc, String str) {
if (iconId != null) {
reportWarning("%s: Duplicate %s".formatted(loc, AT_ICON));
}
iconId = str.trim();
if (!Gui.hasIcon(iconId)) {
reportError(
"%s: Icon id %s not registered in the theme".formatted(loc, iconId));
}
}
protected void parseHelp(Location loc, String str) {
if (helpLocation != null) {
reportWarning("%s: Duplicate %s".formatted(loc, AT_HELP));
}
String[] parts = str.trim().split("#", 2);
if (parts.length != 2) {
reportError(MSGPAT_INVALID_HELP_SYNTAX.formatted(loc, AT_HELP));
return;
}
helpLocation = new HelpLocation(parts[0].trim(), parts[1].trim());
}
protected <T> UserType<T> parseEnumChoices(Location loc, BaseType<T> baseType,
List<String> choiceParts) {
List<T> choices = new ArrayList<>();
@ -642,10 +621,14 @@ public abstract class ScriptAttributesParser {
reportError(MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV));
return;
}
String typePart = tadParts[0].trim();
boolean required = typePart.endsWith("!");
int endType = required ? typePart.length() - 1 : typePart.length();
try {
TypeAndDefault<?> tad =
TypeAndDefault.parse(loc, tadParts[0].trim(), tadParts[1].trim(), userTypes);
LaunchParameter<?> param = tad.createParameter(name, parts.get(1), parts.get(2));
TypeAndDefault<?> tad = TypeAndDefault.parse(loc, typePart.substring(0, endType),
tadParts[1].trim(), userTypes);
LaunchParameter<?> param =
tad.createParameter(name, parts.get(1), parts.get(2), required);
if (parameters.put(name, param) != null) {
reportWarning("%s: Duplicate %s %s. Replaced.".formatted(loc, AT_ENV, trimmed));
}
@ -655,42 +638,75 @@ public abstract class ScriptAttributesParser {
}
}
protected void parseArg(Location loc, String str, int argNum) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 3) {
reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
protected void parseHelp(Location loc, String str) {
if (helpLocation != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_HELP));
}
String[] parts = str.trim().split("#", 2);
if (parts.length != 2) {
reportError(MSGPAT_INVALID_HELP_SYNTAX.formatted(loc, AT_HELP));
return;
}
String colonType = parts.get(0).trim();
if (!colonType.startsWith(":")) {
reportError(MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
return;
helpLocation = new HelpLocation(parts[0].trim(), parts[1].trim());
}
protected void parseIcon(Location loc, String str) {
if (iconId != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_ICON));
}
OptType<?> type;
try {
type = OptType.parse(loc, colonType.substring(1), userTypes);
String name = PREFIX_ARG + argNum;
parameters.put(name,
type.createParameter(name, parts.get(1), parts.get(2), true,
new ValStr<>(null, "")));
}
catch (ParseException e) {
reportError(e.getMessage());
iconId = str.trim();
if (!Gui.hasIcon(iconId)) {
reportError(
"%s: Icon id %s not registered in the theme".formatted(loc, iconId));
}
}
protected void parseArgs(Location loc, String str) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 2) {
reportError(MSGPAT_INVALID_ARGS_SYNTAX.formatted(loc, AT_ARGS));
return;
protected void parseImageOpt(Location loc, String str) {
if (imageOptKey != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_IMAGE_OPT));
}
imageOptKey = str.strip();
}
LaunchParameter<String> parameter = BaseType.STRING.createParameter(
"args", parts.get(0), parts.get(1), false, ValStr.str(""));
if (parameters.put(KEY_ARGS, parameter) != null) {
reportWarning("%s: Duplicate %s. Replaced".formatted(loc, AT_ARGS));
protected void parseMenuGroup(Location loc, String str) {
if (menuGroup != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_MENU_GROUP));
}
menuGroup = str;
}
protected void parseMenuOrder(Location loc, String str) {
if (menuOrder != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_MENU_ORDER));
}
menuOrder = str;
}
protected void parseMenuPath(Location loc, String str) {
if (menuPath != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_MENU_PATH));
}
menuPath = List.of(str.trim().split("\\."));
if (menuPath.isEmpty()) {
reportError(
"%s: Empty %s. Ignoring.".formatted(loc, AT_MENU_PATH));
}
}
protected void parseTimeout(Location loc, String str) {
try {
timeoutMillis = Integer.parseInt(str);
}
catch (NumberFormatException e) {
reportError(MSGPAT_INVALID_TIMEOUT_SYNTAX.formatted(loc, AT_TIMEOUT));
}
}
protected void parseTitle(Location loc, String str) {
if (title != null) {
reportWarning(MSGPAT_DUPLICATE_TAG.formatted(loc, AT_TITLE));
}
title = str;
}
protected void putTty(Location loc, String name, TtyCondition condition) {
@ -749,19 +765,6 @@ public abstract class ScriptAttributesParser {
reportError(MSGPAT_INVALID_TTY_SYNTAX.formatted(loc, AT_TTY));
}
protected void parseTimeout(Location loc, String str) {
try {
timeoutMillis = Integer.parseInt(str);
}
catch (NumberFormatException e) {
reportError(MSGPAT_INVALID_TIMEOUT_SYNTAX.formatted(loc, AT_TIMEOUT));
}
}
protected void parseNoImage(Location loc) {
noImage = true;
}
protected void parseUnrecognized(Location loc, String line) {
reportWarning("%s: Unrecognized metadata: %s".formatted(loc, line));
}
@ -784,10 +787,18 @@ public abstract class ScriptAttributesParser {
if (iconId == null) {
iconId = "icon.debugger";
}
LaunchParameter<?> imageOpt = null;
if (imageOptKey != null) {
imageOpt = parameters.get(imageOptKey);
if (imageOpt == null) {
reportError("%s: %s refers to %s, which is not a parameter name".formatted(fileName,
AT_IMAGE_OPT, imageOptKey));
}
}
return new ScriptAttributes(title, getDescription(), List.copyOf(menuPath), menuGroup,
menuOrder, new GIcon(iconId), helpLocation,
Collections.unmodifiableMap(new LinkedHashMap<>(parameters)),
Collections.unmodifiableMap(new LinkedHashMap<>(extraTtys)), timeoutMillis, noImage);
Collections.unmodifiableMap(new LinkedHashMap<>(extraTtys)), timeoutMillis, imageOpt);
}
private String getDescription() {

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -42,8 +42,13 @@ import ghidra.framework.model.DomainFile;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.ProgramUserData;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.lang.Processor;
import ghidra.program.model.lang.ProcessorNotFoundException;
import ghidra.program.model.listing.*;
import ghidra.program.model.scalar.Scalar;
import ghidra.util.Msg;
import ghidra.util.bean.opteditor.OptionsVetoException;
import ghidra.util.classfinder.ClassSearcher;
@ -124,6 +129,102 @@ public class TraceRmiLauncherServicePlugin extends Plugin
}
}
public static class FieldIndex {
public static FieldIndex fromData(Data data) {
if (!(data.getDataType() instanceof Composite dt)) {
return null;
}
return new FieldIndex(dt);
}
private final Map<String, DataTypeComponent> byName;
public FieldIndex(Composite dt) {
byName = Stream.of(dt.getComponents())
.collect(Collectors.toMap(c -> c.getFieldName(), c -> c));
}
public Data getField(Data data, String name) {
DataTypeComponent dtComp = byName.get(name);
if (dtComp == null) {
return null;
}
return data.getComponent(dtComp.getOrdinal());
}
}
public static String tryProgramJvmClass(Program program) {
Processor procJvm;
try {
procJvm = Processor.toProcessor("JVM");
}
catch (ProcessorNotFoundException e) {
return null;
}
if (program.getLanguage().getProcessor() != procJvm) {
return null;
}
AddressSpace cpool =
program.getLanguage().getAddressFactory().getAddressSpace("constantPool");
Data dClassFile = program.getListing().getDataAt(cpool.getAddress(0));
if (dClassFile == null) {
return null;
}
FieldIndex fiClassFile = FieldIndex.fromData(dClassFile);
if (fiClassFile == null) {
return null;
}
Data dThisClass = fiClassFile.getField(dClassFile, "this_class");
if (dThisClass == null || !(dThisClass.getValue() instanceof Scalar sThisClass)) {
return null;
}
long thisClassCpi = sThisClass.getValue();
Data dConstantPool = fiClassFile.getField(dClassFile, "constant_pool");
if (dConstantPool == null) {
return null;
}
FieldIndex fiConstantPool = FieldIndex.fromData(dConstantPool);
if (fiConstantPool == null) {
return null;
}
Data dThisClassConst =
fiConstantPool.getField(dConstantPool, "constant_pool_0x%x".formatted(thisClassCpi));
if (dThisClassConst == null ||
!"CONSTANT_Class_info".equals(dThisClassConst.getDataType().getName())) {
return null;
}
FieldIndex fiConstantClassInfo = FieldIndex.fromData(dThisClassConst);
if (fiConstantClassInfo == null) {
return null;
}
Data dThisClassNameIndex = fiConstantClassInfo.getField(dThisClassConst, "name_index");
if (dThisClassNameIndex == null ||
!(dThisClassNameIndex.getValue() instanceof Scalar sThisClassNameIndex)) {
return null;
}
long thisClassNameIndexCpi = sThisClassNameIndex.getValue();
Data dThisClassNameConst = fiConstantPool.getField(dConstantPool,
"constant_pool_0x%x".formatted(thisClassNameIndexCpi));
if (dThisClassNameConst == null ||
!dThisClassNameConst.getDataType().getName().startsWith("CONSTANT_Utf8_info")) {
return null;
}
FieldIndex fiUtf8InfoN = FieldIndex.fromData(dThisClassNameConst);
if (fiUtf8InfoN == null) {
return null;
}
Data dThisClassNameData = fiUtf8InfoN.getField(dThisClassNameConst, "data");
if (!(dThisClassNameData.getValue() instanceof String thisClassName)) {
return null;
}
return thisClassName;
}
public static File tryProgramPath(String path) {
if (path == null) {
return null;
@ -154,6 +255,12 @@ public class TraceRmiLauncherServicePlugin extends Plugin
if (program == null) {
return null;
}
// TODO: All these tryers should be extension points...?
// Probably applicable by language/file type.
String jvmClass = tryProgramJvmClass(program);
if (jvmClass != null) {
return jvmClass;
}
File exec = tryProgramPath(program.getExecutablePath());
if (exec != null) {
return exec.getAbsolutePath();
@ -371,8 +478,7 @@ public class TraceRmiLauncherServicePlugin extends Plugin
toolLaunchConfigs.putSaveState(name, state);
}
protected record ConfigLast(String configName, long last, Program program) {
}
protected record ConfigLast(String configName, long last, Program program) {}
protected ConfigLast checkSavedConfig(Program program, ProgramUserData userData,
String propName) {

View File

@ -448,12 +448,13 @@ public class TraceRmiHandler implements TraceRmiConnection {
return true;
}
catch (IOException e) {
Msg.error(this, "Cannot send reply", e);
Msg.error(this, "Cannot send reply: " + e);
return false;
}
}
public void receiveLoop() {
boolean canSend = true;
try {
while (true) {
RootMessage req = receive();
@ -468,8 +469,15 @@ public class TraceRmiHandler implements TraceRmiConnection {
continue;
}
if (!send(rep)) {
return;
/**
* The likely cause of this failing is that the remote end has closed the socket.
* However, we don't return, because there may be commands still in the queue, and
* we should process them until we reach the end of input. This will ensure clients
* that brazenly send a bunch of commands and then disconnect before receiving the
* replies will have their commands processed, even if unsuccessfully.
*/
if (canSend) {
canSend = send(rep);
}
}
}

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -25,6 +25,7 @@ import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.services.*;
import ghidra.debug.api.progress.CloseableTaskMonitor;
import ghidra.debug.api.target.Target;
import ghidra.debug.api.tracermi.*;
import ghidra.debug.api.tracermi.TraceRmiServiceListener.ConnectMode;
import ghidra.framework.plugintool.*;
@ -71,8 +72,8 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
}
}
@AutoServiceConsumed
private DebuggerTargetService targetService;
// @AutoServiceConsumed // via method
private volatile DebuggerTargetService targetService;
@AutoServiceConsumed
private ProgressService progressService;
@SuppressWarnings("unused")
@ -94,6 +95,25 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
}
@AutoServiceConsumed
public void setTargetService(DebuggerTargetService targetService) {
this.targetService = targetService;
record ConnAndTarget(TraceRmiConnection conn, Target target) {}
List<ConnAndTarget> targets = new ArrayList<>();
synchronized (handlers) {
for (TraceRmiConnection conn : getAllConnections()) {
for (Target target : conn.getTargets()) {
targets.add(new ConnAndTarget(conn, target));
}
}
}
for (ConnAndTarget cat : targets) {
targetService.publishTarget(cat.target);
listeners.invoke().targetPublished(cat.conn, cat.target);
}
}
protected CloseableTaskMonitor createMonitor() {
if (progressService == null) {
return fallbackMonitor;
@ -201,6 +221,15 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
void publishTarget(TraceRmiHandler handler, TraceRmiTarget target) {
Swing.runIfSwingOrRunLater(() -> {
if (targetService == null) {
/**
* I have no idea how this is happening, given targetService is a required service,
* and I don't see any way the rmi server can be started before the services are
* wired in. Whatever. I'll have to publish all the targets when the service
* appears, I guess.
*/
return;
}
targetService.publishTarget(target);
listeners.invoke().targetPublished(handler, target);
});
@ -208,6 +237,10 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
void withdrawTarget(TraceRmiTarget target) {
Swing.runIfSwingOrRunLater(() -> {
if (targetService == null) {
// This can happen during tear down.
return;
}
targetService.withdrawTarget(target);
});
}

View File

@ -334,13 +334,13 @@ public class TraceRmiTarget extends AbstractTarget {
ActionName.STEP_OUT.equals(name) ||
ActionName.STEP_OVER.equals(name) ||
ActionName.STEP_SKIP.equals(name)) {
return () -> whenState(obj, state -> state != null && state.isStopped());
return () -> whenState(obj, state -> state != null && (state.isStopped() || state.isUnknown()));
}
else if (ActionName.INTERRUPT.equals(name)) {
return () -> whenState(obj, state -> state == null || state.isRunning());
return () -> whenState(obj, state -> state == null || state.isRunning() || state.isUnknown());
}
else if (ActionName.KILL.equals(name)) {
return () -> whenState(obj, state -> state == null || state.isAlive());
return () -> whenState(obj, state -> state == null || !state.isTerminated());
}
return () -> true;
}

View File

@ -0,0 +1,141 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.tracermi;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.awt.Component;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
import ghidra.app.plugin.core.debug.gui.InvocationDialogHelper;
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.TestRemoteMethod;
import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.TestRemoteParameter;
import ghidra.async.SwingExecutorService;
import ghidra.dbg.target.TargetMethod.Param;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.target.schema.*;
import ghidra.dbg.target.schema.EnumerableTargetObjectSchema.MinimalSchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.debug.api.ValStr;
import ghidra.debug.api.tracermi.RemoteMethod;
import ghidra.debug.api.tracermi.RemoteParameter;
import ghidra.framework.options.PropertyBoolean;
public class RemoteMethodInvocationDialogTest extends AbstractGhidraHeadedDebuggerTest {
private static final SchemaContext CTX = MinimalSchemaContext.INSTANCE;
public static TestRemoteMethod createTestMethod(Method m) {
Map<String, RemoteParameter> params = new LinkedHashMap<>();
for (Parameter p : m.getParameters()) {
TestRemoteParameter parameter = createParameter(p);
params.put(parameter.name(), parameter);
}
return new TestRemoteMethod(m.getName(), null, "Test", "A test method", params,
EnumerableTargetObjectSchema.schemaForPrimitive(m.getReturnType()));
}
public static TestRemoteParameter createParameter(Parameter p) {
ParameterDescription<?> desc = ParameterDescription.annotated(p);
TargetObjectSchema schema = EnumerableTargetObjectSchema.schemaForPrimitive(desc.type);
if (schema == EnumerableTargetObjectSchema.OBJECT ||
schema == EnumerableTargetObjectSchema.ANY) {
schema = CTX.getSchema(new SchemaName(desc.schema));
}
return new TestRemoteParameter(desc.name, schema, desc.required, desc.defaultValue,
desc.display, desc.description);
}
public static Map<String, Object> getDefaults(RemoteMethod method) {
Map<String, Object> result = new HashMap<>();
for (Map.Entry<String, RemoteParameter> ent : method.parameters().entrySet()) {
result.put(ent.getKey(), ent.getValue().getDefaultValue());
}
return result;
}
record TestBits(TestRemoteMethod method, CompletableFuture<Map<String, ValStr<?>>> future,
InvocationDialogHelper<RemoteParameter, ?> helper) {
Component getComponent(String name) {
return helper.getEditorComponent(method.parameters().get(name));
}
void setArg(String name, Object value) {
helper.setArg(method.parameters().get(name), value);
}
Map<String, Object> invoke() throws Exception {
helper.invoke();
Map<String, ValStr<?>> args = future.get(1, TimeUnit.SECONDS);
return args == null ? null : ValStr.toPlainMap(args);
}
}
protected TestBits startTest(Method m) throws Exception {
TestRemoteMethod method = createTestMethod(m);
Map<String, Object> defaults = getDefaults(method);
Map<String, ValStr<?>> defs = ValStr.fromPlainMap(defaults);
RemoteMethodInvocationDialog dialog =
new RemoteMethodInvocationDialog(tool, CTX, method.display(), method.display(), null);
CompletableFuture<Map<String, ValStr<?>>> future = CompletableFuture.supplyAsync(
() -> dialog.promptArguments(method.parameters(), defs, defs),
SwingExecutorService.LATER);
// Yes, I have it in hand, but I still must wait for it to appear on screen.
InvocationDialogHelper<RemoteParameter, ?> helper =
InvocationDialogHelper.waitFor(RemoteMethodInvocationDialog.class);
return new TestBits(method, future, helper);
}
public static class MethodTakesBooleanPrimitive {
public void theMethod(@Param(name = "b") boolean b) {
}
}
@Test
public void testBooleanPrimitiveField() throws Exception {
TestBits bits =
startTest(MethodTakesBooleanPrimitive.class.getMethod("theMethod", boolean.class));
assertTrue(bits.getComponent("b") instanceof PropertyBoolean);
bits.setArg("b", true);
Map<String, Object> values = bits.invoke();
assertEquals(true, values.get("b"));
}
public static class MethodTakesBooleanBoxed {
public void theMethod(@Param(name = "b") Boolean b) {
}
}
@Test
public void testBooleanBoxedField() throws Exception {
TestBits bits =
startTest(MethodTakesBooleanBoxed.class.getMethod("theMethod", Boolean.class));
assertTrue(bits.getComponent("b") instanceof PropertyBoolean);
bits.setArg("b", true);
Map<String, Object> values = bits.invoke();
assertEquals(true, values.get("b"));
}
}

View File

@ -0,0 +1 @@
# Debugger-swig-lldb

View File

@ -3,6 +3,7 @@
##MODULE IP: Apache License 2.0 with LLVM Exceptions
InstructionsForBuildingLLDBInterface.txt||GHIDRA||||END|
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
build.gradle||GHIDRA||||END|
src/llvm-project/lldb/bindings/java/java-typemaps.swig||Apache License 2.0 with LLVM Exceptions||||END|
src/llvm-project/lldb/bindings/java/java.swig||Apache License 2.0 with LLVM Exceptions||||END|

View File

@ -0,0 +1 @@
# Debugger

View File

@ -5,6 +5,7 @@
##MODULE IP: Oxygen Icons - LGPL 3.0
##MODULE IP: Tango Icons - Public Domain
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END|
data/debugger.theme.properties||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||||END|

View File

@ -637,6 +637,9 @@ public abstract class AbstractDebuggerParameterDialog<P> extends DialogComponent
protected void setEditorValue(PropertyEditor editor, P param, ValStr<?> val) {
switch (val.val()) {
case null -> {
if (parameterType(param) == String.class) {
editor.setValue(val.str());
}
}
case BigInteger bi -> editor.setAsText(val.str());
default -> editor.setValue(val.val());

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -90,7 +90,7 @@ public class ObjectTree implements ObjectPane {
if (selectionPaths != null && selectionPaths.length > 0) {
TreePath currentPath = currentSelectionPaths[0];
TreePath selectedPath = selectionPaths[0];
// NB. isDescendant == has a descendent
// NB. isDescendant == has a descendant
if (selectedPath.isDescendant(currentPath)) {
return;
}

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