Writing Software

←Index

Assembly Code on Raspberry Pi

by Professor Petabyte

 

Introduction

If you've dabbled in a bit of program code writing, you've probably used a 'High-level' language, such as Python, Javascript or Basic. These are perfect for beginners as they are easy to understand, and look relatively like English, but they run relatively slowly, because as program execution reaches each line, another program called an 'Interpreter' has to convert the line of program code into 'machine code'; the native language of whatever microprocessor is bing used.

Compiled languages get around this by using a 'Compiler'; a program that converts the program code into machine code all in one go, before it is run. This creates a file called an 'Executable' which runs much faster than any interpreted language because all of the conversion from program code (e.g. Python) to machine code has already been done.

If you want the ultimate fast program however, you need to write it in machine code which is impractical, but the next best option is to write the program in Assembly language which is compiled into machine code, as this gives total control over the machine code produced. Compiling C for example does not give the same level of control as it relies on decisions made by whoever wrote the compiler to guide how the C code in converted into machine language instructions.

If Assembly Code Run Faster, Why isn't Everything Written Using It?

Writing programs in Assembly code is MUCH more laborious than writing in a High-level language, where an huge amount of the work is done by routines and functions the programmer doesn't need to think about, and often doesn't really need to know about. High-level languages are certainly easier to use as they look a lot more like English; For example a line of Python such as
print("Hello World") is relatively self explanatory. It may not convey where the words 'Hello World' will be printed, but is does give the general idea.

Similarly
NI = GrossPay * NIRate
Tax = GrossPay * IncomeTaxRate
Deductions = NI + Tax
NetPay = GrossPay - Deductions
gives a pretty good idea to anyone who can read English, what is going on, even to the extent that it is reasonable to assume that this might form part of some kind of payroll program.

Also, Assembly language programs are microprocessor specific - You cannot expect a program written for one model of microprocessor to work on a different one. Conversely, a Javascript or Python program will run on any computer that can run Javascript, or Python, or whatever language the program is written in. This is because the interpreter program is microprocessor specific, and will convert high-level language into machine code that model of microprocessor running the interpreter can understand.

What Do You Need to Write Assembly Code?

The short answer is "Not much, and it's all free."

You need three things:-

  1. A suitable editor
    If you are using a standard recommended Linux install on your Pi, you will already have at least two; "Geanny Programmer's Editor" in the Programming group of programs on the Raspberry main menu, and also "Text Editor" in the Accessories group of programs.

  2. A compiler
    You should find the "as" compiler installed by default. If not, you may need to use sudo apt get as to install it.
    First go to you command prompt and run uname -m

  3. The knowledge on this page.
    Keep reading. Assuming you don't encounter any unforseen problems, you are 5 minutes from running your first Assembly program.

....and that's about it.

Registers

Assembly language programming is largely about moving bits and bytes around the Microprocessor, and to and from RAM and the Microprocessor. This is achieved using registers, analogous to variables in a high level language like Python, but with some inflexible rules and limitations.

The Raspberry Pi 4, which uses an ARM Cortex-A72 processor (based on the ARMv8-A architecture). The available registers in AArch64 (64-bit ARM) Assembly mode are as follows:

General Purpose Registers (GPRs)

There are 31 general-purpose 64-bit registers named:
X0 to X30:Used for passing arguments, return values, and general computation.
X0-X7:Typically used to pass arguments to functions and return values.
X8:Indirect result location register (e.g., for structure returns).
X9-X15:Temporary registers.
X16-X17:Intra-procedure-call temporary registers (used by the linker).
X18:Platform register (reserved or custom use).
X19-X28:Callee-saved registers.
X29:Frame pointer (FP).
X30:Link register (LR).
XZR/WZR:Zero register (reads as 0, writes are ignored).
Each Xn has a corresponding 32-bit lower half Wn.
E.g., X0 has W0, X1 has W1, etc

Special Purpose Registers

SP: Stack Pointer
PC: Program Counter (not directly accessible in most cases)
NZCV:Condition Flags (Negative, Zero, Carry, Overflow)
PSTATE:Processor State Register (includes interrupt masks, condition flags)

Floating Point / SIMD Registers (V-registers)

V0-V31: 128-bit wide, used for floating-point and SIMD (NEON) operation.
  Subsets:
D0-D31 (64 bit)
S0-S31 (32 bit)
H0-H31 (16 bit)
B0-B31 (8 bit)

Additional Notes

  • Raspberry Pi OS supports both AArch64 (64-bit) and AArch32 (32-bit) modes, but Raspberry Pi 4 typically runs 64-bit Linux now.
  • If using 32-bit mode (AArch32), register names are R0-R15, with R13 as SP, R14 as LR, R15 as PC.

Before Hello World

The first program most programmers write is 'Hello World'. We'll get to that, but maybe it's too big a challenge for a first assembly language program. We'll start with outputting a single number (of your choice) instead. Here's the code for that:-

Copy Code
01: // beforehelloworld.s
02: .section .text
03: .global _start
04:
05: _start:
06:    mov x0, #67 //Exit code = 67
07:    mov c8, #93 //Syscall number for exit in AArch64
08:    svc #0      //make syscall
09:    

So what is going on in there?

  1. Just a comment - Any after the // is ignored by the compiler
  2. The directive .section .text in AArch64 assembly (and other assembly languages like x86) is used to define the code section of a program, i.e. where the executable instructions go.
    Purpose of .section .text
    The .text section is a standard segment in object files where machine code (i.e., the actual instructions) is stored.
    By writing .section .text, you're telling the assembler “Put the following instructions into the executable code section of the output.”
    Why it's important
    Most linkers (like ld) and executable formats (like ELF used in Linux) expect code to be in .text.It separates code from data (which might go in .data or .bss sections).Helps organize the binary, allows for read-only protection, and enables executable-only permissions for code.
  3. makes the _start label visible as the program's entry point.
  4. The label called _start, from which execution will commence.
  5. Move value 67 into register X0
  6. Syscall number for exit in Aarch36
  7. Make the Syscall

Hello World

Copy Code
01: // helloworld.s
02: .section .data
03: .msg: .asciz "Hello World!\n" // Null-terminated string
04:
05: .section .text
06: .global _start
07:
08: _start:
09:     // syscall:write(int fd, const voia *big, size_t count)
10:     mov X0, #1      // fd = 1 (stdout)
11:     ldr X1, =msg    // buf = address of msg
12:     mov X2, #14     // count = length of msg
13:     mov X8, #64     // syscall number for write (64)
14:     svc #0          // make syscall
15:
16:     // syscall: exit(int status)
17:     mov x0, #0      // exit status = 0
18:     mov c8, #93     // Syscall number for exit in AArch64 (93)
19:     svc #0          // make syscall
20:    

What are 'Syscall's?

In Linux (and most modern OSes), a syscall (system call) is how a program requests a service from the kernel - like reading a file, writing output, creating processes, etc.
So what is going on here? For those familiar with High-level languages such as Python, you might want to think of this code as really three lines of code:-

  1. : Lines 3 & 4 are analogous to declaring a variable called 'msg' in a a section of the code reserved for declaring variables called '.data'.
  2. : Lines 10-15 are analogous to writing the string of characters in the variable called 'msg' to the console (aka stdout, on line 11)
  3. : Lines 17-20 simply exit the program.

Why Use Syscalls?

User programs run in user mode with limited access. The kernel runs in privileged mode and controls hardware, memory, filesystems, etc. Syscalls allow safe, controlled access to these privileged operations.

AArch64 Syscall Basics (Raspberry Pi 4)

In AArch64 Linux, syscalls follow this convention:-

RegisterPurpose
X01st argument to syscall
X12nd argument
X23rd argument
X3-X54th-6th arguments (if needed)
X8Syscall number
svc #0Triggers the syscall trap
X0Return value after syscall

AArch64 Linux Syscall Quick Reference

SyscallNumberDescriptionArgs (Parameters)
read63Read from a file descriptorX0 = fd, X1 = buf, X2 = count
write64Write to a file descriptorX0 = fd, X1 = buf, X2 = count
openat56Open file (relative to directory fd)X0 = dirfd, X1 = pathname, etc.
close57Close a file descriptorX0 = fd
exit93Exit the processX0 = exit_code
brk214Set end of data segmentX0 = new_brk
mmap222Memory mappingMany args (usually via stack/struct)
execve221Execute a programX0 = filename, X1 = argv, X2 = envp
for220Create a child process(no arguments)
getpid172Get process ID(no arguments)
nanosleep101Sleep for a timeX0 = req, X1 = rem (struct pointers)

Conclusion

Writing software in Assembly code is ONLY for the patient mind. There's a LOT of work involved compare to languages like Python, or BASIC, or even C. On the other hand compiled Asembly programs benefit the impatient by running very, very fast.

If nothing else, you'll have figured out by now that a single page of a website cannot really constitute a full course on Assembly code, but hopefully the notes above have begun to demystify it, and provided you with a couple of cheap victories in the form of two working Assembly code programs.

To finish here is a simple program, properly annotated (like the examples above), which you can study and hopefully improve your understanding with.

Net Salary Calculator

Calculate Net Salary from Gross Salary of £55,000.00 assuming National Insurance rate of 10% and Income Tax rate of 20%, showing all deductions.

Copy Code
01: // salary_breakdown_v11.s
02:
03: .section .rodata
04: fmt_gross:     .asciz "Gross Salary: GBP %.2f\n"
05: fmt_ni:        .asciz "NI Rate: %.2f%%  Amount: GBP %.2f\n"
06: fmt_tax:       .asciz "Tax Rate: %.2f%%  Amount: GBP %.2f\n"
07: fmt_total:     .asciz "Total Deductions: GBP %.2f\n"
08: fmt_net:       .asciz "Net Pay: GBP %.2f\n"
09:
10: .section .data
11:     .align 3
12: gross_salary:  .double 55000.00
13: ni_rate:       .double 10.00
14: tax_rate:      .double 20.00
15: hundred:       .double 100.00
16:
17: .section .text
18: .global main
19: .extern printf
20:
21: main:
22:     // Load and print Gross Salary
23:     ldr     X0, =gross_salary
24:     ldr     D0, [X0]
25:     ldr     X1, =fmt_gross
26:     mov     X0, X1
27:     bl      printf
28:
29:     // --- NI Calculation ---
30:     ldr     X1, =gross_salary
31:     ldr     D1, [X1]               // D1 = gross
32:
33:     ldr     X2, =ni_rate
34:     ldr     D2, [X2]               // D2 = NI rate
35:
36:     fmul    D3, D1, D2             // gross * NI rate
37:
38:     ldr     X3, =hundred
39:     ldr     D4, [X3]               // D4 = 100
40:     fdiv    D5, D3, D4             // D5 = NI amount
41:
42:     ldr     X0, =fmt_ni
43:     fmov    D0, D2                 // NI rate
44:     fmov    D1, D5                 // NI amount
45:     bl      printf
46:
47:     // --- Tax Calculation ---
48:     ldr     X1, =gross_salary
49:     ldr     D1, [X1]               // D1 = gross again
50:
51:     ldr     X2, =tax_rate
52:     ldr     D2, [X2]               // D2 = tax rate
53:
54:     fmul    D3, D1, D2             // gross * tax rate
55:
56:     ldr     X3, =hundred
57:     ldr     D4, [X3]               // D4 = 100 again (ensure valid)
58:     fdiv    D6, D3, D4             // D6 = tax amount
59:
60:     ldr     X0, =fmt_tax
61:     fmov    D0, D2                 // tax rate
62:     fmov    D1, D6                 // tax amount
63:     bl      printf
64:
65:     // --- Total Deductions ---
66:     fadd    D7, D5, D6             // D7 = NI + Tax
67:     ldr     X0, =fmt_total
68:     fmov    D0, D7
69:     bl      printf
70:
71:     // --- Net Pay ---
72:     ldr     X1, =gross_salary
73:     ldr     D1, [X1]               // D1 = gross again (for accuracy)
74:
75:     fsub    D8, D1, D7             // Net = Gross - Deductions
76:     ldr     X0, =fmt_net
77:     fmov    D0, D8
78:     bl      printf
79:
80:     // Exit
81:     mov     X0, #0
82:     ret
83:
84:     
85:     



←Index




© 2025 Professor Petabyte