There's a special register called rflags.
Its bits act like flags for various conditions.
Some of those are listed below:
| name | symbol | bit |
|---|---|---|
| carry | CF | 0 |
| zero | ZF | 6 |
| sign | SF | 7 |
| overflow | OF | 11 |
For a full list, refer to Intel's Manual.
The flags in rflags are not modified directly.
Instead, they are set by many different instructions.
For instance, ZF is set by many arithmetic or bitwise operations when the result is zero.
One of the most common instructions used to test conditions is cmp.
It takes two operands and updates the flags, but does not modify its operands.
The cmp instruction subtracts the second operand from the first and sets flags according to the result.
If A is the first operand and B, the second:
| flag | set when |
|---|---|
| CF | A < B (unsigned) |
| ZF | A == B |
| SF | A < B (signed, no overflow) |
| OF | overflow in signed subtraction |
As a default, code in x86-64 executes sequentially from top to bottom.
But there are many situations where it's necessary to modify this behavior. For instance, to execute a different set of instructions in response to a condition.
In higher-level languages, this is usually done with abstractions such as if...else conditionals.
However, those do not exist in x86-64.
Instead, x86-64 provides instructions that effectively transfer execution to another location of the code.
This is called branching.
We've already seen two such instructions: call and ret.
When a function is called, execution is transferred from the caller to the called function. And, on return, execution is transferred back to the caller.
If no ret is found, execution fallthroughs from one function to the next.
This can sometimes be used to optimize code flow.
The instruction jmp unconditionally transfers execution of the program to another point of the code. Its single operand is a label which has the address to the point where execution will continue.
Consider, for instance, the following function:
fn:
mov rax, 5
jmp end
add rax, 10
end:
ret
When fn is called, execution starts at mov rax, 5.
This sets the value in rax to be 5 at that point.
The next instruction is jmp end, which transfers execution to the label end.
After end, the next instruction is ret, which transfers execution back to the caller function.
Notice that, since add rax, 10 is located after jmp end and before end, it is never executed.
The value of rax when fn returns is 5.
The family of instructions jcc transfers execution of the program to another point only if a specific condition is met. Otherwise, execution continues sequentially.
Each condition maps to one or more flags in rflags.
Some jcc variants test that a flag is set, others test that it is cleared.
The cc in jcc is not literal, but refers to the specific suffix associated with the flag tested.
There are many suffixes and many of them test the same condition as another. Some of those refer directly to a flag, so that the instruction jumps to a label if the specific flag is set:
| suffix | jumps if |
|---|---|
| z | ZF == 1 |
| c | CF == 1 |
| s | SF == 1 |
| o | OF == 1 |
Many others are chosen in order to refer to their meaning in a cmp instruction.
For example:
| instruction | suffix | jumps if |
|---|---|---|
| cmp A, B | e | A == B |
| cmp A, B | l | A < B (signed) |
| cmp A, B | b | A < B (unsigned) |
| cmp A, B | g | A > B (signed) |
| cmp A, B | a | A > B (unsigned) |
It's possible to add e after l, b, g or a to include the equality in the condition:
cmp rcx, r8
jge two ; this jumps to 'two' if rcx is greater than, or equal to, r8 in a signed comparison
jbe two ; this jumps to 'two' if rcx is lesser than, or equal to, r8 in an unsigned comparison
For all suffixes, there are variants which check the opposite behavior.
They have the same syntax, but with a n before the suffix.
For instance, jnz jumps when ZF is not set.
Similarly, jnae jumps when A is not >= B (A and B interpreted as unsigned integers).
Some suffixes are aliases to the same conditions.
For example, jz (suffix z, for ZF) and je (suffix e, for equal) both jump when ZF is set.
This is because, with cmp, ZF is set when the subtraction yields zero, which corresponds to the two operands being equal.
Other suffixes, however, test a combination of flags and can not be directly substituted by a single flag suffix.
Prefer the suffix that better describes the semantics of your comparison.
Labels are visible in the entire source file, they are not local to a function. So it is impossible to reuse a label name.
In order to mimic the behavior of a local label, NASM has a special notation for a label declared with a period (.) before it.
This notation defines a label which implicitly includes the name of the previous non-dotted label:
section .text
fn1:
...
.example: ; this is fn1.example
...
ret
fn2:
...
.example: ; this is fn2.example
...
ret
It is still possible to jump to this label from anywhere in the code by using the full label name, for instance, jmp fn1.example.
However, a jump that uses the part of the label starting at the dot will be made to the label inside the upper function.
For example, .example behaves as if it was local to the function:
section .text
fn1:
...
.example:
...
jmp .example ; this jumps to fn1.example
fn2:
...
.example:
...
jmp .example ; this jumps to fn2.example
Note that a non-dotted label inside a function in practice defines another function:
section .text
fn1:
...
.example: ; this is 'fn1.example'
...
non_dotted:
...
.example: ; this is 'non_dotted.example'
...
ret