ARM assembly-language programming for the Raspberry Pi
3. Using branch instructions
This example builds on the previous one by illustrating a simple branch ("goto", "jump") instruction. It doesn't do anything more useful -- it just does it in a slightly different way. The ARM instruction set has many different branch instructions and, unlike in most CPUs, we have to use branches to implement function calls as well as jumps.
The example
Here is the example. You can build and execute it exactly the same
as the previous example (as
followed by ld
).
// Set exit value to a literal number, by jumping to code that // invokes sys_exit .text .global _start // Exit the program. // The exit code is always 42 exit: mov %r0, $42 mov %r7, $1 // sys_exit is syscall #1 swi $0 // invoke syscall _start: b exit
New ideas in this example
Notice first of all that the program does not start execution with the
first instruction in the source, as the previous one did. Execution
starts at the address labeled _start
. At this address
there is only one instruction:
b exit
b
is the assembly code for branch -- the most
basic way to move program execution from one address to another.
exit
is a label -- it was defined earlier in the
code.
Branching as a means to control program flow
If we label a piece of self-contained code, then we can jump to it
from anywhere. In this example, the code that exits the program is
labeled exit
, and it's certainly plausible that we might
need to exit the program from a number of different places. Grouping
instructions that form a self-contained unit of functionality is
one of the most basic of all programming techniques, regardless of
the language.
In this example, the block of code that exits the program never completes -- execution stops at the syscall. So the question what to do afterwards never arises. Being able to execute a self-contained piece of code, and then continue with a previous sequence of operations, allows us to implement function calls (or subroutines, or procedures -- these terms are essentially synonymous in assembly programming). I'll have a lot more to say about functions later.
Conditional branches -- a preview
The b
branch is unconditional -- the program execution
moves to the specified location, and that's that. The real power
of branching comes from conditional branches. These are
branches that may, or may not, be executed, depending on certain
conditions. Conditional branches are usually preceded by some
kind of test on a register -- is it greater than some number, or
less; is it zero or not. Conditional branches will become increasingly
important in later, more complex examples.
A digression -- ARM machine code
There are a number of different 32-bit ARM cores on the market, and 64-bit cores are now available, too. There are subtle variations even between the different 32-bit devices. In these articles I will be focusing on the 'traditional' ARMv7 cores, which are found in most mobile devices and many desktop ones. The instruction set is usually just known as "ARM", although some writers use the term "AArch32" to distinguish it from other instructions sets that are available. All instructions in the AArch32 set are exactly 32 bits (four bytes) in length; this is understandable, but not a necessity, for a CPU with a 32-bit register size.
Many ARM devices support an alternative instruction set called "Thumb" and a few, mostly for the microcontroller market, support Thumb exclusively. The Thumb instruction set uses predominantly 16-bit instructions. Originally, Thumb was devised as more space-efficient subset of AArch32, but Thumb has expanded so that it offers overlapping functionality with AArch32. For reasons of space, and to avoid unnecessary duplication, I won't be dealing with Thumb at all in these articles.
If you don't like looking at hexadecimal code, or have no interest in the low-level design of the ARM instruction set, you won't lose much by skipping the rest of this section.
It can be illustrative to look at the executable machine code
using a disassembler
like objdump
.
This will show the source code alongside the machine code
generated by the assembler.
$ objdump -d 02_first_jump.o 00000000: 0: e3a0002a mov r0, #42 ; 0x2a 4: e3a07001 mov r7, #1 8: ef000000 svc 0x00000000 0000000c <_start>: c: eafffffb b 0
It should be clear that each instruction is exactly 32 bits (eight hex digits) long. The first digit is the condition code, which determines whether or not the instruction will actually be executed. The value 0xE means that the instruction should be executed unconditionally. Nearly every instruction in every example in this series will be unconditional, except for conditional branches.
The actual instruction code is split between the second and third hex digits, which makes it hard to pick out. The instruction code is only four bits long, so the ARM instruction set has only sixteen basic operations. Each of these is modified in various ways by the condition code and immediate flag -- this is why there are (many) more than sixteen assembly language instructions.
The lower 5 digits encode the registers that the operation should apply to, and/or an immediate number. The immediate number is only 11 bits long, which partly -- but not completely -- explains why the range of immediate values is limited in the way it is. You should be able to pick out the immediate values 42 (0x2A) and 1 in the hex dump.
Summary
Branching to a label allows self-contained functional units to be coded.
In ARM assembly, we use branches to implement function calls.
The ARM instruction set includes various kinds of conditional branches.
- Previous: 2. A first program, demonstrating how to assemble and run code
- Table of contents
- Next: 4. Passing arguments in registers