mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-10-23 13:41:04 +00:00
Merge remote-tracking branch 'origin/GP-3754_d-millar_traceRmi_dbgeng--SQUASHED'
This commit is contained in:
commit
c072972153
|
@ -20,6 +20,7 @@ apply from: "$rootProject.projectDir/gradle/nativeProject.gradle"
|
|||
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
|
||||
|
||||
apply from: "$rootProject.projectDir/gradle/debugger/hasNodepJar.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/debugger/hasPythonPackage.gradle"
|
||||
|
||||
apply plugin: 'eclipse'
|
||||
eclipse.project.name = 'Debug Debugger-agent-dbgeng'
|
||||
|
|
|
@ -3,3 +3,7 @@
|
|||
Module.manifest||GHIDRA||||END|
|
||||
src/javaprovider/def/javaprovider.def||GHIDRA||||END|
|
||||
src/javaprovider/rc/javaprovider.rc||GHIDRA||||END|
|
||||
src/main/py/LICENSE||GHIDRA||||END|
|
||||
src/main/py/README.md||GHIDRA||||END|
|
||||
src/main/py/pyproject.toml||GHIDRA||||END|
|
||||
src/main/py/src/ghidradbg/schema.xml||GHIDRA||||END|
|
||||
|
|
11
Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/LICENSE
Normal file
11
Ghidra/Debug/Debugger-agent-dbgeng/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.
|
8
Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/README.md
Normal file
8
Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Ghidra Trace RMI
|
||||
|
||||
Package for connecting dbgeng to Ghidra via Trace RMI.
|
||||
|
||||
This connector requires Pybag 2.2.8 or better:
|
||||
|
||||
https://pypi.org/project/Pybag
|
||||
https://github.com/dshikashio/Pybag
|
|
@ -0,0 +1,26 @@
|
|||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "ghidradbg"
|
||||
version = "10.4"
|
||||
authors = [
|
||||
{ name="Ghidra Development Team" },
|
||||
]
|
||||
description = "Ghidra's Plugin for dbgeng"
|
||||
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",
|
||||
"pybag>=2.2.8"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
"Homepage" = "https://github.com/NationalSecurityAgency/ghidra"
|
||||
"Bug Tracker" = "https://github.com/NationalSecurityAgency/ghidra/issues"
|
|
@ -0,0 +1,19 @@
|
|||
## ###
|
||||
# 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 ctypes
|
||||
ctypes.windll.kernel32.SetErrorMode(0x0001|0x0002|0x8000)
|
||||
|
||||
from . import util, commands, methods, hooks
|
|
@ -0,0 +1,240 @@
|
|||
## ###
|
||||
# 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
|
||||
|
||||
from pybag import pydbg
|
||||
|
||||
from . import util
|
||||
|
||||
language_map = {
|
||||
'ARM': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A', 'ARM:BE:64:v8', 'ARM:LE:64:v8'],
|
||||
'Itanium': [],
|
||||
'x86': ['x86:LE:32:default'],
|
||||
'x86_64': ['x86:LE:64:default'],
|
||||
'EFI': ['x86:LE:64:default'],
|
||||
}
|
||||
|
||||
data64_compiler_map = {
|
||||
None: 'pointer64',
|
||||
}
|
||||
|
||||
x86_compiler_map = {
|
||||
'windows': 'windows',
|
||||
'Cygwin': 'windows',
|
||||
}
|
||||
|
||||
arm_compiler_map = {
|
||||
'windows': 'windows',
|
||||
}
|
||||
|
||||
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,
|
||||
'AARCH64:BE:64:v8A': arm_compiler_map,
|
||||
'AARCH64:LE:64:AppleSilicon': arm_compiler_map,
|
||||
'AARCH64:LE:64:v8A': arm_compiler_map,
|
||||
'ARM:BE:64:v8': arm_compiler_map,
|
||||
'ARM:LE:64:v8': arm_compiler_map,
|
||||
}
|
||||
|
||||
|
||||
def get_arch():
|
||||
try:
|
||||
type = util.get_debugger()._control.GetActualProcessorType()
|
||||
except Exception:
|
||||
return "Unknown"
|
||||
if type is None:
|
||||
return "x86_64"
|
||||
if type == 0x8664:
|
||||
return "x86_64"
|
||||
if type == 0x014c:
|
||||
return "x86"
|
||||
if type == 0x01c0:
|
||||
return "ARM"
|
||||
if type == 0x0200:
|
||||
return "Itanium"
|
||||
if type == 0x0EBC:
|
||||
return "EFI"
|
||||
return "Unknown"
|
||||
|
||||
|
||||
def get_endian():
|
||||
parm = util.get_convenience_variable('endian')
|
||||
if parm != 'auto':
|
||||
return parm
|
||||
return 'little'
|
||||
|
||||
|
||||
def get_osabi():
|
||||
parm = util.get_convenience_variable('osabi')
|
||||
if not parm in ['auto', 'default']:
|
||||
return parm
|
||||
try:
|
||||
os = util.get_debugger().cmd("vertarget")
|
||||
if "Windows" not in os:
|
||||
return "default"
|
||||
except Exception:
|
||||
pass
|
||||
return "windows"
|
||||
|
||||
|
||||
def compute_ghidra_language():
|
||||
# First, check if the parameter is set
|
||||
lang = util.get_convenience_variable('ghidra-language')
|
||||
if lang != 'auto':
|
||||
return lang
|
||||
|
||||
# Get the list of possible languages for the arch. We'll need to sift
|
||||
# through them by endian and probably prefer default/simpler variants. The
|
||||
# heuristic for "simpler" will be 'default' then shortest variant id.
|
||||
arch = get_arch()
|
||||
endian = get_endian()
|
||||
lebe = ':BE:' if endian == 'big' else ':LE:'
|
||||
if not arch in language_map:
|
||||
return 'DATA' + lebe + '64:default'
|
||||
langs = language_map[arch]
|
||||
matched_endian = sorted(
|
||||
(l for l in langs if lebe in l),
|
||||
key=lambda l: 0 if l.endswith(':default') else len(l)
|
||||
)
|
||||
if len(matched_endian) > 0:
|
||||
return matched_endian[0]
|
||||
# NOTE: I'm disinclined to fall back to a language match with wrong endian.
|
||||
return 'DATA' + lebe + '64:default'
|
||||
|
||||
|
||||
def compute_ghidra_compiler(lang):
|
||||
# First, check if the parameter is set
|
||||
comp = util.get_convenience_variable('ghidra-compiler')
|
||||
if comp != 'auto':
|
||||
return comp
|
||||
|
||||
# Check if the selected lang has specific compiler recommendations
|
||||
if not lang in compiler_map:
|
||||
return 'default'
|
||||
comp_map = compiler_map[lang]
|
||||
osabi = get_osabi()
|
||||
if osabi in comp_map:
|
||||
return comp_map[osabi]
|
||||
if None in comp_map:
|
||||
return comp_map[None]
|
||||
return 'default'
|
||||
|
||||
|
||||
def compute_ghidra_lcsp():
|
||||
lang = compute_ghidra_language()
|
||||
comp = compute_ghidra_compiler(lang)
|
||||
return lang, comp
|
||||
|
||||
|
||||
class DefaultMemoryMapper(object):
|
||||
|
||||
def __init__(self, defaultSpace):
|
||||
self.defaultSpace = defaultSpace
|
||||
|
||||
def map(self, proc: int, offset: int):
|
||||
space = self.defaultSpace
|
||||
return self.defaultSpace, Address(space, offset)
|
||||
|
||||
def map_back(self, proc: int, address: Address) -> int:
|
||||
if address.space == self.defaultSpace:
|
||||
return address.offset
|
||||
raise ValueError(f"Address {address} is not in process {proc.GetProcessID()}")
|
||||
|
||||
|
||||
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
|
||||
|
||||
memory_mappers = {}
|
||||
|
||||
|
||||
def compute_memory_mapper(lang):
|
||||
if not lang in memory_mappers:
|
||||
return DEFAULT_MEMORY_MAPPER
|
||||
return memory_mappers[lang]
|
||||
|
||||
|
||||
class DefaultRegisterMapper(object):
|
||||
|
||||
def __init__(self, byte_order):
|
||||
if not byte_order in ['big', 'little']:
|
||||
raise ValueError("Invalid byte_order: {}".format(byte_order))
|
||||
self.byte_order = byte_order
|
||||
self.union_winners = {}
|
||||
|
||||
def map_name(self, proc, name):
|
||||
return name
|
||||
|
||||
|
||||
def map_value(self, proc, name, value):
|
||||
try:
|
||||
### TODO: this seems half-baked
|
||||
av = value.to_bytes(8, "big")
|
||||
except Exception:
|
||||
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
|
||||
.format(name, value, type(value)))
|
||||
return RegVal(self.map_name(proc, name), av)
|
||||
|
||||
def map_name_back(self, proc, name):
|
||||
return name
|
||||
|
||||
def map_value_back(self, proc, name, value):
|
||||
return RegVal(self.map_name_back(proc, name), value)
|
||||
|
||||
|
||||
class Intel_x86_64_RegisterMapper(DefaultRegisterMapper):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('little')
|
||||
|
||||
def map_name(self, proc, name):
|
||||
if name is None:
|
||||
return 'UNKNOWN'
|
||||
if name == 'efl':
|
||||
return 'rflags'
|
||||
if name.startswith('zmm'):
|
||||
# Ghidra only goes up to ymm, right now
|
||||
return 'ymm' + name[3:]
|
||||
return super().map_name(proc, name)
|
||||
|
||||
def map_value(self, proc, name, value):
|
||||
rv = super().map_value(proc, name, value)
|
||||
if rv.name.startswith('ymm') and len(rv.value) > 32:
|
||||
return RegVal(rv.name, rv.value[-32:])
|
||||
return rv
|
||||
|
||||
def map_name_back(self, proc, name):
|
||||
if name == 'rflags':
|
||||
return 'eflags'
|
||||
|
||||
|
||||
DEFAULT_BE_REGISTER_MAPPER = DefaultRegisterMapper('big')
|
||||
DEFAULT_LE_REGISTER_MAPPER = DefaultRegisterMapper('little')
|
||||
|
||||
register_mappers = {
|
||||
'x86:LE:64:default': Intel_x86_64_RegisterMapper()
|
||||
}
|
||||
|
||||
|
||||
def compute_register_mapper(lang):
|
||||
if not lang in register_mappers:
|
||||
if ':BE:' in lang:
|
||||
return DEFAULT_BE_REGISTER_MAPPER
|
||||
if ':LE:' in lang:
|
||||
return DEFAULT_LE_REGISTER_MAPPER
|
||||
return register_mappers[lang]
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,439 @@
|
|||
## ###
|
||||
# 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 sys
|
||||
import time
|
||||
import threading
|
||||
|
||||
from pybag import pydbg
|
||||
from pybag.dbgeng.callbacks import EventHandler
|
||||
from pybag.dbgeng import core as DbgEng
|
||||
from pybag.dbgeng import exception
|
||||
from pybag.dbgeng.idebugbreakpoint import DebugBreakpoint
|
||||
|
||||
from . import commands, util
|
||||
|
||||
ALL_EVENTS = 0xFFFF
|
||||
|
||||
class HookState(object):
|
||||
__slots__ = ('installed', 'mem_catchpoint')
|
||||
|
||||
def __init__(self):
|
||||
self.installed = False
|
||||
self.mem_catchpoint = None
|
||||
|
||||
|
||||
class ProcessState(object):
|
||||
__slots__ = ('first', 'regions', 'modules', 'threads', 'breaks', 'watches', '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
|
||||
self.watches = 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_processes()
|
||||
commands.put_environment()
|
||||
if self.threads:
|
||||
commands.put_threads()
|
||||
self.threads = False
|
||||
thread = util.selected_thread()
|
||||
if thread is not None:
|
||||
if first or thread not in self.visited:
|
||||
commands.putreg()
|
||||
commands.putmem("$pc", "1", from_tty=False)
|
||||
commands.putmem("$sp", "1", from_tty=False)
|
||||
commands.put_frames()
|
||||
self.visited.add(thread)
|
||||
frame = util.selected_frame()
|
||||
hashable_frame = (thread, frame)
|
||||
if first or hashable_frame not in self.visited:
|
||||
self.visited.add(hashable_frame)
|
||||
if first or self.regions or self.threads or self.modules:
|
||||
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_processes(running=True)
|
||||
commands.put_threads(running=True)
|
||||
|
||||
def record_exited(self, exit_code, description=None):
|
||||
if description is not None:
|
||||
commands.STATE.trace.snapshot(description)
|
||||
proc = util.selected_process()
|
||||
ipath = commands.PROCESS_PATTERN.format(procnum=proc)
|
||||
commands.STATE.trace.proxy_object_path(
|
||||
ipath).set_value('_exit_code', exit_code)
|
||||
|
||||
|
||||
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.GetID()] = count
|
||||
|
||||
def get_brkloc_count(self, b):
|
||||
return self.break_loc_counts.get(b.GetID(), 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.GetID()]
|
||||
del self.break_loc_counts[b.GetID()]
|
||||
return count
|
||||
|
||||
|
||||
HOOK_STATE = HookState()
|
||||
BRK_STATE = BrkState()
|
||||
PROC_STATE = {}
|
||||
|
||||
|
||||
def on_state_changed(*args):
|
||||
#print("ON_STATE_CHANGED")
|
||||
#print(args[0])
|
||||
if args[0] == DbgEng.DEBUG_CES_CURRENT_THREAD:
|
||||
return on_thread_selected(args)
|
||||
elif args[0] == DbgEng.DEBUG_CES_BREAKPOINTS:
|
||||
return on_breakpoint_modified(args)
|
||||
elif args[0] == DbgEng.DEBUG_CES_RADIX:
|
||||
util.set_convenience_variable('output-radix', args[1])
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
elif args[0] == DbgEng.DEBUG_CES_EXECUTION_STATUS:
|
||||
if args[1] & DbgEng.DEBUG_STATUS_INSIDE_WAIT:
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
if args[1] == DbgEng.DEBUG_STATUS_BREAK:
|
||||
return on_stop(args)
|
||||
else:
|
||||
return on_cont(args)
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_debuggee_changed(*args):
|
||||
#print("ON_DEBUGGEE_CHANGED")
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
if args[1] == DbgEng.DEBUG_CDS_REGISTERS:
|
||||
on_register_changed(args[0][1])
|
||||
#if args[1] == DbgEng.DEBUG_CDS_DATA:
|
||||
# on_memory_changed(args[0][1])
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_session_status_changed(*args):
|
||||
#print("ON_STATUS_CHANGED")
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
if args[0] == DbgEng.DEBUG_SESSION_ACTIVE or args[0] == DbgEng.DEBUG_SSESION_REBOOT:
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_symbol_state_changed(*args):
|
||||
#print("ON_SYMBOL_STATE_CHANGED")
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
if args[0] == 1 or args[0] == 2:
|
||||
PROC_STATE[proc].modules = True
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_system_error(*args):
|
||||
print("ON_SYSTEM_ERROR")
|
||||
print(hex(args[0]))
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_BREAK
|
||||
|
||||
|
||||
def on_new_process(*args):
|
||||
#print("ON_NEW_PROCESS")
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_BREAK
|
||||
|
||||
|
||||
def on_process_selected():
|
||||
#print("PROCESS_SELECTED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Process {} selected".format(proc)):
|
||||
PROC_STATE[proc].record()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_process_deleted(*args):
|
||||
#print("ON_PROCESS_DELETED")
|
||||
proc = args[0]
|
||||
on_exited(proc)
|
||||
if proc in PROC_STATE:
|
||||
del PROC_STATE[proc]
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Process {} deleted".format(proc)):
|
||||
commands.put_processes() # TODO: Could just delete the one....
|
||||
return DbgEng.DEBUG_STATUS_BREAK
|
||||
|
||||
|
||||
def on_threads_changed(*args):
|
||||
#print("ON_THREADS_CHANGED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
PROC_STATE[proc].threads = True
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_thread_selected(*args):
|
||||
#print("THREAD_SELECTED")
|
||||
nthrd = args[0][1]
|
||||
nproc = util.selected_process()
|
||||
if nproc not in PROC_STATE:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Thread {}.{} selected".format(nproc, nthrd)):
|
||||
PROC_STATE[nproc].record()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_register_changed(regnum):
|
||||
#print("REGISTER_CHANGED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Register {} changed".format(regnum)):
|
||||
commands.putreg()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_cont(*args):
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
state = PROC_STATE[proc]
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Continued"):
|
||||
state.record_continued()
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_stop(*args):
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
print("not in state")
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
print("no trace")
|
||||
return
|
||||
state = PROC_STATE[proc]
|
||||
state.visited.clear()
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Stopped"):
|
||||
state.record("Stopped")
|
||||
commands.put_event_thread()
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_exited(proc):
|
||||
if proc not in PROC_STATE:
|
||||
print("not in state")
|
||||
return
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
state = PROC_STATE[proc]
|
||||
state.visited.clear()
|
||||
exit_code = util.GetExitCode()
|
||||
description = "Exited with code {}".format(exit_code)
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx(description):
|
||||
state.record_exited(exit_code, description)
|
||||
commands.activate()
|
||||
|
||||
|
||||
def on_modules_changed(*args):
|
||||
#print("ON_MODULES_CHANGED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
PROC_STATE[proc].modules = True
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_breakpoint_created(bp):
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
PROC_STATE[proc].breaks = True
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
ibpath = commands.PROC_BREAKS_PATTERN.format(procnum=proc)
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Breakpoint {} created".format(bp.GetId())):
|
||||
ibobj = trace.create_object(ibpath)
|
||||
# Do not use retain_values or it'll remove other locs
|
||||
commands.put_single_breakpoint(bp, ibobj, proc, [])
|
||||
ibobj.insert()
|
||||
|
||||
|
||||
def on_breakpoint_modified(*args):
|
||||
#print("BREAKPOINT_MODIFIED")
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
PROC_STATE[proc].breaks = True
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
ibpath = commands.PROC_BREAKS_PATTERN.format(procnum=proc)
|
||||
ibobj = trace.create_object(ibpath)
|
||||
bpid = args[0][1]
|
||||
try:
|
||||
bp = dbg()._control.GetBreakpointById(bpid)
|
||||
except exception.E_NOINTERFACE_Error:
|
||||
dbg().breakpoints._remove_stale(bpid)
|
||||
return on_breakpoint_deleted(bpid)
|
||||
return on_breakpoint_created(bp)
|
||||
|
||||
|
||||
def on_breakpoint_deleted(bpid):
|
||||
proc = util.selected_process()
|
||||
if proc not in PROC_STATE:
|
||||
return
|
||||
PROC_STATE[proc].breaks = True
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
bpath = commands.PROC_BREAK_PATTERN.format(procnum=proc, breaknum=bpid)
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("Breakpoint {} deleted".format(bpid)):
|
||||
trace.proxy_object_path(bpath).remove(tree=True)
|
||||
|
||||
|
||||
def on_breakpoint_hit(*args):
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def on_exception(*args):
|
||||
trace = commands.STATE.trace
|
||||
if trace is None:
|
||||
return
|
||||
with commands.STATE.client.batch():
|
||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
||||
commands.put_processes()
|
||||
return DbgEng.DEBUG_STATUS_GO
|
||||
|
||||
|
||||
def install_hooks():
|
||||
if HOOK_STATE.installed:
|
||||
return
|
||||
HOOK_STATE.installed = True
|
||||
|
||||
events = dbg().events
|
||||
|
||||
events.engine_state(handler=on_state_changed)
|
||||
events.debuggee_state(handler=on_debuggee_changed)
|
||||
events.session_status(handler=on_session_status_changed)
|
||||
events.symbol_state(handler=on_symbol_state_changed)
|
||||
events.system_error(handler=on_system_error)
|
||||
|
||||
events.create_process(handler=on_new_process)
|
||||
events.exit_process(handler=on_process_deleted)
|
||||
events.create_thread(handler=on_threads_changed)
|
||||
events.exit_thread(handler=on_threads_changed)
|
||||
events.module_load(handler=on_modules_changed)
|
||||
events.unload_module(handler=on_modules_changed)
|
||||
|
||||
#events.breakpoint(handler=on_breakpoint_hit)
|
||||
#events.exception(handler=on_exception)
|
||||
|
||||
|
||||
def remove_hooks():
|
||||
if not HOOK_STATE.installed:
|
||||
return
|
||||
HOOK_STATE.installed = False
|
||||
dbg()._reset_callbacks()
|
||||
|
||||
|
||||
def enable_current_process():
|
||||
proc = util.selected_process()
|
||||
PROC_STATE[proc] = ProcessState()
|
||||
|
||||
|
||||
def disable_current_process():
|
||||
proc = util.selected_process()
|
||||
if proc in PROC_STATE:
|
||||
# Silently ignore already disabled
|
||||
del PROC_STATE[proc]
|
||||
|
||||
def dbg():
|
||||
return util.get_debugger()
|
|
@ -0,0 +1,522 @@
|
|||
## ###
|
||||
# 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, ThreadPoolExecutor
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ghidratrace import sch
|
||||
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
||||
|
||||
from pybag import pydbg
|
||||
from pybag.dbgeng import core as DbgEng
|
||||
|
||||
from . import util, commands
|
||||
from contextlib import redirect_stdout
|
||||
from io import StringIO
|
||||
|
||||
|
||||
REGISTRY = MethodRegistry(ThreadPoolExecutor(max_workers=1))
|
||||
|
||||
|
||||
def extre(base, ext):
|
||||
return re.compile(base.pattern + ext)
|
||||
|
||||
|
||||
AVAILABLE_PATTERN = re.compile('Available\[(?P<pid>\\d*)\]')
|
||||
WATCHPOINT_PATTERN = re.compile('Watchpoints\[(?P<watchnum>\\d*)\]')
|
||||
BREAKPOINT_PATTERN = re.compile('Breakpoints\[(?P<breaknum>\\d*)\]')
|
||||
BREAK_LOC_PATTERN = extre(BREAKPOINT_PATTERN, '\[(?P<locnum>\\d*)\]')
|
||||
PROCESS_PATTERN = re.compile('Processes\[(?P<procnum>\\d*)\]')
|
||||
PROC_BREAKS_PATTERN = extre(PROCESS_PATTERN, '\.Breakpoints')
|
||||
PROC_BREAKBPT_PATTERN = extre(PROC_BREAKS_PATTERN, '\[(?P<breaknum>\\d*)\]')
|
||||
ENV_PATTERN = extre(PROCESS_PATTERN, '\.Environment')
|
||||
THREADS_PATTERN = extre(PROCESS_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_PATTERN0 = extre(THREAD_PATTERN, '.Registers')
|
||||
REGS_PATTERN = extre(FRAME_PATTERN, '.Registers')
|
||||
MEMORY_PATTERN = extre(PROCESS_PATTERN, '\.Memory')
|
||||
MODULES_PATTERN = extre(PROCESS_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_proc_by_num(id):
|
||||
if id != util.selected_process():
|
||||
util.select_process(id)
|
||||
return util.selected_process()
|
||||
|
||||
|
||||
def find_proc_by_pattern(object, pattern, err_msg):
|
||||
mat = pattern.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not {err_msg}")
|
||||
procnum = int(mat['procnum'])
|
||||
return find_proc_by_num(procnum)
|
||||
|
||||
|
||||
def find_proc_by_obj(object):
|
||||
return find_proc_by_pattern(object, PROCESS_PATTERN, "an Process")
|
||||
|
||||
|
||||
def find_proc_by_procbreak_obj(object):
|
||||
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
|
||||
"a BreakpointLocationContainer")
|
||||
|
||||
def find_proc_by_procwatch_obj(object):
|
||||
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
|
||||
"a WatchpointContainer")
|
||||
|
||||
|
||||
def find_proc_by_env_obj(object):
|
||||
return find_proc_by_pattern(object, ENV_PATTERN, "an Environment")
|
||||
|
||||
|
||||
def find_proc_by_threads_obj(object):
|
||||
return find_proc_by_pattern(object, THREADS_PATTERN, "a ThreadContainer")
|
||||
|
||||
|
||||
def find_proc_by_mem_obj(object):
|
||||
return find_proc_by_pattern(object, MEMORY_PATTERN, "a Memory")
|
||||
|
||||
|
||||
def find_proc_by_modules_obj(object):
|
||||
return find_proc_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")
|
||||
|
||||
|
||||
def find_thread_by_num(id):
|
||||
if id != util.selected_thread():
|
||||
util.select_thread(id)
|
||||
return util.selected_thread()
|
||||
|
||||
|
||||
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}")
|
||||
pnum = int(mat['procnum'])
|
||||
tnum = int(mat['tnum'])
|
||||
find_proc_by_num(pnum)
|
||||
return find_thread_by_num(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_thread_by_regs_obj(object):
|
||||
return find_thread_by_pattern(REGS_PATTERN0, object, "a RegisterValueContainer")
|
||||
|
||||
|
||||
def find_frame_by_level(level):
|
||||
return dbg().backtrace_list()[level]
|
||||
|
||||
|
||||
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}")
|
||||
pnum = int(mat['procnum'])
|
||||
tnum = int(mat['tnum'])
|
||||
level = int(mat['level'])
|
||||
find_proc_by_num(pnum)
|
||||
find_thread_by_num(tnum)
|
||||
return find_frame_by_level(level)
|
||||
|
||||
|
||||
def find_frame_by_obj(object):
|
||||
return find_frame_by_pattern(FRAME_PATTERN, object, "a StackFrame")
|
||||
|
||||
|
||||
def find_bpt_by_number(breaknum):
|
||||
try:
|
||||
bp = dbg()._control.GetBreakpointById(breaknum)
|
||||
return bp
|
||||
except exception.E_NOINTERFACE_Error:
|
||||
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(PROC_BREAKBPT_PATTERN, object, "a BreakpointSpec")
|
||||
|
||||
|
||||
shared_globals = dict()
|
||||
|
||||
@REGISTRY.method
|
||||
def execute(cmd: str, to_string: bool=False):
|
||||
"""Execute a CLI command."""
|
||||
#print("***{}***".format(cmd))
|
||||
#sys.stderr.flush()
|
||||
#sys.stdout.flush()
|
||||
if to_string:
|
||||
data = StringIO()
|
||||
with redirect_stdout(data):
|
||||
exec("{}".format(cmd), shared_globals)
|
||||
return data.getvalue()
|
||||
else:
|
||||
exec("{}".format(cmd), shared_globals)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def evaluate(expr: str):
|
||||
"""Execute a CLI command."""
|
||||
return str(eval("{}".format(expr), shared_globals))
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_available(node: sch.Schema('AvailableContainer')):
|
||||
"""List processes on pydbg's host system."""
|
||||
with commands.open_tracked_tx('Refresh Available'):
|
||||
commands.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
|
||||
process).
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Breakpoints'):
|
||||
commands.ghidra_trace_put_breakpoints()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_processes(node: sch.Schema('ProcessContainer')):
|
||||
"""Refresh the list of processes."""
|
||||
with commands.open_tracked_tx('Refresh Processes'):
|
||||
commands.ghidra_trace_put_threads()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_proc_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
||||
"""
|
||||
Refresh the breakpoint locations for the process.
|
||||
|
||||
In the course of refreshing the locations, the breakpoint list will also be
|
||||
refreshed.
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Breakpoint Locations'):
|
||||
commands.ghidra_trace_put_breakpoints()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_environment(node: sch.Schema('Environment')):
|
||||
"""Refresh the environment descriptors (arch, os, endian)."""
|
||||
with commands.open_tracked_tx('Refresh Environment'):
|
||||
commands.ghidra_trace_put_environment()
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
||||
"""Refresh the list of threads in the process."""
|
||||
with commands.open_tracked_tx('Refresh Threads'):
|
||||
commands.ghidra_trace_put_threads()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_stack(node: sch.Schema('Stack')):
|
||||
"""Refresh the backtrace for the thread."""
|
||||
tnum = find_thread_by_stack_obj(node)
|
||||
with commands.open_tracked_tx('Refresh Stack'):
|
||||
commands.ghidra_trace_put_frames()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
||||
"""Refresh the register values for the frame."""
|
||||
tnum = find_thread_by_regs_obj(node)
|
||||
with commands.open_tracked_tx('Refresh Registers'):
|
||||
commands.ghidra_trace_putreg()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_mappings(node: sch.Schema('Memory')):
|
||||
"""Refresh the list of memory regions for the process."""
|
||||
with commands.open_tracked_tx('Refresh Memory Regions'):
|
||||
commands.ghidra_trace_put_regions()
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
def refresh_modules(node: sch.Schema('ModuleContainer')):
|
||||
"""
|
||||
Refresh the modules and sections list for the process.
|
||||
|
||||
This will refresh the sections for all modules, not just the selected one.
|
||||
"""
|
||||
with commands.open_tracked_tx('Refresh Modules'):
|
||||
commands.ghidra_trace_put_modules()
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_process(process: sch.Schema('Process')):
|
||||
"""Switch to the process."""
|
||||
find_proc_by_obj(process)
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_thread(thread: sch.Schema('Thread')):
|
||||
"""Switch to the thread."""
|
||||
find_thread_by_obj(thread)
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_frame(frame: sch.Schema('StackFrame')):
|
||||
"""Select the frame."""
|
||||
find_frame_by_obj(frame)
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
def remove_process(process: sch.Schema('Process')):
|
||||
"""Remove the process."""
|
||||
find_proc_by_obj(process)
|
||||
dbg().detach()
|
||||
|
||||
|
||||
@REGISTRY.method(action='connect')
|
||||
def target(process: sch.Schema('Process'), spec: str):
|
||||
"""Connect to a target machine or process."""
|
||||
find_proc_by_obj(process)
|
||||
dbg().attach(spec)
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
def attach_obj(target: sch.Schema('Attachable')):
|
||||
"""Attach the process to the given target."""
|
||||
pid = find_availpid_by_obj(target)
|
||||
dbg().attach(pid)
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
def attach_pid(pid: int):
|
||||
"""Attach the process to the given target."""
|
||||
dbg().attach(pid)
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
def attach_name(process: sch.Schema('Process'), name: str):
|
||||
"""Attach the process to the given target."""
|
||||
dbg().atach(name)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def detach(process: sch.Schema('Process')):
|
||||
"""Detach the process's target."""
|
||||
dbg().detach()
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
def launch_loader(
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
"""
|
||||
Start a native process with the given command line, stopping at the ntdll initial breakpoint.
|
||||
"""
|
||||
command = file
|
||||
if args != None:
|
||||
command += " "+args
|
||||
commands.ghidra_trace_create(command=file, start_trace=False)
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
def launch(
|
||||
timeout: ParamDesc(int, display='Timeout'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
"""
|
||||
Run a native process with the given command line.
|
||||
"""
|
||||
command = file
|
||||
if args != None:
|
||||
command += " "+args
|
||||
commands.ghidra_trace_create(command, initial_break=False, timeout=timeout, start_trace=False)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def kill(process: sch.Schema('Process')):
|
||||
"""Kill execution of the process."""
|
||||
dbg().terminate()
|
||||
|
||||
|
||||
@REGISTRY.method(name='continue', action='resume')
|
||||
def _continue(process: sch.Schema('Process')):
|
||||
"""Continue execution of the process."""
|
||||
dbg().go()
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def interrupt():
|
||||
"""Interrupt the execution of the debugged program."""
|
||||
dbg()._control.SetInterrupt(DbgEng.DEBUG_INTERRUPT_ACTIVE)
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_into')
|
||||
def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||
"""Step on instruction exactly."""
|
||||
find_thread_by_obj(thread)
|
||||
dbg().stepi(n)
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_over')
|
||||
def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||
"""Step one instruction, but proceed through subroutine calls."""
|
||||
find_thread_by_obj(thread)
|
||||
dbg().stepo(n)
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_out')
|
||||
def step_out(thread: sch.Schema('Thread')):
|
||||
"""Execute until the current stack frame returns."""
|
||||
find_thread_by_obj(thread)
|
||||
dbg().stepout()
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_to')
|
||||
def step_to(thread: sch.Schema('Thread'), address: Address, max=None):
|
||||
"""Continue execution up to the given address."""
|
||||
find_thread_by_obj(thread)
|
||||
return dbg().stepto(address.offset, max)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_sw_execute')
|
||||
def break_address(process: sch.Schema('Process'), address: Address):
|
||||
"""Set a breakpoint."""
|
||||
find_proc_by_obj(process)
|
||||
dbg().bp(expr=address.offset)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_sw_execute')
|
||||
def break_expression(expression: str):
|
||||
"""Set a breakpoint."""
|
||||
# TODO: Escape?
|
||||
dbg().bp(expr=expression)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_hw_execute')
|
||||
def break_hw_address(process: sch.Schema('Process'), address: Address):
|
||||
"""Set a hardware-assisted breakpoint."""
|
||||
find_proc_by_obj(process)
|
||||
dbg().ba(expr=address.offset)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_hw_execute')
|
||||
def break_hw_expression(expression: str):
|
||||
"""Set a hardware-assisted breakpoint."""
|
||||
dbg().ba(expr=expression)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_read')
|
||||
def break_read_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
"""Set a read watchpoint."""
|
||||
find_proc_by_obj(process)
|
||||
dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_READ)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_read')
|
||||
def break_read_expression(expression: str):
|
||||
"""Set a read watchpoint."""
|
||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_write')
|
||||
def break_write_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
"""Set a watchpoint."""
|
||||
find_proc_by_obj(process)
|
||||
dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_WRITE)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_write')
|
||||
def break_write_expression(expression: str):
|
||||
"""Set a watchpoint."""
|
||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_WRITE)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_access')
|
||||
def break_access_range(process: sch.Schema('Process'), range: AddressRange):
|
||||
"""Set an access watchpoint."""
|
||||
find_proc_by_obj(process)
|
||||
dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_READ|DbgEng.DEBUG_BREAK_WRITE)
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_access')
|
||||
def break_access_expression(expression: str):
|
||||
"""Set an access watchpoint."""
|
||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ|DbgEng.DEBUG_BREAK_WRITE)
|
||||
|
||||
|
||||
@REGISTRY.method(action='toggle')
|
||||
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
||||
"""Toggle a breakpoint."""
|
||||
bpt = find_bpt_by_obj(breakpoint)
|
||||
if enabled:
|
||||
dbg().be(bpt.GetId())
|
||||
else:
|
||||
dbg().bd(bpt.GetId())
|
||||
|
||||
|
||||
@REGISTRY.method(action='delete')
|
||||
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
||||
"""Delete a breakpoint."""
|
||||
bpt = find_bpt_by_obj(breakpoint)
|
||||
dbg().cmd("bc {}".format(bpt.GetId()))
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def read_mem(process: sch.Schema('Process'), range: AddressRange):
|
||||
"""Read memory."""
|
||||
nproc = find_proc_by_obj(process)
|
||||
offset_start = process.trace.memory_mapper.map_back(
|
||||
nproc, Address(range.space, range.min))
|
||||
with commands.open_tracked_tx('Read Memory'):
|
||||
dbg().read(range.min, range.length())
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
|
||||
"""Write memory."""
|
||||
nproc = find_proc_by_obj(process)
|
||||
offset = process.trace.memory_mapper.map_back(nproc, address)
|
||||
dbg().write(offset, data)
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
def write_reg(frame: sch.Schema('Frame'), name: str, value: bytes):
|
||||
"""Write a register."""
|
||||
util.select_frame()
|
||||
nproc = pydbg.selected_process()
|
||||
dbg().reg._set_register(name, value)
|
||||
|
||||
|
||||
def dbg():
|
||||
return util.get_debugger()
|
|
@ -0,0 +1,434 @@
|
|||
<context>
|
||||
<schema name="Session" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Access" />
|
||||
<interface name="Attacher" />
|
||||
<interface name="Interpreter" />
|
||||
<interface name="Interruptible" />
|
||||
<interface name="Launcher" />
|
||||
<interface name="ActiveScope" />
|
||||
<interface name="EventScope" />
|
||||
<interface name="FocusScope" />
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Processes" schema="ProcessContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Available" schema="AvailableContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_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="BreakpointLocationContainer" />
|
||||
<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="ProcessContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Configurable" />
|
||||
<element schema="Process" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_base" schema="INT" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="BreakpointSpec" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="BreakpointSpec" />
|
||||
<interface name="BreakpointLocation" />
|
||||
<interface name="Deletable" />
|
||||
<interface name="Togglable" />
|
||||
<element schema="VOID" />
|
||||
<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="_spec" schema="BreakpointSpec" />
|
||||
<attribute name="_range" schema="RANGE" 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="Process" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Process" />
|
||||
<interface name="Aggregate" />
|
||||
<interface name="ExecutionStateful" />
|
||||
<interface name="Attacher" />
|
||||
<interface name="Deletable" />
|
||||
<interface name="Detachable" />
|
||||
<interface name="Killable" />
|
||||
<interface name="Launcher" />
|
||||
<interface name="Resumable" />
|
||||
<interface name="Steppable" />
|
||||
<interface name="Interruptible" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Breakpoints" schema="BreakpointContainer" 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="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_tid" schema="LONG" hidden="yes" />
|
||||
<attribute name="_state" schema="EXECUTION_STATE" required="yes" hidden="yes" />
|
||||
<attribute name="_supported_step_kinds" schema="SET_STEP_KIND" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="Advance" schema="Method" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Module" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Module" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="Sections" schema="SectionContainer" required="yes" fixed="yes" />
|
||||
<attribute name="Symbols" schema="SymbolContainer" required="yes" fixed="yes" />
|
||||
<attribute name="range" schema="RANGE" />
|
||||
<attribute name="module name" schema="STRING" />
|
||||
<attribute name="_module_name" schema="STRING" required="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="MemoryRegion" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="MemoryRegion" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_offset" schema="LONG" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_objfile" schema="STRING" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_readable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_writable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_executable" schema="BOOL" required="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" required="yes" hidden="yes" />
|
||||
<attribute name="_memory" schema="Memory" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="SectionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="SectionContainer" />
|
||||
<element schema="Section" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Stack" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Stack" />
|
||||
<element schema="StackFrame" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="SymbolContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="SymbolNamespace" />
|
||||
<element schema="Symbol" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Symbol" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Symbol" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_size" schema="LONG" fixed="yes" hidden="yes" />
|
||||
<attribute name="_namespace" schema="SymbolContainer" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_data_type" schema="DATA_TYPE" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ADDRESS" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute name="_bpt" schema="STRING" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="StackFrame" />
|
||||
<interface name="Aggregate" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_function" schema="STRING" hidden="yes" />
|
||||
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
|
||||
<attribute name="_pc" schema="ADDRESS" required="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="Section" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Section" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="range" schema="RANGE" />
|
||||
<attribute name="_module" schema="Module" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_range" schema="RANGE" required="yes" fixed="yes" />
|
||||
<attribute name="_offset" schema="INT" required="no" fixed="yes" />
|
||||
<attribute name="_objfile" schema="STRING" required="no" fixed="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterValueContainer" canonical="yes" elementResync="ONCE" attributeResync="ONCE">
|
||||
<interface name="RegisterContainer" />
|
||||
<interface name="RegisterBank" />
|
||||
<element schema="RegisterValue" />
|
||||
<attribute name="General Purpose Registers" schema="RegisterBank" />
|
||||
<attribute name="Floating Point Registers" schema="RegisterBank" />
|
||||
<attribute name="Advanced Vector Extensions" schema="RegisterBank" />
|
||||
<attribute name="Memory Protection Extensions" schema="RegisterBank" />
|
||||
<attribute name="_descriptions" schema="RegisterValueContainer" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterBank" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
|
||||
<interface name="RegisterBank" />
|
||||
<element schema="RegisterValue" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
<schema name="RegisterValue" elementResync="NEVER" attributeResync="NEVER">
|
||||
<interface name="Register" />
|
||||
<element schema="VOID" />
|
||||
<attribute name="_container" schema="OBJECT" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_length" schema="INT" required="yes" fixed="yes" hidden="yes" />
|
||||
<attribute name="_value" schema="ANY" hidden="yes" />
|
||||
<attribute name="_type" schema="STRING" hidden="yes" />
|
||||
<attribute name="_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_short_display" schema="STRING" hidden="yes" />
|
||||
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
|
||||
<attribute name="_order" schema="INT" hidden="yes" />
|
||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||
<attribute schema="VOID" />
|
||||
</schema>
|
||||
</context>
|
|
@ -0,0 +1,260 @@
|
|||
## ###
|
||||
# IP: GHIDRA
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
##
|
||||
from collections import namedtuple
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ctypes import *
|
||||
from pybag import pydbg
|
||||
from pybag.dbgeng import core as DbgEng
|
||||
from pybag.dbgeng import exception
|
||||
from pybag.dbgeng import util as DbgUtil
|
||||
|
||||
base = pydbg.DebuggerBase()
|
||||
DbgVersion = namedtuple('DbgVersion', ['full', 'major', 'minor'])
|
||||
|
||||
|
||||
def _compute_pydbg_ver():
|
||||
blurb = "" #base._control.GetActualProcessorType()
|
||||
full = ""
|
||||
major = 0
|
||||
minor = 0
|
||||
return DbgVersion(full, int(major), int(minor))
|
||||
|
||||
|
||||
DBG_VERSION = _compute_pydbg_ver()
|
||||
|
||||
|
||||
def get_debugger():
|
||||
return base
|
||||
|
||||
def get_target():
|
||||
return 0 #get_debugger()._systems.GetCurrentSystemId()
|
||||
|
||||
def get_inst(addr):
|
||||
dbg = get_debugger()
|
||||
ins = DbgUtil.disassemble_instruction(dbg.bitness(), addr, dbg.read(addr, 15))
|
||||
return str(ins)
|
||||
|
||||
def get_inst_sz(addr):
|
||||
dbg = get_debugger()
|
||||
ins = DbgUtil.disassemble_instruction(dbg.bitness(), addr, dbg.read(addr, 15))
|
||||
return str(ins.size)
|
||||
|
||||
def get_breakpoints():
|
||||
ids = [bpid for bpid in get_debugger().breakpoints]
|
||||
offset_set = []
|
||||
expr_set = []
|
||||
prot_set = []
|
||||
width_set = []
|
||||
stat_set = []
|
||||
for bpid in ids:
|
||||
try:
|
||||
bp = get_debugger()._control.GetBreakpointById(bpid)
|
||||
except exception.E_NOINTERFACE_Error:
|
||||
continue
|
||||
|
||||
if bp.GetFlags() & DbgEng.DEBUG_BREAKPOINT_DEFERRED:
|
||||
offset = "[Deferred]"
|
||||
expr = bp.GetOffsetExpression()
|
||||
else:
|
||||
offset = "%016x" % bp.GetOffset()
|
||||
expr = get_debugger().get_name_by_offset(bp.GetOffset())
|
||||
|
||||
if bp.GetType()[0] == DbgEng.DEBUG_BREAKPOINT_DATA:
|
||||
width, prot = bp.GetDataParameters()
|
||||
width = ' sz={}'.format(str(width))
|
||||
prot = {4: 'type=x', 3: 'type=rw', 2: 'type=w', 1: 'type=r'}[prot]
|
||||
else:
|
||||
width = ''
|
||||
prot = ''
|
||||
|
||||
if bp.GetFlags() & DbgEng.DEBUG_BREAKPOINT_ENABLED:
|
||||
status = 'enabled'
|
||||
else:
|
||||
status = 'disabled'
|
||||
|
||||
offset_set.append(offset)
|
||||
expr_set.append(expr)
|
||||
prot_set.append(prot)
|
||||
width_set.append(width)
|
||||
stat_set.append(status)
|
||||
return zip(offset_set, expr_set, prot_set, width_set, stat_set)
|
||||
|
||||
def selected_process():
|
||||
try:
|
||||
return get_debugger()._systems.GetCurrentProcessId()
|
||||
#return current_process
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def selected_thread():
|
||||
try:
|
||||
return get_debugger()._systems.GetCurrentThreadId()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def selected_frame():
|
||||
return 0 #selected_thread().GetSelectedFrame()
|
||||
|
||||
def select_process(id: int):
|
||||
return get_debugger()._systems.SetCurrentProcessId(id)
|
||||
|
||||
def select_thread(id: int):
|
||||
return get_debugger()._systems.SetCurrentThreadId(id)
|
||||
|
||||
def select_frame(id: int):
|
||||
#TODO: this needs to be fixed
|
||||
return id
|
||||
|
||||
def parse_and_eval(expr):
|
||||
regs = get_debugger().reg
|
||||
if expr == "$pc":
|
||||
return regs.get_pc()
|
||||
if expr == "$sp":
|
||||
return regs.get_sp()
|
||||
return get_eval(expr)
|
||||
|
||||
def get_eval(expr, type=None):
|
||||
ctrl = get_debugger()._control._ctrl
|
||||
ctrl.SetExpressionSyntax(1)
|
||||
value = DbgEng._DEBUG_VALUE()
|
||||
index = c_ulong()
|
||||
if type == None:
|
||||
type = DbgEng.DEBUG_VALUE_INT64
|
||||
hr = ctrl.Evaluate(Expression="{}".format(expr).encode(),DesiredType=type,Value=byref(value),RemainderIndex=byref(index))
|
||||
exception.check_err(hr)
|
||||
if type == DbgEng.DEBUG_VALUE_INT8:
|
||||
return value.u.I8
|
||||
if type == DbgEng.DEBUG_VALUE_INT16:
|
||||
return value.u.I16
|
||||
if type == DbgEng.DEBUG_VALUE_INT32:
|
||||
return value.u.I32
|
||||
if type == DbgEng.DEBUG_VALUE_INT64:
|
||||
return value.u.I64.I64
|
||||
if type == DbgEng.DEBUG_VALUE_FLOAT32:
|
||||
return value.u.F32
|
||||
if type == DbgEng.DEBUG_VALUE_FLOAT64:
|
||||
return value.u.F64
|
||||
if type == DbgEng.DEBUG_VALUE_FLOAT80:
|
||||
return value.u.F80Bytes
|
||||
if type == DbgEng.DEBUG_VALUE_FLOAT82:
|
||||
return value.u.F82Bytes
|
||||
if type == DbgEng.DEBUG_VALUE_FLOAT128:
|
||||
return value.u.F128Bytes
|
||||
|
||||
def GetProcessIdsByIndex(count=0):
|
||||
if count == 0:
|
||||
try :
|
||||
count = get_debugger()._systems.GetNumberProcesses()
|
||||
except Exception:
|
||||
count = 0
|
||||
ids = (c_ulong * count)()
|
||||
sysids = (c_ulong * count)()
|
||||
if count != 0:
|
||||
hr = get_debugger()._systems._sys.GetProcessIdsByIndex(0, count, ids, sysids)
|
||||
exception.check_err(hr)
|
||||
return (tuple(ids), tuple(sysids))
|
||||
|
||||
|
||||
def GetCurrentProcessExecutableName():
|
||||
dbg = get_debugger()
|
||||
size = c_ulong()
|
||||
exesize = c_ulong()
|
||||
hr = dbg._systems._sys.GetCurrentProcessExecutableName(None, size, byref(exesize))
|
||||
exception.check_err(hr)
|
||||
buffer = create_string_buffer(exesize.value)
|
||||
size = exesize
|
||||
hr = dbg._systems._sys.GetCurrentProcessExecutableName(buffer, size, None)
|
||||
exception.check_err(hr)
|
||||
buffer = buffer[:size.value]
|
||||
buffer = buffer.rstrip(b'\x00')
|
||||
return buffer
|
||||
|
||||
|
||||
def GetCurrentProcessPeb():
|
||||
dbg = get_debugger()
|
||||
offset = c_ulonglong()
|
||||
hr = dbg._systems._sys.GetCurrentProcessPeb(byref(offset))
|
||||
exception.check_err(hr)
|
||||
return offset.value
|
||||
|
||||
|
||||
def GetExitCode():
|
||||
exit_code = c_ulong()
|
||||
hr = get_debugger()._client._cli.GetExitCode(byref(exit_code))
|
||||
return exit_code.value
|
||||
|
||||
|
||||
def process_list(running=False):
|
||||
"""process_list() -> list of all processes"""
|
||||
dbg = get_debugger()
|
||||
ids, sysids = GetProcessIdsByIndex()
|
||||
pebs = []
|
||||
names = []
|
||||
|
||||
try :
|
||||
curid = dbg._systems.GetCurrentProcessId()
|
||||
if running == False:
|
||||
for id in ids:
|
||||
dbg._systems.SetCurrentProcessId(id)
|
||||
names.append(GetCurrentProcessExecutableName())
|
||||
pebs.append(GetCurrentProcessPeb())
|
||||
if running == False:
|
||||
dbg._systems.SetCurrentProcessId(curid)
|
||||
return zip(sysids, names, pebs)
|
||||
except Exception:
|
||||
pass
|
||||
return zip(sysids)
|
||||
|
||||
def thread_list(running=False):
|
||||
"""thread_list() -> list of all threads"""
|
||||
dbg = get_debugger()
|
||||
try :
|
||||
ids, sysids = dbg._systems.GetThreadIdsByIndex()
|
||||
except Exception:
|
||||
return zip([])
|
||||
tebs = []
|
||||
syms = []
|
||||
|
||||
curid = dbg._systems.GetCurrentThreadId()
|
||||
if running == False:
|
||||
for id in ids:
|
||||
dbg._systems.SetCurrentThreadId(id)
|
||||
tebs.append(dbg._systems.GetCurrentThreadTeb())
|
||||
addr = dbg.reg.get_pc()
|
||||
syms.append(dbg.get_name_by_offset(addr))
|
||||
if running == False:
|
||||
dbg._systems.SetCurrentThreadId(curid)
|
||||
return zip(sysids, tebs, syms)
|
||||
return zip(sysids)
|
||||
|
||||
conv_map = {}
|
||||
|
||||
def get_convenience_variable(id):
|
||||
#val = get_target().GetEnvironment().Get(id)
|
||||
if id not in conv_map:
|
||||
return "auto"
|
||||
val = conv_map[id]
|
||||
if val is None:
|
||||
return "auto"
|
||||
return val
|
||||
|
||||
def set_convenience_variable(id, value):
|
||||
#env = get_target().GetEnvironment()
|
||||
#return env.Set(id, value, True)
|
||||
conv_map[id] = value
|
|
@ -249,7 +249,7 @@ public class TraceRmiHandler {
|
|||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return socket.isClosed();
|
||||
return socket.isClosed() && closed.isDone();
|
||||
}
|
||||
|
||||
public void waitClosed() throws InterruptedException, ExecutionException {
|
||||
|
|
|
@ -52,6 +52,12 @@ public class DummyProc implements AutoCloseable {
|
|||
if (platformExe.exists() && platformExe.getFile(false).canExecute()) {
|
||||
return platformExe.getAbsolutePath();
|
||||
}
|
||||
platformExe = new ResourceFile(modRoot,
|
||||
"build/exe/" + cmd + "/" + Platform.CURRENT_PLATFORM.getDirectoryName() + "/" +
|
||||
cmd + ".exe");
|
||||
if (platformExe.exists() && platformExe.getFile(false).canExecute()) {
|
||||
return platformExe.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
|
|
@ -82,4 +82,5 @@ integrationTest {
|
|||
dependsOn { project(':Debugger-rmi-trace').assemblePyPackage }
|
||||
dependsOn { project(':Debugger-agent-gdb').assemblePyPackage }
|
||||
dependsOn { project(':Debugger-agent-lldb').assemblePyPackage }
|
||||
dependsOn { project(':Debugger-agent-dbgeng').assemblePyPackage }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,424 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.rmi;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.junit.Before;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteAsyncResult;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiAcceptor;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin;
|
||||
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||
import ghidra.app.services.TraceRmiService;
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.framework.TestApplicationUtils;
|
||||
import ghidra.framework.main.ApplicationLevelOnlyPlugin;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.PluginsConfiguration;
|
||||
import ghidra.framework.plugintool.util.PluginDescription;
|
||||
import ghidra.framework.plugintool.util.PluginException;
|
||||
import ghidra.framework.plugintool.util.PluginPackage;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRangeImpl;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.NumericUtilities;
|
||||
|
||||
public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
/**
|
||||
* Some features have to be disabled to avoid permissions issues in the test container. Namely,
|
||||
* don't try to disable ASLR.
|
||||
*/
|
||||
public static final String PREAMBLE = """
|
||||
from ghidradbg.commands import *
|
||||
""";
|
||||
// Connecting should be the first thing the script does, so use a tight timeout.
|
||||
protected static final int CONNECT_TIMEOUT_MS = 3000;
|
||||
protected static final int TIMEOUT_SECONDS = 300;
|
||||
protected static final int QUIT_TIMEOUT_MS = 1000;
|
||||
|
||||
protected TraceRmiService traceRmi;
|
||||
private Path pythonPath;
|
||||
private Path outFile;
|
||||
private Path errFile;
|
||||
|
||||
//@BeforeClass
|
||||
public static void setupPython() throws Throwable {
|
||||
new ProcessBuilder("gradle", "Debugger-agent-dbgeng:assemblePyPackage")
|
||||
.directory(TestApplicationUtils.getInstallationDirectory())
|
||||
.inheritIO()
|
||||
.start()
|
||||
.waitFor();
|
||||
}
|
||||
|
||||
protected void setPythonPath(ProcessBuilder pb) throws IOException {
|
||||
String sep =
|
||||
OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS ? ";" : ":";
|
||||
String rmiPyPkg = Application.getModuleSubDirectory("Debugger-rmi-trace",
|
||||
"build/pypkg/src").getAbsolutePath();
|
||||
String gdbPyPkg = Application.getModuleSubDirectory("Debugger-agent-dbgeng",
|
||||
"build/pypkg/src").getAbsolutePath();
|
||||
String add = rmiPyPkg + sep + gdbPyPkg;
|
||||
pb.environment().compute("PYTHONPATH", (k, v) -> v == null ? add : (v + sep + add));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setupTraceRmi() throws Throwable {
|
||||
traceRmi = addPlugin(tool, TraceRmiPlugin.class);
|
||||
|
||||
try {
|
||||
pythonPath = Paths.get(DummyProc.which("python3"));
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
pythonPath = Paths.get(DummyProc.which("python"));
|
||||
}
|
||||
outFile = Files.createTempFile("pydbgout", null);
|
||||
errFile = Files.createTempFile("pydbgerr", null);
|
||||
}
|
||||
|
||||
protected void addAllDebuggerPlugins() throws PluginException {
|
||||
PluginsConfiguration plugConf = new PluginsConfiguration() {
|
||||
@Override
|
||||
protected boolean accepts(Class<? extends Plugin> pluginClass) {
|
||||
return !ApplicationLevelOnlyPlugin.class.isAssignableFrom(pluginClass);
|
||||
}
|
||||
};
|
||||
|
||||
for (PluginDescription pd : plugConf
|
||||
.getPluginDescriptions(PluginPackage.getPluginPackage("Debugger"))) {
|
||||
addPlugin(tool, pd.getPluginClass());
|
||||
}
|
||||
}
|
||||
|
||||
protected static String addrToStringForPython(InetAddress address) {
|
||||
if (address.isAnyLocalAddress()) {
|
||||
return "127.0.0.1"; // Can't connect to 0.0.0.0 as such. Choose localhost.
|
||||
}
|
||||
return address.getHostAddress();
|
||||
}
|
||||
|
||||
protected static String sockToStringForPython(SocketAddress address) {
|
||||
if (address instanceof InetSocketAddress tcp) {
|
||||
return addrToStringForPython(tcp.getAddress()) + ":" + tcp.getPort();
|
||||
}
|
||||
throw new AssertionError("Unhandled address type " + address);
|
||||
}
|
||||
|
||||
protected record PythonResult(boolean timedOut, int exitCode, String stdout, String stderr) {
|
||||
protected String handle() {
|
||||
if (stderr.contains("Error") || (0 != exitCode && 1 != exitCode && 143 != exitCode)) {
|
||||
throw new PythonError(exitCode, stdout, stderr);
|
||||
}
|
||||
return stdout;
|
||||
}
|
||||
}
|
||||
|
||||
protected record ExecInPython(Process python, CompletableFuture<PythonResult> future) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource") // Do not close stdin
|
||||
protected ExecInPython execInPython(String script) throws IOException {
|
||||
ProcessBuilder pb = new ProcessBuilder(pythonPath.toString(), "-i");
|
||||
setPythonPath(pb);
|
||||
|
||||
// If commands come from file, Python will quit after EOF.
|
||||
Msg.info(this, "outFile: " + outFile);
|
||||
Msg.info(this, "errFile: " + errFile);
|
||||
|
||||
//pb.inheritIO();
|
||||
pb.redirectInput(ProcessBuilder.Redirect.PIPE);
|
||||
pb.redirectOutput(outFile.toFile());
|
||||
pb.redirectError(errFile.toFile());
|
||||
Process pyproc = pb.start();
|
||||
OutputStream stdin = pyproc.getOutputStream();
|
||||
stdin.write(script.getBytes());
|
||||
stdin.flush();
|
||||
//stdin.close();
|
||||
return new ExecInPython(pyproc, CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
if (!pyproc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
|
||||
Msg.error(this, "Timed out waiting for Python");
|
||||
pyproc.destroyForcibly();
|
||||
pyproc.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
return new PythonResult(true, -1, Files.readString(outFile),
|
||||
Files.readString(errFile));
|
||||
}
|
||||
Msg.info(this, "Python exited with code " + pyproc.exitValue());
|
||||
return new PythonResult(false, pyproc.exitValue(), Files.readString(outFile),
|
||||
Files.readString(errFile));
|
||||
}
|
||||
catch (Exception e) {
|
||||
return ExceptionUtils.rethrow(e);
|
||||
}
|
||||
finally {
|
||||
pyproc.destroyForcibly();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static class PythonError extends RuntimeException {
|
||||
public final int exitCode;
|
||||
public final String stdout;
|
||||
public final String stderr;
|
||||
|
||||
public PythonError(int exitCode, String stdout, String stderr) {
|
||||
super("""
|
||||
exitCode=%d:
|
||||
----stdout----
|
||||
%s
|
||||
----stderr----
|
||||
%s
|
||||
""".formatted(exitCode, stdout, stderr));
|
||||
this.exitCode = exitCode;
|
||||
this.stdout = stdout;
|
||||
this.stderr = stderr;
|
||||
}
|
||||
}
|
||||
|
||||
protected String runThrowError(String script) throws Exception {
|
||||
CompletableFuture<PythonResult> result = execInPython(script).future;
|
||||
return result.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
|
||||
}
|
||||
|
||||
protected record PythonAndHandler(ExecInPython exec, TraceRmiHandler handler)
|
||||
implements AutoCloseable {
|
||||
protected RemoteMethod getMethod(String name) {
|
||||
return Objects.requireNonNull(handler.getMethods().get(name));
|
||||
}
|
||||
|
||||
public void execute(String cmd) {
|
||||
RemoteMethod execute = getMethod("execute");
|
||||
execute.invoke(Map.of("cmd", cmd));
|
||||
}
|
||||
|
||||
public RemoteAsyncResult executeAsync(String cmd) {
|
||||
RemoteMethod execute = getMethod("execute");
|
||||
return execute.invokeAsync(Map.of("cmd", cmd));
|
||||
}
|
||||
|
||||
public String executeCapture(String expr) {
|
||||
RemoteMethod execute = getMethod("evaluate");
|
||||
return (String) execute.invoke(Map.of("expr", expr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
Msg.info(this, "Cleaning up python");
|
||||
exec.python().destroy();
|
||||
try {
|
||||
PythonResult r = exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
r.handle();
|
||||
waitForPass(() -> assertTrue(handler.isClosed()));
|
||||
}
|
||||
finally {
|
||||
exec.python.destroyForcibly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected PythonAndHandler startAndConnectPython(Function<String, String> scriptSupplier)
|
||||
throws Exception {
|
||||
TraceRmiAcceptor acceptor = traceRmi.acceptOne(null);
|
||||
ExecInPython exec =
|
||||
execInPython(scriptSupplier.apply(sockToStringForPython(acceptor.getAddress())));
|
||||
acceptor.setTimeout(CONNECT_TIMEOUT_MS);
|
||||
try {
|
||||
TraceRmiHandler handler = acceptor.accept();
|
||||
return new PythonAndHandler(exec, handler);
|
||||
}
|
||||
catch (SocketTimeoutException e) {
|
||||
exec.python.destroyForcibly();
|
||||
exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS).handle();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
protected PythonAndHandler startAndConnectPython() throws Exception {
|
||||
return startAndConnectPython(addr -> """
|
||||
%s
|
||||
ghidra_trace_connect('%s')
|
||||
""".formatted(PREAMBLE, addr));
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
protected String runThrowError(Function<String, String> scriptSupplier)
|
||||
throws Exception {
|
||||
PythonAndHandler conn = startAndConnectPython(scriptSupplier);
|
||||
PythonResult r = conn.exec.future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
String stdout = r.handle();
|
||||
waitForPass(() -> assertTrue(conn.handler.isClosed()));
|
||||
return stdout;
|
||||
}
|
||||
|
||||
protected void waitStopped() {
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
|
||||
waitForPass(() -> assertEquals("STOPPED", tb.objValue(proc, 0, "_state")));
|
||||
waitTxDone();
|
||||
}
|
||||
|
||||
protected void waitRunning() {
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
|
||||
waitForPass(() -> assertEquals("RUNNING", tb.objValue(proc, 0, "_state")));
|
||||
waitTxDone();
|
||||
}
|
||||
|
||||
protected String extractOutSection(String out, String head) {
|
||||
String[] split = out.split("\n");
|
||||
String xout = "";
|
||||
for (String s : split) {
|
||||
if (!s.startsWith("(python)") && !s.equals("")) {
|
||||
xout += s + "\n";
|
||||
}
|
||||
}
|
||||
return xout.split(head)[1].split("---")[0].replace("(python)", "").trim();
|
||||
}
|
||||
|
||||
record MemDump(long address, byte[] data) {
|
||||
}
|
||||
|
||||
protected MemDump parseHexDump(String dump) throws IOException {
|
||||
// First, get the address. Assume contiguous, so only need top line.
|
||||
List<String> lines = List.of(dump.split("\n"));
|
||||
List<String> toksLine0 = List.of(lines.get(0).split("\\s+"));
|
||||
String addrstr = toksLine0.get(0);
|
||||
if (addrstr.contains(":")) {
|
||||
addrstr = addrstr.substring(0, addrstr.indexOf(":"));
|
||||
}
|
||||
long address = Long.parseLong(addrstr, 16);
|
||||
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
for (String l : lines) {
|
||||
List<String> parts = List.of(l.split(":"));
|
||||
assertEquals(2, parts.size());
|
||||
String hex = parts.get(1).substring(0, 48);
|
||||
byte[] lineData = NumericUtilities.convertStringToBytes(hex);
|
||||
assertNotNull("Converted to null: " + hex, parts.get(1));
|
||||
buf.write(lineData);
|
||||
}
|
||||
return new MemDump(address, buf.toByteArray());
|
||||
}
|
||||
|
||||
record RegDump() {
|
||||
}
|
||||
|
||||
protected RegDump parseRegDump(String dump) {
|
||||
return new RegDump();
|
||||
}
|
||||
|
||||
protected ManagedDomainObject openDomainObject(String path) throws Exception {
|
||||
DomainFile df = env.getProject().getProjectData().getFile(path);
|
||||
assertNotNull(df);
|
||||
return new ManagedDomainObject(df, false, false, monitor);
|
||||
}
|
||||
|
||||
protected ManagedDomainObject waitDomainObject(String path) throws Exception {
|
||||
DomainFile df;
|
||||
long start = System.currentTimeMillis();
|
||||
while (true) {
|
||||
df = env.getProject().getProjectData().getFile(path);
|
||||
if (df != null) {
|
||||
return new ManagedDomainObject(df, false, false, monitor);
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
if (System.currentTimeMillis() - start > 30000) {
|
||||
throw new TimeoutException("30 seconds expired waiting for domain file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertBreakLoc(TraceObjectValue locVal, String key, Address addr, int len,
|
||||
Set<TraceBreakpointKind> kinds, String expression) throws Exception {
|
||||
assertEquals(key, locVal.getEntryKey());
|
||||
TraceObject loc = locVal.getChild();
|
||||
TraceObject spec = loc;
|
||||
assertEquals(new AddressRangeImpl(addr, len), loc.getValue(0, "_range").getValue());
|
||||
assertEquals(TraceBreakpointKindSet.encode(kinds), spec.getValue(0, "_kinds").getValue());
|
||||
assertTrue(spec.getValue(0, "_expression").getValue().toString().contains(expression));
|
||||
}
|
||||
|
||||
protected void assertWatchLoc(TraceObjectValue locVal, String key, Address addr, int len,
|
||||
Set<TraceBreakpointKind> kinds, String expression) throws Exception {
|
||||
assertEquals(key, locVal.getEntryKey());
|
||||
TraceObject loc = locVal.getChild();
|
||||
assertEquals(new AddressRangeImpl(addr, len), loc.getValue(0, "_range").getValue());
|
||||
assertEquals(TraceBreakpointKindSet.encode(kinds), loc.getValue(0, "_kinds").getValue());
|
||||
}
|
||||
|
||||
protected void waitTxDone() {
|
||||
waitFor(() -> tb.trace.getCurrentTransactionInfo() == null);
|
||||
}
|
||||
|
||||
public static void waitForPass(Runnable runnable, long timeoutMs, long retryDelayMs) {
|
||||
long start = System.currentTimeMillis();
|
||||
AssertionError lastError = null;
|
||||
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||
try {
|
||||
runnable.run();
|
||||
return;
|
||||
}
|
||||
catch (AssertionError e) {
|
||||
lastError = e;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(retryDelayMs);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// Retry sooner, I guess.
|
||||
}
|
||||
}
|
||||
if (lastError == null) {
|
||||
throw new AssertionError("Timed out before first try?");
|
||||
}
|
||||
throw lastError;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,380 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.rmi;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod;
|
||||
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
|
||||
public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||
private static final long RUN_TIMEOUT_MS = 20000;
|
||||
private static final long RETRY_MS = 500;
|
||||
|
||||
record PythonAndTrace(PythonAndHandler conn, ManagedDomainObject mdo) implements AutoCloseable {
|
||||
public void execute(String cmd) {
|
||||
conn.execute(cmd);
|
||||
}
|
||||
|
||||
public String executeCapture(String cmd) {
|
||||
return conn.executeCapture(cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
conn.close();
|
||||
mdo.close();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
protected PythonAndTrace startAndSyncPython(String exec) throws Exception {
|
||||
PythonAndHandler conn = startAndConnectPython();
|
||||
try {
|
||||
ManagedDomainObject mdo;
|
||||
conn.execute("from ghidradbg.commands import *");
|
||||
conn.execute(
|
||||
"util.set_convenience_variable('ghidra-language', 'x86:LE:64:default')");
|
||||
if (exec != null) {
|
||||
start(conn, exec);
|
||||
mdo = waitDomainObject("/New Traces/pydbg/"+exec);
|
||||
}
|
||||
else {
|
||||
conn.execute("ghidra_trace_start()");
|
||||
mdo = waitDomainObject("/New Traces/pydbg/noname");
|
||||
}
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
return new PythonAndTrace(conn, mdo);
|
||||
}
|
||||
catch (Exception e) {
|
||||
conn.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
protected long lastSnap(PythonAndTrace conn) {
|
||||
return conn.conn.handler().getLastSnapshot(tb.trace);
|
||||
}
|
||||
|
||||
@Test // The 10s wait makes this a pretty expensive test
|
||||
public void testOnNewThread() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
conn.execute("from ghidradbg.commands import *");
|
||||
txPut(conn, "processes");
|
||||
|
||||
waitForPass(() -> {
|
||||
TraceObject proc = tb.objAny("Processes[]");
|
||||
assertNotNull(proc);
|
||||
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
txPut(conn, "threads");
|
||||
waitForPass(() -> assertEquals(4,
|
||||
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
|
||||
RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
conn.execute("dbg().go(10)");
|
||||
|
||||
waitForPass(() -> assertTrue(tb.objValues(lastSnap(conn), "Processes[].Threads[]").size() > 4),
|
||||
RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnThreadSelected() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
txPut(conn, "processes");
|
||||
|
||||
waitForPass(() -> {
|
||||
TraceObject inf = tb.objAny("Processes[]");
|
||||
assertNotNull(inf);
|
||||
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
txPut(conn, "threads");
|
||||
waitForPass(() -> assertEquals(4,
|
||||
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
|
||||
RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
// Now the real test
|
||||
conn.execute("util.select_thread(1)");
|
||||
waitForPass(() -> {
|
||||
String tnum = conn.executeCapture("util.selected_thread()");
|
||||
assertTrue(tnum.contains("1"));
|
||||
String threadIndex = threadIndex(traceManager.getCurrentObject());
|
||||
assertTrue(tnum.contains(threadIndex));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
conn.execute("util.select_thread(2)");
|
||||
waitForPass(() -> {
|
||||
String tnum = conn.executeCapture("util.selected_thread()");
|
||||
assertTrue(tnum.contains("2"));
|
||||
String threadIndex = threadIndex(traceManager.getCurrentObject());
|
||||
assertTrue(tnum.contains(threadIndex));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
|
||||
conn.execute("util.select_thread(0)");
|
||||
waitForPass(() -> {
|
||||
String tnum = conn.executeCapture("util.selected_thread()");
|
||||
assertTrue(tnum.contains("0"));
|
||||
String threadIndex = threadIndex(traceManager.getCurrentObject());
|
||||
assertTrue(tnum.contains(threadIndex));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getIndex(TraceObject object, String pattern, int n) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
PathPattern pat = PathPredicates.parse(pattern).getSingletonPattern();
|
||||
// if (pat.countWildcards() != 1) {
|
||||
// throw new IllegalArgumentException("Exactly one wildcard required");
|
||||
// }
|
||||
List<String> path = object.getCanonicalPath().getKeyList();
|
||||
if (path.size() < pat.asPath().size()) {
|
||||
return null;
|
||||
}
|
||||
List<String> matched = pat.matchKeys(path.subList(0, pat.asPath().size()));
|
||||
if (matched == null) {
|
||||
return null;
|
||||
}
|
||||
if (matched.size() <= n) {
|
||||
return null;
|
||||
}
|
||||
return matched.get(n);
|
||||
}
|
||||
|
||||
protected String threadIndex(TraceObject object) {
|
||||
return getIndex(object, "Processes[].Threads[]", 1);
|
||||
}
|
||||
|
||||
protected String frameIndex(TraceObject object) {
|
||||
return getIndex(object, "Processes[].Threads[].Stack[]", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testOnSyscallMemory() throws Exception {
|
||||
// TODO: Need a specimen
|
||||
// FWIW, I've already seen this getting exercised in other tests.
|
||||
}
|
||||
|
||||
//@Test - dbgeng has limited support via DEBUG_CDS_DATA,
|
||||
// but expensive to implement anything here
|
||||
public void testOnMemoryChanged() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
|
||||
conn.execute("ghidra_trace_txstart('Tx')");
|
||||
conn.execute("ghidra_trace_putmem('$pc 10')");
|
||||
conn.execute("ghidra_trace_txcommit()");
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
conn.execute("util.get_debugger().write("+address+", b'\\x7f')");
|
||||
|
||||
waitForPass(() -> {
|
||||
ByteBuffer buf = ByteBuffer.allocate(10);
|
||||
tb.trace.getMemoryManager().getBytes(lastSnap(conn), tb.addr(address), buf);
|
||||
assertEquals(0x7f, buf.get(0));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnRegisterChanged() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
|
||||
conn.execute("ghidra_trace_txstart('Tx')");
|
||||
conn.execute("ghidra_trace_putreg()");
|
||||
conn.execute("ghidra_trace_txcommit()");
|
||||
conn.execute("util.get_debugger().reg._set_register('rax', 0x1234)");
|
||||
conn.execute("util.get_debugger().stepi()");
|
||||
|
||||
String path = "Processes[].Threads[].Registers";
|
||||
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
|
||||
AddressSpace space = tb.trace.getBaseAddressFactory()
|
||||
.getAddressSpace(registers.getCanonicalPath().toString());
|
||||
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(space, false);
|
||||
waitForPass(() -> assertEquals("1234",
|
||||
regs.getValue(lastSnap(conn), tb.reg("RAX")).getUnsignedValue().toString(16)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnCont() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
txPut(conn, "processes");
|
||||
|
||||
conn.execute("util.get_debugger()._control.SetExecutionStatus(DbgEng.DEBUG_STATUS_GO)");
|
||||
waitRunning();
|
||||
|
||||
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
|
||||
waitForPass(() -> {
|
||||
assertEquals("RUNNING", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStop() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
txPut(conn, "processes");
|
||||
|
||||
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
|
||||
waitForPass(() -> {
|
||||
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnExited() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("netstat.exe")) {
|
||||
txPut(conn, "processes");
|
||||
waitStopped();
|
||||
|
||||
conn.execute("util.get_debugger().go()");
|
||||
|
||||
waitForPass(() -> {
|
||||
TraceSnapshot snapshot =
|
||||
tb.trace.getTimeManager().getSnapshot(lastSnap(conn), false);
|
||||
assertNotNull(snapshot);
|
||||
assertEquals("Exited with code 0", snapshot.getDescription());
|
||||
|
||||
TraceObject proc = tb.objAny("Processes[]");
|
||||
assertNotNull(proc);
|
||||
Object val = tb.objValue(proc, lastSnap(conn), "_exit_code");
|
||||
assertThat(val, instanceOf(Number.class));
|
||||
assertEquals(0, ((Number) val).longValue());
|
||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnBreakpointCreated() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
||||
|
||||
conn.execute("dbg = util.get_debugger()");
|
||||
conn.execute("pc = dbg.reg.get_pc()");
|
||||
conn.execute("dbg.bp(expr=pc)");
|
||||
conn.execute("dbg.stepi()");
|
||||
|
||||
waitForPass(() -> {
|
||||
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
|
||||
assertEquals(1, brks.size());
|
||||
return (TraceObject) brks.get(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnBreakpointModified() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
||||
|
||||
conn.execute("dbg = util.get_debugger()");
|
||||
conn.execute("pc = dbg.reg.get_pc()");
|
||||
conn.execute("dbg.bp(expr=pc)");
|
||||
conn.execute("dbg.stepi()");
|
||||
|
||||
TraceObject brk = waitForPass(() -> {
|
||||
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
|
||||
assertEquals(1, brks.size());
|
||||
return (TraceObject) brks.get(0);
|
||||
});
|
||||
assertEquals(true, tb.objValue(brk, lastSnap(conn), "Enabled"));
|
||||
conn.execute("dbg.bd(0)");
|
||||
conn.execute("dbg.stepi()");
|
||||
assertEquals(false, tb.objValue(brk, lastSnap(conn), "Enabled"));
|
||||
|
||||
/* Not currently enabled
|
||||
assertEquals("", tb.objValue(brk, lastSnap(conn), "Command"));
|
||||
conn.execute("dbg.bp(expr=pc, windbgcmd='bl')");
|
||||
conn.execute("dbg.stepi()");
|
||||
assertEquals("bl", tb.objValue(brk, lastSnap(conn), "Command"));
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnBreakpointDeleted() throws Exception {
|
||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
||||
|
||||
conn.execute("dbg = util.get_debugger()");
|
||||
conn.execute("pc = dbg.reg.get_pc()");
|
||||
conn.execute("dbg.bp(expr=pc)");
|
||||
conn.execute("dbg.stepi()");
|
||||
|
||||
TraceObject brk = waitForPass(() -> {
|
||||
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
|
||||
assertEquals(1, brks.size());
|
||||
return (TraceObject) brks.get(0);
|
||||
});
|
||||
|
||||
conn.execute("dbg.cmd('bc %s')".formatted(brk.getCanonicalPath().index()));
|
||||
conn.execute("dbg.stepi()");
|
||||
|
||||
waitForPass(
|
||||
() -> assertEquals(0,
|
||||
tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()));
|
||||
}
|
||||
}
|
||||
|
||||
private void start(PythonAndHandler conn, String obj) {
|
||||
conn.execute("from ghidradbg.commands import *");
|
||||
if (obj != null)
|
||||
conn.execute("ghidra_trace_create('"+obj+"')");
|
||||
else
|
||||
conn.execute("ghidra_trace_create()");
|
||||
conn.execute("ghidra_trace_sync_enable()");
|
||||
}
|
||||
|
||||
private void txPut(PythonAndTrace conn, String obj) {
|
||||
conn.execute("ghidra_trace_txstart('Tx" + obj + "')");
|
||||
conn.execute("ghidra_trace_put_" + obj +"()");
|
||||
conn.execute("ghidra_trace_txcommit()");
|
||||
}
|
||||
|
||||
private long getAddressAtOffset(PythonAndTrace conn, int offset) {
|
||||
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+"+offset+")";
|
||||
String ret = conn.executeCapture(inst);
|
||||
String[] split = ret.split("\\s+"); // get target
|
||||
return Long.decode(split[1]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,959 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package agent.dbgeng.rmi;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod;
|
||||
import ghidra.app.plugin.core.debug.service.rmi.trace.ValueDecoder;
|
||||
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||
import ghidra.dbg.testutil.DummyProc;
|
||||
import ghidra.dbg.util.PathPattern;
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.modules.TraceModule;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
|
||||
public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||
|
||||
@Test
|
||||
public void testEvaluate() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
RemoteMethod evaluate = conn.getMethod("evaluate");
|
||||
assertEquals("11",
|
||||
evaluate.invoke(Map.of("expr", "3+4*2")));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteCapture() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
RemoteMethod execute = conn.getMethod("execute");
|
||||
assertEquals(false,
|
||||
execute.parameters().get("to_string").defaultValue().get(ValueDecoder.DEFAULT));
|
||||
assertEquals("11\n",
|
||||
execute.invoke(Map.of("cmd", "print(3+4*2)", "to_string", true)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
conn.execute("ghidra_trace_kill()");
|
||||
}
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
// Just confirm it's present
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshAvailable() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, null);
|
||||
txCreate(conn, "Available");
|
||||
|
||||
RemoteMethod refreshAvailable = conn.getMethod("refresh_available");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject available = Objects.requireNonNull(tb.objAny("Available"));
|
||||
|
||||
refreshAvailable.invoke(Map.of("node", available));
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
List<TraceObject> list = tb.trace.getObjectManager()
|
||||
.getValuePaths(Lifespan.at(0), PathPredicates.parse("Available[]"))
|
||||
.map(p -> p.getDestination(null))
|
||||
.toList();
|
||||
assertThat(list.size(), greaterThan(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshBreakpoints() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod refreshBreakpoints = conn.getMethod("refresh_breakpoints");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
conn.execute("dbg = util.get_debugger()");
|
||||
conn.execute("pc = dbg.reg.get_pc()");
|
||||
conn.execute("dbg.bp(expr=pc)");
|
||||
conn.execute("dbg.ba(expr=pc+4)");
|
||||
txPut(conn, "breakpoints");
|
||||
TraceObject breakpoints = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
|
||||
refreshBreakpoints.invoke(Map.of("node", breakpoints));
|
||||
|
||||
List<TraceObjectValue> procBreakLocVals = tb.trace.getObjectManager()
|
||||
.getValuePaths(Lifespan.at(0),
|
||||
PathPredicates.parse("Processes[].Breakpoints[]"))
|
||||
.map(p -> p.getLastEntry())
|
||||
.toList();
|
||||
assertEquals(2, procBreakLocVals.size());
|
||||
AddressRange rangeMain =
|
||||
procBreakLocVals.get(0).getChild().getValue(0, "_range").castValue();
|
||||
Address main = rangeMain.getMinAddress();
|
||||
|
||||
assertBreakLoc(procBreakLocVals.get(0), "[0]", main, 1,
|
||||
Set.of(TraceBreakpointKind.SW_EXECUTE),
|
||||
"ntdll!LdrInit");
|
||||
assertBreakLoc(procBreakLocVals.get(1), "[1]", main.add(4), 1,
|
||||
Set.of(TraceBreakpointKind.HW_EXECUTE),
|
||||
"ntdll!LdrInit");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshBreakpoints2() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "all");
|
||||
|
||||
RemoteMethod refreshProcWatchpoints = conn.getMethod("refresh_breakpoints");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
conn.execute("dbg = util.get_debugger()");
|
||||
conn.execute("pc = dbg.reg.get_pc()");
|
||||
conn.execute("dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)");
|
||||
conn.execute("dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)");
|
||||
conn.execute("dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)");
|
||||
TraceObject locations =
|
||||
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
|
||||
refreshProcWatchpoints.invoke(Map.of("node", locations));
|
||||
|
||||
List<TraceObjectValue> procBreakVals = tb.trace.getObjectManager()
|
||||
.getValuePaths(Lifespan.at(0),
|
||||
PathPredicates.parse("Processes[].Breakpoints[]"))
|
||||
.map(p -> p.getLastEntry())
|
||||
.toList();
|
||||
assertEquals(3, procBreakVals.size());
|
||||
AddressRange rangeMain0 =
|
||||
procBreakVals.get(0).getChild().getValue(0, "_range").castValue();
|
||||
Address main0 = rangeMain0.getMinAddress();
|
||||
AddressRange rangeMain1 =
|
||||
procBreakVals.get(1).getChild().getValue(0, "_range").castValue();
|
||||
Address main1 = rangeMain1.getMinAddress();
|
||||
AddressRange rangeMain2 =
|
||||
procBreakVals.get(2).getChild().getValue(0, "_range").castValue();
|
||||
Address main2 = rangeMain2.getMinAddress();
|
||||
|
||||
assertWatchLoc(procBreakVals.get(0), "[0]", main0, (int) rangeMain0.getLength(),
|
||||
Set.of(TraceBreakpointKind.HW_EXECUTE),
|
||||
"main");
|
||||
assertWatchLoc(procBreakVals.get(1), "[1]", main1, (int) rangeMain1.getLength(),
|
||||
Set.of(TraceBreakpointKind.WRITE),
|
||||
"main+4");
|
||||
assertWatchLoc(procBreakVals.get(2), "[2]", main2, (int) rangeMain1.getLength(),
|
||||
Set.of(TraceBreakpointKind.READ),
|
||||
"main+8");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshProcesses() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, null);
|
||||
txCreate(conn, "Processes");
|
||||
txCreate(conn, "Processes[1]");
|
||||
|
||||
RemoteMethod refreshProcesses = conn.getMethod("refresh_processes");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject processes = Objects.requireNonNull(tb.objAny("Processes"));
|
||||
|
||||
refreshProcesses.invoke(Map.of("node", processes));
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
List<TraceObject> list = tb.trace.getObjectManager()
|
||||
.getValuePaths(Lifespan.at(0), PathPredicates.parse("Processes[]"))
|
||||
.map(p -> p.getDestination(null))
|
||||
.toList();
|
||||
assertEquals(1, list.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshEnvironment() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
String path = "Processes[].Environment";
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "all");
|
||||
|
||||
RemoteMethod refreshEnvironment = conn.getMethod("refresh_environment");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject env = Objects.requireNonNull(tb.objAny(path));
|
||||
|
||||
refreshEnvironment.invoke(Map.of("node", env));
|
||||
|
||||
// Assumes pydbg on Windows amd64
|
||||
assertEquals("pydbg", env.getValue(0, "_debugger").getValue());
|
||||
assertEquals("x86_64", env.getValue(0, "_arch").getValue());
|
||||
assertEquals("windows", env.getValue(0, "_os").getValue());
|
||||
assertEquals("little", env.getValue(0, "_endian").getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshThreads() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
String path = "Processes[].Threads";
|
||||
start(conn, "notepad.exe");
|
||||
txCreate(conn, path);
|
||||
|
||||
RemoteMethod refreshThreads = conn.getMethod("refresh_threads");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject threads = Objects.requireNonNull(tb.objAny(path));
|
||||
|
||||
refreshThreads.invoke(Map.of("node", threads));
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
int listSize = tb.trace.getThreadManager().getAllThreads().size();
|
||||
assertEquals(4, listSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshStack() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
String path = "Processes[].Threads[].Stack";
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod refreshStack = conn.getMethod("refresh_stack");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
txPut(conn, "frames");
|
||||
TraceObject stack = Objects.requireNonNull(tb.objAny(path));
|
||||
refreshStack.invoke(Map.of("node", stack));
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
List<TraceObject> list = tb.trace.getObjectManager()
|
||||
.getValuePaths(Lifespan.at(0),
|
||||
PathPredicates.parse("Processes[].Threads[].Stack[]"))
|
||||
.map(p -> p.getDestination(null))
|
||||
.toList();
|
||||
assertTrue(list.size() > 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshRegisters() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
String path = "Processes[].Threads[].Registers";
|
||||
start(conn, "notepad.exe");
|
||||
conn.execute("ghidra_trace_txstart('Tx')");
|
||||
conn.execute("ghidra_trace_putreg()");
|
||||
conn.execute("ghidra_trace_txcommit()");
|
||||
|
||||
RemoteMethod refreshRegisters = conn.getMethod("refresh_registers");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
conn.execute("regs = util.get_debugger().reg");
|
||||
conn.execute("regs._set_register('rax', int(0xdeadbeef))");
|
||||
|
||||
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
|
||||
refreshRegisters.invoke(Map.of("node", registers));
|
||||
|
||||
long snap = 0;
|
||||
AddressSpace t1f0 = tb.trace.getBaseAddressFactory()
|
||||
.getAddressSpace(registers.getCanonicalPath().toString());
|
||||
TraceMemorySpace regs = tb.trace.getMemoryManager().getMemorySpace(t1f0, false);
|
||||
RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
|
||||
assertEquals("deadbeef", rax.getUnsignedValue().toString(16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshMappings() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
String path = "Processes[].Memory";
|
||||
start(conn, "notepad.exe");
|
||||
txCreate(conn, path);
|
||||
|
||||
RemoteMethod refreshMappings = conn.getMethod("refresh_mappings");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject memory = Objects.requireNonNull(tb.objAny(path));
|
||||
|
||||
refreshMappings.invoke(Map.of("node", memory));
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
Collection<? extends TraceMemoryRegion> all =
|
||||
tb.trace.getMemoryManager().getAllRegions();
|
||||
assertThat(all.size(), greaterThan(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshModules() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
String path = "Processes[].Modules";
|
||||
start(conn, "notepad.exe");
|
||||
txCreate(conn, path);
|
||||
|
||||
RemoteMethod refreshModules = conn.getMethod("refresh_modules");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject modules = Objects.requireNonNull(tb.objAny(path));
|
||||
|
||||
refreshModules.invoke(Map.of("node", modules));
|
||||
|
||||
// Would be nice to control / validate the specifics
|
||||
Collection<? extends TraceModule> all = tb.trace.getModuleManager().getAllModules();
|
||||
TraceModule modBash =
|
||||
Unique.assertOne(all.stream().filter(m -> m.getName().contains("notepad.exe")));
|
||||
assertNotEquals(tb.addr(0), Objects.requireNonNull(modBash.getBase()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateThread() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod activateThread = conn.getMethod("activate_thread");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
txPut(conn, "threads");
|
||||
|
||||
PathPattern pattern =
|
||||
PathPredicates.parse("Processes[].Threads[]").getSingletonPattern();
|
||||
List<TraceObject> list = tb.trace.getObjectManager()
|
||||
.getValuePaths(Lifespan.at(0), pattern)
|
||||
.map(p -> p.getDestination(null))
|
||||
.toList();
|
||||
assertEquals(4, list.size());
|
||||
|
||||
for (TraceObject t : list) {
|
||||
activateThread.invoke(Map.of("thread", t));
|
||||
String out = conn.executeCapture("util.get_debugger().get_thread()");
|
||||
List<String> indices = pattern.matchKeys(t.getCanonicalPath().getKeyList());
|
||||
assertEquals(out, "%s".formatted(indices.get(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveProcess() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "netstat.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod removeProcess = conn.getMethod("remove_process");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/netstat.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
removeProcess.invoke(Map.of("process", proc2));
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("[]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttachObj() throws Exception {
|
||||
try (DummyProc dproc = DummyProc.run("notepad.exe")) {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, null);
|
||||
txPut(conn, "available");
|
||||
|
||||
RemoteMethod attachObj = conn.getMethod("attach_obj");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
TraceObject target =
|
||||
Objects.requireNonNull(tb.obj("Available[%d]".formatted(dproc.pid)));
|
||||
attachObj.invoke(Map.of("target", target));
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("%d".formatted(dproc.pid)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttachPid() throws Exception {
|
||||
try (DummyProc dproc = DummyProc.run("notepad.exe")) {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, null);
|
||||
txPut(conn, "available");
|
||||
|
||||
RemoteMethod attachPid = conn.getMethod("attach_pid");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
Objects.requireNonNull(tb.objAny("Available["+dproc.pid+"]", Lifespan.at(0)));
|
||||
attachPid.invoke(Map.of("pid", dproc.pid));
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("%d".formatted(dproc.pid)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetach() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "netstat.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod detach = conn.getMethod("detach");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/netstat.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
detach.invoke(Map.of("process", proc));
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("[]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchEntry() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, null);
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod launch = conn.getMethod("launch_loader");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
launch.invoke(Map.ofEntries(
|
||||
Map.entry("file", "notepad.exe")));
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("notepad.exe"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test //Can't do this test because create(xxx, initial_break=False) doesn't return
|
||||
public void testLaunch() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, null);
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod launch = conn.getMethod("launch");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/noname")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
launch.invoke(Map.ofEntries(
|
||||
Map.entry("timeout", 1L),
|
||||
Map.entry("file", "notepad.exe")));
|
||||
|
||||
txPut(conn, "processes");
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("notepad.exe"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKill() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod kill = conn.getMethod("kill");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
kill.invoke(Map.of("process", proc));
|
||||
|
||||
String out = conn.executeCapture("list(util.process_list())");
|
||||
assertThat(out, containsString("[]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepInto() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod step_into = conn.getMethod("step_into");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
txPut(conn, "threads");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
|
||||
while (!getInst(conn).contains("call")) {
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
}
|
||||
|
||||
String disCall = getInst(conn);
|
||||
// lab0:
|
||||
// -> addr0
|
||||
//
|
||||
// lab1:
|
||||
// addr1
|
||||
String[] split = disCall.split("\\s+"); // get target
|
||||
long pcCallee = Long.decode(split[split.length-1]);
|
||||
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
long pc = getAddressAtOffset(conn, 0);
|
||||
assertEquals(pcCallee, pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepOver() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod step_over = conn.getMethod("step_over");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
txPut(conn, "threads");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
|
||||
while (!getInst(conn).contains("call")) {
|
||||
step_over.invoke(Map.of("thread", thread));
|
||||
}
|
||||
|
||||
String disCall = getInst(conn);
|
||||
String[] split = disCall.split("\\s+"); // get target
|
||||
long pcCallee = Long.decode(split[split.length-1]);
|
||||
|
||||
step_over.invoke(Map.of("thread", thread));
|
||||
long pc = getAddressAtOffset(conn, 0);
|
||||
assertNotEquals(pcCallee, pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepTo() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
|
||||
RemoteMethod step_into = conn.getMethod("step_into");
|
||||
RemoteMethod step_to = conn.getMethod("step_to");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
txPut(conn, "threads");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
while (!getInst(conn).contains("call")) {
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
}
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
|
||||
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
|
||||
for (int i = 0; i < 4; i++) {
|
||||
sz += Integer.parseInt(getInstSizeAtOffset(conn, sz));
|
||||
}
|
||||
|
||||
long pcNext = getAddressAtOffset(conn, sz);
|
||||
|
||||
boolean success = (boolean) step_to.invoke(Map.of("thread", thread, "address", tb.addr(pcNext), "max", 10));
|
||||
assertTrue(success);
|
||||
|
||||
long pc = getAddressAtOffset(conn, 0);
|
||||
assertEquals(pcNext, pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStepOut() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod step_into = conn.getMethod("step_into");
|
||||
RemoteMethod step_out = conn.getMethod("step_out");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
txPut(conn, "threads");
|
||||
|
||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||
|
||||
while (!getInst(conn).contains("call")) {
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
}
|
||||
|
||||
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
|
||||
long pcNext = getAddressAtOffset(conn, sz);
|
||||
|
||||
step_into.invoke(Map.of("thread", thread));
|
||||
step_out.invoke(Map.of("thread", thread));
|
||||
long pc = getAddressAtOffset(conn, 0);
|
||||
assertEquals(pcNext, pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakAddress() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakAddress = conn.getMethod("break_address");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString(Long.toHexString(address)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakExpression() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "entry"));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("entry"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakHardwareAddress() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakAddress = conn.getMethod("break_hw_address");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString(Long.toHexString(address)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakHardwareExpression() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_hw_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "entry"));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("entry"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakReadRange() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakRange = conn.getMethod("break_read_range");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
AddressRange range = tb.range(address, address + 3); // length 4
|
||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("%x".formatted(address)));
|
||||
assertThat(out, containsString("sz=4"));
|
||||
assertThat(out, containsString("type=r"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakReadExpression() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_read_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString(Long.toHexString(address>>24)));
|
||||
assertThat(out, containsString("sz=1"));
|
||||
assertThat(out, containsString("type=r"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakWriteRange() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakRange = conn.getMethod("break_write_range");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
AddressRange range = tb.range(address, address + 3); // length 4
|
||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("%x".formatted(address)));
|
||||
assertThat(out, containsString("sz=4"));
|
||||
assertThat(out, containsString("type=w"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakWriteExpression() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_write_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString(Long.toHexString(address>>24)));
|
||||
assertThat(out, containsString("sz=1"));
|
||||
assertThat(out, containsString("type=w"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakAccessRange() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakRange = conn.getMethod("break_access_range");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
AddressRange range = tb.range(address, address + 3); // length 4
|
||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("%x".formatted(address)));
|
||||
assertThat(out, containsString("sz=4"));
|
||||
assertThat(out, containsString("type=rw"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreakAccessExpression() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakExpression = conn.getMethod("break_access_expression");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString(Long.toHexString(address>>24)));
|
||||
assertThat(out, containsString("sz=1"));
|
||||
assertThat(out, containsString("type=rw"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToggleBreakpoint() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakAddress = conn.getMethod("break_address");
|
||||
RemoteMethod toggleBreakpoint = conn.getMethod("toggle_breakpoint");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||
|
||||
txPut(conn, "breakpoints");
|
||||
TraceObject bpt = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints[]"));
|
||||
|
||||
toggleBreakpoint.invoke(Map.of("breakpoint", bpt, "enabled", false));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("disabled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteBreakpoint() throws Exception {
|
||||
try (PythonAndHandler conn = startAndConnectPython()) {
|
||||
start(conn, "notepad.exe");
|
||||
txPut(conn, "processes");
|
||||
|
||||
RemoteMethod breakAddress = conn.getMethod("break_address");
|
||||
RemoteMethod deleteBreakpoint = conn.getMethod("delete_breakpoint");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
|
||||
long address = getAddressAtOffset(conn, 0);
|
||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||
|
||||
txPut(conn, "breakpoints");
|
||||
TraceObject bpt = Objects.requireNonNull(tb.objAny("Processes[].Breakpoints[]"));
|
||||
|
||||
deleteBreakpoint.invoke(Map.of("breakpoint", bpt));
|
||||
|
||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
||||
assertThat(out, containsString("[]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void start(PythonAndHandler conn, String obj) {
|
||||
conn.execute("from ghidradbg.commands import *");
|
||||
if (obj != null)
|
||||
conn.execute("ghidra_trace_create('"+obj+"')");
|
||||
else
|
||||
conn.execute("ghidra_trace_create()"); }
|
||||
|
||||
private void txPut(PythonAndHandler conn, String obj) {
|
||||
conn.execute("ghidra_trace_txstart('Tx')");
|
||||
conn.execute("ghidra_trace_put_" + obj + "()");
|
||||
conn.execute("ghidra_trace_txcommit()");
|
||||
}
|
||||
|
||||
private void txCreate(PythonAndHandler conn, String path) {
|
||||
conn.execute("ghidra_trace_txstart('Fake')");
|
||||
conn.execute("ghidra_trace_create_obj('%s')".formatted(path));
|
||||
conn.execute("ghidra_trace_txcommit()");
|
||||
}
|
||||
|
||||
private String getInst(PythonAndHandler conn) {
|
||||
return getInstAtOffset(conn, 0);
|
||||
}
|
||||
|
||||
private String getInstAtOffset(PythonAndHandler conn, int offset) {
|
||||
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+"+offset+")";
|
||||
String ret = conn.executeCapture(inst);
|
||||
return ret.substring(1, ret.length()-1); // remove <>
|
||||
}
|
||||
|
||||
private String getInstSizeAtOffset(PythonAndHandler conn, int offset) {
|
||||
String instSize = "util.get_inst_sz(util.get_debugger().reg.get_pc()+"+offset+")";
|
||||
return conn.executeCapture(instSize);
|
||||
}
|
||||
|
||||
private long getAddressAtOffset(PythonAndHandler conn, int offset) {
|
||||
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+"+offset+")";
|
||||
String ret = conn.executeCapture(inst);
|
||||
String[] split = ret.split("\\s+"); // get target
|
||||
return Long.decode(split[1]);
|
||||
}
|
||||
|
||||
}
|
|
@ -137,7 +137,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
|
|||
ghidra_trace_connect %s
|
||||
file bash
|
||||
script ghidralldb.util.set_convenience_variable('ghidra-language','Toy:BE:64:default')
|
||||
script ghidralldb.util.set_convenience_varaible('ghidra-compiler','default')
|
||||
script ghidralldb.util.set_convenience_variable('ghidra-compiler','default')
|
||||
ghidra_trace_start myToy
|
||||
quit
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue
Block a user