NationalSecurityAgency/ghidra · error · WrongThreadException
Was {}. Want {}
Error message
Was {}. Want {} What it means
Raised as WrongThreadException by the GhidraDbg._base property when the calling thread is not the dedicated dbgeng engine thread. DbgEng COM objects are single-threaded (STA); accessing them from any other thread corrupts state, so the agent enforces thread affinity.
Source
Thrown at Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/util.py:289
remote = os.getenv('OPT_CONNECT_STRING')
if remote is not None:
remote_client = DbgEng.DebugConnect(remote)
debug_client = self._generate_client(remote_client)
self._protected_base = AllDbg(client=debug_client)
else:
self._protected_base = AllDbg()
def _generate_client(self, original: DebugClient) -> DebugClient:
cli = POINTER(DbgEng.IDebugClient)()
cliptr = POINTER(POINTER(DbgEng.IDebugClient))(cli)
hr = original.CreateClient(cliptr)
exception.check_err(hr)
return DebugClient(client=cli)
@property
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: Callable[..., T], *args, **kwargs) -> T:
# TODO: Remove this check?
if hasattr(self, '_thread') and threading.current_thread() is self._thread:
raise WrongThreadException()
future = self._queue.submit(fn, *args, **kwargs)
# https://stackoverflow.com/questions/72621731/is-there-any-graceful-way-to-interrupt-a-python-concurrent-future-result-call gives an alternative
while True:
try:
return future.result(0.5)
except concurrent.futures.TimeoutError:
pass
def run_async(self, fn: Callable[..., T], *args, **kwargs) -> Future[T]:
return self._queue.submit(fn, *args, **kwargs)
View on GitHub (pinned to d5f144c24d)
Solutions
- Annotate any function touching dbg._base with @util.dbg.eng_thread (or call it through dbg.run(...)).
- Ensure background work submits onto the engine thread instead of calling COM inline.
- If you must call from another thread, route through dbg.run(fn, *args) which marshals via the engine queue.
Example fix
// before
def my_op():
return dbg._base.reg.get_pc() # may run off-thread -> WrongThreadException
// after
@util.dbg.eng_thread
def my_op():
return dbg._base.reg.get_pc() Defensive patterns
Strategy: validation
Validate before calling
import threading
if threading.current_thread() is not dbg._thread:
raise RuntimeError('must run on the dbgeng engine thread; decorate with @util.dbg.eng_thread') Type guard
import threading
def on_engine_thread() -> bool:
return threading.current_thread() is getattr(dbg, '_thread', None) Try / catch
from ghidradbg.util import WrongThreadException
try:
val = dbg._base.reg.get_pc()
except WrongThreadException:
val = dbg.run(lambda: dbg._base.reg.get_pc()) # marshal onto engine thread Prevention
- Decorate every function that touches dbg._base with @util.dbg.eng_thread.
- For cross-thread callers, route through dbg.run(fn, *args) instead of direct access.
- Never spawn a raw thread that calls DbgEng COM methods inline.
When it happens
Trigger: Touching dbg()._base / dbg._base from a worker thread, a GUI callback, or any code path not decorated with @dbg.eng_thread (or @dbg.check_thread). The decorator marshals the call onto the engine thread; bypassing it triggers this guard.
Common situations: Adding new agent code that calls dbg()._base directly without the eng_thread decorator; invoking DbgEng operations from a Python background thread or timer; refactoring that drops the decorator.
Related errors
- Address {address} is not in process {proc}
- Cannot convert {}'s value: '{}', type: '{}'
- Breakpoints[{breaknum}] does not exist
- Events[{eventnum}] does not exist
- Events[{excnum}] does not exist
AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14).
Data as JSON: /api/errors/52eb62930f431fdb.
Report an issue: GitHub.