mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-2677: Introduce TraceRmi (API only, experimental)
This commit is contained in:
parent
0fe70e15fa
commit
1de4dfc9c7
96 changed files with 19314 additions and 214 deletions
|
@ -20,6 +20,7 @@ apply from: "$rootProject.projectDir/gradle/nativeProject.gradle"
|
|||
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
|
||||
|
||||
apply from: "$rootProject.projectDir/gradle/debugger/hasExecutableJar.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/debugger/hasPythonPackage.gradle"
|
||||
|
||||
apply plugin: 'eclipse'
|
||||
eclipse.project.name = 'Debug Debugger-agent-gdb'
|
||||
|
@ -33,6 +34,8 @@ dependencies {
|
|||
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
||||
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
|
||||
testImplementation project(path: ':Debugger-gadp', configuration: 'testArtifacts')
|
||||
|
||||
pypkgInstall project(path: ':Debugger-rmi-trace', configuration: 'pypkgInstall')
|
||||
}
|
||||
|
||||
tasks.nodepJar {
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
##VERSION: 2.0
|
||||
##MODULE IP: JSch License
|
||||
DEVNOTES.txt||GHIDRA||||END|
|
||||
Module.manifest||GHIDRA||||END|
|
||||
data/scripts/fallback_info_proc_mappings.gdb||GHIDRA||||END|
|
||||
data/scripts/fallback_maintenance_info_sections.gdb||GHIDRA||||END|
|
||||
data/scripts/getpid-linux-i386.gdb||GHIDRA||||END|
|
||||
data/scripts/wine32_info_proc_mappings.gdb||GHIDRA||||END|
|
||||
src/main/py/LICENSE||GHIDRA||||END|
|
||||
src/main/py/README.md||GHIDRA||||END|
|
||||
src/main/py/ghidragdb/schema.xml||GHIDRA||||END|
|
||||
src/main/py/pyproject.toml||GHIDRA||||END|
|
||||
src/main/py/tests/EMPTY||GHIDRA||||END|
|
||||
|
|
|
@ -21,8 +21,10 @@ public interface GdbBreakpointInsertions {
|
|||
/**
|
||||
* Insert a breakpoint
|
||||
*
|
||||
* <p>
|
||||
* This is equivalent to the CLI command: {@code break [LOC]}, or {@code watch [LOC]}, etc.
|
||||
*
|
||||
* <p>
|
||||
* Breakpoints in GDB can get pretty complicated. Depending on the location specification, the
|
||||
* actual location of the breakpoint may change during the lifetime of an inferior. Take note of
|
||||
* the breakpoint number to track those changes across breakpoint modification events.
|
||||
|
|
11
Ghidra/Debug/Debugger-agent-gdb/src/main/py/LICENSE
Normal file
11
Ghidra/Debug/Debugger-agent-gdb/src/main/py/LICENSE
Normal file
|
@ -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.
|
3
Ghidra/Debug/Debugger-agent-gdb/src/main/py/README.md
Normal file
3
Ghidra/Debug/Debugger-agent-gdb/src/main/py/README.md
Normal file
|
@ -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
|
287
Ghidra/Debug/Debugger-agent-gdb/src/main/py/ghidragdb/arch.py
Normal file
287
Ghidra/Debug/Debugger-agent-gdb/src/main/py/ghidragdb/arch.py
Normal file
|
@ -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]
|
1456
Ghidra/Debug/Debugger-agent-gdb/src/main/py/ghidragdb/commands.py
Normal file
1456
Ghidra/Debug/Debugger-agent-gdb/src/main/py/ghidragdb/commands.py
Normal file
File diff suppressed because it is too large
Load diff
540
Ghidra/Debug/Debugger-agent-gdb/src/main/py/ghidragdb/hooks.py
Normal file
540
Ghidra/Debug/Debugger-agent-gdb/src/main/py/ghidragdb/hooks.py
Normal file
|
@ -0,0 +1,540 @@
|
|||
## ###
|
||||
# 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 time
|
||||
|
||||
import gdb
|
||||
|
||||
from . import commands
|
||||
|
||||
|
||||
class GhidraHookPrefix(gdb.Command):
|
||||
"""Commands for exporting data to a Ghidra trace"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('ghidra-hook', gdb.COMMAND_NONE, prefix=True)
|
||||
|
||||
|
||||
GhidraHookPrefix()
|
||||
|
||||
|
||||
class HookState(object):
|
||||
__slots__ = ('installed', 'mem_catchpoint', 'batch')
|
||||
|
||||
def __init__(self):
|
||||
self.installed = False
|
||||
self.mem_catchpoint = None
|
||||
self.batch = None
|
||||
|
||||
def ensure_batch(self):
|
||||
if self.batch is None:
|
||||
self.batch = commands.STATE.client.start_batch()
|
||||
|
||||
def end_batch(self):
|
||||
if self.batch is None:
|
||||
return
|
||||
commands.STATE.client.end_batch()
|
||||
self.batch = None
|
||||
|
||||
|
||||
class InferiorState(object):
|
||||
__slots__ = ('first', 'regions', 'modules', 'threads', 'breaks', 'visited')
|
||||
|
||||
def __init__(self):
|
||||
self.first = True
|
||||
# For things we can detect changes to between stops
|
||||
self.regions = False
|
||||
self.modules = False
|
||||
self.threads = False
|
||||
self.breaks = False
|
||||
# For frames and threads that have already been synced since last stop
|
||||
self.visited = set()
|
||||
|
||||
def record(self, description=None):
|
||||
first = self.first
|
||||
self.first = False
|
||||
if description is not None:
|
||||
commands.STATE.trace.snapshot(description)
|
||||
if first:
|
||||
commands.put_inferiors()
|
||||
commands.put_environment()
|
||||
if self.threads:
|
||||
commands.put_threads()
|
||||
self.threads = False
|
||||
thread = gdb.selected_thread()
|
||||
if thread is not None:
|
||||
if first or thread not in self.visited:
|
||||
commands.put_frames()
|
||||
self.visited.add(thread)
|
||||
frame = gdb.selected_frame()
|
||||
hashable_frame = (thread, frame.level())
|
||||
if first or hashable_frame not in self.visited:
|
||||
commands.putreg(frame, frame.architecture().registers())
|
||||
commands.putmem("$pc", "1", from_tty=False)
|
||||
commands.putmem("$sp", "1", from_tty=False)
|
||||
self.visited.add(hashable_frame)
|
||||
if first or self.regions or self.threads or self.modules:
|
||||
# Sections, memory syscalls, or stack allocations
|
||||
commands.put_regions()
|
||||
self.regions = False
|
||||
if first or self.modules:
|
||||
commands.put_modules()
|
||||
self.modules = False
|
||||
if first or self.breaks:
|
||||
commands.put_breakpoints()
|
||||
self.breaks = False
|
||||
|
||||
def record_continued(self):
|
||||
commands.put_inferiors()
|
||||
commands.put_threads()
|
||||
|
||||
def record_exited(self, exit_code):
|
||||
inf = gdb.selected_inferior()
|
||||
ipath = commands.INFERIOR_PATTERN.format(infnum=inf.num)
|
||||
infobj = commands.STATE.trace.proxy_object_path(ipath)
|
||||
infobj.set_value('_exit_code', exit_code)
|
||||
infobj.set_value('_state', 'TERMINATED')
|
||||
|
||||
|
||||
class BrkState(object):
|
||||
__slots__ = ('break_loc_counts',)
|
||||
|
||||
def __init__(self):
|
||||
self.break_loc_counts = {}
|
||||
|
||||
def update_brkloc_count(self, b, count):
|
||||
self.break_loc_counts[b] = count
|
||||
|
||||
def get_brkloc_count(self, b):
|
||||
return self.break_loc_counts.get(b, 0)
|
||||
|
||||
def del_brkloc_count(self, b):
|
||||
if b not in self.break_loc_counts:
|
||||
return 0 # TODO: Print a warning?
|
||||
count = self.break_loc_counts[b]
|
||||
del self.break_loc_counts[b]
|
||||
return count
|
||||
|
||||
|
||||
HOOK_STATE = HookState()
|
||||
BRK_STATE = BrkState()
|
||||
INF_STATES = {}
|
||||
|
||||
|
||||
def on_new_inferior(event):
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("New Inferior {}".format(event.inferior.num)):
|
||||
commands.put_inferiors() # TODO: Could put just the one....
|
||||
|
||||
|
||||
def on_inferior_selected():
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Inferior {} selected".format(inf.num)):
|
||||
INF_STATES[inf.num].record()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_inferior_deleted(event):
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
if event.inferior.num in INF_STATES:
|
||||
del INF_STATES[event.inferior.num]
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Inferior {} deleted".format(event.inferior.num)):
|
||||
commands.put_inferiors() # TODO: Could just delete the one....
|
||||
|
||||
|
||||
def on_new_thread(event):
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
INF_STATES[inf.num].threads = True
|
||||
# TODO: Syscall clone/exit to detect thread destruction?
|
||||
|
||||
|
||||
def on_thread_selected():
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
t = gdb.selected_thread()
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Thread {}.{} selected".format(inf.num, t.num)):
|
||||
INF_STATES[inf.num].record()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_frame_selected():
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
t = gdb.selected_thread()
|
||||
f = gdb.selected_frame()
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Frame {}.{}.{} selected".format(inf.num, t.num, f.level())):
|
||||
INF_STATES[inf.num].record()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_syscall_memory():
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
INF_STATES[inf.num].regions = True
|
||||
|
||||
|
||||
def on_memory_changed(event):
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Memory *0x{:08x} changed".format(event.address)):
|
||||
commands.put_bytes(event.address, event.address + event.length,
|
||||
pages=False, is_mi=False, from_tty=False)
|
||||
|
||||
|
||||
def on_register_changed(event):
|
||||
gdb.write("Register changed: {}".format(dir(event)))
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
# I'd rather have a descriptor!
|
||||
# TODO: How do I get the descriptor from the number?
|
||||
# For now, just record the lot
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Register {} changed".format(event.regnum)):
|
||||
commands.putreg(event.frame, event.frame.architecture().registers())
|
||||
|
||||
|
||||
def on_cont(event):
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
state = INF_STATES[inf.num]
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Continued"):
|
||||
state.record_continued()
|
||||
|
||||
|
||||
def on_stop(event):
|
||||
if hasattr(event, 'breakpoints') and HOOK_STATE.mem_catchpoint in event.breakpoints:
|
||||
return
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
state = INF_STATES[inf.num]
|
||||
state.visited.clear()
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Stopped"):
|
||||
state.record("Stopped")
|
||||
commands.put_event_thread()
|
||||
commands.activate()
|
||||
HOOK_STATE.end_batch()
|
||||
|
||||
|
||||
def on_exited(event):
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
state = INF_STATES[inf.num]
|
||||
state.visited.clear()
|
||||
description = "Exited"
|
||||
if hasattr(event, 'exit_code'):
|
||||
description += " with code {}".format(event.exit_code)
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx(description):
|
||||
state.record(description)
|
||||
if hasattr(event, 'exit_code'):
|
||||
state.record_exited(event.exit_code)
|
||||
commands.put_event_thread()
|
||||
commands.activate()
|
||||
HOOK_STATE.end_batch()
|
||||
|
||||
|
||||
def notify_others_breaks(inf):
|
||||
for num, state in INF_STATES.items():
|
||||
if num != inf.num:
|
||||
state.breaks = True
|
||||
|
||||
|
||||
def modules_changed():
|
||||
# Assumption: affects the current inferior
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
INF_STATES[inf.num].modules = True
|
||||
|
||||
|
||||
def on_clear_objfiles(event):
|
||||
modules_changed()
|
||||
|
||||
|
||||
def on_new_objfile(event):
|
||||
modules_changed()
|
||||
|
||||
|
||||
def on_free_objfile(event):
|
||||
modules_changed()
|
||||
|
||||
|
||||
def on_breakpoint_created(b):
|
||||
inf = gdb.selected_inferior()
|
||||
notify_others_breaks(inf)
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
ibpath = commands.INF_BREAKS_PATTERN.format(infnum=inf.num)
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Breakpoint {} created".format(b.number)):
|
||||
ibobj = trace.create_object(ibpath)
|
||||
# Do not use retain_values or it'll remove other locs
|
||||
commands.put_single_breakpoint(b, ibobj, inf, [])
|
||||
ibobj.insert()
|
||||
|
||||
|
||||
def on_breakpoint_modified(b):
|
||||
inf = gdb.selected_inferior()
|
||||
notify_others_breaks(inf)
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
old_count = BRK_STATE.get_brkloc_count(b)
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
ibpath = commands.INF_BREAKS_PATTERN.format(infnum=inf.num)
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Breakpoint {} modified".format(b.number)):
|
||||
ibobj = trace.create_object(ibpath)
|
||||
commands.put_single_breakpoint(b, ibobj, inf, [])
|
||||
new_count = BRK_STATE.get_brkloc_count(b)
|
||||
# NOTE: Location may not apply to inferior, but whatever.
|
||||
for i in range(new_count, old_count):
|
||||
ikey = commands.INF_BREAK_KEY_PATTERN.format(
|
||||
breaknum=b.number, locnum=i+1)
|
||||
ibobj.set_value(ikey, None)
|
||||
|
||||
|
||||
def on_breakpoint_deleted(b):
|
||||
inf = gdb.selected_inferior()
|
||||
notify_others_breaks(inf)
|
||||
if inf.num not in INF_STATES:
|
||||
return
|
||||
old_count = BRK_STATE.del_brkloc_count(b)
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
bpath = commands.BREAKPOINT_PATTERN.format(breaknum=b.number)
|
||||
ibobj = trace.proxy_object_path(
|
||||
commands.INF_BREAKS_PATTERN.format(infnum=inf.num))
|
||||
HOOK_STATE.ensure_batch()
|
||||
with trace.open_tx("Breakpoint {} modified".format(b.number)):
|
||||
trace.proxy_object_path(bpath).remove(tree=True)
|
||||
for i in range(old_count):
|
||||
ikey = commands.INF_BREAK_KEY_PATTERN.format(
|
||||
breaknum=b.number, locnum=i+1)
|
||||
ibobj.set_value(ikey, None)
|
||||
|
||||
|
||||
def on_before_prompt():
|
||||
HOOK_STATE.end_batch()
|
||||
|
||||
|
||||
# This will be called by a catchpoint
|
||||
class GhidraTraceEventMemoryCommand(gdb.Command):
|
||||
def __init__(self):
|
||||
super().__init__('ghidra-hook event-memory', gdb.COMMAND_NONE)
|
||||
|
||||
def invoke(self, argument, from_tty):
|
||||
self.dont_repeat()
|
||||
on_syscall_memory()
|
||||
|
||||
|
||||
GhidraTraceEventMemoryCommand()
|
||||
|
||||
|
||||
def cmd_hook(name):
|
||||
def _cmd_hook(func):
|
||||
class _ActiveCommand(gdb.Command):
|
||||
def __init__(self):
|
||||
# It seems we can't hook commands using the Python API....
|
||||
super().__init__(f"ghidra-hook def-{name}", gdb.COMMAND_USER)
|
||||
gdb.execute(f"""
|
||||
define {name}
|
||||
ghidra-hook def-{name}
|
||||
end
|
||||
""")
|
||||
|
||||
def invoke(self, argument, from_tty):
|
||||
self.dont_repeat()
|
||||
func()
|
||||
|
||||
def _unhook_command():
|
||||
gdb.execute(f"""
|
||||
define {name}
|
||||
end
|
||||
""")
|
||||
func.hook = _ActiveCommand
|
||||
func.unhook = _unhook_command
|
||||
return func
|
||||
return _cmd_hook
|
||||
|
||||
|
||||
@cmd_hook('hookpost-inferior')
|
||||
def hook_inferior():
|
||||
on_inferior_selected()
|
||||
|
||||
|
||||
@cmd_hook('hookpost-thread')
|
||||
def hook_thread():
|
||||
on_thread_selected()
|
||||
|
||||
|
||||
@cmd_hook('hookpost-frame')
|
||||
def hook_frame():
|
||||
on_frame_selected()
|
||||
|
||||
|
||||
# TODO: Checks and workarounds for events missing in gdb 8
|
||||
def install_hooks():
|
||||
if HOOK_STATE.installed:
|
||||
return
|
||||
HOOK_STATE.installed = True
|
||||
|
||||
gdb.events.new_inferior.connect(on_new_inferior)
|
||||
hook_inferior.hook()
|
||||
gdb.events.inferior_deleted.connect(on_inferior_deleted)
|
||||
|
||||
gdb.events.new_thread.connect(on_new_thread)
|
||||
hook_thread.hook()
|
||||
hook_frame.hook()
|
||||
|
||||
# Respond to user-driven state changes: (Not target-driven)
|
||||
gdb.events.memory_changed.connect(on_memory_changed)
|
||||
gdb.events.register_changed.connect(on_register_changed)
|
||||
# Respond to target-driven memory map changes:
|
||||
# group:memory is actually a bit broad, but will probably port better
|
||||
# One alternative is to name all syscalls that cause a change....
|
||||
# Ones we could probably omit:
|
||||
# msync,
|
||||
# (Deals in syncing file-backed pages to disk.)
|
||||
# mlock, munlock, mlockall, munlockall, mincore, madvise,
|
||||
# (Deal in paging. Doesn't affect valid addresses.)
|
||||
# mbind, get_mempolicy, set_mempolicy, migrate_pages, move_pages
|
||||
# (All NUMA stuff)
|
||||
#
|
||||
if HOOK_STATE.mem_catchpoint is not None:
|
||||
HOOK_STATE.mem_catchpoint.enabled = True
|
||||
else:
|
||||
breaks_before = set(gdb.breakpoints())
|
||||
gdb.execute("""
|
||||
catch syscall group:memory
|
||||
commands
|
||||
silent
|
||||
ghidra-hook event-memory
|
||||
cont
|
||||
end
|
||||
""")
|
||||
HOOK_STATE.mem_catchpoint = (
|
||||
set(gdb.breakpoints()) - breaks_before).pop()
|
||||
|
||||
gdb.events.cont.connect(on_cont)
|
||||
gdb.events.stop.connect(on_stop)
|
||||
gdb.events.exited.connect(on_exited) # Inferior exited
|
||||
|
||||
gdb.events.clear_objfiles.connect(on_clear_objfiles)
|
||||
gdb.events.free_objfile.connect(on_free_objfile)
|
||||
gdb.events.new_objfile.connect(on_new_objfile)
|
||||
|
||||
gdb.events.breakpoint_created.connect(on_breakpoint_created)
|
||||
gdb.events.breakpoint_deleted.connect(on_breakpoint_deleted)
|
||||
gdb.events.breakpoint_modified.connect(on_breakpoint_modified)
|
||||
|
||||
gdb.events.before_prompt.connect(on_before_prompt)
|
||||
|
||||
|
||||
def remove_hooks():
|
||||
if not HOOK_STATE.installed:
|
||||
return
|
||||
HOOK_STATE.installed = False
|
||||
|
||||
gdb.events.new_inferior.disconnect(on_new_inferior)
|
||||
hook_inferior.unhook()
|
||||
gdb.events.inferior_deleted.disconnect(on_inferior_deleted)
|
||||
|
||||
gdb.events.new_thread.disconnect(on_new_thread)
|
||||
hook_thread.unhook()
|
||||
hook_frame.unhook()
|
||||
|
||||
gdb.events.memory_changed.disconnect(on_memory_changed)
|
||||
gdb.events.register_changed.disconnect(on_register_changed)
|
||||
HOOK_STATE.mem_catchpoint.enabled = False
|
||||
|
||||
gdb.events.cont.disconnect(on_cont)
|
||||
gdb.events.stop.disconnect(on_stop)
|
||||
gdb.events.exited.disconnect(on_exited) # Inferior exited
|
||||
|
||||
gdb.events.clear_objfiles.disconnect(on_clear_objfiles)
|
||||
gdb.events.free_objfile.disconnect(on_free_objfile)
|
||||
gdb.events.new_objfile.disconnect(on_new_objfile)
|
||||
|
||||
gdb.events.breakpoint_created.disconnect(on_breakpoint_created)
|
||||
gdb.events.breakpoint_deleted.disconnect(on_breakpoint_deleted)
|
||||
gdb.events.breakpoint_modified.disconnect(on_breakpoint_modified)
|
||||
|
||||
gdb.events.before_prompt.disconnect(on_before_prompt)
|
||||
|
||||
|
||||
def enable_current_inferior():
|
||||
inf = gdb.selected_inferior()
|
||||
INF_STATES[inf.num] = InferiorState()
|
||||
|
||||
|
||||
def disable_current_inferior():
|
||||
inf = gdb.selected_inferior()
|
||||
if inf.num in INF_STATES:
|
||||
# Silently ignore already disabled
|
||||
del INF_STATES[inf.num]
|
653
Ghidra/Debug/Debugger-agent-gdb/src/main/py/ghidragdb/methods.py
Normal file
653
Ghidra/Debug/Debugger-agent-gdb/src/main/py/ghidragdb/methods.py
Normal file
|
@ -0,0 +1,653 @@
|
|||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
from concurrent.futures import Future, Executor
|
||||
import re
|
||||
|
||||
from ghidratrace import sch
|
||||
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
||||
|
||||
import gdb
|
||||
|
||||
from . import commands, hooks, util
|
||||
|
||||
|
||||
class GdbExecutor(Executor):
|
||||
def submit(self, fn, *args, **kwargs):
|
||||
fut = Future()
|
||||
|
||||
def _exec():
|
||||
try:
|
||||
result = fn(*args, **kwargs)
|
||||
hooks.HOOK_STATE.end_batch()
|
||||
fut.set_result(result)
|
||||
except Exception as e:
|
||||
fut.set_exception(e)
|
||||
|
||||
gdb.post_event(_exec)
|
||||
return fut
|
||||
|
||||
|
||||
REGISTRY = MethodRegistry(GdbExecutor())
|
||||
|
||||
|
||||
def extre(base, ext):
|
||||
return re.compile(base.pattern + ext)
|
||||
|
||||
|
||||
AVAILABLE_PATTERN = re.compile('Available\[(?P<pid>\\d*)\]')
|
||||
BREAKPOINT_PATTERN = re.compile('Breakpoints\[(?P<breaknum>\\d*)\]')
|
||||
BREAK_LOC_PATTERN = extre(BREAKPOINT_PATTERN, '\[(?P<locnum>\\d*)\]')
|
||||
INFERIOR_PATTERN = re.compile('Inferiors\[(?P<infnum>\\d*)\]')
|
||||
INF_BREAKS_PATTERN = extre(INFERIOR_PATTERN, '\.Breakpoints')
|
||||
ENV_PATTERN = extre(INFERIOR_PATTERN, '\.Environment')
|
||||
THREADS_PATTERN = extre(INFERIOR_PATTERN, '\.Threads')
|
||||
THREAD_PATTERN = extre(THREADS_PATTERN, '\[(?P<tnum>\\d*)\]')
|
||||
STACK_PATTERN = extre(THREAD_PATTERN, '\.Stack')
|
||||
FRAME_PATTERN = extre(STACK_PATTERN, '\[(?P<level>\\d*)\]')
|
||||
REGS_PATTERN = extre(FRAME_PATTERN, '.Registers')
|
||||
MEMORY_PATTERN = extre(INFERIOR_PATTERN, '\.Memory')
|
||||
MODULES_PATTERN = extre(INFERIOR_PATTERN, '\.Modules')
|
||||
|
||||
|
||||
def find_availpid_by_pattern(pattern, object, err_msg):
|
||||
mat = pattern.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
pid = int(mat['pid'])
|
||||
return pid
|
||||
|
||||
|
||||
def find_availpid_by_obj(object):
|
||||
return find_availpid_by_pattern(AVAILABLE_PATTERN, object, "an Available")
|
||||
|
||||
|
||||
def find_inf_by_num(infnum):
|
||||
for inf in gdb.inferiors():
|
||||
if inf.num == infnum:
|
||||
return inf
|
||||
raise KeyError(f"Inferiors[{infnum}] does not exist")
|
||||
|
||||
|
||||
def find_inf_by_pattern(object, pattern, err_msg):
|
||||
mat = pattern.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
infnum = int(mat['infnum'])
|
||||
return find_inf_by_num(infnum)
|
||||
|
||||
|
||||
def find_inf_by_obj(object):
|
||||
return find_inf_by_pattern(object, INFERIOR_PATTERN, "an Inferior")
|
||||
|
||||
|
||||
def find_inf_by_infbreak_obj(object):
|
||||
return find_inf_by_pattern(object, INF_BREAKS_PATTERN,
|
||||
"a BreakpointLocationContainer")
|
||||
|
||||
|
||||
def find_inf_by_env_obj(object):
|
||||
return find_inf_by_pattern(object, ENV_PATTERN, "an Environment")
|
||||
|
||||
|
||||
def find_inf_by_threads_obj(object):
|
||||
return find_inf_by_pattern(object, THREADS_PATTERN, "a ThreadContainer")
|
||||
|
||||
|
||||
def find_inf_by_mem_obj(object):
|
||||
return find_inf_by_pattern(object, MEMORY_PATTERN, "a Memory")
|
||||
|
||||
|
||||
def find_inf_by_modules_obj(object):
|
||||
return find_inf_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")
|
||||
|
||||
|
||||
def find_thread_by_num(inf, tnum):
|
||||
for t in inf.threads():
|
||||
if t.num == tnum:
|
||||
return t
|
||||
raise KeyError(f"Inferiors[{inf.num}].Threads[{tnum}] does not exist")
|
||||
|
||||
|
||||
def find_thread_by_pattern(pattern, object, err_msg):
|
||||
mat = pattern.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
infnum = int(mat['infnum'])
|
||||
tnum = int(mat['tnum'])
|
||||
inf = find_inf_by_num(infnum)
|
||||
return find_thread_by_num(inf, tnum)
|
||||
|
||||
|
||||
def find_thread_by_obj(object):
|
||||
return find_thread_by_pattern(THREAD_PATTERN, object, "a Thread")
|
||||
|
||||
|
||||
def find_thread_by_stack_obj(object):
|
||||
return find_thread_by_pattern(STACK_PATTERN, object, "a Stack")
|
||||
|
||||
|
||||
def find_frame_by_level(thread, level):
|
||||
# Because threads don't have any attribute to get at frames
|
||||
thread.switch()
|
||||
f = gdb.selected_frame()
|
||||
|
||||
# Navigate up or down, because I can't just get by level
|
||||
down = level - f.level()
|
||||
while down > 0:
|
||||
f = f.older()
|
||||
if f is None:
|
||||
raise KeyError(
|
||||
f"Inferiors[{thread.inferior.num}].Threads[{thread.num}].Stack[{level}] does not exist")
|
||||
down -= 1
|
||||
while down < 0:
|
||||
f = f.newer()
|
||||
if f is None:
|
||||
raise KeyError(
|
||||
f"Inferiors[{thread.inferior.num}].Threads[{thread.num}].Stack[{level}] does not exist")
|
||||
down += 1
|
||||
assert f.level() == level
|
||||
return f
|
||||
|
||||
|
||||
def find_frame_by_pattern(pattern, object, err_msg):
|
||||
mat = pattern.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
infnum = int(mat['infnum'])
|
||||
tnum = int(mat['tnum'])
|
||||
level = int(mat['level'])
|
||||
inf = find_inf_by_num(infnum)
|
||||
t = find_thread_by_num(inf, tnum)
|
||||
return find_frame_by_level(t, level)
|
||||
|
||||
|
||||
def find_frame_by_obj(object):
|
||||
return find_frame_by_pattern(FRAME_PATTERN, object, "a StackFrame")
|
||||
|
||||
|
||||
def find_frame_by_regs_obj(object):
|
||||
return find_frame_by_pattern(REGS_PATTERN, object,
|
||||
"a RegisterValueContainer")
|
||||
|
||||
|
||||
# Because there's no method to get a register by name....
|
||||
def find_reg_by_name(f, name):
|
||||
for reg in f.architecture().registers():
|
||||
if reg.name == name:
|
||||
return reg
|
||||
raise KeyError(f"No such register: {name}")
|
||||
|
||||
|
||||
# Oof. no gdb/Python method to get breakpoint by number
|
||||
# I could keep my own cache in a dict, but why?
|
||||
def find_bpt_by_number(breaknum):
|
||||
# TODO: If len exceeds some threshold, use binary search?
|
||||
for b in gdb.breakpoints():
|
||||
if b.number == breaknum:
|
||||
return b
|
||||
raise KeyError(f"Breakpoints[{breaknum}] does not exist")
|
||||
|
||||
|
||||
def find_bpt_by_pattern(pattern, object, err_msg):
|
||||
mat = pattern.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
breaknum = int(mat['breaknum'])
|
||||
return find_bpt_by_number(breaknum)
|
||||
|
||||
|
||||
def find_bpt_by_obj(object):
|
||||
return find_bpt_by_pattern(BREAKPOINT_PATTERN, object, "a BreakpointSpec")
|
||||
|
||||
|
||||
def find_bptlocnum_by_pattern(pattern, object, err_msg):
|
||||
mat = pattern.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypError(f"{object} is not {err_msg}")
|
||||
breaknum = int(mat['breaknum'])
|
||||
locnum = int(mat['locnum'])
|
||||
return breaknum, locnum
|
||||
|
||||
|
||||
def find_bptlocnum_by_obj(object):
|
||||
return find_bptlocnum_by_pattern(BREAK_LOC_PATTERN, object,
|
||||
"a BreakpointLocation")
|
||||
|
||||
|
||||
def find_bpt_loc_by_obj(object):
|
||||
breaknum, locnum = find_bptlocnum_by_obj(object)
|
||||
bpt = find_bpt_by_number(breaknum)
|
||||
# Requires gdb-13.1 or later
|
||||
return bpt.locations[locnum - 1] # Display is 1-up
|
||||
|
||||
|
||||
def switch_inferior(inferior):
|
||||
if gdb.selected_inferior().num == inferior.num:
|
||||
return
|
||||
gdb.execute("inferior {}".format(inferior.num))
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def execute(cmd: str, to_string: bool=False):
|
||||
"""Execute a CLI command."""
|
||||
return gdb.execute(cmd, to_string=to_string)
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_available(node: sch.Schema('AvailableContainer')):
|
||||
"""List processes on gdb's host system."""
|
||||
with commands.open_tracked_tx('Refresh Available'):
|
||||
gdb.execute('ghidra trace put-available')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
|
||||
"""
|
||||
Refresh the list of breakpoints (including locations for the current
|
||||
inferior).
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Breakpoints'):
|
||||
gdb.execute('ghidra trace put-breakpoints')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_inferiors(node: sch.Schema('InferiorContainer')):
|
||||
"""Refresh the list of inferiors."""
|
||||
with commands.open_tracked_tx('Refresh Inferiors'):
|
||||
gdb.execute('ghidra trace put-inferiors')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_inf_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
||||
"""
|
||||
Refresh the breakpoint locations for the inferior.
|
||||
|
||||
In the course of refreshing the locations, the breakpoint list will also be
|
||||
refreshed.
|
||||
"""
|
||||
switch_inferior(find_inf_by_infbreak_obj(node))
|
||||
with commands.open_tracked_tx('Refresh Breakpoint Locations'):
|
||||
gdb.execute('ghidra trace put-breakpoints')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_environment(node: sch.Schema('Environment')):
|
||||
"""Refresh the environment descriptors (arch, os, endian)."""
|
||||
switch_inferior(find_inf_by_env_obj(node))
|
||||
with commands.open_tracked_tx('Refresh Environment'):
|
||||
gdb.execute('ghidra trace put-environment')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
||||
"""Refresh the list of threads in the inferior."""
|
||||
switch_inferior(find_inf_by_threads_obj(node))
|
||||
with commands.open_tracked_tx('Refresh Threads'):
|
||||
gdb.execute('ghidra trace put-threads')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_stack(node: sch.Schema('Stack')):
|
||||
"""Refresh the backtrace for the thread."""
|
||||
find_thread_by_stack_obj(node).switch()
|
||||
with commands.open_tracked_tx('Refresh Stack'):
|
||||
gdb.execute('ghidra trace put-frames')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
||||
"""Refresh the register values for the frame."""
|
||||
find_frame_by_regs_obj(node).select()
|
||||
# TODO: Groups?
|
||||
with commands.open_tracked_tx('Refresh Registers'):
|
||||
gdb.execute('ghidra trace putreg')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_mappings(node: sch.Schema('Memory')):
|
||||
"""Refresh the list of memory regions for the inferior."""
|
||||
switch_inferior(find_inf_by_mem_obj(node))
|
||||
with commands.open_tracked_tx('Refresh Memory Regions'):
|
||||
gdb.execute('ghidra trace put-regions')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_modules(node: sch.Schema('ModuleContainer')):
|
||||
"""
|
||||
Refresh the modules and sections list for the inferior.
|
||||
|
||||
This will refresh the sections for all modules, not just the selected one.
|
||||
"""
|
||||
switch_inferior(find_inf_by_modules_obj(node))
|
||||
with commands.open_tracked_tx('Refresh Modules'):
|
||||
gdb.execute('ghidra trace put-modules')
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_inferior(inferior: sch.Schema('Inferior')):
|
||||
"""Switch to the inferior."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_thread(thread: sch.Schema('Thread')):
|
||||
"""Switch to the thread."""
|
||||
find_thread_by_obj(thread).switch()
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_frame(frame: sch.Schema('StackFrame')):
|
||||
"""Select the frame."""
|
||||
find_frame_by_obj(frame).select()
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def add_inferior(container: sch.Schema('InferiorContainer')):
|
||||
"""Add a new inferior."""
|
||||
gdb.execute('add-inferior')
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
def delete_inferior(inferior: sch.Schema('Inferior')):
|
||||
"""Remove the inferior."""
|
||||
inf = find_inf_by_obj(inferior)
|
||||
gdb.execute(f'remove-inferior {inf.num}')
|
||||
|
||||
|
||||
# TODO: Separate method for each of core, exec, remote, etc...?
|
||||
@REGISTRY.method
|
||||
def connect(inferior: sch.Schema('Inferior'), spec: str):
|
||||
"""Connect to a target machine or process."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute(f'target {spec}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
def attach_obj(inferior: sch.Schema('Inferior'), target: sch.Schema('Attachable')):
|
||||
"""Attach the inferior to the given target."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
pid = find_availpid_by_obj(target)
|
||||
gdb.execute(f'attach {pid}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
def attach_pid(inferior: sch.Schema('Inferior'), pid: int):
|
||||
"""Attach the inferior to the given target."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute(f'attach {pid}')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def detach(inferior: sch.Schema('Inferior')):
|
||||
"""Detach the inferior's target."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute('detach')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
def launch_main(inferior: sch.Schema('Inferior'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
"""
|
||||
Start a native process with the given command line, stopping at 'main'
|
||||
(start).
|
||||
|
||||
If 'main' is not defined in the file, this behaves like 'run'.
|
||||
"""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute(f'''
|
||||
file {file}
|
||||
set args {args}
|
||||
start
|
||||
''')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch', condition=util.GDB_VERSION.major >= 9)
|
||||
def launch_loader(inferior: sch.Schema('Inferior'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
"""
|
||||
Start a native process with the given command line, stopping at first
|
||||
instruction (starti).
|
||||
"""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute(f'''
|
||||
file {file}
|
||||
set args {args}
|
||||
starti
|
||||
''')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
def launch_run(inferior: sch.Schema('Inferior'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
"""
|
||||
Run a native process with the given command line (run).
|
||||
|
||||
The process will not stop until it hits one of your breakpoints, or it is
|
||||
signaled.
|
||||
"""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute(f'''
|
||||
file {file}
|
||||
set args {args}
|
||||
run
|
||||
''')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def kill(inferior: sch.Schema('Inferior')):
|
||||
"""Kill execution of the inferior."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute('kill')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def resume(inferior: sch.Schema('Inferior')):
|
||||
"""Continue execution of the inferior."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute('continue')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def interrupt():
|
||||
"""Interrupt the execution of the debugged program."""
|
||||
gdb.execute('interrupt')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||
"""Step one instruction exactly (stepi)."""
|
||||
find_thread_by_obj(thread).switch()
|
||||
gdb.execute('stepi')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||
"""Step one instruction, but proceed through subroutine calls (nexti)."""
|
||||
find_thread_by_obj(thread).switch()
|
||||
gdb.execute('nexti')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def step_out(thread: sch.Schema('Thread')):
|
||||
"""Execute until the current stack frame returns (finish)."""
|
||||
find_thread_by_obj(thread).switch()
|
||||
gdb.execute('finish')
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_ext')
|
||||
def step_advance(thread: sch.Schema('Thread'), address: Address):
|
||||
"""Continue execution up to the given address (advance)."""
|
||||
t = find_thread_by_obj(thread)
|
||||
t.switch()
|
||||
offset = thread.trace.memory_mapper.map_back(t.inferior, address)
|
||||
gdb.execute(f'advance *0x{offset:x}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_ext')
|
||||
def step_return(thread: sch.Schema('Thread'), value: int=None):
|
||||
"""Skip the remainder of the current function (return)."""
|
||||
find_thread_by_obj(thread).switch()
|
||||
if value is None:
|
||||
gdb.execute('return')
|
||||
else:
|
||||
gdb.execute(f'return {value}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_sw_execute')
|
||||
def break_sw_execute_address(inferior: sch.Schema('Inferior'), address: Address):
|
||||
"""Set a breakpoint (break)."""
|
||||
inf = find_inf_by_obj(inferior)
|
||||
offset = inferior.trace.memory_mapper.map_back(inf, address)
|
||||
gdb.execute(f'break *0x{offset:x}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_sw_execute')
|
||||
def break_sw_execute_expression(expression: str):
|
||||
"""Set a breakpoint (break)."""
|
||||
# TODO: Escape?
|
||||
gdb.execute(f'break {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_hw_execute')
|
||||
def break_hw_execute_address(inferior: sch.Schema('Inferior'), address: Address):
|
||||
"""Set a hardware-assisted breakpoint (hbreak)."""
|
||||
inf = find_inf_by_obj(inferior)
|
||||
offset = inferior.trace.memory_mapper.map_back(inf, address)
|
||||
gdb.execute(f'hbreak *0x{offset:x}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_hw_execute')
|
||||
def break_hw_execute_expression(expression: str):
|
||||
"""Set a hardware-assisted breakpoint (hbreak)."""
|
||||
# TODO: Escape?
|
||||
gdb.execute(f'hbreak {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_read')
|
||||
def break_read_range(inferior: sch.Schema('Inferior'), range: AddressRange):
|
||||
"""Set a read watchpoint (rwatch)."""
|
||||
inf = find_inf_by_obj(inferior)
|
||||
offset_start = inferior.trace.memory_mapper.map_back(
|
||||
inf, Address(range.space, range.min))
|
||||
gdb.execute(
|
||||
f'rwatch -location *((char(*)[{range.length()}]) 0x{offset_start:x})')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_read')
|
||||
def break_read_expression(expression: str):
|
||||
"""Set a read watchpoint (rwatch)."""
|
||||
gdb.execute(f'rwatch {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_write')
|
||||
def break_write_range(inferior: sch.Schema('Inferior'), range: AddressRange):
|
||||
"""Set a watchpoint (watch)."""
|
||||
inf = find_inf_by_obj(inferior)
|
||||
offset_start = inferior.trace.memory_mapper.map_back(
|
||||
inf, Address(range.space, range.min))
|
||||
gdb.execute(
|
||||
f'watch -location *((char(*)[{range.length()}]) 0x{offset_start:x})')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_write')
|
||||
def break_write_expression(expression: str):
|
||||
"""Set a watchpoint (watch)."""
|
||||
gdb.execute(f'watch {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_access')
|
||||
def break_access_range(inferior: sch.Schema('Inferior'), range: AddressRange):
|
||||
"""Set an access watchpoint (awatch)."""
|
||||
inf = find_inf_by_obj(inferior)
|
||||
offset_start = inferior.trace.memory_mapper.map_back(
|
||||
inf, Address(range.space, range.min))
|
||||
gdb.execute(
|
||||
f'awatch -location *((char(*)[{range.length()}]) 0x{offset_start:x})')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_access')
|
||||
def break_access_expression(expression: str):
|
||||
"""Set an access watchpoint (awatch)."""
|
||||
gdb.execute(f'awatch {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_ext')
|
||||
def break_event(spec: str):
|
||||
"""Set a catchpoint (catch)."""
|
||||
gdb.execute(f'catch {spec}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='toggle')
|
||||
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
||||
"""Toggle a breakpoint."""
|
||||
bpt = find_bpt_by_obj(breakpoint)
|
||||
bpt.enabled = enabled
|
||||
|
||||
|
||||
@REGISTRY.method(action='toggle', condition=util.GDB_VERSION.major >= 13)
|
||||
def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabled: bool):
|
||||
"""Toggle a breakpoint location."""
|
||||
loc = find_bpt_loc_by_obj(location)
|
||||
loc.enabled = enabled
|
||||
|
||||
|
||||
@REGISTRY.method(action='toggle', condition=util.GDB_VERSION.major < 13)
|
||||
def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabled: bool):
|
||||
"""Toggle a breakpoint location."""
|
||||
bptnum, locnum = find_bptlocnum_by_obj(location)
|
||||
cmd = 'enable' if enabled else 'disable'
|
||||
gdb.execute(f'{cmd} {bptnum}.{locnum}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
||||
"""Delete a breakpoint."""
|
||||
bpt = find_bpt_by_obj(breakpoint)
|
||||
bpt.delete()
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def read_mem(inferior: sch.Schema('Inferior'), range: AddressRange):
|
||||
"""Read memory."""
|
||||
inf = find_inf_by_obj(inferior)
|
||||
offset_start = inferior.trace.memory_mapper.map_back(
|
||||
inf, Address(range.space, range.min))
|
||||
with commands.open_tracked_tx('Read Memory'):
|
||||
gdb.execute(f'ghidra trace putmem 0x{offset_start:x} {range.length()}')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def write_mem(inferior: sch.Schema('Inferior'), address: Address, data: bytes):
|
||||
"""Write memory."""
|
||||
inf = find_inf_by_obj(inferior)
|
||||
offset = inferior.trace.memory_mapper.map_back(inf, address)
|
||||
inf.write_memory(offset, data)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def write_reg(frame: sch.Schema('Frame'), name: str, value: bytes):
|
||||
"""Write a register."""
|
||||
f = find_frame_by_obj(frame)
|
||||
f.select()
|
||||
inf = gdb.selected_inferior()
|
||||
mname, mval = frame.trace.register_mapper.map_value_back(inf, name, value)
|
||||
reg = find_reg_by_name(f, mname)
|
||||
size = int(gdb.parse_and_eval(f'sizeof(${mname})'))
|
||||
arr = '{' + ','.join(str(b) for b in mval) + '}'
|
||||
gdb.execute(f'set ((unsigned char[{size}])${mname}) = {arr}')
|
|
@ -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()
|
||||
|
413
Ghidra/Debug/Debugger-agent-gdb/src/main/py/ghidragdb/schema.xml
Normal file
413
Ghidra/Debug/Debugger-agent-gdb/src/main/py/ghidragdb/schema.xml
Normal file
|
@ -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>
|
286
Ghidra/Debug/Debugger-agent-gdb/src/main/py/ghidragdb/util.py
Normal file
286
Ghidra/Debug/Debugger-agent-gdb/src/main/py/ghidragdb/util.py
Normal file
|
@ -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()
|
25
Ghidra/Debug/Debugger-agent-gdb/src/main/py/pyproject.toml
Normal file
25
Ghidra/Debug/Debugger-agent-gdb/src/main/py/pyproject.toml
Normal file
|
@ -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"
|
0
Ghidra/Debug/Debugger-agent-gdb/src/main/py/tests/EMPTY
Normal file
0
Ghidra/Debug/Debugger-agent-gdb/src/main/py/tests/EMPTY
Normal file
|
@ -30,49 +30,49 @@ import ghidra.dbg.util.ShellUtils;
|
|||
public enum GdbLinuxSpecimen implements DebuggerTestSpecimen, DebuggerModelTestUtils {
|
||||
SLEEP {
|
||||
@Override
|
||||
String getCommandLine() {
|
||||
public String getCommandLine() {
|
||||
return DummyProc.which("expTraceableSleep");
|
||||
}
|
||||
},
|
||||
FORK_EXIT {
|
||||
@Override
|
||||
String getCommandLine() {
|
||||
public String getCommandLine() {
|
||||
return DummyProc.which("expFork");
|
||||
}
|
||||
},
|
||||
|
||||
CLONE_EXIT {
|
||||
@Override
|
||||
String getCommandLine() {
|
||||
public String getCommandLine() {
|
||||
return DummyProc.which("expCloneExit");
|
||||
}
|
||||
},
|
||||
PRINT {
|
||||
@Override
|
||||
String getCommandLine() {
|
||||
public String getCommandLine() {
|
||||
return DummyProc.which("expPrint");
|
||||
}
|
||||
},
|
||||
REGISTERS {
|
||||
@Override
|
||||
String getCommandLine() {
|
||||
public String getCommandLine() {
|
||||
return DummyProc.which("expRegisters");
|
||||
}
|
||||
},
|
||||
SPIN_STRIPPED {
|
||||
@Override
|
||||
String getCommandLine() {
|
||||
public String getCommandLine() {
|
||||
return DummyProc.which("expSpin.stripped");
|
||||
}
|
||||
},
|
||||
STACK {
|
||||
@Override
|
||||
String getCommandLine() {
|
||||
public String getCommandLine() {
|
||||
return DummyProc.which("expStack");
|
||||
}
|
||||
};
|
||||
|
||||
abstract String getCommandLine();
|
||||
public abstract String getCommandLine();
|
||||
|
||||
@Override
|
||||
public DummyProc runDummy() throws Throwable {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue