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

This commit is contained in:
Dan 2023-04-21 16:17:59 -04:00
parent 0fe70e15fa
commit 1de4dfc9c7
96 changed files with 19314 additions and 214 deletions

View file

@ -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 {

View file

@ -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|

View file

@ -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.

View 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.

View file

@ -0,0 +1,3 @@
# Ghidra Trace RMI
Package for connecting GDB to Ghidra via Trace RMI.

View file

@ -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

View 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]

File diff suppressed because it is too large Load diff

View 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]

View 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}')

View file

@ -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()

View 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>

View 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()

View 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"

View 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 {