mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-20 22:36:25 +08:00
GP-2677: Introduce TraceRmi (API only, experimental)
This commit is contained in:
@@ -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|
|
||||
|
||||
+2
@@ -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"
|
||||
+15
-20
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
@@ -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;
|
||||
}
|
||||
}
|
||||
+70
@@ -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);
|
||||
}
|
||||
}
|
||||
+330
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -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) {
|
||||
}
|
||||
+45
@@ -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;
|
||||
}
|
||||
}
|
||||
+33
@@ -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);
|
||||
}
|
||||
}
|
||||
+1162
File diff suppressed because it is too large
Load Diff
+119
@@ -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;
|
||||
}
|
||||
}
|
||||
+99
@@ -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();
|
||||
}
|
||||
}
|
||||
+95
@@ -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
Reference in New Issue
Block a user