NationalSecurityAgency/ghidra · error · IllegalArgumentException

Emulator edits require a thread.

Error message

Emulator edits require a thread.

What it means

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.

Source

Thrown at Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/control/ControlMode.java:321

			}
			AddressRange ctxRange = TraceRegisterUtils.rangeForRegister(ctxReg);
			if (ctxRange.contains(address)) {
				return false;
			}
			return true;
		}

		@Override
		public CompletableFuture<Void> setVariable(PluginTool tool,
				DebuggerCoordinates coordinates, Address address, byte[] data) {
			if (!(coordinates.getView() instanceof TraceVariableSnapProgramView)) {
				throw new IllegalArgumentException("Cannot emulate using a Fixed Program View");
			}
			TraceThread thread = coordinates.getThread();
			if (thread == null) {
				// TODO: Well, technically, only for register edits
				// It's a limitation in TraceSchedule. Every step requires a thread
				throw new IllegalArgumentException("Emulator edits require a thread.");
			}
			Language language = coordinates.getPlatform().getLanguage();
			TraceSchedule time = coordinates.getTime()
					.patched(thread, language,
						PatchStep.generateSleigh(language, address, data));

			DebuggerCoordinates withTime = coordinates.time(time);
			DebuggerTraceManagerService traceManager =
				Objects.requireNonNull(tool.getService(DebuggerTraceManagerService.class),
					"No trace manager service");

			return traceManager.activateAndNotify(withTime, ActivationCause.EMU_STATE_EDIT);
		}

		@Override
		public boolean useEmulatedBreakpoints() {
			return true;
		}

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Activate/select a thread in the coordinates before any emulator edit: coordinates.thread(someThread).
  2. Check isVariableEditable(coordinates, ...) returns true (it returns false when thread == null) before calling setVariable.
  3. If no thread context exists, use RW_TRACE mode for memory-only edits.

Example fix

// before
RW_EMULATOR.setVariable(tool, coordinates, address, data);  // no thread

// after
DebuggerCoordinates withThread = coordinates.thread(selectedThread);
if (RW_EMULATOR.isVariableEditable(withThread, address, data.length)) {
    RW_EMULATOR.setVariable(tool, withThread, address, data);
}
Defensive patterns

Strategy: validation

Validate before calling

if (coordinates.getThread() == null) {
    throw new IllegalStateException("emulator edits require an active thread");
}
if (!RW_EMULATOR.isVariableEditable(coordinates, address, data.length)) {
    throw new IllegalStateException("not editable in emulator mode");
}

Type guard

boolean emuHasThread(DebuggerCoordinates c) {
    return c.getThread() != null;
}

Try / catch

try {
    RW_EMULATOR.setVariable(tool, coordinates, address, data);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("require a thread")) {
        coordinates = coordinates.thread(selectThread());
        RW_EMULATOR.setVariable(tool, coordinates, address, data);
    } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14). Data as JSON: /api/errors/dd06b562c15aa14a. Report an issue: GitHub.