by Professor Petabyte
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.
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
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.
Tax = GrossPay * IncomeTaxRate
Deductions = NI + Tax
NetPay = GrossPay - Deductions
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.
The short answer is "Not much, and it's all free."
You need three things:-
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.
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
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.
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 | |
|
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?
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:
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:-
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.
In AArch64 Linux, syscalls follow this convention:-
Register | Purpose |
---|---|
X0 | 1st argument to syscall |
X1 | 2nd argument |
X2 | 3rd argument |
X3-X5 | 4th-6th arguments (if needed) |
X8 | Syscall number |
svc #0 | Triggers the syscall trap |
X0 | Return value after syscall |
Syscall | Number | Description | Args (Parameters) |
---|---|---|---|
read | 63 | Read from a file descriptor | X0 = fd, X1 = buf, X2 = count |
write | 64 | Write to a file descriptor | X0 = fd, X1 = buf, X2 = count |
openat | 56 | Open file (relative to directory fd) | X0 = dirfd, X1 = pathname, etc. |
close | 57 | Close a file descriptor | X0 = fd |
exit | 93 | Exit the process | X0 = exit_code |
brk | 214 | Set end of data segment | X0 = new_brk |
mmap | 222 | Memory mapping | Many args (usually via stack/struct) |
execve | 221 | Execute a program | X0 = filename, X1 = argv, X2 = envp |
for | 220 | Create a child process | (no arguments) |
getpid | 172 | Get process ID | (no arguments) |
nanosleep | 101 | Sleep for a time | X0 = req, X1 = rem (struct pointers) |
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 CalculatorCalculate 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: