How x86 Conditional Jumps Really Work — EFLAGS Truth with GDB + pwndbg
Field notes on x86 conditional jumps. `ja`, `jb`, `je`, `jl` — these aren't independent instructions reading different state. They're all checking different bits of EFLAGS, set by whatever ALU instruction ran most recently. Once you see this in GDB, every malware analysis and reverse-engineering session reads differently.
Status: Companion blog draft for the existing video. Long-form transcript + bridge framing TBD.
Companion assets
- Original video:
How x86 Jumps REALLY Work: EFLAGS Truth with GDB + pwndbg | Malware, RE, Debugging
- GitHub: harrison001/RETAlchemy — return-address manipulation lab; related material for x86 control flow
TL;DR
All x86 conditional jumps (ja, jb, je, jge, js, …) read from one place: EFLAGS. The mnemonics suggest they read operand values; they don’t. They read bits that the previous ALU instruction (cmp, sub, test, add, etc.) wrote into EFLAGS. Once you watch this in GDB’s info registers eflags between every step, malware analysis and reverse engineering stop being mysterious — and you stop misreading branch conditions in production crash dumps.
The setup
- A small x86 program with several conditional branches
- GDB with pwndbg (or peda) for live EFLAGS display
- Step instruction by instruction; watch
cmpset flags; watch the nextj*read them
Debug command transcript
# TODO: paste actual gdb + pwndbg sequence from the video
# gdb ./demo
# pwndbg> start
# pwndbg> nexti
# pwndbg> info registers eflags
# pwndbg> nexti # cmp instruction
# pwndbg> info registers eflags # now flags reflect cmp result
# pwndbg> nexti # ja / jb / je / etc.
# pwndbg> info registers eflags # unchanged; branch decision was made from these bits
What broke (or rather, what was misread)
A common bug class in reverse engineering and crash analysis: looking at a sequence like
cmp eax, ebx
ja .target
and reading ja as “jump if eax > ebx.” That’s mostly true — but ja is actually checking the carry flag and the zero flag set by cmp. If anything between cmp and ja modifies EFLAGS (a function call, a system call, even a test on an unrelated register), the branch will read stale or wrong flags.
This is the source of “but the values look right, why did the branch take the wrong path?” debugging.
What fixed it
TODO: write up the rule:
- EFLAGS is the state actually read by
j*instructions. - Many instructions write to EFLAGS — not just
cmp/test/arithmetic. Function returns can; some syscalls can. - To know what the branch will do, read EFLAGS at the moment of the branch — not at the moment of the previous comparison.
What this teaches backend / AI infra engineers
You don’t read raw x86 in production. But you do:
- Read coredumps with
j*instructions where the wrong branch was taken - Understand compiled output of Go’s
if x < yto know if it’s actually a single comparison or two (compiler may emitjb+ fallback) - Audit security-critical code where flags are used for constant-time comparisons (cryptography, timing-safe equality)
The deeper lesson: CPU state is global mutable state, and instructions you don’t realize touch EFLAGS can change branch behavior. Senior systems engineers know which instructions are pure and which have side effects on flags. This separates “I can debug the assembly” from “I can read assembly fluently.”
Related work
- Video: Rust vs C: Assembly Comparison
- GitHub: RETAlchemy
🎧 More Ways to Consume This Content
I occasionally advise small teams on backend reliability, Go performance, and production AI systems. Learn more: /services
Comments
This space is waiting for your voice.
Comments will be supported shortly. Stay connected for updates!
This section will display user comments from various platforms like X, Reddit, YouTube, and more. Comments will be curated for quality and relevance.
Have questions? Reach out through:
Want to see your comment featured? Mention us on X or tag us on Reddit.
Leave a Comment