GP-2677: Introduce TraceRmi (API only, experimental)

This commit is contained in:
Dan
2023-04-21 16:17:59 -04:00
parent 0fe70e15fa
commit 1de4dfc9c7
96 changed files with 19314 additions and 214 deletions
+1
View File
@@ -68,6 +68,7 @@ Release
.classpath
.settings/
.prefs
.pydevproject
# Ignore XTEXT generated dirs/files
*/*/*/*/xtend-gen
@@ -20,6 +20,7 @@ apply from: "$rootProject.projectDir/gradle/nativeProject.gradle"
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
apply from: "$rootProject.projectDir/gradle/debugger/hasExecutableJar.gradle"
apply from: "$rootProject.projectDir/gradle/debugger/hasPythonPackage.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Debug Debugger-agent-gdb'
@@ -33,6 +34,8 @@ dependencies {
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
testImplementation project(path: ':Debugger-gadp', configuration: 'testArtifacts')
pypkgInstall project(path: ':Debugger-rmi-trace', configuration: 'pypkgInstall')
}
tasks.nodepJar {
@@ -1,7 +1,13 @@
##VERSION: 2.0
##MODULE IP: JSch License
DEVNOTES.txt||GHIDRA||||END|
Module.manifest||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|
data/scripts/wine32_info_proc_mappings.gdb||GHIDRA||||END|
src/main/py/LICENSE||GHIDRA||||END|
src/main/py/README.md||GHIDRA||||END|
src/main/py/ghidragdb/schema.xml||GHIDRA||||END|
src/main/py/pyproject.toml||GHIDRA||||END|
src/main/py/tests/EMPTY||GHIDRA||||END|
@@ -21,8 +21,10 @@ public interface GdbBreakpointInsertions {
/**
* Insert a breakpoint
*
* <p>
* This is equivalent to the CLI command: {@code break [LOC]}, or {@code watch [LOC]}, etc.
*
* <p>
* Breakpoints in GDB can get pretty complicated. Depending on the location specification, the
* actual location of the breakpoint may change during the lifetime of an inferior. Take note of
* the breakpoint number to track those changes across breakpoint modification events.
@@ -0,0 +1,11 @@
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.
@@ -0,0 +1,3 @@
# Ghidra Trace RMI
Package for connecting GDB to Ghidra via Trace RMI.
@@ -0,0 +1,16 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from . import util, commands, parameters
@@ -0,0 +1,287 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ghidratrace.client import Address, RegVal
import gdb
# NOTE: This map is derived from the ldefs using a script
language_map = {
'aarch64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
'aarch64:ilp32': ['AARCH64:BE:32:ilp32', 'AARCH64:LE:32:ilp32', 'AARCH64:LE:64:AppleSilicon'],
'arm_any': ['ARM:BE:32:v8', 'ARM:BE:32:v8T', 'ARM:LE:32:v8', 'ARM:LE:32:v8T'],
'armv2': ['ARM:BE:32:v4', 'ARM:LE:32:v4'],
'armv2a': ['ARM:BE:32:v4', 'ARM:LE:32:v4'],
'armv3': ['ARM:BE:32:v4', 'ARM:LE:32:v4'],
'armv3m': ['ARM:BE:32:v4', 'ARM:LE:32:v4'],
'armv4': ['ARM:BE:32:v4', 'ARM:LE:32:v4'],
'armv4t': ['ARM:BE:32:v4t', 'ARM:LE:32:v4t'],
'armv5': ['ARM:BE:32:v5', 'ARM:LE:32:v5'],
'armv5t': ['ARM:BE:32:v5t', 'ARM:LE:32:v5t'],
'armv5tej': ['ARM:BE:32:v5t', 'ARM:LE:32:v5t'],
'armv6': ['ARM:BE:32:v6', 'ARM:LE:32:v6'],
'armv6-m': ['ARM:BE:32:Cortex', 'ARM:LE:32:Cortex'],
'armv6k': ['ARM:BE:32:Cortex', 'ARM:LE:32:Cortex'],
'armv6kz': ['ARM:BE:32:Cortex', 'ARM:LE:32:Cortex'],
'armv6s-m': ['ARM:BE:32:Cortex', 'ARM:LE:32:Cortex'],
'armv7': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
'armv7e-m': ['ARM:LE:32:Cortex'],
'armv8-a': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
'armv8-m.base': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
'armv8-m.main': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
'armv8-r': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
'armv8.1-m.main': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
'avr:107': ['avr8:LE:24:xmega'],
'avr:31': ['avr8:LE:16:default'],
'avr:51': ['avr8:LE:16:atmega256'],
'avr:6': ['avr8:LE:16:atmega256'],
'hppa2.0w': ['pa-risc:BE:32:default'],
'i386:intel': ['x86:LE:32:default'],
'i386:x86-64': ['x86:LE:64:default'],
'i386:x86-64:intel': ['x86:LE:64:default'],
'i8086': ['x86:LE:16:Protected Mode', 'x86:LE:16:Real Mode'],
'iwmmxt': ['ARM:BE:32:v7', 'ARM:BE:32:v8', 'ARM:BE:32:v8T', 'ARM:LE:32:v7', 'ARM:LE:32:v8', 'ARM:LE:32:v8T'],
'm68hc12': ['HC-12:BE:16:default'],
'm68k': ['68000:BE:32:default'],
'm68k:68020': ['68000:BE:32:MC68020'],
'm68k:68030': ['68000:BE:32:MC68030'],
'm9s12x': ['HCS-12:BE:24:default', 'HCS-12X:BE:24:default'],
'mips:4000': ['MIPS:BE:32:default', 'MIPS:LE:32:default'],
'mips:5000': ['MIPS:BE:64:64-32addr', 'MIPS:BE:64:default', 'MIPS:LE:64:64-32addr', 'MIPS:LE:64:default'],
'mips:micromips': ['MIPS:BE:32:micro'],
'msp:430X': ['TI_MSP430:LE:16:default'],
'powerpc:403': ['PowerPC:BE:32:4xx', 'PowerPC:LE:32:4xx'],
'powerpc:MPC8XX': ['PowerPC:BE:32:MPC8270', 'PowerPC:BE:32:QUICC', 'PowerPC:LE:32:QUICC'],
'powerpc:common': ['PowerPC:BE:32:default', 'PowerPC:LE:32:default'],
'powerpc:common64': ['PowerPC:BE:64:64-32addr', 'PowerPC:BE:64:default', 'PowerPC:LE:64:64-32addr', 'PowerPC:LE:64:default'],
'powerpc:e500': ['PowerPC:BE:32:e500', 'PowerPC:LE:32:e500'],
'powerpc:e500mc': ['PowerPC:BE:64:A2ALT', 'PowerPC:LE:64:A2ALT'],
'powerpc:e500mc64': ['PowerPC:BE:64:A2-32addr', 'PowerPC:BE:64:A2ALT-32addr', 'PowerPC:LE:64:A2-32addr', 'PowerPC:LE:64:A2ALT-32addr'],
'riscv:rv32': ['RISCV:LE:32:RV32G', 'RISCV:LE:32:RV32GC', 'RISCV:LE:32:RV32I', 'RISCV:LE:32:RV32IC', 'RISCV:LE:32:RV32IMC', 'RISCV:LE:32:default'],
'riscv:rv64': ['RISCV:LE:64:RV64G', 'RISCV:LE:64:RV64GC', 'RISCV:LE:64:RV64I', 'RISCV:LE:64:RV64IC', 'RISCV:LE:64:default'],
'sh4': ['SuperH4:BE:32:default', 'SuperH4:LE:32:default'],
'sparc:v9b': ['sparc:BE:32:default', 'sparc:BE:64:default'],
'xscale': ['ARM:BE:32:v6', 'ARM:LE:32:v6'],
'z80': ['z80:LE:16:default', 'z8401x:LE:16:default']
}
data64_compiler_map = {
None: 'pointer64',
}
x86_compiler_map = {
'GNU/Linux': 'gcc',
'Windows': 'Visual Studio',
# This may seem wrong, but Ghidra cspecs really describe the ABI
'Cygwin': 'Visual Studio',
}
compiler_map = {
'DATA:BE:64:default': data64_compiler_map,
'DATA:LE:64:default': data64_compiler_map,
'x86:LE:32:default': x86_compiler_map,
'x86:LE:64:default': x86_compiler_map,
}
def get_arch():
return gdb.selected_inferior().architecture().name()
def get_endian():
parm = gdb.parameter('endian')
if parm != 'auto':
return parm
# Once again, we have to hack using the human-readable 'show'
show = gdb.execute('show endian', to_string=True)
if 'little' in show:
return 'little'
if 'big' in show:
return 'big'
return 'unrecognized'
def get_osabi():
parm = gdb.parameter('osabi')
if not parm in ['auto', 'default']:
return parm
# We have to hack around the fact the GDB won't give us the current OS ABI
# via the API if it is "auto" or "default". Using "show", we can get it, but
# we have to parse output meant for a human. The current value will be on
# the top line, delimited by double quotes. It will be the last delimited
# thing on that line. ("auto" may appear earlier on the line.)
show = gdb.execute('show osabi', to_string=True)
line = show.split('\n')[0]
return line.split('"')[-2]
def compute_ghidra_language():
# First, check if the parameter is set
lang = gdb.parameter('ghidra-language')
if lang != 'auto':
return lang
# Get the list of possible languages for the arch. We'll need to sift
# through them by endian and probably prefer default/simpler variants. The
# heuristic for "simpler" will be 'default' then shortest variant id.
arch = get_arch()
endian = get_endian()
lebe = ':BE:' if endian == 'big' else ':LE:'
if not arch in language_map:
return 'DATA' + lebe + '64:default'
langs = language_map[arch]
matched_endian = sorted(
(l for l in langs if lebe in l),
key=lambda l: 0 if l.endswith(':default') else len(l)
)
if len(matched_endian) > 0:
return matched_endian[0]
# NOTE: I'm disinclined to fall back to a language match with wrong endian.
return 'DATA' + lebe + '64:default'
def compute_ghidra_compiler(lang):
# First, check if the parameter is set
comp = gdb.parameter('ghidra-compiler')
if comp != 'auto':
return comp
# Check if the selected lang has specific compiler recommendations
if not lang in compiler_map:
return 'default'
comp_map = compiler_map[lang]
osabi = get_osabi()
if osabi in comp_map:
return comp_map[osabi]
if None in comp_map:
return comp_map[None]
return 'default'
def compute_ghidra_lcsp():
lang = compute_ghidra_language()
comp = compute_ghidra_compiler(lang)
return lang, comp
class DefaultMemoryMapper(object):
def __init__(self, defaultSpace):
self.defaultSpace = defaultSpace
def map(self, inf: gdb.Inferior, offset: int):
if inf.num == 1:
space = self.defaultSpace
else:
space = f'{self.defaultSpace}{inf.num}'
return self.defaultSpace, Address(space, offset)
def map_back(self, inf: gdb.Inferior, address: Address) -> int:
if address.space == self.defaultSpace and inf.num == 1:
return address.offset
if address.space == f'{self.defaultSpace}{inf.num}':
return address.offset
raise ValueError(f"Address {address} is not in inferior {inf.num}")
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
memory_mappers = {}
def compute_memory_mapper(lang):
if not lang in memory_mappers:
return DEFAULT_MEMORY_MAPPER
return memory_mappers[lang]
class DefaultRegisterMapper(object):
def __init__(self, byte_order):
if not byte_order in ['big', 'little']:
raise ValueError("Invalid byte_order: {}".format(byte_order))
self.byte_order = byte_order
self.union_winners = {}
def map_name(self, inf, name):
return name
def convert_value(self, value, type=None):
if type is None:
type = value.dynamic_type.strip_typedefs()
l = type.sizeof
# l - 1 because array() takes the max index, inclusive
# NOTE: Might like to pre-lookup 'unsigned char', but it depends on the
# architecture *at the time of lookup*.
cv = value.cast(gdb.lookup_type('unsigned char').array(l - 1))
rng = range(l)
if self.byte_order == 'little':
rng = reversed(rng)
return bytes(cv[i] for i in rng)
def map_value(self, inf, name, value):
try:
av = self.convert_value(value)
except gdb.error as e:
raise gdb.GdbError("Cannot convert {}'s value: '{}', type: '{}'"
.format(name, value, value.type))
return RegVal(self.map_name(inf, name), av)
def map_name_back(self, inf, name):
return name
def map_value_back(self, inf, name, value):
return RegVal(self.map_name_back(inf, name), value)
class Intel_x86_64_RegisterMapper(DefaultRegisterMapper):
def __init__(self):
super().__init__('little')
def map_name(self, inf, name):
if name == 'eflags':
return 'rflags'
if name.startswith('zmm'):
# Ghidra only goes up to ymm, right now
return 'ymm' + name[3:]
return super().map_name(inf, name)
def map_value(self, inf, name, value):
rv = super().map_value(inf, name, value)
if rv.name.startswith('ymm') and len(rv.value) > 32:
return RegVal(rv.name, rv.value[-32:])
return rv
def map_name_back(self, inf, name):
if name == 'rflags':
return 'eflags'
DEFAULT_BE_REGISTER_MAPPER = DefaultRegisterMapper('big')
DEFAULT_LE_REGISTER_MAPPER = DefaultRegisterMapper('little')
register_mappers = {
'x86:LE:64:default': Intel_x86_64_RegisterMapper()
}
def compute_register_mapper(lang):
if not lang in register_mappers:
if ':BE:' in lang:
return DEFAULT_BE_REGISTER_MAPPER
if ':LE:' in lang:
return DEFAULT_LE_REGISTER_MAPPER
return register_mappers[lang]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,46 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
import gdb
# TODO: I don't know how to register a custom parameter prefix. I would rather
# these were 'ghidra language' and 'ghidra compiler'
class GhidraLanguageParameter(gdb.Parameter):
"""
The language id for Ghidra traces. Set this to 'auto' to try to derive it
from 'show arch' and 'show endian'. Otherwise, set it to a Ghidra
LanguageID.
"""
def __init__(self):
super().__init__('ghidra-language', gdb.COMMAND_DATA, gdb.PARAM_STRING)
self.value = 'auto'
GhidraLanguageParameter()
class GhidraCompilerParameter(gdb.Parameter):
"""
The compiler spec id for Ghidra traces. Set this to 'auto' to try to derive
it from 'show osabi'. Otherwise, set it to a Ghidra CompilerSpecID. Note
that valid compiler spec ids depend on the language id.
"""
def __init__(self):
super().__init__('ghidra-compiler', gdb.COMMAND_DATA, gdb.PARAM_STRING)
self.value = 'auto'
GhidraCompilerParameter()
@@ -0,0 +1,413 @@
<context>
<schema name="Session" elementResync="NEVER" attributeResync="NEVER">
<interface name="Access" />
<interface name="Attacher" />
<interface name="Interpreter" />
<interface name="Interruptible" />
<interface name="Launcher" />
<interface name="ActiveScope" />
<interface name="EventScope" />
<interface name="FocusScope" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="Inferiors" schema="InferiorContainer" required="yes" fixed="yes" />
<attribute name="Available" schema="AvailableContainer" required="yes" fixed="yes" />
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
<attribute name="_accessible" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_supported_attach_kinds" schema="SET_ATTACH_KIND" required="yes" hidden="yes" />
<attribute name="_prompt" schema="STRING" required="yes" hidden="yes" />
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
<attribute name="_event_thread" schema="OBJECT" hidden="yes" />
<attribute name="_focus" schema="Selectable" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Selectable" elementResync="NEVER" attributeResync="NEVER">
<element schema="OBJECT" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointSpecContainer" />
<element schema="BreakpointSpec" />
<attribute name="_supported_breakpoint_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="AvailableContainer" canonical="yes" elementResync="ALWAYS" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Attachable" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_base" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="InferiorContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Inferior" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_base" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointSpec" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointSpec" />
<interface name="Deletable" />
<interface name="Togglable" />
<element schema="BreakpointLocation" />
<attribute name="_container" schema="BreakpointContainer" required="yes" hidden="yes" />
<attribute name="_expression" schema="STRING" required="yes" hidden="yes" />
<attribute name="_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
<attribute name="Commands" schema="STRING" />
<attribute name="Condition" schema="STRING" />
<attribute name="Hit Count" schema="INT" />
<attribute name="Ignore Count" schema="INT" />
<attribute name="Pending" schema="BOOL" />
<attribute name="Silent" schema="BOOL" />
<attribute name="Temporary" schema="BOOL" />
<attribute schema="VOID" />
</schema>
<schema name="Attachable" elementResync="NEVER" attributeResync="NEVER">
<interface name="Attachable" />
<element schema="VOID" />
<attribute name="_pid" schema="LONG" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Inferior" elementResync="NEVER" attributeResync="NEVER">
<interface name="Process" />
<interface name="Aggregate" />
<interface name="ExecutionStateful" />
<interface name="Attacher" />
<interface name="Deletable" />
<interface name="Detachable" />
<interface name="Killable" />
<interface name="Launcher" />
<interface name="Resumable" />
<interface name="Steppable" />
<interface name="Interruptible" />
<element schema="VOID" />
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
<attribute name="Breakpoints" schema="BreakpointLocationContainer" required="yes" fixed="yes" />
<attribute name="_exit_code" schema="LONG" />
<attribute name="Environment" schema="Environment" required="yes" fixed="yes" />
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
<attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" />
<attribute name="_pid" schema="LONG" hidden="yes" />
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute name="_supported_attach_kinds" schema="SET_ATTACH_KIND" required="yes" hidden="yes" />
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Environment" elementResync="NEVER" attributeResync="NEVER">
<interface name="Environment" />
<element schema="VOID" />
<attribute name="arch" schema="STRING" />
<attribute name="os" schema="STRING" />
<attribute name="endian" schema="STRING" />
<attribute name="_arch" schema="STRING" hidden="yes" />
<attribute name="_debugger" schema="STRING" hidden="yes" />
<attribute name="_os" schema="STRING" hidden="yes" />
<attribute name="_endian" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ModuleContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<interface name="ModuleContainer" />
<element schema="Module" />
<attribute name="_supports_synthetic_modules" schema="BOOL" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Memory" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Memory" />
<element schema="MemoryRegion" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointLocation" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointLocation" />
<element schema="VOID" />
<attribute name="_range" schema="RANGE" hidden="yes" />
<attribute name="_spec" schema="BreakpointSpec" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointLocationContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointLocationContainer" />
<element schema="BreakpointLocation" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ThreadContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Thread" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_base" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="Method" elementResync="NEVER" attributeResync="NEVER">
<interface name="Method" />
<element schema="VOID" />
<attribute name="_display" schema="STRING" required="yes" fixed="yes" hidden="yes" />
<attribute name="_return_type" schema="TYPE" required="yes" fixed="yes" hidden="yes" />
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" fixed="yes" hidden="yes" />
<attribute schema="VOID" fixed="yes" hidden="yes" />
</schema>
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
<interface name="Thread" />
<interface name="ExecutionStateful" />
<interface name="Steppable" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="Stack" schema="Stack" required="yes" fixed="yes" />
<attribute name="_tid" schema="LONG" hidden="yes" />
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="Advance" schema="Method" required="yes" fixed="yes" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Module" elementResync="NEVER" attributeResync="NEVER">
<interface name="Module" />
<element schema="VOID" />
<attribute name="Sections" schema="SectionContainer" required="yes" fixed="yes" />
<attribute name="Symbols" schema="SymbolContainer" required="yes" fixed="yes" />
<attribute name="range" schema="RANGE" />
<attribute name="module name" schema="STRING" />
<attribute name="_module_name" schema="STRING" required="yes" hidden="yes" />
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="MemoryRegion" elementResync="NEVER" attributeResync="NEVER">
<interface name="MemoryRegion" />
<element schema="VOID" />
<attribute name="_offset" schema="LONG" required="yes" fixed="yes" hidden="yes" />
<attribute name="_objfile" schema="STRING" required="yes" fixed="yes" hidden="yes" />
<attribute name="_readable" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_writable" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_executable" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
<attribute name="_memory" schema="Memory" required="yes" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="SectionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="SectionContainer" />
<element schema="Section" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Stack" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Stack" />
<element schema="StackFrame" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="SymbolContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<interface name="SymbolNamespace" />
<element schema="Symbol" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Symbol" elementResync="NEVER" attributeResync="NEVER">
<interface name="Symbol" />
<element schema="VOID" />
<attribute name="_size" schema="LONG" fixed="yes" hidden="yes" />
<attribute name="_namespace" schema="SymbolContainer" required="yes" fixed="yes" hidden="yes" />
<attribute name="_data_type" schema="DATA_TYPE" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ADDRESS" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_bpt" schema="STRING" />
<attribute schema="VOID" />
</schema>
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
<interface name="StackFrame" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="_function" schema="STRING" hidden="yes" />
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
<attribute name="_pc" schema="ADDRESS" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Section" elementResync="NEVER" attributeResync="NEVER">
<interface name="Section" />
<element schema="VOID" />
<attribute name="range" schema="RANGE" />
<attribute name="_module" schema="Module" required="yes" fixed="yes" hidden="yes" />
<attribute name="_range" schema="RANGE" required="yes" fixed="yes" />
<attribute name="_offset" schema="INT" required="no" fixed="yes" />
<attribute name="_objfile" schema="STRING" required="no" fixed="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="RegisterValueContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<interface name="RegisterContainer" />
<interface name="RegisterBank" />
<element schema="RegisterValue" />
<attribute name="_descriptions" schema="RegisterValueContainer" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="RegisterValue" elementResync="NEVER" attributeResync="NEVER">
<interface name="Register" />
<element schema="VOID" />
<attribute name="_container" schema="OBJECT" required="yes" fixed="yes" hidden="yes" />
<attribute name="_length" schema="INT" required="yes" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
</context>
@@ -0,0 +1,286 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from collections import namedtuple
import re
import gdb
GdbVersion = namedtuple('GdbVersion', ['full', 'major', 'minor'])
def _compute_gdb_ver():
blurb = gdb.execute('show version', to_string=True)
top = blurb.split('\n')[0]
full = top.split(' ')[-1]
major, minor = full.split('.')[:2]
return GdbVersion(full, int(major), int(minor))
GDB_VERSION = _compute_gdb_ver()
MODULES_CMD_V8 = 'maintenance info sections ALLOBJ'
MODULES_CMD_V11 = 'maintenance info sections -all-objects'
OBJFILE_PATTERN_V8 = re.compile("\\s*Object file: (?P<name>.*)")
OBJFILE_PATTERN_V11 = re.compile(
"\\s*((Object)|(Exec)) file: `(?P<name>.*)', file type (?P<type>.*)")
OBJFILE_SECTION_PATTERN_V8 = re.compile("\\s*" +
"0x(?P<vmaS>[0-9A-Fa-f]+)\\s*->\\s*" +
"0x(?P<vmaE>[0-9A-Fa-f]+)\\s+at\\s+" +
"0x(?P<offset>[0-9A-Fa-f]+)\\s*:\\s*" +
"(?P<name>\\S+)\\s+" +
"(?P<attrs>.*)")
OBJFILE_SECTION_PATTERN_V9 = re.compile("\\s*" +
"\\[\\s*(?P<idx>\\d+)\\]\\s+" +
"0x(?P<vmaS>[0-9A-Fa-f]+)\\s*->\\s*" +
"0x(?P<vmaE>[0-9A-Fa-f]+)\\s+at\\s+" +
"0x(?P<offset>[0-9A-Fa-f]+)\\s*:\\s*" +
"(?P<name>\\S+)\\s+" +
"(?P<attrs>.*)")
GNU_DEBUGDATA_PREFIX = ".gnu_debugdata for "
class Module(namedtuple('BaseModule', ['name', 'base', 'max', 'sections'])):
pass
class Section(namedtuple('BaseSection', ['name', 'start', 'end', 'offset', 'attrs'])):
def better(self, other):
start = self.start if self.start != 0 else other.start
end = self.end if self.end != 0 else other.end
offset = self.offset if self.offset != 0 else other.offset
attrs = dict.fromkeys(self.attrs)
attrs.update(dict.fromkeys(other.attrs))
return Section(self.name, start, end, offset, list(attrs))
def try_hexint(val, name):
try:
return int(val, 16)
except ValueError:
gdb.write("Invalid {}: {}".format(name, val), stream=gdb.STDERR)
return 0
# AFAICT, Objfile does not give info about load addresses :(
class ModuleInfoReader(object):
def name_from_line(self, line):
mat = self.objfile_pattern.fullmatch(line)
if mat is None:
return None
n = mat['name']
if n.startswith(GNU_DEBUGDATA_PREFIX):
return None
return None if mat is None else mat['name']
def section_from_line(self, line):
mat = self.section_pattern.fullmatch(line)
if mat is None:
return None
start = try_hexint(mat['vmaS'], 'section start')
end = try_hexint(mat['vmaE'], 'section end')
offset = try_hexint(mat['offset'], 'section offset')
name = mat['name']
attrs = [a for a in mat['attrs'].split(' ') if a != '']
return Section(name, start, end, offset, attrs)
def finish_module(self, name, sections):
alloc = {k: s for k, s in sections.items() if 'ALLOC' in s.attrs}
if len(alloc) == 0:
return Module(name, 0, 0, alloc)
# TODO: This may not be the module base, depending on headers
base_addr = min(s.start - s.offset for s in alloc.values())
max_addr = max(s.end for s in alloc.values())
return Module(name, base_addr, max_addr, alloc)
def get_modules(self):
modules = {}
out = gdb.execute(self.cmd, to_string=True)
name = None
sections = None
for line in out.split('\n'):
n = self.name_from_line(line)
if n is not None:
if name is not None:
modules[name] = self.finish_module(name, sections)
name = n
sections = {}
continue
if name is None:
# Don't waste time parsing if no module
continue
s = self.section_from_line(line)
if s is not None:
if s.name in sections:
s = s.better(sections[s.name])
sections[s.name] = s
if name is not None:
modules[name] = self.finish_module(name, sections)
return modules
class ModuleInfoReaderV8(ModuleInfoReader):
cmd = MODULES_CMD_V8
objfile_pattern = OBJFILE_PATTERN_V8
section_pattern = OBJFILE_SECTION_PATTERN_V8
class ModuleInfoReaderV9(ModuleInfoReader):
cmd = MODULES_CMD_V8
objfile_pattern = OBJFILE_PATTERN_V8
section_pattern = OBJFILE_SECTION_PATTERN_V9
class ModuleInfoReaderV11(ModuleInfoReader):
cmd = MODULES_CMD_V11
objfile_pattern = OBJFILE_PATTERN_V11
section_pattern = OBJFILE_SECTION_PATTERN_V9
def _choose_module_info_reader():
if GDB_VERSION.major == 8:
return ModuleInfoReaderV8()
elif GDB_VERSION.major == 9:
return ModuleInfoReaderV9()
elif GDB_VERSION.major == 10:
return ModuleInfoReaderV9()
elif GDB_VERSION.major == 11:
return ModuleInfoReaderV11()
elif GDB_VERSION.major == 12:
return ModuleInfoReaderV11()
elif GDB_VERSION.major > 12:
return ModuleInfoReaderV11()
else:
raise gdb.GdbError(
"GDB version not recognized by ghidragdb: " + GDB_VERSION.full)
MODULE_INFO_READER = _choose_module_info_reader()
REGIONS_CMD = 'info proc mappings'
REGION_PATTERN_V8 = re.compile("\\s*" +
"0x(?P<start>[0-9,A-F,a-f]+)\\s+" +
"0x(?P<end>[0-9,A-F,a-f]+)\\s+" +
"0x(?P<size>[0-9,A-F,a-f]+)\\s+" +
"0x(?P<offset>[0-9,A-F,a-f]+)\\s+" +
"(?P<objfile>.*)")
REGION_PATTERN_V12 = re.compile("\\s*" +
"0x(?P<start>[0-9,A-F,a-f]+)\\s+" +
"0x(?P<end>[0-9,A-F,a-f]+)\\s+" +
"0x(?P<size>[0-9,A-F,a-f]+)\\s+" +
"0x(?P<offset>[0-9,A-F,a-f]+)\\s+" +
"(?P<perms>[rwsxp\\-]+)\\s+" +
"(?P<objfile>.*)")
class Region(namedtuple('BaseRegion', ['start', 'end', 'offset', 'perms', 'objfile'])):
pass
class RegionInfoReader(object):
def region_from_line(self, line):
mat = self.region_pattern.fullmatch(line)
if mat is None:
return None
start = try_hexint(mat['start'], 'region start')
end = try_hexint(mat['end'], 'region end')
offset = try_hexint(mat['offset'], 'region offset')
perms = self.get_region_perms(mat)
objfile = mat['objfile']
return Region(start, end, offset, perms, objfile)
def get_regions(self):
regions = []
out = gdb.execute(self.cmd, to_string=True)
for line in out.split('\n'):
r = self.region_from_line(line)
if r is None:
continue
regions.append(r)
return regions
def full_mem(self):
# TODO: This may not work for Harvard architectures
sizeptr = int(gdb.parse_and_eval('sizeof(void*)')) * 8
return Region(0, 1 << sizeptr, 0, None, 'full memory')
class RegionInfoReaderV8(RegionInfoReader):
cmd = REGIONS_CMD
region_pattern = REGION_PATTERN_V8
def get_region_perms(self, mat):
return None
class RegionInfoReaderV12(RegionInfoReader):
cmd = REGIONS_CMD
region_pattern = REGION_PATTERN_V12
def get_region_perms(self, mat):
return mat['perms']
def _choose_region_info_reader():
if 8 <= GDB_VERSION.major < 12:
return RegionInfoReaderV8()
elif GDB_VERSION.major >= 12:
return RegionInfoReaderV12()
else:
raise gdb.GdbError(
"GDB version not recognized by ghidragdb: " + GDB_VERSION.full)
REGION_INFO_READER = _choose_region_info_reader()
BREAK_LOCS_CMD = 'info break {}'
BREAK_PATTERN = re.compile('')
BREAK_LOC_PATTERN = re.compile('')
class BreakpointLocation(namedtuple('BaseBreakpointLocation', ['address', 'enabled', 'thread_groups'])):
pass
class BreakpointLocationInfoReaderV8(object):
def breakpoint_from_line(self, line):
pass
def location_from_line(self, line):
pass
def get_locations(self, breakpoint):
pass
class BreakpointLocationInfoReaderV13(object):
def get_locations(self, breakpoint):
return breakpoint.locations
def _choose_breakpoint_location_info_reader():
if 8 <= GDB_VERSION.major < 13:
return BreakpointLocationInfoReaderV8()
elif GDB_VERSION.major >= 13:
return BreakpointLocationInfoReaderV13()
else:
raise gdb.GdbError(
"GDB version not recognized by ghidragdb: " + GDB_VERSION.full)
BREAKPOINT_LOCATION_INFO_READER = _choose_breakpoint_location_info_reader()
@@ -0,0 +1,25 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "ghidragdb"
version = "10.4"
authors = [
{ name="Ghidra Development Team" },
]
description = "Ghidra's Plugin for gdb"
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
]
dependencies = [
"ghidratrace==10.4",
]
[project.urls]
"Homepage" = "https://github.com/NationalSecurityAgency/ghidra"
"Bug Tracker" = "https://github.com/NationalSecurityAgency/ghidra/issues"
@@ -30,49 +30,49 @@ import ghidra.dbg.util.ShellUtils;
public enum GdbLinuxSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUtils {
SLEEP {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expTraceableSleep");
}
},
FORK_EXIT {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expFork");
}
},
CLONE_EXIT {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expCloneExit");
}
},
PRINT {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expPrint");
}
},
REGISTERS {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expRegisters");
}
},
SPIN_STRIPPED {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expSpin.stripped");
}
},
STACK {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expStack");
}
};
abstract String getCommandLine();
public abstract String getCommandLine();
@Override
public DummyProc runDummy() throws Throwable {
@@ -20,6 +20,7 @@ apply from: "$rootProject.projectDir/gradle/nativeProject.gradle"
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
apply from: "$rootProject.projectDir/gradle/debugger/hasExecutableJar.gradle"
apply from: "$rootProject.projectDir/gradle/debugger/hasPythonPackage.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Debug Debugger-agent-lldb'
@@ -33,6 +34,8 @@ dependencies {
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
testImplementation project(path: ':Debugger-gadp', configuration: 'testArtifacts')
pypkgInstall project(path: ':Debugger-rmi-trace', configuration: 'pypkgInstall')
}
tasks.nodepJar {
@@ -5,7 +5,9 @@
.project||NONE||reviewed||END|
Module.manifest||GHIDRA||||END|
build.gradle||GHIDRA||||END|
data/InstructionsForBuildingLLDBInterface.txt||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|
src/llvm-project/lldb/build_script||GHIDRA||||END|
src/main/py/LICENSE||GHIDRA||||END|
src/main/py/README.md||GHIDRA||||END|
src/main/py/ghidralldb/schema.xml||GHIDRA||||END|
src/main/py/pyproject.toml||GHIDRA||||END|
@@ -0,0 +1,11 @@
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.
@@ -0,0 +1,3 @@
# Ghidra Trace RMI
Package for connecting LLDB to Ghidra via Trace RMI.
@@ -0,0 +1,16 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from . import util, commands
@@ -0,0 +1,261 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ghidratrace.client import Address, RegVal
import lldb
from . import util
# NOTE: This map is derived from the ldefs using a script
language_map = {
'aarch64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
'armv7': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
'armv7k': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
'armv7s': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
'arm64': ['ARM:BE:64:v8', 'ARM:LE:64:v8'],
'arm64_32': ['ARM:BE:32:v8', 'ARM:LE:32:v8'],
'arm64e': ['ARM:BE:64:v8', 'ARM:LE:64:v8'],
'i386': ['x86:LE:32:default'],
'thumbv7': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
'thumbv7k': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
'thumbv7s': ['ARM:BE:32:v7', 'ARM:LE:32:v7'],
'x86_64': ['x86:LE:64:default'],
'wasm32': ['x86:LE:64:default'],
}
data64_compiler_map = {
None: 'pointer64',
}
x86_compiler_map = {
'freebsd': 'gcc',
'linux': 'gcc',
'netbsd': 'gcc',
'ps4': 'gcc',
'ios': 'clang',
'macosx': 'clang',
'tvos': 'clang',
'watchos': 'clang',
'windows': 'Visual Studio',
# This may seem wrong, but Ghidra cspecs really describe the ABI
'Cygwin': 'Visual Studio',
}
compiler_map = {
'DATA:BE:64:default': data64_compiler_map,
'DATA:LE:64:default': data64_compiler_map,
'x86:LE:32:default': x86_compiler_map,
'x86:LE:64:default': x86_compiler_map,
}
def get_arch():
triple = util.get_target().triple
if triple is None:
return "x86_64"
return triple.split('-')[0]
def get_endian():
parm = util.get_convenience_variable('endian')
if parm != 'auto':
return parm
# Once again, we have to hack using the human-readable 'show'
order = util.get_target().GetByteOrder()
if order is lldb.eByteOrderLittle:
return 'little'
if order is lldb.eByteOrderBig:
return 'big'
if order is lldb.eByteOrderPDP:
return 'pdp'
return 'unrecognized'
def get_osabi():
parm = util.get_convenience_variable('osabi')
if not parm in ['auto', 'default']:
return parm
# We have to hack around the fact the LLDB won't give us the current OS ABI
# via the API if it is "auto" or "default". Using "show", we can get it, but
# we have to parse output meant for a human. The current value will be on
# the top line, delimited by double quotes. It will be the last delimited
# thing on that line. ("auto" may appear earlier on the line.)
triple = util.get_target().triple
# this is an unfortunate feature of the tests
if triple is None:
return "linux"
return triple.split('-')[2]
def compute_ghidra_language():
# First, check if the parameter is set
lang = util.get_convenience_variable('ghidra-language')
if lang != 'auto':
return lang
# Get the list of possible languages for the arch. We'll need to sift
# through them by endian and probably prefer default/simpler variants. The
# heuristic for "simpler" will be 'default' then shortest variant id.
arch = get_arch()
endian = get_endian()
lebe = ':BE:' if endian == 'big' else ':LE:'
if not arch in language_map:
return 'DATA' + lebe + '64:default'
langs = language_map[arch]
matched_endian = sorted(
(l for l in langs if lebe in l),
key=lambda l: 0 if l.endswith(':default') else len(l)
)
if len(matched_endian) > 0:
return matched_endian[0]
# NOTE: I'm disinclined to fall back to a language match with wrong endian.
return 'DATA' + lebe + '64:default'
def compute_ghidra_compiler(lang):
# First, check if the parameter is set
comp = util.get_convenience_variable('ghidra-compiler')
if comp != 'auto':
return comp
# Check if the selected lang has specific compiler recommendations
if not lang in compiler_map:
return 'default'
comp_map = compiler_map[lang]
osabi = get_osabi()
if osabi in comp_map:
return comp_map[osabi]
if None in comp_map:
return comp_map[None]
return 'default'
def compute_ghidra_lcsp():
lang = compute_ghidra_language()
comp = compute_ghidra_compiler(lang)
return lang, comp
class DefaultMemoryMapper(object):
def __init__(self, defaultSpace):
self.defaultSpace = defaultSpace
def map(self, proc: lldb.SBProcess, offset: int):
space = self.defaultSpace
return self.defaultSpace, Address(space, offset)
def map_back(self, proc: lldb.SBProcess, address: Address) -> int:
if address.space == self.defaultSpace:
return address.offset
raise ValueError(f"Address {address} is not in process {proc.GetProcessID()}")
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
memory_mappers = {}
def compute_memory_mapper(lang):
if not lang in memory_mappers:
return DEFAULT_MEMORY_MAPPER
return memory_mappers[lang]
class DefaultRegisterMapper(object):
def __init__(self, byte_order):
if not byte_order in ['big', 'little']:
raise ValueError("Invalid byte_order: {}".format(byte_order))
self.byte_order = byte_order
self.union_winners = {}
def map_name(self, proc, name):
return name
"""
def convert_value(self, value, type=None):
if type is None:
type = value.dynamic_type.strip_typedefs()
l = type.sizeof
# l - 1 because array() takes the max index, inclusive
# NOTE: Might like to pre-lookup 'unsigned char', but it depends on the
# architecture *at the time of lookup*.
cv = value.cast(lldb.lookup_type('unsigned char').array(l - 1))
rng = range(l)
if self.byte_order == 'little':
rng = reversed(rng)
return bytes(cv[i] for i in rng)
"""
def map_value(self, proc, name, value):
try:
### TODO: this seems half-baked
av = value.to_bytes(8, "big")
except e:
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
.format(name, value, value.type))
return RegVal(self.map_name(proc, name), av)
def map_name_back(self, proc, name):
return name
def map_value_back(self, proc, name, value):
return RegVal(self.map_name_back(proc, name), value)
class Intel_x86_64_RegisterMapper(DefaultRegisterMapper):
def __init__(self):
super().__init__('little')
def map_name(self, proc, name):
if name is None:
return 'UNKNOWN'
if name == 'eflags':
return 'rflags'
if name.startswith('zmm'):
# Ghidra only goes up to ymm, right now
return 'ymm' + name[3:]
return super().map_name(proc, name)
def map_value(self, proc, name, value):
rv = super().map_value(proc, name, value)
if rv.name.startswith('ymm') and len(rv.value) > 32:
return RegVal(rv.name, rv.value[-32:])
return rv
def map_name_back(self, proc, name):
if name == 'rflags':
return 'eflags'
DEFAULT_BE_REGISTER_MAPPER = DefaultRegisterMapper('big')
DEFAULT_LE_REGISTER_MAPPER = DefaultRegisterMapper('little')
register_mappers = {
'x86:LE:64:default': Intel_x86_64_RegisterMapper()
}
def compute_register_mapper(lang):
if not lang in register_mappers:
if ':BE:' in lang:
return DEFAULT_BE_REGISTER_MAPPER
if ':LE:' in lang:
return DEFAULT_LE_REGISTER_MAPPER
return register_mappers[lang]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,46 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
import lldb
# TODO: I don't know how to register a custom parameter prefix. I would rather
# these were 'ghidra language' and 'ghidra compiler'
class GhidraLanguageParameter(lldb.Parameter):
"""
The language id for Ghidra traces. Set this to 'auto' to try to derive it
from 'show arch' and 'show endian'. Otherwise, set it to a Ghidra
LanguageID.
"""
def __init__(self):
super().__init__('ghidra-language', lldb.COMMAND_DATA, lldb.PARAM_STRING)
self.value = 'auto'
GhidraLanguageParameter()
class GhidraCompilerParameter(lldb.Parameter):
"""
The compiler spec id for Ghidra traces. Set this to 'auto' to try to derive
it from 'show osabi'. Otherwise, set it to a Ghidra CompilerSpecID. Note
that valid compiler spec ids depend on the language id.
"""
def __init__(self):
super().__init__('ghidra-compiler', lldb.COMMAND_DATA, lldb.PARAM_STRING)
self.value = 'auto'
GhidraCompilerParameter()
@@ -0,0 +1,465 @@
<context>
<schema name="Session" elementResync="NEVER" attributeResync="NEVER">
<interface name="Access" />
<interface name="Attacher" />
<interface name="Interpreter" />
<interface name="Interruptible" />
<interface name="Launcher" />
<interface name="ActiveScope" />
<interface name="EventScope" />
<interface name="FocusScope" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="Processes" schema="ProcessContainer" required="yes" fixed="yes" />
<attribute name="Available" schema="AvailableContainer" required="yes" fixed="yes" />
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
<attribute name="Watchpoints" schema="WatchpointContainer" required="yes" fixed="yes" />
<attribute name="_accessible" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_supported_attach_kinds" schema="SET_ATTACH_KIND" required="yes" hidden="yes" />
<attribute name="_prompt" schema="STRING" required="yes" hidden="yes" />
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
<attribute name="_event_thread" schema="OBJECT" hidden="yes" />
<attribute name="_focus" schema="Selectable" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Selectable" elementResync="NEVER" attributeResync="NEVER">
<element schema="OBJECT" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointSpecContainer" />
<element schema="BreakpointSpec" />
<attribute name="_supported_breakpoint_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="WatchpointContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="WatchpointSpecContainer" />
<element schema="WatchpointSpec" />
<attribute name="_supported_breakpoint_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="AvailableContainer" canonical="yes" elementResync="ALWAYS" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Attachable" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_base" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="ProcessContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Process" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_base" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointSpec" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointSpec" />
<interface name="Deletable" />
<interface name="Togglable" />
<element schema="BreakpointLocation" />
<attribute name="_container" schema="BreakpointContainer" required="yes" hidden="yes" />
<attribute name="_expression" schema="STRING" required="yes" hidden="yes" />
<attribute name="_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
<attribute name="Commands" schema="STRING" />
<attribute name="Condition" schema="STRING" />
<attribute name="Hit Count" schema="INT" />
<attribute name="Ignore Count" schema="INT" />
<attribute name="Pending" schema="BOOL" />
<attribute name="Silent" schema="BOOL" />
<attribute name="Temporary" schema="BOOL" />
<attribute schema="VOID" />
</schema>
<schema name="WatchpointSpec" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointSpec" />
<interface name="Deletable" />
<interface name="Togglable" />
<attribute name="_container" schema="WatchpointContainer" required="yes" hidden="yes" />
<attribute name="_expression" schema="STRING" required="yes" hidden="yes" />
<attribute name="_kinds" schema="SET_BREAKPOINT_KIND" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_range" schema="RANGE" hidden="yes" />
<attribute name="Condition" schema="STRING" />
<attribute name="Hit Count" schema="INT" />
<attribute name="Ignore Count" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="Attachable" elementResync="NEVER" attributeResync="NEVER">
<interface name="Attachable" />
<element schema="VOID" />
<attribute name="_pid" schema="LONG" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Process" elementResync="NEVER" attributeResync="NEVER">
<interface name="Process" />
<interface name="Aggregate" />
<interface name="ExecutionStateful" />
<interface name="Attacher" />
<interface name="Deletable" />
<interface name="Detachable" />
<interface name="Killable" />
<interface name="Launcher" />
<interface name="Resumable" />
<interface name="Steppable" />
<interface name="Interruptible" />
<element schema="VOID" />
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
<attribute name="Breakpoints" schema="BreakpointLocationContainer" required="yes" fixed="yes" />
<attribute name="Watchpoints" schema="WatchpointContainer" required="yes" fixed="yes" />
<attribute name="_exit_code" schema="LONG" />
<attribute name="Environment" schema="Environment" required="yes" fixed="yes" />
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
<attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" />
<attribute name="_pid" schema="LONG" hidden="yes" />
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute name="_supported_attach_kinds" schema="SET_ATTACH_KIND" required="yes" hidden="yes" />
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" hidden="yes" />
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Environment" elementResync="NEVER" attributeResync="NEVER">
<interface name="Environment" />
<element schema="VOID" />
<attribute name="arch" schema="STRING" />
<attribute name="os" schema="STRING" />
<attribute name="endian" schema="STRING" />
<attribute name="_arch" schema="STRING" hidden="yes" />
<attribute name="_debugger" schema="STRING" hidden="yes" />
<attribute name="_os" schema="STRING" hidden="yes" />
<attribute name="_endian" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ModuleContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<interface name="ModuleContainer" />
<element schema="Module" />
<attribute name="_supports_synthetic_modules" schema="BOOL" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Memory" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Memory" />
<element schema="MemoryRegion" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointLocation" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointLocation" />
<element schema="VOID" />
<attribute name="_range" schema="RANGE" hidden="yes" />
<attribute name="_spec" schema="BreakpointSpec" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="BreakpointLocationContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="BreakpointLocationContainer" />
<element schema="BreakpointLocation" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ThreadContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Configurable" />
<element schema="Thread" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_base" schema="INT" />
<attribute schema="VOID" />
</schema>
<schema name="Method" elementResync="NEVER" attributeResync="NEVER">
<interface name="Method" />
<element schema="VOID" />
<attribute name="_display" schema="STRING" required="yes" fixed="yes" hidden="yes" />
<attribute name="_return_type" schema="TYPE" required="yes" fixed="yes" hidden="yes" />
<attribute name="_parameters" schema="MAP_PARAMETERS" required="yes" fixed="yes" hidden="yes" />
<attribute schema="VOID" fixed="yes" hidden="yes" />
</schema>
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
<interface name="Thread" />
<interface name="ExecutionStateful" />
<interface name="Steppable" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="Stack" schema="Stack" required="yes" fixed="yes" />
<attribute name="_tid" schema="LONG" hidden="yes" />
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="Advance" schema="Method" required="yes" fixed="yes" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Module" elementResync="NEVER" attributeResync="NEVER">
<interface name="Module" />
<element schema="VOID" />
<attribute name="Sections" schema="SectionContainer" required="yes" fixed="yes" />
<attribute name="Symbols" schema="SymbolContainer" required="yes" fixed="yes" />
<attribute name="range" schema="RANGE" />
<attribute name="module name" schema="STRING" />
<attribute name="_module_name" schema="STRING" required="yes" hidden="yes" />
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="MemoryRegion" elementResync="NEVER" attributeResync="NEVER">
<interface name="MemoryRegion" />
<element schema="VOID" />
<attribute name="_offset" schema="LONG" required="yes" fixed="yes" hidden="yes" />
<attribute name="_objfile" schema="STRING" required="yes" fixed="yes" hidden="yes" />
<attribute name="_readable" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_writable" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_executable" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
<attribute name="_memory" schema="Memory" required="yes" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="SectionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="SectionContainer" />
<element schema="Section" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Stack" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Stack" />
<element schema="StackFrame" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="SymbolContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<interface name="SymbolNamespace" />
<element schema="Symbol" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Symbol" elementResync="NEVER" attributeResync="NEVER">
<interface name="Symbol" />
<element schema="VOID" />
<attribute name="_size" schema="LONG" fixed="yes" hidden="yes" />
<attribute name="_namespace" schema="SymbolContainer" required="yes" fixed="yes" hidden="yes" />
<attribute name="_data_type" schema="DATA_TYPE" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ADDRESS" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_bpt" schema="STRING" />
<attribute schema="VOID" />
</schema>
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
<interface name="StackFrame" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="_function" schema="STRING" hidden="yes" />
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
<attribute name="_pc" schema="ADDRESS" required="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Section" elementResync="NEVER" attributeResync="NEVER">
<interface name="Section" />
<element schema="VOID" />
<attribute name="range" schema="RANGE" />
<attribute name="_module" schema="Module" required="yes" fixed="yes" hidden="yes" />
<attribute name="_range" schema="RANGE" required="yes" fixed="yes" />
<attribute name="_offset" schema="INT" required="no" fixed="yes" />
<attribute name="_objfile" schema="STRING" required="no" fixed="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="RegisterValueContainer" canonical="yes" elementResync="ONCE" attributeResync="ONCE">
<interface name="RegisterContainer" />
<interface name="RegisterBank" />
<element schema="RegisterValue" />
<attribute name="General Purpose Registers" schema="RegisterBank" />
<attribute name="Floating Point Registers" schema="RegisterBank" />
<attribute name="Advanced Vector Extensions" schema="RegisterBank" />
<attribute name="Memory Protection Extensions" schema="RegisterBank" />
<attribute name="_descriptions" schema="RegisterValueContainer" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<interface name="RegisterBank" />
<element schema="RegisterValue" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="RegisterValue" elementResync="NEVER" attributeResync="NEVER">
<interface name="Register" />
<element schema="VOID" />
<attribute name="_container" schema="OBJECT" required="yes" fixed="yes" hidden="yes" />
<attribute name="_length" schema="INT" required="yes" fixed="yes" hidden="yes" />
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute schema="VOID" />
</schema>
</context>
@@ -0,0 +1,236 @@
## ###
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from collections import namedtuple
import os
import re
import sys
import lldb
LldbVersion = namedtuple('LldbVersion', ['full', 'major', 'minor'])
def _compute_lldb_ver():
blurb = lldb.debugger.GetVersionString()
top = blurb.split('\n')[0]
full = top.split(' ')[2]
major, minor = full.split('.')[:2]
return LldbVersion(full, int(major), int(minor))
LLDB_VERSION = _compute_lldb_ver()
GNU_DEBUGDATA_PREFIX = ".gnu_debugdata for "
class Module(namedtuple('BaseModule', ['name', 'base', 'max', 'sections'])):
pass
class Section(namedtuple('BaseSection', ['name', 'start', 'end', 'offset', 'attrs'])):
def better(self, other):
start = self.start if self.start != 0 else other.start
end = self.end if self.end != 0 else other.end
offset = self.offset if self.offset != 0 else other.offset
attrs = dict.fromkeys(self.attrs)
attrs.update(dict.fromkeys(other.attrs))
return Section(self.name, start, end, offset, list(attrs))
# AFAICT, Objfile does not give info about load addresses :(
class ModuleInfoReader(object):
def name_from_line(self, line):
mat = self.objfile_pattern.fullmatch(line)
if mat is None:
return None
n = mat['name']
if n.startswith(GNU_DEBUGDATA_PREFIX):
return None
return None if mat is None else mat['name']
def section_from_sbsection(self, s):
start = s.GetLoadAddress(get_target())
if start >= sys.maxsize*2:
start = 0
end = start + s.GetFileByteSize()
offset = s.GetFileOffset()
name = s.GetName()
attrs = s.GetPermissions()
return Section(name, start, end, offset, attrs)
def finish_module(self, name, sections):
alloc = {k: s for k, s in sections.items()}
if len(alloc) == 0:
return Module(name, 0, 0, alloc)
# TODO: This may not be the module base, depending on headers
all_zero = True
for s in alloc.values():
if s.start != 0:
all_zero = False
if all_zero:
base_addr = 0
else:
base_addr = min(s.start for s in alloc.values() if s.start != 0)
max_addr = max(s.end for s in alloc.values())
return Module(name, base_addr, max_addr, alloc)
def get_modules(self):
modules = {}
name = None
sections = {}
for i in range(0, get_target().GetNumModules()):
module = get_target().GetModuleAtIndex(i)
fspec = module.GetFileSpec()
name = debracket(fspec.GetFilename())
sections = {}
for i in range(0, module.GetNumSections()):
s = self.section_from_sbsection(module.GetSectionAtIndex(i))
sname = debracket(s.name)
sections[sname] = s
modules[name] = self.finish_module(name, sections)
return modules
def _choose_module_info_reader():
return ModuleInfoReader()
MODULE_INFO_READER = _choose_module_info_reader()
class Region(namedtuple('BaseRegion', ['start', 'end', 'offset', 'perms', 'objfile'])):
pass
class RegionInfoReader(object):
def region_from_sbmemreg(self, info):
start = info.GetRegionBase()
end = info.GetRegionEnd()
offset = info.GetRegionBase()
if offset >= sys.maxsize:
offset = 0
perms = ""
if info.IsReadable():
perms += 'r'
if info.IsWritable():
perms += 'w'
if info.IsExecutable():
perms += 'x'
objfile = info.GetName()
return Region(start, end, offset, perms, objfile)
def get_regions(self):
regions = []
reglist = get_process().GetMemoryRegions()
for i in range(0, reglist.GetSize()):
module = get_target().GetModuleAtIndex(i)
info = lldb.SBMemoryRegionInfo();
success = reglist.GetMemoryRegionAtIndex(i, info);
if success:
r = self.region_from_sbmemreg(info)
regions.append(r)
return regions
def full_mem(self):
# TODO: This may not work for Harvard architectures
sizeptr = int(parse_and_eval('sizeof(void*)')) * 8
return Region(0, 1 << sizeptr, 0, None, 'full memory')
def _choose_region_info_reader():
return RegionInfoReader()
REGION_INFO_READER = _choose_region_info_reader()
BREAK_LOCS_CMD = 'breakpoint list {}'
BREAK_PATTERN = re.compile('')
BREAK_LOC_PATTERN = re.compile('')
class BreakpointLocation(namedtuple('BaseBreakpointLocation', ['address', 'enabled', 'thread_groups'])):
pass
class BreakpointLocationInfoReader(object):
def get_locations(self, breakpoint):
return breakpoint.locations
def _choose_breakpoint_location_info_reader():
return BreakpointLocationInfoReader()
BREAKPOINT_LOCATION_INFO_READER = _choose_breakpoint_location_info_reader()
def get_debugger():
return lldb.SBDebugger.FindDebuggerWithID(1)
def get_target():
return get_debugger().GetTargetAtIndex(0)
def get_process():
return get_target().GetProcess()
def selected_thread():
return get_process().GetSelectedThread()
def selected_frame():
return selected_thread().GetSelectedFrame()
def parse_and_eval(expr, signed=False):
if signed is True:
return get_target().EvaluateExpression(expr).GetValueAsSigned()
return get_target().EvaluateExpression(expr).GetValueAsUnsigned()
def get_eval(expr):
return get_target().EvaluateExpression(expr)
def get_description(object, level=None):
stream = lldb.SBStream()
if level is None:
object.GetDescription(stream)
else:
object.GetDescription(stream, level)
return escape_ansi(stream.GetData())
conv_map = {}
def get_convenience_variable(id):
#val = get_target().GetEnvironment().Get(id)
if id not in conv_map:
return "auto"
val = conv_map[id]
if val is None:
return "auto"
return val
def set_convenience_variable(id, value):
#env = get_target().GetEnvironment()
#return env.Set(id, value, True)
conv_map[id] = value
def escape_ansi(line):
ansi_escape =re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
return ansi_escape.sub('', line)
def debracket(init):
val = init
val = val.replace("[","(")
val = val.replace("]",")")
return val
@@ -0,0 +1,25 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "ghidralldb"
version = "10.4"
authors = [
{ name="Ghidra Development Team" },
]
description = "Ghidra's Plugin for lldb"
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
]
dependencies = [
"ghidratrace==10.4",
]
[project.urls]
"Homepage" = "https://github.com/NationalSecurityAgency/ghidra"
"Bug Tracker" = "https://github.com/NationalSecurityAgency/ghidra/issues"
@@ -31,54 +31,54 @@ import ghidra.dbg.testutil.DummyProc;
public enum MacOSSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUtils {
SPIN {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expSpin");
}
},
FORK_EXIT {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expFork");
}
},
CLONE_EXIT {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expCloneExit");
}
},
PRINT {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expPrint");
}
},
REGISTERS {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expRegisters");
}
},
STACK {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expStack");
}
},
CREATE_PROCESS {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expCreateProcess");
}
},
CREATE_THREAD_EXIT {
@Override
String getCommandLine() {
public String getCommandLine() {
return DummyProc.which("expCreateThreadExit");
}
};
abstract String getCommandLine();
public abstract String getCommandLine();
@Override
public DummyProc runDummy() throws Throwable {
@@ -117,24 +117,19 @@ public enum MacOSSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUtil
}
@Override
public boolean isRunningIn(TargetProcess process, AbstractDebuggerModelTest test)
throws Throwable {
public boolean isRunningIn(TargetProcess process, AbstractDebuggerModelTest test) throws Throwable {
// NB. ShellUtils.parseArgs removes the \s. Not good.
String expected = getBinModuleName();
TargetObject session = process.getParent().getParent();
Collection<TargetModule> modules =
test.m.findAll(TargetModule.class, session.getPath(), true).values();
return modules.stream()
.anyMatch(m -> expected.equalsIgnoreCase(getShortName(m.getModuleName())));
Collection<TargetModule> modules = test.m.findAll(TargetModule.class, session.getPath(), true).values();
return modules.stream().anyMatch(m -> expected.equalsIgnoreCase(getShortName(m.getModuleName())));
}
@Override
public boolean isAttachable(DummyProc dummy, TargetAttachable attachable,
AbstractDebuggerModelTest test) throws Throwable {
public boolean isAttachable(DummyProc dummy, TargetAttachable attachable, AbstractDebuggerModelTest test)
throws Throwable {
waitOn(attachable.fetchAttributes());
long pid =
attachable.getTypedAttributeNowByName(LldbModelTargetAvailable.PID_ATTRIBUTE_NAME,
Long.class, -1L);
long pid = attachable.getTypedAttributeNowByName(LldbModelTargetAvailable.PID_ATTRIBUTE_NAME, Long.class, -1L);
return pid == dummy.pid;
}
}
+2 -78
View File
@@ -13,97 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*plugins {
id 'com.google.protobuf' version '0.8.10'
}*/
apply from: "${rootProject.projectDir}/gradle/javaProject.gradle"
apply from: "${rootProject.projectDir}/gradle/jacocoProject.gradle"
apply from: "${rootProject.projectDir}/gradle/javaTestProject.gradle"
apply from: "${rootProject.projectDir}/gradle/distributableGhidraModule.gradle"
apply from: "${rootProject.projectDir}/gradle/debugger/hasProtobuf.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Debug Debugger-gadp'
configurations {
allProtocArtifacts
protocArtifact
}
def platform = getCurrentPlatformName()
dependencies {
allProtocArtifacts 'com.google.protobuf:protoc:3.21.8:windows-x86_64@exe'
allProtocArtifacts 'com.google.protobuf:protoc:3.21.8:linux-x86_64@exe'
allProtocArtifacts 'com.google.protobuf:protoc:3.21.8:linux-aarch_64@exe'
allProtocArtifacts 'com.google.protobuf:protoc:3.21.8:osx-x86_64@exe'
allProtocArtifacts 'com.google.protobuf:protoc:3.21.8:osx-aarch_64@exe'
if (isCurrentWindows()) {
protocArtifact 'com.google.protobuf:protoc:3.21.8:windows-x86_64@exe'
}
if (isCurrentLinux()) {
if (platform.endsWith("x86_64")) {
protocArtifact 'com.google.protobuf:protoc:3.21.8:linux-x86_64@exe'
}
else {
protocArtifact 'com.google.protobuf:protoc:3.21.8:linux-aarch_64@exe'
}
}
if (isCurrentMac()) {
if (platform.endsWith("x86_64")) {
protocArtifact 'com.google.protobuf:protoc:3.21.8:osx-x86_64@exe'
}
else {
protocArtifact 'com.google.protobuf:protoc:3.21.8:osx-aarch_64@exe'
}
}
api project(':Framework-AsyncComm')
api project(':Framework-Debugging')
api project(':ProposedUtils')
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
}
/*protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.21.8'
}
}*/
task generateProto {
ext.srcdir = file("src/main/proto")
ext.src = fileTree(srcdir) {
include "**/*.proto"
}
ext.outdir = file("build/generated/source/proto/main/java")
outputs.dir(outdir)
inputs.files(src)
dependsOn(configurations.protocArtifact)
doLast {
def exe = configurations.protocArtifact.first()
if (!isCurrentWindows()) {
exe.setExecutable(true)
}
exec {
commandLine exe, "--java_out=$outdir", "-I$srcdir"
args src
}
}
}
tasks.compileJava.dependsOn(tasks.generateProto)
tasks.eclipse.dependsOn(tasks.generateProto)
rootProject.tasks.prepDev.dependsOn(tasks.generateProto)
sourceSets {
main {
java {
srcDir tasks.generateProto.outdir
}
}
}
zipSourceSubproject.dependsOn generateProto
+4 -71
View File
@@ -18,91 +18,24 @@ apply from: "${rootProject.projectDir}/gradle/javaProject.gradle"
apply from: "${rootProject.projectDir}/gradle/jacocoProject.gradle"
apply from: "${rootProject.projectDir}/gradle/javaTestProject.gradle"
apply from: "${rootProject.projectDir}/gradle/distributableGhidraModule.gradle"
apply from: "${rootProject.projectDir}/gradle/debugger/hasProtobuf.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Debug Debugger-isf'
configurations {
allProtocArtifacts
protocArtifact
}
def platform = getCurrentPlatformName()
dependencies {
allProtocArtifacts 'com.google.protobuf:protoc:3.21.8:windows-x86_64@exe'
allProtocArtifacts 'com.google.protobuf:protoc:3.21.8:linux-x86_64@exe'
allProtocArtifacts 'com.google.protobuf:protoc:3.21.8:linux-aarch_64@exe'
allProtocArtifacts 'com.google.protobuf:protoc:3.21.8:osx-x86_64@exe'
allProtocArtifacts 'com.google.protobuf:protoc:3.21.8:osx-aarch_64@exe'
if (isCurrentWindows()) {
protocArtifact 'com.google.protobuf:protoc:3.21.8:windows-x86_64@exe'
}
if (isCurrentLinux()) {
if (platform.endsWith("x86_64")) {
protocArtifact 'com.google.protobuf:protoc:3.21.8:linux-x86_64@exe'
}
else {
protocArtifact 'com.google.protobuf:protoc:3.21.8:linux-aarch_64@exe'
}
}
if (isCurrentMac()) {
if (platform.endsWith("x86_64")) {
protocArtifact 'com.google.protobuf:protoc:3.21.8:osx-x86_64@exe'
}
else {
protocArtifact 'com.google.protobuf:protoc:3.21.8:osx-aarch_64@exe'
}
}
api project(':Framework-AsyncComm')
api project(':Framework-Debugging')
api project(':ProposedUtils')
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
}
task generateProto {
ext.srcdir = file("src/main/proto")
ext.src = fileTree(srcdir) {
include "**/*.proto"
}
ext.outdir = file("build/generated/source/proto/main/java")
outputs.dir(outdir)
inputs.files(src)
dependsOn(configurations.protocArtifact)
doLast {
def exe = configurations.protocArtifact.first()
if (!isCurrentWindows()) {
exe.setExecutable(true)
}
exec {
commandLine exe, "--java_out=$outdir", "-I$srcdir"
args src
}
}
}
tasks.compileJava.dependsOn(tasks.generateProto)
tasks.eclipse.dependsOn(tasks.generateProto)
rootProject.tasks.prepDev.dependsOn(tasks.generateProto)
sourceSets {
main {
java {
srcDir tasks.generateProto.outdir
}
}
}
zipSourceSubproject.dependsOn generateProto
// Include buildable native source in distribution
rootProject.assembleDistribution {
from (this.project.projectDir.toString()) {
from (this.project.projectDir.toString()) {
include "runISFServer"
into { getZipPath(this.project) }
}
}
}
@@ -0,0 +1,280 @@
This is just a scratchpad of notes for development.
After developer documentation is authored, this file should be deleted.
Terminology can be a bit weird regarding client vs server.
Instead, I prefer to use "front end" and "back end".
Ghidra is always the front end, as it provides the UI.
The actual debugger is always the "back end" is it provides the actual instrumentation and access to the target.
wrt/ TCP, the connection can go either way, but once established, Ghidra still plays the front end role.
Client/Server otherwise depends on context.
For the trace-recording channel, the back-end is the client, and the front-end (Ghidra) is the server.
The back-end invokes remote methods on the DBTrace, and those cause DomainObjectChange events, updating the UI.
The front-end replies with minimal information.
(More on this and sync/async/batching later)
For the command channel, the front-end (Ghidra) is the client, and the back-end is the server.
The user presses a button, which invokes a remote method on the back-end.
Often, that method and/or its effects on the target and back-end result in it updating the trace, and the loop is complete.
Again, the back-end replies with minimal information.
One notable exception is the `execute` method, which can optionally return captured console output.
In general, methods should only respond with actual information that doesn't belong in the trace.
While I've not yet needed this, I suppose another exception could be for methods that want to return the path to an object, to clarify association of cause and effect.
Regarding sync/async and batching:
One of the goals of TraceRmi was to simplify the trace-recording process.
It does this in three ways:
1. Providing direct control to write the Trace database.
The ObjectModel approach was more descriptive.
It would announce the existence of things, and a recorder at the front end would decide (applying some arcane rules) what to record and display.
Almost every new model required some adjustment to the recorder.
2. Changing to a synchronous RMI scheme.
The decision to use an asynchronous scheme was to avoid accidental lock-ups of the Swing thread.
In practice, it just poisoned every API that depended on it, and we still got Swing lock-ups.
And worse, they were harder to diagnose, because the stack traces were obscured.
And still worse, execution order and threading was difficult to predict.
We've only been somewhat successful in changing to a fully synchronous scheme, but even then, we've (attempted to) mitigate each of the above complaints.
On the front-end, the internals still use CompletableFuture, but we're more apt to use .get(), which keeps the stack together on the thread waiting for the result.
In essence, there's little difference in blocking on .get() vs blocking on .recv().
The reason we need a dedicated background thread to receive is to sort out the two channels.
The recommended public API method is RemoteMethod.invoke(), which uses .get() internally, so this is mostly transparent, except when debugging the front end.
There is still an .invokeAsync(), if desired, giving better control of timeouts, which is actually a feature we would not have using a purely synchronous .recv() (at least not without implementing non-blocking IO)
To mitigate Swing lock-ups the .get() methods are overridden to explicitly check for the Swing thread.
On the back end, the internals work similarly to the front end.
We use a Future to handle waiting for the result, and the implementation of each trace modification method will immediately invoke .result().
Unfortunately, this does slow things down far too much, since every miniscule operation requires a round trip.
We mitigate this by implementing a `batch` context manager.
Inside this context, most of the trace modification methods will now return the Future.
However, a reference to each such future is stored off in the context.
When the context is exited, all the Futures' results are waited on.
This maintains a mostly synchronous behavior, while alleviating the repeated round-trip costs.
3. Simplifying the back end implementation, and providing it in Python.
It turns out no debugger we've encountered up to this point provides Java language bindings out of the box.
The closest we've seen is LLDB, which has specified their interfaces using SWIG, which lent itself to exporting Java bindings.
And that was lucky, too, because accessing C++ virtual functions from JNA is fraught with peril.
For gdb, we've been using a pseudo-terminal or ssh connection to its Machine Interface, which aside from the piping delays, has been pretty nice.
It's not been great on Windows, though -- their ConPTY stuff has some ANSI oddities, the handling of which has slowed our performance.
For dbgeng/dbgmodel, we've been fortunate that they follow COM+, which is fairly well understood by JNA.
Nevertheless, all of these have required us to hack some kind of native bindings in Java.
This introduces risks of crashing the JVM, and in some cases can cause interesting conflicts, e.g., the JVM and dbgeng may try to handle the same signals differently.
dbgeng also only allows a single session.
If the user connects twice to it using IN-VM (this is easy to do by accident), then the two connections are aliases of the same dbgeng session.
Both gdb and lldb offer Python bindings, so it is an obvious choice for back end implementations.
We are already using protobuf, so we keep it, but developed a new protocol specification.
The trace modification methods are prescribed by Ghidra, so each is implemented specifically in the trace client.
The back end remote methods are described entirely by the back end.
They are enumerated during connection negotiation; otherwise, there is only one generic "Invoke" message.
Because we're more tightly integrated with the debugger, there may be some interesting caveats.
Pay careful attention to synchronization and session tear down.
At one point, I was using gdb's post_event as a Python Executor.
A separate thread handled the method invocation requests, scheduled it on the executor, waited for the result, and then responded.
This worked until the front end invoked `execute("quit")`.
I was expecting gdb to just quit, and the front end would expect the connection to die.
However, this never happened.
Instead, during execution of the `quit`, gdb wanted to clean up the Python interpreter.
Part of that was gracefully cleaning up all the Python threads, one of which was blocking indefinitely on execution of the `quit`.
Thus, the two threads were waiting on each other, and gdb locked up.
Depending on the debugger, the Python API may be more or less mature, and there could be much variation among versions we'd like to support.
For retrieving information, we at least have console capture as a fallback; however, there's not always a reliable way to detect certain events without a direct callback.
At worst, we can always hook something like `prompt`, but if we do, we must be quick in our checks.
Dealing with multiple versions, there's at least two ways:
1. Probe for the feature.
This is one case where Python's dynamic nature helps out.
Use `hasattr` to check for the existence of various features and choose accordingly.
2. Check the version string.
Assuming version information can be consistently and reliably retrieved across all the supported versions, parse it first thing.
If the implementation of a feature various across versions, the appropriate one can be selected.
This may not work well for users of development branches, or are otherwise off the standard releases of their debuggers.
This is probably well understood by the Python community, but I'll overstate it here:
If you've written something, but you haven't unit tested it yet, then you haven't really written it.
This may be mitigated by some static analysis tools and type annotations, but I didn't use them.
In fact, you might even say I abused type annotations for remote method specifications.
For gdb, I did all of my unit testing using JUnit as the front end in Java.
This is perhaps not ideal, since this is inherently an integration test; nevertheless, it does allow me to test each intended feature of the back end separately.
# Package installation
I don't know what the community preference will be here, but now that we're playing in the Python ecosystem, we have to figure out how to play nicely.
Granted, some of this depends on how nicely the debugger plays in the Python ecosystem.
My current thought is distribute our stuff as Python packages, and let the user figure it out.
We'll still want to figure out the best way, if possible, to make things work out of the box.
Nevertheless, a `pip install` command may not be *that* offensive for a set-up step.
That said, for unit testing, I've had to incorporate package installation as a @BeforeClass method.
There's probably a better way, and that way may also help with out-of-the-box support.
Something like setting PYTHON_PATH before invoking the debugger?
There's still the issue of installing protobuf, though.
And the version we use is not the latest, which may put users who already have protobuf in dependency hell.
We use version 3.20, while the latest is 4.something.
According to protobuf docs, major versions are not guaranteed backward compatible.
To upgrade, we'd also have to upgrade the Java side.
# Protobuf headaches
Protobufs in Java have these nifty `writeDelimitedTo` and `parseDelimitedFrom` methods.
There's no equivalent for Python :(
That said, according to a stackoverflow post (which I've lost track of, but it's easily confirmed by examining protobufs Java source), you can hand-spin this by prepending a varint giving each message's length.
If only the varint codec were part of protobuf's public Python API....
They're pretty easily accessed in Python by importing the `internal` package, but that's probably not a good idea.
Also, (as I had been doing that), it's easy to goof up receiving just variable-length int and keeping the encoded message in tact for parsing.
I instead just use a fixed 32-bit int now.
# How-To?
For now, I'd say just the the gdb implementation as a template / guide.
Just beware, the whole thing is a bit unstable, so the code may change, but still, I don't expect it to change so drastically that integration work would be scrapped.
If you're writing Python, create a Python package following the template for gdb's.
I'd like the version numbers to match Ghidra's, though this may need discussion.
Currently, only Python 3 is supported.
I expect older versions of gdb may not support Py3, so we may need some backporting.
That said, if your distro's package for whatever debugger is compiled for Py2, you may need to build from source, assuming it supports Py3 at all.
I recommend mirroring the file layout:
__init__.py:
Python package marker, but also initialization.
For gdb, this file gets executed when the user types `python import ghidragdb`.
Thus, that's how they load the extension.
arch.py:
Utilities for mapping architecture-specific things between back and front ends.
Technically, you should just be able to use the "DATA" processor for your trace, things will generally work better if you can map.
commands.py:
These are commands we add to the debugger's CLI.
For gdb, we use classes that extend `gdb.Command`, which allows the user to access them whether or not connected to Ghidra.
For now, this is the recommendation, as I expect it'll allow users to "hack" on it more easily, either to customize or to retrieve diagnostics, etc.
Notice that I use gdb's expression evaluator wherever that can enhance the command's usability, e.g., `ghidra trace putval`
hooks.py:
These are event callbacks from the debugger as well as whatever plumbing in necessary to actually install them.
That "plumbing" may vary, since the debugger may not directly support the callback you're hoping for.
In gdb, there are at least 3 flavors:
1. A directly-supported callback, i.e., in `gdb.events`
2. A breakpoint callback, which also breaks down into two sub-flavors:
* Internal breakpoint called back via `gdb.Breakpoint.stop`
* Normal breakpoint whose commands invoke a CLI command
3. A hooked command to invoke a CLI command, e.g., `define hook-inferior`
method.py:
These are remote methods available to the front end.
See the `MethodRegistry` object in the Python implementation, or the `RemoteMethod` interface in the Java implementation.
parameters.py:
These are for gdb parameters, which may not map to anything in your debugger, so adjust as necessary.
They're preferred to custom commands whose only purpose is to access a variable.
schema.xml:
This is exactly what you think it is.
It is recommended you copy this directly from the ObjectModel-based implementation and make adjustments as needed.
See `commands.start_trace` to see how to load this file from your Python package.
util.py:
Just utilities and such.
For the gdb connector, this is where I put my version-specific implementations, e.g., to retrieve the memory map and module list.
For testing, similarly copy the JUnit tests (they're in the IntegrationTests project) into a separate properly named package.
I don't intend to factor out test cases, except for a few utilities.
The only real service that did in the past was to remind you what cases you ought to test.
Prescribing exactly *how* to test those and the scenarios, I think, was a mistake.
If I provide a base test class, it might just be to name some methods that all fail by default.
Then, as a tester, the failures would remind you to override each method with the actual test code.
For manual testing, I've used two methods
1. See `GdbCommandsTest#testManual`.
Uncomment it to have JUnit start a trace-rmi front-end listener.
You can then manually connect from inside your debugger and send/diagnose commands one at a time.
Typically, I'd use the script from another test that was giving me trouble.
2. Start the full Ghidra Debugger and use a script to connect.
At the moment, there's little UI integration beyond what is already offered by viewing a populated trace.
Use either ConnectTraceRmiScript or ListenTraceRmiScript and follow the prompts / console.
The handler will activate the trace when commanded, and it will follow the latest snapshot.
# User installation instructions:
The intent is to provide .whl or whatever Python packages as part of the Ghidra distribution.
A user should be able to install them using `pip3 install ...`, however:
We've recently encountered issues where the version of Python that gdb is linked to may not be the same version of Python the user gets when the type `python`, `python3` or `pip3`.
To manually check for this version, a user must type, starting in their shell:
```bash
gdb
python-interactive
import sys
print(sys.version)
```
Suppose they get `3.8.10`.
They'd then take the major and minor numbers to invoke `python3.8` directly:
```bash
python3.8 -m pip install ...
```
A fancy way to just have gdb print the python command for you is:
```bash
gdb --batch -ex 'python import sys' -ex 'python print(f"python{sys.version_info.major}.{sys.version_info.minor}")'
```
Regarding method registry, the executor has to be truly asynchronous.
You cannot just invoke the method synchronously and return a completed future.
If you do, you'll hang the message receiver thread, which may need to be free if the invoked method interacts with the trace.
We've currently adopted a method-naming convention that aims for a somewhat consistent API across back-end plugins.
In general, the method name should match the action name exactly, e.g., the method corresponding the Ghidra's `resume` action should be defined as:
@REGISTRY.method
def resume(...):
...
Not:
@REGISTRY.method(name='continue', action='resume')
def _continue(...):
...
Even though the back-end's command set and/or API may call it "continue."
If you would like to provide a hint to the user regarding the actual back-end command, do so in the method's docstring:
@REGISTRY.method
def resume(...):
"""Continue execution of the current target (continue)."""
...
There are exceptions:
1. When there is not a one-to-one mapping from the method to an action.
This is usually the case for delete, toggle, refresh, etc.
For these, use the action as the prefix, and then some suffix, usually describing the type of object affected, e.g., delete_breakpoint.
2. When using an "_ext" class of action, e.g., step_ext or break_ext.
There is almost certainly not a one-to-one method for such an action.
The naming convention is the same as 1, but omitting the "_ext", e.g., step_advance or break_event
Even if you only have one method that maps to step_ext, the method should *never* be called step_ext.
3. There is no corresponding action at all.
In this case, call it what you want, but strive for consistency among related methods in this category for your back-end.
Act as though there could one day be a Ghidra action that you'd like to map them to.
There may be some naming you find annoying, e.g., "resume" (not "continue") or "launch" (not "start")
We also do not use the term "watchpoint." We instead say "write breakpoint."
Thus, the method for placing one is named `break_write_whatever`, not `watch_whatever`.
# Regarding transactions:
At the moment, I've defined two modes for transaction management on the client side.
The server side couldn't care less. A transactions is a transaction.
For hooks, i.e., things driven by events on the back end, use the client's transaction manager directly.
For commands, i.e., things driven by the user via the CLI, things are a little dicey.
I wouldn't expect the user to manage multiple transaction objects.
The recommendation is that the CLI can have at most one active transaction.
For the user to open a second transaction may be considered an error.
Take care as you're coding (and likely re-using command logic) that you don't accidentally take or otherwise conflict with the CLI's transaction manager when processing an event.
@@ -0,0 +1,56 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply from: "${rootProject.projectDir}/gradle/javaProject.gradle"
apply from: "${rootProject.projectDir}/gradle/jacocoProject.gradle"
apply from: "${rootProject.projectDir}/gradle/javaTestProject.gradle"
apply from: "${rootProject.projectDir}/gradle/distributableGhidraModule.gradle"
apply from: "${rootProject.projectDir}/gradle/debugger/hasProtobuf.gradle"
apply from: "${rootProject.projectDir}/gradle/debugger/hasPythonPackage.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Debug Debugger-rmi-trace'
dependencies {
api project(':Debugger')
}
task generateProtoPy {
ext.srcdir = file("src/main/proto")
ext.src = fileTree(srcdir) {
include "**/*.proto"
}
ext.outdir = file("build/generated/source/proto/main/py")
outputs.dir(outdir)
inputs.files(src)
dependsOn(configurations.protocArtifact)
doLast {
def exe = configurations.protocArtifact.first()
if (!isCurrentWindows()) {
exe.setExecutable(true)
}
exec {
commandLine exe, "--python_out=$outdir", "-I$srcdir"
args src
}
}
}
tasks.assemblePyPackage {
from(generateProtoPy) {
into "src/ghidratrace"
}
}
@@ -0,0 +1,7 @@
##VERSION: 2.0
DEVNOTES.txt||GHIDRA||||END|
Module.manifest||GHIDRA||||END|
src/main/py/LICENSE||GHIDRA||||END|
src/main/py/README.md||GHIDRA||||END|
src/main/py/pyproject.toml||GHIDRA||||END|
src/main/py/tests/EMPTY||GHIDRA||||END|
@@ -0,0 +1,48 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Objects;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.TraceRmiService;
public class ConnectTraceRmiScript extends GhidraScript {
TraceRmiService getService() throws Exception {
TraceRmiService service = state.getTool().getService(TraceRmiService.class);
if (service != null) {
return service;
}
state.getTool().addPlugin(TraceRmiPlugin.class.getName());
return Objects.requireNonNull(state.getTool().getService(TraceRmiService.class));
}
@Override
protected void run() throws Exception {
TraceRmiService service = getService();
TraceRmiHandler handler = service.connect(
new InetSocketAddress(askString("Trace RMI", "hostname", "localhost"), askInt("Trace RMI", "port")));
println("Connected");
handler.start();
// if (askYesNo("Execute?", "Execute 'echo test'?")) {
// handler.getMethods().get("execute").invoke(Map.of("cmd", "script print('test')"));
// }
}
}
@@ -0,0 +1,48 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.Map;
import java.util.Objects;
import ghidra.app.plugin.core.debug.service.rmi.trace.*;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.TraceRmiService;
public class ListenTraceRmiScript extends GhidraScript {
TraceRmiService getService() throws Exception {
TraceRmiService service = state.getTool().getService(TraceRmiService.class);
if (service != null) {
return service;
}
state.getTool().addPlugin(TraceRmiPlugin.class.getName());
return Objects.requireNonNull(state.getTool().getService(TraceRmiService.class));
}
@Override
protected void run() throws Exception {
TraceRmiService service = getService();
TraceRmiAcceptor acceptor = service.acceptOne(null);
println("Listening at " + acceptor.getAddress());
TraceRmiHandler handler = acceptor.accept();
println("Connection from " + handler.getRemoteAddress());
handler.start();
while (askYesNo("Execute?", "Execute 'echo test'?")) {
handler.getMethods().get("execute").invoke(Map.of("cmd", "echo test"));
}
}
}
@@ -0,0 +1,107 @@
/* ###
* 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.service.rmi.trace;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler.*;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.rmi.trace.TraceRmi.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.time.TraceSnapshot;
class OpenTrace implements ValueDecoder {
final DoId doId;
final Trace trace;
TraceSnapshot lastSnapshot;
OpenTrace(DoId doId, Trace trace) {
this.doId = doId;
this.trace = trace;
}
public TraceSnapshot createSnapshot(Snap snap, String description) {
TraceSnapshot snapshot = trace.getTimeManager().getSnapshot(snap.getSnap(), true);
snapshot.setDescription(description);
return this.lastSnapshot = snapshot;
}
public TraceObject getObject(long id, boolean required) {
TraceObject object = trace.getObjectManager().getObjectById(id);
if (object == null) {
throw new InvalidObjIdError();
}
return object;
}
public TraceObject getObject(ObjPath path, boolean required) {
TraceObject object =
trace.getObjectManager().getObjectByCanonicalPath(TraceRmiHandler.toKeyPath(path));
if (required && object == null) {
throw new InvalidObjPathError();
}
return object;
}
@Override
public TraceObject getObject(ObjDesc desc, boolean required) {
return getObject(desc.getId(), required);
}
@Override
public TraceObject getObject(ObjSpec object, boolean required) {
return switch (object.getKeyCase()) {
case KEY_NOT_SET -> throw new TraceRmiError("Must set id or path");
case ID -> getObject(object.getId(), required);
case PATH -> getObject(object.getPath(), required);
default -> throw new AssertionError();
};
}
public AddressSpace getSpace(String name, boolean required) {
AddressSpace space = trace.getBaseAddressFactory().getAddressSpace(name);
if (required && space == null) {
throw new NoSuchAddressSpaceError();
}
return space;
}
@Override
public Address toAddress(Addr addr, boolean required) {
AddressSpace space = getSpace(addr.getSpace(), required);
return space.getAddress(addr.getOffset());
}
@Override
public AddressRange toRange(AddrRange range, boolean required)
throws AddressOverflowException {
AddressSpace space = getSpace(range.getSpace(), required);
if (space == null) {
return null;
}
Address min = space.getAddress(range.getOffset());
Address max = space.getAddress(range.getOffset() + range.getExtend());
return new AddressRangeImpl(min, max);
}
public Register getRegister(String name, boolean required) {
Register register = trace.getBaseLanguage().getRegister(name);
if (required && register == null) {
throw new InvalidRegisterError(name);
}
return register;
}
}
@@ -0,0 +1,70 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.rmi.trace;
import java.util.concurrent.*;
import ghidra.trace.model.target.TraceObject;
import ghidra.util.Swing;
/**
* The future result of invoking a {@link RemoteMethod}.
*
* <p>
* While this can technically result in an object, returning values from remote methods is highly
* discouraged. This has led to several issues in the past, including duplication of information
* (and a lot of it) over the connection. Instead, most methods should just update the trace
* database, and the client can retrieve the relevant information from it. One exception might be
* the {@code execute} method. This is typically for executing a CLI command with captured output.
* There is generally no place for such output to go into the trace, and the use cases for such a
* method to return the output are compelling. For other cases, perhaps the most you can do is
* return a {@link TraceObject}, so that a client can quickly associate the trace changes with the
* method. Otherwise, please return null/void/None for all methods.
*
* <b>NOTE:</b> To avoid the mistake of blocking the Swing thread on an asynchronous result, the
* {@link #get()} methods have been overridden to check for the Swing thread. If invoked on the
* Swing thread with a timeout greater than 1 second, an assertion error will be thrown. Please use
* a non-swing thread, e.g., a task thread or script thread, to wait for results, or chain
* callbacks.
*/
public class RemoteAsyncResult extends CompletableFuture<Object> {
final ValueDecoder decoder;
public RemoteAsyncResult() {
this.decoder = ValueDecoder.DEFAULT;
}
public RemoteAsyncResult(OpenTrace open) {
this.decoder = open;
}
@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);
}
}
@@ -0,0 +1,330 @@
/* ###
* 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.service.rmi.trace;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.*;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
/**
* A remote method registered by the back-end debugger.
*
* <p>
* Remote methods must describe the parameters names and types at a minimum. They should also
* provide a display name and description for the method itself and each of its parameters. These
* methods should not return a result. Instead, any "result" should be recorded into a trace. The
* invocation can result in an error, which is communicated by an exception that can carry only a
* message string. Choice few methods should return a result, for example, the {@code execute}
* method with output capture. That output generally does not belong in a trace, so the only way to
* communicate it back to the front end is to return it.
*/
public interface RemoteMethod {
/**
* A "hint" for how to map the method to a common action.
*
* <p>
* Many common commands/actions have varying names across different back-end debuggers. We'd
* like to present common idioms for these common actions, but allow them to keep the names used
* by the back-end, because those names are probably better known to users of that back-end than
* Ghidra's action names are known. The action hints will affect the icon and placement of the
* action in the UI, but the display name will still reflect the name given by the back-end.
* Note that the "stock" action names are not a fixed enumeration. These are just the ones that
* might get special treatment from Ghidra. All methods should appear somewhere (at least, e.g.,
* in context menus for applicable objects), even if the action name is unspecified or does not
* match a stock name. This list may change over time, but that shouldn't matter much. Each
* back-end should make its best effort to match its methods to these stock actions where
* applicable, but ultimately, it is up to the UI to decide what is presented where.
*/
public record Action(String name) {
public static final Action REFRESH = new Action("refresh");
public static final Action ACTIVATE = new Action("activate");
/**
* A weaker form of activate.
*
* <p>
* The user has expressed interest in an object, but has not activated it yet. This is often
* used to communicate selection (i.e., highlight) of the object. Whereas, double-clicking
* or pressing enter would more likely invoke 'activate.'
*/
public static final Action FOCUS = new Action("focus");
public static final Action TOGGLE = new Action("toggle");
public static final Action DELETE = new Action("delete");
/**
* Forms: (cmd:STRING):STRING
*
* Optional arguments: capture:BOOL
*/
public static final Action EXECUTE = new Action("execute");
/**
* Forms: (spec:STRING)
*/
public static final Action CONNECT = new Action("connect");
/**
* Forms: (target:Attachable), (pid:INT), (spec:STRING)
*/
public static final Action ATTACH = new Action("attach");
public static final Action DETACH = new Action("detach");
/**
* Forms: (command_line:STRING), (file:STRING,args:STRING), (file:STRING,args:STRING_ARRAY),
* (ANY*)
*/
public static final Action LAUNCH = new Action("launch");
public static final Action KILL = new Action("kill");
public static final Action RESUME = new Action("resume");
public static final Action INTERRUPT = new Action("interrupt");
/**
* All of these will show in the "step" portion of the control toolbar, if present. The
* difference in each "step_x" is minor. The icon will indicate which form, and the
* positions will be shifted so they appear in a consistent order. The display name is
* determined by the method name, not the action name. For stepping actions that don't fit
* the standards, use {@link #STEP_EXT}. There should be at most one of each standard
* applicable for any given context. (Multiple will appear, but may confuse the user.) You
* can have as many extended step actions as you like. They will be ordered
* lexicographically by name.
*/
public static final Action STEP_INTO = new Action("step_into");
public static final Action STEP_OVER = new Action("step_over");
public static final Action STEP_OUT = new Action("step_out");
/**
* Skip is not typically available, except in emulators. If the back-end debugger does not
* have a command for this action out-of-the-box, we do not recommend trying to implement it
* yourself. The purpose of these actions just to expose/map each command to the UI, not to
* invent new features for the back-end debugger.
*/
public static final Action STEP_SKIP = new Action("step_skip");
/**
* Step back is not typically available, except in emulators and timeless (or time-travel)
* debuggers.
*/
public static final Action STEP_BACK = new Action("step_back");
/**
* The action for steps that don't fit one of the common stepping actions.
*/
public static final Action STEP_EXT = new Action("step_ext");
/**
* Forms: (addr:ADDRESS), R/W(rng:RANGE), set(expr:STRING)
*
* Optional arguments: condition:STRING, commands:STRING
*/
public static final Action BREAK_SW_EXECUTE = new Action("break_sw_execute");
public static final Action BREAK_HW_EXECUTE = new Action("break_hw_execute");
public static final Action BREAK_READ = new Action("break_read");
public static final Action BREAK_WRITE = new Action("break_write");
public static final Action BREAK_ACCESS = new Action("break_access");
public static final Action BREAK_EXT = new Action("break_ext");
/**
* Forms: (rng:RANGE)
*/
public static final Action READ_MEM = new Action("read_mem");
/**
* Forms: (addr:ADDRESS,data:BYTES)
*/
public static final Action WRITE_MEM = new Action("write_mem");
// NOTE: no read_reg. Use refresh(RegContainer), refresh(RegGroup), refresh(Register)
/**
* Forms: (frame:Frame,name:STRING,value:BYTES), (register:Register,value:BYTES)
*/
public static final Action WRITE_REG = new Action("write_reg");
}
/**
* The name of the method.
*
* @return the name
*/
String name();
/**
* A string that hints at the UI action this method achieves.
*
* @return the action
*/
Action action();
/**
* A description of the method.
*
* <p>
* This is the text for tooltips or other information presented by actions whose purpose is to
* invoke this method. If the back-end command name is well known to its users, this text should
* include that name.
*
* @return the description
*/
String description();
/**
* The methods parameters.
*
* <p>
* Parameters are all keyword-style parameters. This returns a map of names to parameter
* descriptions.
*
* @return the parameter map
*/
Map<String, RemoteParameter> parameters();
/**
* Get the schema for the return type.
*
* <b>NOTE:</b> Most methods should return void, i.e., either they succeed, or they throw/raise
* an error message. One notable exception is "execute," which may return the console output
* from executing a command. In most cases, the method should only cause an update to the trace
* database. That effect is its result.
*
* @return the schema name for the method's return type.
*/
SchemaName retType();
/**
* Check the type of an argument.
*
* <p>
* This is a hack, because {@link TargetObjectSchema} expects {@link TargetObject}, or a
* primitive. We instead need {@link TraceObject}. I'd add the method to the schema, except that
* trace stuff is not in its dependencies.
*
* @param name the name of the parameter
* @param sch the type of the parameter
* @param arg the argument
*/
static void checkType(String name, TargetObjectSchema sch, Object arg) {
if (sch.getType() != TargetObject.class) {
if (sch.getType().isInstance(arg)) {
return;
}
}
else if (arg instanceof TraceObject obj) {
if (sch.equals(obj.getTargetSchema())) {
return;
}
}
throw new IllegalArgumentException(
"For parameter %s: argument %s is not a %s".formatted(name, arg, sch));
}
/**
* Validate the given argument.
*
* <p>
* This method is for checking parameter sanity before they are marshalled to the back-end. This
* is called automatically during invocation. Clients can use this method to pre-test or
* validate in the UI, when invocation is not yet desired.
*
* @param arguments the arguments
* @return the trace if any object arguments were given, or null
* @throws IllegalArgumentException if the arguments are not valid
*/
default Trace validate(Map<String, Object> arguments) {
Trace trace = null;
SchemaContext ctx = EnumerableTargetObjectSchema.MinimalSchemaContext.INSTANCE;
for (Map.Entry<String, RemoteParameter> ent : parameters().entrySet()) {
if (!arguments.containsKey(ent.getKey())) {
if (ent.getValue().required()) {
throw new IllegalArgumentException(
"Missing required parameter '" + ent.getKey() + "'");
}
continue; // Should not need to check the default value
}
Object arg = arguments.get(ent.getKey());
if (arg instanceof TraceObject obj) {
if (trace == null) {
trace = obj.getTrace();
ctx = trace.getObjectManager().getRootSchema().getContext();
}
else if (trace != obj.getTrace()) {
throw new IllegalArgumentException(
"All TraceObject parameters must come from the same trace");
}
}
TargetObjectSchema sch = ctx.getSchema(ent.getValue().type());
checkType(ent.getKey(), sch, arg);
}
for (Map.Entry<String, Object> ent : arguments.entrySet()) {
if (!parameters().containsKey(ent.getKey())) {
throw new IllegalArgumentException("Extra argument '" + ent.getKey() + "'");
}
}
return trace;
}
/**
* Invoke the remote method, getting a future result.
*
* <p>
* This invokes the method asynchronously. The returned objects is a {@link CompletableFuture},
* whose getters are overridden to prevent blocking the Swing thread for more than 1 second. Use
* of this method is not recommended, if it can be avoided; however, you should not create a
* thread whose sole purpose is to invoke this method. UI actions that need to invoke a remote
* method should do so using this method, but they must be sure to handle errors using, e.g.,
* using {@link CompletableFuture#exceptionally(Function)}, lest the actions fail silently.
*
* @param arguments the keyword arguments to the remote method
* @return the future result
* @throws IllegalArgumentException if the arguments are not valid
*/
RemoteAsyncResult invokeAsync(Map<String, Object> arguments);
/**
* Invoke the remote method and wait for its completion.
*
* <p>
* This method cannot be invoked from the Swing thread. This is to avoid locking up the user
* interface. If you are on the Swing thread, consider {@link #invokeAsync(Map)} instead. You
* can chain the follow-up actions and then schedule any UI updates on the Swing thread using
* {@link AsyncUtils#SWING_EXECUTOR}.
*
* @param arguments the keyword arguments to the remote method
* @throws IllegalArgumentException if the arguments are not valid
*/
default Object invoke(Map<String, Object> arguments) {
try {
return invokeAsync(arguments).get();
}
catch (InterruptedException | ExecutionException e) {
throw new TraceRmiError(e);
}
}
record RecordRemoteMethod(TraceRmiHandler handler, String name, Action action,
String description, Map<String, RemoteParameter> parameters, SchemaName retType)
implements RemoteMethod {
@Override
public RemoteAsyncResult invokeAsync(Map<String, Object> arguments) {
Trace trace = validate(arguments);
OpenTrace open = handler.getOpenTrace(trace);
return handler.invoke(open, name, arguments);
}
}
}
@@ -0,0 +1,50 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.rmi.trace;
import java.util.*;
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod.Action;
public class RemoteMethodRegistry {
private final Map<String, RemoteMethod> map = new HashMap<>();
private final Map<Action, Set<RemoteMethod>> byAction = new HashMap<>();
protected void add(RemoteMethod method) {
synchronized (map) {
map.put(method.name(), method);
byAction.computeIfAbsent(method.action(), k -> new HashSet<>()).add(method);
}
}
public Map<String, RemoteMethod> all() {
synchronized (map) {
return Map.copyOf(map);
}
}
public RemoteMethod get(String name) {
synchronized (map) {
return map.get(name);
}
}
public Set<RemoteMethod> getByAction(Action action) {
synchronized (map) {
return byAction.getOrDefault(action, Set.of());
}
}
}
@@ -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.service.rmi.trace;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
public record RemoteParameter(String name, SchemaName type, boolean required,
ValueSupplier defaultValue, String display, String description) {
}
@@ -0,0 +1,45 @@
/* ###
* 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.service.rmi.trace;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.SocketAddress;
public class TraceRmiAcceptor extends TraceRmiServer {
public TraceRmiAcceptor(TraceRmiPlugin plugin, SocketAddress address) {
super(plugin, address);
}
@Override
public void start() throws IOException {
socket = new ServerSocket();
bind();
}
@Override
protected void bind() throws IOException {
socket.bind(address, 1);
}
@Override
public TraceRmiHandler accept() throws IOException {
TraceRmiHandler handler = super.accept();
close();
return handler;
}
}
@@ -0,0 +1,33 @@
/* ###
* 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.service.rmi.trace;
public class TraceRmiError extends RuntimeException {
public TraceRmiError() {
}
public TraceRmiError(Throwable cause) {
super(cause);
}
public TraceRmiError(String message) {
super(message);
}
public TraceRmiError(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,119 @@
/* ###
* 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.service.rmi.trace;
import java.io.IOException;
import java.net.*;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.services.TraceRmiService;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.util.task.ConsoleTaskMonitor;
import ghidra.util.task.TaskMonitor;
@PluginInfo(
shortDescription = "Connect to back-end debuggers via Trace RMI",
description = """
Provides an alternative for connecting to back-end debuggers. The DebuggerModel has
become a bit onerous to implement. Despite its apparent flexibility, the recorder at
the front-end imposes many restrictions, and getting it to work turns into a lot of
guess work and frustration. Trace RMI should offer a more direct means of recording a
trace from a back-end.
""",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceActivatedPluginEvent.class,
TraceClosedPluginEvent.class,
},
servicesProvided = {
TraceRmiService.class,
})
public class TraceRmiPlugin extends Plugin implements TraceRmiService {
private static final int DEFAULT_PORT = 15432;
private final TaskMonitor monitor = new ConsoleTaskMonitor();
private SocketAddress serverAddress = new InetSocketAddress("0.0.0.0", DEFAULT_PORT);
private TraceRmiServer server;
public TraceRmiPlugin(PluginTool tool) {
super(tool);
}
public TaskMonitor getTaskMonitor() {
// TODO: Create one in the Debug Console?
return monitor;
}
@Override
public SocketAddress getServerAddress() {
if (server != null) {
// In case serverAddress is ephemeral, get its actual address
return server.getAddress();
}
return serverAddress;
}
@Override
public void setServerAddress(SocketAddress serverAddress) {
if (server != null) {
throw new IllegalStateException("Cannot change server address while it is started");
}
this.serverAddress = serverAddress;
}
@Override
public void startServer() throws IOException {
if (server != null) {
throw new IllegalStateException("Server is already started");
}
server = new TraceRmiServer(this, serverAddress);
server.start();
}
@Override
public void stopServer() {
if (server != null) {
server.close();
}
server = null;
}
@Override
public boolean isServerStarted() {
return server != null;
}
@Override
@SuppressWarnings("resource")
public TraceRmiHandler connect(SocketAddress address) throws IOException {
Socket socket = new Socket();
socket.connect(address);
return new TraceRmiHandler(this, socket);
}
@Override
public TraceRmiAcceptor acceptOne(SocketAddress address) throws IOException {
TraceRmiAcceptor acceptor = new TraceRmiAcceptor(this, address);
acceptor.start();
return acceptor;
}
}
@@ -0,0 +1,99 @@
/* ###
* 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.service.rmi.trace;
import java.io.IOException;
import java.net.*;
import ghidra.util.Msg;
public class TraceRmiServer {
protected final TraceRmiPlugin plugin;
protected final SocketAddress address;
protected ServerSocket socket;
public TraceRmiServer(TraceRmiPlugin plugin, SocketAddress address) {
this.plugin = plugin;
this.address = address;
}
protected void bind() throws IOException {
socket.bind(address);
}
public void start() throws IOException {
socket = new ServerSocket();
bind();
new Thread(this::serviceLoop, "trace-rmi server " + socket.getLocalSocketAddress()).start();
}
public void setTimeout(int millis) throws SocketException {
socket.setSoTimeout(millis);
}
/**
* Accept a connection and handle its requests.
*
* <p>
* This launches a new thread to handle the requests. The thread remains alive until the socket
* is closed by either side.
*
* @return the handler
* @throws IOException on error
*/
@SuppressWarnings("resource")
protected TraceRmiHandler accept() throws IOException {
Socket client = socket.accept();
TraceRmiHandler handler = new TraceRmiHandler(plugin, client);
handler.start();
return handler;
}
protected void serviceLoop() {
try {
accept();
}
catch (IOException e) {
if (socket.isClosed()) {
return;
}
Msg.error("Error accepting TraceRmi client", e);
return;
}
finally {
try {
socket.close();
}
catch (IOException e) {
Msg.error("Error closing TraceRmi service", e);
}
}
}
public void close() {
try {
socket.close();
}
catch (IOException e) {
Msg.error("Error closing TraceRmi service", e);
}
}
public SocketAddress getAddress() {
return socket.getLocalSocketAddress();
}
}
@@ -0,0 +1,95 @@
/* ###
* 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.service.rmi.trace;
import org.apache.commons.lang3.ArrayUtils;
import ghidra.program.model.address.*;
import ghidra.rmi.trace.TraceRmi.*;
public interface ValueDecoder {
ValueDecoder DEFAULT = new ValueDecoder() {};
default Address toAddress(Addr addr, boolean required) {
if (required) {
throw new IllegalStateException("Address requires a trace for context");
}
return null;
}
default AddressRange toRange(AddrRange range, boolean required)
throws AddressOverflowException {
if (required) {
throw new IllegalStateException("AddressRange requires a trace for context");
}
return null;
}
default Object getObject(ObjSpec spec, boolean required) {
if (required) {
throw new IllegalStateException("TraceObject requires a trace for context");
}
return null;
}
default Object getObject(ObjDesc desc, boolean required) {
if (required) {
throw new IllegalStateException("TraceObject requires a trace for context");
}
return null;
}
default Object toValue(Value value) throws AddressOverflowException {
return switch (value.getValueCase()) {
case NULL_VALUE -> null;
case BOOL_VALUE -> value.getBoolValue();
case BYTE_VALUE -> (byte) value.getByteValue();
case CHAR_VALUE -> (char) value.getCharValue();
case SHORT_VALUE -> (short) value.getShortValue();
case INT_VALUE -> value.getIntValue();
case LONG_VALUE -> value.getLongValue();
case STRING_VALUE -> value.getStringValue();
case BOOL_ARR_VALUE -> ArrayUtils.toPrimitive(
value.getBoolArrValue().getArrList().stream().toArray(Boolean[]::new));
case BYTES_VALUE -> value.getBytesValue().toByteArray();
case CHAR_ARR_VALUE -> value.getCharArrValue().toCharArray();
case SHORT_ARR_VALUE -> ArrayUtils.toPrimitive(
value.getShortArrValue()
.getArrList()
.stream()
.map(Integer::shortValue)
.toArray(Short[]::new));
case INT_ARR_VALUE -> value.getIntArrValue()
.getArrList()
.stream()
.mapToInt(Integer::intValue)
.toArray();
case LONG_ARR_VALUE -> value.getLongArrValue()
.getArrList()
.stream()
.mapToLong(Long::longValue)
.toArray();
case STRING_ARR_VALUE -> value.getStringArrValue()
.getArrList()
.toArray(String[]::new);
case ADDRESS_VALUE -> toAddress(value.getAddressValue(), true);
case RANGE_VALUE -> toRange(value.getRangeValue(), true);
case CHILD_SPEC -> getObject(value.getChildSpec(), true);
case CHILD_DESC -> getObject(value.getChildDesc(), true);
default -> throw new AssertionError("Unrecognized value: " + value);
};
}
}

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