2026-09-09
The Direction Flag is a single bit in RFLAGS (bit 10) that decides which way the x86 string instructions walk memory. When DF=0, REP MOVSB increments RSI and RDI after each byte; when DF=1, it decrements them. It exists because memmove-style copies with overlapping regions sometimes need to copy backwards to avoid trampling their own source.
Two instructions manipulate it: CLD (clear, DF=0) and STD (set, DF=1). They're one byte each, and the CPU tracks DF specially — it's renamed alongside the flags, so back-to-back CLD+REP MOVS pairs don't stall.
The ABI trap. The System V AMD64 ABI requires DF=0 on function entry and on function return. Compilers assume this — they emit REP MOVSB and REP STOSB without a preceding CLD, because they trust every caller they'll ever see to have already cleared it. Break the assumption and every string operation in every callee starts walking backwards through memory.
The real-world burn. Linux had this exact bug for years. Before commit a5b9e5a2a (2015), the signal delivery path would enter user-space signal handlers without clearing DF. If glibc's signal handler ran a memcpy, and glibc had chosen the REP MOVSB implementation for that CPU, the copy would run backwards — corrupting whatever memory happened to sit below the destination. The bug hid because glibc's SSE-based memcpy on most CPUs didn't use string instructions, so only certain CPU families crashed. The fix was two bytes: an unconditional CLD in the signal trampoline.
Where you'll see DF=1 legitimately. A hand-written memmove that detects dst > src with overlap will STD, adjust RSI/RDI to point at the last byte, run REP MOVSB, then CLD before returning. The CLD before return is not optional — it's the ABI contract.
Rule of thumb. Any assembly routine that executes STD must execute CLD on every path back to a C caller, including the exception-unwind path. If your inline assembly clobbers DF, list "cc" in the clobbers and issue CLD yourself — GCC won't insert one.
Cost. CLD is effectively free on modern cores (renamed, retired quickly), but toggling DF mid-function forces a partial-flags stall on some microarchitectures because DF isn't renamed with the arithmetic flags. Set it once, use it, clear it once.
CLD makes every downstream REP MOVS silently corrupt memory in the wrong direction.
