mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-27 23:17:03 +08:00
GP-2677: Introduce TraceRmi (API only, experimental)
This commit is contained in:
@@ -68,6 +68,7 @@ Release
|
|||||||
.classpath
|
.classpath
|
||||||
.settings/
|
.settings/
|
||||||
.prefs
|
.prefs
|
||||||
|
.pydevproject
|
||||||
|
|
||||||
# Ignore XTEXT generated dirs/files
|
# Ignore XTEXT generated dirs/files
|
||||||
*/*/*/*/xtend-gen
|
*/*/*/*/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/distributableGhidraModule.gradle"
|
||||||
|
|
||||||
apply from: "$rootProject.projectDir/gradle/debugger/hasExecutableJar.gradle"
|
apply from: "$rootProject.projectDir/gradle/debugger/hasExecutableJar.gradle"
|
||||||
|
apply from: "$rootProject.projectDir/gradle/debugger/hasPythonPackage.gradle"
|
||||||
|
|
||||||
apply plugin: 'eclipse'
|
apply plugin: 'eclipse'
|
||||||
eclipse.project.name = 'Debug Debugger-agent-gdb'
|
eclipse.project.name = 'Debug Debugger-agent-gdb'
|
||||||
@@ -33,6 +34,8 @@ dependencies {
|
|||||||
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
||||||
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
|
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
|
||||||
testImplementation project(path: ':Debugger-gadp', configuration: 'testArtifacts')
|
testImplementation project(path: ':Debugger-gadp', configuration: 'testArtifacts')
|
||||||
|
|
||||||
|
pypkgInstall project(path: ':Debugger-rmi-trace', configuration: 'pypkgInstall')
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.nodepJar {
|
tasks.nodepJar {
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
##VERSION: 2.0
|
##VERSION: 2.0
|
||||||
##MODULE IP: JSch License
|
##MODULE IP: JSch License
|
||||||
|
DEVNOTES.txt||GHIDRA||||END|
|
||||||
Module.manifest||GHIDRA||||END|
|
Module.manifest||GHIDRA||||END|
|
||||||
data/scripts/fallback_info_proc_mappings.gdb||GHIDRA||||END|
|
data/scripts/fallback_info_proc_mappings.gdb||GHIDRA||||END|
|
||||||
data/scripts/fallback_maintenance_info_sections.gdb||GHIDRA||||END|
|
data/scripts/fallback_maintenance_info_sections.gdb||GHIDRA||||END|
|
||||||
data/scripts/getpid-linux-i386.gdb||GHIDRA||||END|
|
data/scripts/getpid-linux-i386.gdb||GHIDRA||||END|
|
||||||
data/scripts/wine32_info_proc_mappings.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
|
* Insert a breakpoint
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* This is equivalent to the CLI command: {@code break [LOC]}, or {@code watch [LOC]}, etc.
|
* 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
|
* 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
|
* 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.
|
* 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 {
|
public enum GdbLinuxSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUtils {
|
||||||
SLEEP {
|
SLEEP {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expTraceableSleep");
|
return DummyProc.which("expTraceableSleep");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FORK_EXIT {
|
FORK_EXIT {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expFork");
|
return DummyProc.which("expFork");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
CLONE_EXIT {
|
CLONE_EXIT {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expCloneExit");
|
return DummyProc.which("expCloneExit");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PRINT {
|
PRINT {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expPrint");
|
return DummyProc.which("expPrint");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
REGISTERS {
|
REGISTERS {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expRegisters");
|
return DummyProc.which("expRegisters");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SPIN_STRIPPED {
|
SPIN_STRIPPED {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expSpin.stripped");
|
return DummyProc.which("expSpin.stripped");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
STACK {
|
STACK {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expStack");
|
return DummyProc.which("expStack");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
abstract String getCommandLine();
|
public abstract String getCommandLine();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DummyProc runDummy() throws Throwable {
|
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/distributableGhidraModule.gradle"
|
||||||
|
|
||||||
apply from: "$rootProject.projectDir/gradle/debugger/hasExecutableJar.gradle"
|
apply from: "$rootProject.projectDir/gradle/debugger/hasExecutableJar.gradle"
|
||||||
|
apply from: "$rootProject.projectDir/gradle/debugger/hasPythonPackage.gradle"
|
||||||
|
|
||||||
apply plugin: 'eclipse'
|
apply plugin: 'eclipse'
|
||||||
eclipse.project.name = 'Debug Debugger-agent-lldb'
|
eclipse.project.name = 'Debug Debugger-agent-lldb'
|
||||||
@@ -33,6 +34,8 @@ dependencies {
|
|||||||
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
||||||
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
|
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
|
||||||
testImplementation project(path: ':Debugger-gadp', configuration: 'testArtifacts')
|
testImplementation project(path: ':Debugger-gadp', configuration: 'testArtifacts')
|
||||||
|
|
||||||
|
pypkgInstall project(path: ':Debugger-rmi-trace', configuration: 'pypkgInstall')
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.nodepJar {
|
tasks.nodepJar {
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
.project||NONE||reviewed||END|
|
.project||NONE||reviewed||END|
|
||||||
Module.manifest||GHIDRA||||END|
|
Module.manifest||GHIDRA||||END|
|
||||||
build.gradle||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-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/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 {
|
public enum MacOSSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUtils {
|
||||||
SPIN {
|
SPIN {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expSpin");
|
return DummyProc.which("expSpin");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FORK_EXIT {
|
FORK_EXIT {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expFork");
|
return DummyProc.which("expFork");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CLONE_EXIT {
|
CLONE_EXIT {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expCloneExit");
|
return DummyProc.which("expCloneExit");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PRINT {
|
PRINT {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expPrint");
|
return DummyProc.which("expPrint");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
REGISTERS {
|
REGISTERS {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expRegisters");
|
return DummyProc.which("expRegisters");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
STACK {
|
STACK {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expStack");
|
return DummyProc.which("expStack");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CREATE_PROCESS {
|
CREATE_PROCESS {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expCreateProcess");
|
return DummyProc.which("expCreateProcess");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CREATE_THREAD_EXIT {
|
CREATE_THREAD_EXIT {
|
||||||
@Override
|
@Override
|
||||||
String getCommandLine() {
|
public String getCommandLine() {
|
||||||
return DummyProc.which("expCreateThreadExit");
|
return DummyProc.which("expCreateThreadExit");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
abstract String getCommandLine();
|
public abstract String getCommandLine();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DummyProc runDummy() throws Throwable {
|
public DummyProc runDummy() throws Throwable {
|
||||||
@@ -117,24 +117,19 @@ public enum MacOSSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUtil
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRunningIn(TargetProcess process, AbstractDebuggerModelTest test)
|
public boolean isRunningIn(TargetProcess process, AbstractDebuggerModelTest test) throws Throwable {
|
||||||
throws Throwable {
|
|
||||||
// NB. ShellUtils.parseArgs removes the \s. Not good.
|
// NB. ShellUtils.parseArgs removes the \s. Not good.
|
||||||
String expected = getBinModuleName();
|
String expected = getBinModuleName();
|
||||||
TargetObject session = process.getParent().getParent();
|
TargetObject session = process.getParent().getParent();
|
||||||
Collection<TargetModule> modules =
|
Collection<TargetModule> modules = test.m.findAll(TargetModule.class, session.getPath(), true).values();
|
||||||
test.m.findAll(TargetModule.class, session.getPath(), true).values();
|
return modules.stream().anyMatch(m -> expected.equalsIgnoreCase(getShortName(m.getModuleName())));
|
||||||
return modules.stream()
|
|
||||||
.anyMatch(m -> expected.equalsIgnoreCase(getShortName(m.getModuleName())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAttachable(DummyProc dummy, TargetAttachable attachable,
|
public boolean isAttachable(DummyProc dummy, TargetAttachable attachable, AbstractDebuggerModelTest test)
|
||||||
AbstractDebuggerModelTest test) throws Throwable {
|
throws Throwable {
|
||||||
waitOn(attachable.fetchAttributes());
|
waitOn(attachable.fetchAttributes());
|
||||||
long pid =
|
long pid = attachable.getTypedAttributeNowByName(LldbModelTargetAvailable.PID_ATTRIBUTE_NAME, Long.class, -1L);
|
||||||
attachable.getTypedAttributeNowByName(LldbModelTargetAvailable.PID_ATTRIBUTE_NAME,
|
|
||||||
Long.class, -1L);
|
|
||||||
return pid == dummy.pid;
|
return pid == dummy.pid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,97 +13,21 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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/javaProject.gradle"
|
||||||
apply from: "${rootProject.projectDir}/gradle/jacocoProject.gradle"
|
apply from: "${rootProject.projectDir}/gradle/jacocoProject.gradle"
|
||||||
apply from: "${rootProject.projectDir}/gradle/javaTestProject.gradle"
|
apply from: "${rootProject.projectDir}/gradle/javaTestProject.gradle"
|
||||||
apply from: "${rootProject.projectDir}/gradle/distributableGhidraModule.gradle"
|
apply from: "${rootProject.projectDir}/gradle/distributableGhidraModule.gradle"
|
||||||
|
apply from: "${rootProject.projectDir}/gradle/debugger/hasProtobuf.gradle"
|
||||||
|
|
||||||
apply plugin: 'eclipse'
|
apply plugin: 'eclipse'
|
||||||
eclipse.project.name = 'Debug Debugger-gadp'
|
eclipse.project.name = 'Debug Debugger-gadp'
|
||||||
|
|
||||||
configurations {
|
|
||||||
allProtocArtifacts
|
|
||||||
protocArtifact
|
|
||||||
}
|
|
||||||
|
|
||||||
def platform = getCurrentPlatformName()
|
|
||||||
|
|
||||||
dependencies {
|
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-AsyncComm')
|
||||||
api project(':Framework-Debugging')
|
api project(':Framework-Debugging')
|
||||||
api project(':ProposedUtils')
|
api project(':ProposedUtils')
|
||||||
|
|
||||||
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
||||||
testImplementation project(path: ':Framework-Debugging', 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/jacocoProject.gradle"
|
||||||
apply from: "${rootProject.projectDir}/gradle/javaTestProject.gradle"
|
apply from: "${rootProject.projectDir}/gradle/javaTestProject.gradle"
|
||||||
apply from: "${rootProject.projectDir}/gradle/distributableGhidraModule.gradle"
|
apply from: "${rootProject.projectDir}/gradle/distributableGhidraModule.gradle"
|
||||||
|
apply from: "${rootProject.projectDir}/gradle/debugger/hasProtobuf.gradle"
|
||||||
|
|
||||||
apply plugin: 'eclipse'
|
apply plugin: 'eclipse'
|
||||||
eclipse.project.name = 'Debug Debugger-isf'
|
eclipse.project.name = 'Debug Debugger-isf'
|
||||||
|
|
||||||
configurations {
|
|
||||||
allProtocArtifacts
|
|
||||||
protocArtifact
|
|
||||||
}
|
|
||||||
|
|
||||||
def platform = getCurrentPlatformName()
|
|
||||||
|
|
||||||
dependencies {
|
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-AsyncComm')
|
||||||
api project(':Framework-Debugging')
|
api project(':Framework-Debugging')
|
||||||
api project(':ProposedUtils')
|
api project(':ProposedUtils')
|
||||||
|
|
||||||
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
||||||
testImplementation project(path: ':Framework-Debugging', 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
|
// Include buildable native source in distribution
|
||||||
rootProject.assembleDistribution {
|
rootProject.assembleDistribution {
|
||||||
from (this.project.projectDir.toString()) {
|
from (this.project.projectDir.toString()) {
|
||||||
include "runISFServer"
|
include "runISFServer"
|
||||||
into { getZipPath(this.project) }
|
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