Getting Rust on the GBA 1

Posted on November 21, 2015

I've seen a bunch of excellent posts about kernels recently, which has inspired me to give it a try too. I've decided on trying to get Rust running on the Game Boy Advance. For an introduction to GBA programming, I really cannot recommend [TONC][TONC] any higher.

Lets take a look at what happens when we compile a C program for the GBA. I'm using devkitARM as my cross-compiler and toolchain. I've set $DEVKITARM to point into the root of it, and will refer to things in it that way.

And I'm just going to be compiling the first example from TONC. This is an intensionally silly example, to flaunt how low level GBA programming is.

// First demo. You are not expected to understand it 
// (don't spend too much time trying and read on).
// But if you do understand (as a newbie): wow!

int main()
{
    *(unsigned int*)0x04000000 = 0x0403;

    ((unsigned short*)0x06000000)[120+80*240] = 0x001F;
    ((unsigned short*)0x06000000)[136+80*240] = 0x03E0;
    ((unsigned short*)0x06000000)[120+96*240] = 0x7C00;

    while(1);

    return 0;
}

This just puts 3 coloured dots on the screen. It'll be easy to port to rust, since it really doesn't depend on anything, which is why I chose to use it.

Lets look at what commands get run when I make the project.

arm-none-eabi-gcc -c first.c -mthumb-interwork -mthumb -O2 -Wall \
    -fno-strict-aliasing -o first.o
arm-none-eabi-gcc first.o -mthumb-interwork -mthumb -specs=gba.specs -o first.elf
arm-none-eabi-objcopy -v -O binary first.elf first.gba
    copy from `first.elf' [elf32-littlearm] to `first.gba' [binary]
gbafix first.gba
    ROM fixed!

This produces first.gba, which runs nicely on my emulator.

It's using the cross compiled gcc toolchain living in $DEVKITARM/bin. That's also where the gbafix tool lives.

Lets understand what all these flags are doing.

Then, when we are linking, we have this -specs=gba.specs flag. Lets find this gba.specs file and see what it's doing. Ah, it's in $DEVKITARM/arm-none-eabi/lib/. Lots of other interesting things in there too. Here's what's in the gba.specs file:

%rename link                old_link

*link:
%(old_link) -T gba_cart.ld%s

*startfile:
gba_crt0%O%s crti%O%s crtbegin%O%s

So, gcc uses .specs files to re-write some rules. I'll summarize what this means.

Lets try and build without the specs file now.

arm-none-eabi-gcc -nostdlib -mthumb-interwork -mthumb -c first.c -o first.o
arm-none-eabi-gcc -nostdlib -mthumb-interwork -mthumb -c gba_crt0.s -o gba_crt0.o
arm-none-eabi-gcc -nostdlib -mthumb-interwork -mthumb -T gba_cart.ld \
    first.o gba_crt0.o crti.o -o first.elf
arm-none-eabi-objcopy -O binary first.elf first.gba
gbafix first.gba

Blarg, errors

crtbegin.o: In function `deregister_tm_clones':
crtstuff.c:(.text+0x20): undefined reference to `__TMC_END__'
crtbegin.o: In function `register_tm_clones':
crtstuff.c:(.text+0x54): undefined reference to `__TMC_END__'

Looks like there's some more symbols, we can fix this by also linking in crtn.o and crtend.o (Not shown, lots of time in gobjdump and gnm looking at what symbols are defined where).

arm-none-eabi-gcc -nostdlib -mthumb-interwork -mthumb -c first.c -o first.o
arm-none-eabi-gcc -nostdlib -mthumb-interwork -mthumb -T gba_cart.ld \
    first.o gba_crt0.s crti.o crtbegin.o crtend.o crtn.o -o first.elf
arm-none-eabi-objcopy -O binary first.elf first.gba
gbafix first.gba
(.init+0x200): undefined reference to `fake_heap_end'

Ah no, I must have missed something. Poking around with ag and gobjdump leads me to find that

$ ag --all-types fake
...
Binary file libsysbase.a matches.
...
$ gnm --target=elf32-littlearm libsysbase.a
...
sbrk.o:
         U __end__
00000000 T _sbrk_r
         U fake_heap_end
         U fake_heap_start
00000000 b heap_start.5558
...

Well, I'm not going to using system memory anyways, or malloc, so I'll just comment out that part of gba_crt0.s and see if it works.

It does. Nice.


These crt* files are the C Runtime. They handle things such as initializing global variables and calling main. The gbacrt0.s has some GBA specific code, for example it has the Nintendo header at the start:

	.section	".init"
	.global     _start
	.align
	.arm
@----------------------------------------------------------------------------
_start:
@----------------------------------------------------------------------------
	b	rom_header_end

	.fill   156,1,0			@ Nintendo Logo Character Data (8000004h)
	.fill	16,1,0			@ Game Title
	.byte   0x30,0x31		@ Maker Code (80000B0h)
	.byte   0x96			@ Fixed Value (80000B2h)
	.byte   0x00			@ Main Unit Code (80000B3h)
	.byte   0x00			@ Device Type (80000B4h)
	.fill	7,1,0			@ unused
	.byte	0x00			@ Software Version No (80000BCh)
	.byte	0xf0			@ Complement Check (80000BDh)
	.byte	0x00,0x00    		@ Checksum (80000BEh)

@----------------------------------------------------------------------------
rom_header_end:

So at the very start of the code, we actually just skip forward over Nintendo's header. This header has 0x00,0x00 for it's checksum, which get overwritten when we run gbafix.

We'll look at the rest of gba_crt0.s later, right now I want to drop the extra files we don't need. Since we loop forever and don't return from main, I want to stop using crtn and crtend, since we will never use them. However crtbegin uses a symbol from them. Eh, we'll cut that too, see if it runs.

arm-none-eabi-gcc -nostdlib -mthumb-interwork -mthumb -c first.c -o first.o
arm-none-eabi-gcc -nostdlib -mthumb-interwork -mthumb -T gba_cart.ld \
    first.o gba_crt0.s crti.o -o first.elf
arm-none-eabi-objcopy -O binary first.elf first.gba
gbafix first.gba

Yes, it does work!


(TODO: talk about gba_cart.ld?)


Now, since Rust uses LLVM, I wanted to try and compile this using clang. This is actually the reason I expanded the gba.specs file, since clang does accept it.

I'm going to skip over lots of the trial and error, and point out a few highlights.

First of all, gba_crt0.s is using the pre-AUL instructions. This basically boils down to needing to add a bunch of s suffixes to commands, and there's also one straight up wrong instruction

85:    mov     r6, r2

which requires ARMv6 or latter. Now, if you were past-me, you might think "Oh, but the GBA's chip is called ARM7, and 7 > 6, so there's no problem, right?". Wrong! Confusingly, the ARM7 chip supports ARMv4 instructions. That 7 is totally unrelated.

This took me going to #llvm on irc to figure out.

I have decided for now to keep compiling gba_crt0.s with arm-none-eabi-gcc, and not worry about fixing everything up so clang likes it for now, since clang still depends on arm-none-eabi-gcc for the linking step anyways.

Here's the script I ended up with for compiling things using clang.

#!/bin/bash -ex
# -ex will stop on the first error, and print every command
# as it gets run.

clang -nostdlib -target arm-none-eabi -mcpu=arm7tdmi -c first.c -o first.clang.o
# Clang is a cross compiler, so we don't need to rebuild it from source.
# We do need to pass it the target triple (arm-none-eabi), and what cpu
# we are using.
# Also, since we aren't using libc, we pass -nostdlib

arm-none-eabi-gcc -nostdlib -mthumb-interwork -mthumb \
    -c gba_crt0.s -o gba_crt0.o
# Compile gba_crt0.s using gcc still.

clang -nostdlib -target arm-none-eabi -Tgba_cart.ld \
    first.clang.o gba_crt0.o crti.o -o first.clang.elf

# Call clang to link everything.  Actually, clang will just call out to
# arm-none-eabi-gcc to link everything properly.  It needs to be in the path.

arm-none-eabi-objcopy -O binary first.clang.elf first.clang.gba
gbafix first.clang.gba

For comparison, here is the final gcc script:

#!/bin/bash -ex
arm-none-eabi-gcc -nostdlib -mthumb-interwork -mthumb -c first.c -o first.gcc.o
arm-none-eabi-gcc -nostdlib -mthumb-interwork -mthumb -c gba_crt0.s -o gba_crt0.o
arm-none-eabi-gcc -nostdlib -mthumb-interwork -mthumb -T gba_cart.ld \
    first.gcc.o gba_crt0.o crti.o -o first.gcc.elf
arm-none-eabi-objcopy -O binary first.gcc.elf first.gcc.gba
gbafix first.gcc.gba

That took a few days to get correct.

Lets take a break for now, and I'll cover using Rust code in the next post. See you next time.