mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
GP-4144: Many fixes, esp., for dbgeng Trace RMI.
This commit is contained in:
parent
1281fb979b
commit
a6549947ab
30 changed files with 1526 additions and 725 deletions
|
@ -28,7 +28,4 @@ if exist "%GHIDRA_HOME%\ghidra\.git\" (
|
||||||
set PYTHONPATH=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src;%PYTHONPATH%
|
set PYTHONPATH=%GHIDRA_HOME%\Ghidra\Debug\Debugger-rmi-trace\pypkg\src;%PYTHONPATH%
|
||||||
)
|
)
|
||||||
|
|
||||||
echo PYTHONPATH is %PYTHONPATH%
|
|
||||||
echo OPT_TARGET_IMG is [%OPT_TARGET_IMG%]
|
|
||||||
|
|
||||||
"%OPT_PYTHON_EXE%" -i ..\support\local-dbgeng.py
|
"%OPT_PYTHON_EXE%" -i ..\support\local-dbgeng.py
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
##
|
##
|
||||||
import os
|
import os
|
||||||
|
from ghidradbg.util import *
|
||||||
from ghidradbg.commands import *
|
from ghidradbg.commands import *
|
||||||
|
|
||||||
ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR'))
|
ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR'))
|
||||||
|
@ -25,6 +26,6 @@ ghidra_trace_start(os.getenv('OPT_TARGET_IMG'))
|
||||||
ghidra_trace_sync_enable()
|
ghidra_trace_sync_enable()
|
||||||
|
|
||||||
# TODO: HACK
|
# TODO: HACK
|
||||||
dbg().wait()
|
dbg.wait()
|
||||||
|
|
||||||
repl()
|
repl()
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
##
|
##
|
||||||
import ctypes
|
|
||||||
ctypes.windll.kernel32.SetErrorMode(0x0001|0x0002|0x8000)
|
|
||||||
|
|
||||||
from . import util, commands, methods, hooks
|
from . import util, commands, methods, hooks
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
|
||||||
|
ctypes.windll.kernel32.SetErrorMode(0x0001 | 0x0002 | 0x8000)
|
||||||
|
|
|
@ -55,8 +55,9 @@ compiler_map = {
|
||||||
|
|
||||||
def get_arch():
|
def get_arch():
|
||||||
try:
|
try:
|
||||||
type = util.get_debugger()._control.GetActualProcessorType()
|
type = util.dbg.get_actual_processor_type()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
print("Error getting actual processor type.")
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
if type is None:
|
if type is None:
|
||||||
return "x86_64"
|
return "x86_64"
|
||||||
|
@ -85,10 +86,11 @@ def get_osabi():
|
||||||
if not parm in ['auto', 'default']:
|
if not parm in ['auto', 'default']:
|
||||||
return parm
|
return parm
|
||||||
try:
|
try:
|
||||||
os = util.get_debugger().cmd("vertarget")
|
os = util.dbg.cmd("vertarget")
|
||||||
if "Windows" not in os:
|
if "Windows" not in os:
|
||||||
return "default"
|
return "default"
|
||||||
except Exception:
|
except Exception:
|
||||||
|
print("Error getting target OS/ABI")
|
||||||
pass
|
pass
|
||||||
return "windows"
|
return "windows"
|
||||||
|
|
||||||
|
@ -154,7 +156,8 @@ class DefaultMemoryMapper(object):
|
||||||
def map_back(self, proc: int, address: Address) -> int:
|
def map_back(self, proc: int, address: Address) -> int:
|
||||||
if address.space == self.defaultSpace:
|
if address.space == self.defaultSpace:
|
||||||
return address.offset
|
return address.offset
|
||||||
raise ValueError(f"Address {address} is not in process {proc.GetProcessID()}")
|
raise ValueError(
|
||||||
|
f"Address {address} is not in process {proc.GetProcessID()}")
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
|
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
|
||||||
|
@ -179,14 +182,13 @@ class DefaultRegisterMapper(object):
|
||||||
def map_name(self, proc, name):
|
def map_name(self, proc, name):
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
def map_value(self, proc, name, value):
|
def map_value(self, proc, name, value):
|
||||||
try:
|
try:
|
||||||
### TODO: this seems half-baked
|
# TODO: this seems half-baked
|
||||||
av = value.to_bytes(8, "big")
|
av = value.to_bytes(8, "big")
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
|
raise ValueError("Cannot convert {}'s value: '{}', type: '{}'"
|
||||||
.format(name, value, type(value)))
|
.format(name, value, type(value)))
|
||||||
return RegVal(self.map_name(proc, name), av)
|
return RegVal(self.map_name(proc, name), av)
|
||||||
|
|
||||||
def map_name_back(self, proc, name):
|
def map_name_back(self, proc, name):
|
||||||
|
@ -237,4 +239,3 @@ def compute_register_mapper(lang):
|
||||||
if ':LE:' in lang:
|
if ':LE:' in lang:
|
||||||
return DEFAULT_LE_REGISTER_MAPPER
|
return DEFAULT_LE_REGISTER_MAPPER
|
||||||
return register_mappers[lang]
|
return register_mappers[lang]
|
||||||
|
|
||||||
|
|
|
@ -13,23 +13,24 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
##
|
##
|
||||||
|
import code
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import inspect
|
import inspect
|
||||||
import os.path
|
import os.path
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import re
|
import re
|
||||||
|
import socket
|
||||||
from ghidratrace import sch
|
import sys
|
||||||
from ghidratrace.client import Client, Address, AddressRange, TraceObject
|
import time
|
||||||
|
|
||||||
from pybag import pydbg, userdbg, kerneldbg
|
from pybag import pydbg, userdbg, kerneldbg
|
||||||
from pybag.dbgeng import core as DbgEng
|
from pybag.dbgeng import core as DbgEng
|
||||||
from pybag.dbgeng import exception
|
from pybag.dbgeng import exception
|
||||||
|
|
||||||
|
from ghidratrace import sch
|
||||||
|
from ghidratrace.client import Client, Address, AddressRange, TraceObject
|
||||||
|
|
||||||
from . import util, arch, methods, hooks
|
from . import util, arch, methods, hooks
|
||||||
import code
|
|
||||||
|
|
||||||
PAGE_SIZE = 4096
|
PAGE_SIZE = 4096
|
||||||
|
|
||||||
|
@ -170,10 +171,10 @@ def ghidra_trace_listen(address='0.0.0.0:0'):
|
||||||
s.bind((host, int(port)))
|
s.bind((host, int(port)))
|
||||||
host, port = s.getsockname()
|
host, port = s.getsockname()
|
||||||
s.listen(1)
|
s.listen(1)
|
||||||
print("Listening at {}:{}...\n".format(host, port))
|
print("Listening at {}:{}...".format(host, port))
|
||||||
c, (chost, cport) = s.accept()
|
c, (chost, cport) = s.accept()
|
||||||
s.close()
|
s.close()
|
||||||
print("Connection from {}:{}\n".format(chost, cport))
|
print("Connection from {}:{}".format(chost, cport))
|
||||||
STATE.client = Client(c, "dbgeng.dll", methods.REGISTRY)
|
STATE.client = Client(c, "dbgeng.dll", methods.REGISTRY)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RuntimeError("port must be numeric")
|
raise RuntimeError("port must be numeric")
|
||||||
|
@ -209,7 +210,7 @@ def start_trace(name):
|
||||||
schema_xml = schema_file.read()
|
schema_xml = schema_file.read()
|
||||||
with STATE.trace.open_tx("Create Root Object"):
|
with STATE.trace.open_tx("Create Root Object"):
|
||||||
root = STATE.trace.create_root_object(schema_xml, 'Session')
|
root = STATE.trace.create_root_object(schema_xml, 'Session')
|
||||||
root.set_value('_display', 'pydbg(dbgeng) ' + util.DBG_VERSION.full)
|
root.set_value('_display', util.DBG_VERSION.full + ' via pybag')
|
||||||
util.set_convenience_variable('_ghidra_tracing', "true")
|
util.set_convenience_variable('_ghidra_tracing', "true")
|
||||||
|
|
||||||
|
|
||||||
|
@ -240,47 +241,49 @@ def ghidra_trace_restart(name=None):
|
||||||
start_trace(name)
|
start_trace(name)
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_create(command=None, initial_break=True, timeout=None, start_trace=True):
|
@util.dbg.eng_thread
|
||||||
|
def ghidra_trace_create(command=None, initial_break=True, timeout=DbgEng.WAIT_INFINITE, start_trace=True):
|
||||||
"""
|
"""
|
||||||
Create a session.
|
Create a session.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
util.base = userdbg.UserDbg()
|
dbg = util.dbg._base
|
||||||
if command != None:
|
if command != None:
|
||||||
if timeout != None:
|
dbg._client.CreateProcess(command, DbgEng.DEBUG_PROCESS)
|
||||||
util.base._client.CreateProcess(command, DbgEng.DEBUG_PROCESS)
|
if initial_break:
|
||||||
if initial_break:
|
dbg._control.AddEngineOptions(DbgEng.DEBUG_ENGINITIAL_BREAK)
|
||||||
util.base._control.AddEngineOptions(
|
dbg.wait(timeout)
|
||||||
DbgEng.DEBUG_ENGINITIAL_BREAK)
|
|
||||||
util.base.wait(timeout)
|
|
||||||
else:
|
|
||||||
util.base.create(command, initial_break)
|
|
||||||
if start_trace:
|
if start_trace:
|
||||||
ghidra_trace_start(command)
|
ghidra_trace_start(command)
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def ghidra_trace_kill():
|
def ghidra_trace_kill():
|
||||||
"""
|
"""
|
||||||
Kill a session.
|
Kill a session.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dbg()._client.TerminateCurrentProcess()
|
dbg = util.dbg._base
|
||||||
|
dbg._client.TerminateCurrentProcess()
|
||||||
|
try:
|
||||||
|
dbg.wait()
|
||||||
|
except exception.E_UNEXPECTED_Error:
|
||||||
|
# Expect the unexpected, I guess.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_info():
|
def ghidra_trace_info():
|
||||||
"""Get info about the Ghidra connection"""
|
"""Get info about the Ghidra connection"""
|
||||||
|
|
||||||
result = {}
|
|
||||||
if STATE.client is None:
|
if STATE.client is None:
|
||||||
print("Not connected to Ghidra\n")
|
print("Not connected to Ghidra")
|
||||||
return
|
return
|
||||||
host, port = STATE.client.s.getpeername()
|
host, port = STATE.client.s.getpeername()
|
||||||
print(f"Connected to {STATE.client.description} at {host}:{port}\n")
|
print(f"Connected to {STATE.client.description} at {host}:{port}")
|
||||||
if STATE.trace is None:
|
if STATE.trace is None:
|
||||||
print("No trace\n")
|
print("No trace")
|
||||||
return
|
return
|
||||||
print("Trace active\n")
|
print("Trace active")
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_info_lcsp():
|
def ghidra_trace_info_lcsp():
|
||||||
|
@ -289,8 +292,8 @@ def ghidra_trace_info_lcsp():
|
||||||
"""
|
"""
|
||||||
|
|
||||||
language, compiler = arch.compute_ghidra_lcsp()
|
language, compiler = arch.compute_ghidra_lcsp()
|
||||||
print("Selected Ghidra language: {}\n".format(language))
|
print("Selected Ghidra language: {}".format(language))
|
||||||
print("Selected Ghidra compiler: {}\n".format(compiler))
|
print("Selected Ghidra compiler: {}".format(compiler))
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_txstart(description="tx"):
|
def ghidra_trace_txstart(description="tx"):
|
||||||
|
@ -319,7 +322,7 @@ def ghidra_trace_txabort():
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tx = STATE.require_tx()
|
tx = STATE.require_tx()
|
||||||
print("Aborting trace transaction!\n")
|
print("Aborting trace transaction!")
|
||||||
tx.abort()
|
tx.abort()
|
||||||
STATE.reset_tx()
|
STATE.reset_tx()
|
||||||
|
|
||||||
|
@ -362,15 +365,22 @@ def ghidra_trace_set_snap(snap=None):
|
||||||
STATE.require_trace().set_snap(int(snap))
|
STATE.require_trace().set_snap(int(snap))
|
||||||
|
|
||||||
|
|
||||||
|
def quantize_pages(start, end):
|
||||||
|
return (start // PAGE_SIZE * PAGE_SIZE, (end+PAGE_SIZE-1) // PAGE_SIZE*PAGE_SIZE)
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def put_bytes(start, end, pages, display_result):
|
def put_bytes(start, end, pages, display_result):
|
||||||
trace = STATE.require_trace()
|
trace = STATE.require_trace()
|
||||||
if pages:
|
if pages:
|
||||||
start = start // PAGE_SIZE * PAGE_SIZE
|
start, end = quantize_pages(start, end)
|
||||||
end = (end + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE
|
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
if end - start <= 0:
|
if end - start <= 0:
|
||||||
return {'count': 0}
|
return {'count': 0}
|
||||||
buf = dbg().read(start, end - start)
|
try:
|
||||||
|
buf = util.dbg._base.read(start, end - start)
|
||||||
|
except OSError:
|
||||||
|
return {'count': 0}
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
if buf != None:
|
if buf != None:
|
||||||
|
@ -379,7 +389,7 @@ def put_bytes(start, end, pages, display_result):
|
||||||
trace.create_overlay_space(base, addr.space)
|
trace.create_overlay_space(base, addr.space)
|
||||||
count = trace.put_bytes(addr, buf)
|
count = trace.put_bytes(addr, buf)
|
||||||
if display_result:
|
if display_result:
|
||||||
print("Wrote {} bytes\n".format(count))
|
print("Wrote {} bytes".format(count))
|
||||||
return {'count': count}
|
return {'count': count}
|
||||||
|
|
||||||
|
|
||||||
|
@ -404,16 +414,11 @@ def putmem(address, length, pages=True, display_result=True):
|
||||||
return put_bytes(start, end, pages, display_result)
|
return put_bytes(start, end, pages, display_result)
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_putmem(items):
|
def ghidra_trace_putmem(address, length, pages=True):
|
||||||
"""
|
"""
|
||||||
Record the given block of memory into the Ghidra trace.
|
Record the given block of memory into the Ghidra trace.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
items = items.split(" ")
|
|
||||||
address = items[0]
|
|
||||||
length = items[1]
|
|
||||||
pages = items[2] if len(items) > 2 else True
|
|
||||||
|
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
return putmem(address, length, pages, True)
|
return putmem(address, length, pages, True)
|
||||||
|
|
||||||
|
@ -436,27 +441,28 @@ def ghidra_trace_putval(items):
|
||||||
return put_bytes(start, end, pages, True)
|
return put_bytes(start, end, pages, True)
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_putmem_state(items):
|
def putmem_state(address, length, state, pages=True):
|
||||||
"""
|
|
||||||
Set the state of the given range of memory in the Ghidra trace.
|
|
||||||
"""
|
|
||||||
|
|
||||||
items = items.split(" ")
|
|
||||||
address = items[0]
|
|
||||||
length = items[1]
|
|
||||||
state = items[2]
|
|
||||||
|
|
||||||
STATE.require_tx()
|
|
||||||
STATE.trace.validate_state(state)
|
STATE.trace.validate_state(state)
|
||||||
start, end = eval_range(address, length)
|
start, end = eval_range(address, length)
|
||||||
|
if pages:
|
||||||
|
start, end = quantize_pages(start, end)
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
base, addr = STATE.trace.memory_mapper.map(nproc, start)
|
base, addr = STATE.trace.memory_mapper.map(nproc, start)
|
||||||
if base != addr.space:
|
if base != addr.space and state != 'unknown':
|
||||||
trace.create_overlay_space(base, addr.space)
|
trace.create_overlay_space(base, addr.space)
|
||||||
STATE.trace.set_memory_state(addr.extend(end - start), state)
|
STATE.trace.set_memory_state(addr.extend(end - start), state)
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_delmem(items):
|
def ghidra_trace_putmem_state(address, length, state, pages=True):
|
||||||
|
"""
|
||||||
|
Set the state of the given range of memory in the Ghidra trace.
|
||||||
|
"""
|
||||||
|
|
||||||
|
STATE.require_tx()
|
||||||
|
return putmem_state(address, length, state, pages)
|
||||||
|
|
||||||
|
|
||||||
|
def ghidra_trace_delmem(address, length):
|
||||||
"""
|
"""
|
||||||
Delete the given range of memory from the Ghidra trace.
|
Delete the given range of memory from the Ghidra trace.
|
||||||
|
|
||||||
|
@ -465,10 +471,6 @@ def ghidra_trace_delmem(items):
|
||||||
not quantize. You must do that yourself, if necessary.
|
not quantize. You must do that yourself, if necessary.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
items = items.split(" ")
|
|
||||||
address = items[0]
|
|
||||||
length = items[1]
|
|
||||||
|
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
start, end = eval_range(address, length)
|
start, end = eval_range(address, length)
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
|
@ -477,6 +479,7 @@ def ghidra_trace_delmem(items):
|
||||||
STATE.trace.delete_bytes(addr.extend(end - start))
|
STATE.trace.delete_bytes(addr.extend(end - start))
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def putreg():
|
def putreg():
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
if nproc < 0:
|
if nproc < 0:
|
||||||
|
@ -488,7 +491,7 @@ def putreg():
|
||||||
robj.insert()
|
robj.insert()
|
||||||
mapper = STATE.trace.register_mapper
|
mapper = STATE.trace.register_mapper
|
||||||
values = []
|
values = []
|
||||||
regs = dbg().reg
|
regs = util.dbg._base.reg
|
||||||
for i in range(0, len(regs)):
|
for i in range(0, len(regs)):
|
||||||
name = regs._reg.GetDescription(i)[0]
|
name = regs._reg.GetDescription(i)[0]
|
||||||
value = regs._get_register_by_index(i)
|
value = regs._get_register_by_index(i)
|
||||||
|
@ -511,6 +514,7 @@ def ghidra_trace_putreg():
|
||||||
putreg()
|
putreg()
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def ghidra_trace_delreg(group='all'):
|
def ghidra_trace_delreg(group='all'):
|
||||||
"""
|
"""
|
||||||
Delete the given register group for the curent frame from the Ghidra trace.
|
Delete the given register group for the curent frame from the Ghidra trace.
|
||||||
|
@ -524,11 +528,11 @@ def ghidra_trace_delreg(group='all'):
|
||||||
space = REGS_PATTERN.format(procnum=nproc, tnum=nthrd)
|
space = REGS_PATTERN.format(procnum=nproc, tnum=nthrd)
|
||||||
mapper = STATE.trace.register_mapper
|
mapper = STATE.trace.register_mapper
|
||||||
names = []
|
names = []
|
||||||
regs = dbg().reg
|
regs = util.dbg._base.reg
|
||||||
for i in range(0, len(regs)):
|
for i in range(0, len(regs)):
|
||||||
name = regs._reg.GetDescription(i)[0]
|
name = regs._reg.GetDescription(i)[0]
|
||||||
names.append(mapper.map_name(nproc, name))
|
names.append(mapper.map_name(nproc, name))
|
||||||
return STATE.trace.delete_registers(space, names)
|
STATE.trace.delete_registers(space, names)
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_create_obj(path=None):
|
def ghidra_trace_create_obj(path=None):
|
||||||
|
@ -543,8 +547,7 @@ def ghidra_trace_create_obj(path=None):
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
obj = STATE.trace.create_object(path)
|
obj = STATE.trace.create_object(path)
|
||||||
obj.insert()
|
obj.insert()
|
||||||
print("Created object: id={}, path='{}'\n".format(obj.id, obj.path))
|
print("Created object: id={}, path='{}'".format(obj.id, obj.path))
|
||||||
return {'id': obj.id, 'path': obj.path}
|
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_insert_obj(path):
|
def ghidra_trace_insert_obj(path):
|
||||||
|
@ -556,8 +559,7 @@ def ghidra_trace_insert_obj(path):
|
||||||
# humans.
|
# humans.
|
||||||
STATE.require_tx()
|
STATE.require_tx()
|
||||||
span = STATE.trace.proxy_object_path(path).insert()
|
span = STATE.trace.proxy_object_path(path).insert()
|
||||||
print("Inserted object: lifespan={}\n".format(span))
|
print("Inserted object: lifespan={}".format(span))
|
||||||
return {'lifespan': span}
|
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_remove_obj(path):
|
def ghidra_trace_remove_obj(path):
|
||||||
|
@ -600,10 +602,10 @@ def to_string_list(value, encoding):
|
||||||
|
|
||||||
def eval_value(value, schema=None):
|
def eval_value(value, schema=None):
|
||||||
if schema == sch.CHAR or schema == sch.BYTE or schema == sch.SHORT or schema == sch.INT or schema == sch.LONG or schema == None:
|
if schema == sch.CHAR or schema == sch.BYTE or schema == sch.SHORT or schema == sch.INT or schema == sch.LONG or schema == None:
|
||||||
value = util.get_eval(value)
|
value = util.parse_and_eval(value)
|
||||||
return value, schema
|
return value, schema
|
||||||
if schema == sch.ADDRESS:
|
if schema == sch.ADDRESS:
|
||||||
value = util.get_eval(value)
|
value = util.parse_and_eval(value)
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
base, addr = STATE.trace.memory_mapper.map(nproc, value)
|
base, addr = STATE.trace.memory_mapper.map(nproc, value)
|
||||||
return (base, addr), sch.ADDRESS
|
return (base, addr), sch.ADDRESS
|
||||||
|
@ -696,8 +698,7 @@ def ghidra_trace_get_obj(path):
|
||||||
|
|
||||||
trace = STATE.require_trace()
|
trace = STATE.require_trace()
|
||||||
object = trace.get_object(path)
|
object = trace.get_object(path)
|
||||||
print("{}\t{}\n".format(object.id, object.path))
|
print("{}\t{}".format(object.id, object.path))
|
||||||
return object
|
|
||||||
|
|
||||||
|
|
||||||
class TableColumn(object):
|
class TableColumn(object):
|
||||||
|
@ -734,7 +735,7 @@ class Tabular(object):
|
||||||
for rn in range(self.num_rows):
|
for rn in range(self.num_rows):
|
||||||
for c in self.columns:
|
for c in self.columns:
|
||||||
c.print_cell(rn)
|
c.print_cell(rn)
|
||||||
print('\n')
|
print('')
|
||||||
|
|
||||||
|
|
||||||
def val_repr(value):
|
def val_repr(value):
|
||||||
|
@ -761,18 +762,13 @@ def ghidra_trace_get_values(pattern):
|
||||||
trace = STATE.require_trace()
|
trace = STATE.require_trace()
|
||||||
values = trace.get_values(pattern)
|
values = trace.get_values(pattern)
|
||||||
print_values(values)
|
print_values(values)
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_get_values_rng(items):
|
def ghidra_trace_get_values_rng(address, length):
|
||||||
"""
|
"""
|
||||||
List all values intersecting a given address range.
|
List all values intersecting a given address range.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
items = items.split(" ")
|
|
||||||
address = items[0]
|
|
||||||
length = items[1]
|
|
||||||
|
|
||||||
trace = STATE.require_trace()
|
trace = STATE.require_trace()
|
||||||
start, end = eval_range(address, length)
|
start, end = eval_range(address, length)
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
|
@ -780,7 +776,6 @@ def ghidra_trace_get_values_rng(items):
|
||||||
# Do not create the space. We're querying. No tx.
|
# Do not create the space. We're querying. No tx.
|
||||||
values = trace.get_values_intersecting(addr.extend(end - start))
|
values = trace.get_values_intersecting(addr.extend(end - start))
|
||||||
print_values(values)
|
print_values(values)
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
def activate(path=None):
|
def activate(path=None):
|
||||||
|
@ -825,34 +820,33 @@ def ghidra_trace_disassemble(address):
|
||||||
trace.create_overlay_space(base, addr.space)
|
trace.create_overlay_space(base, addr.space)
|
||||||
|
|
||||||
length = STATE.trace.disassemble(addr)
|
length = STATE.trace.disassemble(addr)
|
||||||
print("Disassembled {} bytes\n".format(length))
|
print("Disassembled {} bytes".format(length))
|
||||||
return {'length': length}
|
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def compute_proc_state(nproc=None):
|
def compute_proc_state(nproc=None):
|
||||||
status = dbg()._control.GetExecutionStatus()
|
status = util.dbg._base._control.GetExecutionStatus()
|
||||||
if status == DbgEng.DEBUG_STATUS_BREAK:
|
if status == DbgEng.DEBUG_STATUS_BREAK:
|
||||||
return 'STOPPED'
|
return 'STOPPED'
|
||||||
return 'RUNNING'
|
return 'RUNNING'
|
||||||
|
|
||||||
|
|
||||||
def put_processes(running=False):
|
def put_processes(running=False):
|
||||||
radix = util.get_convenience_variable('output-radix')
|
# | always displays PID in hex
|
||||||
if radix == 'auto':
|
# TODO: I'm not sure about the engine id
|
||||||
radix = 16
|
|
||||||
keys = []
|
keys = []
|
||||||
for i, p in enumerate(util.process_list(running)):
|
# Set running=True to avoid process changes, even while stopped
|
||||||
|
for i, p in enumerate(util.process_list(running=True)):
|
||||||
ipath = PROCESS_PATTERN.format(procnum=i)
|
ipath = PROCESS_PATTERN.format(procnum=i)
|
||||||
keys.append(PROCESS_KEY_PATTERN.format(procnum=i))
|
keys.append(PROCESS_KEY_PATTERN.format(procnum=i))
|
||||||
procobj = STATE.trace.create_object(ipath)
|
procobj = STATE.trace.create_object(ipath)
|
||||||
|
|
||||||
istate = compute_proc_state(i)
|
istate = compute_proc_state(i)
|
||||||
procobj.set_value('_state', istate)
|
procobj.set_value('_state', istate)
|
||||||
if running == False:
|
pid = p[0]
|
||||||
procobj.set_value('_pid', p[0])
|
procobj.set_value('_pid', pid)
|
||||||
pidstr = ('0x{:x}' if radix ==
|
procobj.set_value('_display', '{:x} {:x}'.format(i, pid))
|
||||||
16 else '0{:o}' if radix == 8 else '{}').format(p[0])
|
if len(p) > 1:
|
||||||
procobj.set_value('_display', pidstr)
|
|
||||||
procobj.set_value('Name', str(p[1]))
|
procobj.set_value('Name', str(p[1]))
|
||||||
procobj.set_value('PEB', hex(p[2]))
|
procobj.set_value('PEB', hex(p[2]))
|
||||||
procobj.insert()
|
procobj.insert()
|
||||||
|
@ -860,8 +854,6 @@ def put_processes(running=False):
|
||||||
|
|
||||||
|
|
||||||
def put_state(event_process):
|
def put_state(event_process):
|
||||||
STATE.require_no_tx()
|
|
||||||
STATE.tx = STATE.require_trace().start_tx("state", undoable=False)
|
|
||||||
ipath = PROCESS_PATTERN.format(procnum=event_process)
|
ipath = PROCESS_PATTERN.format(procnum=event_process)
|
||||||
procobj = STATE.trace.create_object(ipath)
|
procobj = STATE.trace.create_object(ipath)
|
||||||
state = compute_proc_state(event_process)
|
state = compute_proc_state(event_process)
|
||||||
|
@ -873,8 +865,6 @@ def put_state(event_process):
|
||||||
threadobj = STATE.trace.create_object(ipath)
|
threadobj = STATE.trace.create_object(ipath)
|
||||||
threadobj.set_value('_state', state)
|
threadobj.set_value('_state', state)
|
||||||
threadobj.insert()
|
threadobj.insert()
|
||||||
STATE.require_tx().commit()
|
|
||||||
STATE.reset_tx()
|
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_put_processes():
|
def ghidra_trace_put_processes():
|
||||||
|
@ -887,10 +877,11 @@ def ghidra_trace_put_processes():
|
||||||
put_processes()
|
put_processes()
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def put_available():
|
def put_available():
|
||||||
radix = util.get_convenience_variable('output-radix')
|
radix = util.get_convenience_variable('output-radix')
|
||||||
keys = []
|
keys = []
|
||||||
result = dbg().cmd(".tlist")
|
result = util.dbg._base.cmd(".tlist")
|
||||||
lines = result.split("\n")
|
lines = result.split("\n")
|
||||||
for i in lines:
|
for i in lines:
|
||||||
i = i.strip()
|
i = i.strip()
|
||||||
|
@ -923,6 +914,7 @@ def ghidra_trace_put_available():
|
||||||
put_available()
|
put_available()
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
||||||
mapper = STATE.trace.memory_mapper
|
mapper = STATE.trace.memory_mapper
|
||||||
bpath = PROC_BREAK_PATTERN.format(procnum=nproc, breaknum=bp.GetId())
|
bpath = PROC_BREAK_PATTERN.format(procnum=nproc, breaknum=bp.GetId())
|
||||||
|
@ -937,7 +929,7 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
||||||
else:
|
else:
|
||||||
address = bp.GetOffset()
|
address = bp.GetOffset()
|
||||||
offset = "%016x" % address
|
offset = "%016x" % address
|
||||||
expr = dbg().get_name_by_offset(address)
|
expr = util.dbg._base.get_name_by_offset(address)
|
||||||
try:
|
try:
|
||||||
tid = bp.GetMatchThreadId()
|
tid = bp.GetMatchThreadId()
|
||||||
tid = "%04x" % tid
|
tid = "%04x" % tid
|
||||||
|
@ -965,7 +957,7 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
||||||
STATE.trace.create_overlay_space(base, addr.space)
|
STATE.trace.create_overlay_space(base, addr.space)
|
||||||
brkobj.set_value('_range', addr.extend(width))
|
brkobj.set_value('_range', addr.extend(width))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error: Could not get range for breakpoint: {}\n".format(e))
|
print("Error: Could not get range for breakpoint: {}".format(e))
|
||||||
else: # I guess it's a catchpoint
|
else: # I guess it's a catchpoint
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -985,6 +977,7 @@ def put_single_breakpoint(bp, ibobj, nproc, ikeys):
|
||||||
ikeys.append(k)
|
ikeys.append(k)
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def put_breakpoints():
|
def put_breakpoints():
|
||||||
target = util.get_target()
|
target = util.get_target()
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
|
@ -992,12 +985,12 @@ def put_breakpoints():
|
||||||
ibobj = STATE.trace.create_object(ibpath)
|
ibobj = STATE.trace.create_object(ibpath)
|
||||||
keys = []
|
keys = []
|
||||||
ikeys = []
|
ikeys = []
|
||||||
ids = [bpid for bpid in dbg().breakpoints]
|
ids = [bpid for bpid in util.dbg._base.breakpoints]
|
||||||
for bpid in ids:
|
for bpid in ids:
|
||||||
try:
|
try:
|
||||||
bp = dbg()._control.GetBreakpointById(bpid)
|
bp = util.dbg._base._control.GetBreakpointById(bpid)
|
||||||
except exception.E_NOINTERFACE_Error:
|
except exception.E_NOINTERFACE_Error:
|
||||||
dbg().breakpoints._remove_stale(bpid)
|
util.dbg._base.breakpoints._remove_stale(bpid)
|
||||||
continue
|
continue
|
||||||
keys.append(PROC_BREAK_KEY_PATTERN.format(breaknum=bpid))
|
keys.append(PROC_BREAK_KEY_PATTERN.format(breaknum=bpid))
|
||||||
put_single_breakpoint(bp, ibobj, nproc, ikeys)
|
put_single_breakpoint(bp, ibobj, nproc, ikeys)
|
||||||
|
@ -1036,10 +1029,11 @@ def ghidra_trace_put_environment():
|
||||||
put_environment()
|
put_environment()
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def put_regions():
|
def put_regions():
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
try:
|
try:
|
||||||
regions = dbg().memory_list()
|
regions = util.dbg._base.memory_list()
|
||||||
except Exception:
|
except Exception:
|
||||||
regions = []
|
regions = []
|
||||||
if len(regions) == 0 and util.selected_thread() != None:
|
if len(regions) == 0 and util.selected_thread() != None:
|
||||||
|
@ -1082,10 +1076,11 @@ def ghidra_trace_put_regions():
|
||||||
put_regions()
|
put_regions()
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def put_modules():
|
def put_modules():
|
||||||
target = util.get_target()
|
target = util.get_target()
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
modules = dbg().module_list()
|
modules = util.dbg._base.module_list()
|
||||||
mapper = STATE.trace.memory_mapper
|
mapper = STATE.trace.memory_mapper
|
||||||
mod_keys = []
|
mod_keys = []
|
||||||
for m in modules:
|
for m in modules:
|
||||||
|
@ -1137,38 +1132,33 @@ def convert_state(t):
|
||||||
return 'RUNNING'
|
return 'RUNNING'
|
||||||
|
|
||||||
|
|
||||||
def convert_tid(t):
|
def compute_thread_display(i, pid, tid, t):
|
||||||
if t[1] == 0:
|
if len(t) > 1:
|
||||||
return t[2]
|
return '{:x} {:x}:{:x} {}'.format(i, pid, tid, t[2])
|
||||||
return t[1]
|
return '{:x} {:x}:{:x}'.format(i, pid, tid)
|
||||||
|
|
||||||
|
|
||||||
def compute_thread_display(tidstr, t):
|
|
||||||
return '[{} {}]'.format(tidstr, t[2])
|
|
||||||
|
|
||||||
|
|
||||||
def put_threads(running=False):
|
def put_threads(running=False):
|
||||||
radix = util.get_convenience_variable('output-radix')
|
# ~ always displays PID:TID in hex
|
||||||
if radix == 'auto':
|
# TODO: I'm not sure about the engine id
|
||||||
radix = 16
|
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
if nproc == None:
|
if nproc is None:
|
||||||
return
|
return
|
||||||
|
pid = util.dbg.pid
|
||||||
|
|
||||||
keys = []
|
keys = []
|
||||||
for i, t in enumerate(util.thread_list(running)):
|
# Set running=True to avoid thread changes, even while stopped
|
||||||
|
for i, t in enumerate(util.thread_list(running=True)):
|
||||||
tpath = THREAD_PATTERN.format(procnum=nproc, tnum=i)
|
tpath = THREAD_PATTERN.format(procnum=nproc, tnum=i)
|
||||||
tobj = STATE.trace.create_object(tpath)
|
tobj = STATE.trace.create_object(tpath)
|
||||||
keys.append(THREAD_KEY_PATTERN.format(tnum=i))
|
keys.append(THREAD_KEY_PATTERN.format(tnum=i))
|
||||||
#tobj.set_value('_state', convert_state(t))
|
|
||||||
if running == False:
|
tid = t[0]
|
||||||
tobj.set_value('_name', t[2])
|
tobj.set_value('_tid', tid)
|
||||||
tid = t[0]
|
tobj.set_value('_short_display',
|
||||||
tobj.set_value('_tid', tid)
|
'{:x} {:x}:{:x}'.format(i, pid, tid))
|
||||||
tidstr = ('0x{:x}' if radix ==
|
tobj.set_value('_display', compute_thread_display(i, pid, tid, t))
|
||||||
16 else '0{:o}' if radix == 8 else '{}').format(tid)
|
if len(t) > 1:
|
||||||
tobj.set_value('_short_display', '[{}.{}:{}]'.format(
|
|
||||||
nproc, i, tidstr))
|
|
||||||
tobj.set_value('_display', compute_thread_display(tidstr, t))
|
|
||||||
tobj.set_value('TEB', hex(t[1]))
|
tobj.set_value('TEB', hex(t[1]))
|
||||||
tobj.set_value('Name', t[2])
|
tobj.set_value('Name', t[2])
|
||||||
tobj.insert()
|
tobj.insert()
|
||||||
|
@ -1199,6 +1189,7 @@ def ghidra_trace_put_threads():
|
||||||
put_threads()
|
put_threads()
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def put_frames():
|
def put_frames():
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
mapper = STATE.trace.memory_mapper
|
mapper = STATE.trace.memory_mapper
|
||||||
|
@ -1207,7 +1198,7 @@ def put_frames():
|
||||||
return
|
return
|
||||||
keys = []
|
keys = []
|
||||||
# f : _DEBUG_STACK_FRAME
|
# f : _DEBUG_STACK_FRAME
|
||||||
for f in dbg().backtrace_list():
|
for f in util.dbg._base.backtrace_list():
|
||||||
fpath = FRAME_PATTERN.format(
|
fpath = FRAME_PATTERN.format(
|
||||||
procnum=nproc, tnum=nthrd, level=f.FrameNumber)
|
procnum=nproc, tnum=nthrd, level=f.FrameNumber)
|
||||||
fobj = STATE.trace.create_object(fpath)
|
fobj = STATE.trace.create_object(fpath)
|
||||||
|
@ -1254,8 +1245,8 @@ def ghidra_trace_put_all():
|
||||||
put_breakpoints()
|
put_breakpoints()
|
||||||
put_available()
|
put_available()
|
||||||
ghidra_trace_putreg()
|
ghidra_trace_putreg()
|
||||||
ghidra_trace_putmem("$pc 1")
|
ghidra_trace_putmem(util.get_pc(), 1)
|
||||||
ghidra_trace_putmem("$sp 1")
|
ghidra_trace_putmem(util.get_sp(), 1)
|
||||||
|
|
||||||
|
|
||||||
def ghidra_trace_install_hooks():
|
def ghidra_trace_install_hooks():
|
||||||
|
@ -1324,34 +1315,42 @@ def ghidra_util_wait_stopped(timeout=1):
|
||||||
raise RuntimeError('Timed out waiting for thread to stop')
|
raise RuntimeError('Timed out waiting for thread to stop')
|
||||||
|
|
||||||
|
|
||||||
def dbg():
|
def get_prompt_text():
|
||||||
return util.get_debugger()
|
try:
|
||||||
|
return util.dbg.get_prompt_text()
|
||||||
|
except util.DebuggeeRunningException:
|
||||||
|
return 'Running>'
|
||||||
|
|
||||||
|
|
||||||
SHOULD_WAIT = ['GO', 'STEP_BRANCH', 'STEP_INTO', 'STEP_OVER']
|
@util.dbg.eng_thread
|
||||||
|
def exec_cmd(cmd):
|
||||||
|
dbg = util.dbg
|
||||||
|
dbg.cmd(cmd, quiet=False)
|
||||||
|
stat = dbg.exec_status()
|
||||||
|
if stat != 'BREAK':
|
||||||
|
dbg.wait()
|
||||||
|
|
||||||
|
|
||||||
def repl():
|
def repl():
|
||||||
print("This is the dbgeng.dll (WinDbg) REPL. To drop to Python3, press Ctrl-C.")
|
print("")
|
||||||
|
print("This is the Windows Debugger REPL. To drop to Python, type .exit")
|
||||||
while True:
|
while True:
|
||||||
# TODO: Implement prompt retrieval in PR to pybag?
|
print(get_prompt_text(), end=' ')
|
||||||
print('dbg> ', end='')
|
|
||||||
try:
|
try:
|
||||||
cmd = input().strip()
|
cmd = input().strip()
|
||||||
if not cmd:
|
if cmd == '':
|
||||||
continue
|
continue
|
||||||
dbg().cmd(cmd, quiet=True)
|
elif cmd == '.exit':
|
||||||
stat = dbg().exec_status()
|
break
|
||||||
if stat != 'BREAK':
|
exec_cmd(cmd)
|
||||||
dbg().wait()
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
# dbg().dispatch_events()
|
|
||||||
except KeyboardInterrupt as e:
|
except KeyboardInterrupt as e:
|
||||||
|
util.dbg.interrupt()
|
||||||
|
except util.DebuggeeRunningException as e:
|
||||||
print("")
|
print("")
|
||||||
print("You have left the dbgeng REPL and are now at the Python3 interpreter.")
|
print("Debuggee is Running. Use Ctrl-C to interrupt.")
|
||||||
print("use repl() to re-enter.")
|
except BaseException as e:
|
||||||
return
|
pass # Error is printed by another mechanism
|
||||||
except:
|
print("")
|
||||||
# Assume cmd() has already output the error
|
print("You have left the Windows Debugger REPL and are now at the Python "
|
||||||
pass
|
"interpreter.")
|
||||||
|
print("To re-enter, type repl()")
|
||||||
|
|
|
@ -13,20 +13,27 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
##
|
##
|
||||||
|
from _ctypes_test import func
|
||||||
|
import functools
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from comtypes.hresult import S_OK
|
||||||
from pybag import pydbg
|
from pybag import pydbg
|
||||||
from pybag.dbgeng.callbacks import EventHandler
|
|
||||||
from pybag.dbgeng import core as DbgEng
|
from pybag.dbgeng import core as DbgEng
|
||||||
from pybag.dbgeng import exception
|
from pybag.dbgeng import exception
|
||||||
|
from pybag.dbgeng.callbacks import EventHandler
|
||||||
from pybag.dbgeng.idebugbreakpoint import DebugBreakpoint
|
from pybag.dbgeng.idebugbreakpoint import DebugBreakpoint
|
||||||
|
|
||||||
from . import commands, util
|
from . import commands, util
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ALL_EVENTS = 0xFFFF
|
ALL_EVENTS = 0xFFFF
|
||||||
|
|
||||||
|
|
||||||
class HookState(object):
|
class HookState(object):
|
||||||
__slots__ = ('installed', 'mem_catchpoint')
|
__slots__ = ('installed', 'mem_catchpoint')
|
||||||
|
|
||||||
|
@ -36,7 +43,8 @@ class HookState(object):
|
||||||
|
|
||||||
|
|
||||||
class ProcessState(object):
|
class ProcessState(object):
|
||||||
__slots__ = ('first', 'regions', 'modules', 'threads', 'breaks', 'watches', 'visited', 'waiting')
|
__slots__ = ('first', 'regions', 'modules', 'threads',
|
||||||
|
'breaks', 'watches', 'visited', 'waiting')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.first = True
|
self.first = True
|
||||||
|
@ -48,10 +56,10 @@ class ProcessState(object):
|
||||||
self.watches = False
|
self.watches = False
|
||||||
# For frames and threads that have already been synced since last stop
|
# For frames and threads that have already been synced since last stop
|
||||||
self.visited = set()
|
self.visited = set()
|
||||||
self.waiting = True
|
self.waiting = False
|
||||||
|
|
||||||
def record(self, description=None):
|
def record(self, description=None):
|
||||||
#print("RECORDING")
|
# print("RECORDING")
|
||||||
first = self.first
|
first = self.first
|
||||||
self.first = False
|
self.first = False
|
||||||
if description is not None:
|
if description is not None:
|
||||||
|
@ -66,8 +74,10 @@ class ProcessState(object):
|
||||||
if thread is not None:
|
if thread is not None:
|
||||||
if first or thread not in self.visited:
|
if first or thread not in self.visited:
|
||||||
commands.putreg()
|
commands.putreg()
|
||||||
commands.putmem("$pc", "1", display_result=False)
|
commands.putmem('0x{:x}'.format(util.get_pc()),
|
||||||
commands.putmem("$sp", "1", display_result=False)
|
"1", display_result=False)
|
||||||
|
commands.putmem('0x{:x}'.format(util.get_sp()),
|
||||||
|
"1", display_result=False)
|
||||||
commands.put_frames()
|
commands.put_frames()
|
||||||
self.visited.add(thread)
|
self.visited.add(thread)
|
||||||
frame = util.selected_frame()
|
frame = util.selected_frame()
|
||||||
|
@ -122,55 +132,90 @@ BRK_STATE = BrkState()
|
||||||
PROC_STATE = {}
|
PROC_STATE = {}
|
||||||
|
|
||||||
|
|
||||||
|
def log_errors(func):
|
||||||
|
'''
|
||||||
|
Wrap a function in a try-except that prints and reraises the
|
||||||
|
exception.
|
||||||
|
|
||||||
|
This is needed because pybag and/or the COM wrappers do not print
|
||||||
|
exceptions that occur during event callbacks.
|
||||||
|
'''
|
||||||
|
@functools.wraps(func)
|
||||||
|
def _func(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
raise
|
||||||
|
return _func
|
||||||
|
|
||||||
|
|
||||||
|
@log_errors
|
||||||
def on_state_changed(*args):
|
def on_state_changed(*args):
|
||||||
#print("ON_STATE_CHANGED")
|
# print("ON_STATE_CHANGED")
|
||||||
|
# print(args)
|
||||||
if args[0] == DbgEng.DEBUG_CES_CURRENT_THREAD:
|
if args[0] == DbgEng.DEBUG_CES_CURRENT_THREAD:
|
||||||
return on_thread_selected(args)
|
return on_thread_selected(args)
|
||||||
elif args[0] == DbgEng.DEBUG_CES_BREAKPOINTS:
|
elif args[0] == DbgEng.DEBUG_CES_BREAKPOINTS:
|
||||||
return on_breakpoint_modified(args)
|
return on_breakpoint_modified(args)
|
||||||
elif args[0] == DbgEng.DEBUG_CES_RADIX:
|
elif args[0] == DbgEng.DEBUG_CES_RADIX:
|
||||||
util.set_convenience_variable('output-radix', args[1])
|
util.set_convenience_variable('output-radix', args[1])
|
||||||
return DbgEng.DEBUG_STATUS_GO
|
return S_OK
|
||||||
elif args[0] == DbgEng.DEBUG_CES_EXECUTION_STATUS:
|
elif args[0] == DbgEng.DEBUG_CES_EXECUTION_STATUS:
|
||||||
|
util.dbg._ces_exec_status(args[1])
|
||||||
proc = util.selected_process()
|
proc = util.selected_process()
|
||||||
if args[1] & DbgEng.DEBUG_STATUS_INSIDE_WAIT:
|
if args[1] & DbgEng.DEBUG_STATUS_INSIDE_WAIT:
|
||||||
PROC_STATE[proc].waiting = True
|
if proc in PROC_STATE:
|
||||||
return DbgEng.DEBUG_STATUS_GO
|
# Process may have exited (so deleted) first
|
||||||
PROC_STATE[proc].waiting = False
|
PROC_STATE[proc].waiting = True
|
||||||
commands.put_state(proc)
|
return S_OK
|
||||||
|
if proc in PROC_STATE:
|
||||||
|
# Process may have exited (so deleted) first.
|
||||||
|
PROC_STATE[proc].waiting = False
|
||||||
|
trace = commands.STATE.trace
|
||||||
|
with commands.STATE.client.batch():
|
||||||
|
with trace.open_tx("State changed proc {}".format(proc)):
|
||||||
|
commands.put_state(proc)
|
||||||
if args[1] == DbgEng.DEBUG_STATUS_BREAK:
|
if args[1] == DbgEng.DEBUG_STATUS_BREAK:
|
||||||
return on_stop(args)
|
return on_stop(args)
|
||||||
else:
|
else:
|
||||||
return on_cont(args)
|
return on_cont(args)
|
||||||
return DbgEng.DEBUG_STATUS_GO
|
return S_OK
|
||||||
|
|
||||||
|
|
||||||
|
@log_errors
|
||||||
def on_debuggee_changed(*args):
|
def on_debuggee_changed(*args):
|
||||||
#print("ON_DEBUGGEE_CHANGED")
|
# print("ON_DEBUGGEE_CHANGED: args={}".format(args))
|
||||||
|
# sys.stdout.flush()
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
return
|
return S_OK
|
||||||
if args[1] == DbgEng.DEBUG_CDS_REGISTERS:
|
if args[0] == DbgEng.DEBUG_CDS_REGISTERS:
|
||||||
on_register_changed(args[0][1])
|
on_register_changed(args[1])
|
||||||
#if args[1] == DbgEng.DEBUG_CDS_DATA:
|
if args[0] == DbgEng.DEBUG_CDS_DATA:
|
||||||
# on_memory_changed(args[0][1])
|
on_memory_changed(args[1])
|
||||||
return DbgEng.DEBUG_STATUS_GO
|
return S_OK
|
||||||
|
|
||||||
|
|
||||||
|
@log_errors
|
||||||
def on_session_status_changed(*args):
|
def on_session_status_changed(*args):
|
||||||
#print("ON_STATUS_CHANGED")
|
print("ON_STATUS_CHANGED: args={}".format(args))
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
return
|
return
|
||||||
if args[0] == DbgEng.DEBUG_SESSION_ACTIVE or args[0] == DbgEng.DEBUG_SSESION_REBOOT:
|
if args[0] == DbgEng.DEBUG_SESSION_ACTIVE or args[0] == DbgEng.DEBUG_SESSION_REBOOT:
|
||||||
with commands.STATE.client.batch():
|
with commands.STATE.client.batch():
|
||||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
with trace.open_tx("New Session {}".format(util.selected_process())):
|
||||||
commands.put_processes()
|
commands.put_processes()
|
||||||
return DbgEng.DEBUG_STATUS_GO
|
return DbgEng.DEBUG_STATUS_GO
|
||||||
|
|
||||||
|
|
||||||
|
@log_errors
|
||||||
def on_symbol_state_changed(*args):
|
def on_symbol_state_changed(*args):
|
||||||
#print("ON_SYMBOL_STATE_CHANGED")
|
# print("ON_SYMBOL_STATE_CHANGED")
|
||||||
|
proc = util.selected_process()
|
||||||
|
if proc not in PROC_STATE:
|
||||||
|
return
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
return
|
return
|
||||||
|
@ -179,20 +224,22 @@ def on_symbol_state_changed(*args):
|
||||||
return DbgEng.DEBUG_STATUS_GO
|
return DbgEng.DEBUG_STATUS_GO
|
||||||
|
|
||||||
|
|
||||||
|
@log_errors
|
||||||
def on_system_error(*args):
|
def on_system_error(*args):
|
||||||
print("ON_SYSTEM_ERROR")
|
print("ON_SYSTEM_ERROR: args={}".format(args))
|
||||||
print(hex(args[0]))
|
# print(hex(args[0]))
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
return
|
return
|
||||||
with commands.STATE.client.batch():
|
with commands.STATE.client.batch():
|
||||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
with trace.open_tx("System Error {}".format(util.selected_process())):
|
||||||
commands.put_processes()
|
commands.put_processes()
|
||||||
return DbgEng.DEBUG_STATUS_BREAK
|
return DbgEng.DEBUG_STATUS_BREAK
|
||||||
|
|
||||||
|
|
||||||
|
@log_errors
|
||||||
def on_new_process(*args):
|
def on_new_process(*args):
|
||||||
#print("ON_NEW_PROCESS")
|
# print("ON_NEW_PROCESS")
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
return
|
return
|
||||||
|
@ -203,7 +250,7 @@ def on_new_process(*args):
|
||||||
|
|
||||||
|
|
||||||
def on_process_selected():
|
def on_process_selected():
|
||||||
#print("PROCESS_SELECTED")
|
# print("PROCESS_SELECTED")
|
||||||
proc = util.selected_process()
|
proc = util.selected_process()
|
||||||
if proc not in PROC_STATE:
|
if proc not in PROC_STATE:
|
||||||
return
|
return
|
||||||
|
@ -216,8 +263,9 @@ def on_process_selected():
|
||||||
commands.activate()
|
commands.activate()
|
||||||
|
|
||||||
|
|
||||||
|
@log_errors
|
||||||
def on_process_deleted(*args):
|
def on_process_deleted(*args):
|
||||||
#print("ON_PROCESS_DELETED")
|
# print("ON_PROCESS_DELETED")
|
||||||
proc = args[0]
|
proc = args[0]
|
||||||
on_exited(proc)
|
on_exited(proc)
|
||||||
if proc in PROC_STATE:
|
if proc in PROC_STATE:
|
||||||
|
@ -231,8 +279,9 @@ def on_process_deleted(*args):
|
||||||
return DbgEng.DEBUG_STATUS_BREAK
|
return DbgEng.DEBUG_STATUS_BREAK
|
||||||
|
|
||||||
|
|
||||||
|
@log_errors
|
||||||
def on_threads_changed(*args):
|
def on_threads_changed(*args):
|
||||||
#print("ON_THREADS_CHANGED")
|
# print("ON_THREADS_CHANGED")
|
||||||
proc = util.selected_process()
|
proc = util.selected_process()
|
||||||
if proc not in PROC_STATE:
|
if proc not in PROC_STATE:
|
||||||
return DbgEng.DEBUG_STATUS_GO
|
return DbgEng.DEBUG_STATUS_GO
|
||||||
|
@ -241,7 +290,8 @@ def on_threads_changed(*args):
|
||||||
|
|
||||||
|
|
||||||
def on_thread_selected(*args):
|
def on_thread_selected(*args):
|
||||||
#print("THREAD_SELECTED")
|
# print("THREAD_SELECTED: args={}".format(args))
|
||||||
|
# sys.stdout.flush()
|
||||||
nthrd = args[0][1]
|
nthrd = args[0][1]
|
||||||
nproc = util.selected_process()
|
nproc = util.selected_process()
|
||||||
if nproc not in PROC_STATE:
|
if nproc not in PROC_STATE:
|
||||||
|
@ -261,7 +311,7 @@ def on_thread_selected(*args):
|
||||||
|
|
||||||
|
|
||||||
def on_register_changed(regnum):
|
def on_register_changed(regnum):
|
||||||
#print("REGISTER_CHANGED")
|
# print("REGISTER_CHANGED")
|
||||||
proc = util.selected_process()
|
proc = util.selected_process()
|
||||||
if proc not in PROC_STATE:
|
if proc not in PROC_STATE:
|
||||||
return
|
return
|
||||||
|
@ -274,7 +324,25 @@ def on_register_changed(regnum):
|
||||||
commands.activate()
|
commands.activate()
|
||||||
|
|
||||||
|
|
||||||
|
def on_memory_changed(space):
|
||||||
|
if space != DbgEng.DEBUG_DATA_SPACE_VIRTUAL:
|
||||||
|
return
|
||||||
|
proc = util.selected_process()
|
||||||
|
if proc not in PROC_STATE:
|
||||||
|
return
|
||||||
|
trace = commands.STATE.trace
|
||||||
|
if trace is None:
|
||||||
|
return
|
||||||
|
# Not great, but invalidate the whole space
|
||||||
|
# UI will only re-fetch what it needs
|
||||||
|
# But, some observations will not be recovered
|
||||||
|
with commands.STATE.client.batch():
|
||||||
|
with trace.open_tx("Memory changed"):
|
||||||
|
commands.putmem_state(0, 2**64, 'unknown')
|
||||||
|
|
||||||
|
|
||||||
def on_cont(*args):
|
def on_cont(*args):
|
||||||
|
# print("ON CONT")
|
||||||
proc = util.selected_process()
|
proc = util.selected_process()
|
||||||
if proc not in PROC_STATE:
|
if proc not in PROC_STATE:
|
||||||
return
|
return
|
||||||
|
@ -289,13 +357,14 @@ def on_cont(*args):
|
||||||
|
|
||||||
|
|
||||||
def on_stop(*args):
|
def on_stop(*args):
|
||||||
|
# print("ON STOP")
|
||||||
proc = util.selected_process()
|
proc = util.selected_process()
|
||||||
if proc not in PROC_STATE:
|
if proc not in PROC_STATE:
|
||||||
print("not in state")
|
# print("not in state")
|
||||||
return
|
return
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
print("no trace")
|
# print("no trace")
|
||||||
return
|
return
|
||||||
state = PROC_STATE[proc]
|
state = PROC_STATE[proc]
|
||||||
state.visited.clear()
|
state.visited.clear()
|
||||||
|
@ -308,7 +377,7 @@ def on_stop(*args):
|
||||||
|
|
||||||
def on_exited(proc):
|
def on_exited(proc):
|
||||||
if proc not in PROC_STATE:
|
if proc not in PROC_STATE:
|
||||||
print("not in state")
|
# print("not in state")
|
||||||
return
|
return
|
||||||
trace = commands.STATE.trace
|
trace = commands.STATE.trace
|
||||||
if trace is None:
|
if trace is None:
|
||||||
|
@ -323,8 +392,9 @@ def on_exited(proc):
|
||||||
commands.activate()
|
commands.activate()
|
||||||
|
|
||||||
|
|
||||||
|
@log_errors
|
||||||
def on_modules_changed(*args):
|
def on_modules_changed(*args):
|
||||||
#print("ON_MODULES_CHANGED")
|
# print("ON_MODULES_CHANGED")
|
||||||
proc = util.selected_process()
|
proc = util.selected_process()
|
||||||
if proc not in PROC_STATE:
|
if proc not in PROC_STATE:
|
||||||
return DbgEng.DEBUG_STATUS_GO
|
return DbgEng.DEBUG_STATUS_GO
|
||||||
|
@ -333,6 +403,7 @@ def on_modules_changed(*args):
|
||||||
|
|
||||||
|
|
||||||
def on_breakpoint_created(bp):
|
def on_breakpoint_created(bp):
|
||||||
|
# print("ON_BREAKPOINT_CREATED")
|
||||||
proc = util.selected_process()
|
proc = util.selected_process()
|
||||||
if proc not in PROC_STATE:
|
if proc not in PROC_STATE:
|
||||||
return
|
return
|
||||||
|
@ -350,7 +421,7 @@ def on_breakpoint_created(bp):
|
||||||
|
|
||||||
|
|
||||||
def on_breakpoint_modified(*args):
|
def on_breakpoint_modified(*args):
|
||||||
#print("BREAKPOINT_MODIFIED")
|
# print("BREAKPOINT_MODIFIED")
|
||||||
proc = util.selected_process()
|
proc = util.selected_process()
|
||||||
if proc not in PROC_STATE:
|
if proc not in PROC_STATE:
|
||||||
return
|
return
|
||||||
|
@ -362,9 +433,9 @@ def on_breakpoint_modified(*args):
|
||||||
ibobj = trace.create_object(ibpath)
|
ibobj = trace.create_object(ibpath)
|
||||||
bpid = args[0][1]
|
bpid = args[0][1]
|
||||||
try:
|
try:
|
||||||
bp = dbg()._control.GetBreakpointById(bpid)
|
bp = util.dbg._base._control.GetBreakpointById(bpid)
|
||||||
except exception.E_NOINTERFACE_Error:
|
except exception.E_NOINTERFACE_Error:
|
||||||
dbg().breakpoints._remove_stale(bpid)
|
util.dbg._base.breakpoints._remove_stale(bpid)
|
||||||
return on_breakpoint_deleted(bpid)
|
return on_breakpoint_deleted(bpid)
|
||||||
return on_breakpoint_created(bp)
|
return on_breakpoint_created(bp)
|
||||||
|
|
||||||
|
@ -383,32 +454,26 @@ def on_breakpoint_deleted(bpid):
|
||||||
trace.proxy_object_path(bpath).remove(tree=True)
|
trace.proxy_object_path(bpath).remove(tree=True)
|
||||||
|
|
||||||
|
|
||||||
|
@log_errors
|
||||||
def on_breakpoint_hit(*args):
|
def on_breakpoint_hit(*args):
|
||||||
trace = commands.STATE.trace
|
# print("ON_BREAKPOINT_HIT: args={}".format(args))
|
||||||
if trace is None:
|
return DbgEng.DEBUG_STATUS_BREAK
|
||||||
return
|
|
||||||
with commands.STATE.client.batch():
|
|
||||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
|
||||||
commands.put_processes()
|
|
||||||
return DbgEng.DEBUG_STATUS_GO
|
|
||||||
|
|
||||||
|
|
||||||
|
@log_errors
|
||||||
def on_exception(*args):
|
def on_exception(*args):
|
||||||
trace = commands.STATE.trace
|
# print("ON_EXCEPTION: args={}".format(args))
|
||||||
if trace is None:
|
return DbgEng.DEBUG_STATUS_BREAK
|
||||||
return
|
|
||||||
with commands.STATE.client.batch():
|
|
||||||
with trace.open_tx("New Process {}".format(util.selected_process())):
|
|
||||||
commands.put_processes()
|
|
||||||
return DbgEng.DEBUG_STATUS_GO
|
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def install_hooks():
|
def install_hooks():
|
||||||
|
# print("Installing hooks")
|
||||||
if HOOK_STATE.installed:
|
if HOOK_STATE.installed:
|
||||||
return
|
return
|
||||||
HOOK_STATE.installed = True
|
HOOK_STATE.installed = True
|
||||||
|
|
||||||
events = dbg().events
|
events = util.dbg._base.events
|
||||||
|
|
||||||
events.engine_state(handler=on_state_changed)
|
events.engine_state(handler=on_state_changed)
|
||||||
events.debuggee_state(handler=on_debuggee_changed)
|
events.debuggee_state(handler=on_debuggee_changed)
|
||||||
|
@ -423,19 +488,23 @@ def install_hooks():
|
||||||
events.module_load(handler=on_modules_changed)
|
events.module_load(handler=on_modules_changed)
|
||||||
events.unload_module(handler=on_modules_changed)
|
events.unload_module(handler=on_modules_changed)
|
||||||
|
|
||||||
#events.breakpoint(handler=on_breakpoint_hit)
|
events.breakpoint(handler=on_breakpoint_hit)
|
||||||
#events.exception(handler=on_exception)
|
events.exception(handler=on_exception)
|
||||||
|
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
def remove_hooks():
|
def remove_hooks():
|
||||||
|
# print("Removing hooks")
|
||||||
if not HOOK_STATE.installed:
|
if not HOOK_STATE.installed:
|
||||||
return
|
return
|
||||||
HOOK_STATE.installed = False
|
HOOK_STATE.installed = False
|
||||||
dbg()._reset_callbacks()
|
util.dbg._base._reset_callbacks()
|
||||||
|
|
||||||
|
|
||||||
def enable_current_process():
|
def enable_current_process():
|
||||||
|
# print("Enable current process")
|
||||||
proc = util.selected_process()
|
proc = util.selected_process()
|
||||||
|
# print("proc: {}".format(proc))
|
||||||
PROC_STATE[proc] = ProcessState()
|
PROC_STATE[proc] = ProcessState()
|
||||||
|
|
||||||
|
|
||||||
|
@ -444,6 +513,3 @@ def disable_current_process():
|
||||||
if proc in PROC_STATE:
|
if proc in PROC_STATE:
|
||||||
# Silently ignore already disabled
|
# Silently ignore already disabled
|
||||||
del PROC_STATE[proc]
|
del PROC_STATE[proc]
|
||||||
|
|
||||||
def dbg():
|
|
||||||
return util.get_debugger()
|
|
||||||
|
|
|
@ -14,21 +14,22 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
##
|
##
|
||||||
from concurrent.futures import Future, ThreadPoolExecutor
|
from concurrent.futures import Future, ThreadPoolExecutor
|
||||||
|
from contextlib import redirect_stdout
|
||||||
|
from io import StringIO
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from pybag import pydbg
|
||||||
|
from pybag.dbgeng import core as DbgEng, exception
|
||||||
|
|
||||||
from ghidratrace import sch
|
from ghidratrace import sch
|
||||||
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
|
||||||
|
|
||||||
from pybag import pydbg
|
|
||||||
from pybag.dbgeng import core as DbgEng
|
|
||||||
|
|
||||||
from . import util, commands
|
from . import util, commands
|
||||||
from contextlib import redirect_stdout
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
|
|
||||||
REGISTRY = MethodRegistry(ThreadPoolExecutor(max_workers=1))
|
REGISTRY = MethodRegistry(ThreadPoolExecutor(
|
||||||
|
max_workers=1, thread_name_prefix='MethodRegistry'))
|
||||||
|
|
||||||
|
|
||||||
def extre(base, ext):
|
def extre(base, ext):
|
||||||
|
@ -85,11 +86,12 @@ def find_proc_by_obj(object):
|
||||||
|
|
||||||
def find_proc_by_procbreak_obj(object):
|
def find_proc_by_procbreak_obj(object):
|
||||||
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
|
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
|
||||||
"a BreakpointLocationContainer")
|
"a BreakpointLocationContainer")
|
||||||
|
|
||||||
|
|
||||||
def find_proc_by_procwatch_obj(object):
|
def find_proc_by_procwatch_obj(object):
|
||||||
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
|
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
|
||||||
"a WatchpointContainer")
|
"a WatchpointContainer")
|
||||||
|
|
||||||
|
|
||||||
def find_proc_by_env_obj(object):
|
def find_proc_by_env_obj(object):
|
||||||
|
@ -178,25 +180,28 @@ def find_bpt_by_obj(object):
|
||||||
|
|
||||||
shared_globals = dict()
|
shared_globals = dict()
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method
|
@REGISTRY.method
|
||||||
|
# @util.dbg.eng_thread
|
||||||
def execute(cmd: str, to_string: bool=False):
|
def execute(cmd: str, to_string: bool=False):
|
||||||
"""Execute a CLI command."""
|
"""Execute a Python3 command or script."""
|
||||||
#print("***{}***".format(cmd))
|
# print("***{}***".format(cmd))
|
||||||
#sys.stderr.flush()
|
# sys.stderr.flush()
|
||||||
#sys.stdout.flush()
|
# sys.stdout.flush()
|
||||||
if to_string:
|
if to_string:
|
||||||
data = StringIO()
|
data = StringIO()
|
||||||
with redirect_stdout(data):
|
with redirect_stdout(data):
|
||||||
exec("{}".format(cmd), shared_globals)
|
exec(cmd, shared_globals)
|
||||||
return data.getvalue()
|
return data.getvalue()
|
||||||
else:
|
else:
|
||||||
exec("{}".format(cmd), shared_globals)
|
exec(cmd, shared_globals)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method
|
@REGISTRY.method
|
||||||
|
# @util.dbg.eng_thread
|
||||||
def evaluate(expr: str):
|
def evaluate(expr: str):
|
||||||
"""Execute a CLI command."""
|
"""Evaluate a Python3 expression."""
|
||||||
return str(eval("{}".format(expr), shared_globals))
|
return str(eval(expr, shared_globals))
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='refresh')
|
@REGISTRY.method(action='refresh')
|
||||||
|
@ -241,6 +246,7 @@ def refresh_environment(node: sch.Schema('Environment')):
|
||||||
with commands.open_tracked_tx('Refresh Environment'):
|
with commands.open_tracked_tx('Refresh Environment'):
|
||||||
commands.ghidra_trace_put_environment()
|
commands.ghidra_trace_put_environment()
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='refresh')
|
@REGISTRY.method(action='refresh')
|
||||||
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
||||||
"""Refresh the list of threads in the process."""
|
"""Refresh the list of threads in the process."""
|
||||||
|
@ -287,6 +293,7 @@ def activate_process(process: sch.Schema('Process')):
|
||||||
"""Switch to the process."""
|
"""Switch to the process."""
|
||||||
find_proc_by_obj(process)
|
find_proc_by_obj(process)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='activate')
|
@REGISTRY.method(action='activate')
|
||||||
def activate_thread(thread: sch.Schema('Thread')):
|
def activate_thread(thread: sch.Schema('Thread')):
|
||||||
"""Switch to the thread."""
|
"""Switch to the thread."""
|
||||||
|
@ -300,46 +307,54 @@ def activate_frame(frame: sch.Schema('StackFrame')):
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='delete')
|
@REGISTRY.method(action='delete')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def remove_process(process: sch.Schema('Process')):
|
def remove_process(process: sch.Schema('Process')):
|
||||||
"""Remove the process."""
|
"""Remove the process."""
|
||||||
find_proc_by_obj(process)
|
find_proc_by_obj(process)
|
||||||
dbg().detach()
|
dbg().detach_proc()
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='connect')
|
@REGISTRY.method(action='connect')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def target(process: sch.Schema('Process'), spec: str):
|
def target(process: sch.Schema('Process'), spec: str):
|
||||||
"""Connect to a target machine or process."""
|
"""Connect to a target machine or process."""
|
||||||
find_proc_by_obj(process)
|
find_proc_by_obj(process)
|
||||||
dbg().attach(spec)
|
dbg().attach_kernel(spec)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='attach')
|
@REGISTRY.method(action='attach')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def attach_obj(target: sch.Schema('Attachable')):
|
def attach_obj(target: sch.Schema('Attachable')):
|
||||||
"""Attach the process to the given target."""
|
"""Attach the process to the given target."""
|
||||||
pid = find_availpid_by_obj(target)
|
pid = find_availpid_by_obj(target)
|
||||||
dbg().attach(pid)
|
dbg().attach_proc(pid)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='attach')
|
@REGISTRY.method(action='attach')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def attach_pid(pid: int):
|
def attach_pid(pid: int):
|
||||||
"""Attach the process to the given target."""
|
"""Attach the process to the given target."""
|
||||||
dbg().attach(pid)
|
dbg().attach_proc(pid)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='attach')
|
@REGISTRY.method(action='attach')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def attach_name(process: sch.Schema('Process'), name: str):
|
def attach_name(process: sch.Schema('Process'), name: str):
|
||||||
"""Attach the process to the given target."""
|
"""Attach the process to the given target."""
|
||||||
dbg().atach(name)
|
dbg().attach_proc(name)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method
|
@REGISTRY.method
|
||||||
|
@util.dbg.eng_thread
|
||||||
def detach(process: sch.Schema('Process')):
|
def detach(process: sch.Schema('Process')):
|
||||||
"""Detach the process's target."""
|
"""Detach the process's target."""
|
||||||
dbg().detach()
|
dbg().detach_proc()
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='launch')
|
@REGISTRY.method(action='launch')
|
||||||
def launch_loader(
|
def launch_loader(
|
||||||
file: ParamDesc(str, display='File'),
|
file: ParamDesc(str, display='File'),
|
||||||
args: ParamDesc(str, display='Arguments')=''):
|
args: ParamDesc(str, display='Arguments')=''):
|
||||||
"""
|
"""
|
||||||
Start a native process with the given command line, stopping at the ntdll initial breakpoint.
|
Start a native process with the given command line, stopping at the ntdll initial breakpoint.
|
||||||
"""
|
"""
|
||||||
|
@ -351,65 +366,72 @@ def launch_loader(
|
||||||
|
|
||||||
@REGISTRY.method(action='launch')
|
@REGISTRY.method(action='launch')
|
||||||
def launch(
|
def launch(
|
||||||
timeout: ParamDesc(int, display='Timeout'),
|
|
||||||
file: ParamDesc(str, display='File'),
|
file: ParamDesc(str, display='File'),
|
||||||
args: ParamDesc(str, display='Arguments')=''):
|
args: ParamDesc(str, display='Arguments')='',
|
||||||
|
initial_break: ParamDesc(bool, display='Initial Break')=True,
|
||||||
|
timeout: ParamDesc(int, display='Timeout')=-1):
|
||||||
"""
|
"""
|
||||||
Run a native process with the given command line.
|
Run a native process with the given command line.
|
||||||
"""
|
"""
|
||||||
command = file
|
command = file
|
||||||
if args != None:
|
if args != None:
|
||||||
command += " "+args
|
command += " "+args
|
||||||
commands.ghidra_trace_create(command, initial_break=False, timeout=timeout, start_trace=False)
|
commands.ghidra_trace_create(
|
||||||
|
command, initial_break=initial_break, timeout=timeout, start_trace=False)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method
|
@REGISTRY.method
|
||||||
|
@util.dbg.eng_thread
|
||||||
def kill(process: sch.Schema('Process')):
|
def kill(process: sch.Schema('Process')):
|
||||||
"""Kill execution of the process."""
|
"""Kill execution of the process."""
|
||||||
dbg().terminate()
|
commands.ghidra_trace_kill()
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(name='continue', action='resume')
|
@REGISTRY.method(action='resume')
|
||||||
def _continue(process: sch.Schema('Process')):
|
def go(process: sch.Schema('Process')):
|
||||||
"""Continue execution of the process."""
|
"""Continue execution of the process."""
|
||||||
dbg().go()
|
util.dbg.run_async(lambda: dbg().go())
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method
|
@REGISTRY.method
|
||||||
def interrupt(process: sch.Schema('Process')):
|
def interrupt(process: sch.Schema('Process')):
|
||||||
"""Interrupt the execution of the debugged program."""
|
"""Interrupt the execution of the debugged program."""
|
||||||
dbg()._control.SetInterrupt(DbgEng.DEBUG_INTERRUPT_ACTIVE)
|
# SetInterrupt is reentrant, so bypass the thread checks
|
||||||
|
util.dbg._protected_base._control.SetInterrupt(
|
||||||
|
DbgEng.DEBUG_INTERRUPT_ACTIVE)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='step_into')
|
@REGISTRY.method(action='step_into')
|
||||||
def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||||
"""Step one instruction exactly."""
|
"""Step one instruction exactly."""
|
||||||
find_thread_by_obj(thread)
|
find_thread_by_obj(thread)
|
||||||
dbg().stepi(n)
|
util.dbg.run_async(lambda: dbg().stepi(n))
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='step_over')
|
@REGISTRY.method(action='step_over')
|
||||||
def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
|
||||||
"""Step one instruction, but proceed through subroutine calls."""
|
"""Step one instruction, but proceed through subroutine calls."""
|
||||||
find_thread_by_obj(thread)
|
find_thread_by_obj(thread)
|
||||||
dbg().stepo(n)
|
util.dbg.run_async(lambda: dbg().stepo(n))
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='step_out')
|
@REGISTRY.method(action='step_out')
|
||||||
def step_out(thread: sch.Schema('Thread')):
|
def step_out(thread: sch.Schema('Thread')):
|
||||||
"""Execute until the current stack frame returns."""
|
"""Execute until the current stack frame returns."""
|
||||||
find_thread_by_obj(thread)
|
find_thread_by_obj(thread)
|
||||||
dbg().stepout()
|
util.dbg.run_async(lambda: dbg().stepout())
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='step_to')
|
@REGISTRY.method(action='step_to')
|
||||||
def step_to(thread: sch.Schema('Thread'), address: Address, max=None):
|
def step_to(thread: sch.Schema('Thread'), address: Address, max=None):
|
||||||
"""Continue execution up to the given address."""
|
"""Continue execution up to the given address."""
|
||||||
find_thread_by_obj(thread)
|
find_thread_by_obj(thread)
|
||||||
return dbg().stepto(address.offset, max)
|
# TODO: The address may need mapping.
|
||||||
|
util.dbg.run_async(lambda: dbg().stepto(address.offset, max))
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_sw_execute')
|
@REGISTRY.method(action='break_sw_execute')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def break_address(process: sch.Schema('Process'), address: Address):
|
def break_address(process: sch.Schema('Process'), address: Address):
|
||||||
"""Set a breakpoint."""
|
"""Set a breakpoint."""
|
||||||
find_proc_by_obj(process)
|
find_proc_by_obj(process)
|
||||||
|
@ -417,6 +439,7 @@ def break_address(process: sch.Schema('Process'), address: Address):
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_sw_execute')
|
@REGISTRY.method(action='break_sw_execute')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def break_expression(expression: str):
|
def break_expression(expression: str):
|
||||||
"""Set a breakpoint."""
|
"""Set a breakpoint."""
|
||||||
# TODO: Escape?
|
# TODO: Escape?
|
||||||
|
@ -424,6 +447,7 @@ def break_expression(expression: str):
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_hw_execute')
|
@REGISTRY.method(action='break_hw_execute')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def break_hw_address(process: sch.Schema('Process'), address: Address):
|
def break_hw_address(process: sch.Schema('Process'), address: Address):
|
||||||
"""Set a hardware-assisted breakpoint."""
|
"""Set a hardware-assisted breakpoint."""
|
||||||
find_proc_by_obj(process)
|
find_proc_by_obj(process)
|
||||||
|
@ -431,12 +455,14 @@ def break_hw_address(process: sch.Schema('Process'), address: Address):
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_hw_execute')
|
@REGISTRY.method(action='break_hw_execute')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def break_hw_expression(expression: str):
|
def break_hw_expression(expression: str):
|
||||||
"""Set a hardware-assisted breakpoint."""
|
"""Set a hardware-assisted breakpoint."""
|
||||||
dbg().ba(expr=expression)
|
dbg().ba(expr=expression)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_read')
|
@REGISTRY.method(action='break_read')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def break_read_range(process: sch.Schema('Process'), range: AddressRange):
|
def break_read_range(process: sch.Schema('Process'), range: AddressRange):
|
||||||
"""Set a read watchpoint."""
|
"""Set a read watchpoint."""
|
||||||
find_proc_by_obj(process)
|
find_proc_by_obj(process)
|
||||||
|
@ -444,12 +470,14 @@ def break_read_range(process: sch.Schema('Process'), range: AddressRange):
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_read')
|
@REGISTRY.method(action='break_read')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def break_read_expression(expression: str):
|
def break_read_expression(expression: str):
|
||||||
"""Set a read watchpoint."""
|
"""Set a read watchpoint."""
|
||||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ)
|
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_write')
|
@REGISTRY.method(action='break_write')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def break_write_range(process: sch.Schema('Process'), range: AddressRange):
|
def break_write_range(process: sch.Schema('Process'), range: AddressRange):
|
||||||
"""Set a watchpoint."""
|
"""Set a watchpoint."""
|
||||||
find_proc_by_obj(process)
|
find_proc_by_obj(process)
|
||||||
|
@ -457,25 +485,30 @@ def break_write_range(process: sch.Schema('Process'), range: AddressRange):
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_write')
|
@REGISTRY.method(action='break_write')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def break_write_expression(expression: str):
|
def break_write_expression(expression: str):
|
||||||
"""Set a watchpoint."""
|
"""Set a watchpoint."""
|
||||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_WRITE)
|
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_WRITE)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_access')
|
@REGISTRY.method(action='break_access')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def break_access_range(process: sch.Schema('Process'), range: AddressRange):
|
def break_access_range(process: sch.Schema('Process'), range: AddressRange):
|
||||||
"""Set an access watchpoint."""
|
"""Set an access watchpoint."""
|
||||||
find_proc_by_obj(process)
|
find_proc_by_obj(process)
|
||||||
dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_READ|DbgEng.DEBUG_BREAK_WRITE)
|
dbg().ba(expr=range.min, size=range.length(),
|
||||||
|
access=DbgEng.DEBUG_BREAK_READ | DbgEng.DEBUG_BREAK_WRITE)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='break_access')
|
@REGISTRY.method(action='break_access')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def break_access_expression(expression: str):
|
def break_access_expression(expression: str):
|
||||||
"""Set an access watchpoint."""
|
"""Set an access watchpoint."""
|
||||||
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ|DbgEng.DEBUG_BREAK_WRITE)
|
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ | DbgEng.DEBUG_BREAK_WRITE)
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='toggle')
|
@REGISTRY.method(action='toggle')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
||||||
"""Toggle a breakpoint."""
|
"""Toggle a breakpoint."""
|
||||||
bpt = find_bpt_by_obj(breakpoint)
|
bpt = find_bpt_by_obj(breakpoint)
|
||||||
|
@ -486,6 +519,7 @@ def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method(action='delete')
|
@REGISTRY.method(action='delete')
|
||||||
|
@util.dbg.eng_thread
|
||||||
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
||||||
"""Delete a breakpoint."""
|
"""Delete a breakpoint."""
|
||||||
bpt = find_bpt_by_obj(breakpoint)
|
bpt = find_bpt_by_obj(breakpoint)
|
||||||
|
@ -495,14 +529,20 @@ def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
|
||||||
@REGISTRY.method
|
@REGISTRY.method
|
||||||
def read_mem(process: sch.Schema('Process'), range: AddressRange):
|
def read_mem(process: sch.Schema('Process'), range: AddressRange):
|
||||||
"""Read memory."""
|
"""Read memory."""
|
||||||
|
# print("READ_MEM: process={}, range={}".format(process, range))
|
||||||
nproc = find_proc_by_obj(process)
|
nproc = find_proc_by_obj(process)
|
||||||
offset_start = process.trace.memory_mapper.map_back(
|
offset_start = process.trace.memory_mapper.map_back(
|
||||||
nproc, Address(range.space, range.min))
|
nproc, Address(range.space, range.min))
|
||||||
with commands.open_tracked_tx('Read Memory'):
|
with commands.open_tracked_tx('Read Memory'):
|
||||||
dbg().read(range.min, range.length())
|
result = commands.put_bytes(
|
||||||
|
offset_start, offset_start + range.length() - 1, pages=True, display_result=False)
|
||||||
|
if result['count'] == 0:
|
||||||
|
commands.putmem_state(
|
||||||
|
offset_start, offset_start+range.length() - 1, 'error')
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method
|
@REGISTRY.method
|
||||||
|
@util.dbg.eng_thread
|
||||||
def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
|
def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
|
||||||
"""Write memory."""
|
"""Write memory."""
|
||||||
nproc = find_proc_by_obj(process)
|
nproc = find_proc_by_obj(process)
|
||||||
|
@ -511,6 +551,7 @@ def write_mem(process: sch.Schema('Process'), address: Address, data: bytes):
|
||||||
|
|
||||||
|
|
||||||
@REGISTRY.method
|
@REGISTRY.method
|
||||||
|
@util.dbg.eng_thread
|
||||||
def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
|
def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
|
||||||
"""Write a register."""
|
"""Write a register."""
|
||||||
util.select_frame()
|
util.select_frame()
|
||||||
|
@ -519,4 +560,4 @@ def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
|
||||||
|
|
||||||
|
|
||||||
def dbg():
|
def dbg():
|
||||||
return util.get_debugger()
|
return util.dbg._base
|
||||||
|
|
|
@ -96,7 +96,8 @@
|
||||||
<attribute name="_spec" schema="BreakpointSpec" />
|
<attribute name="_spec" schema="BreakpointSpec" />
|
||||||
<attribute name="_range" schema="RANGE" hidden="yes" />
|
<attribute name="_range" schema="RANGE" hidden="yes" />
|
||||||
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
<attribute name="_modified" schema="BOOL" hidden="yes" />
|
||||||
<attribute name="_enabled" schema="BOOL" required="yes" hidden="yes" />
|
<attribute name="Enabled" schema="BOOL" required="yes" />
|
||||||
|
<attribute-alias from="_enabled" to="Enabled" />
|
||||||
<attribute name="Commands" schema="STRING" />
|
<attribute name="Commands" schema="STRING" />
|
||||||
<attribute name="Condition" schema="STRING" />
|
<attribute name="Condition" schema="STRING" />
|
||||||
<attribute name="Hit Count" schema="INT" />
|
<attribute name="Hit Count" schema="INT" />
|
||||||
|
|
|
@ -14,49 +14,397 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
##
|
##
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from concurrent.futures import Future
|
||||||
|
import concurrent.futures
|
||||||
|
from ctypes import *
|
||||||
|
import functools
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
|
import queue
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
import traceback
|
||||||
|
|
||||||
from ctypes import *
|
from comtypes import CoClass, GUID
|
||||||
from pybag import pydbg
|
import comtypes
|
||||||
|
from comtypes.hresult import S_OK
|
||||||
|
from pybag import pydbg, userdbg, kerneldbg, crashdbg
|
||||||
from pybag.dbgeng import core as DbgEng
|
from pybag.dbgeng import core as DbgEng
|
||||||
from pybag.dbgeng import exception
|
from pybag.dbgeng import exception
|
||||||
from pybag.dbgeng import util as DbgUtil
|
from pybag.dbgeng import util as DbgUtil
|
||||||
|
from pybag.dbgeng.callbacks import DbgEngCallbacks
|
||||||
base = pydbg.DebuggerBase()
|
|
||||||
DbgVersion = namedtuple('DbgVersion', ['full', 'major', 'minor'])
|
|
||||||
|
|
||||||
|
|
||||||
def _compute_pydbg_ver():
|
DbgVersion = namedtuple('DbgVersion', ['full', 'name', 'dotted', 'arch'])
|
||||||
blurb = "" #base._control.GetActualProcessorType()
|
|
||||||
full = ""
|
|
||||||
major = 0
|
|
||||||
minor = 0
|
|
||||||
return DbgVersion(full, int(major), int(minor))
|
|
||||||
|
|
||||||
|
|
||||||
DBG_VERSION = _compute_pydbg_ver()
|
class StdInputCallbacks(CoClass):
|
||||||
|
# This is the UUID listed for IDebugInputCallbacks in DbgEng.h
|
||||||
|
# See https://github.com/tpn/winsdk-10/blob/master/Include/10.0.10240.0/um/DbgEng.h
|
||||||
|
# Accessed 9 Jan 2024
|
||||||
|
_reg_clsid_ = GUID("{9f50e42c-f136-499e-9a97-73036c94ed2d}")
|
||||||
|
_reg_threading_ = "Both"
|
||||||
|
_reg_progid_ = "dbgeng.DbgEngInputCallbacks.1"
|
||||||
|
_reg_novers_progid_ = "dbgeng.DbgEngInputCallbacks"
|
||||||
|
_reg_desc_ = "InputCallbacks"
|
||||||
|
_reg_clsctx_ = comtypes.CLSCTX_INPROC_SERVER
|
||||||
|
|
||||||
|
_com_interfaces_ = [DbgEng.IDebugInputCallbacks,
|
||||||
|
comtypes.typeinfo.IProvideClassInfo2,
|
||||||
|
comtypes.errorinfo.ISupportErrorInfo,
|
||||||
|
comtypes.connectionpoints.IConnectionPointContainer]
|
||||||
|
|
||||||
|
def __init__(self, ghidra_dbg):
|
||||||
|
self.ghidra_dbg = ghidra_dbg
|
||||||
|
self.expecting_input = False
|
||||||
|
|
||||||
|
def IDebugInputCallbacks_StartInput(self, buffer_size):
|
||||||
|
try:
|
||||||
|
self.expecting_input = True
|
||||||
|
self.buffer_size = buffer_size
|
||||||
|
print('Input>', end=' ')
|
||||||
|
line = input()
|
||||||
|
self.ghidra_dbg.return_input(line)
|
||||||
|
return S_OK
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
raise
|
||||||
|
|
||||||
|
def IDebugInputCallbacks_EndInput(self):
|
||||||
|
self.expecting_input = False
|
||||||
|
|
||||||
|
|
||||||
def get_debugger():
|
class _Worker(threading.Thread):
|
||||||
return base
|
def __init__(self, new_base, work_queue, dispatch):
|
||||||
|
super().__init__(name='DbgWorker', daemon=True)
|
||||||
|
self.new_base = new_base
|
||||||
|
self.work_queue = work_queue
|
||||||
|
self.dispatch = dispatch
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.new_base()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
work_item = self.work_queue.get_nowait()
|
||||||
|
except queue.Empty:
|
||||||
|
work_item = None
|
||||||
|
if work_item is None:
|
||||||
|
# HACK to avoid lockup on race condition
|
||||||
|
try:
|
||||||
|
self.dispatch(100)
|
||||||
|
except exception.DbgEngTimeout:
|
||||||
|
# This is routine :/
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
work_item.run()
|
||||||
|
|
||||||
|
|
||||||
|
# Derived from Python core library
|
||||||
|
# https://github.com/python/cpython/blob/main/Lib/concurrent/futures/thread.py
|
||||||
|
# accessed 9 Jan 2024
|
||||||
|
class _WorkItem(object):
|
||||||
|
def __init__(self, future, fn, args, kwargs):
|
||||||
|
self.future = future
|
||||||
|
self.fn = fn
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
result = self.fn(*self.args, **self.kwargs)
|
||||||
|
except BaseException as exc:
|
||||||
|
self.future.set_exception(exc)
|
||||||
|
# Python core lib does this, I presume for good reason
|
||||||
|
self = None
|
||||||
|
else:
|
||||||
|
self.future.set_result(result)
|
||||||
|
|
||||||
|
|
||||||
|
class DebuggeeRunningException(BaseException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DbgExecutor(object):
|
||||||
|
def __init__(self, ghidra_dbg):
|
||||||
|
self._ghidra_dbg = ghidra_dbg
|
||||||
|
self._work_queue = queue.SimpleQueue()
|
||||||
|
self._thread = _Worker(ghidra_dbg._new_base,
|
||||||
|
self._work_queue, ghidra_dbg._dispatch_events)
|
||||||
|
self._thread.start()
|
||||||
|
self._executing = False
|
||||||
|
|
||||||
|
def submit(self, fn, / , *args, **kwargs):
|
||||||
|
f = self._submit_no_exit(fn, *args, **kwargs)
|
||||||
|
self._ghidra_dbg.exit_dispatch()
|
||||||
|
return f
|
||||||
|
|
||||||
|
def _submit_no_exit(self, fn, / , *args, **kwargs):
|
||||||
|
f = Future()
|
||||||
|
if self._executing:
|
||||||
|
f.set_exception(DebuggeeRunningException("Debuggee is Running"))
|
||||||
|
return f
|
||||||
|
w = _WorkItem(f, fn, args, kwargs)
|
||||||
|
self._work_queue.put(w)
|
||||||
|
return f
|
||||||
|
|
||||||
|
def _clear_queue(self):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
work_item = self._work_queue.get_nowait()
|
||||||
|
except queue.Empty:
|
||||||
|
return
|
||||||
|
work_item.future.set_exception(
|
||||||
|
DebuggeeRunningException("Debuggee is Running"))
|
||||||
|
|
||||||
|
def _state_execute(self):
|
||||||
|
self._executing = True
|
||||||
|
self._clear_queue()
|
||||||
|
|
||||||
|
def _state_break(self):
|
||||||
|
self._executing = False
|
||||||
|
|
||||||
|
|
||||||
|
class WrongThreadException(BaseException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AllDbg(pydbg.DebuggerBase):
|
||||||
|
# Steal user-mode methods
|
||||||
|
proc_list = userdbg.UserDbg.proc_list
|
||||||
|
ps = userdbg.UserDbg.ps
|
||||||
|
pids_by_name = userdbg.UserDbg.pids_by_name
|
||||||
|
create_proc = userdbg.UserDbg.create
|
||||||
|
attach_proc = userdbg.UserDbg.attach
|
||||||
|
reattach_proc = userdbg.UserDbg.reattach
|
||||||
|
detach_proc = userdbg.UserDbg.detach
|
||||||
|
abandon_proc = userdbg.UserDbg.abandon
|
||||||
|
terminate_proc = userdbg.UserDbg.terminate
|
||||||
|
handoff_proc = userdbg.UserDbg.handoff
|
||||||
|
connect_proc = userdbg.UserDbg.connect
|
||||||
|
disconnect_proc = userdbg.UserDbg.disconnect
|
||||||
|
|
||||||
|
# Steal kernel-mode methods
|
||||||
|
attach_kernel = kerneldbg.KernelDbg.attach
|
||||||
|
detach_kernel = kerneldbg.KernelDbg.detach
|
||||||
|
|
||||||
|
# Steal crash methods
|
||||||
|
load_dump = crashdbg.CrashDbg.load_dump
|
||||||
|
|
||||||
|
|
||||||
|
class GhidraDbg(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._queue = DbgExecutor(self)
|
||||||
|
self._thread = self._queue._thread
|
||||||
|
# Wait for the executor to be operational before getting base
|
||||||
|
self._queue._submit_no_exit(lambda: None).result()
|
||||||
|
self._install_stdin()
|
||||||
|
|
||||||
|
base = self._protected_base
|
||||||
|
for name in ['set_output_mask', 'get_output_mask',
|
||||||
|
'exec_status', 'go', 'goto', 'go_handled', 'go_nothandled',
|
||||||
|
'stepi', 'stepo', 'stepbr', 'stepto', 'stepout',
|
||||||
|
'trace', 'traceto',
|
||||||
|
'wait',
|
||||||
|
'bitness',
|
||||||
|
'read', 'readstr', 'readptr', 'poi',
|
||||||
|
'write', 'writeptr',
|
||||||
|
'memory_list', 'address',
|
||||||
|
'instruction_at', 'disasm',
|
||||||
|
'pc', 'r', 'registers',
|
||||||
|
'get_name_by_offset', 'symbol', 'find_symbol',
|
||||||
|
'whereami',
|
||||||
|
'dd', 'dp', 'ds',
|
||||||
|
'bl', 'bc', 'bd', 'be', 'bp', 'ba',
|
||||||
|
'handle_list', 'handles',
|
||||||
|
'get_thread', 'set_thread', 'apply_threads', 'thread_list', 'threads',
|
||||||
|
'teb_addr', 'teb', 'peb_addr', 'peb',
|
||||||
|
'backtrace_list', 'backtrace',
|
||||||
|
'module_list', 'lm', 'exports', 'imports',
|
||||||
|
# User-mode
|
||||||
|
'proc_list', 'ps', 'pids_by_name',
|
||||||
|
'create_proc', 'attach_proc', 'reattach_proc',
|
||||||
|
'detach_proc', 'abandon_proc', 'terminate_proc', 'handoff_proc',
|
||||||
|
'connect_proc', 'disconnect_proc',
|
||||||
|
# Kernel-model
|
||||||
|
'attach_kernel', 'detach_kernel',
|
||||||
|
# Crash dump
|
||||||
|
'load_dump'
|
||||||
|
]:
|
||||||
|
setattr(self, name, self.eng_thread(getattr(base, name)))
|
||||||
|
|
||||||
|
def _new_base(self):
|
||||||
|
self._protected_base = AllDbg()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _base(self):
|
||||||
|
if threading.current_thread() is not self._thread:
|
||||||
|
raise WrongThreadException("Was {}. Want {}".format(
|
||||||
|
threading.current_thread(), self._thread))
|
||||||
|
return self._protected_base
|
||||||
|
|
||||||
|
def run(self, fn, *args, **kwargs):
|
||||||
|
# TODO: Remove this check?
|
||||||
|
if hasattr(self, '_thread') and threading.current_thread() is self._thread:
|
||||||
|
raise WrongThreadException()
|
||||||
|
future = self._queue.submit(fn, *args, **kwargs)
|
||||||
|
# https://stackoverflow.com/questions/72621731/is-there-any-graceful-way-to-interrupt-a-python-concurrent-future-result-call gives an alternative
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
return future.result(0.5)
|
||||||
|
except concurrent.futures.TimeoutError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run_async(self, fn, *args, **kwargs):
|
||||||
|
return self._queue.submit(fn, *args, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_thread(func):
|
||||||
|
'''
|
||||||
|
For methods inside of GhidraDbg, ensure it runs on the dbgeng
|
||||||
|
thread
|
||||||
|
'''
|
||||||
|
@functools.wraps(func)
|
||||||
|
def _func(self, *args, **kwargs):
|
||||||
|
if threading.current_thread() is self._thread:
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
return self.run(func, self, *args, **kwargs)
|
||||||
|
return _func
|
||||||
|
|
||||||
|
def eng_thread(self, func):
|
||||||
|
'''
|
||||||
|
For methods and functions outside of GhidraDbg, ensure it
|
||||||
|
runs on this GhidraDbg's dbgeng thread
|
||||||
|
'''
|
||||||
|
@functools.wraps(func)
|
||||||
|
def _func(*args, **kwargs):
|
||||||
|
if threading.current_thread() is self._thread:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
return self.run(func, *args, **kwargs)
|
||||||
|
return _func
|
||||||
|
|
||||||
|
def _ces_exec_status(self, argument):
|
||||||
|
if argument & 0xfffffff == DbgEng.DEBUG_STATUS_BREAK:
|
||||||
|
self._queue._state_break()
|
||||||
|
else:
|
||||||
|
self._queue._state_execute()
|
||||||
|
|
||||||
|
@check_thread
|
||||||
|
def _install_stdin(self):
|
||||||
|
self.input = StdInputCallbacks(self)
|
||||||
|
self._base._client.SetInputCallbacks(self.input)
|
||||||
|
|
||||||
|
# Manually decorated to preserve undecorated
|
||||||
|
def _dispatch_events(self, timeout=DbgEng.WAIT_INFINITE):
|
||||||
|
# NB: pybag's impl doesn't heed standalone
|
||||||
|
self._protected_base._client.DispatchCallbacks(timeout)
|
||||||
|
dispatch_events = check_thread(_dispatch_events)
|
||||||
|
|
||||||
|
# no check_thread. Must allow reentry
|
||||||
|
def exit_dispatch(self):
|
||||||
|
self._protected_base._client.ExitDispatch()
|
||||||
|
|
||||||
|
@check_thread
|
||||||
|
def cmd(self, cmdline, quiet=True):
|
||||||
|
# NB: pybag's impl always captures.
|
||||||
|
# Here, we let it print without capture if quiet is False
|
||||||
|
if quiet:
|
||||||
|
try:
|
||||||
|
buffer = io.StringIO()
|
||||||
|
self._base.callbacks.stdout = buffer
|
||||||
|
self._base._control.Execute(cmdline)
|
||||||
|
buffer.seek(0)
|
||||||
|
return buffer.read()
|
||||||
|
finally:
|
||||||
|
self._base.callbacks.reset_stdout()
|
||||||
|
else:
|
||||||
|
return self._base._control.Execute(cmdline)
|
||||||
|
|
||||||
|
@check_thread
|
||||||
|
def return_input(self, input):
|
||||||
|
# TODO: Contribute fix upstream (check_hr -> check_err)
|
||||||
|
# return self._base._control.ReturnInput(input.encode())
|
||||||
|
hr = self._base._control._ctrl.ReturnInput(input.encode())
|
||||||
|
exception.check_err(hr)
|
||||||
|
|
||||||
|
def interrupt(self):
|
||||||
|
# Contribute upstream?
|
||||||
|
# NOTE: This can be called from any thread
|
||||||
|
self._protected_base._control.SetInterrupt(
|
||||||
|
DbgEng.DEBUG_INTERRUPT_ACTIVE)
|
||||||
|
|
||||||
|
@check_thread
|
||||||
|
def get_current_system_id(self):
|
||||||
|
# TODO: upstream?
|
||||||
|
sys_id = c_ulong()
|
||||||
|
hr = self._base._systems._sys.GetCurrentSystemId(byref(sys_id))
|
||||||
|
exception.check_err(hr)
|
||||||
|
return sys_id.value
|
||||||
|
|
||||||
|
@check_thread
|
||||||
|
def get_prompt_text(self):
|
||||||
|
# TODO: upstream?
|
||||||
|
size = c_ulong()
|
||||||
|
hr = self._base._control._ctrl.GetPromptText(None, 0, byref(size))
|
||||||
|
prompt_buf = create_string_buffer(size.value)
|
||||||
|
hr = self._base._control._ctrl.GetPromptText(prompt_buf, size, None)
|
||||||
|
return prompt_buf.value.decode()
|
||||||
|
|
||||||
|
@check_thread
|
||||||
|
def get_actual_processor_type(self):
|
||||||
|
return self._base._control.GetActualProcessorType()
|
||||||
|
|
||||||
|
@property
|
||||||
|
@check_thread
|
||||||
|
def pid(self):
|
||||||
|
try:
|
||||||
|
return self._base._systems.GetCurrentProcessSystemId()
|
||||||
|
except exception.E_UNEXPECTED_Error:
|
||||||
|
# There is no process
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
dbg = GhidraDbg()
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
|
def compute_pydbg_ver():
|
||||||
|
pat = re.compile(
|
||||||
|
'(?P<name>.*Debugger.*) Version (?P<dotted>[\\d\\.]*) (?P<arch>\\w*)')
|
||||||
|
blurb = dbg.cmd('version')
|
||||||
|
matches = [pat.match(l) for l in blurb.splitlines() if pat.match(l)]
|
||||||
|
if len(matches) == 0:
|
||||||
|
return DbgVersion('Unknown', 'Unknown', '0', 'Unknown')
|
||||||
|
m = matches[0]
|
||||||
|
return DbgVersion(full=m.group(), **m.groupdict())
|
||||||
|
name, dotted_and_arch = full.split(' Version ')
|
||||||
|
|
||||||
|
|
||||||
|
DBG_VERSION = compute_pydbg_ver()
|
||||||
|
|
||||||
|
|
||||||
def get_target():
|
def get_target():
|
||||||
return 0 #get_debugger()._systems.GetCurrentSystemId()
|
return dbg.get_current_system_id()
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
|
def disassemble1(addr):
|
||||||
|
return DbgUtil.disassemble_instruction(dbg._base.bitness(), addr, dbg.read(addr, 15))
|
||||||
|
|
||||||
|
|
||||||
def get_inst(addr):
|
def get_inst(addr):
|
||||||
dbg = get_debugger()
|
return str(disassemble1(addr))
|
||||||
ins = DbgUtil.disassemble_instruction(dbg.bitness(), addr, dbg.read(addr, 15))
|
|
||||||
return str(ins)
|
|
||||||
|
|
||||||
def get_inst_sz(addr):
|
def get_inst_sz(addr):
|
||||||
dbg = get_debugger()
|
return int(disassemble1(addr).size)
|
||||||
ins = DbgUtil.disassemble_instruction(dbg.bitness(), addr, dbg.read(addr, 15))
|
|
||||||
return str(ins.size)
|
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def get_breakpoints():
|
def get_breakpoints():
|
||||||
ids = [bpid for bpid in get_debugger().breakpoints]
|
ids = [bpid for bpid in dbg._base.breakpoints]
|
||||||
offset_set = []
|
offset_set = []
|
||||||
expr_set = []
|
expr_set = []
|
||||||
prot_set = []
|
prot_set = []
|
||||||
|
@ -64,7 +412,7 @@ def get_breakpoints():
|
||||||
stat_set = []
|
stat_set = []
|
||||||
for bpid in ids:
|
for bpid in ids:
|
||||||
try:
|
try:
|
||||||
bp = get_debugger()._control.GetBreakpointById(bpid)
|
bp = dbg._base._control.GetBreakpointById(bpid)
|
||||||
except exception.E_NOINTERFACE_Error:
|
except exception.E_NOINTERFACE_Error:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -73,7 +421,7 @@ def get_breakpoints():
|
||||||
expr = bp.GetOffsetExpression()
|
expr = bp.GetOffsetExpression()
|
||||||
else:
|
else:
|
||||||
offset = "%016x" % bp.GetOffset()
|
offset = "%016x" % bp.GetOffset()
|
||||||
expr = get_debugger().get_name_by_offset(bp.GetOffset())
|
expr = dbg._base.get_name_by_offset(bp.GetOffset())
|
||||||
|
|
||||||
if bp.GetType()[0] == DbgEng.DEBUG_BREAKPOINT_DATA:
|
if bp.GetType()[0] == DbgEng.DEBUG_BREAKPOINT_DATA:
|
||||||
width, prot = bp.GetDataParameters()
|
width, prot = bp.GetDataParameters()
|
||||||
|
@ -81,7 +429,7 @@ def get_breakpoints():
|
||||||
prot = {4: 'type=x', 3: 'type=rw', 2: 'type=w', 1: 'type=r'}[prot]
|
prot = {4: 'type=x', 3: 'type=rw', 2: 'type=w', 1: 'type=r'}[prot]
|
||||||
else:
|
else:
|
||||||
width = ''
|
width = ''
|
||||||
prot = ''
|
prot = ''
|
||||||
|
|
||||||
if bp.GetFlags() & DbgEng.DEBUG_BREAKPOINT_ENABLED:
|
if bp.GetFlags() & DbgEng.DEBUG_BREAKPOINT_ENABLED:
|
||||||
status = 'enabled'
|
status = 'enabled'
|
||||||
|
@ -95,48 +443,65 @@ def get_breakpoints():
|
||||||
stat_set.append(status)
|
stat_set.append(status)
|
||||||
return zip(offset_set, expr_set, prot_set, width_set, stat_set)
|
return zip(offset_set, expr_set, prot_set, width_set, stat_set)
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def selected_process():
|
def selected_process():
|
||||||
try:
|
try:
|
||||||
return get_debugger()._systems.GetCurrentProcessId()
|
return dbg._base._systems.GetCurrentProcessId()
|
||||||
#return current_process
|
except exception.E_UNEXPECTED_Error:
|
||||||
except Exception:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def selected_thread():
|
def selected_thread():
|
||||||
try:
|
try:
|
||||||
return get_debugger()._systems.GetCurrentThreadId()
|
return dbg._base._systems.GetCurrentThreadId()
|
||||||
except Exception:
|
except exception.E_UNEXPECTED_Error:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def selected_frame():
|
def selected_frame():
|
||||||
return 0 #selected_thread().GetSelectedFrame()
|
try:
|
||||||
|
line = dbg.cmd('.frame').strip()
|
||||||
|
if not line:
|
||||||
|
return None
|
||||||
|
num_str = line.split(sep=None, maxsplit=1)[0]
|
||||||
|
return int(num_str, 16)
|
||||||
|
except OSError:
|
||||||
|
return None
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def select_process(id: int):
|
def select_process(id: int):
|
||||||
return get_debugger()._systems.SetCurrentProcessId(id)
|
return dbg._base._systems.SetCurrentProcessId(id)
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def select_thread(id: int):
|
def select_thread(id: int):
|
||||||
return get_debugger()._systems.SetCurrentThreadId(id)
|
return dbg._base._systems.SetCurrentThreadId(id)
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def select_frame(id: int):
|
def select_frame(id: int):
|
||||||
#TODO: this needs to be fixed
|
return dbg.cmd('.frame 0x{:x}'.format(id))
|
||||||
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):
|
@dbg.eng_thread
|
||||||
ctrl = get_debugger()._control._ctrl
|
def parse_and_eval(expr, type=None):
|
||||||
|
if isinstance(expr, int):
|
||||||
|
return expr
|
||||||
|
# TODO: This could be contributed upstream
|
||||||
|
ctrl = dbg._base._control._ctrl
|
||||||
ctrl.SetExpressionSyntax(1)
|
ctrl.SetExpressionSyntax(1)
|
||||||
value = DbgEng._DEBUG_VALUE()
|
value = DbgEng._DEBUG_VALUE()
|
||||||
index = c_ulong()
|
index = c_ulong()
|
||||||
if type == None:
|
if type == None:
|
||||||
type = DbgEng.DEBUG_VALUE_INT64
|
type = DbgEng.DEBUG_VALUE_INT64
|
||||||
hr = ctrl.Evaluate(Expression="{}".format(expr).encode(),DesiredType=type,Value=byref(value),RemainderIndex=byref(index))
|
hr = ctrl.Evaluate(Expression=expr.encode(), DesiredType=type,
|
||||||
|
Value=byref(value), RemainderIndex=byref(index))
|
||||||
exception.check_err(hr)
|
exception.check_err(hr)
|
||||||
if type == DbgEng.DEBUG_VALUE_INT8:
|
if type == DbgEng.DEBUG_VALUE_INT8:
|
||||||
return value.u.I8
|
return value.u.I8
|
||||||
|
@ -157,96 +522,128 @@ def get_eval(expr, type=None):
|
||||||
if type == DbgEng.DEBUG_VALUE_FLOAT128:
|
if type == DbgEng.DEBUG_VALUE_FLOAT128:
|
||||||
return value.u.F128Bytes
|
return value.u.F128Bytes
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
|
def get_pc():
|
||||||
|
return dbg._base.reg.get_pc()
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
|
def get_sp():
|
||||||
|
return dbg._base.reg.get_sp()
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def GetProcessIdsByIndex(count=0):
|
def GetProcessIdsByIndex(count=0):
|
||||||
|
# TODO: This could be contributed upstream?
|
||||||
if count == 0:
|
if count == 0:
|
||||||
try :
|
try:
|
||||||
count = get_debugger()._systems.GetNumberProcesses()
|
count = dbg._base._systems.GetNumberProcesses()
|
||||||
except Exception:
|
except Exception:
|
||||||
count = 0
|
count = 0
|
||||||
ids = (c_ulong * count)()
|
ids = (c_ulong * count)()
|
||||||
sysids = (c_ulong * count)()
|
sysids = (c_ulong * count)()
|
||||||
if count != 0:
|
if count != 0:
|
||||||
hr = get_debugger()._systems._sys.GetProcessIdsByIndex(0, count, ids, sysids)
|
hr = dbg._base._systems._sys.GetProcessIdsByIndex(
|
||||||
|
0, count, ids, sysids)
|
||||||
exception.check_err(hr)
|
exception.check_err(hr)
|
||||||
return (tuple(ids), tuple(sysids))
|
return (tuple(ids), tuple(sysids))
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def GetCurrentProcessExecutableName():
|
def GetCurrentProcessExecutableName():
|
||||||
dbg = get_debugger()
|
# TODO: upstream?
|
||||||
|
_dbg = dbg._base
|
||||||
size = c_ulong()
|
size = c_ulong()
|
||||||
exesize = c_ulong()
|
exesize = c_ulong()
|
||||||
hr = dbg._systems._sys.GetCurrentProcessExecutableName(None, size, byref(exesize))
|
hr = _dbg._systems._sys.GetCurrentProcessExecutableName(
|
||||||
|
None, size, byref(exesize))
|
||||||
exception.check_err(hr)
|
exception.check_err(hr)
|
||||||
buffer = create_string_buffer(exesize.value)
|
buffer = create_string_buffer(exesize.value)
|
||||||
size = exesize
|
size = exesize
|
||||||
hr = dbg._systems._sys.GetCurrentProcessExecutableName(buffer, size, None)
|
hr = _dbg._systems._sys.GetCurrentProcessExecutableName(buffer, size, None)
|
||||||
exception.check_err(hr)
|
exception.check_err(hr)
|
||||||
buffer = buffer[:size.value]
|
buffer = buffer[:size.value]
|
||||||
buffer = buffer.rstrip(b'\x00')
|
buffer = buffer.rstrip(b'\x00')
|
||||||
return buffer
|
return buffer
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def GetCurrentProcessPeb():
|
def GetCurrentProcessPeb():
|
||||||
dbg = get_debugger()
|
# TODO: upstream?
|
||||||
|
_dbg = dbg._base
|
||||||
offset = c_ulonglong()
|
offset = c_ulonglong()
|
||||||
hr = dbg._systems._sys.GetCurrentProcessPeb(byref(offset))
|
hr = _dbg._systems._sys.GetCurrentProcessPeb(byref(offset))
|
||||||
exception.check_err(hr)
|
exception.check_err(hr)
|
||||||
return offset.value
|
return offset.value
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def GetExitCode():
|
def GetExitCode():
|
||||||
|
# TODO: upstream?
|
||||||
exit_code = c_ulong()
|
exit_code = c_ulong()
|
||||||
hr = get_debugger()._client._cli.GetExitCode(byref(exit_code))
|
hr = dbg._base._client._cli.GetExitCode(byref(exit_code))
|
||||||
return exit_code.value
|
return exit_code.value
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def process_list(running=False):
|
def process_list(running=False):
|
||||||
"""process_list() -> list of all processes"""
|
"""Get the list of all processes"""
|
||||||
dbg = get_debugger()
|
_dbg = dbg._base
|
||||||
ids, sysids = GetProcessIdsByIndex()
|
ids, sysids = GetProcessIdsByIndex()
|
||||||
pebs = []
|
pebs = []
|
||||||
names = []
|
names = []
|
||||||
|
|
||||||
try :
|
curid = selected_process()
|
||||||
curid = dbg._systems.GetCurrentProcessId()
|
try:
|
||||||
if running == False:
|
if running:
|
||||||
|
return zip(sysids)
|
||||||
|
else:
|
||||||
for id in ids:
|
for id in ids:
|
||||||
dbg._systems.SetCurrentProcessId(id)
|
_dbg._systems.SetCurrentProcessId(id)
|
||||||
names.append(GetCurrentProcessExecutableName())
|
names.append(GetCurrentProcessExecutableName())
|
||||||
pebs.append(GetCurrentProcessPeb())
|
pebs.append(GetCurrentProcessPeb())
|
||||||
if running == False:
|
|
||||||
dbg._systems.SetCurrentProcessId(curid)
|
|
||||||
return zip(sysids, names, pebs)
|
return zip(sysids, names, pebs)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
return zip(sysids)
|
||||||
return zip(sysids)
|
finally:
|
||||||
|
if not running and curid is not None:
|
||||||
|
_dbg._systems.SetCurrentProcessId(curid)
|
||||||
|
|
||||||
|
|
||||||
|
@dbg.eng_thread
|
||||||
def thread_list(running=False):
|
def thread_list(running=False):
|
||||||
"""thread_list() -> list of all threads"""
|
"""Get the list of all threads"""
|
||||||
dbg = get_debugger()
|
_dbg = dbg._base
|
||||||
try :
|
try:
|
||||||
ids, sysids = dbg._systems.GetThreadIdsByIndex()
|
ids, sysids = _dbg._systems.GetThreadIdsByIndex()
|
||||||
except Exception:
|
except Exception:
|
||||||
return zip([])
|
return zip([])
|
||||||
tebs = []
|
tebs = []
|
||||||
syms = []
|
syms = []
|
||||||
|
|
||||||
curid = dbg._systems.GetCurrentThreadId()
|
curid = selected_thread()
|
||||||
if running == False:
|
try:
|
||||||
for id in ids:
|
if running:
|
||||||
dbg._systems.SetCurrentThreadId(id)
|
return zip(sysids)
|
||||||
tebs.append(dbg._systems.GetCurrentThreadTeb())
|
else:
|
||||||
addr = dbg.reg.get_pc()
|
for id in ids:
|
||||||
syms.append(dbg.get_name_by_offset(addr))
|
_dbg._systems.SetCurrentThreadId(id)
|
||||||
if running == False:
|
tebs.append(_dbg._systems.GetCurrentThreadTeb())
|
||||||
dbg._systems.SetCurrentThreadId(curid)
|
addr = _dbg.reg.get_pc()
|
||||||
return zip(sysids, tebs, syms)
|
syms.append(_dbg.get_name_by_offset(addr))
|
||||||
return zip(sysids)
|
return zip(sysids, tebs, syms)
|
||||||
|
except Exception:
|
||||||
|
return zip(sysids)
|
||||||
|
finally:
|
||||||
|
if not running and curid is not None:
|
||||||
|
_dbg._systems.SetCurrentThreadId(curid)
|
||||||
|
|
||||||
|
|
||||||
conv_map = {}
|
conv_map = {}
|
||||||
|
|
||||||
|
|
||||||
def get_convenience_variable(id):
|
def get_convenience_variable(id):
|
||||||
#val = get_target().GetEnvironment().Get(id)
|
|
||||||
if id not in conv_map:
|
if id not in conv_map:
|
||||||
return "auto"
|
return "auto"
|
||||||
val = conv_map[id]
|
val = conv_map[id]
|
||||||
|
@ -254,7 +651,6 @@ def get_convenience_variable(id):
|
||||||
return "auto"
|
return "auto"
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
def set_convenience_variable(id, value):
|
def set_convenience_variable(id, value):
|
||||||
#env = get_target().GetEnvironment()
|
|
||||||
#return env.Set(id, value, True)
|
|
||||||
conv_map[id] = value
|
conv_map[id] = value
|
||||||
|
|
|
@ -391,7 +391,8 @@ public class DebuggerCoordinates {
|
||||||
return NOWHERE;
|
return NOWHERE;
|
||||||
}
|
}
|
||||||
long snap = newTime.getSnap();
|
long snap = newTime.getSnap();
|
||||||
TraceThread newThread = thread != null && thread.getLifespan().contains(snap) ? thread
|
Lifespan threadLifespan = thread == null ? null : thread.getLifespan();
|
||||||
|
TraceThread newThread = threadLifespan != null && threadLifespan.contains(snap) ? thread
|
||||||
: resolveThread(trace, target, newTime);
|
: resolveThread(trace, target, newTime);
|
||||||
// This will cause the frame to reset to 0 on every snap change. That's fair....
|
// This will cause the frame to reset to 0 on every snap change. That's fair....
|
||||||
Integer newFrame = resolveFrame(newThread, newTime);
|
Integer newFrame = resolveFrame(newThread, newTime);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import ghidra.rmi.trace.TraceRmi.*;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
class OpenTrace implements ValueDecoder {
|
class OpenTrace implements ValueDecoder {
|
||||||
final DoId doId;
|
final DoId doId;
|
||||||
|
@ -84,19 +85,47 @@ class OpenTrace implements ValueDecoder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Address toAddress(Addr addr, boolean required) {
|
public Address toAddress(Addr addr, boolean required) {
|
||||||
|
/**
|
||||||
|
* Do not clamp here, like we do for ranges. The purpose of the given address is more
|
||||||
|
* specific here. Plus, we're not just omitting some addresses (like we would for ranges),
|
||||||
|
* we'd be moving the address. Thus, we'd be applying some attribute to a location that was
|
||||||
|
* never intended.
|
||||||
|
*/
|
||||||
AddressSpace space = getSpace(addr.getSpace(), required);
|
AddressSpace space = getSpace(addr.getSpace(), required);
|
||||||
return space.getAddress(addr.getOffset());
|
return space.getAddress(addr.getOffset());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AddressRange toRange(AddrRange range, boolean required)
|
public AddressRange toRange(AddrRange range, boolean required) {
|
||||||
throws AddressOverflowException {
|
|
||||||
AddressSpace space = getSpace(range.getSpace(), required);
|
AddressSpace space = getSpace(range.getSpace(), required);
|
||||||
if (space == null) {
|
if (space == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Address min = space.getAddress(range.getOffset());
|
/**
|
||||||
Address max = space.getAddress(range.getOffset() + range.getExtend());
|
* Clamp to only the valid addresses, but do at least warn.
|
||||||
|
*/
|
||||||
|
long minOffset = range.getOffset();
|
||||||
|
if (Long.compareUnsigned(minOffset, space.getMinAddress().getOffset()) < 0) {
|
||||||
|
Msg.warn(this, "Range [%s:%x+%x] partially exceeds space min. Clamping."
|
||||||
|
.formatted(range.getSpace(), range.getOffset(), range.getExtend()));
|
||||||
|
minOffset = space.getMinAddress().getOffset();
|
||||||
|
}
|
||||||
|
else if (Long.compareUnsigned(minOffset, space.getMaxAddress().getOffset()) > 0) {
|
||||||
|
throw new AddressOutOfBoundsException("Range [%s:%x+%x] entirely exceeds space max"
|
||||||
|
.formatted(range.getSpace(), range.getOffset(), range.getExtend()));
|
||||||
|
}
|
||||||
|
long maxOffset = range.getOffset() + range.getExtend(); // Use the requested offset, not adjusted
|
||||||
|
if (Long.compareUnsigned(maxOffset, space.getMaxAddress().getOffset()) > 0) {
|
||||||
|
Msg.warn(this, "Range [%s:%x+%x] partially exceeds space max. Clamping."
|
||||||
|
.formatted(range.getSpace(), range.getOffset(), range.getExtend()));
|
||||||
|
maxOffset = space.getMaxAddress().getOffset();
|
||||||
|
}
|
||||||
|
else if (Long.compareUnsigned(maxOffset, space.getMinAddress().getOffset()) < 0) {
|
||||||
|
throw new AddressOutOfBoundsException("Range [%s:%x+%x] entirely exceeds space min"
|
||||||
|
.formatted(range.getSpace(), range.getOffset(), range.getExtend()));
|
||||||
|
}
|
||||||
|
Address min = space.getAddress(minOffset);
|
||||||
|
Address max = space.getAddress(maxOffset);
|
||||||
return new AddressRangeImpl(min, max);
|
return new AddressRangeImpl(min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -503,22 +503,29 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
default String toString(RootMessage req) {
|
default String toString(RootMessage req) {
|
||||||
return switch (req.getMsgCase()) {
|
try {
|
||||||
case REQUEST_ACTIVATE -> "activate(%d, %d, %s)".formatted(
|
return switch (req.getMsgCase()) {
|
||||||
req.getRequestActivate().getOid().getId(),
|
case REQUEST_ACTIVATE -> "activate(%d, %d, %s)".formatted(
|
||||||
req.getRequestActivate().getObject().getId(),
|
req.getRequestActivate().getOid().getId(),
|
||||||
req.getRequestActivate().getObject().getPath().getPath());
|
req.getRequestActivate().getObject().getId(),
|
||||||
case REQUEST_END_TX -> "endTx(%d)".formatted(
|
req.getRequestActivate().getObject().getPath().getPath());
|
||||||
req.getRequestEndTx().getTxid().getId());
|
case REQUEST_END_TX -> "endTx(%d)".formatted(
|
||||||
case REQUEST_START_TX -> "startTx(%d,%s)".formatted(
|
req.getRequestEndTx().getTxid().getId());
|
||||||
req.getRequestStartTx().getTxid().getId(),
|
case REQUEST_START_TX -> "startTx(%d,%s)".formatted(
|
||||||
req.getRequestStartTx().getDescription());
|
req.getRequestStartTx().getTxid().getId(),
|
||||||
case REQUEST_SET_VALUE -> "setValue(%d,%s,%s)".formatted(
|
req.getRequestStartTx().getDescription());
|
||||||
req.getRequestSetValue().getValue().getParent().getId(),
|
case REQUEST_SET_VALUE -> "setValue(%d,%s,%s,=%s)".formatted(
|
||||||
req.getRequestSetValue().getValue().getParent().getPath().getPath(),
|
req.getRequestSetValue().getValue().getParent().getId(),
|
||||||
req.getRequestSetValue().getValue().getKey());
|
req.getRequestSetValue().getValue().getParent().getPath().getPath(),
|
||||||
default -> null;
|
req.getRequestSetValue().getValue().getKey(),
|
||||||
};
|
ValueDecoder.DISPLAY
|
||||||
|
.toValue(req.getRequestSetValue().getValue().getValue()));
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Throwable e) {
|
||||||
|
return "ERROR toStringing request: " + e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
private final Trace trace;
|
private final Trace trace;
|
||||||
|
|
||||||
private final Matches matches = new Matches();
|
private final Matches matches = new Matches();
|
||||||
private final RequestCaches requestCaches = new RequestCaches();
|
private final RequestCaches requestCaches = new DorkedRequestCaches();
|
||||||
private final Set<TraceBreakpointKind> supportedBreakpointKinds;
|
private final Set<TraceBreakpointKind> supportedBreakpointKinds;
|
||||||
|
|
||||||
public TraceRmiTarget(PluginTool tool, TraceRmiConnection connection, Trace trace) {
|
public TraceRmiTarget(PluginTool tool, TraceRmiConnection connection, Trace trace) {
|
||||||
|
@ -760,21 +760,41 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class RequestCaches {
|
interface RequestCaches {
|
||||||
|
void invalidate();
|
||||||
|
|
||||||
|
void invalidateMemory();
|
||||||
|
|
||||||
|
CompletableFuture<Void> readBlock(Address min, RemoteMethod method,
|
||||||
|
Map<String, Object> args);
|
||||||
|
|
||||||
|
CompletableFuture<Void> readRegs(TraceObject obj, RemoteMethod method,
|
||||||
|
Map<String, Object> args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class DefaultRequestCaches implements RequestCaches {
|
||||||
final Map<TraceObject, CompletableFuture<Void>> readRegs = new HashMap<>();
|
final Map<TraceObject, CompletableFuture<Void>> readRegs = new HashMap<>();
|
||||||
final Map<Address, CompletableFuture<Void>> readBlock = new HashMap<>();
|
final Map<Address, CompletableFuture<Void>> readBlock = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void invalidateMemory() {
|
||||||
|
readBlock.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public synchronized void invalidate() {
|
public synchronized void invalidate() {
|
||||||
readRegs.clear();
|
readRegs.clear();
|
||||||
readBlock.clear();
|
readBlock.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public synchronized CompletableFuture<Void> readRegs(TraceObject obj, RemoteMethod method,
|
public synchronized CompletableFuture<Void> readRegs(TraceObject obj, RemoteMethod method,
|
||||||
Map<String, Object> args) {
|
Map<String, Object> args) {
|
||||||
return readRegs.computeIfAbsent(obj,
|
return readRegs.computeIfAbsent(obj,
|
||||||
o -> method.invokeAsync(args).toCompletableFuture().thenApply(__ -> null));
|
o -> method.invokeAsync(args).toCompletableFuture().thenApply(__ -> null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public synchronized CompletableFuture<Void> readBlock(Address min, RemoteMethod method,
|
public synchronized CompletableFuture<Void> readBlock(Address min, RemoteMethod method,
|
||||||
Map<String, Object> args) {
|
Map<String, Object> args) {
|
||||||
return readBlock.computeIfAbsent(min,
|
return readBlock.computeIfAbsent(min,
|
||||||
|
@ -782,6 +802,28 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static class DorkedRequestCaches implements RequestCaches {
|
||||||
|
@Override
|
||||||
|
public void invalidate() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateMemory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> readBlock(Address min, RemoteMethod method,
|
||||||
|
Map<String, Object> args) {
|
||||||
|
return method.invokeAsync(args).toCompletableFuture().thenApply(__ -> null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> readRegs(TraceObject obj, RemoteMethod method,
|
||||||
|
Map<String, Object> args) {
|
||||||
|
return method.invokeAsync(args).toCompletableFuture().thenApply(__ -> null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> activateAsync(DebuggerCoordinates prev,
|
public CompletableFuture<Void> activateAsync(DebuggerCoordinates prev,
|
||||||
DebuggerCoordinates coords) {
|
DebuggerCoordinates coords) {
|
||||||
|
@ -818,6 +860,7 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> invalidateMemoryCachesAsync() {
|
public CompletableFuture<Void> invalidateMemoryCachesAsync() {
|
||||||
|
requestCaches.invalidateMemory();
|
||||||
return AsyncUtils.nil();
|
return AsyncUtils.nil();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.service.tracermi;
|
package ghidra.app.plugin.core.debug.service.tracermi;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
|
@ -22,6 +25,44 @@ import ghidra.rmi.trace.TraceRmi.*;
|
||||||
|
|
||||||
public interface ValueDecoder {
|
public interface ValueDecoder {
|
||||||
ValueDecoder DEFAULT = new ValueDecoder() {};
|
ValueDecoder DEFAULT = new ValueDecoder() {};
|
||||||
|
ValueDecoder DISPLAY = new ValueDecoder() {
|
||||||
|
final Map<String, AddressSpace> spaces = new HashMap<>();
|
||||||
|
|
||||||
|
private AddressSpace getSpace(String space) {
|
||||||
|
return spaces.computeIfAbsent(space, name -> {
|
||||||
|
return new GenericAddressSpace(name, 64, AddressSpace.TYPE_RAM, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Address toAddress(Addr addr, boolean required) {
|
||||||
|
AddressSpace space = getSpace(addr.getSpace());
|
||||||
|
return space.getAddress(addr.getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AddressRange toRange(AddrRange range, boolean required) {
|
||||||
|
AddressSpace space = getSpace(range.getSpace());
|
||||||
|
Address min = space.getAddress(range.getOffset());
|
||||||
|
Address max = space.getAddress(range.getOffset() + range.getExtend());
|
||||||
|
return new AddressRangeImpl(min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObject(ObjDesc desc, boolean required) {
|
||||||
|
return "<Object id=%d path=%s>".formatted(desc.getId(), desc.getPath().getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObject(ObjSpec spec, boolean required) {
|
||||||
|
return switch (spec.getKeyCase()) {
|
||||||
|
case KEY_NOT_SET -> "<ERROR: No key>";
|
||||||
|
case ID -> "<Object id=%d>".formatted(spec.getId());
|
||||||
|
case PATH -> "<Object path=%s>".formatted(spec.getPath());
|
||||||
|
default -> "<ERROR: default>";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
default Address toAddress(Addr addr, boolean required) {
|
default Address toAddress(Addr addr, boolean required) {
|
||||||
if (required) {
|
if (required) {
|
||||||
|
@ -30,8 +71,7 @@ public interface ValueDecoder {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
default AddressRange toRange(AddrRange range, boolean required)
|
default AddressRange toRange(AddrRange range, boolean required) {
|
||||||
throws AddressOverflowException {
|
|
||||||
if (required) {
|
if (required) {
|
||||||
throw new IllegalStateException("AddressRange requires a trace for context");
|
throw new IllegalStateException("AddressRange requires a trace for context");
|
||||||
}
|
}
|
||||||
|
@ -52,7 +92,7 @@ public interface ValueDecoder {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
default Object toValue(Value value) throws AddressOverflowException {
|
default Object toValue(Value value) {
|
||||||
return switch (value.getValueCase()) {
|
return switch (value.getValueCase()) {
|
||||||
case NULL_VALUE -> null;
|
case NULL_VALUE -> null;
|
||||||
case BOOL_VALUE -> value.getBoolValue();
|
case BOOL_VALUE -> value.getBoolValue();
|
||||||
|
|
|
@ -61,7 +61,7 @@ class Receiver(Thread):
|
||||||
result = self.client._handle_invoke_method(request)
|
result = self.client._handle_invoke_method(request)
|
||||||
Client._write_value(
|
Client._write_value(
|
||||||
reply.xreply_invoke_method.return_value, result)
|
reply.xreply_invoke_method.return_value, result)
|
||||||
except Exception as e:
|
except BaseException as e:
|
||||||
reply.xreply_invoke_method.error = ''.join(
|
reply.xreply_invoke_method.error = ''.join(
|
||||||
traceback.format_exc())
|
traceback.format_exc())
|
||||||
self.client._send(reply)
|
self.client._send(reply)
|
||||||
|
@ -79,7 +79,7 @@ class Receiver(Thread):
|
||||||
result = request.handler(
|
result = request.handler(
|
||||||
getattr(reply, request.field_name))
|
getattr(reply, request.field_name))
|
||||||
request.set_result(result)
|
request.set_result(result)
|
||||||
except Exception as e:
|
except BaseException as e:
|
||||||
request.set_exception(e)
|
request.set_exception(e)
|
||||||
|
|
||||||
def _recv(self, field_name, handler):
|
def _recv(self, field_name, handler):
|
||||||
|
|
|
@ -18,67 +18,37 @@ package ghidra.app.plugin.core.debug.disassemble;
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import ghidra.app.context.ListingActionContext;
|
import ghidra.app.context.ListingActionContext;
|
||||||
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin.Reqs;
|
import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand.Reqs;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.debug.api.platform.DebuggerPlatformMapper;
|
|
||||||
import ghidra.debug.api.platform.DisassemblyResult;
|
|
||||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
|
||||||
import ghidra.framework.cmd.TypedBackgroundCommand;
|
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.trace.model.Trace;
|
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
|
||||||
import ghidra.trace.model.target.TraceObject;
|
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
public class CurrentPlatformTraceDisassembleAction extends DockingAction {
|
public class CurrentPlatformTraceDisassembleAction extends DockingAction {
|
||||||
private static final String NAME = "Disassemble";
|
private static final String NAME = "Disassemble";
|
||||||
private static final String MENU_GROUP = "Disassembly";
|
private static final String MENU_GROUP = "Disassembly";
|
||||||
private static final KeyBindingData KEY_BINDING = new KeyBindingData("D");
|
private static final KeyBindingData KEY_BINDING = new KeyBindingData("D");
|
||||||
|
|
||||||
private final DebuggerDisassemblerPlugin plugin;
|
private final PluginTool tool;
|
||||||
|
|
||||||
public CurrentPlatformTraceDisassembleAction(DebuggerDisassemblerPlugin plugin) {
|
public CurrentPlatformTraceDisassembleAction(DebuggerDisassemblerPlugin plugin) {
|
||||||
super(NAME, plugin.getName());
|
super(NAME, plugin.getName());
|
||||||
this.plugin = plugin;
|
this.tool = plugin.getTool();
|
||||||
|
|
||||||
setPopupMenuData(new MenuData(new String[] { NAME }, MENU_GROUP));
|
setPopupMenuData(new MenuData(new String[] { NAME }, MENU_GROUP));
|
||||||
setKeyBindingData(KEY_BINDING);
|
setKeyBindingData(KEY_BINDING);
|
||||||
setHelpLocation(new HelpLocation(plugin.getName(), "disassemble"));
|
setHelpLocation(new HelpLocation(plugin.getName(), "disassemble"));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Reqs getReqs(ActionContext context) {
|
|
||||||
if (plugin.platformService == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!(context instanceof DebuggerListingActionContext lac)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
TraceProgramView view = lac.getProgram();
|
|
||||||
Trace trace = view.getTrace();
|
|
||||||
DebuggerCoordinates current = plugin.traceManager == null ? DebuggerCoordinates.NOWHERE
|
|
||||||
: plugin.traceManager.getCurrentFor(trace);
|
|
||||||
TraceThread thread = current.getThread();
|
|
||||||
TraceObject object = current.getObject();
|
|
||||||
DebuggerPlatformMapper mapper =
|
|
||||||
plugin.platformService.getMapper(trace, object, view.getSnap());
|
|
||||||
if (mapper == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Reqs(mapper, thread, object, view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAddToPopup(ActionContext context) {
|
public boolean isAddToPopup(ActionContext context) {
|
||||||
Reqs reqs = getReqs(context);
|
Reqs reqs = Reqs.fromContext(tool, context);
|
||||||
return reqs != null;
|
return reqs != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabledForContext(ActionContext context) {
|
public boolean isEnabledForContext(ActionContext context) {
|
||||||
Reqs reqs = getReqs(context);
|
Reqs reqs = Reqs.fromContext(tool, context);
|
||||||
if (reqs == null) {
|
if (reqs == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -87,7 +57,7 @@ public class CurrentPlatformTraceDisassembleAction extends DockingAction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionContext context) {
|
public void actionPerformed(ActionContext context) {
|
||||||
Reqs reqs = getReqs(context);
|
Reqs reqs = Reqs.fromContext(tool, context);
|
||||||
if (reqs == null) {
|
if (reqs == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -100,21 +70,12 @@ public class CurrentPlatformTraceDisassembleAction extends DockingAction {
|
||||||
set = selection;
|
set = selection;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
set = reqs.view.getAddressFactory()
|
set = reqs.view()
|
||||||
|
.getAddressFactory()
|
||||||
.getAddressSet(space.getMinAddress(), space.getMaxAddress());
|
.getAddressSet(space.getMinAddress(), space.getMaxAddress());
|
||||||
}
|
}
|
||||||
TypedBackgroundCommand<TraceProgramView> cmd =
|
CurrentPlatformTraceDisassembleCommand cmd =
|
||||||
new TypedBackgroundCommand<>(NAME, true, true, false) {
|
new CurrentPlatformTraceDisassembleCommand(tool, set, reqs, address);
|
||||||
@Override
|
cmd.run(tool, reqs.view());
|
||||||
public boolean applyToTyped(TraceProgramView view, TaskMonitor monitor) {
|
|
||||||
DisassemblyResult result = reqs.mapper.disassemble(
|
|
||||||
reqs.thread, reqs.object, address, set, view.getSnap(), monitor);
|
|
||||||
if (!result.isSuccess()) {
|
|
||||||
plugin.getTool().setStatusInfo(result.getErrorMessage(), true);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
cmd.run(plugin.getTool(), reqs.view);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.debug.disassemble;
|
||||||
|
|
||||||
|
import docking.ActionContext;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext;
|
||||||
|
import ghidra.app.services.DebuggerPlatformService;
|
||||||
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
|
import ghidra.debug.api.platform.DebuggerPlatformMapper;
|
||||||
|
import ghidra.debug.api.platform.DisassemblyResult;
|
||||||
|
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||||
|
import ghidra.framework.cmd.TypedBackgroundCommand;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressSetView;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
|
import ghidra.trace.model.target.TraceObject;
|
||||||
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
public final class CurrentPlatformTraceDisassembleCommand
|
||||||
|
extends TypedBackgroundCommand<TraceProgramView> {
|
||||||
|
public static final String NAME = "Disassemble";
|
||||||
|
|
||||||
|
public record Reqs(DebuggerPlatformMapper mapper, TraceThread thread, TraceObject object,
|
||||||
|
TraceProgramView view) {
|
||||||
|
|
||||||
|
public static Reqs fromView(PluginTool tool, TraceProgramView view) {
|
||||||
|
DebuggerTraceManagerService traceManager =
|
||||||
|
tool.getService(DebuggerTraceManagerService.class);
|
||||||
|
DebuggerPlatformService platformService =
|
||||||
|
tool.getService(DebuggerPlatformService.class);
|
||||||
|
if (platformService == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Trace trace = view.getTrace();
|
||||||
|
DebuggerCoordinates current = traceManager == null
|
||||||
|
? DebuggerCoordinates.NOWHERE
|
||||||
|
: traceManager.getCurrentFor(trace);
|
||||||
|
TraceThread thread = current.getThread();
|
||||||
|
TraceObject object = current.getObject();
|
||||||
|
DebuggerPlatformMapper mapper =
|
||||||
|
platformService.getMapper(trace, object, view.getSnap());
|
||||||
|
if (mapper == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Reqs(mapper, thread, object, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Reqs fromContext(PluginTool tool, ActionContext context) {
|
||||||
|
if (!(context instanceof DebuggerListingActionContext lac)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return fromView(tool, lac.getProgram());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final PluginTool tool;
|
||||||
|
private final AddressSetView set;
|
||||||
|
private final Reqs reqs;
|
||||||
|
private final Address address;
|
||||||
|
|
||||||
|
public CurrentPlatformTraceDisassembleCommand(PluginTool tool, AddressSetView set,
|
||||||
|
Reqs reqs, Address address) {
|
||||||
|
super(NAME, true, true, false);
|
||||||
|
this.tool = tool;
|
||||||
|
this.set = set;
|
||||||
|
this.reqs = reqs;
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean applyToTyped(TraceProgramView view, TaskMonitor monitor) {
|
||||||
|
DisassemblyResult result = reqs.mapper.disassemble(
|
||||||
|
reqs.thread, reqs.object, address, set, view.getSnap(), monitor);
|
||||||
|
if (!result.isSuccess()) {
|
||||||
|
tool.setStatusInfo(result.getErrorMessage(), true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,10 +27,7 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext;
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext;
|
||||||
import ghidra.app.services.DebuggerPlatformService;
|
import ghidra.app.services.DebuggerPlatformService;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.debug.api.platform.DebuggerPlatformMapper;
|
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.lang.*;
|
import ghidra.program.model.lang.*;
|
||||||
|
@ -40,8 +37,6 @@ import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.guest.TraceGuestPlatform;
|
import ghidra.trace.model.guest.TraceGuestPlatform;
|
||||||
import ghidra.trace.model.guest.TracePlatform;
|
import ghidra.trace.model.guest.TracePlatform;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
|
||||||
|
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
shortDescription = "Disassemble trace bytes in the debugger",
|
shortDescription = "Disassemble trace bytes in the debugger",
|
||||||
|
@ -61,21 +56,6 @@ import ghidra.trace.model.thread.TraceThread;
|
||||||
})
|
})
|
||||||
public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionProvider {
|
public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionProvider {
|
||||||
|
|
||||||
protected static class Reqs {
|
|
||||||
final DebuggerPlatformMapper mapper;
|
|
||||||
final TraceThread thread;
|
|
||||||
final TraceObject object;
|
|
||||||
final TraceProgramView view;
|
|
||||||
|
|
||||||
public Reqs(DebuggerPlatformMapper mapper, TraceThread thread, TraceObject object,
|
|
||||||
TraceProgramView view) {
|
|
||||||
this.mapper = mapper;
|
|
||||||
this.thread = thread;
|
|
||||||
this.object = object;
|
|
||||||
this.view = view;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RegisterValue deriveAlternativeDefaultContext(Language language,
|
public static RegisterValue deriveAlternativeDefaultContext(Language language,
|
||||||
LanguageID alternative, Address address) {
|
LanguageID alternative, Address address) {
|
||||||
LanguageService langServ = DefaultLanguageService.getLanguageService();
|
LanguageService langServ = DefaultLanguageService.getLanguageService();
|
||||||
|
@ -100,19 +80,11 @@ public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionPro
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoServiceConsumed
|
|
||||||
DebuggerTraceManagerService traceManager;
|
|
||||||
@AutoServiceConsumed
|
|
||||||
DebuggerPlatformService platformService;
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private final Wiring autoServiceWiring;
|
|
||||||
|
|
||||||
CurrentPlatformTraceDisassembleAction actionDisassemble;
|
CurrentPlatformTraceDisassembleAction actionDisassemble;
|
||||||
CurrentPlatformTracePatchInstructionAction actionPatchInstruction;
|
CurrentPlatformTracePatchInstructionAction actionPatchInstruction;
|
||||||
|
|
||||||
public DebuggerDisassemblerPlugin(PluginTool tool) {
|
public DebuggerDisassemblerPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
this.autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -33,6 +33,7 @@ import ghidra.debug.api.target.Target;
|
||||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||||
import ghidra.framework.cmd.BackgroundCommand;
|
import ghidra.framework.cmd.BackgroundCommand;
|
||||||
import ghidra.framework.model.DomainObject;
|
import ghidra.framework.model.DomainObject;
|
||||||
|
import ghidra.framework.model.DomainObjectChangeRecord;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
||||||
|
@ -100,10 +101,16 @@ public abstract class DebuggerReadsMemoryTrait {
|
||||||
|
|
||||||
protected class ForReadsTraceListener extends TraceDomainObjectListener {
|
protected class ForReadsTraceListener extends TraceDomainObjectListener {
|
||||||
public ForReadsTraceListener() {
|
public ForReadsTraceListener() {
|
||||||
|
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, this::objectRestored);
|
||||||
listenFor(TraceSnapshotChangeType.ADDED, this::snapshotAdded);
|
listenFor(TraceSnapshotChangeType.ADDED, this::snapshotAdded);
|
||||||
listenFor(TraceMemoryStateChangeType.CHANGED, this::memStateChanged);
|
listenFor(TraceMemoryStateChangeType.CHANGED, this::memStateChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void objectRestored(DomainObjectChangeRecord rec) {
|
||||||
|
actionRefreshSelected.updateEnabled(null);
|
||||||
|
doAutoRead();
|
||||||
|
}
|
||||||
|
|
||||||
private void snapshotAdded(TraceSnapshot snapshot) {
|
private void snapshotAdded(TraceSnapshot snapshot) {
|
||||||
actionRefreshSelected.updateEnabled(null);
|
actionRefreshSelected.updateEnabled(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ public class VisibleAutoReadMemorySpec implements AutoReadMemorySpec {
|
||||||
Target target = coordinates.getTarget();
|
Target target = coordinates.getTarget();
|
||||||
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
|
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
|
||||||
AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible,
|
AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible,
|
||||||
s -> s == TraceMemoryState.KNOWN);
|
s -> s == TraceMemoryState.KNOWN || s == TraceMemoryState.ERROR);
|
||||||
AddressSet toRead = visible.subtract(alreadyKnown);
|
AddressSet toRead = visible.subtract(alreadyKnown);
|
||||||
|
|
||||||
if (toRead.isEmpty()) {
|
if (toRead.isEmpty()) {
|
||||||
|
|
|
@ -57,7 +57,7 @@ public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec {
|
||||||
Target target = coordinates.getTarget();
|
Target target = coordinates.getTarget();
|
||||||
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
|
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
|
||||||
AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible,
|
AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible,
|
||||||
s -> s == TraceMemoryState.KNOWN);
|
s -> s == TraceMemoryState.KNOWN || s == TraceMemoryState.ERROR);
|
||||||
AddressSet toRead = visible.subtract(alreadyKnown);
|
AddressSet toRead = visible.subtract(alreadyKnown);
|
||||||
|
|
||||||
if (toRead.isEmpty()) {
|
if (toRead.isEmpty()) {
|
||||||
|
|
|
@ -48,7 +48,8 @@ import ghidra.app.nav.ListingPanelContainer;
|
||||||
import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider;
|
import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider;
|
||||||
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
||||||
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
|
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
|
||||||
import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand;
|
import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand;
|
||||||
|
import ghidra.app.plugin.core.debug.disassemble.CurrentPlatformTraceDisassembleCommand.Reqs;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel;
|
import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAction;
|
||||||
|
@ -180,10 +181,10 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
if (codeViewer.isEmpty()) {
|
if (codeViewer.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Swing.runIfSwingOrRunLater(
|
ListingPanel listingPanel = codeViewer.get().getListingPanel();
|
||||||
() -> codeViewer.get()
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
.getListingPanel()
|
listingPanel.scrollTo(new ProgramLocation(program, selection.getMinAddress()));
|
||||||
.scrollTo(new ProgramLocation(program, selection.getMinAddress())));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1203,8 +1204,10 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
}
|
}
|
||||||
AddressSpace space = start.getAddressSpace();
|
AddressSpace space = start.getAddressSpace();
|
||||||
AddressSet set = new AddressSet(space.getMinAddress(), space.getMaxAddress());
|
AddressSet set = new AddressSet(space.getMinAddress(), space.getMaxAddress());
|
||||||
TraceDisassembleCommand dis =
|
|
||||||
new TraceDisassembleCommand(current.getPlatform(), start, set);
|
Reqs reqs = Reqs.fromView(tool, view);
|
||||||
|
CurrentPlatformTraceDisassembleCommand dis =
|
||||||
|
new CurrentPlatformTraceDisassembleCommand(tool, set, reqs, start);
|
||||||
dis.run(tool, view);
|
dis.run(tool, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -210,7 +210,7 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||||
/**
|
/**
|
||||||
* Our nodes are re-usable. They're cached so that as an item comes and goes, its
|
* Our nodes are re-usable. They're cached so that as an item comes and goes, its
|
||||||
* corresponding node can also come and go without being re-instantiated each time.
|
* corresponding node can also come and go without being re-instantiated each time.
|
||||||
* Furthermore, it's like to have all the same children as before, too. For now, we'll
|
* Furthermore, it's likely to have all the same children as before, too. For now, we'll
|
||||||
* just ignore dispose. If there's too many unexpected behaviors resulting from this,
|
* just ignore dispose. If there's too many unexpected behaviors resulting from this,
|
||||||
* then perhaps we should just have dispose also remove itself from the node cache.
|
* then perhaps we should just have dispose also remove itself from the node cache.
|
||||||
*/
|
*/
|
||||||
|
@ -219,12 +219,25 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(GTreeNode node) {
|
public int compareTo(GTreeNode node) {
|
||||||
return TargetObjectKeyComparator.CHILD.compare(this.getName(), node.getName());
|
if (!(node instanceof AbstractNode that)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int c;
|
||||||
|
c = TargetObjectKeyComparator.CHILD.compare(this.getValue().getEntryKey(),
|
||||||
|
that.getValue().getEntryKey());
|
||||||
|
if (c != 0) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
c = Lifespan.DOMAIN.compare(this.getValue().getMinSnap(), that.getValue().getMinSnap());
|
||||||
|
if (c != 0) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return getValue().getEntryKey() + "@" + getValue().getMinSnap();
|
return getValue().getEntryKey() + "@" + System.identityHashCode(getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -340,14 +353,14 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayText() {
|
public String getDisplayText() {
|
||||||
if (trace == null) {
|
if (trace == null) {
|
||||||
return "<html><em>No trace is active</em>";
|
return "<html><em>No trace is active</em>";
|
||||||
}
|
}
|
||||||
TraceObject root = trace.getObjectManager().getRootObject();
|
TraceObject root = trace.getObjectManager().getRootObject();
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return "<html><em>Trace has no model</em>";
|
return "<html><em>Trace has no model</em>";
|
||||||
}
|
}
|
||||||
return "<html>" +
|
return "<html>" + HTMLUtilities
|
||||||
HTMLUtilities.escapeHTML(display.getObjectDisplay(root.getCanonicalParent(0)));
|
.escapeHTML(display.getObjectDisplay(root.getCanonicalParent(0)), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -424,7 +437,8 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayText() {
|
public String getDisplayText() {
|
||||||
String html = HTMLUtilities.escapeHTML(
|
String html = HTMLUtilities.escapeHTML(
|
||||||
value.getEntryKey() + ": " + display.getPrimitiveValueDisplay(value.getValue()));
|
value.getEntryKey() + ": " + display.getPrimitiveValueDisplay(value.getValue()),
|
||||||
|
true);
|
||||||
return "<html>" + html;
|
return "<html>" + html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,8 +485,8 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayText() {
|
public String getDisplayText() {
|
||||||
return "<html>" + HTMLUtilities.escapeHTML(value.getEntryKey()) + ": <em>" +
|
return "<html>" + HTMLUtilities.escapeHTML(value.getEntryKey(), true) + ": <em>" +
|
||||||
HTMLUtilities.escapeHTML(display.getObjectLinkDisplay(value)) + "</em>";
|
HTMLUtilities.escapeHTML(display.getObjectLinkDisplay(value), true) + "</em>";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -513,7 +527,7 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayText() {
|
public String getDisplayText() {
|
||||||
return "<html>" + HTMLUtilities.escapeHTML(display.getObjectDisplay(value));
|
return "<html>" + HTMLUtilities.escapeHTML(display.getObjectDisplay(value), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -262,6 +262,20 @@ public class ObjectsTreePanel extends JPanel {
|
||||||
}
|
}
|
||||||
//tree.filterChanged();
|
//tree.filterChanged();
|
||||||
// Repaint for bold current path is already going to happen
|
// Repaint for bold current path is already going to happen
|
||||||
|
|
||||||
|
// Repaint is not enough, as node sizes may change
|
||||||
|
for (TraceObjectKeyPath path = current.getPath(); path != null; path = path.parent()) {
|
||||||
|
AbstractNode node = treeModel.getNode(path);
|
||||||
|
if (node != null) {
|
||||||
|
node.fireNodeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (TraceObjectKeyPath path = previous.getPath(); path != null; path = path.parent()) {
|
||||||
|
AbstractNode node = treeModel.getNode(path);
|
||||||
|
if (node != null) {
|
||||||
|
node.fireNodeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -218,8 +218,41 @@ public class TerminalTextFieldElement implements FieldElement {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void paint(JComponent c, Graphics g, int x, int y) {
|
public void paint(JComponent c, Graphics g, int x, int y) {
|
||||||
line.forEachRun(
|
if (!(g instanceof Graphics2D g2)) {
|
||||||
(attrs, start, end) -> paintChars(c, g, x, y, attrs, start, end));
|
line.forEachRun(
|
||||||
|
(attrs, start, end) -> paintChars(c, g, x, y, attrs, start, end));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object aaHint = c.getClientProperty(RenderingHints.KEY_TEXT_ANTIALIASING);
|
||||||
|
Object lcdHint = c.getClientProperty(RenderingHints.KEY_TEXT_LCD_CONTRAST);
|
||||||
|
Object aaOld =
|
||||||
|
aaHint == null ? null : g2.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
|
||||||
|
Object lcdOld =
|
||||||
|
lcdHint == null ? null : g2.getRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST);
|
||||||
|
if (aaOld == aaHint) {
|
||||||
|
aaHint = null;
|
||||||
|
}
|
||||||
|
if (lcdOld == lcdHint) {
|
||||||
|
lcdHint = null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (aaHint != null) {
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, aaHint);
|
||||||
|
}
|
||||||
|
if (lcdHint != null) {
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST, lcdHint);
|
||||||
|
}
|
||||||
|
line.forEachRun(
|
||||||
|
(attrs, start, end) -> paintChars(c, g, x, y, attrs, start, end));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (aaHint != null) {
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, aaOld);
|
||||||
|
}
|
||||||
|
if (lcdHint != null) {
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_TEXT_LCD_CONTRAST, lcdOld);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -609,10 +609,16 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll the view of the listing to the given location.
|
* Scroll the view of the listing to the given location.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If the given location is not displayed, this has no effect.
|
||||||
* @param location the location
|
* @param location the location
|
||||||
*/
|
*/
|
||||||
public void scrollTo(ProgramLocation location) {
|
public void scrollTo(ProgramLocation location) {
|
||||||
FieldLocation fieldLocation = getFieldLocation(location);
|
FieldLocation fieldLocation = getFieldLocation(location);
|
||||||
|
if (fieldLocation == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
fieldPanel.scrollTo(fieldLocation);
|
fieldPanel.scrollTo(fieldLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
package agent.dbgeng.rmi;
|
package agent.dbgeng.rmi;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assume.*;
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
|
@ -27,6 +27,7 @@ import java.util.function.Function;
|
||||||
|
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest;
|
||||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
|
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiPlugin;
|
||||||
|
@ -63,6 +64,40 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
|
||||||
protected static final int TIMEOUT_SECONDS = 300;
|
protected static final int TIMEOUT_SECONDS = 300;
|
||||||
protected static final int QUIT_TIMEOUT_MS = 1000;
|
protected static final int QUIT_TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
protected static boolean didSetupPython = false;
|
||||||
|
|
||||||
|
public static final String INSTRUMENT_STATE = """
|
||||||
|
import sys
|
||||||
|
from comtypes.hresult import S_OK
|
||||||
|
from ghidradbg import commands, hooks
|
||||||
|
print("Instrumenting")
|
||||||
|
@hooks.log_errors
|
||||||
|
def on_state_changed(*args):
|
||||||
|
if args[0] != DbgEng.DEBUG_CES_EXECUTION_STATUS:
|
||||||
|
return S_OK
|
||||||
|
print("State changed: {:x}".format(args[1]))
|
||||||
|
sys.stdout.flush()
|
||||||
|
proc = util.selected_process()
|
||||||
|
trace = commands.STATE.trace
|
||||||
|
with commands.STATE.client.batch():
|
||||||
|
with trace.open_tx("State changed proc {}".format(proc)):
|
||||||
|
commands.put_state(proc)
|
||||||
|
return S_OK
|
||||||
|
|
||||||
|
# Without this, the engine seems to GO after the interrupt
|
||||||
|
@hooks.log_errors
|
||||||
|
def on_exception(*args):
|
||||||
|
return DbgEng.DEBUG_STATUS_BREAK
|
||||||
|
|
||||||
|
@util.dbg.eng_thread
|
||||||
|
def install_hooks():
|
||||||
|
print("Installing")
|
||||||
|
util.dbg._base.events.engine_state(handler=on_state_changed)
|
||||||
|
util.dbg._base.events.exception(handler=on_exception)
|
||||||
|
|
||||||
|
install_hooks()
|
||||||
|
""";
|
||||||
|
|
||||||
protected TraceRmiService traceRmi;
|
protected TraceRmiService traceRmi;
|
||||||
private Path pythonPath;
|
private Path pythonPath;
|
||||||
private Path outFile;
|
private Path outFile;
|
||||||
|
@ -75,11 +110,17 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
|
||||||
|
|
||||||
//@BeforeClass
|
//@BeforeClass
|
||||||
public static void setupPython() throws Throwable {
|
public static void setupPython() throws Throwable {
|
||||||
new ProcessBuilder("gradle", "Debugger-agent-dbgeng:assemblePyPackage")
|
if (didSetupPython) {
|
||||||
|
// Only do this once when running the full suite.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String gradle = DummyProc.which("gradle.bat");
|
||||||
|
new ProcessBuilder(gradle, "Debugger-agent-dbgeng:assemblePyPackage")
|
||||||
.directory(TestApplicationUtils.getInstallationDirectory())
|
.directory(TestApplicationUtils.getInstallationDirectory())
|
||||||
.inheritIO()
|
.inheritIO()
|
||||||
.start()
|
.start()
|
||||||
.waitFor();
|
.waitFor();
|
||||||
|
didSetupPython = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setPythonPath(ProcessBuilder pb) throws IOException {
|
protected void setPythonPath(ProcessBuilder pb) throws IOException {
|
||||||
|
@ -140,6 +181,10 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
|
||||||
if (stderr.contains("Error") || (0 != exitCode && 1 != exitCode && 143 != exitCode)) {
|
if (stderr.contains("Error") || (0 != exitCode && 1 != exitCode && 143 != exitCode)) {
|
||||||
throw new PythonError(exitCode, stdout, stderr);
|
throw new PythonError(exitCode, stdout, stderr);
|
||||||
}
|
}
|
||||||
|
System.out.println("--stdout--");
|
||||||
|
System.out.println(stdout);
|
||||||
|
System.out.println("--stderr--");
|
||||||
|
System.out.println(stderr);
|
||||||
return stdout;
|
return stdout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,9 +272,9 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
|
||||||
return execute.invokeAsync(Map.of("cmd", cmd));
|
return execute.invokeAsync(Map.of("cmd", cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String executeCapture(String expr) {
|
public String executeCapture(String cmd) {
|
||||||
RemoteMethod execute = getMethod("evaluate");
|
RemoteMethod execute = getMethod("execute");
|
||||||
return (String) execute.invoke(Map.of("expr", expr));
|
return (String) execute.invoke(Map.of("cmd", cmd, "to_string", true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -281,15 +326,15 @@ public abstract class AbstractDbgEngTraceRmiTest extends AbstractGhidraHeadedDeb
|
||||||
return stdout;
|
return stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void waitStopped() {
|
protected void waitStopped(String message) {
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
|
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
|
||||||
waitForPass(() -> assertEquals("STOPPED", tb.objValue(proc, 0, "_state")));
|
waitForPass(() -> assertEquals(message, "STOPPED", tb.objValue(proc, 0, "_state")));
|
||||||
waitTxDone();
|
waitTxDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void waitRunning() {
|
protected void waitRunning(String message) {
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
|
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]", Lifespan.at(0)));
|
||||||
waitForPass(() -> assertEquals("RUNNING", tb.objValue(proc, 0, "_state")));
|
waitForPass(() -> assertEquals(message, "RUNNING", tb.objValue(proc, 0, "_state")));
|
||||||
waitTxDone();
|
waitTxDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,18 +28,23 @@ import java.util.stream.IntStream;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import db.Transaction;
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||||
import ghidra.dbg.util.PathPredicates;
|
import ghidra.dbg.util.PathPredicates;
|
||||||
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
|
||||||
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
import ghidra.debug.api.tracermi.TraceRmiConnection;
|
||||||
|
import ghidra.framework.Application;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
|
import ghidra.program.model.data.Float10DataType;
|
||||||
import ghidra.program.model.lang.RegisterValue;
|
import ghidra.program.model.lang.RegisterValue;
|
||||||
import ghidra.program.model.listing.CodeUnit;
|
import ghidra.program.model.listing.CodeUnit;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||||
|
import ghidra.trace.model.listing.TraceCodeSpace;
|
||||||
|
import ghidra.trace.model.listing.TraceData;
|
||||||
import ghidra.trace.model.memory.*;
|
import ghidra.trace.model.memory.*;
|
||||||
import ghidra.trace.model.modules.TraceModule;
|
import ghidra.trace.model.modules.TraceModule;
|
||||||
import ghidra.trace.model.target.*;
|
import ghidra.trace.model.target.*;
|
||||||
|
@ -176,9 +181,11 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
%s
|
%s
|
||||||
print('---Import---')
|
print('---Import---')
|
||||||
ghidra_trace_info()
|
ghidra_trace_info()
|
||||||
|
print('---BeforeConnect---')
|
||||||
ghidra_trace_connect('%s')
|
ghidra_trace_connect('%s')
|
||||||
print('---Connect---')
|
print('---Connect---')
|
||||||
ghidra_trace_info()
|
ghidra_trace_info()
|
||||||
|
print('---Create---')
|
||||||
ghidra_trace_create('notepad.exe')
|
ghidra_trace_create('notepad.exe')
|
||||||
print('---Start---')
|
print('---Start---')
|
||||||
ghidra_trace_info()
|
ghidra_trace_info()
|
||||||
|
@ -196,20 +203,19 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
Not connected to Ghidra""",
|
Not connected to Ghidra""",
|
||||||
extractOutSection(out, "---Import---"));
|
extractOutSection(out, "---Import---"));
|
||||||
assertEquals("""
|
assertEquals("""
|
||||||
Connected to Ghidra at %s
|
Connected to %s %s at %s
|
||||||
|
No trace""".formatted(
|
||||||
No trace""".formatted(refAddr.get()),
|
Application.getName(), Application.getApplicationVersion(), refAddr.get()),
|
||||||
extractOutSection(out, "---Connect---").replaceAll("\r", "").substring(0, 48));
|
extractOutSection(out, "---Connect---").replaceAll("\r", ""));
|
||||||
String expected = """
|
|
||||||
Connected to Ghidra at %s
|
|
||||||
|
|
||||||
Trace active""".formatted(refAddr.get());
|
|
||||||
String actual = extractOutSection(out, "---Start---").replaceAll("\r", "");
|
|
||||||
assertEquals(expected, actual.substring(0, expected.length()));
|
|
||||||
assertEquals("""
|
assertEquals("""
|
||||||
Connected to Ghidra at %s
|
Connected to %s %s at %s
|
||||||
|
Trace active""".formatted(
|
||||||
No trace""".formatted(refAddr.get()),
|
Application.getName(), Application.getApplicationVersion(), refAddr.get()),
|
||||||
|
extractOutSection(out, "---Start---").replaceAll("\r", ""));
|
||||||
|
assertEquals("""
|
||||||
|
Connected to %s %s at %s
|
||||||
|
No trace""".formatted(
|
||||||
|
Application.getName(), Application.getApplicationVersion(), refAddr.get()),
|
||||||
extractOutSection(out, "---Stop---").replaceAll("\r", ""));
|
extractOutSection(out, "---Stop---").replaceAll("\r", ""));
|
||||||
assertEquals("""
|
assertEquals("""
|
||||||
Not connected to Ghidra""",
|
Not connected to Ghidra""",
|
||||||
|
@ -224,7 +230,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
%s
|
%s
|
||||||
print('---Import---')
|
print('---Import---')
|
||||||
ghidra_trace_info_lcsp()
|
ghidra_trace_info_lcsp()
|
||||||
print('---')
|
print('---Create---')
|
||||||
ghidra_trace_create('notepad.exe', start_trace=False)
|
ghidra_trace_create('notepad.exe', start_trace=False)
|
||||||
print('---File---')
|
print('---File---')
|
||||||
ghidra_trace_info_lcsp()
|
ghidra_trace_info_lcsp()
|
||||||
|
@ -237,21 +243,16 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
quit()
|
quit()
|
||||||
""".formatted(PREAMBLE));
|
""".formatted(PREAMBLE));
|
||||||
|
|
||||||
assertTrue(
|
assertEquals("""
|
||||||
extractOutSection(out, "---File---").replaceAll("\r", "")
|
Selected Ghidra language: x86:LE:64:default
|
||||||
.contains(
|
Selected Ghidra compiler: windows""",
|
||||||
"""
|
extractOutSection(out, "---File---").replaceAll("\r", ""));
|
||||||
Selected Ghidra language: x86:LE:64:default
|
|
||||||
|
|
||||||
Selected Ghidra compiler: windows"""));
|
|
||||||
assertEquals("""
|
assertEquals("""
|
||||||
Selected Ghidra language: Toy:BE:64:default
|
Selected Ghidra language: Toy:BE:64:default
|
||||||
|
|
||||||
Selected Ghidra compiler: default""",
|
Selected Ghidra compiler: default""",
|
||||||
extractOutSection(out, "---Language---").replaceAll("\r", ""));
|
extractOutSection(out, "---Language---").replaceAll("\r", ""));
|
||||||
assertEquals("""
|
assertEquals("""
|
||||||
Selected Ghidra language: Toy:BE:64:default
|
Selected Ghidra language: Toy:BE:64:default
|
||||||
|
|
||||||
Selected Ghidra compiler: posStack""",
|
Selected Ghidra compiler: posStack""",
|
||||||
extractOutSection(out, "---Compiler---").replaceAll("\r", ""));
|
extractOutSection(out, "---Compiler---").replaceAll("\r", ""));
|
||||||
}
|
}
|
||||||
|
@ -319,11 +320,11 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
ghidra_trace_create('notepad.exe')
|
ghidra_trace_create('notepad.exe')
|
||||||
ghidra_trace_txstart('Create snapshot')
|
ghidra_trace_txstart('Create snapshot')
|
||||||
ghidra_trace_new_snap('Scripted snapshot')
|
ghidra_trace_new_snap('Scripted snapshot')
|
||||||
ghidra_trace_putmem('$pc 16')
|
pc = util.get_pc()
|
||||||
|
ghidra_trace_putmem(pc, 16)
|
||||||
ghidra_trace_txcommit()
|
ghidra_trace_txcommit()
|
||||||
print('---Dump---')
|
print('---Dump---')
|
||||||
pc = util.get_debugger().reg.get_pc()
|
util.dbg.dd(pc, count=1)
|
||||||
util.get_debugger().dd(pc, count=1)
|
|
||||||
print('---')
|
print('---')
|
||||||
ghidra_trace_kill()
|
ghidra_trace_kill()
|
||||||
quit()
|
quit()
|
||||||
|
@ -348,11 +349,11 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
ghidra_trace_create('notepad.exe')
|
ghidra_trace_create('notepad.exe')
|
||||||
ghidra_trace_txstart('Create snapshot')
|
ghidra_trace_txstart('Create snapshot')
|
||||||
ghidra_trace_new_snap('Scripted snapshot')
|
ghidra_trace_new_snap('Scripted snapshot')
|
||||||
ghidra_trace_putmem_state('$pc 16 error')
|
pc = util.get_pc()
|
||||||
|
ghidra_trace_putmem_state(pc, 16, 'error', pages=False)
|
||||||
ghidra_trace_txcommit()
|
ghidra_trace_txcommit()
|
||||||
print('---Start---')
|
print('---Start---')
|
||||||
pc = util.get_debugger().reg.get_pc()
|
util.dbg.dd(pc, count=1)
|
||||||
util.get_debugger().dd(pc, count=1)
|
|
||||||
print('---')
|
print('---')
|
||||||
ghidra_trace_kill()
|
ghidra_trace_kill()
|
||||||
quit()
|
quit()
|
||||||
|
@ -380,12 +381,12 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
ghidra_trace_create('notepad.exe')
|
ghidra_trace_create('notepad.exe')
|
||||||
ghidra_trace_txstart('Create snapshot')
|
ghidra_trace_txstart('Create snapshot')
|
||||||
ghidra_trace_new_snap('Scripted snapshot')
|
ghidra_trace_new_snap('Scripted snapshot')
|
||||||
ghidra_trace_putmem('$pc 16')
|
pc = util.get_pc()
|
||||||
ghidra_trace_delmem('$pc 8')
|
ghidra_trace_putmem(pc, 16)
|
||||||
|
ghidra_trace_delmem(pc, 8)
|
||||||
ghidra_trace_txcommit()
|
ghidra_trace_txcommit()
|
||||||
print('---Dump---')
|
print('---Dump---')
|
||||||
pc = util.get_debugger().reg.get_pc()
|
util.dbg.dd(pc, count=1)
|
||||||
util.get_debugger().dd(pc, count=1)
|
|
||||||
print('---')
|
print('---')
|
||||||
ghidra_trace_kill()
|
ghidra_trace_kill()
|
||||||
quit()
|
quit()
|
||||||
|
@ -412,9 +413,8 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
%s
|
%s
|
||||||
ghidra_trace_connect('%s')
|
ghidra_trace_connect('%s')
|
||||||
ghidra_trace_create('notepad.exe')
|
ghidra_trace_create('notepad.exe')
|
||||||
regs = util.get_debugger().reg
|
util.dbg.cmd('r rax=0xdeadbeef')
|
||||||
regs._set_register("rax", int(0xdeadbeef))
|
util.dbg.cmd('r st0=1.5')
|
||||||
regs._set_register("st0", int(1.5))
|
|
||||||
ghidra_trace_txstart('Create snapshot')
|
ghidra_trace_txstart('Create snapshot')
|
||||||
ghidra_trace_new_snap('Scripted snapshot')
|
ghidra_trace_new_snap('Scripted snapshot')
|
||||||
ghidra_trace_putreg()
|
ghidra_trace_putreg()
|
||||||
|
@ -438,20 +438,15 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
|
RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
|
||||||
assertEquals("deadbeef", rax.getUnsignedValue().toString(16));
|
assertEquals("deadbeef", rax.getUnsignedValue().toString(16));
|
||||||
|
|
||||||
// TODO: Pybag currently doesn't suppport non-int assignments
|
TraceData st0;
|
||||||
/*
|
try (Transaction tx = tb.trace.openTransaction("Float80 unit")) {
|
||||||
* // RegisterValue ymm0 = regs.getValue(snap, tb.reg("ymm0")); // // LLDB
|
TraceCodeSpace code = tb.trace.getCodeManager().getCodeSpace(t1f0, true);
|
||||||
* treats registers in arch's endian // assertEquals(
|
st0 = code.definedData()
|
||||||
* "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100", //
|
.create(Lifespan.nowOn(0), tb.reg("st0"), Float10DataType.dataType);
|
||||||
* ymm0.getUnsignedValue().toString(16));
|
}
|
||||||
*
|
|
||||||
* // TraceData st0; // try (Transaction tx =
|
// TODO: Pybag doesn't support non-integer registers
|
||||||
* tb.trace.openTransaction("Float80 unit")) { // TraceCodeSpace code =
|
// assertEquals("1.5", st0.getDefaultValueRepresentation());
|
||||||
* tb.trace.getCodeManager().getCodeSpace(t1f0, true); // st0 =
|
|
||||||
* code.definedData() // .create(Lifespan.nowOn(0), tb.reg("st0"),
|
|
||||||
* Float10DataType.dataType); // } // assertEquals("1.5",
|
|
||||||
* st0.getDefaultValueRepresentation());
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,8 +459,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
%s
|
%s
|
||||||
ghidra_trace_connect('%s')
|
ghidra_trace_connect('%s')
|
||||||
ghidra_trace_create('notepad.exe')
|
ghidra_trace_create('notepad.exe')
|
||||||
regs = util.get_debugger().reg
|
util.dbg.cmd('r rax=0xdeadbeef')
|
||||||
regs._set_register("st0", int(1.5))
|
|
||||||
ghidra_trace_txstart('Create snapshot')
|
ghidra_trace_txstart('Create snapshot')
|
||||||
ghidra_trace_new_snap('Scripted snapshot')
|
ghidra_trace_new_snap('Scripted snapshot')
|
||||||
ghidra_trace_putreg()
|
ghidra_trace_putreg()
|
||||||
|
@ -490,19 +484,6 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
|
|
||||||
RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
|
RegisterValue rax = regs.getValue(snap, tb.reg("rax"));
|
||||||
assertEquals("0", rax.getUnsignedValue().toString(16));
|
assertEquals("0", rax.getUnsignedValue().toString(16));
|
||||||
|
|
||||||
// TODO: As above, not currently supported by pybag
|
|
||||||
/*
|
|
||||||
* // RegisterValue ymm0 = regs.getValue(snap, tb.reg("ymm0")); //
|
|
||||||
* assertEquals("0", ymm0.getUnsignedValue().toString(16));
|
|
||||||
*
|
|
||||||
* // TraceData st0; // try (Transaction tx =
|
|
||||||
* tb.trace.openTransaction("Float80 unit")) { // TraceCodeSpace code =
|
|
||||||
* tb.trace.getCodeManager().getCodeSpace(t1f0, true); // st0 =
|
|
||||||
* code.definedData() // .create(Lifespan.nowOn(0), tb.reg("st0"),
|
|
||||||
* Float10DataType.dataType); // } // assertEquals("0.0",
|
|
||||||
* st0.getDefaultValueRepresentation());
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,9 +532,8 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
assertNotNull(object);
|
assertNotNull(object);
|
||||||
Lifespan life = Unique.assertOne(object.getLife().spans());
|
Lifespan life = Unique.assertOne(object.getLife().spans());
|
||||||
assertEquals(Lifespan.nowOn(0), life);
|
assertEquals(Lifespan.nowOn(0), life);
|
||||||
String expected = "Inserted object: lifespan=[0,+inf)";
|
assertEquals("Inserted object: lifespan=[0,+inf)",
|
||||||
String actual = extractOutSection(out, "---Lifespan---");
|
extractOutSection(out, "---Lifespan---"));
|
||||||
assertEquals(expected, actual.substring(0, expected.length()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -788,9 +768,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
TraceObject object = tb.trace.getObjectManager()
|
TraceObject object = tb.trace.getObjectManager()
|
||||||
.getObjectByCanonicalPath(TraceObjectKeyPath.parse("Test.Objects[1]"));
|
.getObjectByCanonicalPath(TraceObjectKeyPath.parse("Test.Objects[1]"));
|
||||||
assertNotNull(object);
|
assertNotNull(object);
|
||||||
String expected = "1\tTest.Objects[1]";
|
assertEquals("1\tTest.Objects[1]", extractOutSection(out, "---GetObject---"));
|
||||||
String actual = extractOutSection(out, "---GetObject---");
|
|
||||||
assertEquals(expected, actual.substring(0, expected.length()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -834,8 +812,9 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
""".formatted(PREAMBLE, addr));
|
""".formatted(PREAMBLE, addr));
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
String expected = """
|
assertEquals("""
|
||||||
Parent Key Span Value Type
|
Parent Key Span Value Type
|
||||||
|
Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS
|
||||||
Test.Objects[1] vbool [0,+inf) True BOOL
|
Test.Objects[1] vbool [0,+inf) True BOOL
|
||||||
Test.Objects[1] vboolarr [0,+inf) [True, False] BOOL_ARR
|
Test.Objects[1] vboolarr [0,+inf) [True, False] BOOL_ARR
|
||||||
Test.Objects[1] vbyte [0,+inf) 1 BYTE
|
Test.Objects[1] vbyte [0,+inf) 1 BYTE
|
||||||
|
@ -849,16 +828,8 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
Test.Objects[1] vobj [0,+inf) Test.Objects[1] OBJECT
|
Test.Objects[1] vobj [0,+inf) Test.Objects[1] OBJECT
|
||||||
Test.Objects[1] vshort [0,+inf) 2 SHORT
|
Test.Objects[1] vshort [0,+inf) 2 SHORT
|
||||||
Test.Objects[1] vshortarr [0,+inf) [1, 2, 3] SHORT_ARR
|
Test.Objects[1] vshortarr [0,+inf) [1, 2, 3] SHORT_ARR
|
||||||
Test.Objects[1] vstring [0,+inf) 'Hello' STRING
|
Test.Objects[1] vstring [0,+inf) 'Hello' STRING""",
|
||||||
Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS"""
|
extractOutSection(out, "---GetValues---").replaceAll("\r", ""));
|
||||||
.replaceAll(" ", "")
|
|
||||||
.replaceAll("\n", "");
|
|
||||||
String actual = extractOutSection(out, "---GetValues---").replaceAll(" ", "")
|
|
||||||
.replaceAll("\r", "")
|
|
||||||
.replaceAll("\n", "");
|
|
||||||
assertEquals(
|
|
||||||
expected,
|
|
||||||
actual.substring(0, expected.length()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -874,19 +845,17 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
ghidra_trace_set_value('Test.Objects[1]', 'vaddr', '(void*)0xdeadbeef', 'ADDRESS')
|
ghidra_trace_set_value('Test.Objects[1]', 'vaddr', '(void*)0xdeadbeef', 'ADDRESS')
|
||||||
ghidra_trace_txcommit()
|
ghidra_trace_txcommit()
|
||||||
print('---GetValues---')
|
print('---GetValues---')
|
||||||
ghidra_trace_get_values_rng('(void*)0xdeadbeef 10')
|
ghidra_trace_get_values_rng(0xdeadbeef, 10)
|
||||||
print('---')
|
print('---')
|
||||||
ghidra_trace_kill()
|
ghidra_trace_kill()
|
||||||
quit()
|
quit()
|
||||||
""".formatted(PREAMBLE, addr));
|
""".formatted(PREAMBLE, addr));
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
String expected = """
|
assertEquals("""
|
||||||
Parent Key Span Value Type
|
Parent Key Span Value Type
|
||||||
|
Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS""",
|
||||||
Test.Objects[1] vaddr [0,+inf) ram:deadbeef ADDRESS""";
|
extractOutSection(out, "---GetValues---").replaceAll("\r", ""));
|
||||||
String actual = extractOutSection(out, "---GetValues---").replaceAll("\r", "");
|
|
||||||
assertEquals(expected, actual.substring(0, expected.length()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -919,9 +888,10 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
ghidra_trace_connect('%s')
|
ghidra_trace_connect('%s')
|
||||||
ghidra_trace_create('notepad.exe')
|
ghidra_trace_create('notepad.exe')
|
||||||
ghidra_trace_txstart('Tx')
|
ghidra_trace_txstart('Tx')
|
||||||
ghidra_trace_putmem('$pc 16')
|
pc = util.get_pc()
|
||||||
|
ghidra_trace_putmem(pc, 16)
|
||||||
print('---Disassemble---')
|
print('---Disassemble---')
|
||||||
ghidra_trace_disassemble('$pc')
|
ghidra_trace_disassemble(pc)
|
||||||
print('---')
|
print('---')
|
||||||
ghidra_trace_txcommit()
|
ghidra_trace_txcommit()
|
||||||
ghidra_trace_kill()
|
ghidra_trace_kill()
|
||||||
|
@ -991,10 +961,9 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
%s
|
%s
|
||||||
ghidra_trace_connect('%s')
|
ghidra_trace_connect('%s')
|
||||||
ghidra_trace_create('notepad.exe')
|
ghidra_trace_create('notepad.exe')
|
||||||
dbg = util.get_debugger()
|
pc = util.get_pc()
|
||||||
pc = dbg.reg.get_pc()
|
util.dbg.bp(expr=pc)
|
||||||
dbg.bp(expr=pc)
|
util.dbg.ba(expr=pc+4)
|
||||||
dbg.ba(expr=pc+4)
|
|
||||||
ghidra_trace_txstart('Tx')
|
ghidra_trace_txstart('Tx')
|
||||||
ghidra_trace_put_breakpoints()
|
ghidra_trace_put_breakpoints()
|
||||||
ghidra_trace_txcommit()
|
ghidra_trace_txcommit()
|
||||||
|
@ -1007,6 +976,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
.getValuePaths(Lifespan.at(0),
|
.getValuePaths(Lifespan.at(0),
|
||||||
PathPredicates.parse("Processes[].Breakpoints[]"))
|
PathPredicates.parse("Processes[].Breakpoints[]"))
|
||||||
.map(p -> p.getLastEntry())
|
.map(p -> p.getLastEntry())
|
||||||
|
.sorted(Comparator.comparing(TraceObjectValue::getEntryKey))
|
||||||
.toList();
|
.toList();
|
||||||
assertEquals(2, procBreakLocVals.size());
|
assertEquals(2, procBreakLocVals.size());
|
||||||
AddressRange rangeMain =
|
AddressRange rangeMain =
|
||||||
|
@ -1029,11 +999,10 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
ghidra_trace_connect('%s')
|
ghidra_trace_connect('%s')
|
||||||
ghidra_trace_create('notepad.exe')
|
ghidra_trace_create('notepad.exe')
|
||||||
ghidra_trace_txstart('Tx')
|
ghidra_trace_txstart('Tx')
|
||||||
dbg = util.get_debugger()
|
pc = util.get_pc()
|
||||||
pc = dbg.reg.get_pc()
|
util.dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)
|
||||||
dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)
|
util.dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)
|
||||||
dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)
|
util.dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)
|
||||||
dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)
|
|
||||||
ghidra_trace_put_breakpoints()
|
ghidra_trace_put_breakpoints()
|
||||||
ghidra_trace_txcommit()
|
ghidra_trace_txcommit()
|
||||||
ghidra_trace_kill()
|
ghidra_trace_kill()
|
||||||
|
@ -1045,6 +1014,7 @@ public class DbgEngCommandsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
.getValuePaths(Lifespan.at(0),
|
.getValuePaths(Lifespan.at(0),
|
||||||
PathPredicates.parse("Processes[].Breakpoints[]"))
|
PathPredicates.parse("Processes[].Breakpoints[]"))
|
||||||
.map(p -> p.getLastEntry())
|
.map(p -> p.getLastEntry())
|
||||||
|
.sorted(Comparator.comparing(TraceObjectValue::getEntryKey))
|
||||||
.toList();
|
.toList();
|
||||||
assertEquals(3, procBreakVals.size());
|
assertEquals(3, procBreakVals.size());
|
||||||
AddressRange rangeMain0 =
|
AddressRange rangeMain0 =
|
||||||
|
|
|
@ -15,12 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package agent.dbgeng.rmi;
|
package agent.dbgeng.rmi;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -28,19 +27,22 @@ import org.junit.Test;
|
||||||
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
import ghidra.app.plugin.core.debug.utils.ManagedDomainObject;
|
||||||
import ghidra.dbg.util.PathPattern;
|
import ghidra.dbg.util.PathPattern;
|
||||||
import ghidra.dbg.util.PathPredicates;
|
import ghidra.dbg.util.PathPredicates;
|
||||||
|
import ghidra.debug.api.tracermi.RemoteMethod;
|
||||||
import ghidra.program.model.address.AddressSpace;
|
import ghidra.program.model.address.AddressSpace;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
import ghidra.trace.model.Lifespan;
|
import ghidra.trace.model.Lifespan;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||||
|
import ghidra.trace.model.memory.TraceMemoryState;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
|
||||||
public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||||
private static final long RUN_TIMEOUT_MS = 20000;
|
private static final long RUN_TIMEOUT_MS = 5000;
|
||||||
private static final long RETRY_MS = 500;
|
private static final long RETRY_MS = 500;
|
||||||
|
|
||||||
record PythonAndTrace(PythonAndConnection conn, ManagedDomainObject mdo) implements AutoCloseable {
|
record PythonAndTrace(PythonAndConnection conn, ManagedDomainObject mdo)
|
||||||
|
implements AutoCloseable {
|
||||||
public void execute(String cmd) {
|
public void execute(String cmd) {
|
||||||
conn.execute(cmd);
|
conn.execute(cmd);
|
||||||
}
|
}
|
||||||
|
@ -85,8 +87,9 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||||
return conn.conn.connection().getLastSnapshot(tb.trace);
|
return conn.conn.connection().getLastSnapshot(tb.trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // The 10s wait makes this a pretty expensive test
|
@Test
|
||||||
public void testOnNewThread() throws Exception {
|
public void testOnNewThread() throws Exception {
|
||||||
|
final int INIT_NOTEPAD_THREAD_COUNT = 4; // This could be fragile
|
||||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||||
conn.execute("from ghidradbg.commands import *");
|
conn.execute("from ghidradbg.commands import *");
|
||||||
txPut(conn, "processes");
|
txPut(conn, "processes");
|
||||||
|
@ -98,14 +101,18 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
|
|
||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
waitForPass(() -> assertEquals(4,
|
waitForPass(() -> assertEquals(INIT_NOTEPAD_THREAD_COUNT,
|
||||||
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
|
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
|
||||||
RUN_TIMEOUT_MS, RETRY_MS);
|
RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
|
|
||||||
conn.execute("dbg().go(10)");
|
// Via method, go is asynchronous
|
||||||
|
RemoteMethod go = conn.conn.getMethod("go");
|
||||||
|
TraceObject proc = tb.objAny("Processes[]");
|
||||||
|
go.invoke(Map.of("process", proc));
|
||||||
|
|
||||||
waitForPass(
|
waitForPass(
|
||||||
() -> assertTrue(tb.objValues(lastSnap(conn), "Processes[].Threads[]").size() > 4),
|
() -> assertThat(tb.objValues(lastSnap(conn), "Processes[].Threads[]").size(),
|
||||||
|
greaterThan(INIT_NOTEPAD_THREAD_COUNT)),
|
||||||
RUN_TIMEOUT_MS, RETRY_MS);
|
RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,39 +123,39 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||||
txPut(conn, "processes");
|
txPut(conn, "processes");
|
||||||
|
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
TraceObject inf = tb.objAny("Processes[]");
|
TraceObject proc = tb.obj("Processes[0]");
|
||||||
assertNotNull(inf);
|
assertNotNull(proc);
|
||||||
assertEquals("STOPPED", tb.objValue(inf, lastSnap(conn), "_state"));
|
assertEquals("STOPPED", tb.objValue(proc, lastSnap(conn), "_state"));
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
|
|
||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
waitForPass(() -> assertEquals(4,
|
waitForPass(() -> assertEquals(4,
|
||||||
tb.objValues(lastSnap(conn), "Processes[].Threads[]").size()),
|
tb.objValues(lastSnap(conn), "Processes[0].Threads[]").size()),
|
||||||
RUN_TIMEOUT_MS, RETRY_MS);
|
RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
|
|
||||||
// Now the real test
|
// Now the real test
|
||||||
|
conn.execute("print('Selecting 1')");
|
||||||
conn.execute("util.select_thread(1)");
|
conn.execute("util.select_thread(1)");
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
String tnum = conn.executeCapture("util.selected_thread()");
|
String tnum = conn.executeCapture("print(util.selected_thread())").strip();
|
||||||
assertTrue(tnum.contains("1"));
|
assertEquals("1", tnum);
|
||||||
String threadIndex = threadIndex(traceManager.getCurrentObject());
|
assertEquals(tb.obj("Processes[0].Threads[1]"), traceManager.getCurrentObject());
|
||||||
assertTrue(tnum.contains(threadIndex));
|
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
|
|
||||||
conn.execute("util.select_thread(2)");
|
conn.execute("util.select_thread(2)");
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
String tnum = conn.executeCapture("util.selected_thread()");
|
String tnum = conn.executeCapture("print(util.selected_thread())").strip();
|
||||||
assertTrue(tnum.contains("2"));
|
assertEquals("2", tnum);
|
||||||
String threadIndex = threadIndex(traceManager.getCurrentObject());
|
String threadIndex = threadIndex(traceManager.getCurrentObject());
|
||||||
assertTrue(tnum.contains(threadIndex));
|
assertEquals("2", threadIndex);
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
|
|
||||||
conn.execute("util.select_thread(0)");
|
conn.execute("util.select_thread(0)");
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
String tnum = conn.executeCapture("util.selected_thread()");
|
String tnum = conn.executeCapture("print(util.selected_thread())").strip();
|
||||||
assertTrue(tnum.contains("0"));
|
assertEquals("0", tnum);
|
||||||
String threadIndex = threadIndex(traceManager.getCurrentObject());
|
String threadIndex = threadIndex(traceManager.getCurrentObject());
|
||||||
assertTrue(tnum.contains(threadIndex));
|
assertEquals("0", threadIndex);
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,21 +197,44 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||||
// FWIW, I've already seen this getting exercised in other tests.
|
// 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
|
* dbgeng has limited support via DEBUG_CDS_DATA. It tells us what space has changed, but not
|
||||||
|
* the address(es). We have some options:
|
||||||
|
*
|
||||||
|
* 1) Ignore it. This puts the onus of refreshing on the user. The upside is that past
|
||||||
|
* observations are preserved. The downside is we can't be certain of their accuracy.
|
||||||
|
*
|
||||||
|
* 2) Invalidate the entire space. This will ensure the UI either updates automatically or
|
||||||
|
* indicates the possible staleness. The downside is that we lose past observations.
|
||||||
|
*
|
||||||
|
* 3) Remember what addresses have been fetched since last BREAK, and refresh them all. This is
|
||||||
|
* better than refreshing the entire space (prohibitive), but we could get right back there if
|
||||||
|
* the user has captured the full space and then modifies a single byte.
|
||||||
|
*
|
||||||
|
* For the moment, we favor option (2), as we'd prefer never to display inaccurate data,
|
||||||
|
* especially as non-stale. The lost observations are a small price to pay, since they're not
|
||||||
|
* particularly important for the interactive use case.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
public void testOnMemoryChanged() throws Exception {
|
public void testOnMemoryChanged() throws Exception {
|
||||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
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);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
conn.execute("util.get_debugger().write(" + address + ", b'\\x7f')");
|
|
||||||
|
conn.execute("ghidra_trace_txstart('Tx')");
|
||||||
|
conn.execute("ghidra_trace_putmem(%d, 10)".formatted(address));
|
||||||
|
conn.execute("ghidra_trace_txcommit()");
|
||||||
|
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
ByteBuffer buf = ByteBuffer.allocate(10);
|
assertEquals(TraceMemoryState.KNOWN,
|
||||||
tb.trace.getMemoryManager().getBytes(lastSnap(conn), tb.addr(address), buf);
|
tb.trace.getMemoryManager().getState(lastSnap(conn), tb.addr(address)));
|
||||||
assertEquals(0x7f, buf.get(0));
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
|
|
||||||
|
conn.execute("util.dbg.write(%d, b'\\x7f')".formatted(address));
|
||||||
|
|
||||||
|
waitForPass(() -> {
|
||||||
|
assertEquals(TraceMemoryState.UNKNOWN,
|
||||||
|
tb.trace.getMemoryManager().getState(lastSnap(conn), tb.addr(address)));
|
||||||
}, RUN_TIMEOUT_MS, RETRY_MS);
|
}, RUN_TIMEOUT_MS, RETRY_MS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,8 +246,7 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||||
conn.execute("ghidra_trace_txstart('Tx')");
|
conn.execute("ghidra_trace_txstart('Tx')");
|
||||||
conn.execute("ghidra_trace_putreg()");
|
conn.execute("ghidra_trace_putreg()");
|
||||||
conn.execute("ghidra_trace_txcommit()");
|
conn.execute("ghidra_trace_txcommit()");
|
||||||
conn.execute("util.get_debugger().reg._set_register('rax', 0x1234)");
|
conn.execute("util.dbg.cmd('r rax=0x1234')");
|
||||||
conn.execute("util.get_debugger().stepi()");
|
|
||||||
|
|
||||||
String path = "Processes[].Threads[].Registers";
|
String path = "Processes[].Threads[].Registers";
|
||||||
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
|
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
|
||||||
|
@ -234,8 +263,15 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||||
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
try (PythonAndTrace conn = startAndSyncPython("notepad.exe")) {
|
||||||
txPut(conn, "processes");
|
txPut(conn, "processes");
|
||||||
|
|
||||||
conn.execute("util.get_debugger()._control.SetExecutionStatus(DbgEng.DEBUG_STATUS_GO)");
|
// WaitForEvents is not required for this test to pass.
|
||||||
waitRunning();
|
conn.execute("""
|
||||||
|
@util.dbg.eng_thread
|
||||||
|
def go_no_wait():
|
||||||
|
util.dbg._base._control.SetExecutionStatus(DbgEng.DEBUG_STATUS_GO)
|
||||||
|
|
||||||
|
go_no_wait()
|
||||||
|
""");
|
||||||
|
waitRunning("Missed running after go");
|
||||||
|
|
||||||
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
|
TraceObject proc = waitForValue(() -> tb.objAny("Processes[]"));
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
|
@ -260,9 +296,10 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||||
public void testOnExited() throws Exception {
|
public void testOnExited() throws Exception {
|
||||||
try (PythonAndTrace conn = startAndSyncPython("netstat.exe")) {
|
try (PythonAndTrace conn = startAndSyncPython("netstat.exe")) {
|
||||||
txPut(conn, "processes");
|
txPut(conn, "processes");
|
||||||
waitStopped();
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
conn.execute("util.get_debugger().go()");
|
// Do the synchronous wait here, since netstat should terminate
|
||||||
|
conn.execute("util.dbg.go()");
|
||||||
|
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
TraceSnapshot snapshot =
|
TraceSnapshot snapshot =
|
||||||
|
@ -285,14 +322,12 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||||
txPut(conn, "breakpoints");
|
txPut(conn, "breakpoints");
|
||||||
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
||||||
|
|
||||||
conn.execute("dbg = util.get_debugger()");
|
conn.execute("pc = util.get_pc()");
|
||||||
conn.execute("pc = dbg.reg.get_pc()");
|
conn.execute("util.dbg.bp(expr=pc)");
|
||||||
conn.execute("dbg.bp(expr=pc)");
|
|
||||||
|
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
|
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
|
||||||
assertEquals(1, brks.size());
|
assertEquals(1, brks.size());
|
||||||
return (TraceObject) brks.get(0);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -303,24 +338,22 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||||
txPut(conn, "breakpoints");
|
txPut(conn, "breakpoints");
|
||||||
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
||||||
|
|
||||||
conn.execute("dbg = util.get_debugger()");
|
conn.execute("pc = util.get_pc()");
|
||||||
conn.execute("pc = dbg.reg.get_pc()");
|
conn.execute("util.dbg.bp(expr=pc)");
|
||||||
conn.execute("dbg.bp(expr=pc)");
|
|
||||||
|
|
||||||
TraceObject brk = waitForPass(() -> {
|
TraceObject brk = waitForPass(() -> {
|
||||||
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
|
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
|
||||||
assertEquals(1, brks.size());
|
assertEquals(1, brks.size());
|
||||||
return (TraceObject) brks.get(0);
|
return (TraceObject) brks.get(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(true, tb.objValue(brk, lastSnap(conn), "Enabled"));
|
assertEquals(true, tb.objValue(brk, lastSnap(conn), "Enabled"));
|
||||||
conn.execute("dbg.bd(0)");
|
conn.execute("util.dbg.bd(0)");
|
||||||
conn.execute("dbg.stepi()");
|
|
||||||
assertEquals(false, tb.objValue(brk, lastSnap(conn), "Enabled"));
|
assertEquals(false, tb.objValue(brk, lastSnap(conn), "Enabled"));
|
||||||
|
|
||||||
/* Not currently enabled
|
/* Not currently enabled
|
||||||
assertEquals("", tb.objValue(brk, lastSnap(conn), "Command"));
|
assertEquals("", tb.objValue(brk, lastSnap(conn), "Command"));
|
||||||
conn.execute("dbg.bp(expr=pc, windbgcmd='bl')");
|
conn.execute("util.dbg.bp(expr=pc, windbgcmd='bl')");
|
||||||
conn.execute("dbg.stepi()");
|
|
||||||
assertEquals("bl", tb.objValue(brk, lastSnap(conn), "Command"));
|
assertEquals("bl", tb.objValue(brk, lastSnap(conn), "Command"));
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
@ -332,22 +365,23 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||||
txPut(conn, "breakpoints");
|
txPut(conn, "breakpoints");
|
||||||
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
assertEquals(0, tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size());
|
||||||
|
|
||||||
conn.execute("dbg = util.get_debugger()");
|
conn.execute("pc = util.get_pc()");
|
||||||
conn.execute("pc = dbg.reg.get_pc()");
|
conn.execute("util.dbg.bp(expr=pc)");
|
||||||
conn.execute("dbg.bp(expr=pc)");
|
|
||||||
|
|
||||||
TraceObject brk = waitForPass(() -> {
|
TraceObject brk = waitForPass(() -> {
|
||||||
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
|
List<Object> brks = tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]");
|
||||||
assertEquals(1, brks.size());
|
assertEquals(1, brks.size());
|
||||||
return (TraceObject) brks.get(0);
|
return (TraceObject) brks.get(0);
|
||||||
});
|
});
|
||||||
|
String id = brk.getCanonicalPath().index();
|
||||||
|
assertEquals("0", id);
|
||||||
|
|
||||||
conn.execute("dbg.cmd('bc %s')".formatted(brk.getCanonicalPath().index()));
|
// Causes access violation in pybag/comtypes during tear-down
|
||||||
conn.execute("dbg.stepi()");
|
//conn.execute("util.dbg.bc(%s)".formatted(id));
|
||||||
|
conn.execute("util.dbg.cmd('bc %s')".formatted(id));
|
||||||
|
|
||||||
waitForPass(
|
waitForPass(() -> assertEquals(0,
|
||||||
() -> assertEquals(0,
|
tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()));
|
||||||
tb.objValues(lastSnap(conn), "Processes[].Breakpoints[]").size()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +401,7 @@ public class DbgEngHooksTest extends AbstractDbgEngTraceRmiTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getAddressAtOffset(PythonAndTrace conn, int offset) {
|
private long getAddressAtOffset(PythonAndTrace conn, int offset) {
|
||||||
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+" + offset + ")";
|
String inst = "print(util.get_inst(util.get_pc()+" + offset + "))";
|
||||||
String ret = conn.executeCapture(inst);
|
String ret = conn.executeCapture(inst);
|
||||||
String[] split = ret.split("\\s+"); // get target
|
String[] split = ret.split("\\s+"); // get target
|
||||||
return Long.decode(split[1]);
|
return Long.decode(split[1]);
|
||||||
|
|
|
@ -106,10 +106,9 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
conn.execute("dbg = util.get_debugger()");
|
conn.execute("pc = util.get_pc()");
|
||||||
conn.execute("pc = dbg.reg.get_pc()");
|
conn.execute("util.dbg.bp(expr=pc)");
|
||||||
conn.execute("dbg.bp(expr=pc)");
|
conn.execute("util.dbg.ba(expr=pc+4)");
|
||||||
conn.execute("dbg.ba(expr=pc+4)");
|
|
||||||
txPut(conn, "breakpoints");
|
txPut(conn, "breakpoints");
|
||||||
TraceObject breakpoints =
|
TraceObject breakpoints =
|
||||||
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
|
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
|
||||||
|
@ -119,6 +118,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
.getValuePaths(Lifespan.at(0),
|
.getValuePaths(Lifespan.at(0),
|
||||||
PathPredicates.parse("Processes[].Breakpoints[]"))
|
PathPredicates.parse("Processes[].Breakpoints[]"))
|
||||||
.map(p -> p.getLastEntry())
|
.map(p -> p.getLastEntry())
|
||||||
|
.sorted(Comparator.comparing(TraceObjectValue::getEntryKey))
|
||||||
.toList();
|
.toList();
|
||||||
assertEquals(2, procBreakLocVals.size());
|
assertEquals(2, procBreakLocVals.size());
|
||||||
AddressRange rangeMain =
|
AddressRange rangeMain =
|
||||||
|
@ -145,11 +145,10 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
conn.execute("dbg = util.get_debugger()");
|
conn.execute("pc = util.get_pc()");
|
||||||
conn.execute("pc = dbg.reg.get_pc()");
|
conn.execute("util.dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)");
|
||||||
conn.execute("dbg.ba(expr=pc, access=DbgEng.DEBUG_BREAK_EXECUTE)");
|
conn.execute("util.dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)");
|
||||||
conn.execute("dbg.ba(expr=pc+4, access=DbgEng.DEBUG_BREAK_READ)");
|
conn.execute("util.dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)");
|
||||||
conn.execute("dbg.ba(expr=pc+8, access=DbgEng.DEBUG_BREAK_WRITE)");
|
|
||||||
TraceObject locations =
|
TraceObject locations =
|
||||||
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
|
Objects.requireNonNull(tb.objAny("Processes[].Breakpoints"));
|
||||||
refreshProcWatchpoints.invoke(Map.of("node", locations));
|
refreshProcWatchpoints.invoke(Map.of("node", locations));
|
||||||
|
@ -158,6 +157,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
.getValuePaths(Lifespan.at(0),
|
.getValuePaths(Lifespan.at(0),
|
||||||
PathPredicates.parse("Processes[].Breakpoints[]"))
|
PathPredicates.parse("Processes[].Breakpoints[]"))
|
||||||
.map(p -> p.getLastEntry())
|
.map(p -> p.getLastEntry())
|
||||||
|
.sorted(Comparator.comparing(TraceObjectValue::getEntryKey))
|
||||||
.toList();
|
.toList();
|
||||||
assertEquals(3, procBreakVals.size());
|
assertEquals(3, procBreakVals.size());
|
||||||
AddressRange rangeMain0 =
|
AddressRange rangeMain0 =
|
||||||
|
@ -290,8 +290,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
conn.execute("regs = util.get_debugger().reg");
|
conn.execute("util.dbg.cmd('r rax=0xdeadbeef')");
|
||||||
conn.execute("regs._set_register('rax', int(0xdeadbeef))");
|
|
||||||
|
|
||||||
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
|
TraceObject registers = Objects.requireNonNull(tb.objAny(path, Lifespan.at(0)));
|
||||||
refreshRegisters.invoke(Map.of("node", registers));
|
refreshRegisters.invoke(Map.of("node", registers));
|
||||||
|
@ -373,9 +372,9 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
|
|
||||||
for (TraceObject t : list) {
|
for (TraceObject t : list) {
|
||||||
activateThread.invoke(Map.of("thread", t));
|
activateThread.invoke(Map.of("thread", t));
|
||||||
String out = conn.executeCapture("util.get_debugger().get_thread()");
|
String out = conn.executeCapture("print(util.dbg.get_thread())").strip();
|
||||||
List<String> indices = pattern.matchKeys(t.getCanonicalPath().getKeyList());
|
List<String> indices = pattern.matchKeys(t.getCanonicalPath().getKeyList());
|
||||||
assertEquals(out, "%s".formatted(indices.get(1)));
|
assertEquals("%s".formatted(indices.get(1)), out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -394,7 +393,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc2 = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||||
removeProcess.invoke(Map.of("process", proc2));
|
removeProcess.invoke(Map.of("process", proc2));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.process_list())");
|
String out = conn.executeCapture("print(list(util.process_list()))");
|
||||||
assertThat(out, containsString("[]"));
|
assertThat(out, containsString("[]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -414,7 +413,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
Objects.requireNonNull(tb.obj("Available[%d]".formatted(dproc.pid)));
|
Objects.requireNonNull(tb.obj("Available[%d]".formatted(dproc.pid)));
|
||||||
attachObj.invoke(Map.of("target", target));
|
attachObj.invoke(Map.of("target", target));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.process_list())");
|
String out = conn.executeCapture("print(list(util.process_list()))");
|
||||||
assertThat(out, containsString("%d".formatted(dproc.pid)));
|
assertThat(out, containsString("%d".formatted(dproc.pid)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -435,7 +434,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
tb.objAny("Available[" + dproc.pid + "]", Lifespan.at(0)));
|
tb.objAny("Available[" + dproc.pid + "]", Lifespan.at(0)));
|
||||||
attachPid.invoke(Map.of("pid", dproc.pid));
|
attachPid.invoke(Map.of("pid", dproc.pid));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.process_list())");
|
String out = conn.executeCapture("print(list(util.process_list()))");
|
||||||
assertThat(out, containsString("%d".formatted(dproc.pid)));
|
assertThat(out, containsString("%d".formatted(dproc.pid)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -455,7 +454,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||||
detach.invoke(Map.of("process", proc));
|
detach.invoke(Map.of("process", proc));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.process_list())");
|
String out = conn.executeCapture("print(list(util.process_list()))");
|
||||||
assertThat(out, containsString("[]"));
|
assertThat(out, containsString("[]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -474,13 +473,13 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
launch.invoke(Map.ofEntries(
|
launch.invoke(Map.ofEntries(
|
||||||
Map.entry("file", "notepad.exe")));
|
Map.entry("file", "notepad.exe")));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.process_list())");
|
String out = conn.executeCapture("print(list(util.process_list()))");
|
||||||
assertThat(out, containsString("notepad.exe"));
|
assertThat(out, containsString("notepad.exe"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test //Can't do this test because create(xxx, initial_break=False) doesn't return
|
@Test
|
||||||
public void testLaunch() throws Exception {
|
public void testLaunch() throws Exception {
|
||||||
try (PythonAndConnection conn = startAndConnectPython()) {
|
try (PythonAndConnection conn = startAndConnectPython()) {
|
||||||
start(conn, null);
|
start(conn, null);
|
||||||
|
@ -491,12 +490,12 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
|
||||||
launch.invoke(Map.ofEntries(
|
launch.invoke(Map.ofEntries(
|
||||||
Map.entry("timeout", 1L),
|
Map.entry("initial_break", true),
|
||||||
Map.entry("file", "notepad.exe")));
|
Map.entry("file", "notepad.exe")));
|
||||||
|
|
||||||
txPut(conn, "processes");
|
txPut(conn, "processes");
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.process_list())");
|
String out = conn.executeCapture("print(list(util.process_list()))");
|
||||||
assertThat(out, containsString("notepad.exe"));
|
assertThat(out, containsString("notepad.exe"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -511,33 +510,61 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
RemoteMethod kill = conn.getMethod("kill");
|
RemoteMethod kill = conn.getMethod("kill");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped();
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||||
kill.invoke(Map.of("process", proc));
|
kill.invoke(Map.of("process", proc));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.process_list())");
|
String out = conn.executeCapture("print(list(util.process_list()))");
|
||||||
assertThat(out, containsString("[]"));
|
assertThat(out, containsString("[]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGoInterrupt5() throws Exception {
|
||||||
|
try (PythonAndConnection conn = startAndConnectPython()) {
|
||||||
|
start(conn, "notepad.exe");
|
||||||
|
txPut(conn, "processes");
|
||||||
|
|
||||||
|
conn.execute(INSTRUMENT_STATE);
|
||||||
|
|
||||||
|
RemoteMethod go = conn.getMethod("go");
|
||||||
|
RemoteMethod interrupt = conn.getMethod("interrupt");
|
||||||
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
|
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
go.invoke(Map.of("process", proc));
|
||||||
|
waitRunning("Missed running " + i);
|
||||||
|
|
||||||
|
interrupt.invoke(Map.of("process", proc));
|
||||||
|
waitStopped("Missed stopped " + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The waits are the assertions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStepInto() throws Exception {
|
public void testStepInto() throws Exception {
|
||||||
try (PythonAndConnection conn = startAndConnectPython()) {
|
try (PythonAndConnection conn = startAndConnectPython()) {
|
||||||
start(conn, "notepad.exe");
|
start(conn, "notepad.exe");
|
||||||
txPut(conn, "processes");
|
txPut(conn, "processes");
|
||||||
|
|
||||||
RemoteMethod step_into = conn.getMethod("step_into");
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped();
|
waitStopped("Missed initial stop");
|
||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||||
|
|
||||||
while (!getInst(conn).contains("call")) {
|
while (!getInst(conn).contains("call")) {
|
||||||
step_into.invoke(Map.of("thread", thread));
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
}
|
}
|
||||||
|
|
||||||
String disCall = getInst(conn);
|
String disCall = getInst(conn);
|
||||||
|
@ -549,7 +576,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
String[] split = disCall.split("\\s+"); // get target
|
String[] split = disCall.split("\\s+"); // get target
|
||||||
long pcCallee = Long.decode(split[split.length - 1]);
|
long pcCallee = Long.decode(split[split.length - 1]);
|
||||||
|
|
||||||
step_into.invoke(Map.of("thread", thread));
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
long pc = getAddressAtOffset(conn, 0);
|
long pc = getAddressAtOffset(conn, 0);
|
||||||
assertEquals(pcCallee, pc);
|
assertEquals(pcCallee, pc);
|
||||||
}
|
}
|
||||||
|
@ -562,23 +589,23 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
start(conn, "notepad.exe");
|
start(conn, "notepad.exe");
|
||||||
txPut(conn, "processes");
|
txPut(conn, "processes");
|
||||||
|
|
||||||
RemoteMethod step_over = conn.getMethod("step_over");
|
RemoteMethod stepOver = conn.getMethod("step_over");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped();
|
waitStopped("Missed initial stop");
|
||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||||
|
|
||||||
while (!getInst(conn).contains("call")) {
|
while (!getInst(conn).contains("call")) {
|
||||||
step_over.invoke(Map.of("thread", thread));
|
stepOver.invoke(Map.of("thread", thread));
|
||||||
}
|
}
|
||||||
|
|
||||||
String disCall = getInst(conn);
|
String disCall = getInst(conn);
|
||||||
String[] split = disCall.split("\\s+"); // get target
|
String[] split = disCall.split("\\s+"); // get target
|
||||||
long pcCallee = Long.decode(split[split.length - 1]);
|
long pcCallee = Long.decode(split[split.length - 1]);
|
||||||
|
|
||||||
step_over.invoke(Map.of("thread", thread));
|
stepOver.invoke(Map.of("thread", thread));
|
||||||
long pc = getAddressAtOffset(conn, 0);
|
long pc = getAddressAtOffset(conn, 0);
|
||||||
assertNotEquals(pcCallee, pc);
|
assertNotEquals(pcCallee, pc);
|
||||||
}
|
}
|
||||||
|
@ -590,17 +617,17 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
try (PythonAndConnection conn = startAndConnectPython()) {
|
try (PythonAndConnection conn = startAndConnectPython()) {
|
||||||
start(conn, "notepad.exe");
|
start(conn, "notepad.exe");
|
||||||
|
|
||||||
RemoteMethod step_into = conn.getMethod("step_into");
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
RemoteMethod step_to = conn.getMethod("step_to");
|
RemoteMethod stepTo = conn.getMethod("step_to");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||||
while (!getInst(conn).contains("call")) {
|
while (!getInst(conn).contains("call")) {
|
||||||
step_into.invoke(Map.of("thread", thread));
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
}
|
}
|
||||||
step_into.invoke(Map.of("thread", thread));
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
|
|
||||||
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
|
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
|
@ -608,10 +635,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
long pcNext = getAddressAtOffset(conn, sz);
|
long pcNext = getAddressAtOffset(conn, sz);
|
||||||
|
stepTo.invoke(Map.of("thread", thread, "address", tb.addr(pcNext), "max", 10));
|
||||||
boolean success = (boolean) step_to
|
|
||||||
.invoke(Map.of("thread", thread, "address", tb.addr(pcNext), "max", 10));
|
|
||||||
assertTrue(success);
|
|
||||||
|
|
||||||
long pc = getAddressAtOffset(conn, 0);
|
long pc = getAddressAtOffset(conn, 0);
|
||||||
assertEquals(pcNext, pc);
|
assertEquals(pcNext, pc);
|
||||||
|
@ -625,24 +649,24 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
start(conn, "notepad.exe");
|
start(conn, "notepad.exe");
|
||||||
txPut(conn, "processes");
|
txPut(conn, "processes");
|
||||||
|
|
||||||
RemoteMethod step_into = conn.getMethod("step_into");
|
RemoteMethod stepInto = conn.getMethod("step_into");
|
||||||
RemoteMethod step_out = conn.getMethod("step_out");
|
RemoteMethod stepOut = conn.getMethod("step_out");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped();
|
waitStopped("Missed initial stop");
|
||||||
txPut(conn, "threads");
|
txPut(conn, "threads");
|
||||||
|
|
||||||
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
TraceObject thread = Objects.requireNonNull(tb.objAny("Processes[].Threads[]"));
|
||||||
|
|
||||||
while (!getInst(conn).contains("call")) {
|
while (!getInst(conn).contains("call")) {
|
||||||
step_into.invoke(Map.of("thread", thread));
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
}
|
}
|
||||||
|
|
||||||
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
|
int sz = Integer.parseInt(getInstSizeAtOffset(conn, 0));
|
||||||
long pcNext = getAddressAtOffset(conn, sz);
|
long pcNext = getAddressAtOffset(conn, sz);
|
||||||
|
|
||||||
step_into.invoke(Map.of("thread", thread));
|
stepInto.invoke(Map.of("thread", thread));
|
||||||
step_out.invoke(Map.of("thread", thread));
|
stepOut.invoke(Map.of("thread", thread));
|
||||||
long pc = getAddressAtOffset(conn, 0);
|
long pc = getAddressAtOffset(conn, 0);
|
||||||
assertEquals(pcNext, pc);
|
assertEquals(pcNext, pc);
|
||||||
}
|
}
|
||||||
|
@ -664,7 +688,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
String out = conn.executeCapture("print(list(util.get_breakpoints()))");
|
||||||
assertThat(out, containsString(Long.toHexString(address)));
|
assertThat(out, containsString(Long.toHexString(address)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -679,11 +703,11 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
RemoteMethod breakExpression = conn.getMethod("break_expression");
|
RemoteMethod breakExpression = conn.getMethod("break_expression");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped();
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
breakExpression.invoke(Map.of("expression", "entry"));
|
breakExpression.invoke(Map.of("expression", "entry"));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
String out = conn.executeCapture("print(list(util.get_breakpoints()))");
|
||||||
assertThat(out, containsString("entry"));
|
assertThat(out, containsString("entry"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -704,7 +728,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
breakAddress.invoke(Map.of("process", proc, "address", tb.addr(address)));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
String out = conn.executeCapture("print(list(util.get_breakpoints()))");
|
||||||
assertThat(out, containsString(Long.toHexString(address)));
|
assertThat(out, containsString(Long.toHexString(address)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -719,11 +743,11 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
RemoteMethod breakExpression = conn.getMethod("break_hw_expression");
|
RemoteMethod breakExpression = conn.getMethod("break_hw_expression");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped();
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
breakExpression.invoke(Map.of("expression", "entry"));
|
breakExpression.invoke(Map.of("expression", "entry"));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
String out = conn.executeCapture("print(list(util.get_breakpoints()))");
|
||||||
assertThat(out, containsString("entry"));
|
assertThat(out, containsString("entry"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -738,14 +762,14 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
RemoteMethod breakRange = conn.getMethod("break_read_range");
|
RemoteMethod breakRange = conn.getMethod("break_read_range");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped();
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
AddressRange range = tb.range(address, address + 3); // length 4
|
AddressRange range = tb.range(address, address + 3); // length 4
|
||||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
String out = conn.executeCapture("print(list(util.get_breakpoints()))");
|
||||||
assertThat(out, containsString("%x".formatted(address)));
|
assertThat(out, containsString("%x".formatted(address)));
|
||||||
assertThat(out, containsString("sz=4"));
|
assertThat(out, containsString("sz=4"));
|
||||||
assertThat(out, containsString("type=r"));
|
assertThat(out, containsString("type=r"));
|
||||||
|
@ -766,7 +790,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
|
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
String out = conn.executeCapture("print(list(util.get_breakpoints()))");
|
||||||
assertThat(out, containsString(Long.toHexString(address >> 24)));
|
assertThat(out, containsString(Long.toHexString(address >> 24)));
|
||||||
assertThat(out, containsString("sz=1"));
|
assertThat(out, containsString("sz=1"));
|
||||||
assertThat(out, containsString("type=r"));
|
assertThat(out, containsString("type=r"));
|
||||||
|
@ -783,14 +807,14 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
RemoteMethod breakRange = conn.getMethod("break_write_range");
|
RemoteMethod breakRange = conn.getMethod("break_write_range");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped();
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
AddressRange range = tb.range(address, address + 3); // length 4
|
AddressRange range = tb.range(address, address + 3); // length 4
|
||||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
String out = conn.executeCapture("print(list(util.get_breakpoints()))");
|
||||||
assertThat(out, containsString("%x".formatted(address)));
|
assertThat(out, containsString("%x".formatted(address)));
|
||||||
assertThat(out, containsString("sz=4"));
|
assertThat(out, containsString("sz=4"));
|
||||||
assertThat(out, containsString("type=w"));
|
assertThat(out, containsString("type=w"));
|
||||||
|
@ -811,7 +835,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
|
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
String out = conn.executeCapture("print(list(util.get_breakpoints()))");
|
||||||
assertThat(out, containsString(Long.toHexString(address >> 24)));
|
assertThat(out, containsString(Long.toHexString(address >> 24)));
|
||||||
assertThat(out, containsString("sz=1"));
|
assertThat(out, containsString("sz=1"));
|
||||||
assertThat(out, containsString("type=w"));
|
assertThat(out, containsString("type=w"));
|
||||||
|
@ -828,14 +852,14 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
RemoteMethod breakRange = conn.getMethod("break_access_range");
|
RemoteMethod breakRange = conn.getMethod("break_access_range");
|
||||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
try (ManagedDomainObject mdo = openDomainObject("/New Traces/pydbg/notepad.exe")) {
|
||||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||||
waitStopped();
|
waitStopped("Missed initial stop");
|
||||||
|
|
||||||
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
TraceObject proc = Objects.requireNonNull(tb.objAny("Processes[]"));
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
AddressRange range = tb.range(address, address + 3); // length 4
|
AddressRange range = tb.range(address, address + 3); // length 4
|
||||||
breakRange.invoke(Map.of("process", proc, "range", range));
|
breakRange.invoke(Map.of("process", proc, "range", range));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
String out = conn.executeCapture("print(list(util.get_breakpoints()))");
|
||||||
assertThat(out, containsString("%x".formatted(address)));
|
assertThat(out, containsString("%x".formatted(address)));
|
||||||
assertThat(out, containsString("sz=4"));
|
assertThat(out, containsString("sz=4"));
|
||||||
assertThat(out, containsString("type=rw"));
|
assertThat(out, containsString("type=rw"));
|
||||||
|
@ -856,7 +880,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
|
breakExpression.invoke(Map.of("expression", "ntdll!LdrInitShimEngineDynamic"));
|
||||||
long address = getAddressAtOffset(conn, 0);
|
long address = getAddressAtOffset(conn, 0);
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
String out = conn.executeCapture("print(list(util.get_breakpoints()))");
|
||||||
assertThat(out, containsString(Long.toHexString(address >> 24)));
|
assertThat(out, containsString(Long.toHexString(address >> 24)));
|
||||||
assertThat(out, containsString("sz=1"));
|
assertThat(out, containsString("sz=1"));
|
||||||
assertThat(out, containsString("type=rw"));
|
assertThat(out, containsString("type=rw"));
|
||||||
|
@ -884,7 +908,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
|
|
||||||
toggleBreakpoint.invoke(Map.of("breakpoint", bpt, "enabled", false));
|
toggleBreakpoint.invoke(Map.of("breakpoint", bpt, "enabled", false));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
String out = conn.executeCapture("print(list(util.get_breakpoints()))");
|
||||||
assertThat(out, containsString("disabled"));
|
assertThat(out, containsString("disabled"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -910,7 +934,7 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
|
|
||||||
deleteBreakpoint.invoke(Map.of("breakpoint", bpt));
|
deleteBreakpoint.invoke(Map.of("breakpoint", bpt));
|
||||||
|
|
||||||
String out = conn.executeCapture("list(util.get_breakpoints())");
|
String out = conn.executeCapture("print(list(util.get_breakpoints()))");
|
||||||
assertThat(out, containsString("[]"));
|
assertThat(out, containsString("[]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -941,19 +965,19 @@ public class DbgEngMethodsTest extends AbstractDbgEngTraceRmiTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getInstAtOffset(PythonAndConnection conn, int offset) {
|
private String getInstAtOffset(PythonAndConnection conn, int offset) {
|
||||||
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+" + offset + ")";
|
String inst = "print(util.get_inst(util.get_pc()+" + offset + "))";
|
||||||
String ret = conn.executeCapture(inst);
|
String ret = conn.executeCapture(inst).strip();
|
||||||
return ret.substring(1, ret.length() - 1); // remove <>
|
return ret.substring(1, ret.length() - 1); // remove <>
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getInstSizeAtOffset(PythonAndConnection conn, int offset) {
|
private String getInstSizeAtOffset(PythonAndConnection conn, int offset) {
|
||||||
String instSize = "util.get_inst_sz(util.get_debugger().reg.get_pc()+" + offset + ")";
|
String instSize = "print(util.get_inst_sz(util.get_pc()+" + offset + "))";
|
||||||
return conn.executeCapture(instSize);
|
return conn.executeCapture(instSize).strip();
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getAddressAtOffset(PythonAndConnection conn, int offset) {
|
private long getAddressAtOffset(PythonAndConnection conn, int offset) {
|
||||||
String inst = "util.get_inst(util.get_debugger().reg.get_pc()+" + offset + ")";
|
String inst = "print(util.get_inst(util.get_pc()+" + offset + "))";
|
||||||
String ret = conn.executeCapture(inst);
|
String ret = conn.executeCapture(inst).strip();
|
||||||
String[] split = ret.split("\\s+"); // get target
|
String[] split = ret.split("\\s+"); // get target
|
||||||
return Long.decode(split[1]);
|
return Long.decode(split[1]);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue