Conditional Branching
So far our programs have been pretty simple. The instructions execute in sequence, one after another, until the end is reached and the program stops. In order to do more complicated things, we have to introduce conditional branching. Conditional branching just means choosing which code to execute based on the state of the program (usually the value of a register). Primarily, conditional branching is used to do variations of the following:
- Execute code only if a register or memory has a certain value
- Run the same code over and over (looping)
Branching generally takes at least 2 instructions:
- First, a comparison instruction compares two values.
- Second, a jump instruction decides whether or not to jump to a particular instruction based on the result of the previous comparison.
Here's a full example program that demonstrates a conditional branch. All it
does is set rbx
to a value, compare that value to the number 10, and print
a different message depending on whether rbx
was 10 or not.
%define sys_exit 60
%define success 0
%define sys_write 1
%define stdout 1
%define newline 10
section .data
; Message to be shown if rbx is equal to 10
msg_equal: db "rbx is 10", newline
msg_equal_len: equ $-msg_equal
; Message to be shown if rbx is not equal to 10
msg_unequal: db "rbx is not 10", newline
msg_unequal_len: equ $-msg_unequal
section .text
global _start
_start:
; Set the input value
mov rbx, 7
; Compare the input value to 10
cmp rbx, 10
; Jump to the 'unequal:' label if rbx is not equal to 10
jne unequal
; Print a message saying that rbx is equal to 10
mov rax, sys_write
mov rdi, stdout
mov rsi, msg_equal
mov rdx, msg_equal_len
syscall
unequal:
; Print a message saying that rbx is not equal to 10
mov rax, sys_write
mov rdi, stdout
mov rsi, msg_unequal
mov rdx, msg_unequal_len
syscall
; Exit the program
mov rax, sys_exit
mov rdi, success
syscall
The high level overview of this program is:
- Set
rbx
to 7. - Compare
rbx
to 10. - Jump to unequal: if
rbx
is not equal to 10. - Print the corresponding message.
- End the program.
This is why it's called conditional branching. The first message,
"rbx is 10", is only printed if rbx
is actually 10. If it's not, that message
is skipped. This means that different code executes depending on the result
of the comparison.
Now let's look at the program in more detail:
section .data
; Message to be shown if rbx is equal to 10
msg_equal: db "rbx is 10", newline
msg_equal_len: equ $-msg_equal
; Message to be shown if rbx is not equal to 10
msg_unequal: db "rbx is not 10", newline
msg_unequal_len: equ $-msg_unequal
This is where the output strings are declared, along with their accompanying character counts:
- msg_equal is what will be printed if
rbx
equals 10. - msg_unequal is what will be printed if
rbx
does not equal 10.
section .text
global _start
_start:
This is the beginning of the program code.
; Set the input value
mov rbx, 7
Here is where we give rbx
its value of 7, which will be compared against 10
in the next instruction.
; Compare the input value to 10
cmp rbx, 10
This is the cmp instruction, which is short for "compare". This instruction
compares two values. In this case, we're comparing the value of rbx
against the integer 10. The possibilities are:
rbx
is greater than 10rbx
is equal to 10rbx
is less than 10
This instruction does not act on the result of the comparison by itself, but it sets things up so the next instruction can.
; Jump to the 'unequal:' label if rbx is not equal to 10
jne unequal
This is a conditional jump. Normally, code executes sequentially: one instruction after another. A jump interrupts the normal flow of code execution, jumping somewhere else instead of executing the next instruction.
There are several different kinds of jumps. Some examples are given below:
Instruction | Meaning |
---|---|
jmp | Unconditional jump (always jump) |
je | Jump if equal |
jne | Jump if not equal |
jl | Jump if less than |
jle | Jump if less than or equal |
jg | Jump if greater than |
jge | Jump if greater than or equal |
In this case, we're using the jne instruction, which stands for "jump if
not equal". The jump is performed conditionally, based on the result of
the previous cmp instruction: if rbx
is not equal to 10, execution will
jump ahead to the unequal: label. If rbx
is equal to 10, the jump is not
performed.
You may be wondering how the jne instruction acts on the outcome of the cmp
instruction. The cmp instruction compares two values and then stores the
result of that comparison in a special-purpose register called rflags
. The
conditional jump instructions use the state of rflags
to decide whether or
not to jump.
So the cmp instruction writes to the rflags
register, then conditional
jump instructions like jl and jge use the values in rflags
to decide
whether to jump.
In this case, because rbx
is 7, and 7 is not equal to 10, the jne
instruction will jump ahead to the unequal: label. If rbx
was set to 10,
it would not jump. In that case, execution would continue sequentially like
normal.
; Print a message saying that rbx is equal to 10
mov rax, sys_write
mov rdi, stdout
mov rsi, msg_equal
mov rdx, msg_equal_len
syscall
This is the code that runs if rbx
happened to be equal to 10. It prints out a
message informing the user that rbx
is equal to 10. With rbx
set to 7, this
code will be skipped.
unequal:
This is called a label. A label lets you name a section of code so that it can be referred to elsewhere. Labels can be jumped to, meaning that you can tell the computer to run the first instruction after the specified label instead of running the next instruction in order like normal.
This is where the jne instruction jumps to in the event that rbx
is not
equal to 10. The previous instructions will be skipped and execution will
resume after this label.
You may notice that this looks a lot like the _start: line in every program we've written so far. _start: is also an example of a label. What makes _start: unique is that when nasm assembles your program, it looks for a special label named _start for where the program begins.
While _start: marks the beginning of the program, unequal: marks the
beginning of the code which handles the situation where rbx
is not equal to
10.
In higher-level languages, jumping is often referred to as goto
, and you may
have heard that goto
is bad programming practice and should be avoided. In
higher-level languages, that's mostly true: you should only use a goto
in a
few very specific situations. But this is assembly language, baby! There's
no other game in town. This is the only way to implement conditionals and
loops in assembly, so get your fill of goto
here!
; Print a message saying that rbx is not equal to 10
mov rax, sys_write
mov rdi, stdout
mov rsi, msg_unequal
mov rdx, msg_unequal_len
syscall
This is the code which runs when rbx
is not equal to 10. It prints out a
message saying something to that effect.
; Exit the program
mov rax, sys_exit
mov rdi, success
syscall
Here is where the program ends. Type the previous program into a file called "conditional.asm" and run it. You should see the following output:
rbx is not 10
Success! Or is it? Try changing the line:
mov rbx, 7
To this:
mov rbx, 10
With rbx
set to 10, this should change the result of the cmp instruction,
which should in turn change the behavior of the jne instruction, causing the
jump to unequal: not to happen. Run it and see what happens:
rbx is 10
rbx is not 10
Whoops. Both messages printed. Let's step through the code and see what's going on.
mov rbx, 10
First, rbx
is set to 10.
cmp rbx, 10
Next, rbx
is compared to 10. rbx
is equal to 10.
jne unequal
Here, execution skips ahead to the unequal: label if rbx
is not equal
to 10. Since it is equal to 10, the jump will not occur. Execution will
continue to the next line sequentially.
; Print a message saying that rbx is equal to 10
mov rax, sys_write
mov rdi, stdout
mov rsi, msg_equal
mov rdx, msg_equal_len
syscall
Here's the first message that printed: "rbx is 10". So far so good.
unequal:
; Print a message saying that rbx is not equal to 10
mov rax, sys_write
mov rdi, stdout
mov rsi, msg_unequal
mov rdx, msg_unequal_len
syscall
Here's where the second message is printed: "rbx is not 10". This is the erroneous message.
And that's the problem: the unequal: code runs in both cases. If rbx
is not
equal to 10, the first message is skipped, printing only the second message.
But if rbx
is equal to 10, the second message is printed as well. Execution
falls through from the first message to the second.
To get around this, we need a second jump. After the first message is printed, we need to jump ahead, skipping the second message. That way, only one message will ever print. Take a look at the fixed program in its entirety:
%define sys_exit 60
%define success 0
%define sys_write 1
%define stdout 1
%define newline 10
section .data
; Message to be shown if rbx is equal to 10
msg_equal: db "rbx is 10", newline
msg_equal_len: equ $-msg_equal
; Message to be shown if rbx is not equal to 10
msg_unequal: db "rbx is not 10", newline
msg_unequal_len: equ $-msg_unequal
section .text
global _start
_start:
; Set the input value
mov rbx, 10
; Compare the input value to 10
cmp rbx, 10
; Jump to the 'unequal:' label if rbx is not equal to 10
jne unequal
; Print a message saying that rbx is equal to 10
mov rax, sys_write
mov rdi, stdout
mov rsi, msg_equal
mov rdx, msg_equal_len
syscall
; Skip ahead to the done label
jmp done
unequal:
; Print a message saying that rbx is not equal to 10
mov rax, sys_write
mov rdi, stdout
mov rsi, msg_unequal
mov rdx, msg_unequal_len
syscall
done:
; Exit the program
mov rax, sys_exit
mov rdi, success
syscall
It's almost identical. We've added a new jmp instruction and a new label called done:. Let's take a look at these in more detail:
; Skip ahead to the done label
jmp done
After the first message, which is printed only when rbx
equals 10, we come to
this unconditional jump instruction. This instruction always jumps to the
given label, regardless of any conditions. This causes the computer to skip
the second message if it printed the first message.
done:
This is the jump target for when the first message is finished printing.
Make the changes above or create a new file and run it. Try with rbx set to a
few different values. If rbx
is 10, you should see:
rbx is 10
If it's not 10, you should see:
rbx is not 10
But you should never see both messages at once.