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

TT
Emily Ross
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)

hlasm
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   HELLO

What 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)

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
    syscall
bash
nasm -f elf64 hello.asm -o hello.o && ld -o hello hello.o && ./hello

AArch64 Version (Linux GNU AS)

asm
.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     #0

Project 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

hlasm
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   CALC

x86-64 Version

nasm
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
    syscall

Project 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

hlasm
*        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   STRLEN

x86-64 Version

nasm
; 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
c
// 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

asm
// 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
    ret

Project 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)

nasm
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
    syscall

Project 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.

nasm
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
    syscall

Project 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.

hlasm
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   FIZZBUZZ

Progression Summary

ProjectKey SkillsDifficulty
1. Hello WorldOS I/O, entry/exit, linkageBeginner
2. CalculatorArithmetic, integer-to-string conversionBeginner
3. strlenLoops, byte addressing, subroutineBeginner+
4. Array sum/maxArray traversal, comparison, two loopsIntermediate
5. File readMultiple syscalls, error handling, buffersIntermediate
6. FizzBuzzDivision/remainder, multi-condition branchingIntermediate

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.