Fixing my Old OS Assignment with Machine Code Hacks

Back in 2012 I took an Operating Systems course, the main assessment of which was a 12 week pair assignment implementing a user-level OS personality for the seL4 microkernel. I recently got the urge to boot up our little OS. I saved the contents of my university computer system home directory before I graduated so I still have a copy of the code, and fortunately I didn’t make clean the last time I worked on it so the bootable (at least in theory!) disk image is still in the project directory.

Also fortunately my project partner somehow forgot to return the hardware loaned to them by the university and over the following years it ended up in one of my (ahem) several boxes of junk that I’ll definitely do something with someday.

And for this Storage Link for USB 2.0 Disk Drives, a.k.a NSLU2, affectionately referred to as the Slug, today was that day.

My Network Storage Link for USB 2.0 Disk Drives

Powering it On

Plugging it into power and switching it on illuminated the power indicator LED. There’s no screen or input devices, so all interaction will be via an aftermarket USB serial port. In the image below the USB serial port is the left-most port which was clearly made by a power tool.

Back of the slug showing the aftermarket serial port

Plugging it into my computer with a USB cable causes the serial device to show up in the output of dmesg:

[138157.363235] usb 1-5: new full-speed USB device number 18 using xhci_hcd
[138157.511474] usb 1-5: New USB device found, idVendor=0403, idProduct=6001, bcdDevice= 6.00
[138157.511481] usb 1-5: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[138157.511483] usb 1-5: Product: USB <-> UNSW-AOS
[138157.511485] usb 1-5: Manufacturer: FTDI
[138157.511487] usb 1-5: SerialNumber: 12345678
[138157.516506] ftdi_sio 1-5:1.0: FTDI USB Serial Device converter detected
[138157.516539] usb 1-5: Detected FT232R
[138157.521742] usb 1-5: FTDI USB Serial Device converter now attached to ttyUSB0

Running picocom to open a terminal on /dev/ttyUSB0:

$ picocom /dev/ttyUSB0
picocom v2024-07

port is        : /dev/ttyUSB0
flowcontrol    : none
baudrate is    : 9600
parity is      : none
databits are   : 8
stopbits are   : 1
txdelay is     : 0 ns
escape is      : C-a
local echo is  : no
noinit is      : no
noreset is     : no
hangup is      : no
nolock is      : no
send_cmd is    : /nix/store/cmpvp7nx52dmbjaqkiscgkdzqzaikmaz-lrzsz-0.12.20/bin/sz -vv
receive_cmd is : /nix/store/cmpvp7nx52dmbjaqkiscgkdzqzaikmaz-lrzsz-0.12.20/bin/rz -vv -E
imap is        :
omap is        :
emap is        : crcrlf,delbs,
logfile is     : none
initstring     : none
exit_after is  : not set
exit is        : no
minimal cmds is: no

Type [C-a] [C-h] to see available commands
Terminal ready

…but there was no output.

I couldn’t remember the process we used to load our OS project onto the slug. I was hoping that it would already be loaded on there, and powering it on would print something to the terminal, but no luck there.

On a whim I searched my entire old uni home directory for “ttyUSB0” since I assumed the project would be transferred over the serial port and thus I would have some script that referenced the name of the serial device. This assumption would later turn out to be false but I did find something helpful in my search:

$ grep -rni 'ttyUSB0'
nslu2-util/nslu2.c:67:  char defport[] = "/dev/ttyUSB0";

The nslu2-util program is a command-line utility for starting, stopping, and resetting the slug. Students in the OS course were given a copy of the tool. It’s a tiny C project with a Makefile and running make built an nslu2 executable:

$ ./nslu2 -h
Usage: nslu2 <-p port> up|down|reset

So I ran ./nslu2 reset and this caused something to appear on my picocom serial terminal:

Terminal ready
>R+;#23/#766'2"*""3+":

Getting garbage text over a serial port usually means the baudrate is wrong. The default baudrate was 9600. After trying 115200 instead and resetting the slug I could read the output:

$ picocom -b 115200 /dev/ttyUSB0
...
Terminal ready
++
Ethernet eth0: MAC address 00:14:bf:68:99:03
IP: 192.168.168.2/255.255.255.0, Gateway: 192.168.168.1
Default server: 0.0.0.0, DNS server IP: 0.0.0.0

RedBoot(tm) bootstrap and debug environment [ROMRAM]
Red Hat certified release, version 1.92 - built 22:30:50, Jul 22 2008

Platform: IXDP425 Development Platform (XScale)
Copyright (C) 2000, 2001, 2002, Red Hat, Inc.

RAM: 0x00000000-0x02000000, 0x000724b0-0x01ff3000 available
FLASH: 0x50000000 - 0x50800000, 64 blocks of 0x00020000 bytes each.
== Executing boot script in 2.000 seconds - enter ^C to abort
RedBoot> load -r -v -b 0x00100000 -h 192.168.168.1 bootimg.bin;go
Unable to reach host 192.168.168.1 (192.168.168.1)

RedBoot

The printout indicates that the slug is running a default boot script:

load -r -v -b 0x00100000 -h 192.168.168.1 bootimg.bin;go

I don’t remember interacting with RedBoot as a student, this command must have once caused our OS project to start running. That script would have been set up by the people running the OS course; it would be different or absent on a brand new slug.

The line above is also interesting:

== Executing boot script in 2.000 seconds - enter ^C to abort

Resetting the slug and hitting ctrl+c within 2 seconds of seeing that starts an interactive shell. Luckily this shell accepts a help command. I’ll paste the entire output here in case it’s helpful to someone in the future:

RedBoot> help
go to assign mode
   assign
Set/Query the system console baud rate
   baudrate [-b <rate>]
sercomm boot flow
   boot
Manage machine caches
   cache [ON | OFF]
Display/switch console channel
   channel [-1|<channel number>]
Compute a 32bit checksum [POSIX algorithm] for a range of memory
   cksum -b <location> -l <length>
Display (hex dump) a range of memory
   dump -b <location> [-l <length>] [-s] [-1|2|4]
Execute an image - with MMU off
   exec [-w timeout] [-b <load addr> [-l <length>]]
        [-r <ramdisk addr> [-s <ramdisk length>]]
        [-c "kernel command line"] [<entry_point>]
Manage FLASH images
   fis {cmds}
Execute code at a location
   go [-w <timeout>] [entry]
Help about help?
   help [<topic>]
Set/change IP addresses
   ip_address [-l <local_ip_address>] [-h <server_address>]
Load a file
   load [-r] [-v] [-d] [-h <host>] [-m <varies>] [-c <channel_number>]
        [-b <base_address>] <file_name>
Compare two blocks of memory
   mcmp -s <location> -d <location> -l <length> [-1|-2|-4]
Fill a block of memory with a pattern
   mfill -b <location> -l <length> -p <pattern> [-1|-2|-4]
move kernel&ramdisk to ram
   move
Network connectivity test
   ping [-v] [-n <count>] [-l <length>] [-t <timeout>] [-r <rate>]
        [-i <IP_addr>] -h <IP_addr>
Reset the system
   reset
Set/Read MAC address for NPE ethernet ports
   set_npe_mac [-p <portnum>] [xx:xx:xx:xx:xx:xx]
go to upgrade mode
   upgrade
Display RedBoot version information
   version
Display (hex dump) a range of memory
   x -b <location> [-l <length>] [-s] [-1|2|4]

This tells us that the default boot script is running the load command to download the OS image over the network and then the go command to boot it. This unlocked a memory of running some software (I forget exactly what) on my laptop to serve the OS image over the network and plugging the slug directly into my laptop with an Ethernet cable. Based on the load -r -v -b 0x00100000 -h 192.168.168.1 bootimg.bin command, in this setup my laptop would have had the IP address 192.168.168.1, and the bootable OS image was in a file named bootimg.bin.

Searching my old home directory for a bootimg.bin and fortunately I still have one:

$  find . -name bootimg.bin
./aos/aoshg/images/bootimg.bin

But the 192.168.168.1 address isn’t going to work. My home network uses addresses in the 192.168.1.* range, so unless I set up a computer with a spare Ethernet port as a router and plug the slug directly into it, I’m going to have to change the network configuration of the slug. Fortunately there’s a command for that:

Set/change IP addresses
   ip_address [-l <local_ip_address>] [-h <server_address>]

Running ip_address with no arguments prints the current configuration:

RedBoot> ip_addr
IP: 192.168.168.2/255.255.255.0, Gateway: 192.168.168.1
Default server: 0.0.0.0, DNS server IP: 0.0.0.0

And I can change the address with:

RedBoot> ip_addr -l 192.168.1.22
IP: 192.168.1.22/255.255.255.0, Gateway: 192.168.168.1
Default server: 0.0.0.0, DNS server IP: 0.0.0.0

I chose 192.168.1.22 arbitrarily among the unused IP addresses on my home network. After this change I could ping that address from my computer, and also use RedBoot’s own ping command to ping my computer (whose address is 192.168.1.7):

RedBoot> ping -h 192.168.1.7
Network PING - from 192.168.1.22 to 192.168.1.7
PING - received 10 of 10 expected

So the slug is on my home network now and it can see my development machine where bootimg.bin is located. Now I just need a way to send bootimg.bin over the network to the slug. The documentation printed by help about the load command doesn’t go into much detail about what network protocol RedBoot will use to transfer a file:

Load a file
   load [-r] [-v] [-d] [-h <host>] [-m <varies>] [-c <channel_number>]
        [-b <base_address>] <file_name>

I found some better documentation online which explained that the -m option accepts TFTP or HTTP. There’s no way to specify the port number. For some reason this caused me to prefer TFTP, possibly because I’m so used to HTTP servers running on ports besides the default (80) when running websites locally, but not so for TFTP. I actually implemented a simple send-only TFTP server a few years ago when I got frustrated that all the TFTP servers I could find wouldn’t let me just serve files out of the current directory, so that’s what I’ll be using here.

To install it:

$ cargo install tftp-ro

And then I ran it from the directory containing bootimg.bin like:

$ sudo tftp-ro -v -d .
[2025-08-24T07:46:46Z INFO  tftp_ro] Listening on 0.0.0.0:69
[2025-08-24T07:46:46Z INFO  tftp_ro] Serving files out of .

Then I tried downloading the file to the slug:

RedBoot> load -r -v -b 0x00100000 -h 192.168.1.7 -m TFTP bootimg.bin
Can't load 'bootimg.bin': operation timed out

I’m not sure if this is a bug or performance issue with my TFTP server but my quick fix was to compress the image. RedBoot’s load command takes a flag -d which decompresses the image assuming it’s compressed with gzip.

So I compressed the image:

$ gzip -k bootimg.bin

…and tried load-ing it again:

RedBoot> load -r -v -d -b 0x00100000 -h 192.168.1.7 -m TFTP bootimg.bin.gz
\
Raw file loaded 0x00100000-0x0017bfff, assumed entry at 0x00100000

Success! RedBoot remembers that we loaded that image starting at address 0x00100000, so we can now run the command go to start executing at that address:

RedBoot> go
ELF-loader image started:   paddr=[0x00100000..0x0017c000]
ELF-loading kernel image:   paddr=[0x01000000..0x01039154] vaddr=[0xf0000000..0xf0039154] v_entry=0xf0000000
ELF-loading userland image: paddr=[0x00407000..0x005d0000] vaddr=[0x00007000..0x001d0000] v_entry=0x000080b4
Enabling MMU and paging
Jumping to kernel-image entry point
Bootstrapping kernel

SOS Starting...
Info Page:  0x001d1000
IPC Buffer: 0x001d0000
Node ID: 0 (of 1)
IOPT levels: 0
Init cnode size bits: 12

Cap details:
Type              Start      End
Empty             0x00000266 0x00001000
Shared frames     0x00000000 0x00000000
User image frames 0x0000000c 0x000001d5
User image PTs    0x000001d5 0x000001d7
Untypeds          0x000001d7 0x000001f7

Untyped details:
Untyped Slot       Paddr      Bits
  0     0x000001d7 0x01000000 12
  1     0x000001d8 0x01001000 12
  2     0x000001d9 0x01002000 12
  3     0x000001da 0x01003000 12
  4     0x000001db 0x01004000 12
  5     0x000001dc 0x01005000 12
  6     0x000001dd 0x01006000 12
  7     0x000001de 0x01007000 12
  8     0x000001df 0x01008000 12
  9     0x000001e0 0x01009000 12
 10     0x000001e1 0x0100a000 12
 11     0x000001e2 0x0100b000 12
 12     0x000001e3 0x0100c000 12
 13     0x000001e4 0x0100d000 12
 14     0x000001e5 0x0100e000 12
 15     0x000001e6 0x0100f000 12
 16     0x000001e7 0x01ff0000 13
 17     0x000001e8 0x01064000 14
 18     0x000001e9 0x01068000 15
 19     0x000001ea 0x01070000 16
 20     0x000001eb 0x01fe0000 16
 21     0x000001ec 0x01fc0000 17
 22     0x000001ed 0x01f80000 18
 23     0x000001ee 0x01080000 19
 24     0x000001ef 0x01f00000 19
 25     0x000001f0 0x01100000 20
 26     0x000001f1 0x01e00000 20
 27     0x000001f2 0x01200000 21
 28     0x000001f3 0x01c00000 21
 29     0x000001f4 0x01400000 22
 30     0x000001f5 0x01800000 22
 31     0x000001f6 0x0103a000 13

Num device regions: 13
Device Addr     Size Start      End
 0 0xc8001000 12 0x000001f7 0x000001f8
 1 0xc8002000 12 0x000001f8 0x000001f9
 2 0xc8004000 12 0x000001f9 0x000001fa
 3 0xc8005000 12 0x000001fa 0x000001fb
 4 0xc8006000 12 0x000001fb 0x000001fc
 5 0xc8007000 12 0x000001fc 0x000001fd
 6 0xc8008000 12 0x000001fd 0x000001fe
 7 0xc8009000 12 0x000001fe 0x000001ff
 8 0xc800a000 12 0x000001ff 0x00000200
 9 0xc800b000 12 0x00000200 0x00000201
10 0x50000000 12 0x00000201 0x00000261
11 0x60000000 12 0x00000261 0x00000265
12 0xc4000000 12 0x00000265 0x00000266
-----------------------------------------

Parsing Dite data:
Found section              sos at index 0 (0x00007018)
        flags 0
        entry 80b4
        base 0x00008000
        size 1838928
        magic (nil)
        pbase 0x00008000
Found section         tty_test at index 1 (0x00007040)
        flags 0
        entry 0
        base 0x001c9000
        size 27863
        magic (nil)
        pbase 0x001c9000
DMA memory is at 0x10000000 to 0x10400000
Initialising the frame table!
Made a frame, physical address 181d000, cap 1646
Made a frame, physical address 181e000, cap 1647
Made a frame, physical address 181f000, cap 1648
...
Made a frame, physical address 18e2000, cap 1843
Made a frame, physical address 18e3000, cap 1844
Made a frame, physical address 18e4000, cap 1845
Initialising the pager!
Leaking a frame.
Mapping our leaked frame to the MASTER_PAGEDIR address (leaking a hardware page table, unavoidable)
Leaking a frame.
Mapping our leaked frame to the MASTER_PAGETABLE address (probably not but possibly also leaking a hardware page table)
Creating the original 4 capdirs
We are mapping in a kernel page at vaddr e3ffa000 to pd 3
We are mapping in a kernel page at vaddr e4000000 to pd 3
Trying to map in a hardware page table at vaddr e4000000 for pd 3
We are mapping in a kernel page at vaddr e3ffb000 to pd 3
We are mapping in a kernel page at vaddr e3ffc000 to pd 3
We are mapping in a kernel page at vaddr e3ffd000 to pd 3
Adding to capdir (cap dir 0 is 0xe3ffa000)
Pager initialised!  Moving morecore across to pager-based allocation!

Starting network_init

        Network Mask: 225.225.225.0
  Gateway IP Address: 192.168.168.1
    Local IP Address: 192.168.168.2

Device memory at 0xb8005000 to 0xb8006000
Device memory at 0x40000000 to 0x40060000
Device memory at 0x50000000 to 0x50004000
Device memory at 0xb8006000 to 0xb8007000
Device memory at 0xb8007000 to 0xb8008000
Device memory at 0xb8008000 to 0xb8009000
Undelivered IRQ: 2
Device memory at 0xb4000000 to 0xb4001000
Device memory at 0xb8009000 to 0xb800a000
Device memory at 0xb800a000 to 0xb800b000
waiting for PHY 0 to become ready...

Network failed to respond to ARP query

seL4 root server abortedDebug halt syscall from user thread 0xf0063e00
halting...

This is debug output from our OS project which is pretty exciting. It’s running! It’s failing an assertion after unsuccessfully attempting to connect to the network. It has the same IP addresses hard-coded into it as the initial boot script, so can’t connect to my home network, and the code is written in such a way that doesn’t let it proceed to boot without network access.

We’ll get to fixing this, but first let’s talk about the goal of this endeavour.

Call Me Maybe

I built an Easter egg into the OS. The slug has a buzzer, and there’s a particular bit in a hardware register that can be toggled at a given frequency to buzz at that frequency. I wrote this code in mid 2012 and so the obvious song to play according to 20 year old me was Call Me Maybe by Carly Rae Jepsen. I remember implementing a device driver with a unix-style “everything’s a file” interface, where opening the file /dev/maybe would lock up the OS while it buzzed the hottest song of 2012.

Unfortunately it appears the snapshot of the project I stored on the university server was not its final form. I did most of the work for this project on a netbook (remember netbooks?!) which has sadly been lost to the ages. The implementation of /dev/maybe is not in this version, but it still has the logic to play the song and print the lyrics to the terminal:

void call_me_maybe(int beat_len) {

    printf("Hey I just met you\n");
    udelay(beat_len);
    tone(NOTE_G4, beat_len);
    tone(NOTE_B3, beat_len/4);
    tone(NOTE_D4, beat_len/2);
    tone(NOTE_G4, beat_len/4);
    tone(NOTE_G4, beat_len/4);
    tone(NOTE_D4, 3*beat_len/4);

    printf("And this is crazy\n");
    udelay(beat_len);
    udelay(beat_len/2);
    tone(NOTE_B3, beat_len/2);
    tone(NOTE_B3, beat_len/4);
    tone(NOTE_D4, beat_len/2);
    tone(NOTE_B4, beat_len/4);
    tone(NOTE_B4, beat_len/2);
    tone(NOTE_G4, beat_len/2);

    printf("But here's my number\n");
    udelay(beat_len);
    udelay(beat_len/2);
    tone(NOTE_G4, beat_len/2);
    tone(NOTE_B4, beat_len/2);
    tone(NOTE_C5, beat_len/2);
    tone(NOTE_B4, beat_len/2);
    tone(NOTE_G4, beat_len/2);

    printf("So call me maybe\n");
    udelay(beat_len);
    udelay(beat_len/2);
    tone(NOTE_G4, beat_len/2);
    tone(NOTE_B4, beat_len/2);
    tone(NOTE_A4, beat_len/2);
    tone(NOTE_A4, beat_len/2);
    tone(NOTE_G4, beat_len/2);
}

Rather than playing the song by reading a file, this version of the project would play it immediately after booting if a certain compile-time flag was set:

    if (I_JUST_MET_YOU) {
        call_me_maybe(500000);
    }

However the flag was not set:

#define I_JUST_MET_YOU 0

The C compiler was probably smart enough to elide the entire call to call_me_maybe since that condition is never true. Fortunately the presence of the song’s lyrics in the bootable image tells us that the call_me_maybe function did make it into the image despite never being called:

$ strings bootimg.bin | grep Hey
Hey I just met you

So once the network issue is fixed, my goal is going to be getting Call Me Maybe to play on the buzzer of my slug.

Why don’t you just call the call_me_maybe function?

Well…

Fixing the Network

I can’t build the project. I have the source code, and luckily I have a mostly working bootable image. I also have the object files for each source file which were built with debug symbols. But I don’t have the software necessary to change the source code and rebuild the image to incorporate those changes.

And I refuse to do the kind of archaeology that would be required to get a 32-bit ARM compiler toolchain and seL4 build scripts circa 2012 running on a modern OS. Getting all the tools working was hard enough back in 2012.

In order to get past the failing assertion we need the OS to choose an IP address in a valid range for my home network. Currently it tries to use 192.168.168.2:

        Network Mask: 225.225.225.0
  Gateway IP Address: 192.168.168.1
    Local IP Address: 192.168.168.2

The network information is a compile-time constant string which means these strings are literally present in the binary:

$ strings bootimg.bin | grep '192\.168'
192.168.168.1
192.168.168.2

To change them to suitable addresses, one can simply modify the binary directly! I don’t own a magnetized needle so instead I’ll use the tool hexedit.

The hexedit tool lets you print and modify the raw bytes in any type of file. When you run hexedit bootimg.bin it prints the hexadecimal and ASCII representations of the binary data in the file:

000000B0   E5 9F 03 24  E5 9F 13 24  E5 9F 23 24  EB 00 04 97  ...$...$..#$....
000000C0   E5 9F 03 20  EB 00 00 F6  E3 50 00 00  0A 00 00 02  ... .....P......
000000D0   E5 9F 03 14  EB 00 04 9F  EB 00 00 D1  E5 9F 43 04  ..............C.
000000E0   E1 A0 00 04  E3 A0 10 01  E2 8D 20 40  E2 8D 30 38  .......... @..08
000000F0   EB 00 01 F3  E1 A0 00 04  E3 A0 10 00  E2 8D 20 20  ..............
00000100   E2 8D 30 18  EB 00 01 EE  E1 A0 00 04  EB 00 01 DB  ..0.............
00000110   E1 A0 90 01  E5 9D 30 1C  E5 8D 30 00  E5 8D 10 04  ......0...0.....
00000120   E5 9F 02 C8  E5 9D 10 44  E5 9D 20 3C  E5 9D 30 24  .......D.. <..0$
00000130   EB 00 04 7A  E5 9F 62 A4  E5 9F A2 A4  E5 9D 00 44  ...z..b........D
00000140   E5 9D 10 3C  E2 41 10 01  E1 A0 20 06  E1 A0 30 0A  ...<.A.... ...0.
00000150   EB FF FF CC  E3 50 00 00  0A 00 00 02  E5 9F 02 90  .....P..........
00000160   EB 00 04 7C  EB 00 00 AE  E5 9F 02 78  E3 A0 10 01  ...|.......x....
00000170   EB 00 02 35  E3 50 00 00  1A 00 00 02  E5 9F 02 74  ...5.P.........t
00000180   EB 00 04 74  EB 00 00 A6  E5 9F 02 6C  EB 00 00 C4  ...t.......l....
00000190   E3 50 00 00  0A 00 00 02  E5 9F 02 60  EB 00 04 6D  .P.........`...m
000001A0   EB 00 00 9F  E5 9F 42 50  E1 A0 00 04  E3 A0 10 01  ......BP........
-**  bootimg.bin       --0x0/0x7C000--0%---------------------------------------

To find the IP addresses, we’ll be changing, search for a sequence of hexadecimal digits representing the ASCII encoding of part of the address.

Here’s an ASCII table so you can play along at home:

Dec Hex    Dec Hex    Dec Hex  Dec Hex  Dec Hex  Dec Hex   Dec Hex   Dec Hex
  0 00 NUL  16 10 DLE  32 20    48 30 0  64 40 @  80 50 P   96 60 `  112 70 p
  1 01 SOH  17 11 DC1  33 21 !  49 31 1  65 41 A  81 51 Q   97 61 a  113 71 q
  2 02 STX  18 12 DC2  34 22 "  50 32 2  66 42 B  82 52 R   98 62 b  114 72 r
  3 03 ETX  19 13 DC3  35 23 #  51 33 3  67 43 C  83 53 S   99 63 c  115 73 s
  4 04 EOT  20 14 DC4  36 24 $  52 34 4  68 44 D  84 54 T  100 64 d  116 74 t
  5 05 ENQ  21 15 NAK  37 25 %  53 35 5  69 45 E  85 55 U  101 65 e  117 75 u
  6 06 ACK  22 16 SYN  38 26 &  54 36 6  70 46 F  86 56 V  102 66 f  118 76 v
  7 07 BEL  23 17 ETB  39 27 '  55 37 7  71 47 G  87 57 W  103 67 g  119 77 w
  8 08 BS   24 18 CAN  40 28 (  56 38 8  72 48 H  88 58 X  104 68 h  120 78 x
  9 09 HT   25 19 EM   41 29 )  57 39 9  73 49 I  89 59 Y  105 69 i  121 79 y
 10 0A LF   26 1A SUB  42 2A *  58 3A :  74 4A J  90 5A Z  106 6A j  122 7A z
 11 0B VT   27 1B ESC  43 2B +  59 3B ;  75 4B K  91 5B [  107 6B k  123 7B {
 12 0C FF   28 1C FS   44 2C ,  60 3C <  76 4C L  92 5C \  108 6C l  124 7C |
 13 0D CR   29 1D GS   45 2D -  61 3D =  77 4D M  93 5D ]  109 6D m  125 7D }
 14 0E SO   30 1E RS   46 2E .  62 3E >  78 4E N  94 5E ^  110 6E n  126 7E ~
 15 0F SI   31 1F US   47 2F /  63 3F ?  79 4F O  95 5F _  111 6F o  127 7F DEL

For example to find 192, search for 0x313932:

0005F5E0   30 00 00 00  4E 65 74 77  6F 72 6B 20  4D 61 73 6B  0...Network Mask
0005F5F0   00 00 00 00  31 39 32 2E  31 36 38 2E  31 36 38 2E  ....192.168.168.
0005F600   31 00 00 00  47 61 74 65  77 61 79 20  49 50 20 41  1...Gateway IP A
0005F610   64 64 72 65  73 73 00 00  00 00 00 00  31 39 32 2E  ddress......192.
0005F620   31 36 38 2E  31 36 38 2E  32 00 00 00  4C 6F 63 61  168.168.2...Loca
0005F630   6C 20 49 50  20 41 64 64  72 65 73 73  00 00 00 00  l IP Address....
0005F640   00 00 00 00  6E 65 74 69  66 20 21 3D  20 4E 55 4C  ....netif != NUL
---  bootimg.bin       --0x5F5E4/0x7C000--77%----------------------------------

The ASCII representation of the data confirms that we’re in the right place.

The game is that all the changes to the binary file must be made by updating values in-place. It’s not possible to insert new bytes between existing bytes, nor to remove bytes. This is because parts of the code will refer to other parts of the code and static data (like these IP addresses) by their relative offset from one another. If we insert or remove bytes then that will change the relative position of all following bytes, causing references to no longer point to the correct place.

I changed the local and gateway addresses to 192.168.1.25 and 192.168.1.1 respectively, again choosing an arbitrary unused valid address for the local address and my router’s address as the gateway address. The new addresses are made up of fewer characters than the original addresses so they’ll fit in the allocated space. I overwrote the remaining characters of the original strings with 0s as these as null-terminated C strings (so technically I just needed to put a single 0 byte immediately after the end of each string).

0005F5E0   30 00 00 00  4E 65 74 77  6F 72 6B 20  4D 61 73 6B  0...Network Mask
0005F5F0   00 00 00 00  31 39 32 2E  31 36 38 2E  31 2E 31 00  ....192.168.1.1.
0005F600   00 00 00 00  47 61 74 65  77 61 79 20  49 50 20 41  ....Gateway IP A
0005F610   64 64 72 65  73 73 00 00  00 00 00 00  31 39 32 2E  ddress......192.
0005F620   31 36 38 2E  31 2E 32 35  00 00 00 00  4C 6F 63 61  168.1.25....Loca
0005F630   6C 20 49 50  20 41 64 64  72 65 73 73  00 00 00 00  l IP Address....
0005F640   00 00 00 00  6E 65 74 69  66 20 21 3D  20 4E 55 4C  ....netif != NUL
-**  bootimg.bin       --0x5F629/0x7C000--77%----------------------------------

After re-gzip-ing the image and resetting the slug, and rerunning all the RedBoot commands as before, the OS makes it past the network initialization successfully:

Starting network_init

        Network Mask: 225.225.225.0
  Gateway IP Address: 192.168.1.1
    Local IP Address: 192.168.1.25

Device memory at 0xb8005000 to 0xb8006000
Device memory at 0x40000000 to 0x40060000
Device memory at 0x50000000 to 0x50004000
Device memory at 0xb8006000 to 0xb8007000
Device memory at 0xb8007000 to 0xb8008000
Device memory at 0xb8008000 to 0xb8009000
Undelivered IRQ: 2
Device memory at 0xb4000000 to 0xb4001000
Device memory at 0xb8009000 to 0xb800a000
Device memory at 0xb800a000 to 0xb800b000
waiting for PHY 0 to become ready....

Mounting NFS
Error receiving time using UDP time protocol
Failed to initialise NFS

Setting up capabilities for timer...

Starting timer...
Undelivered IRQ: 5
[timer] starting timer!
[timer] base address: c8005000
[timer] frame count: 1
[timer] frame size: 4096
[timer] paging success
Getting timestamp...
[gpio] starting
[gpio] found region 0x001d13a8
[gpio] paging success

Starting "tty_test"...
Loading first process.  Trying to set up a page table!
init'd some startup stuff! - only_process_page_dir is at 0x000b9cb0, only_procss_vroot (sel4_pd) is 1985
 * Loading segment 00008000-->0000a3c0
We are mapping in a kernel page at vaddr 8000 to pd 1985
Trying to map in a hardware page table at vaddr 0 for pd 1985
We are mapping in a kernel page at vaddr d0008000 to pd 3
Trying to map in a hardware page table at vaddr d0000000 for pd 3
We are mapping in a kernel page at vaddr e4001000 to pd 3
We are mapping in a kernel page at vaddr 9000 to pd 1985
We are mapping in a kernel page at vaddr d0009000 to pd 3
We are mapping in a kernel page at vaddr a000 to pd 1985
We are mapping in a kernel page at vaddr d000a000 to pd 3
 * Loading segment 000123c0-->00013490
We are mapping in a kernel page at vaddr 12000 to pd 1985
We are mapping in a kernel page at vaddr d0012000 to pd 3
We are mapping in a kernel page at vaddr 13000 to pd 1985
We are mapping in a kernel page at vaddr d0013000 to pd 3
Mapping in a hardware page (vaddr 8ffff000, pd 1985)
We are mapping in a kernel page at vaddr 8ffff000 to pd 1985
Trying to map in a hardware page table at vaddr 8ff00000 for pd 1985
Paged in the stack frame.

SOS entering syscall loop
vm fault at 0x8ffdffd0, pc = 0x000083a0, Data fault
Mapping in a hardware page (vaddr 8ffdffd0, pd 1985)
We are mapping in a kernel page at vaddr 8ffdf000 to pd 1985
vm fault at 0x8ffe0fe8, pc = 0x000083ac, Data fault
Mapping in a hardware page (vaddr 8ffe0fe8, pd 1985)
We are mapping in a kernel page at vaddr 8ffe0000 to pd 1985
vm fault at 0x8ffe1fe8, pc = 0x000083ac, Data fault
Mapping in a hardware page (vaddr 8ffe1fe8, pd 1985)
...
We are mapping in a kernel page at vaddr 8fffc000 to pd 1985
vm fault at 0x8fffdfe8, pc = 0x000083ac, Data fault
Mapping in a hardware page (vaddr 8fffdfe8, pd 1985)
We are mapping in a kernel page at vaddr 8fffd000 to pd 1985
vm fault at 0x8fffefe8, pc = 0x000083ac, Data fault
Mapping in a hardware page (vaddr 8fffefe8, pd 1985)
We are mapping in a kernel page at vaddr 8fffe000 to pd 1985
brk syscall, tty_brk = 32498
vm fault at 0x0003249c, pc = 0x000096a4, Data fault
PANIC /home/steve/src/aos/aoshg/apps/sos/src/main.c-handle_page_fault:161 segmentation fault

Debug halt syscall from user thread 0xf0063e00
halting...

It still crashes eventually but that’s ok. Remember this is a snapshot of the project when it was half finished. What matters is that it prints the line:

SOS entering syscall loop

Looking at the main function:

    ...
    gpio_init(_boot_info);
    buzzer_init();
    if(I_JUST_MET_YOU) {
        call_me_maybe(500000);
    }

    /* Start the user application */
    start_first_process(TTY_NAME, _sos_ipc_ep_cap);

    /* Initialise the serial connection for tty_test */
    console_init();

    /* Wait on synchronous endpoint for IPC */
    dprintf(0, "\nSOS entering syscall loop\n");
    ...

The printout happens after the buzzer has been initialized. This means that all the necessary hardware initialization has taken place to play the song. So now we just need to call the call_me_maybe function.

Just calling the call_me_maybe function

To call a function we need to know its address. Specifically its virtual address. This is determined at runtime by how memory is mapped by the page table. The logic for setting up the page table up exists somewhere in the project, but trying to determine which virtual address maps to a particular offset into the bootable image without the ability to instrument the code or any debugging tooling sounds unpleasant.

No matter what approach I eventually take, I want to know where in the binary image call_me_maybe is located. To do this we’ll benefit from some slightly more sophisticated tools. It will be helpful to be able to use the debug symbols in object files to learn which sequences of instructions in the binary correspond to which functions in the source code. Short of setting up an ARM toolchain, I found that the objdump implementation that comes with LLVM (specifically the llvm derivation from nixpkgs) can disassemble the object files in the project well enough, despite them having been compiled for a foreign architecture (I’m using the x86_64 llvm package and these files were compiled for 32-bit ARM).

Since call_me_maybe is defined in buzzer.c, I ran llvm-objdump -d build/arm/nslu2/sos/src/buzzer.o. The relevant part of the output is:

00000054 <buzzer_init>:
      54: e3 a0 23 3a   blo     #9339788 <$d+0x8e815c>
      58: e5 9f 30 04   ldrteq  r9, [r0], #-4069
      5c: e5 83 20 00   eoreq   r8, r0, r5, ror #7
      60: e1 2f ff 1e   cdpne   p15, #15, c2, c15, c1, #7

00000064 <$d>:
      64:       00 00 00 00     .word   0x00000000

00000068 <tone>:
      68: e9 2d 40 f8   <unknown>
      6c: e1 a0 40 01   smlaltteq       r10, r0, r1, r0
      70: e1 a0 10 80   andshi  r10, r0, r1, ror #1
      74: e3 a0 09 3d   stclo   p0, c10, [r9, #-908]
      78: e2 80 0d 09   stmdbeq sp, {r1, r5, r6, r7, pc}
      7c: eb ff ff fe   cdp2    p15, #15, c15, c15, c11, #7
      80: e1 a0 60 00   rsbeq   r10, r0, r1, ror #1
      84: e1 a0 00 04   streq   r10, [r0], #-225
      88: e1 a0 10 86   ldrhi   r10, [r0], -r1, ror #1
      8c: eb ff ff fe   cdp2    p15, #15, c15, c15, c11, #7
      90: e2 50 70 00   rsbseq  r5, r0, r2, ror #1
      94: da 00 00 10   ldrdne  r0, r1, [r0], -r10
      98: e3 a0 40 00   subeq   r10, r0, r3, ror #1
      9c: e5 9f 50 3c   mrrclo  p15, #14, r9, r0, c5
      a0: e5 95 30 00   eorseq  r9, r0, r5, ror #11
      a4: e5 93 20 00   eoreq   r9, r0, r5, ror #7
      a8: e3 82 20 10   eorne   r8, r0, r3, ror #5
      ac: e5 83 20 00   eoreq   r8, r0, r5, ror #7
      b0: e1 a0 00 06   streq   r10, [r0], -r1, ror #1
      b4: eb ff ff d1   mvnsle  pc, r11, ror #31
      b8: e5 95 30 00   eorseq  r9, r0, r5, ror #11
      bc: e5 93 20 00   eoreq   r9, r0, r5, ror #7
      c0: e3 c2 20 10   eorne   r12, r0, r3, ror #5
      c4: e5 83 20 00   eoreq   r8, r0, r5, ror #7
      c8: e1 a0 00 06   streq   r10, [r0], -r1, ror #1
      cc: eb ff ff cb   blgt    #-84 <tone+0x18>
      d0: e2 84 40 01   smlaltteq       r8, r0, r2, r4
      d4: e1 57 00 04   streq   r5, [r0], #-2017
      d8: ca ff ff f0   <unknown>
      dc: e8 bd 80 f8   <unknown>

000000e0 <$d>:
      e0:       00 00 00 00     .word   0x00000000

000000e4 <call_me_maybe>:
      e4: e9 2d 40 f8   <unknown>
      e8: e1 a0 60 00   rsbeq   r10, r0, r1, ror #1
      ec: e5 9f 01 98   stmdals r1, {r0, r2, r5, r6, r7, r8, r9, r10, r11, r12, pc}
      f0: eb ff ff fe   cdp2    p15, #15, c15, c15, c11, #7
      f4: e1 a0 00 06   streq   r10, [r0], -r1, ror #1
      f8: eb ff ff c0   rscsgt  pc, pc, r11, ror #31
      fc: e3 a0 0f 62   andvs   r10, pc, #227
     100: e1 a0 10 06   ldreq   r10, [r0], -r1, ror #1
     104: eb ff ff fe   cdp2    p15, #15, c15, c15, c11, #7
     108: e2 86 70 03   cmneq   r0, #236978176

This shows function names (buzzer_init, tone, call_me_maybe), the offset and encoding of each instruction, and the more human readable assembly code corresponding to that instruction. The human readable instructions produced by this tool look highly suspect to me and I quickly learnt to ignore them and just focus on the machine code (e.g. the first instruction of buzzer_init is e3 a0 23 3a).

I ended up relying heavily on an online ARM (dis)assembler for decoding and eventually encoding instructions.

As a sanity check, I disassembled the buzzer_init function since it’s short. The concatenation of the machine code of its 4 instructions (copied from the output of llvm-objdump above) is:

e3 a0 23 3a e5 9f 30 04 e5 83 20 00 e1 2f ff 1e

Disassembling this gives:

0x0000000000000000:  E3 A0 23 3A    mov r2, #0xe8000000
0x0000000000000004:  E5 9F 30 04    ldr r3, [pc, #4]
0x0000000000000008:  E5 83 20 00    str r2, [r3]
0x000000000000000c:  E1 2F FF 1E    bx  lr

The implementation of this function in C is:

void buzzer_init() {
    VM_BASE_INIT(GPIO_VSTART);
}

Expanding the macro and constant:

void buzzer_init() {
    _global_base = 0xE8000000;
}

The presence of the constant 0xE8000000 in the C and assembly gave me some degree of confidence that objdump was correctly locating functions in the object file and the online assembler could disassemble machine code into instructions for the slug’s processor architecture.

Observing the instruction offsets in the object file as analyzed by llvm-objdump, all the functions are consecutive. This invites the question as to whether they are also consecutive with the same relative offset in the binary image.

Choosing a prefix of a function’s machine code (taken from the output of llvm-objdump) which is long enough to be unique in the binary image, and searching for it with hexedit, I could locate that function in the binary image. I’ve highlighted the machine code for buzzer_init, tone, and the beginning of call_me_maybe in red, green, and blue respectively in the following output from hexedit:

00032920   E0 D0 30 04  BA FF FF FB  E8 BD 80 F8  E3 A0 23 3A  ..0...........#:
00032930   E5 9F 30 04  E5 83 20 00  E1 2F FF 1E  00 04 C2 50  ..0... ../.....P
00032940   E9 2D 40 F8  E1 A0 40 01  E1 A0 10 80  E3 A0 09 3D  .-@...@........=
00032950   E2 80 0D 09  EB 00 AA 17  E1 A0 60 00  E1 A0 00 04  ..........`.....
00032960   E1 A0 10 86  EB 00 AA 13  E2 50 70 00  DA 00 00 10  .........Pp.....
00032970   E3 A0 40 00  E5 9F 50 3C  E5 95 30 00  E5 93 20 00  ..@...P<..0... .
00032980   E3 82 20 10  E5 83 20 00  E1 A0 00 06  EB FF FF D1  .. ... .........
00032990   E5 95 30 00  E5 93 20 00  E3 C2 20 10  E5 83 20 00  ..0... ... ... .
000329A0   E1 A0 00 06  EB FF FF CB  E2 84 40 01  E1 57 00 04  ..........@..W..
000329B0   CA FF FF F0  E8 BD 80 F8  00 04 C2 50  E9 2D 40 F8  ...........P.-@.
000329C0   E1 A0 60 00  E5 9F 01 98  EB 00 16 16  E1 A0 00 06  ..`.............
000329D0   EB FF FF C0  E3 A0 0F 62  E1 A0 10 06  EB FF FF D7  .......b........
000329E0   E2 86 70 03  E3 56 00 00  A1 A0 70 06  E1 A0 71 47  ..p..V....p...qG
000329F0   E3 A0 00 F6  E1 A0 10 07  EB FF FF D0  E0 86 4F A6  ..............O.
00032A00   E1 A0 40 C4  E3 A0 5F 49  E2 85 50 01  E1 A0 00 05  ..@..._I..P.....
00032A10   E1 A0 10 04  EB FF FF C9  E3 A0 0F 62  E1 A0 10 07  ...........b....
---  bootimg.bin       --0x32A1C/0x7C000--41%----------------------------------

Cross referencing this with the objdump output it becomes clear that the layout of this part of bootable image is the same as the object file buzzer.o. The location in memory of the buzzer functions relative to each other will be the same as in the object file.

Staring at this for a while I had the idea of rather than working out how to call call_me_maybe as a function, I could modify the buzzer_init function to jump directly to the beginning of call_me_maybe instead of returning. When the main function calls buzzer_init, that would then cause the song to play immediately rather than requiring a separate call to call_me_maybe.

ARM assembly has a b instruction which jumps to a nearby address specified by a relative offset. So if we’re replacing the final instruction of buzzer_init to jump to the beginning of call_me_maybe, that’s a relative offset of 0xE4 - 0x60 = 0x84. The instruction b #0x84 encodes to the machine code EA 00 00 1F, so I used hexedit to replace the final instruction of buzzer_init (in red above) with EA 00 00 1F.

This shows the same section of the binary as above, with the updated 4 bytes in yellow:

00032920   E0 D0 30 04  BA FF FF FB  E8 BD 80 F8  E3 A0 23 3A  ..0...........#:
00032930   E5 9F 30 04  E5 83 20 00  EA 00 00 1F  00 04 C2 50  ..0... ../.....P
00032940   E9 2D 40 F8  E1 A0 40 01  E1 A0 10 80  E3 A0 09 3D  .-@...@........=
00032950   E2 80 0D 09  EB 00 AA 17  E1 A0 60 00  E1 A0 00 04  ..........`.....
00032960   E1 A0 10 86  EB 00 AA 13  E2 50 70 00  DA 00 00 10  .........Pp.....
00032970   E3 A0 40 00  E5 9F 50 3C  E5 95 30 00  E5 93 20 00  ..@...P<..0... .
00032980   E3 82 20 10  E5 83 20 00  E1 A0 00 06  EB FF FF D1  .. ... .........
00032990   E5 95 30 00  E5 93 20 00  E3 C2 20 10  E5 83 20 00  ..0... ... ... .
000329A0   E1 A0 00 06  EB FF FF CB  E2 84 40 01  E1 57 00 04  ..........@..W..
000329B0   CA FF FF F0  E8 BD 80 F8  00 04 C2 50  E9 2D 40 F8  ...........P.-@.
000329C0   E1 A0 60 00  E5 9F 01 98  EB 00 16 16  E1 A0 00 06  ..`.............
000329D0   EB FF FF C0  E3 A0 0F 62  E1 A0 10 06  EB FF FF D7  .......b........
000329E0   E2 86 70 03  E3 56 00 00  A1 A0 70 06  E1 A0 71 47  ..p..V....p...qG
000329F0   E3 A0 00 F6  E1 A0 10 07  EB FF FF D0  E0 86 4F A6  ..............O.
00032A00   E1 A0 40 C4  E3 A0 5F 49  E2 85 50 01  E1 A0 00 05  ..@..._I..P.....
00032A10   E1 A0 10 04  EB FF FF C9  E3 A0 0F 62  E1 A0 10 07  ...........b....
---  bootimg.bin       --0x32A1C/0x7C000--41%----------------------------------

And running the result:

...
[gpio] starting
[gpio] found region 0x001d13a8
[gpio] paging success
Hey I just met you
And this is crazy
But here's my number
So call me maybe

Starting "tty_test"...
Loading first process.  Trying to set up a page table!
...

Hacker voice: I’m in.

We’re successfully jumping into the call_me_maybe function as evidence by the printouts, but it’s not playing any sound yet. Here’s the C code again for convenience:

void call_me_maybe(int beat_len) {

    printf("Hey I just met you\n");
    udelay(beat_len);
    tone(NOTE_G4, beat_len);
    tone(NOTE_B3, beat_len/4);
    tone(NOTE_D4, beat_len/2);
    tone(NOTE_G4, beat_len/4);
    tone(NOTE_G4, beat_len/4);
    tone(NOTE_D4, 3*beat_len/4);

    printf("And this is crazy\n");
    udelay(beat_len);
    udelay(beat_len/2);
    tone(NOTE_B3, beat_len/2);
    tone(NOTE_B3, beat_len/4);
    tone(NOTE_D4, beat_len/2);
    tone(NOTE_B4, beat_len/4);
    tone(NOTE_B4, beat_len/2);
    tone(NOTE_G4, beat_len/2);

    printf("But here's my number\n");
    udelay(beat_len);
    udelay(beat_len/2);
    tone(NOTE_G4, beat_len/2);
    tone(NOTE_B4, beat_len/2);
    tone(NOTE_C5, beat_len/2);
    tone(NOTE_B4, beat_len/2);
    tone(NOTE_G4, beat_len/2);

    printf("So call me maybe\n");
    udelay(beat_len);
    udelay(beat_len/2);
    tone(NOTE_G4, beat_len/2);
    tone(NOTE_B4, beat_len/2);
    tone(NOTE_A4, beat_len/2);
    tone(NOTE_A4, beat_len/2);
    tone(NOTE_G4, beat_len/2);
}

Normally this function gets passed a beat_len argument specifying the duration of each beat in microseconds. But now that this function isn’t being called but rather jumped into, whatever processor register stores the beat length isn’t being initialized to anything in particular. Its value will be whatever it was before jumping into this function. Probably this values is very low, possibly 0, so the buzzes are too short to be heard, hence the silence. More evidence for this theory is that all the song lyrics are printed immediately, rather than with a noticeable delay in between.

Let’s take a look at the disassembly of the beginning of call_me_maybe which I’ve manually annotated:

0x0000000000000000:  E9 2D 40 F8    push  {r3, r4, r5, r6, r7, lr}
0x0000000000000004:  E1 A0 60 00    mov   r6, r0      }- save the beat_len arg in r6
0x0000000000000008:  E5 9F 01 98    ldr   r0, [pc, #0x198]
0x000000000000000c:  EB 00 16 16    bl    #0x586c     }- call printf
0x0000000000000010:  E1 A0 00 06    mov   r0, r6      }- arg for udelay
0x0000000000000014:  EB FF FF C0    bl    #0xffffff1c }- call udelay
0x0000000000000018:  E3 A0 0F 62    mov   r0, #0x188  }- first arg for tone (note G4)
0x000000000000001c:  E1 A0 10 06    mov   r1, r6      }- second arg for tone (beat_len)
0x0000000000000020:  EB FF FF D7    bl    #0xffffff84 }- call tone
0x0000000000000024:  E2 86 70 03    add   r7, r6, #3
0x0000000000000028:  E3 56 00 00    cmp   r6, #0
0x000000000000002c:  A1 A0 70 06    movge r7, r6
0x0000000000000030:  E1 A0 71 47    asr   r7, r7, #2
0x0000000000000034:  E3 A0 00 F6    mov   r0, #0xf6   }- first arg for tone (note B3)
0x0000000000000038:  E1 A0 10 07    mov   r1, r7
0x000000000000003c:  EB FF FF D0    bl    #0xffffff84 }- call tone
0x0000000000000040:  E0 86 4F A6    add   r4, r6, r6, lsr #31
0x0000000000000044:  E1 A0 40 C4    asr   r4, r4, #1
0x0000000000000048:  E3 A0 5F 49    mov   r5, #0x124  }
0x000000000000004c:  E2 85 50 01    add   r5, r5, #1  }- first arg for tone (note D4)
0x0000000000000050:  E1 A0 00 05    mov   r0, r5      }
0x0000000000000054:  E1 A0 10 04    mov   r1, r4
0x0000000000000058:  EB FF FF C9    bl    #0xffffff84 }- call tone
0x000000000000005c:  E3 A0 0F 62    mov   r0, #0x188  }- first arg for tone (note G4)
0x0000000000000060:  E1 A0 10 07    mov   r1, r7

It appears r0 is being used to pass the first argument to functions (and r1 is used to pass the second argument).

To build some confidence in this hypothesis, look at the signature for the tone function:

void tone(int frequency, int time);

Throughout the assembly of call_me_maybe we see r0 being set to a representation of the note frequency and r1 being passed the note duration right before each bl #0xffffff84 instruction, which looks like calls to the tone function.

The second line mov r6, r0 is saving the beat length (call_me_maybe’s first and only argument) to r6 before using r0 for some other purpose (to pass the argument to printf). To increase the beat length we need to replace this second instruction with an instruction that stores a higher value in r6. This has to be done with a single instruction, since the first instruction of this function is important in maintaining the function calling convention (otherwise the OS would crash when call_me_maybe returned), and the third instruction is necessary for printing “Hey I just met you”. And remember we can’t insert or remove bytes - only update them in place.

I found that the original value of r6 (before the second instruction set it to the contents of r0) was non-zero, as replacing the mov r6, r0 with an instruction that does nothing (such as mov r0, r0) caused the song to play, albeit too fast. But that indicates this approach will definitely work!

Rather than doing nothing, I need the second instruction to increase the existing value of r6 by some amount. There are several approaches I could have taken, but I found that simply doubling its value by shifting it by one bit to the left gives a suitable beat length. I replaced the second instruction with lsl r6, r6, #1 which assembles to E1 A0 60 86.

Here’s the result:

What strikes me about this project is that I worked on it in a time before I started compulsively uploading all the code I wrote to a code forge like github. This was for a university assignment so perhaps there was a policy against that sort of thing. At this time github was still relatively new and I wasn’t very familiar with version control, so it sometimes seemed too complex to be worth using it for every project.

Nowadays I can’t imagine a workflow that doesn’t involve regular commits to a version control system and it’s so easy to push all my code to github that I doubt I’ll ever lose access to code I wrote since 2015 or so. I horde everything in the off chance that it will be useful or at least interesting to someone in the future.

Since this assignment was group work we did use version control to manage changes. My copy of the source still contains version control metadata and I can see we used my assignment partner’s user account on the university server as our centralized repository, and I doubt that’s archived anywhere. The last commit in my copy of the project is from midway through the semester. Fortunately the copy I made on my account was taken after I added the Easter egg.

changeset:   41:1329df276e9a
parent:      35:29e1993e26ba
user:        Stephen Sherratt <ssteve@cse.unsw.edu.au>
date:        Sun Jul 29 16:25:22 2012 +1000
summary:     wrote function that plays Call Me Maybe