NationalSecurityAgency/ghidra · error · IllegalArgumentException

Register edits require a thread.

Error message

Register edits require a thread.

What it means

Thrown as IllegalArgumentException by RW_TRACE.setVariable (ControlMode.java:239-242) when the mapped host address is a register address but coordinates.getThread() is null. Register state lives in a per-thread register space (TraceMemorySpace for the thread/frame), so a register edit is impossible without a thread; memory edits do not require one.

Source

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

		@Override
		public CompletableFuture<Void> setVariable(PluginTool tool,
				DebuggerCoordinates coordinates, Address guestAddress, byte[] data) {
			Trace trace = coordinates.getTrace();
			TracePlatform platform = platformFor(coordinates, guestAddress);
			long snap = coordinates.getViewSnap();
			Address hostAddress = platform.mapGuestToHost(guestAddress);
			if (hostAddress == null) {
				throw new IllegalArgumentException(
					"Guest address " + guestAddress + " is not mapped");
			}
			TraceMemoryOperations memOrRegs;
			Address overlayAddress;
			try (Transaction tx = trace.openTransaction("Edit Variable")) {
				if (hostAddress.isRegisterAddress()) {
					TraceThread thread = coordinates.getThread();
					if (thread == null) {
						throw new IllegalArgumentException("Register edits require a thread.");
					}
					TraceMemorySpace regs = trace.getMemoryManager()
							.getMemoryRegisterSpace(thread, coordinates.getFrame(),
								true);
					memOrRegs = regs;
					overlayAddress = regs.getAddressSpace().getOverlayAddress(hostAddress);
				}
				else {
					memOrRegs = trace.getMemoryManager();
					overlayAddress = hostAddress;
				}
				if (memOrRegs.putBytes(snap, overlayAddress,
					ByteBuffer.wrap(data)) != data.length) {
					return CompletableFuture.failedFuture(new MemoryAccessException());
				}
			}
			return CompletableFuture.completedFuture(null);
		}

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Activate/select a thread in the coordinates before editing a register: coordinates = coordinates.thread(someThread).
  2. If editing memory, ensure the address is a memory address (not a register address) so the thread precondition is skipped.
  3. Check coordinates.getThread() != null before issuing a register edit.

Example fix

// before
RW_TRACE.setVariable(tool, coordinates, regAddress, data);  // no thread

// after
DebuggerCoordinates withThread = coordinates.thread(selectedThread);
RW_TRACE.setVariable(tool, withThread, regAddress, data);
Defensive patterns

Strategy: validation

Validate before calling

if (hostAddress.isRegisterAddress() && coordinates.getThread() == null) {
    throw new IllegalStateException("select a thread before editing registers");
}

Type guard

boolean canEditRegister(DebuggerCoordinates c, Address host) {
    return !host.isRegisterAddress() || c.getThread() != null;
}

Try / catch

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

Prevention

When it happens

Trigger: Editing a register variable in RW_TRACE mode while no thread is active in DebuggerCoordinates: coordinates.getThread() == null and hostAddress.isRegisterAddress() is true.

Common situations: A user opens the Registers view at the process level without selecting a thread; programmatic edit issued before thread activation; a coordinates object built without a thread for a register write.

Related errors


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