Skip to content

Instructions

x86-64

XCHG

Atomically exchange two operands. Harmless between registers — but with a memory operand it carries an implicit LOCK, making it a foundational synchronization primitive.

XCHG a, b swaps the contents of its two operands. At least one must be a register; the other can be a register or memory.

asm
xchg eax, ebx     ; swap the two registers
xchg [rdi], eax   ; swap eax with the memory at [rdi]  (atomic — see below)

The implicit LOCK

This is the detail that matters for reverse engineering: when one operand is memory, XCHG behaves as if it had a LOCK prefix even if none is written. The read-modify-write is atomic with respect to other cores.

That makes xchg reg, [mem] the simplest atomic swap — the classic implementation of std::atomic::exchange, a test-and-set spinlock acquire, and similar primitives:

asm
; acquire a spinlock: put 1 into the lock, learn the old value
mov eax, 1
xchg eax, [lock]  ; atomically: old = *lock; *lock = 1
test eax, eax     ; was it already held?
jnz spin          ; yes → keep spinning

Register-to-register xchg has no such locking and is just a swap.

Reverse-engineering notes

  • xchg reg, [mem] (or any lock-prefixed RMW) is a strong signal you're inside locking / lock-free concurrency code: spinlocks, mutex fast paths, reference-count swaps.
  • xchg eax, eax (encoded 0x90) is NOP — they share an opcode. A disassembler will show nop, but now you know why.
  • Compilers rarely use register xchg to swap variables; three movs through a temporary are usually faster on modern cores. A register xchg therefore often points to hand-written assembly or a deliberate size optimisation.
  • Don't confuse it with CMPXCHG (compare-and-swap), the other workhorse of lock-free code — CMPXCHG swaps only if the destination still equals a comparand, and needs an explicit lock to be atomic.

See also: MOV · NOP · PUSH / POP.