GP-4209: GhidraTime-MSTTD integration. Type hints for (most) Python agents.

This commit is contained in:
Dan 2025-03-24 18:28:07 +00:00
parent deb49d5322
commit 21a1602579
93 changed files with 6453 additions and 4118 deletions

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "ghidradbg"
version = "11.3"
version = "11.4"
authors = [
{ name="Ghidra Development Team" },
]
@ -17,7 +17,7 @@ classifiers = [
"Operating System :: OS Independent",
]
dependencies = [
"ghidratrace==11.3",
"ghidratrace==11.4",
"pybag>=2.2.12"
]
@ -26,7 +26,7 @@ dependencies = [
"Bug Tracker" = "https://github.com/NationalSecurityAgency/ghidra/issues"
[tool.setuptools.package-data]
ghidradbg = ["*.tlb"]
ghidradbg = ["*.tlb", "py.typed"]
[tool.setuptools]
include-package-data = true

View file

@ -1,17 +1,17 @@
## ###
# 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.
# 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.
##
# NOTE: libraries must precede EVERYTHING, esp pybag and DbgMod

View file

@ -13,13 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from typing import Dict, List, Optional, Tuple
from ghidratrace.client import Address, RegVal
from pybag import pydbg
from . import util
language_map = {
language_map: Dict[str, List[str]] = {
'AARCH64': ['AARCH64:LE:64:AppleSilicon'],
'ARM': ['ARM:LE:32:v8'],
'Itanium': [],
@ -31,25 +33,25 @@ language_map = {
'SH4': ['SuperH4:LE:32:default'],
}
data64_compiler_map = {
data64_compiler_map: Dict[Optional[str], str] = {
None: 'pointer64',
}
x86_compiler_map = {
x86_compiler_map: Dict[Optional[str], str] = {
'windows': 'windows',
'Cygwin': 'windows',
'default': 'windows',
}
default_compiler_map = {
default_compiler_map: Dict[Optional[str], str] = {
'windows': 'default',
}
windows_compiler_map = {
windows_compiler_map: Dict[Optional[str], str] = {
'windows': 'windows',
}
compiler_map = {
compiler_map : Dict[str, Dict[Optional[str], str]]= {
'DATA:BE:64:default': data64_compiler_map,
'DATA:LE:64:default': data64_compiler_map,
'x86:LE:32:default': x86_compiler_map,
@ -62,11 +64,11 @@ compiler_map = {
}
def get_arch():
def get_arch() -> str:
try:
type = util.dbg.get_actual_processor_type()
except Exception:
print("Error getting actual processor type.")
except Exception as e:
print(f"Error getting actual processor type: {e}")
return "Unknown"
if type is None:
return "x86_64"
@ -76,25 +78,25 @@ def get_arch():
return "AARCH64"
if type == 0x014c:
return "x86"
if type == 0x0160: # R3000 BE
if type == 0x0160: # R3000 BE
return "MIPS-BE"
if type == 0x0162: # R3000 LE
if type == 0x0162: # R3000 LE
return "MIPS"
if type == 0x0166: # R4000 LE
if type == 0x0166: # R4000 LE
return "MIPS"
if type == 0x0168: # R10000 LE
if type == 0x0168: # R10000 LE
return "MIPS"
if type == 0x0169: # WCE v2 LE
if type == 0x0169: # WCE v2 LE
return "MIPS"
if type == 0x0266: # MIPS 16
if type == 0x0266: # MIPS 16
return "MIPS"
if type == 0x0366: # MIPS FPU
if type == 0x0366: # MIPS FPU
return "MIPS"
if type == 0x0466: # MIPS FPU16
if type == 0x0466: # MIPS FPU16
return "MIPS"
if type == 0x0184: # Alpha AXP
if type == 0x0184: # Alpha AXP
return "Alpha"
if type == 0x0284: # Aplha 64
if type == 0x0284: # Aplha 64
return "Alpha"
if type >= 0x01a2 and type < 0x01a6:
return "SH"
@ -102,17 +104,17 @@ def get_arch():
return "SH4"
if type == 0x01a6:
return "SH5"
if type == 0x01c0: # ARM LE
if type == 0x01c0: # ARM LE
return "ARM"
if type == 0x01c2: # ARM Thumb/Thumb-2 LE
if type == 0x01c2: # ARM Thumb/Thumb-2 LE
return "ARM"
if type == 0x01c4: # ARM Thumb-2 LE
if type == 0x01c4: # ARM Thumb-2 LE
return "ARM"
if type == 0x01d3: # AM33
if type == 0x01d3: # AM33
return "ARM"
if type == 0x01f0 or type == 0x1f1: # PPC
if type == 0x01f0 or type == 0x1f1: # PPC
return "PPC"
if type == 0x0200:
if type == 0x0200:
return "Itanium"
if type == 0x0520:
return "Infineon"
@ -120,23 +122,23 @@ def get_arch():
return "CEF"
if type == 0x0EBC:
return "EFI"
if type == 0x8664: # AMD64 (K8)
if type == 0x8664: # AMD64 (K8)
return "x86_64"
if type == 0x9041: # M32R
if type == 0x9041: # M32R
return "M32R"
if type == 0xC0EE:
return "CEE"
return "Unknown"
def get_endian():
def get_endian() -> str:
parm = util.get_convenience_variable('endian')
if parm != 'auto':
return parm
return 'little'
def get_osabi():
def get_osabi() -> str:
parm = util.get_convenience_variable('osabi')
if not parm in ['auto', 'default']:
return parm
@ -150,7 +152,7 @@ def get_osabi():
return "windows"
def compute_ghidra_language():
def compute_ghidra_language() -> str:
# First, check if the parameter is set
lang = util.get_convenience_variable('ghidra-language')
if lang != 'auto':
@ -175,7 +177,7 @@ def compute_ghidra_language():
return 'DATA' + lebe + '64:default'
def compute_ghidra_compiler(lang):
def compute_ghidra_compiler(lang: str) -> str:
# First, check if the parameter is set
comp = util.get_convenience_variable('ghidra-compiler')
if comp != 'auto':
@ -197,7 +199,7 @@ def compute_ghidra_compiler(lang):
return 'default'
def compute_ghidra_lcsp():
def compute_ghidra_lcsp() -> Tuple[str, str]:
lang = compute_ghidra_language()
comp = compute_ghidra_compiler(lang)
return lang, comp
@ -205,10 +207,10 @@ def compute_ghidra_lcsp():
class DefaultMemoryMapper(object):
def __init__(self, defaultSpace):
def __init__(self, defaultSpace: str) -> None:
self.defaultSpace = defaultSpace
def map(self, proc: int, offset: int):
def map(self, proc: int, offset: int) -> Tuple[str, Address]:
space = self.defaultSpace
return self.defaultSpace, Address(space, offset)
@ -220,10 +222,10 @@ class DefaultMemoryMapper(object):
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
memory_mappers = {}
memory_mappers: Dict[str, DefaultMemoryMapper] = {}
def compute_memory_mapper(lang):
def compute_memory_mapper(lang: str) -> DefaultMemoryMapper:
if not lang in memory_mappers:
return DEFAULT_MEMORY_MAPPER
return memory_mappers[lang]
@ -231,16 +233,15 @@ def compute_memory_mapper(lang):
class DefaultRegisterMapper(object):
def __init__(self, byte_order):
def __init__(self, byte_order: str) -> None:
if not byte_order in ['big', 'little']:
raise ValueError("Invalid byte_order: {}".format(byte_order))
self.byte_order = byte_order
self.union_winners = {}
def map_name(self, proc, name):
def map_name(self, proc: int, name: str):
return name
def map_value(self, proc, name, value):
def map_value(self, proc: int, name: str, value: int):
try:
# TODO: this seems half-baked
av = value.to_bytes(8, "big")
@ -249,10 +250,10 @@ class DefaultRegisterMapper(object):
.format(name, value, type(value)))
return RegVal(self.map_name(proc, name), av)
def map_name_back(self, proc, name):
def map_name_back(self, proc: int, name: str) -> str:
return name
def map_value_back(self, proc, name, value):
def map_value_back(self, proc: int, name: str, value: bytes):
return RegVal(self.map_name_back(proc, name), value)

View file

@ -1,17 +1,17 @@
## ###
# 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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
import os

View file

@ -1,17 +1,17 @@
## ###
# 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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *

View file

@ -1,17 +1,17 @@
## ###
# 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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *

View file

@ -1,17 +1,17 @@
## ###
# 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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *

View file

@ -1,17 +1,17 @@
## ###
# 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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *

View file

@ -44,7 +44,7 @@ class ModelIterator(object):
next = (self._index, mo.ModelObject(object))
self._index += 1
return next
index = mo.ModelObject(indexer)
ival = index.GetIntrinsicValue()
if ival is None:

View file

@ -37,9 +37,8 @@ class ModelMethod(object):
metadata = POINTER(DbgMod.IKeyStore)()
try:
self._method.Call(byref(object), argcount, byref(arguments),
byref(result), byref(metadata))
byref(result), byref(metadata))
except COMError as ce:
return None
return mo.ModelObject(result)

View file

@ -1,17 +1,17 @@
## ###
# 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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *
from enum import Enum
@ -221,10 +221,10 @@ class ModelObject(object):
return None
self.dconcept = StringDisplayableConcept(dconcept)
return self.dconcept.ToDisplayString(self)
# This does NOT work - returns a null pointer for value. Why?
# One possibility: casting is not a valid way to obtain an IModelMethod
#
#
# def ToDisplayString0(self):
# map = self.GetAttributes()
# method = map["ToDisplayString"]
@ -338,11 +338,10 @@ class ModelObject(object):
next = map[element]
else:
next = next.GetKeyValue(element)
#if next is None:
# if next is None:
# print(f"{element} not found")
return next
def GetValue(self):
value = self.GetIntrinsicValue()
if value is None:
@ -350,4 +349,3 @@ class ModelObject(object):
if value.vt == 0xd:
return None
return value.value

View file

@ -1,17 +1,17 @@
## ###
# 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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *

View file

@ -13,12 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ghidradbg import arch, commands, util
from ghidratrace import sch
from ghidratrace.client import Client, Address, AddressRange, TraceObject
from ghidratrace.client import Client, Address, AddressRange, Trace, TraceObject
PAGE_SIZE = 4096
from ghidradbg import arch, commands, util
SESSION_PATH = 'Sessions[0]'
PROCESSES_PATH = SESSION_PATH + '.ExdiProcesses'
@ -42,106 +42,97 @@ SECTIONS_ADD_PATTERN = '.Sections'
SECTION_KEY_PATTERN = '[{secname}]'
SECTION_ADD_PATTERN = SECTIONS_ADD_PATTERN + SECTION_KEY_PATTERN
@util.dbg.eng_thread
def ghidra_trace_put_processes_exdi():
"""
Put the list of processes into the trace's processes list.
"""
def ghidra_trace_put_processes_exdi() -> None:
"""Put the list of processes into the trace's processes list."""
radix = util.get_convenience_variable('output-radix')
commands.STATE.require_tx()
with commands.STATE.client.batch() as b:
put_processes_exdi(commands.STATE, radix)
trace, tx = commands.STATE.require_tx()
with trace.client.batch() as b:
put_processes_exdi(trace, radix)
@util.dbg.eng_thread
def ghidra_trace_put_regions_exdi():
"""
Read the memory map, if applicable, and write to the trace's Regions
"""
def ghidra_trace_put_regions_exdi() -> None:
"""Read the memory map, if applicable, and write to the trace's Regions."""
commands.STATE.require_tx()
with commands.STATE.client.batch() as b:
put_regions_exdi(commands.STATE)
trace, tx = commands.STATE.require_tx()
with trace.client.batch() as b:
put_regions_exdi(trace)
@util.dbg.eng_thread
def ghidra_trace_put_kmodules_exdi():
"""
Gather object files, if applicable, and write to the trace's Modules
"""
def ghidra_trace_put_kmodules_exdi() -> None:
"""Gather object files, if applicable, and write to the trace's Modules."""
commands.STATE.require_tx()
with commands.STATE.client.batch() as b:
put_kmodules_exdi(commands.STATE)
trace, tx = commands.STATE.require_tx()
with trace.client.batch() as b:
put_kmodules_exdi(trace)
@util.dbg.eng_thread
def ghidra_trace_put_threads_exdi(pid):
"""
Put the current process's threads into the Ghidra trace
"""
def ghidra_trace_put_threads_exdi(pid: int) -> None:
"""Put the current process's threads into the Ghidra trace."""
radix = util.get_convenience_variable('output-radix')
commands.STATE.require_tx()
with commands.STATE.client.batch() as b:
put_threads_exdi(commands.STATE, pid, radix)
trace, tx = commands.STATE.require_tx()
with trace.client.batch() as b:
put_threads_exdi(trace, pid, radix)
@util.dbg.eng_thread
def ghidra_trace_put_all_exdi():
"""
Put everything currently selected into the Ghidra trace
"""
def ghidra_trace_put_all_exdi() -> None:
"""Put everything currently selected into the Ghidra trace."""
radix = util.get_convenience_variable('output-radix')
commands.STATE.require_tx()
with commands.STATE.client.batch() as b:
trace, tx = commands.STATE.require_tx()
with trace.client.batch() as b:
if util.dbg.use_generics == False:
put_processes_exdi(commands.STATE, radix)
put_regions_exdi(commands.STATE)
put_kmodules_exdi(commands.STATE)
put_processes_exdi(trace, radix)
put_regions_exdi(trace)
put_kmodules_exdi(trace)
@util.dbg.eng_thread
def put_processes_exdi(state, radix):
def put_processes_exdi(trace: Trace, radix: int) -> None:
radix = util.get_convenience_variable('output-radix')
keys = []
result = util.dbg._base.cmd("!process 0 0")
lines = list(x for x in result.splitlines() if "DeepFreeze" not in x)
count = int((len(lines)-2)/5)
for i in range(0,count):
l1 = lines[i*5+1].strip().split() # PROCESS
l2 = lines[i*5+2].strip().split() # SessionId, Cid, Peb: ParentId
l3 = lines[i*5+3].strip().split() # DirBase, ObjectTable, HandleCount
l4 = lines[i*5+4].strip().split() # Image
for i in range(0, count):
l1 = lines[i*5+1].strip().split() # PROCESS
l2 = lines[i*5+2].strip().split() # SessionId, Cid, Peb: ParentId
l3 = lines[i*5+3].strip().split() # DirBase, ObjectTable, HandleCount
l4 = lines[i*5+4].strip().split() # Image
id = int(l2[3], 16)
name = l4[1]
ppath = PROCESS_PATTERN.format(pid=id)
procobj = state.trace.create_object(ppath)
procobj = trace.create_object(ppath)
keys.append(PROCESS_KEY_PATTERN.format(pid=id))
pidstr = ('0x{:x}' if radix ==
pidstr = ('0x{:x}' if radix ==
16 else '0{:o}' if radix == 8 else '{}').format(id)
procobj.set_value('PID', id)
procobj.set_value('Name', name)
procobj.set_value('_display', '[{}] {}'.format(pidstr, name))
(base, addr) = commands.map_address(int(l1[1],16))
procobj.set_value('EPROCESS', addr, schema="ADDRESS")
(base, addr) = commands.map_address(int(l2[5],16))
procobj.set_value('PEB', addr, schema="ADDRESS")
(base, addr) = commands.map_address(int(l3[1],16))
procobj.set_value('DirBase', addr, schema="ADDRESS")
(base, addr) = commands.map_address(int(l3[3],16))
procobj.set_value('ObjectTable', addr, schema="ADDRESS")
#procobj.set_value('ObjectTable', l3[3])
tcobj = state.trace.create_object(ppath+".Threads")
(base, addr) = commands.map_address(int(l1[1], 16))
procobj.set_value('EPROCESS', addr, schema=sch.ADDRESS)
(base, addr) = commands.map_address(int(l2[5], 16))
procobj.set_value('PEB', addr, schema=sch.ADDRESS)
(base, addr) = commands.map_address(int(l3[1], 16))
procobj.set_value('DirBase', addr, schema=sch.ADDRESS)
(base, addr) = commands.map_address(int(l3[3], 16))
procobj.set_value('ObjectTable', addr, schema=sch.ADDRESS)
# procobj.set_value('ObjectTable', l3[3])
tcobj = trace.create_object(ppath+".Threads")
procobj.insert()
tcobj.insert()
state.trace.proxy_object_path(PROCESSES_PATH).retain_values(keys)
trace.proxy_object_path(PROCESSES_PATH).retain_values(keys)
@util.dbg.eng_thread
def put_regions_exdi(state):
def put_regions_exdi(trace: Trace) -> None:
radix = util.get_convenience_variable('output-radix')
keys = []
result = util.dbg._base.cmd("!address")
@ -153,33 +144,33 @@ def put_regions_exdi(state):
continue
if init == False:
continue
fields = l.strip().replace('`','').split() # PROCESS
fields = l.strip().replace('`', '').split() # PROCESS
if len(fields) < 4:
continue
start = fields[0]
#finish = fields[1]
# finish = fields[1]
length = fields[2]
type = fields[3]
(sbase, saddr) = commands.map_address(int(start,16))
#(fbase, faddr) = commands.map_address(int(finish,16))
rng = saddr.extend(int(length,16))
(sbase, saddr) = commands.map_address(int(start, 16))
# (fbase, faddr) = commands.map_address(int(finish,16))
rng = saddr.extend(int(length, 16))
rpath = REGION_PATTERN.format(start=start)
keys.append(REGION_KEY_PATTERN.format(start=start))
regobj = state.trace.create_object(rpath)
regobj.set_value('Range', rng, schema="RANGE")
regobj = trace.create_object(rpath)
regobj.set_value('Range', rng, schema=sch.RANGE)
regobj.set_value('Size', length)
regobj.set_value('Type', type)
regobj.set_value('_readable', True)
regobj.set_value('_writable', True)
regobj.set_value('_executable', True)
regobj.set_value('_display', '[{}] {}'.format(
start, type))
start, type))
regobj.insert()
state.trace.proxy_object_path(MEMORY_PATH).retain_values(keys)
trace.proxy_object_path(MEMORY_PATH).retain_values(keys)
@util.dbg.eng_thread
def put_kmodules_exdi(state):
def put_kmodules_exdi(trace: Trace) -> None:
radix = util.get_convenience_variable('output-radix')
keys = []
result = util.dbg._base.cmd("lm")
@ -190,32 +181,33 @@ def put_kmodules_exdi(state):
continue
if "Unloaded" in l:
continue
fields = l.strip().replace('`','').split()
fields = l.strip().replace('`', '').split()
if len(fields) < 3:
continue
start = fields[0]
finish = fields[1]
name = fields[2]
sname = name.replace('.sys','').replace('.dll','')
(sbase, saddr) = commands.map_address(int(start,16))
(fbase, faddr) = commands.map_address(int(finish,16))
sz = faddr.offset - saddr.offset
sname = name.replace('.sys', '').replace('.dll', '')
(sbase, saddr) = commands.map_address(int(start, 16))
(fbase, faddr) = commands.map_address(int(finish, 16))
sz = faddr.offset - saddr.offset
rng = saddr.extend(sz)
mpath = KMODULE_PATTERN.format(modpath=sname)
keys.append(KMODULE_KEY_PATTERN.format(modpath=sname))
modobj = commands.STATE.trace.create_object(mpath)
modobj = trace.create_object(mpath)
modobj.set_value('Name', name)
modobj.set_value('Base', saddr, schema="ADDRESS")
modobj.set_value('Range', rng, schema="RANGE")
modobj.set_value('Base', saddr, schema=sch.ADDRESS)
modobj.set_value('Range', rng, schema=sch.RANGE)
modobj.set_value('Size', hex(sz))
modobj.insert()
state.trace.proxy_object_path(KMODULES_PATH).retain_values(keys)
trace.proxy_object_path(KMODULES_PATH).retain_values(keys)
@util.dbg.eng_thread
def put_threads_exdi(state, pid, radix):
def put_threads_exdi(trace: Trace, pid: int, radix: int) -> None:
radix = util.get_convenience_variable('output-radix')
pidstr = ('0x{:x}' if radix == 16 else '0{:o}' if radix == 8 else '{}').format(pid)
pidstr = ('0x{:x}' if radix == 16 else '0{:o}' if radix ==
8 else '{}').format(pid)
keys = []
result = util.dbg._base.cmd("!process "+hex(pid)+" 4")
lines = result.split("\n")
@ -223,15 +215,15 @@ def put_threads_exdi(state, pid, radix):
l = l.strip()
if "THREAD" not in l:
continue
fields = l.split()
cid = fields[3] # pid.tid (decimal)
tid = int(cid.split('.')[1],16)
fields = l.split()
cid = fields[3] # pid.tid (decimal)
tid = int(cid.split('.')[1], 16)
tidstr = ('0x{:x}' if radix ==
16 else '0{:o}' if radix == 8 else '{}').format(tid)
tpath = THREAD_PATTERN.format(pid=pid, tnum=tid)
tobj = commands.STATE.trace.create_object(tpath)
tobj = trace.create_object(tpath)
keys.append(THREAD_KEY_PATTERN.format(tnum=tidstr))
tobj = state.trace.create_object(tpath)
tobj = trace.create_object(tpath)
tobj.set_value('PID', pidstr)
tobj.set_value('TID', tidstr)
tobj.set_value('_display', '[{}]'.format(tidstr))
@ -240,5 +232,5 @@ def put_threads_exdi(state, pid, radix):
tobj.set_value('Win32Thread', fields[7])
tobj.set_value('State', fields[8])
tobj.insert()
commands.STATE.trace.proxy_object_path(
THREADS_PATTERN.format(pid=pidstr)).retain_values(keys)
trace.proxy_object_path(THREADS_PATTERN.format(
pid=pidstr)).retain_values(keys)

View file

@ -16,15 +16,17 @@
import re
from ghidratrace import sch
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
from ghidratrace.client import (MethodRegistry, ParamDesc, Address,
AddressRange, TraceObject)
from ghidradbg import util, commands, methods
from ghidradbg.methods import REGISTRY, SESSIONS_PATTERN, SESSION_PATTERN, extre
from . import exdi_commands
XPROCESSES_PATTERN = extre(SESSION_PATTERN, '\.ExdiProcesses')
XPROCESS_PATTERN = extre(XPROCESSES_PATTERN, '\[(?P<procnum>\\d*)\]')
XTHREADS_PATTERN = extre(XPROCESS_PATTERN, '\.Threads')
XPROCESSES_PATTERN = extre(SESSION_PATTERN, '\\.ExdiProcesses')
XPROCESS_PATTERN = extre(XPROCESSES_PATTERN, '\\[(?P<procnum>\\d*)\\]')
XTHREADS_PATTERN = extre(XPROCESS_PATTERN, '\\.Threads')
def find_pid_by_pattern(pattern, object, err_msg):
mat = pattern.fullmatch(object.path)
@ -38,16 +40,23 @@ def find_pid_by_obj(object):
return find_pid_by_pattern(XTHREADS_PATTERN, object, "an ExdiThreadsContainer")
class ExdiProcessContainer(TraceObject):
pass
class ExdiThreadContainer(TraceObject):
pass
@REGISTRY.method(action='refresh', display="Refresh Target Processes")
def refresh_exdi_processes(node: sch.Schema('ExdiProcessContainer')):
def refresh_exdi_processes(node: ExdiProcessContainer) -> None:
"""Refresh the list of processes in the target kernel."""
with commands.open_tracked_tx('Refresh Processes'):
exdi_commands.ghidra_trace_put_processes_exdi()
@REGISTRY.method(action='refresh', display="Refresh Process Threads")
def refresh_exdi_threads(node: sch.Schema('ExdiThreadContainer')):
def refresh_exdi_threads(node: ExdiThreadContainer) -> None:
"""Refresh the list of threads in the process."""
pid = find_pid_by_obj(node)
with commands.open_tracked_tx('Refresh Threads'):

View file

@ -13,11 +13,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from bisect import bisect_left, bisect_right
from dataclasses import dataclass, field
import functools
import sys
import threading
import time
import traceback
from typing import Any, Callable, Collection, Dict, Optional, TypeVar, cast
from comtypes.hresult import S_OK
from pybag import pydbg
@ -26,6 +29,8 @@ from pybag.dbgeng import exception
from pybag.dbgeng.callbacks import EventHandler
from pybag.dbgeng.idebugbreakpoint import DebugBreakpoint
from ghidratrace.client import Schedule
from . import commands, util
from .exdi import exdi_commands
@ -33,36 +38,33 @@ from .exdi import exdi_commands
ALL_EVENTS = 0xFFFF
class HookState(object):
__slots__ = ('installed', 'mem_catchpoint')
def __init__(self):
self.installed = False
self.mem_catchpoint = None
@dataclass(frozen=False)
class HookState:
installed = False
mem_catchpoint = None
class ProcessState(object):
__slots__ = ('first', 'regions', 'modules', 'threads',
'breaks', 'watches', 'visited', 'waiting')
@dataclass(frozen=False)
class ProcessState:
first = True
# For things we can detect changes to between stops
regions = False
modules = False
threads = False
breaks = False
watches = False
# For frames and threads that have already been synced since last stop
visited: set[Any] = field(default_factory=set)
waiting = False
def __init__(self):
self.first = True
# For things we can detect changes to between stops
self.regions = False
self.modules = False
self.threads = False
self.breaks = False
self.watches = False
# For frames and threads that have already been synced since last stop
self.visited = set()
self.waiting = False
def record(self, description=None, snap=None):
def record(self, description: Optional[str] = None,
time: Optional[Schedule] = None) -> None:
# print("RECORDING")
first = self.first
self.first = False
trace = commands.STATE.require_trace()
if description is not None:
commands.STATE.trace.snapshot(description, snap=snap)
trace.snapshot(description, time=time)
if first:
if util.is_kernel():
commands.create_generic("Sessions")
@ -73,7 +75,7 @@ class ProcessState(object):
commands.put_threads()
if util.is_trace():
commands.init_ttd()
#commands.put_events()
# commands.put_events()
if self.threads:
commands.put_threads()
self.threads = False
@ -93,46 +95,46 @@ class ProcessState(object):
self.visited.add(hashable_frame)
if first or self.regions:
if util.is_exdi():
exdi_commands.put_regions_exdi(commands.STATE)
exdi_commands.put_regions_exdi(trace)
commands.put_regions()
self.regions = False
if first or self.modules:
if util.is_exdi():
exdi_commands.put_kmodules_exdi(commands.STATE)
exdi_commands.put_kmodules_exdi(trace)
commands.put_modules()
self.modules = False
if first or self.breaks:
commands.put_breakpoints()
self.breaks = False
def record_continued(self):
def record_continued(self) -> None:
commands.put_processes(running=True)
commands.put_threads(running=True)
def record_exited(self, exit_code, description=None, snap=None):
def record_exited(self, exit_code: int, description: Optional[str] = None,
time: Optional[Schedule] = None) -> None:
# print("RECORD_EXITED")
trace = commands.STATE.require_trace()
if description is not None:
commands.STATE.trace.snapshot(description, snap=snap)
trace.snapshot(description, time=time)
proc = util.selected_process()
ipath = commands.PROCESS_PATTERN.format(procnum=proc)
procobj = commands.STATE.trace.proxy_object_path(ipath)
procobj = trace.proxy_object_path(ipath)
procobj.set_value('Exit Code', exit_code)
procobj.set_value('State', 'TERMINATED')
class BrkState(object):
__slots__ = ('break_loc_counts',)
@dataclass(frozen=False)
class BrkState:
break_loc_counts: Dict[int, int] = field(default_factory=dict)
def __init__(self):
self.break_loc_counts = {}
def update_brkloc_count(self, b, count):
def update_brkloc_count(self, b: DebugBreakpoint, count: int) -> None:
self.break_loc_counts[b.GetID()] = count
def get_brkloc_count(self, b):
def get_brkloc_count(self, b: DebugBreakpoint) -> int:
return self.break_loc_counts.get(b.GetID(), 0)
def del_brkloc_count(self, b):
def del_brkloc_count(self, b: DebugBreakpoint) -> int:
if b not in self.break_loc_counts:
return 0 # TODO: Print a warning?
count = self.break_loc_counts[b.GetID()]
@ -142,35 +144,37 @@ class BrkState(object):
HOOK_STATE = HookState()
BRK_STATE = BrkState()
PROC_STATE = {}
PROC_STATE: Dict[int, ProcessState] = {}
def log_errors(func):
'''
Wrap a function in a try-except that prints and reraises the
exception.
C = TypeVar('C', bound=Callable)
def log_errors(func: C) -> C:
"""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):
def _func(*args, **kwargs) -> Any:
try:
return func(*args, **kwargs)
except:
traceback.print_exc()
raise
return _func
return cast(C, _func)
@log_errors
def on_state_changed(*args):
# print("ON_STATE_CHANGED")
# print(args)
def on_state_changed(*args) -> int:
# print(f"---ON_STATE_CHANGED:{args}---")
if args[0] == DbgEng.DEBUG_CES_CURRENT_THREAD:
return on_thread_selected(args)
on_thread_selected(args)
return S_OK
elif args[0] == DbgEng.DEBUG_CES_BREAKPOINTS:
return on_breakpoint_modified(args)
on_breakpoint_modified(args)
return S_OK
elif args[0] == DbgEng.DEBUG_CES_RADIX:
util.set_convenience_variable('output-radix', args[1])
return S_OK
@ -179,27 +183,30 @@ def on_state_changed(*args):
proc = util.selected_process()
if args[1] & DbgEng.DEBUG_STATUS_INSIDE_WAIT:
if proc in PROC_STATE:
# Process may have exited (so deleted) first
# Process may have exited (so deleted) first
PROC_STATE[proc].waiting = True
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():
trace = commands.STATE.require_trace()
with trace.client.batch():
with trace.open_tx("State changed proc {}".format(proc)):
commands.put_state(proc)
if args[1] == DbgEng.DEBUG_STATUS_BREAK:
return on_stop(args)
on_stop(args)
return S_OK
elif args[1] == DbgEng.DEBUG_STATUS_NO_DEBUGGEE:
return on_exited(proc)
on_exited(proc)
return S_OK
else:
return on_cont(args)
on_cont(args)
return S_OK
return S_OK
@log_errors
def on_debuggee_changed(*args):
def on_debuggee_changed(*args) -> int:
# print("ON_DEBUGGEE_CHANGED: args={}".format(args))
# sys.stdout.flush()
trace = commands.STATE.trace
@ -213,20 +220,20 @@ def on_debuggee_changed(*args):
@log_errors
def on_session_status_changed(*args):
def on_session_status_changed(*args) -> None:
# print("ON_STATUS_CHANGED: args={}".format(args))
trace = commands.STATE.trace
if trace is None:
return
if args[0] == DbgEng.DEBUG_SESSION_ACTIVE or args[0] == DbgEng.DEBUG_SESSION_REBOOT:
with commands.STATE.client.batch():
with trace.client.batch():
with trace.open_tx("New Session {}".format(util.selected_process())):
commands.put_processes()
return DbgEng.DEBUG_STATUS_GO
@log_errors
def on_symbol_state_changed(*args):
def on_symbol_state_changed(*args) -> None:
# print("ON_SYMBOL_STATE_CHANGED")
proc = util.selected_process()
if proc not in PROC_STATE:
@ -240,31 +247,31 @@ def on_symbol_state_changed(*args):
@log_errors
def on_system_error(*args):
def on_system_error(*args) -> None:
print("ON_SYSTEM_ERROR: args={}".format(args))
# print(hex(args[0]))
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.client.batch():
with trace.open_tx("System Error {}".format(util.selected_process())):
commands.put_processes()
return DbgEng.DEBUG_STATUS_BREAK
@log_errors
def on_new_process(*args):
def on_new_process(*args) -> None:
# print("ON_NEW_PROCESS")
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.client.batch():
with trace.open_tx("New Process {}".format(util.selected_process())):
commands.put_processes()
return DbgEng.DEBUG_STATUS_BREAK
def on_process_selected():
def on_process_selected() -> None:
# print("PROCESS_SELECTED")
proc = util.selected_process()
if proc not in PROC_STATE:
@ -272,14 +279,14 @@ def on_process_selected():
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.client.batch():
with trace.open_tx("Process {} selected".format(proc)):
PROC_STATE[proc].record()
commands.activate()
@log_errors
def on_process_deleted(*args):
def on_process_deleted(*args) -> None:
# print("ON_PROCESS_DELETED")
exit_code = args[0]
proc = util.selected_process()
@ -289,14 +296,14 @@ def on_process_deleted(*args):
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.client.batch():
with trace.open_tx("Process {} deleted".format(proc)):
commands.put_processes() # TODO: Could just delete the one....
return DbgEng.DEBUG_STATUS_BREAK
@log_errors
def on_threads_changed(*args):
def on_threads_changed(*args) -> None:
# print("ON_THREADS_CHANGED")
proc = util.selected_process()
if proc not in PROC_STATE:
@ -305,7 +312,7 @@ def on_threads_changed(*args):
return DbgEng.DEBUG_STATUS_GO
def on_thread_selected(*args):
def on_thread_selected(*args) -> None:
# print("THREAD_SELECTED: args={}".format(args))
# sys.stdout.flush()
nthrd = args[0][1]
@ -315,7 +322,7 @@ def on_thread_selected(*args):
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.client.batch():
with trace.open_tx("Thread {}.{} selected".format(nproc, nthrd)):
commands.put_state(nproc)
state = PROC_STATE[nproc]
@ -326,7 +333,7 @@ def on_thread_selected(*args):
commands.activate()
def on_register_changed(regnum):
def on_register_changed(regnum) -> None:
# print("REGISTER_CHANGED")
proc = util.selected_process()
if proc not in PROC_STATE:
@ -334,13 +341,13 @@ def on_register_changed(regnum):
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.client.batch():
with trace.open_tx("Register {} changed".format(regnum)):
commands.putreg()
commands.activate()
def on_memory_changed(space):
def on_memory_changed(space) -> None:
if space != DbgEng.DEBUG_DATA_SPACE_VIRTUAL:
return
proc = util.selected_process()
@ -352,12 +359,12 @@ def on_memory_changed(space):
# 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.client.batch():
with trace.open_tx("Memory changed"):
commands.putmem_state(0, 2**64, 'unknown')
def on_cont(*args):
def on_cont(*args) -> None:
# print("ON CONT")
proc = util.selected_process()
if proc not in PROC_STATE:
@ -366,56 +373,55 @@ def on_cont(*args):
if trace is None:
return
state = PROC_STATE[proc]
with commands.STATE.client.batch():
with trace.client.batch():
with trace.open_tx("Continued"):
state.record_continued()
return DbgEng.DEBUG_STATUS_GO
def on_stop(*args):
# print("ON STOP")
def on_stop(*args) -> None:
proc = util.selected_process()
if proc not in PROC_STATE:
# print("not in state")
return
trace = commands.STATE.trace
if trace is None:
# print("no trace")
return
state = PROC_STATE[proc]
state.visited.clear()
snap = update_position()
with commands.STATE.client.batch():
time = update_position()
with trace.client.batch():
with trace.open_tx("Stopped"):
state.record("Stopped", snap)
description = util.compute_description(time, "Stopped")
state.record(description, time)
commands.put_event_thread()
commands.activate()
def update_position():
"""Update the position"""
cursor = util.get_cursor()
if cursor is None:
def update_position() -> Optional[Schedule]:
"""Update the position."""
posobj = util.get_object("State.DebuggerVariables.curthread.TTD.Position")
if posobj is None:
return None
pos = cursor.get_position()
pos = util.pos2split(posobj)
lpos = util.get_last_position()
rng = range(pos.major, lpos.major)
if pos.major > lpos.major:
rng = range(lpos.major, pos.major)
for i in rng:
type = util.get_event_type(i)
if type == "modload" or type == "modunload":
on_modules_changed()
break
for i in rng:
type = util.get_event_type(i)
if type == "threadcreated" or type == "threadterm":
on_threads_changed()
util.set_last_position(pos)
return util.pos2snap(pos)
if lpos is None:
return util.split2schedule(pos)
def on_exited(proc):
minpos, maxpos = (lpos, pos) if lpos < pos else (pos, lpos)
evts = list(util.ttd.evttypes.keys())
minidx = bisect_left(evts, minpos)
maxidx = bisect_right(evts, maxpos)
types = set(util.ttd.evttypes[p] for p in evts[minidx:maxidx])
if "modload" in types or "modunload" in types:
on_modules_changed()
if "threadcreated" in types or "threadterm" in types:
on_threads_changed()
util.set_last_position(pos)
return util.split2schedule(pos)
def on_exited(proc) -> None:
# print("ON EXITED")
if proc not in PROC_STATE:
# print("not in state")
@ -427,14 +433,14 @@ def on_exited(proc):
state.visited.clear()
exit_code = util.GetExitCode()
description = "Exited with code {}".format(exit_code)
with commands.STATE.client.batch():
with trace.client.batch():
with trace.open_tx(description):
state.record_exited(exit_code, description)
commands.activate()
@log_errors
def on_modules_changed(*args):
def on_modules_changed(*args) -> None:
# print("ON_MODULES_CHANGED")
proc = util.selected_process()
if proc not in PROC_STATE:
@ -443,7 +449,7 @@ def on_modules_changed(*args):
return DbgEng.DEBUG_STATUS_GO
def on_breakpoint_created(bp):
def on_breakpoint_created(bp) -> None:
# print("ON_BREAKPOINT_CREATED")
proc = util.selected_process()
if proc not in PROC_STATE:
@ -453,15 +459,14 @@ def on_breakpoint_created(bp):
if trace is None:
return
ibpath = commands.PROC_BREAKS_PATTERN.format(procnum=proc)
with commands.STATE.client.batch():
with trace.client.batch():
with trace.open_tx("Breakpoint {} created".format(bp.GetId())):
ibobj = trace.create_object(ibpath)
# Do not use retain_values or it'll remove other locs
commands.put_single_breakpoint(bp, ibobj, proc, [])
ibobj.insert()
def on_breakpoint_modified(*args):
def on_breakpoint_modified(*args) -> None:
# print("BREAKPOINT_MODIFIED")
proc = util.selected_process()
if proc not in PROC_STATE:
@ -481,7 +486,7 @@ def on_breakpoint_modified(*args):
return on_breakpoint_created(bp)
def on_breakpoint_deleted(bpid):
def on_breakpoint_deleted(bpid) -> None:
proc = util.selected_process()
if proc not in PROC_STATE:
return
@ -490,68 +495,68 @@ def on_breakpoint_deleted(bpid):
if trace is None:
return
bpath = commands.PROC_BREAK_PATTERN.format(procnum=proc, breaknum=bpid)
with commands.STATE.client.batch():
with trace.client.batch():
with trace.open_tx("Breakpoint {} deleted".format(bpid)):
trace.proxy_object_path(bpath).remove(tree=True)
@log_errors
def on_breakpoint_hit(*args):
def on_breakpoint_hit(*args) -> None:
# print("ON_BREAKPOINT_HIT: args={}".format(args))
return DbgEng.DEBUG_STATUS_BREAK
@log_errors
def on_exception(*args):
def on_exception(*args) -> None:
# print("ON_EXCEPTION: args={}".format(args))
return DbgEng.DEBUG_STATUS_BREAK
@util.dbg.eng_thread
def install_hooks():
# print("Installing hooks")
if HOOK_STATE.installed:
return
HOOK_STATE.installed = True
def install_hooks() -> None:
# print("Installing hooks")
if HOOK_STATE.installed:
return
HOOK_STATE.installed = True
events = util.dbg._base.events
if util.is_remote():
events.engine_state(handler=on_state_changed_async)
events.debuggee_state(handler=on_debuggee_changed_async)
events.session_status(handler=on_session_status_changed_async)
events.symbol_state(handler=on_symbol_state_changed_async)
events.system_error(handler=on_system_error_async)
events = util.dbg._base.events
events.create_process(handler=on_new_process_async)
events.exit_process(handler=on_process_deleted_async)
events.create_thread(handler=on_threads_changed_async)
events.exit_thread(handler=on_threads_changed_async)
events.module_load(handler=on_modules_changed_async)
events.unload_module(handler=on_modules_changed_async)
if util.is_remote():
events.engine_state(handler=on_state_changed_async)
events.debuggee_state(handler=on_debuggee_changed_async)
events.session_status(handler=on_session_status_changed_async)
events.symbol_state(handler=on_symbol_state_changed_async)
events.system_error(handler=on_system_error_async)
events.breakpoint(handler=on_breakpoint_hit_async)
events.exception(handler=on_exception_async)
else:
events.engine_state(handler=on_state_changed)
events.debuggee_state(handler=on_debuggee_changed)
events.session_status(handler=on_session_status_changed)
events.symbol_state(handler=on_symbol_state_changed)
events.system_error(handler=on_system_error)
events.create_process(handler=on_new_process_async)
events.exit_process(handler=on_process_deleted_async)
events.create_thread(handler=on_threads_changed_async)
events.exit_thread(handler=on_threads_changed_async)
events.module_load(handler=on_modules_changed_async)
events.unload_module(handler=on_modules_changed_async)
events.create_process(handler=on_new_process)
events.exit_process(handler=on_process_deleted)
events.create_thread(handler=on_threads_changed)
events.exit_thread(handler=on_threads_changed)
events.module_load(handler=on_modules_changed)
events.unload_module(handler=on_modules_changed)
events.breakpoint(handler=on_breakpoint_hit_async)
events.exception(handler=on_exception_async)
else:
events.engine_state(handler=on_state_changed)
events.debuggee_state(handler=on_debuggee_changed)
events.session_status(handler=on_session_status_changed)
events.symbol_state(handler=on_symbol_state_changed)
events.system_error(handler=on_system_error)
events.breakpoint(handler=on_breakpoint_hit)
events.exception(handler=on_exception)
events.create_process(handler=on_new_process)
events.exit_process(handler=on_process_deleted)
events.create_thread(handler=on_threads_changed)
events.exit_thread(handler=on_threads_changed)
events.module_load(handler=on_modules_changed)
events.unload_module(handler=on_modules_changed)
events.breakpoint(handler=on_breakpoint_hit)
events.exception(handler=on_exception)
@util.dbg.eng_thread
def remove_hooks():
def remove_hooks() -> None:
# print("Removing hooks")
if not HOOK_STATE.installed:
return
@ -559,14 +564,14 @@ def remove_hooks():
util.dbg._base._reset_callbacks()
def enable_current_process():
def enable_current_process() -> None:
# print("Enable current process")
proc = util.selected_process()
# print("proc: {}".format(proc))
PROC_STATE[proc] = ProcessState()
def disable_current_process():
def disable_current_process() -> None:
proc = util.selected_process()
if proc in PROC_STATE:
# Silently ignore already disabled
@ -574,56 +579,55 @@ def disable_current_process():
@log_errors
def on_state_changed_async(*args):
util.dbg.run_async(on_state_changed, *args)
def on_state_changed_async(*args) -> None:
util.dbg.run_async(on_state_changed, *args)
@log_errors
def on_debuggee_changed_async(*args):
util.dbg.run_async(on_debuggee_changed, *args)
def on_debuggee_changed_async(*args) -> None:
util.dbg.run_async(on_debuggee_changed, *args)
@log_errors
def on_session_status_changed_async(*args):
util.dbg.run_async(on_session_status_changed, *args)
def on_session_status_changed_async(*args) -> None:
util.dbg.run_async(on_session_status_changed, *args)
@log_errors
def on_symbol_state_changed_async(*args):
util.dbg.run_async(on_symbol_state_changed, *args)
def on_symbol_state_changed_async(*args) -> None:
util.dbg.run_async(on_symbol_state_changed, *args)
@log_errors
def on_system_error_async(*args):
util.dbg.run_async(on_system_error, *args)
def on_system_error_async(*args) -> None:
util.dbg.run_async(on_system_error, *args)
@log_errors
def on_new_process_async(*args):
util.dbg.run_async(on_new_process, *args)
def on_new_process_async(*args) -> None:
util.dbg.run_async(on_new_process, *args)
@log_errors
def on_process_deleted_async(*args):
util.dbg.run_async(on_process_deleted, *args)
def on_process_deleted_async(*args) -> None:
util.dbg.run_async(on_process_deleted, *args)
@log_errors
def on_threads_changed_async(*args):
util.dbg.run_async(on_threads_changed, *args)
def on_threads_changed_async(*args) -> None:
util.dbg.run_async(on_threads_changed, *args)
@log_errors
def on_modules_changed_async(*args):
util.dbg.run_async(on_modules_changed, *args)
def on_modules_changed_async(*args) -> None:
util.dbg.run_async(on_modules_changed, *args)
@log_errors
def on_breakpoint_hit_async(*args):
util.dbg.run_async(on_breakpoint_hit, *args)
def on_breakpoint_hit_async(*args) -> None:
util.dbg.run_async(on_breakpoint_hit, *args)
@log_errors
def on_exception_async(*args):
util.dbg.run_async(on_exception, *args)
def on_exception_async(*args) -> None:
util.dbg.run_async(on_exception, *args)

View file

@ -1,17 +1,17 @@
## ###
# 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.
# IP: GHIDRA
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
import ctypes
import os

View file

@ -18,44 +18,48 @@ from contextlib import redirect_stdout
from io import StringIO
import re
import sys
from typing import Annotated, Any, Dict, Optional
from ghidratrace import sch
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
from ghidratrace.client import (MethodRegistry, ParamDesc, Address,
AddressRange, Schedule, TraceObject)
from pybag import pydbg
from pybag.dbgeng import core as DbgEng, exception
from . import util, commands
REGISTRY = MethodRegistry(ThreadPoolExecutor(
max_workers=1, thread_name_prefix='MethodRegistry'))
def extre(base, ext):
def extre(base: re.Pattern, ext: str) -> re.Pattern:
return re.compile(base.pattern + ext)
AVAILABLE_PATTERN = re.compile('Available\[(?P<pid>\\d*)\]')
WATCHPOINT_PATTERN = re.compile('Watchpoints\[(?P<watchnum>\\d*)\]')
BREAKPOINT_PATTERN = re.compile('Breakpoints\[(?P<breaknum>\\d*)\]')
BREAK_LOC_PATTERN = extre(BREAKPOINT_PATTERN, '\[(?P<locnum>\\d*)\]')
WATCHPOINT_PATTERN = re.compile('Watchpoints\\[(?P<watchnum>\\d*)\\]')
BREAKPOINT_PATTERN = re.compile('Breakpoints\\[(?P<breaknum>\\d*)\\]')
BREAK_LOC_PATTERN = extre(BREAKPOINT_PATTERN, '\\[(?P<locnum>\\d*)\\]')
SESSIONS_PATTERN = re.compile('Sessions')
SESSION_PATTERN = extre(SESSIONS_PATTERN, '\[(?P<snum>\\d*)\]')
PROCESSES_PATTERN = extre(SESSION_PATTERN, '\.Processes')
PROCESS_PATTERN = extre(PROCESSES_PATTERN, '\[(?P<procnum>\\d*)\]')
PROC_BREAKS_PATTERN = extre(PROCESS_PATTERN, '\.Debug.Breakpoints')
PROC_BREAKBPT_PATTERN = extre(PROC_BREAKS_PATTERN, '\[(?P<breaknum>\\d*)\]')
ENV_PATTERN = extre(PROCESS_PATTERN, '\.Environment')
THREADS_PATTERN = extre(PROCESS_PATTERN, '\.Threads')
THREAD_PATTERN = extre(THREADS_PATTERN, '\[(?P<tnum>\\d*)\]')
STACK_PATTERN = extre(THREAD_PATTERN, '\.Stack.Frames')
FRAME_PATTERN = extre(STACK_PATTERN, '\[(?P<level>\\d*)\]')
REGS_PATTERN0 = extre(THREAD_PATTERN, '.Registers')
REGS_PATTERN = extre(FRAME_PATTERN, '.Registers')
MEMORY_PATTERN = extre(PROCESS_PATTERN, '\.Memory')
MODULES_PATTERN = extre(PROCESS_PATTERN, '\.Modules')
SESSION_PATTERN = extre(SESSIONS_PATTERN, '\\[(?P<snum>\\d*)\\]')
AVAILABLE_PATTERN = extre(SESSION_PATTERN, '\\.Available\\[(?P<pid>\\d*)\\]')
PROCESSES_PATTERN = extre(SESSION_PATTERN, '\\.Processes')
PROCESS_PATTERN = extre(PROCESSES_PATTERN, '\\[(?P<procnum>\\d*)\\]')
PROC_BREAKS_PATTERN = extre(PROCESS_PATTERN, '\\.Debug.Breakpoints')
PROC_BREAKBPT_PATTERN = extre(PROC_BREAKS_PATTERN, '\\[(?P<breaknum>\\d*)\\]')
ENV_PATTERN = extre(PROCESS_PATTERN, '\\.Environment')
THREADS_PATTERN = extre(PROCESS_PATTERN, '\\.Threads')
THREAD_PATTERN = extre(THREADS_PATTERN, '\\[(?P<tnum>\\d*)\\]')
STACK_PATTERN = extre(THREAD_PATTERN, '\\.Stack.Frames')
FRAME_PATTERN = extre(STACK_PATTERN, '\\[(?P<level>\\d*)\\]')
REGS_PATTERN0 = extre(THREAD_PATTERN, '\\.Registers')
REGS_PATTERN = extre(FRAME_PATTERN, '\\.Registers')
MEMORY_PATTERN = extre(PROCESS_PATTERN, '\\.Memory')
MODULES_PATTERN = extre(PROCESS_PATTERN, '\\.Modules')
def find_availpid_by_pattern(pattern, object, err_msg):
def find_availpid_by_pattern(pattern: re.Pattern, object: TraceObject,
err_msg: str) -> int:
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
@ -63,17 +67,18 @@ def find_availpid_by_pattern(pattern, object, err_msg):
return pid
def find_availpid_by_obj(object):
return find_availpid_by_pattern(AVAILABLE_PATTERN, object, "an Available")
def find_availpid_by_obj(object: TraceObject) -> int:
return find_availpid_by_pattern(AVAILABLE_PATTERN, object, "an Attachable")
def find_proc_by_num(id):
def find_proc_by_num(id: int) -> int:
if id != util.selected_process():
util.select_process(id)
return util.selected_process()
def find_proc_by_pattern(object, pattern, err_msg):
def find_proc_by_pattern(object: TraceObject, pattern: re.Pattern,
err_msg: str) -> int:
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
@ -81,43 +86,39 @@ def find_proc_by_pattern(object, pattern, err_msg):
return find_proc_by_num(procnum)
def find_proc_by_obj(object):
def find_proc_by_obj(object: TraceObject) -> int:
return find_proc_by_pattern(object, PROCESS_PATTERN, "an Process")
def find_proc_by_procbreak_obj(object):
def find_proc_by_procbreak_obj(object: TraceObject) -> int:
return find_proc_by_pattern(object, PROC_BREAKS_PATTERN,
"a BreakpointLocationContainer")
def find_proc_by_procwatch_obj(object):
return find_proc_by_pattern(object, PROC_WATCHES_PATTERN,
"a WatchpointContainer")
def find_proc_by_env_obj(object):
def find_proc_by_env_obj(object: TraceObject) -> int:
return find_proc_by_pattern(object, ENV_PATTERN, "an Environment")
def find_proc_by_threads_obj(object):
def find_proc_by_threads_obj(object: TraceObject) -> int:
return find_proc_by_pattern(object, THREADS_PATTERN, "a ThreadContainer")
def find_proc_by_mem_obj(object):
def find_proc_by_mem_obj(object: TraceObject) -> int:
return find_proc_by_pattern(object, MEMORY_PATTERN, "a Memory")
def find_proc_by_modules_obj(object):
def find_proc_by_modules_obj(object: TraceObject) -> int:
return find_proc_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")
def find_thread_by_num(id):
def find_thread_by_num(id: int) -> Optional[int]:
if id != util.selected_thread():
util.select_thread(id)
return util.selected_thread()
def find_thread_by_pattern(pattern, object, err_msg):
def find_thread_by_pattern(pattern: re.Pattern, object: TraceObject,
err_msg: str) -> Optional[int]:
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
@ -127,27 +128,29 @@ def find_thread_by_pattern(pattern, object, err_msg):
return find_thread_by_num(tnum)
def find_thread_by_obj(object):
def find_thread_by_obj(object: TraceObject) -> Optional[int]:
return find_thread_by_pattern(THREAD_PATTERN, object, "a Thread")
def find_thread_by_stack_obj(object):
def find_thread_by_stack_obj(object: TraceObject) -> Optional[int]:
return find_thread_by_pattern(STACK_PATTERN, object, "a Stack")
def find_thread_by_regs_obj(object):
return find_thread_by_pattern(REGS_PATTERN0, object, "a RegisterValueContainer")
def find_thread_by_regs_obj(object: TraceObject) -> Optional[int]:
return find_thread_by_pattern(REGS_PATTERN0, object,
"a RegisterValueContainer")
@util.dbg.eng_thread
def find_frame_by_level(level):
def find_frame_by_level(level: int) -> DbgEng._DEBUG_STACK_FRAME:
for f in util.dbg._base.backtrace_list():
if f.FrameNumber == level:
return f
# return dbg().backtrace_list()[level]
def find_frame_by_pattern(pattern, object, err_msg):
def find_frame_by_pattern(pattern: re.Pattern, object: TraceObject,
err_msg: str) -> DbgEng._DEBUG_STACK_FRAME:
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
@ -159,11 +162,11 @@ def find_frame_by_pattern(pattern, object, err_msg):
return find_frame_by_level(level)
def find_frame_by_obj(object):
def find_frame_by_obj(object: TraceObject) -> DbgEng._DEBUG_STACK_FRAME:
return find_frame_by_pattern(FRAME_PATTERN, object, "a StackFrame")
def find_bpt_by_number(breaknum):
def find_bpt_by_number(breaknum: int) -> DbgEng.IDebugBreakpoint:
try:
bp = dbg()._control.GetBreakpointById(breaknum)
return bp
@ -171,7 +174,8 @@ def find_bpt_by_number(breaknum):
raise KeyError(f"Breakpoints[{breaknum}] does not exist")
def find_bpt_by_pattern(pattern, object, err_msg):
def find_bpt_by_pattern(pattern: re.Pattern, object: TraceObject,
err_msg: str) -> DbgEng.IDebugBreakpoint:
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
@ -179,16 +183,80 @@ def find_bpt_by_pattern(pattern, object, err_msg):
return find_bpt_by_number(breaknum)
def find_bpt_by_obj(object):
def find_bpt_by_obj(object: TraceObject) -> DbgEng.IDebugBreakpoint:
return find_bpt_by_pattern(PROC_BREAKBPT_PATTERN, object, "a BreakpointSpec")
shared_globals = dict()
shared_globals: Dict[str, Any] = dict()
@REGISTRY.method
class Session(TraceObject):
pass
class AvailableContainer(TraceObject):
pass
class BreakpointContainer(TraceObject):
pass
class ProcessContainer(TraceObject):
pass
class Environment(TraceObject):
pass
class ThreadContainer(TraceObject):
pass
class Stack(TraceObject):
pass
class RegisterValueContainer(TraceObject):
pass
class Memory(TraceObject):
pass
class ModuleContainer(TraceObject):
pass
class State(TraceObject):
pass
class Process(TraceObject):
pass
class Thread(TraceObject):
pass
class StackFrame(TraceObject):
pass
class Attachable(TraceObject):
pass
class BreakpointSpec(TraceObject):
pass
@REGISTRY.method()
# @util.dbg.eng_thread
def execute(cmd: str, to_string: bool=False):
def execute(cmd: str, to_string: bool = False):
"""Execute a Python3 command or script."""
# print("***{}***".format(cmd))
# sys.stderr.flush()
@ -205,59 +273,58 @@ def execute(cmd: str, to_string: bool=False):
@REGISTRY.method(action='evaluate', display='Evaluate')
# @util.dbg.eng_thread
def evaluate(
session: sch.Schema('Session'),
expr: ParamDesc(str, display='Expr')):
session: Session,
expr: Annotated[str, ParamDesc(display='Expr')]) -> str:
"""Evaluate a Python3 expression."""
return str(eval(expr, shared_globals))
@REGISTRY.method(action='refresh', display="Refresh", condition=util.dbg.use_generics)
def refresh_generic(node: sch.OBJECT):
@REGISTRY.method(action='refresh', display="Refresh",
condition=util.dbg.use_generics)
def refresh_generic(node: TraceObject) -> None:
"""List the children for a generic node."""
with commands.open_tracked_tx('Refresh Generic'):
commands.ghidra_trace_put_generic(node)
@REGISTRY.method(action='refresh', display='Refresh Available')
def refresh_available(node: sch.Schema('AvailableContainer')):
def refresh_available(node: AvailableContainer) -> None:
"""List processes on pydbg's host system."""
with commands.open_tracked_tx('Refresh Available'):
commands.ghidra_trace_put_available()
@REGISTRY.method(action='refresh', display='Refresh Breakpoints')
def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
"""
Refresh the list of breakpoints (including locations for the current
process).
"""
def refresh_breakpoints(node: BreakpointContainer) -> None:
"""Refresh the list of breakpoints (including locations for the current
process)."""
with commands.open_tracked_tx('Refresh Breakpoints'):
commands.ghidra_trace_put_breakpoints()
@REGISTRY.method(action='refresh', display='Refresh Processes')
def refresh_processes(node: sch.Schema('ProcessContainer')):
def refresh_processes(node: ProcessContainer) -> None:
"""Refresh the list of processes."""
with commands.open_tracked_tx('Refresh Processes'):
commands.ghidra_trace_put_processes()
@REGISTRY.method(action='refresh', display='Refresh Environment')
def refresh_environment(node: sch.Schema('Environment')):
def refresh_environment(node: Environment) -> None:
"""Refresh the environment descriptors (arch, os, endian)."""
with commands.open_tracked_tx('Refresh Environment'):
commands.ghidra_trace_put_environment()
@REGISTRY.method(action='refresh', display='Refresh Threads')
def refresh_threads(node: sch.Schema('ThreadContainer')):
def refresh_threads(node: ThreadContainer) -> None:
"""Refresh the list of threads in the process."""
with commands.open_tracked_tx('Refresh Threads'):
commands.ghidra_trace_put_threads()
@REGISTRY.method(action='refresh', display='Refresh Stack')
def refresh_stack(node: sch.Schema('Stack')):
def refresh_stack(node: Stack) -> None:
"""Refresh the backtrace for the thread."""
tnum = find_thread_by_stack_obj(node)
util.reset_frames()
@ -268,55 +335,67 @@ def refresh_stack(node: sch.Schema('Stack')):
@REGISTRY.method(action='refresh', display='Refresh Registers')
def refresh_registers(node: sch.Schema('RegisterValueContainer')):
"""Refresh the register values for the selected frame"""
def refresh_registers(node: RegisterValueContainer) -> None:
"""Refresh the register values for the selected frame."""
tnum = find_thread_by_regs_obj(node)
with commands.open_tracked_tx('Refresh Registers'):
commands.ghidra_trace_putreg()
@REGISTRY.method(action='refresh', display='Refresh Memory')
def refresh_mappings(node: sch.Schema('Memory')):
def refresh_mappings(node: Memory) -> None:
"""Refresh the list of memory regions for the process."""
with commands.open_tracked_tx('Refresh Memory Regions'):
commands.ghidra_trace_put_regions()
@REGISTRY.method(action='refresh', display='Refresh Modules')
def refresh_modules(node: sch.Schema('ModuleContainer')):
"""
Refresh the modules and sections list for the process.
def refresh_modules(node: ModuleContainer) -> None:
"""Refresh the modules and sections list for the process.
This will refresh the sections for all modules, not just the selected one.
This will refresh the sections for all modules, not just the
selected one.
"""
with commands.open_tracked_tx('Refresh Modules'):
commands.ghidra_trace_put_modules()
@REGISTRY.method(action='refresh', display='Refresh Events')
def refresh_events(node: sch.Schema('State')):
"""
Refresh the events list for a trace.
"""
def refresh_events(node: State) -> None:
"""Refresh the events list for a trace."""
with commands.open_tracked_tx('Refresh Events'):
commands.ghidra_trace_put_events(node)
commands.ghidra_trace_put_events()
@util.dbg.eng_thread
def do_maybe_activate_time(time: Optional[str]) -> None:
if time is not None:
sch: Schedule = Schedule.parse(time)
dbg().cmd(f"!tt " + util.schedule2ss(sch), quiet=False)
dbg().wait()
@REGISTRY.method(action='activate')
def activate_process(process: sch.Schema('Process')):
def activate_process(process: Process,
time: Optional[str] = None) -> None:
"""Switch to the process."""
do_maybe_activate_time(time)
find_proc_by_obj(process)
@REGISTRY.method(action='activate')
def activate_thread(thread: sch.Schema('Thread')):
def activate_thread(thread: Thread,
time: Optional[str] = None) -> None:
"""Switch to the thread."""
do_maybe_activate_time(time)
find_thread_by_obj(thread)
@REGISTRY.method(action='activate')
def activate_frame(frame: sch.Schema('StackFrame')):
def activate_frame(frame: StackFrame,
time: Optional[str] = None) -> None:
"""Select the frame."""
do_maybe_activate_time(time)
f = find_frame_by_obj(frame)
util.select_frame(f.FrameNumber)
with commands.open_tracked_tx('Refresh Stack'):
@ -327,7 +406,7 @@ def activate_frame(frame: sch.Schema('StackFrame')):
@REGISTRY.method(action='delete')
@util.dbg.eng_thread
def remove_process(process: sch.Schema('Process')):
def remove_process(process: Process) -> None:
"""Remove the process."""
find_proc_by_obj(process)
dbg().detach_proc()
@ -336,15 +415,15 @@ def remove_process(process: sch.Schema('Process')):
@REGISTRY.method(action='connect', display='Connect')
@util.dbg.eng_thread
def target(
session: sch.Schema('Session'),
cmd: ParamDesc(str, display='Command')):
session: Session,
cmd: Annotated[str, ParamDesc(display='Command')]) -> None:
"""Connect to a target machine or process."""
dbg().attach_kernel(cmd)
@REGISTRY.method(action='attach', display='Attach')
@util.dbg.eng_thread
def attach_obj(target: sch.Schema('Attachable')):
def attach_obj(target: Attachable) -> None:
"""Attach the process to the given target."""
pid = find_availpid_by_obj(target)
dbg().attach_proc(pid)
@ -353,82 +432,90 @@ def attach_obj(target: sch.Schema('Attachable')):
@REGISTRY.method(action='attach', display='Attach by pid')
@util.dbg.eng_thread
def attach_pid(
session: sch.Schema('Session'),
pid: ParamDesc(str, display='PID')):
session: Session,
pid: Annotated[int, ParamDesc(display='PID')]) -> None:
"""Attach the process to the given target."""
dbg().attach_proc(int(pid))
dbg().attach_proc(pid)
@REGISTRY.method(action='attach', display='Attach by name')
@util.dbg.eng_thread
def attach_name(
session: sch.Schema('Session'),
name: ParamDesc(str, display='Name')):
session: Session,
name: Annotated[str, ParamDesc(display='Name')]) -> None:
"""Attach the process to the given target."""
dbg().attach_proc(name)
@REGISTRY.method(action='detach', display='Detach')
@util.dbg.eng_thread
def detach(process: sch.Schema('Process')):
def detach(process: Process) -> None:
"""Detach the process's target."""
dbg().detach_proc()
@REGISTRY.method(action='launch', display='Launch')
def launch_loader(
session: sch.Schema('Session'),
file: ParamDesc(str, display='File'),
args: ParamDesc(str, display='Arguments')=''):
"""
Start a native process with the given command line, stopping at the ntdll initial breakpoint.
"""
session: Session,
file: Annotated[str, ParamDesc(display='File')],
args: Annotated[str, ParamDesc(display='Arguments')] = '',
timeout: Annotated[int, ParamDesc(display='Timeout')] = -1,
wait: Annotated[bool, ParamDesc(
display='Wait',
description='Perform the initial WaitForEvents')] = False) -> None:
"""Start a native process with the given command line, stopping at the
ntdll initial breakpoint."""
command = file
if args != None:
command += " " + args
commands.ghidra_trace_create(command=file, start_trace=False)
commands.ghidra_trace_create(command=command, start_trace=False,
timeout=timeout, wait=wait)
@REGISTRY.method(action='launch', display='LaunchEx')
def launch(
session: sch.Schema('Session'),
file: ParamDesc(str, display='File'),
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.
"""
session: Session,
file: Annotated[str, ParamDesc(display='File')],
args: Annotated[str, ParamDesc(display='Arguments')] = '',
initial_break: Annotated[bool, ParamDesc(
display='Initial Break')] = True,
timeout: Annotated[int, ParamDesc(display='Timeout')] = -1,
wait: Annotated[bool, ParamDesc(
display='Wait',
description='Perform the initial WaitForEvents')] = False) -> None:
"""Run a native process with the given command line."""
command = file
if args != None:
command += " " + args
commands.ghidra_trace_create(
command, initial_break=initial_break, timeout=timeout, start_trace=False)
commands.ghidra_trace_create(command=command, start_trace=False,
initial_break=initial_break,
timeout=timeout, wait=wait)
@REGISTRY.method
@REGISTRY.method()
@util.dbg.eng_thread
def kill(process: sch.Schema('Process')):
def kill(process: Process) -> None:
"""Kill execution of the process."""
commands.ghidra_trace_kill()
@REGISTRY.method(action='resume', display="Go")
def go(process: sch.Schema('Process')):
def go(process: Process) -> None:
"""Continue execution of the process."""
util.dbg.run_async(lambda: dbg().go())
@REGISTRY.method(action='step_ext', display='Go (backwards)', icon='icon.debugger.resume.back', condition=util.dbg.IS_TRACE)
@REGISTRY.method(action='step_ext', display='Go (backwards)',
icon='icon.debugger.resume.back', condition=util.dbg.IS_TRACE)
@util.dbg.eng_thread
def go_back(thread: sch.Schema('Process')):
def go_back(process: Process) -> None:
"""Continue execution of the process backwards."""
dbg().cmd("g-")
dbg().wait()
@REGISTRY.method
def interrupt(process: sch.Schema('Process')):
@REGISTRY.method()
def interrupt(process: Process) -> None:
"""Interrupt the execution of the debugged program."""
# SetInterrupt is reentrant, so bypass the thread checks
util.dbg._protected_base._control.SetInterrupt(
@ -436,53 +523,64 @@ def interrupt(process: sch.Schema('Process')):
@REGISTRY.method(action='step_into')
def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
def step_into(thread: Thread,
n: Annotated[int, ParamDesc(display='N')] = 1) -> None:
"""Step one instruction exactly."""
find_thread_by_obj(thread)
util.dbg.run_async(lambda: dbg().stepi(n))
@REGISTRY.method(action='step_over')
def step_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
def step_over(thread: Thread,
n: Annotated[int, ParamDesc(display='N')] = 1) -> None:
"""Step one instruction, but proceed through subroutine calls."""
find_thread_by_obj(thread)
util.dbg.run_async(lambda: dbg().stepo(n))
@REGISTRY.method(action='step_ext', display='Step Into (backwards)', icon='icon.debugger.step.back.into', condition=util.dbg.IS_TRACE)
@REGISTRY.method(action='step_ext', display='Step Into (backwards)',
icon='icon.debugger.step.back.into',
condition=util.dbg.IS_TRACE)
@util.dbg.eng_thread
def step_back_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
def step_back_into(thread: Thread,
n: Annotated[int, ParamDesc(display='N')] = 1) -> None:
"""Step one instruction backward exactly."""
dbg().cmd("t- " + str(n))
dbg().wait()
@REGISTRY.method(action='step_ext', display='Step Over (backwards)', icon='icon.debugger.step.back.over', condition=util.dbg.IS_TRACE)
@REGISTRY.method(action='step_ext', display='Step Over (backwards)',
icon='icon.debugger.step.back.over',
condition=util.dbg.IS_TRACE)
@util.dbg.eng_thread
def step_back_over(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
def step_back_over(thread: Thread,
n: Annotated[int, ParamDesc(display='N')] = 1) -> None:
"""Step one instruction backward, but proceed through subroutine calls."""
dbg().cmd("p- " + str(n))
dbg().wait()
@REGISTRY.method(action='step_out')
def step_out(thread: sch.Schema('Thread')):
def step_out(thread: Thread) -> None:
"""Execute until the current stack frame returns."""
find_thread_by_obj(thread)
util.dbg.run_async(lambda: dbg().stepout())
@REGISTRY.method(action='step_to', display='Step To')
def step_to(thread: sch.Schema('Thread'), address: Address, max=None):
def step_to(thread: Thread, address: Address,
max: Optional[int] = None) -> None:
"""Continue execution up to the given address."""
find_thread_by_obj(thread)
# TODO: The address may need mapping.
util.dbg.run_async(lambda: dbg().stepto(address.offset, max))
@REGISTRY.method(action='go_to_time', display='Go To (event)', condition=util.dbg.IS_TRACE)
@REGISTRY.method(action='go_to_time', display='Go To (event)',
condition=util.dbg.IS_TRACE)
@util.dbg.eng_thread
def go_to_time(node: sch.Schema('State'), evt: ParamDesc(str, display='Event')):
def go_to_time(node: State,
evt: Annotated[str, ParamDesc(display='Event')]) -> None:
"""Reset the trace to a specific time."""
dbg().cmd("!tt " + evt)
dbg().wait()
@ -490,7 +588,7 @@ def go_to_time(node: sch.Schema('State'), evt: ParamDesc(str, display='Event')):
@REGISTRY.method(action='break_sw_execute')
@util.dbg.eng_thread
def break_address(process: sch.Schema('Process'), address: Address):
def break_address(process: Process, address: Address) -> None:
"""Set a breakpoint."""
find_proc_by_obj(process)
dbg().bp(expr=address.offset)
@ -498,7 +596,7 @@ def break_address(process: sch.Schema('Process'), address: Address):
@REGISTRY.method(action='break_ext', display='Set Breakpoint')
@util.dbg.eng_thread
def break_expression(expression: str):
def break_expression(expression: str) -> None:
"""Set a breakpoint."""
# TODO: Escape?
dbg().bp(expr=expression)
@ -506,7 +604,7 @@ def break_expression(expression: str):
@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: Process, address: Address) -> None:
"""Set a hardware-assisted breakpoint."""
find_proc_by_obj(process)
dbg().ba(expr=address.offset)
@ -514,44 +612,46 @@ def break_hw_address(process: sch.Schema('Process'), address: Address):
@REGISTRY.method(action='break_ext', display='Set Hardware Breakpoint')
@util.dbg.eng_thread
def break_hw_expression(expression: str):
def break_hw_expression(expression: str) -> None:
"""Set a hardware-assisted breakpoint."""
dbg().ba(expr=expression)
@REGISTRY.method(action='break_read')
@util.dbg.eng_thread
def break_read_range(process: sch.Schema('Process'), range: AddressRange):
def break_read_range(process: Process, range: AddressRange) -> None:
"""Set a read breakpoint."""
find_proc_by_obj(process)
dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_READ)
dbg().ba(expr=range.min, size=range.length(),
access=DbgEng.DEBUG_BREAK_READ)
@REGISTRY.method(action='break_ext', display='Set Read Breakpoint')
@util.dbg.eng_thread
def break_read_expression(expression: str):
def break_read_expression(expression: str) -> None:
"""Set a read breakpoint."""
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ)
@REGISTRY.method(action='break_write')
@util.dbg.eng_thread
def break_write_range(process: sch.Schema('Process'), range: AddressRange):
def break_write_range(process: Process, range: AddressRange) -> None:
"""Set a write breakpoint."""
find_proc_by_obj(process)
dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_WRITE)
dbg().ba(expr=range.min, size=range.length(),
access=DbgEng.DEBUG_BREAK_WRITE)
@REGISTRY.method(action='break_ext', display='Set Write Breakpoint')
@util.dbg.eng_thread
def break_write_expression(expression: str):
def break_write_expression(expression: str) -> None:
"""Set a write breakpoint."""
dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_WRITE)
@REGISTRY.method(action='break_access')
@util.dbg.eng_thread
def break_access_range(process: sch.Schema('Process'), range: AddressRange):
def break_access_range(process: Process, range: AddressRange) -> None:
"""Set an access breakpoint."""
find_proc_by_obj(process)
dbg().ba(expr=range.min, size=range.length(),
@ -560,14 +660,15 @@ def break_access_range(process: sch.Schema('Process'), range: AddressRange):
@REGISTRY.method(action='break_ext', display='Set Access Breakpoint')
@util.dbg.eng_thread
def break_access_expression(expression: str):
def break_access_expression(expression: str) -> None:
"""Set an access breakpoint."""
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', display='Toggle Breakpoint')
@util.dbg.eng_thread
def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
def toggle_breakpoint(breakpoint: BreakpointSpec, enabled: bool) -> None:
"""Toggle a breakpoint."""
bpt = find_bpt_by_obj(breakpoint)
if enabled:
@ -578,50 +679,53 @@ def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool):
@REGISTRY.method(action='delete', display='Delete Breakpoint')
@util.dbg.eng_thread
def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')):
def delete_breakpoint(breakpoint: BreakpointSpec) -> None:
"""Delete a breakpoint."""
bpt = find_bpt_by_obj(breakpoint)
dbg().cmd("bc {}".format(bpt.GetId()))
@REGISTRY.method
@REGISTRY.method()
@util.dbg.eng_thread
def read_mem(process: sch.Schema('Process'), range: AddressRange):
def read_mem(process: Process, range: AddressRange) -> None:
"""Read memory."""
# print("READ_MEM: process={}, range={}".format(process, range))
nproc = find_proc_by_obj(process)
offset_start = process.trace.memory_mapper.map_back(
offset_start = process.trace.extra.require_mm().map_back(
nproc, Address(range.space, range.min))
with commands.open_tracked_tx('Read Memory'):
result = commands.put_bytes(
offset_start, offset_start + range.length() - 1, pages=True, display_result=False)
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: Process, address: Address, data: bytes) -> None:
"""Write memory."""
nproc = find_proc_by_obj(process)
offset = process.trace.memory_mapper.map_back(nproc, address)
offset = process.trace.extra.required_mm().map_back(nproc, address)
dbg().write(offset, data)
@REGISTRY.method
@REGISTRY.method()
@util.dbg.eng_thread
def write_reg(frame: sch.Schema('StackFrame'), name: str, value: bytes):
def write_reg(frame: StackFrame, name: str, value: bytes) -> None:
"""Write a register."""
util.select_frame()
f = find_frame_by_obj(frame)
util.select_frame(f.FrameNumber)
nproc = pydbg.selected_process()
dbg().reg._set_register(name, value)
@REGISTRY.method(display='Refresh Events (custom)', condition=util.dbg.IS_TRACE)
@util.dbg.eng_thread
def refresh_events_custom(node: sch.Schema('State'), cmd: ParamDesc(str, display='Cmd'),
prefix: ParamDesc(str, display='Prefix')="dx -r2 @$cursession.TTD"):
def refresh_events_custom(node: State,
cmd: Annotated[str, ParamDesc(display='Cmd')],
prefix: Annotated[str, ParamDesc(display='Prefix')] = "dx -r2 @$cursession.TTD") -> None:
"""Parse TTD objects generated from a LINQ command."""
with commands.open_tracked_tx('Put Events (custom)'):
commands.ghidra_trace_put_events_custom(prefix, cmd)

View file

@ -1,5 +1,6 @@
<context>
<schema name="DbgRoot" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="EventScope" />
<attribute name="Sessions" schema="SessionContainer" required="yes" fixed="yes" />
<attribute name="Settings" schema="ANY" />
<attribute name="State" schema="State" />
@ -16,7 +17,6 @@
</schema>
<schema name="Session" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="EventScope" />
<interface name="FocusScope" />
<interface name="Aggregate" />
<element schema="VOID" />

View file

@ -1,5 +1,6 @@
<context>
<schema name="DbgRoot" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="EventScope" />
<attribute name="Sessions" schema="SessionContainer" required="yes" fixed="yes" />
<attribute name="Settings" schema="ANY" />
<attribute name="State" schema="ANY" />
@ -16,7 +17,6 @@
</schema>
<schema name="Session" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="EventScope" />
<interface name="FocusScope" />
<interface name="Aggregate" />
<element schema="VOID" />

View file

@ -13,10 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from comtypes.automation import VARIANT
from ghidratrace.client import Schedule
from .dbgmodel.imodelobject import ModelObject
from capstone import CsInsn
from _winapi import STILL_ACTIVE
from collections import namedtuple
from concurrent.futures import Future
import concurrent.futures
from ctypes import *
from ctypes import POINTER, byref, c_ulong, c_ulonglong, create_string_buffer
import functools
import io
import os
@ -25,11 +31,14 @@ import re
import sys
import threading
import traceback
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, TypeVar, Union, cast
from comtypes import CoClass, GUID
import comtypes
from comtypes.gen import DbgMod
from comtypes.hresult import S_OK, S_FALSE
from ghidradbg.dbgmodel.ihostdatamodelaccess import HostDataModelAccess
from ghidradbg.dbgmodel.imodelmethod import ModelMethod
from pybag import pydbg, userdbg, kerneldbg, crashdbg
from pybag.dbgeng import core as DbgEng
from pybag.dbgeng import exception
@ -37,9 +46,7 @@ from pybag.dbgeng import util as DbgUtil
from pybag.dbgeng.callbacks import DbgEngCallbacks
from pybag.dbgeng.idebugclient import DebugClient
from ghidradbg.dbgmodel.ihostdatamodelaccess import HostDataModelAccess
from ghidradbg.dbgmodel.imodelmethod import ModelMethod
from _winapi import STILL_ACTIVE
DESCRIPTION_PATTERN = '[{major:X}:{minor:X}] {type}'
DbgVersion = namedtuple('DbgVersion', ['full', 'name', 'dotted', 'arch'])
@ -132,23 +139,27 @@ class DebuggeeRunningException(BaseException):
pass
T = TypeVar('T')
class DbgExecutor(object):
def __init__(self, ghidra_dbg):
def __init__(self, ghidra_dbg: 'GhidraDbg') -> None:
self._ghidra_dbg = ghidra_dbg
self._work_queue = queue.SimpleQueue()
self._work_queue: queue.SimpleQueue = 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):
def submit(self, fn: Callable[..., T], /, *args, **kwargs) -> Future[T]:
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()
def _submit_no_exit(self, fn: Callable[..., T], /,
*args, **kwargs) -> Future[T]:
f: Future[T] = Future()
if self._executing and self._ghidra_dbg.IS_REMOTE == False:
f.set_exception(DebuggeeRunningException("Debuggee is Running"))
return f
@ -156,7 +167,7 @@ class DbgExecutor(object):
self._work_queue.put(w)
return f
def _clear_queue(self):
def _clear_queue(self) -> None:
while True:
try:
work_item = self._work_queue.get_nowait()
@ -165,12 +176,12 @@ class DbgExecutor(object):
work_item.future.set_exception(
DebuggeeRunningException("Debuggee is Running"))
def _state_execute(self):
def _state_execute(self) -> None:
self._executing = True
if self._ghidra_dbg.IS_REMOTE == False:
self._clear_queue()
def _state_break(self):
def _state_break(self) -> None:
self._executing = False
@ -201,9 +212,12 @@ class AllDbg(pydbg.DebuggerBase):
load_dump = crashdbg.CrashDbg.load_dump
C = TypeVar('C', bound=Callable[..., Any])
class GhidraDbg(object):
def __init__(self):
def __init__(self) -> None:
self._queue = DbgExecutor(self)
self._thread = self._queue._thread
# Wait for the executor to be operational before getting base
@ -245,10 +259,10 @@ class GhidraDbg(object):
setattr(self, name, self.eng_thread(getattr(base, name)))
self.IS_KERNEL = False
self.IS_EXDI = False
self.IS_REMOTE = os.getenv('OPT_CONNECT_STRING') is not None
self.IS_TRACE = os.getenv('USE_TTD') == "true"
def _new_base(self):
self.IS_REMOTE: bool = os.getenv('OPT_CONNECT_STRING') is not None
self.IS_TRACE: bool = os.getenv('USE_TTD') == "true"
def _new_base(self) -> None:
remote = os.getenv('OPT_CONNECT_STRING')
if remote is not None:
remote_client = DbgEng.DebugConnect(remote)
@ -256,8 +270,8 @@ class GhidraDbg(object):
self._protected_base = AllDbg(client=debug_client)
else:
self._protected_base = AllDbg()
def _generate_client(self, original):
def _generate_client(self, original: DebugClient) -> DebugClient:
cli = POINTER(DbgEng.IDebugClient)()
cliptr = POINTER(POINTER(DbgEng.IDebugClient))(cli)
hr = original.CreateClient(cliptr)
@ -265,13 +279,13 @@ class GhidraDbg(object):
return DebugClient(client=cli)
@property
def _base(self):
def _base(self) -> AllDbg:
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):
def run(self, fn: Callable[..., T], *args, **kwargs) -> T:
# TODO: Remove this check?
if hasattr(self, '_thread') and threading.current_thread() is self._thread:
raise WrongThreadException()
@ -283,64 +297,60 @@ class GhidraDbg(object):
except concurrent.futures.TimeoutError:
pass
def run_async(self, fn, *args, **kwargs):
def run_async(self, fn: Callable[..., T], *args, **kwargs) -> Future[T]:
return self._queue.submit(fn, *args, **kwargs)
@staticmethod
def check_thread(func):
'''
For methods inside of GhidraDbg, ensure it runs on the dbgeng
thread
'''
def check_thread(func: C) -> C:
"""For methods inside of GhidraDbg, ensure it runs on the dbgeng
thread."""
@functools.wraps(func)
def _func(self, *args, **kwargs):
def _func(self, *args, **kwargs) -> Any:
if threading.current_thread() is self._thread:
return func(self, *args, **kwargs)
else:
return self.run(func, self, *args, **kwargs)
return _func
return cast(C, _func)
def eng_thread(self, func):
'''
For methods and functions outside of GhidraDbg, ensure it
runs on this GhidraDbg's dbgeng thread
'''
def eng_thread(self, func: C) -> C:
"""For methods and functions outside of GhidraDbg, ensure it runs on
this GhidraDbg's dbgeng thread."""
@functools.wraps(func)
def _func(*args, **kwargs):
def _func(*args, **kwargs) -> Any:
if threading.current_thread() is self._thread:
return func(*args, **kwargs)
else:
return self.run(func, *args, **kwargs)
return _func
return cast(C, _func)
def _ces_exec_status(self, argument):
def _ces_exec_status(self, argument: int):
if argument & 0xfffffff == DbgEng.DEBUG_STATUS_BREAK:
self._queue._state_break()
else:
self._queue._state_execute()
@check_thread
def _install_stdin(self):
def _install_stdin(self) -> None:
self.input = StdInputCallbacks(self)
self._base._client.SetInputCallbacks(self.input)
# Manually decorated to preserve undecorated
def _dispatch_events(self, timeout=DbgEng.WAIT_INFINITE):
def _dispatch_events(self, timeout: int = DbgEng.WAIT_INFINITE) -> None:
# 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):
def exit_dispatch(self) -> None:
self._protected_base._client.ExitDispatch()
@check_thread
def cmd(self, cmdline, quiet=True):
def cmd(self, cmdline: str, quiet: bool = True) -> str:
# NB: pybag's impl always captures.
# Here, we let it print without capture if quiet is False
if quiet:
@ -356,20 +366,20 @@ class GhidraDbg(object):
return self._base._control.Execute(cmdline)
@check_thread
def return_input(self, input):
def return_input(self, input: str) -> None:
# 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):
def interrupt(self) -> None:
# 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):
def get_current_system_id(self) -> int:
# TODO: upstream?
sys_id = c_ulong()
hr = self._base._systems._sys.GetCurrentSystemId(byref(sys_id))
@ -377,7 +387,7 @@ class GhidraDbg(object):
return sys_id.value
@check_thread
def get_prompt_text(self):
def get_prompt_text(self) -> str:
# TODO: upstream?
size = c_ulong()
hr = self._base._control._ctrl.GetPromptText(None, 0, byref(size))
@ -386,12 +396,12 @@ class GhidraDbg(object):
return prompt_buf.value.decode()
@check_thread
def get_actual_processor_type(self):
def get_actual_processor_type(self) -> int:
return self._base._control.GetActualProcessorType()
@property
@check_thread
def pid(self):
def pid(self) -> Optional[int]:
try:
if is_kernel():
return 0
@ -403,17 +413,12 @@ class GhidraDbg(object):
class TTDState(object):
def __init__(self):
self._cursor = None
self._first = None
self._last = None
self._lastmajor = None
self._lastpos = None
self.breakpoints = []
self.events = {}
self.evttypes = {}
self.starts = {}
self.stops = {}
def __init__(self) -> None:
self._first: Optional[Tuple[int, int]] = None
self._last: Optional[Tuple[int, int]] = None
self._lastpos: Optional[Tuple[int, int]] = None
self.evttypes: Dict[Tuple[int, int], str] = {}
self.MAX_STEP: int
dbg = GhidraDbg()
@ -421,16 +426,16 @@ ttd = TTDState()
@dbg.eng_thread
def compute_pydbg_ver():
def compute_pydbg_ver() -> DbgVersion:
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)]
matches_opt = [pat.match(l) for l in blurb.splitlines()]
matches = [m for m in matches_opt if m is not None]
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()
@ -441,26 +446,27 @@ def get_target():
@dbg.eng_thread
def disassemble1(addr):
return DbgUtil.disassemble_instruction(dbg._base.bitness(), addr, dbg.read(addr, 15))
def disassemble1(addr: int) -> CsInsn:
data = dbg.read(addr, 15) # type:ignore
return DbgUtil.disassemble_instruction(dbg._base.bitness(), addr, data)
def get_inst(addr):
def get_inst(addr: int) -> str:
return str(disassemble1(addr))
def get_inst_sz(addr):
def get_inst_sz(addr: int) -> int:
return int(disassemble1(addr).size)
@dbg.eng_thread
def get_breakpoints():
def get_breakpoints() -> Iterable[Tuple[str, str, str, str, str]]:
ids = [bpid for bpid in dbg._base.breakpoints]
offset_set = []
expr_set = []
prot_set = []
width_set = []
stat_set = []
offset_set: List[str] = []
expr_set: List[str] = []
prot_set: List[str] = []
width_set: List[str] = []
stat_set: List[str] = []
for bpid in ids:
try:
bp = dbg._base._control.GetBreakpointById(bpid)
@ -496,7 +502,7 @@ def get_breakpoints():
@dbg.eng_thread
def selected_process():
def selected_process() -> int:
try:
if is_exdi():
return 0
@ -504,7 +510,8 @@ def selected_process():
do = dbg._base._systems.GetCurrentProcessDataOffset()
id = c_ulong()
offset = c_ulonglong(do)
nproc = dbg._base._systems._sys.GetProcessIdByDataOffset(offset, byref(id))
nproc = dbg._base._systems._sys.GetProcessIdByDataOffset(
offset, byref(id))
return id.value
if dbg.use_generics:
return dbg._base._systems.GetCurrentProcessSystemId()
@ -515,7 +522,7 @@ def selected_process():
@dbg.eng_thread
def selected_process_space():
def selected_process_space() -> int:
try:
if is_exdi():
return 0
@ -528,7 +535,7 @@ def selected_process_space():
@dbg.eng_thread
def selected_thread():
def selected_thread() -> Optional[int]:
try:
if is_kernel():
return 0
@ -540,7 +547,7 @@ def selected_thread():
@dbg.eng_thread
def selected_frame():
def selected_frame() -> Optional[int]:
try:
line = dbg.cmd('.frame').strip()
if not line:
@ -553,40 +560,47 @@ def selected_frame():
return None
def require(t: Optional[T]) -> T:
if t is None:
raise ValueError("Unexpected None")
return t
@dbg.eng_thread
def select_process(id: int):
def select_process(id: int) -> None:
if is_kernel():
# TODO: Ideally this should get the data offset from the id and then call
# SetImplicitProcessDataOffset
return
if dbg.use_generics:
id = get_proc_id(id)
id = require(get_proc_id(id))
return dbg._base._systems.SetCurrentProcessId(id)
@dbg.eng_thread
def select_thread(id: int):
def select_thread(id: int) -> None:
if is_kernel():
# TODO: Ideally this should get the data offset from the id and then call
# SetImplicitThreadDataOffset
return
if dbg.use_generics:
id = get_thread_id(id)
id = require(get_thread_id(id))
return dbg._base._systems.SetCurrentThreadId(id)
@dbg.eng_thread
def select_frame(id: int):
def select_frame(id: int) -> str:
return dbg.cmd('.frame /c {}'.format(id))
@dbg.eng_thread
def reset_frames():
def reset_frames() -> str:
return dbg.cmd('.cxr')
@dbg.eng_thread
def parse_and_eval(expr, type=None):
def parse_and_eval(expr: Union[str, int],
type: Optional[int] = None) -> Union[int, float, bytes]:
if isinstance(expr, int):
return expr
# TODO: This could be contributed upstream
@ -617,20 +631,22 @@ def parse_and_eval(expr, type=None):
return value.u.F82Bytes
if type == DbgEng.DEBUG_VALUE_FLOAT128:
return value.u.F128Bytes
raise ValueError(
f"Could not evaluate '{expr}' or convert result '{value}'")
@dbg.eng_thread
def get_pc():
def get_pc() -> int:
return dbg._base.reg.get_pc()
@dbg.eng_thread
def get_sp():
def get_sp() -> int:
return dbg._base.reg.get_sp()
@dbg.eng_thread
def GetProcessIdsByIndex(count=0):
def GetProcessIdsByIndex(count: int = 0) -> Tuple[List[int], List[int]]:
# TODO: This could be contributed upstream?
if count == 0:
try:
@ -643,11 +659,11 @@ def GetProcessIdsByIndex(count=0):
hr = dbg._base._systems._sys.GetProcessIdsByIndex(
0, count, ids, sysids)
exception.check_err(hr)
return (tuple(ids), tuple(sysids))
return (list(ids), list(sysids))
@dbg.eng_thread
def GetCurrentProcessExecutableName():
def GetCurrentProcessExecutableName() -> str:
# TODO: upstream?
_dbg = dbg._base
size = c_ulong()
@ -659,17 +675,15 @@ def GetCurrentProcessExecutableName():
size = exesize
hr = _dbg._systems._sys.GetCurrentProcessExecutableName(buffer, size, None)
exception.check_err(hr)
buffer = buffer[:size.value]
buffer = buffer.rstrip(b'\x00')
return buffer
return buffer.value.decode()
@dbg.eng_thread
def GetCurrentProcessPeb():
def GetCurrentProcessPeb() -> int:
# TODO: upstream?
_dbg = dbg._base
offset = c_ulonglong()
if dbg.is_kernel():
if is_kernel():
hr = _dbg._systems._sys.GetCurrentProcessDataOffset(byref(offset))
else:
hr = _dbg._systems._sys.GetCurrentProcessPeb(byref(offset))
@ -678,7 +692,7 @@ def GetCurrentProcessPeb():
@dbg.eng_thread
def GetCurrentThreadTeb():
def GetCurrentThreadTeb() -> int:
# TODO: upstream?
_dbg = dbg._base
offset = c_ulonglong()
@ -691,7 +705,7 @@ def GetCurrentThreadTeb():
@dbg.eng_thread
def GetExitCode():
def GetExitCode() -> int:
# TODO: upstream?
if is_kernel():
return STILL_ACTIVE
@ -704,8 +718,9 @@ def GetExitCode():
@dbg.eng_thread
def process_list(running=False):
"""Get the list of all processes"""
def process_list(running: bool = False) -> Union[
Iterable[Tuple[int, str, int]], Iterable[Tuple[int]]]:
"""Get the list of all processes."""
_dbg = dbg._base
ids, sysids = GetProcessIdsByIndex()
pebs = []
@ -725,12 +740,16 @@ def process_list(running=False):
return zip(sysids)
finally:
if not running and curid is not None:
_dbg._systems.SetCurrentProcessId(curid)
try:
_dbg._systems.SetCurrentProcessId(curid)
except Exception as e:
print(f"Couldn't restore current process: {e}")
@dbg.eng_thread
def thread_list(running=False):
"""Get the list of all threads"""
def thread_list(running: bool = False) -> Union[
Iterable[Tuple[int, int, str]], Iterable[Tuple[int]]]:
"""Get the list of all threads."""
_dbg = dbg._base
try:
ids, sysids = _dbg._systems.GetThreadIdsByIndex()
@ -758,8 +777,8 @@ def thread_list(running=False):
@dbg.eng_thread
def get_proc_id(pid):
"""Get the list of all processes"""
def get_proc_id(pid: int) -> Optional[int]:
"""Get the id for the given system process id."""
# TODO: Implement GetProcessIdBySystemId and replace this logic
_dbg = dbg._base
map = {}
@ -773,18 +792,18 @@ def get_proc_id(pid):
return None
def full_mem():
def full_mem() -> List[DbgEng._MEMORY_BASIC_INFORMATION64]:
info = DbgEng._MEMORY_BASIC_INFORMATION64()
info.BaseAddress = 0
info.RegionSize = (1 << 64) - 1
info.Protect = 0xFFF
info.Name = "full memory"
return [ info ]
return [info]
@dbg.eng_thread
def get_thread_id(tid):
"""Get the list of all threads"""
def get_thread_id(tid: int) -> Optional[int]:
"""Get the id for the given system thread id."""
# TODO: Implement GetThreadIdBySystemId and replace this logic
_dbg = dbg._base
map = {}
@ -799,8 +818,8 @@ def get_thread_id(tid):
@dbg.eng_thread
def open_trace_or_dump(filename):
"""Open a trace or dump file"""
def open_trace_or_dump(filename: Union[str, bytes]) -> None:
"""Open a trace or dump file."""
_cli = dbg._base._client._cli
if isinstance(filename, str):
filename = filename.encode()
@ -808,7 +827,7 @@ def open_trace_or_dump(filename):
exception.check_err(hr)
def split_path(pathString):
def split_path(pathString: str) -> List[str]:
list = []
segs = pathString.split(".")
for s in segs:
@ -823,23 +842,23 @@ def split_path(pathString):
return list
def IHostDataModelAccess():
return HostDataModelAccess(
dbg._base._client._cli.QueryInterface(interface=DbgMod.IHostDataModelAccess))
def IHostDataModelAccess() -> HostDataModelAccess:
return HostDataModelAccess(dbg._base._client._cli.QueryInterface(
interface=DbgMod.IHostDataModelAccess))
def IModelMethod(method_ptr):
return ModelMethod(
method_ptr.GetIntrinsicValue().value.QueryInterface(interface=DbgMod.IModelMethod))
def IModelMethod(method_ptr) -> ModelMethod:
return ModelMethod(method_ptr.GetIntrinsicValue().value.QueryInterface(
interface=DbgMod.IModelMethod))
@dbg.eng_thread
def get_object(relpath):
"""Get the list of all threads"""
def get_object(relpath: str) -> Optional[ModelObject]:
"""Get the model object at the given path."""
_cli = dbg._base._client._cli
access = HostDataModelAccess(_cli.QueryInterface(
interface=DbgMod.IHostDataModelAccess))
(mgr, host) = access.GetDataModel()
mgr, host = access.GetDataModel()
root = mgr.GetRootNamespace()
pathstr = "Debugger"
if relpath != '':
@ -850,11 +869,13 @@ def get_object(relpath):
@dbg.eng_thread
def get_method(context_path, method_name):
"""Get the list of all threads"""
def get_method(context_path: str, method_name: str) -> Optional[ModelMethod]:
"""Get method for the given object (path) and name."""
obj = get_object(context_path)
if obj is None:
return None
keys = obj.EnumerateKeys()
(k, v) = keys.GetNext()
k, v = keys.GetNext()
while k is not None:
if k.value == method_name:
break
@ -865,24 +886,24 @@ def get_method(context_path, method_name):
@dbg.eng_thread
def get_attributes(obj):
"""Get the list of attributes"""
def get_attributes(obj: ModelObject) -> Dict[str, ModelObject]:
"""Get the list of attributes."""
if obj is None:
return None
return obj.GetAttributes()
@dbg.eng_thread
def get_elements(obj):
"""Get the list of elements"""
def get_elements(obj: ModelObject) -> List[Tuple[int, ModelObject]]:
"""Get the list of elements."""
if obj is None:
return None
return obj.GetElements()
@dbg.eng_thread
def get_kind(obj):
"""Get the kind"""
def get_kind(obj) -> Optional[int]:
"""Get the kind."""
if obj is None:
return None
kind = obj.GetKind()
@ -891,65 +912,66 @@ def get_kind(obj):
return obj.GetKind().value
@dbg.eng_thread
def get_type(obj):
"""Get the type"""
if obj is None:
return None
return obj.GetTypeKind()
# DOESN'T WORK YET
# @dbg.eng_thread
# def get_type(obj: ModelObject):
# """Get the type."""
# if obj is None:
# return None
# return obj.GetTypeKind()
@dbg.eng_thread
def get_value(obj):
"""Get the value"""
def get_value(obj: ModelObject) -> Any:
"""Get the value."""
if obj is None:
return None
return obj.GetValue()
@dbg.eng_thread
def get_intrinsic_value(obj):
"""Get the intrinsic value"""
def get_intrinsic_value(obj: ModelObject) -> VARIANT:
"""Get the intrinsic value."""
if obj is None:
return None
return obj.GetIntrinsicValue()
@dbg.eng_thread
def get_target_info(obj):
"""Get the target info"""
def get_target_info(obj: ModelObject) -> ModelObject:
"""Get the target info."""
if obj is None:
return None
return obj.GetTargetInfo()
@dbg.eng_thread
def get_type_info(obj):
"""Get the type info"""
def get_type_info(obj: ModelObject) -> ModelObject:
"""Get the type info."""
if obj is None:
return None
return obj.GetTypeInfo()
@dbg.eng_thread
def get_name(obj):
"""Get the name"""
def get_name(obj: ModelObject) -> str:
"""Get the name."""
if obj is None:
return None
return obj.GetName().value
@dbg.eng_thread
def to_display_string(obj):
"""Get the display string"""
def to_display_string(obj: ModelObject) -> str:
"""Get the display string."""
if obj is None:
return None
return obj.ToDisplayString()
@dbg.eng_thread
def get_location(obj):
"""Get the location"""
def get_location(obj: ModelObject) -> Optional[str]:
"""Get the location."""
if obj is None:
return None
try:
@ -961,10 +983,10 @@ def get_location(obj):
return None
conv_map = {}
conv_map: Dict[str, str] = {}
def get_convenience_variable(id):
def get_convenience_variable(id: str) -> Any:
if id not in conv_map:
return "auto"
val = conv_map[id]
@ -973,77 +995,89 @@ def get_convenience_variable(id):
return val
def get_cursor():
return ttd._cursor
def get_last_position():
def get_last_position() -> Optional[Tuple[int, int]]:
return ttd._lastpos
def set_last_position(pos):
def set_last_position(pos: Tuple[int, int]) -> None:
ttd._lastpos = pos
def get_event_type(rng):
if ttd.evttypes.__contains__(rng):
return ttd.evttypes[rng]
def get_event_type(pos: Tuple[int, int]) -> Optional[str]:
if ttd.evttypes.__contains__(pos):
return ttd.evttypes[pos]
return None
def pos2snap(pos):
pmap = get_attributes(pos)
major = get_value(pmap["Sequence"])
minor = get_value(pmap["Steps"])
return mm2snap(major, minor)
def split2schedule(pos: Tuple[int, int]) -> Schedule:
major, minor = pos
return mm2schedule(major, minor)
def mm2snap(major, minor):
def schedule2split(time: Schedule) -> Tuple[int, int]:
return time.snap, time.steps
def mm2schedule(major: int, minor: int) -> Schedule:
index = int(major)
if index < 0 or index >= ttd.MAX_STEP:
return int(ttd._lastmajor) # << 32
snap = index # << 32 + int(minor)
return snap
if index < 0 or hasattr(ttd, 'MAX_STEP') and index >= ttd.MAX_STEP:
return Schedule(require(ttd._last)[0])
if index >= 1 << 63:
return Schedule((1 << 63) - 1)
return Schedule(index, minor)
def pos2split(pos):
def pos2split(pos: ModelObject) -> Tuple[int, int]:
pmap = get_attributes(pos)
major = get_value(pmap["Sequence"])
minor = get_value(pmap["Steps"])
return (major, minor)
def set_convenience_variable(id, value):
def schedule2ss(time: Schedule) -> str:
return f'{time.snap:x}:{time.steps:x}'
def compute_description(time: Optional[Schedule], fallback: str) -> str:
if time is None:
return fallback
evt_type = get_event_type(schedule2split(time))
evt_str = evt_type or fallback
return DESCRIPTION_PATTERN.format(major=time.snap, minor=time.steps,
type=evt_str)
def set_convenience_variable(id: str, value: Any) -> None:
conv_map[id] = value
def set_kernel(value):
def set_kernel(value: bool) -> None:
dbg.IS_KERNEL = value
def is_kernel():
def is_kernel() -> bool:
return dbg.IS_KERNEL
def set_exdi(value):
def set_exdi(value: bool) -> None:
dbg.IS_EXDI = value
def is_exdi():
def is_exdi() -> bool:
return dbg.IS_EXDI
def set_remote(value):
def set_remote(value: bool) -> None:
dbg.IS_REMOTE = value
def is_remote():
def is_remote() -> bool:
return dbg.IS_REMOTE
def set_trace(value):
def set_trace(value: bool) -> None:
dbg.IS_TRACE = value
def is_trace():
def is_trace() -> bool:
return dbg.IS_TRACE