Mini Projects in Assembly Language: HLASM, x86, and ARM Exercises

Mini Projects in Assembly Language
The gap between understanding assembly language concepts and being able to write a working program is bridged only by writing programs. This article presents a progression of hands-on projects, each targeting a specific skill set, with annotated code in HLASM (z/OS), x86-64 (Linux NASM), and AArch64 (Linux GNU AS).
Work through these projects in order. Each one builds on the previous.
Project 1 — Hello World Without a Runtime
Skills: System calls / MVS macros, program structure, entry point, exit.
The classic starting point. The goal is to print a message and exit cleanly — using only raw OS services, no C library.
HLASM Version (z/OS)
HELLO CSECT
STM R14,R12,12(R13) Save caller's registers
BALR R12,0 Establish base register
USING *,R12 Tell assembler R12 is base
ST R13,SAVEAREA+4 Chain save areas
LA R13,SAVEAREA Point R13 to our save area
*
WTO 'Hello from HLASM!' Issue write-to-operator message
*
L R13,SAVEAREA+4 Restore caller's save area pointer
RETURN (14,12),RC=0 Restore registers and return
*
SAVEAREA DS 18F 72-byte register save area
END HELLOWhat to observe: After a successful run, the message appears in the job log. The return code (RC) in the job output should be 0. Any non-zero ABEND code means something in the linkage went wrong.
x86-64 Version (Linux NASM)
section .data
msg db "Hello from x86-64!", 10
msglen equ $ - msg
section .text
global _start
_start:
mov rax, 1 ; sys_write
mov rdi, 1 ; stdout
mov rsi, msg
mov rdx, msglen
syscall
mov rax, 60 ; sys_exit
xor rdi, rdi ; exit code 0
syscallnasm -f elf64 hello.asm -o hello.o && ld -o hello hello.o && ./helloAArch64 Version (Linux GNU AS)
.section .data
msg: .ascii "Hello from ARM64!\n"
msglen = . - msg
.section .text
.global _start
_start:
mov x8, #64 // sys_write
mov x0, #1 // stdout
adr x1, msg
mov x2, #msglen
svc #0
mov x8, #93 // sys_exit
mov x0, #0
svc #0Project 2 — Integer Arithmetic Calculator
Skills: Arithmetic instructions, register manipulation, multiple operations in sequence.
Write a program that computes (A + B) * C - D for hardcoded values and prints the result.
HLASM Version
CALC CSECT
STM R14,R12,12(R13)
BALR R12,0
USING *,R12
ST R13,SAVEAREA+4
LA R13,SAVEAREA
*
* Compute (10 + 20) * 3 - 5 = 85
L R2,=F'10' Load A = 10
A R2,=F'20' R2 = A + B = 30
M R2,=F'3' R3:R2 = R2 * 3 = 90 (M uses even/odd pair)
* Note: M R2,... uses R2 as even register; result is in R3
S R3,=F'5' R3 = 90 - 5 = 85
*
* Convert result to printable decimal and display
CVD R3,PACKED Convert binary to packed decimal
UNPK DISPLAY,PACKED Unpack to zoned decimal
OI DISPLAY+7,X'F0' Fix sign byte for display
WTO MF=(E,WTOMSG) Display result
*
L R13,SAVEAREA+4
RETURN (14,12),RC=0
*
PACKED DS D
DISPLAY DS CL8
WTOMSG WTO ' ',MF=L (length and text filled at runtime)
SAVEAREA DS 18F
LTORG
END CALCx86-64 Version
section .bss
result_buf resb 32
section .text
global _start
_start:
; compute (10 + 20) * 3 - 5
mov rax, 10
add rax, 20 ; rax = 30
imul rax, 3 ; rax = 90
sub rax, 5 ; rax = 85
; convert integer to ASCII string and write
lea rdi, [result_buf + 31]
mov byte [rdi], 10 ; newline at end
dec rdi
mov rcx, rax ; value to convert
mov rbx, 10 ; divisor
.convert:
xor rdx, rdx
div rbx ; rax = quotient, rdx = remainder (digit)
add dl, '0' ; convert digit to ASCII
mov [rdi], dl
dec rdi
test rax, rax
jnz .convert
inc rdi ; rdi now points to first digit
; write result
lea rsi, [result_buf + 31]
inc rsi ; past the newline? No — compute length
lea rdx, [result_buf + 32]
sub rdx, rdi ; length = end - start
mov rax, 1
mov rdi_save, rdi
mov rdi, 1
mov rsi, rdi_save
syscall
mov rax, 60
xor rdi, rdi
syscallProject 3 — String Length Function
Skills: Loops, memory traversal, subroutine with proper calling convention.
Implement your own strlen — traverse a null-terminated string byte by byte and return the length.
HLASM Version
* STRLEN — returns length of null-terminated string in R0
* Input: R1 = address of string
* Output: R0 = length (excluding null terminator)
STRLEN CSECT
STM R14,R12,12(R13)
BALR R12,0
USING *,R12
ST R13,SAVE+4
LA R13,SAVE
*
LR R3,R1 R3 = string pointer
SR R0,R0 R0 = length counter = 0
*
LOOP CLI 0(R3),X'00' Is current byte null (X'00')?
BE DONE Yes — done
LA R3,1(,R3) Advance pointer by 1
LA R0,1(,R0) Increment length
B LOOP
*
DONE L R13,SAVE+4
RETURN (14,12),RC=0
*
SAVE DS 18F
END STRLENx86-64 Version
; strlen: RDI = string pointer, returns length in RAX
global strlen_asm
strlen_asm:
xor rax, rax ; length = 0
.loop:
cmp byte [rdi + rax], 0 ; is current byte null?
je .done
inc rax
jmp .loop
.done:
ret// Test from C:
extern long strlen_asm(const char *s);
#include <stdio.h>
int main() {
printf("%ld\n", strlen_asm("Hello")); // should print 5
}AArch64 Version
// strlen_arm: X0 = string pointer, returns length in X0
.global strlen_arm
strlen_arm:
mov x1, x0 // save original pointer
mov x2, #0 // length counter
.loop:
ldrb w3, [x1, x2] // load byte at x1 + x2
cbz w3, .done // if zero, done
add x2, x2, #1
b .loop
.done:
mov x0, x2 // return length in X0
retProject 4 — Array Sum and Maximum
Skills: Arrays, loops with index, comparison, multiple subroutines.
Given an array of signed integers, compute the sum and find the maximum value.
x86-64 Version (annotated fully)
section .data
array dq -5, 12, 7, -3, 42, 18, 0, 9 ; 8 elements
count equ 8
section .bss
sum_out resq 1
max_out resq 1
section .text
global _start
_start:
; --- compute sum ---
xor rax, rax ; sum = 0
lea rsi, [array]
mov rcx, count
.sum_loop:
add rax, [rsi] ; sum += current element
add rsi, 8 ; advance 8 bytes (64-bit element)
dec rcx
jnz .sum_loop
mov [sum_out], rax
; --- find maximum ---
lea rsi, [array]
mov rcx, count
mov rbx, [rsi] ; max = first element
add rsi, 8
dec rcx
.max_loop:
mov rax, [rsi]
cmp rax, rbx
jle .no_update ; if current <= max, skip
mov rbx, rax ; update max
.no_update:
add rsi, 8
dec rcx
jnz .max_loop
mov [max_out], rbx
mov rax, 60
xor rdi, rdi
syscallProject 5 — Simple File Read (x86-64 Linux)
Skills: System calls for file I/O (open, read, write, close), error handling.
Read a text file and write its contents to stdout.
section .data
filename db "/etc/hostname", 0 ; null-terminated filename
section .bss
fd resq 1 ; file descriptor storage
buffer resb 4096 ; read buffer
section .text
global _start
_start:
; open file
mov rax, 2 ; sys_open
lea rdi, [filename]
xor rsi, rsi ; O_RDONLY = 0
xor rdx, rdx ; mode = 0
syscall
test rax, rax
js .error ; negative return = error
mov [fd], rax
; read file
mov rax, 0 ; sys_read
mov rdi, [fd]
lea rsi, [buffer]
mov rdx, 4096
syscall
mov r12, rax ; save bytes read
; write to stdout
mov rax, 1 ; sys_write
mov rdi, 1
lea rsi, [buffer]
mov rdx, r12
syscall
; close file
mov rax, 3 ; sys_close
mov rdi, [fd]
syscall
.exit:
mov rax, 60
xor rdi, rdi
syscall
.error:
mov rax, 60
mov rdi, 1 ; exit code 1 = error
syscallProject 6 — FizzBuzz in HLASM
Skills: Loops, modulo (remainder), conditional branching, multiple conditions.
Print "Fizz" for multiples of 3, "Buzz" for multiples of 5, "FizzBuzz" for multiples of 15, and the number otherwise — from 1 to 30.
FIZZBUZZ CSECT
STM R14,R12,12(R13)
BALR R12,0
USING *,R12
ST R13,SAVE+4
LA R13,SAVE
*
LA R6,1 Counter: start at 1
MAINLOOP C R6,=F'30' Done at 30?
BH DONE
*
* Test for FizzBuzz (mod 15 = 0)
LR R7,R6
SRDA R7,32 Sign-extend R7 into R7:R8 pair
D R7,=F'15' R7 = R6 mod 15
LTR R7,R7
BNZ TRYFIZZ
WTO 'FizzBuzz'
B NEXT
*
TRYFIZZ LR R7,R6
SRDA R7,32
D R7,=F'3'
LTR R7,R7
BNZ TRYBUZZ
WTO 'Fizz'
B NEXT
*
TRYBUZZ LR R7,R6
SRDA R7,32
D R7,=F'5'
LTR R7,R7
BNZ PRINTNUM
WTO 'Buzz'
B NEXT
*
PRINTNUM CVD R6,PACKED
UNPK NUMOUT,PACKED
OI NUMOUT+7,X'F0'
WTO MF=(E,NUMWTO)
*
NEXT LA R6,1(,R6)
B MAINLOOP
*
DONE L R13,SAVE+4
RETURN (14,12),RC=0
*
PACKED DS D
NUMOUT DS CL8
NUMWTO WTO ' ',MF=L
SAVE DS 18F
LTORG
END FIZZBUZZProgression Summary
| Project | Key Skills | Difficulty |
|---|---|---|
| 1. Hello World | OS I/O, entry/exit, linkage | Beginner |
| 2. Calculator | Arithmetic, integer-to-string conversion | Beginner |
| 3. strlen | Loops, byte addressing, subroutine | Beginner+ |
| 4. Array sum/max | Array traversal, comparison, two loops | Intermediate |
| 5. File read | Multiple syscalls, error handling, buffers | Intermediate |
| 6. FizzBuzz | Division/remainder, multi-condition branching | Intermediate |
Complete all six before moving to the capstone project. Each one introduces a pattern — I/O, arithmetic, memory traversal, subroutines — that the capstone combines.
Continue to the HLASM and Assembly Language Capstone Project to apply everything in a single substantial program.
