{"record":{"id":"52eb62930f431fdb","repo":"NationalSecurityAgency/ghidra","slug":"was-want","errorCode":null,"errorMessage":"Was {}. Want {}","messagePattern":"Was (.+?)\\. Want (.+?)","errorType":"exception","errorClass":"WrongThreadException","httpStatus":null,"severity":"error","filePath":"Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/util.py","lineNumber":289,"sourceCode":"        remote = os.getenv('OPT_CONNECT_STRING')\n        if remote is not None:\n            remote_client = DbgEng.DebugConnect(remote)\n            debug_client = self._generate_client(remote_client)\n            self._protected_base = AllDbg(client=debug_client)\n        else:\n            self._protected_base = AllDbg()\n\n    def _generate_client(self, original: DebugClient) -> DebugClient:\n        cli = POINTER(DbgEng.IDebugClient)()\n        cliptr = POINTER(POINTER(DbgEng.IDebugClient))(cli)\n        hr = original.CreateClient(cliptr)\n        exception.check_err(hr)\n        return DebugClient(client=cli)\n\n    @property\n    def _base(self) -> AllDbg:\n        if threading.current_thread() is not self._thread:\n            raise WrongThreadException(\"Was {}. Want {}\".format(\n                threading.current_thread(), self._thread))\n        return self._protected_base\n\n    def run(self, fn: Callable[..., T], *args, **kwargs) -> T:\n        # TODO: Remove this check?\n        if hasattr(self, '_thread') and threading.current_thread() is self._thread:\n            raise WrongThreadException()\n        future = self._queue.submit(fn, *args, **kwargs)\n        # https://stackoverflow.com/questions/72621731/is-there-any-graceful-way-to-interrupt-a-python-concurrent-future-result-call gives an alternative\n        while True:\n            try:\n                return future.result(0.5)\n            except concurrent.futures.TimeoutError:\n                pass\n\n    def run_async(self, fn: Callable[..., T], *args, **kwargs) -> Future[T]:\n        return self._queue.submit(fn, *args, **kwargs)\n","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/NationalSecurityAgency/ghidra/blob/d5f144c24d6bc53c9cbf4448c6d11143e7696206/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/util.py#L271-L307","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ndef my_op():\n    return dbg._base.reg.get_pc()  # may run off-thread -> WrongThreadException\n// after\n@util.dbg.eng_thread\ndef my_op():\n    return dbg._base.reg.get_pc()","handlingStrategy":"validation","validationCode":"import threading\nif threading.current_thread() is not dbg._thread:\n    raise RuntimeError('must run on the dbgeng engine thread; decorate with @util.dbg.eng_thread')","typeGuard":"import threading\n\ndef on_engine_thread() -> bool:\n    return threading.current_thread() is getattr(dbg, '_thread', None)","tryCatchPattern":"from ghidradbg.util import WrongThreadException\ntry:\n    val = dbg._base.reg.get_pc()\nexcept WrongThreadException:\n    val = dbg.run(lambda: dbg._base.reg.get_pc())  # marshal onto engine thread","preventionTips":["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."],"tags":["dbgeng","threading","com","sta","thread-affinity"],"backgroundTag":null,"analyzedSha":"d5f144c24d6bc53c9cbf4448c6d11143e7696206","analyzedAt":"2026-08-14T01:00:57.564Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}