{"record":{"id":"dd06b562c15aa14a","repo":"NationalSecurityAgency/ghidra","slug":"emulator-edits-require-a-thread","errorCode":null,"errorMessage":"Emulator edits require a thread.","messagePattern":"Emulator edits require a thread\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java","lineNumber":321,"sourceCode":"\t\t\t}\n\t\t\tAddressRange ctxRange = TraceRegisterUtils.rangeForRegister(ctxReg);\n\t\t\tif (ctxRange.contains(address)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\t@Override\n\t\tpublic CompletableFuture<Void> setVariable(PluginTool tool,\n\t\t\t\tDebuggerCoordinates coordinates, Address address, byte[] data) {\n\t\t\tif (!(coordinates.getView() instanceof TraceVariableSnapProgramView)) {\n\t\t\t\tthrow new IllegalArgumentException(\"Cannot emulate using a Fixed Program View\");\n\t\t\t}\n\t\t\tTraceThread thread = coordinates.getThread();\n\t\t\tif (thread == null) {\n\t\t\t\t// TODO: Well, technically, only for register edits\n\t\t\t\t// It's a limitation in TraceSchedule. Every step requires a thread\n\t\t\t\tthrow new IllegalArgumentException(\"Emulator edits require a thread.\");\n\t\t\t}\n\t\t\tLanguage language = coordinates.getPlatform().getLanguage();\n\t\t\tTraceSchedule time = coordinates.getTime()\n\t\t\t\t\t.patched(thread, language,\n\t\t\t\t\t\tPatchStep.generateSleigh(language, address, data));\n\n\t\t\tDebuggerCoordinates withTime = coordinates.time(time);\n\t\t\tDebuggerTraceManagerService traceManager =\n\t\t\t\tObjects.requireNonNull(tool.getService(DebuggerTraceManagerService.class),\n\t\t\t\t\t\"No trace manager service\");\n\n\t\t\treturn traceManager.activateAndNotify(withTime, ActivationCause.EMU_STATE_EDIT);\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean useEmulatedBreakpoints() {\n\t\t\treturn true;\n\t\t}","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/NationalSecurityAgency/ghidra/blob/d5f144c24d6bc53c9cbf4448c6d11143e7696206/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java#L303-L339","documentation":"Thrown as IllegalArgumentException by RW_EMULATOR.setVariable (ControlMode.java:317-322) when coordinates.getThread() is null. Emulator edits are implemented as TraceSchedule patches (patched(thread, ...)), and every schedule step requires a thread; without one the patch cannot be generated. The code comment notes this is a TraceSchedule limitation.","triggerScenarios":"Editing any variable in RW_EMULATOR mode while no thread is active. Unlike RW_TRACE (which only needs a thread for register edits), the emulator requires a thread for ALL edits because PatchStep.generateSleigh is thread-bound.","commonSituations":"A memory edit attempted in emulator mode before a thread is selected; coordinates built without a thread; switching to emulator mode at the process level.","solutions":["Activate/select a thread in the coordinates before any emulator edit: coordinates.thread(someThread).","Check isVariableEditable(coordinates, ...) returns true (it returns false when thread == null) before calling setVariable.","If no thread context exists, use RW_TRACE mode for memory-only edits."],"exampleFix":"// before\nRW_EMULATOR.setVariable(tool, coordinates, address, data);  // no thread\n\n// after\nDebuggerCoordinates withThread = coordinates.thread(selectedThread);\nif (RW_EMULATOR.isVariableEditable(withThread, address, data.length)) {\n    RW_EMULATOR.setVariable(tool, withThread, address, data);\n}","handlingStrategy":"validation","validationCode":"if (coordinates.getThread() == null) {\n    throw new IllegalStateException(\"emulator edits require an active thread\");\n}\nif (!RW_EMULATOR.isVariableEditable(coordinates, address, data.length)) {\n    throw new IllegalStateException(\"not editable in emulator mode\");\n}","typeGuard":"boolean emuHasThread(DebuggerCoordinates c) {\n    return c.getThread() != null;\n}","tryCatchPattern":"try {\n    RW_EMULATOR.setVariable(tool, coordinates, address, data);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"require a thread\")) {\n        coordinates = coordinates.thread(selectThread());\n        RW_EMULATOR.setVariable(tool, coordinates, address, data);\n    } else throw e;\n}","preventionTips":["Always select a thread before emulator edits (required for memory AND registers).","Gate edits on RW_EMULATOR.isVariableEditable() which returns false without a thread.","Use RW_TRACE for thread-less memory edits."],"tags":["control-mode","emulator","thread-required","trace","java"],"backgroundTag":null,"analyzedSha":"d5f144c24d6bc53c9cbf4448c6d11143e7696206","analyzedAt":"2026-08-14T01:00:57.564Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}