.text:004169F0; =============== S U B R O U T I N E ===============
.text:004169F0
.text:004169F0

Recognising if/else and switch in disassembly

.text:004169F0; author: Can Ozdemir date: 2024-11-30 size: 0xE80 tags: x86-64, control-flow, tutorial
.text:004169F0
.text:004169F0sub_4169F0procnear
.text:004169F1

Control flow is the skeleton of a function. Once you can see the ifs, loops and switches, the rest is filling in details. Here are the common shapes, from real GCC 13 output.

.text:00416AA3

if / else if / else at -O0

.text:00416AC0
int clamp(int x)
{
    if (x < 0)
        return 0;
    else if (x > 255)
        return 255;
    return x;
}
.text:00416B2D
0000:  push   rbp
0001:  mov    rbp, rsp
0004:  mov    DWORD PTR [rbp-0x4], edi
0007:  cmp    DWORD PTR [rbp-0x4], 0x0
000b:  jns    0014                        ; x >= 0: skip the first branch
000d:  mov    eax, 0x0                    ; return 0
0012:  jmp    0027
0014:  cmp    DWORD PTR [rbp-0x4], 0xff
001b:  jle    0024                        ; x <= 255: skip the second branch
001d:  mov    eax, 0xff                   ; return 255
0022:  jmp    0027
0024:  mov    eax, DWORD PTR [rbp-0x4]    ; return x
0027:  pop    rbp
0028:  ret
.text:00416D46

The pattern: a compare, a conditional jump over the body using the inverted condition (x < 0 became jns, "not negative"), and an unconditional jmp at the end of each body to the common exit. Each if is a forward conditional jump; each else is the jmp that skips the rest.

.text:00416E69

The same function at -O1: no jumps at all

.text:00416E95
0000:  mov    eax, 0xff
0005:  cmp    edi, eax
0007:  cmovle eax, edi       ; eax = (x <= 255) ? x : 255
000a:  test   eax, eax
000c:  mov    edx, 0x0
0011:  cmovs  eax, edx       ; if negative: 0
0014:  ret
.text:00416F64

cmov (conditional move) lets the compiler compute both outcomes and pick one, avoiding branches that the CPU might mispredict. When you see cmovXX, read it as a small ? : expression.

.text:00417020

switch: the jump table

.text:00417039
int classify(int n)
{
    switch (n) {
    case 0: return 10;
    case 1: return 23;
    case 2: return 37;
    case 3: return 41;
    case 4: return 58;
    case 5: return 66;
    case 6: return 79;
    default: return -1;
    }
}
.text:00417120
004d:  cmp    edi, 0x6
0050:  ja     007f                        ; n > 6 unsigned: default
0052:  mov    edi, edi                    ; zero-extend n to 64 bits
0054:  jmp    QWORD PTR [rdi*8+table]     ; jump to table[n]
005b:  mov    eax, 0xa                    ; case 0
0060:  ret
0061:  mov    eax, 0x25                   ; case 2
0066:  ret
       ...
007f:  mov    eax, 0xffffffff             ; default: -1
0084:  ret
0085:  mov    eax, 0x17                   ; case 1
008a:  ret
.text:00417304

Three things identify a jump table:

.text:00417327
  1. A single unsigned bounds check (cmp + ja). Negative numbers become huge unsigned values, so one check covers both n < 0 and n > 6.
  2. An indirect jump through memory, indexed by the value times 8 (the size of a pointer).
  3. A table of code addresses in .rodata, one per case.
.text:00417450

The case bodies don't have to be in order: here case 1 ended up after the default. Disassemblers usually recognise the table and label each target (case 1, case 2…); if yours doesn't, read the table from .rodata yourself: each entry is an 8-byte address.

.text:00417556

When cases are sparse (say 1, 100 and 5000), compilers use a chain of compares or a binary search instead of a table. And when every case just returns a constant, as above, GCC at -O2 may turn the whole switch into a lookup table of values and a single load, with no jumps at all.

.text:00417676

Loops, briefly

.text:00417687

A backwards jump is a loop. The compare right before it is the loop condition. See "From C to x86-64" for a full example.

.text:00417700

Habit

.text:00417708

Draw the graph. IDA's graph view (space bar) and Ghidra's function graph do it for you: blocks are straight-line code, edges are jumps. ifs look like diamonds, loops like cycles, and a switch is one block fanning out to many.

.text:004177ED
.text:004177EDretn
.text:004177EDsub_4169F0endp
.text:004177ED; © 2026 Can Ozdemir. All rights reserved. terms
.text:004177ED
.text:004177EE; -------- cross references --------
Output
Loading ozdc.us… 34 functions, 3 data segments.
Type help in the command line below and press Enter.