Skip to content

Latest commit

 

History

History
247 lines (185 loc) · 8.04 KB

README.md

File metadata and controls

247 lines (185 loc) · 8.04 KB

Advent of Code Day 1

  • Language of choice: AArch64 Assembly.
  • Difficulty: Yes. (I'm a C# developer...)
  • Final binary size: 5896 bytes.
  • Test hardware: Raspberry Pi 4.
  • Program runtime: ~4ms.

Contents

Requirements

I'm not sure why you'd want to, but if you do want to run this you'll need a couple of things.

Input Data

The input data can be obtained from the Day 1 Advent of Code page. You'll need to sign in to get a puzzle input.

The path to the puzzle input is passed in as an argument to the program, so it's easiest to put it next to main.s.

Warning

At some point throughout the AoC 2024, the line ending style of the input files was changed from CR LF to LF. You will need to modify main.s if you have the earlier CR LF style. See section below for more details.

Input File Line Ending Modification

I've updated my day 1 solution after the fact to work with input files downloaded going forwards (which contain the LF line ending). However, if you have one of the original files you will need to modify main.s to get the correct answer (or just re-download the input file...). In the Constants section at the top of the program, modify the following lines from this:

// Program constants.
.equ INPUT_SIZE, 4000       // The number of bytes in each input data column.
.equ LINE_BUFFER_SIZE, 14   // Number of bytes per line (LF line endings).
.equ PRINT_BUFFER_SIZE, 32  // The number of bytes to use as a print buffer.

To this:

// Program constants.
.equ INPUT_SIZE, 4000       // The number of bytes in each input data column.
.equ LINE_BUFFER_SIZE, 15   // Number of bytes per line (CR LF line endings).
.equ PRINT_BUFFER_SIZE, 32  // The number of bytes to use as a print buffer.

When using LF line endings instead of CR LF, one less byte is used in each line. Since the program targets the newer LF endings, just increasing the line buffer size by a byte gets the program working with the original input files.

I know this seems like a bodge fix, and that's because it is. I'd already finished this challenge by the time this change was made and honestly my brain will melt if I have to write any more assembly.

Linux

This will only work if you have Linux of some description. Because it's written in aarch64 assembly, you'll either need native aarch64 hardware or to emulate it. A Raspberry Pi would be great here (Raspberry Pi Zero 2 / Raspberry Pi 3 onwards are 64-bit), but I've chosen to use QEMU to emulate it to make it more accessible.

The reason that Linux is a requirement, is that I've chosen to use User Mode Emulation, which is only supported on Linux.

Don't fret though! On Windows you can easily install QEMU in Windows Subsystem for Linux. I've chosen to use Debian in WSL for this challenge.

If you've freshly installed Debian in WSL, it might be worth doing an update before moving on:

sudo apt update && sudo apt dist-upgrade

QEMU User Space Emulator

If you're not on native aarch64 hardware, install the QEMU User Space Emulator using your package manager. For example, on Debian:

sudo apt install qemu-user

Assembler and Linker

If you're on native aarch64 hardware, the native binutils will be fine. If you're not, you'll need to install binutils-aarch64-linux-gnu. This can be done in Debian using apt:

sudo apt install binutils-aarch64-linux-gnu

(Optional) Debugger

If you need to debug, you'll need either gdb on native aarch64 hardware, or gdb-multiarch if you're using QEMU. This can be installed as follows:

sudo apt install gdb-multiarch

Just

Instead of using a Makefile, I've decided to try out just on the recommendation of Dom. Check the GitHub page for install instructions on your OS.

Getting Started

The justfile contains the basic recipes to build the main executable. For a complete list of recipes, just type just!

 ~#@❯ just
just --list
Available recipes:
    build # (Default) build without debugging information.
    clean # Clean up any generated files.
    debug # Build with debugging information, and launches gdb after building.

To build the program, just run just build. You should then be able to run the binary using qemu-aarch64 main.

For it to run successfully, you'll need to supply the path to the input file using the -f option:

qemu-aarch64 main -f input.txt

You can also view the program usage information by supplying only the -h flag:

$ qemu-aarch64 main -h
Usage: ./main -f <input_file>

Debugging

This is more for my own notes... Maybe I'll re-discover this at some point and need it...

Checking Return Codes

There are some return codes defined using the .equ directives in main.s. After running the program, you can use echo $? to print the return code and compare it to these values to see if it indicates an error.

Viewing System Call Logs (QEMU)

Before jumping in with gdb, it is sometimes helpful to log the system calls from QEMU to see if it helps pinpoint a problem. To do this, simply build the binary as normal, but run it with qemu-aarch64 -strace main in place of qemu-aarch64 main, supplying the same arguments as usual. This will show information about any syscalls made by the program, and any return values from the calls.

This can also be used to view the return codes, as it shows the exit syscall and return value:

$ qemu-aarch64 -strace main -f
2088 write(1,0x4105c9,36)Error: Invalid arguments provided.
 = 36
2088 write(1,0x4105ed,31)Usage: ./main -f <input_file>
 = 31
2088 exit(1)

Building with Debug Information

To build the object files with debug information, use the just debug recipe. This will include the debug information in the object files, and also ensure that the binary is up to date.

In another shell instance, you can then launch the binary in QEMU, and get it to wait for gdb to connect with qemu-aarch64 -g 2001 main -f input.txt, supplying arguments to the program as normal.

Since the just debug recipe automatically launches gdb, you should then be able to enter target remote localhost:2001 into the gdb window to connect.

Using gdb to Display Variables

Once in the program, set breakpoints as normal with break, and use continue, stepi, and nexti as usual to get to a point of interest.

The following commands can then be used within gdb to inspect the buffers that store each column of data:

Store the first 6 numbers in both data columns (used for test input):

x/6uw &left_column
x/6uw &right_column

View the 4 elements in the middle of both columns (memory location is incremented by 1992 as column is 4000 bytes long, grouped into 4-byte uints, 0-indexed):

x/4uw ((char*)&left_column + 1992)
x/4uw ((char*)&right_column + 1992)

Display the entire of the left and right column:

x/1000uw &left_column
x/1000uw &right_column