Reputation: 1688
I've got DevKitPro working on Game Boy Advance, but I've run into a few problems. The biggest one I see is that my code is assembled at 0x00000000
instead of the normal 0x08000000
for ROM cartridges. My understanding is that C compilers don't use an .org
directive to create the code at a specified memory location; rather the linker is supposed to take care of all that for me. But it seems to be placing the code at the "wrong" address. The game will run correctly, but I imagine this is because it's running on an emulator and the emulator doesn't care that it's located somewhere it shouldn't be. How do I get the code to be "assembled" at 0x08000000
?
I'm new to the concept of makefiles, compilers, linkers, etc. so I probably have everything set up very poorly. I'll show my makefile, batch script that runs the makefile, and the C code that's being compiled. I've also included the objdump in case that's relevant.
C code:
// LIBGBA HEADERS
#include <gba_console.h>
#include <gba_video.h>
#include <gba_interrupt.h>
#include <gba_systemcalls.h>
#include <gba_input.h>
#include <stdio.h>
#include <stdlib.h>
// GAME-SPECIFIC INCLUDES
#include "M:\SrcGBA\PaintBoyAdvance\include\bitmap.h"
#include "M:\SrcGBA\PaintBoyAdvance\include\bitmap.c" //BITMAP SCREEN FUNCTIONS
//---------------------------------------------------------------------------------
// Program entry point
//---------------------------------------------------------------------------------
int main(void) {
//---------------------------------------------------------------------------------
// the vblank interrupt must be enabled for VBlankIntrWait() to work
// since the default dispatcher handles the bios flags no vblank handler
// is required
irqInit();
irqEnable(IRQ_VBLANK);
// consoleDemoInit();
REG_DISPCNT = 0x1403;
while (1) {
VBlankIntrWait();
}
}
Batch script:
@echo off
set path=C:\devkitPro\;%path%
cd M:\SrcGBA\PaintBoyAdvance
make
if not "%errorlevel%"=="0" goto Abandon
C:\devkitPro\devkitARM\bin\arm-none-eabi-objdump -h M:\SrcGBA\PaintBoyAdvance\build\paintboyadvance.o
C:\Users\puppy\Documents\VisualBoyAdvance\visualboyadvance-m.exe M:\SrcGBA\PaintBoyAdvance\PaintBoyAdvance.gba
:Abandon
exit
Makefile:
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
include $(DEVKITARM)/gba_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
# DATA is a list of directories containing binary data
# GRAPHICS is a list of directories containing files to be processed by grit
#
# All directories are specified relative to the project directory where
# the makefile is found
#
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
BUILD := build
SOURCES := source
INCLUDES := include
DATA := data
MUSIC :=
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -mthumb -mthumb-interwork
SPECS := -specs=gba.specs
CFLAGS := -g -Wall -O2\
-mcpu=arm7tdmi -mtune=arm7tdmi\
-ffreestanding \
$(ARCH)
CFLAGS := $(INCLUDE)
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := -g $(ARCH)
LDFLAGS = -g $(ARCH) $(INCLUDE) -Wl,-Map,$(notdir [email protected])
#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project
#---------------------------------------------------------------------------------
LIBS := -lmm -lgba
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBGBA)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
$(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
ifneq ($(strip $(MUSIC)),)
export AUDIOFILES := $(foreach dir,$(notdir $(wildcard $(MUSIC)/*.*)),$(CURDIR)/$(MUSIC)/$(dir))
BINFILES += soundbank.bin
endif
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SOURCES)
export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
.PHONY: $(BUILD) clean
#---------------------------------------------------------------------------------
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(OUTPUT).gba : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
$(OFILES_SOURCES) : $(HFILES)
#---------------------------------------------------------------------------------
# The bin2o rule should be copied and modified
# for each extension used in the data directories
#---------------------------------------------------------------------------------
#---------------------------------------------------------------------------------
# rule to build soundbank from music files
#---------------------------------------------------------------------------------
soundbank.bin soundbank.h : $(AUDIOFILES)
#---------------------------------------------------------------------------------
@mmutil $^ -osoundbank.bin -hsoundbank.h
#---------------------------------------------------------------------------------
# This rule links in binary data with the .bin extension
#---------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPSDIR)/*.d
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------
Output of object dump:
M:\SrcGBA\PaintBoyAdvance\build\paintboyadvance.o: file format elf32-littlearm
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000268 00000000 00000000 00000034 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 00000000 00000000 0000029c 2**0
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 0000029c 2**0
ALLOC
3 .comment 00000024 00000000 00000000 0000029c 2**0
CONTENTS, READONLY
4 .ARM.attributes 0000002a 00000000 00000000 000002c0 2**0
CONTENTS, READONLY
EDIT: As requested here is the output of the batch file:
\SrcGBA\PaintBoyAdvance\compile.bat paintboyadvance.c M:\SrcGBA\PaintBoyAdvance\source nopause
Process started >>>
paintboyadvance.c
linking cartridge
built ... PaintBoyAdvance.gba
ROM fixed!
M:\SrcGBA\PaintBoyAdvance\build\paintboyadvance.o: file format elf32-littlearm
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000280 00000000 00000000 00000034 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 00000000 00000000 000002b4 2**0
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 000002b4 2**0
ALLOC
3 .comment 00000024 00000000 00000000 000002b4 2**0
CONTENTS, READONLY
4 .ARM.attributes 0000002a 00000000 00000000 000002d8 2**0
CONTENTS, READONLY
Upvotes: 3
Views: 484
Reputation: 71526
The key is to use a linker script. I will just post a complete working example.
startup.s
.cpu arm7tdmi
.code 32
rom_start:
b ram_start
.space 0xA0-0x04,0
.space 0xC0-0xA0,0
ram_start:
b _start
.space 0xE0-0xC4,0
_start:
ldr sp,=0x03008000
bl notmain
hang:
b hang
.globl PUT16
PUT16:
strh r1,[r0]
bx lr
.globl GET16
GET16:
ldrh r0,[r0]
bx lr
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.globl GET32
GET32:
ldr r0,[r0]
bx lr
notmain.c
extern void PUT32 ( unsigned int, unsigned int );
extern unsigned int GET32 ( unsigned int );
extern void PUT16 ( unsigned int, unsigned int );
extern unsigned int GET16 ( unsigned int );
#define DISPCNT 0x04000000
#define BG0CNT 0x04000008
#define PMEM 0x05000000
#define TMEM 0x06000000
#define VMEM 0x06008000
//#define TWIDE 32
//#define THIGH 20
//#define TVISIBLE 30
void notmain ( void )
{
unsigned int ra;
unsigned int rb;
unsigned int rc;
//display control,
//mode 0
//enable BG0
PUT16(DISPCNT,0x0100);
//BG0 control
//256 color palette
//tiles defined at 0x60000000
//screen at 0x60008000
PUT16( BG0CNT,0x1080);
//setup the first 8 colors
PUT16(PMEM+0x0,0x0000); //BLACK
PUT16(PMEM+0x2,0x001F); //RED
PUT16(PMEM+0x4,0x03E0); //GREEN
PUT16(PMEM+0x6,0x03FF); //GREEN+RED
PUT16(PMEM+0x8,0x7C00); //BLUE
PUT16(PMEM+0xA,0x7C1F); //BLUE+RED
PUT16(PMEM+0xC,0x7FE0); //BLUE+GREEN
PUT16(PMEM+0xE,0x7FFF); //BLUE+GREEN+RED (WHITE)
// Let's make a few tiles 64 bytes per tile.
ra=TMEM;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x00000000); ra+=4; PUT32(ra,0x00000000); ra+=4;
PUT32(ra,0x01010000); ra+=4; PUT32(ra,0x01010000); ra+=4;
PUT32(ra,0x01010000); ra+=4; PUT32(ra,0x01010000); ra+=4;
PUT32(ra,0x01010000); ra+=4; PUT32(ra,0x01010000); ra+=4;
PUT32(ra,0x01010000); ra+=4; PUT32(ra,0x01010000); ra+=4;
PUT32(ra,0x00010001); ra+=4; PUT32(ra,0x00010001); ra+=4;
PUT32(ra,0x01000100); ra+=4; PUT32(ra,0x01000100); ra+=4;
PUT32(ra,0x00010001); ra+=4; PUT32(ra,0x00010001); ra+=4;
PUT32(ra,0x01000100); ra+=4; PUT32(ra,0x01000100); ra+=4;
PUT32(ra,0x02020000); ra+=4; PUT32(ra,0x02020000); ra+=4;
PUT32(ra,0x02020000); ra+=4; PUT32(ra,0x02020000); ra+=4;
PUT32(ra,0x02020000); ra+=4; PUT32(ra,0x02020000); ra+=4;
PUT32(ra,0x02020000); ra+=4; PUT32(ra,0x02020000); ra+=4;
PUT32(ra,0x00020002); ra+=4; PUT32(ra,0x00020002); ra+=4;
PUT32(ra,0x02000200); ra+=4; PUT32(ra,0x02000200); ra+=4;
PUT32(ra,0x00020002); ra+=4; PUT32(ra,0x00020002); ra+=4;
PUT32(ra,0x02000200); ra+=4; PUT32(ra,0x02000200); ra+=4;
PUT32(ra,0x03030000); ra+=4; PUT32(ra,0x03030000); ra+=4;
PUT32(ra,0x03030000); ra+=4; PUT32(ra,0x03030000); ra+=4;
PUT32(ra,0x03030000); ra+=4; PUT32(ra,0x03030000); ra+=4;
PUT32(ra,0x03030000); ra+=4; PUT32(ra,0x03030000); ra+=4;
PUT32(ra,0x00030003); ra+=4; PUT32(ra,0x00030003); ra+=4;
PUT32(ra,0x03000300); ra+=4; PUT32(ra,0x03000300); ra+=4;
PUT32(ra,0x00030003); ra+=4; PUT32(ra,0x00030003); ra+=4;
PUT32(ra,0x03000300); ra+=4; PUT32(ra,0x03000300); ra+=4;
PUT32(ra,0x04040000); ra+=4; PUT32(ra,0x04040000); ra+=4;
PUT32(ra,0x04040000); ra+=4; PUT32(ra,0x04040000); ra+=4;
PUT32(ra,0x04040000); ra+=4; PUT32(ra,0x04040000); ra+=4;
PUT32(ra,0x04040000); ra+=4; PUT32(ra,0x04040000); ra+=4;
PUT32(ra,0x00040004); ra+=4; PUT32(ra,0x00040004); ra+=4;
PUT32(ra,0x04000400); ra+=4; PUT32(ra,0x04000400); ra+=4;
PUT32(ra,0x00040004); ra+=4; PUT32(ra,0x00040004); ra+=4;
PUT32(ra,0x04000400); ra+=4; PUT32(ra,0x04000400); ra+=4;
PUT32(ra,0x05050000); ra+=4; PUT32(ra,0x05050000); ra+=4;
PUT32(ra,0x05050000); ra+=4; PUT32(ra,0x05050000); ra+=4;
PUT32(ra,0x05050000); ra+=4; PUT32(ra,0x05050000); ra+=4;
PUT32(ra,0x05050000); ra+=4; PUT32(ra,0x05050000); ra+=4;
PUT32(ra,0x00050005); ra+=4; PUT32(ra,0x00050005); ra+=4;
PUT32(ra,0x05000500); ra+=4; PUT32(ra,0x05000500); ra+=4;
PUT32(ra,0x00050005); ra+=4; PUT32(ra,0x00050005); ra+=4;
PUT32(ra,0x05000500); ra+=4; PUT32(ra,0x05000500); ra+=4;
PUT32(ra,0x06060000); ra+=4; PUT32(ra,0x06060000); ra+=4;
PUT32(ra,0x06060000); ra+=4; PUT32(ra,0x06060000); ra+=4;
PUT32(ra,0x06060000); ra+=4; PUT32(ra,0x06060000); ra+=4;
PUT32(ra,0x06060000); ra+=4; PUT32(ra,0x06060000); ra+=4;
PUT32(ra,0x00060006); ra+=4; PUT32(ra,0x00060006); ra+=4;
PUT32(ra,0x06000600); ra+=4; PUT32(ra,0x06000600); ra+=4;
PUT32(ra,0x00060006); ra+=4; PUT32(ra,0x00060006); ra+=4;
PUT32(ra,0x06000600); ra+=4; PUT32(ra,0x06000600); ra+=4;
PUT32(ra,0x07070000); ra+=4; PUT32(ra,0x07070000); ra+=4;
PUT32(ra,0x07070000); ra+=4; PUT32(ra,0x07070000); ra+=4;
PUT32(ra,0x07070000); ra+=4; PUT32(ra,0x07070000); ra+=4;
PUT32(ra,0x07070000); ra+=4; PUT32(ra,0x07070000); ra+=4;
PUT32(ra,0x00070007); ra+=4; PUT32(ra,0x00070007); ra+=4;
PUT32(ra,0x07000700); ra+=4; PUT32(ra,0x07000700); ra+=4;
PUT32(ra,0x00070007); ra+=4; PUT32(ra,0x00070007); ra+=4;
PUT32(ra,0x07000700); ra+=4; PUT32(ra,0x07000700); ra+=4;
//make screen black/clear screen
ra=VMEM;
for(rb=0;rb<(32*20);rb++)
{
PUT16(ra,0x0000);
ra+=2;
}
//put some tiles on the screen
ra=VMEM;
for(rc=0,rb=0;rb<(32*10);rb++,rc++)
{
rc&=7;
if(rc==0) rc=1;
PUT16(ra,rc);
ra+=2;
}
}
memmap
MEMORY
{
ewram : ORIGIN = 0x02000000, LENGTH = 256K
}
SECTIONS
{
.text : { *(.text*) } > ewram
}
rommap
MEMORY
{
rom : ORIGIN = 0x08000000, LENGTH = 32K
}
SECTIONS
{
.text : { *(.text*) } > rom
}
build
arm-none-eabi-as --warn --fatal-warnings startup.s -o startup.o
arm-none-eabi-gcc -c -mcpu=arm7tdmi -Wall -Werror -O2 -ffreestanding notmain.c -o notmain.o
arm-none-eabi-ld -T rommap startup.o notmain.o -o gbarom.elf
arm-none-eabi-objdump -D gbarom.elf > gbarom.list
arm-none-eabi-objcopy gbarom.elf -O binary notmain.gba
arm-none-eabi-ld -T memmap startup.o notmain.o -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy notmain.elf -O binary notmain.mb
Now vba lets you run multiboot files as well as gba files
vba notmain.mb
vba motmain.gba
One uses EWRAM the other the GAME ROM.
You do not have to use both a MEMORY and SECTIONS in the linker script, can hard code addresses in the sections but I recommend memory being there as well. I recommend against using (rwx) stuff, YMMV.
MEMORY
{
rom : ORIGIN = 0x08000000, LENGTH = 32K
}
SECTIONS
{
.text : { *(.text*) } > rom
}
The name rom does not matter - you could call it pickle and it will be fine. It is just a name that you use to connect a memory range definition to one or more sections. .text* inside is all the stuff in the object(s) that start with .text (.text is the instructions). The *() outside that means all objects that are fed through the linker you could specify specific objects for each entry to have finer control (beware the linker script language is not as nice and perfect as you expect, sometimes calling out objects does not work the way you would think).
Then the .text up front is so the output binary uses that name, you could call that .baseball and it would work just fine just tools that you feed that file would not know what to do so just using .text is a good idea.
You add more things like this
MEMORY
{
ewram : ORIGIN = 0x02000000, LENGTH = 256K
}
SECTIONS
{
.text : { *(.text*) } > ewram
.rodata : { *(.rodata*) } > ewram
.bss : { *(.bss*) } > ewram
.data : { *(.data*) } > ewram
}
and it will process those in order in that the .text stuff goes in the output first then .rodata then .bss and last .data. Also note that since no files or objects are called out here the command line drives the order of things as well.
arm-none-eabi-ld -T memmap startup.o notmain.o -o notmain.elf
we need the entry point code to be first so that file goes first on the command line (easier than complicating it with made up section names, etc).
Note as soon as you build and before you try commit something to a flash
Look at the disassembly:
Disassembly of section .text:
02000000 <rom_start>:
2000000: ea00002e b 20000c0 <ram_start>
...
020000c0 <ram_start>:
20000c0: ea000006 b 20000e0 <_start>
...
020000e0 <_start>:
20000e0: e59fd024 ldr sp, [pc, #36] ; 200010c <GET32+0x8>
20000e4: eb000009 bl 2000110 <notmain>
020000e8 <hang>:
20000e8: eafffffe b 20000e8 <hang>
If you swap the files on the command line
Disassembly of section .text:
02000000 <notmain>:
2000000: e92d4070 push {r4, r5, r6, lr}
2000004: e3a01c01 mov r1, #256 ; 0x100
2000008: e3a00301 mov r0, #67108864 ; 0x4000000
200000c: eb00028f bl 2000a50 <PUT16>
2000010: e3a01d42 mov r1, #4224 ; 0x1080
2000014: e59f06bc ldr r0, [pc, #1724] ; 20006d8 <notmai
game over, that is going to just crash. certainly won't run like you hope/expect (you might get lucky sometimes, but in general this is a fail).
I left room in the bootstrap for logo data and such to get it to work as a real rom for mgba or a real gba with the right cartridge (some have a menu and you do not have to have the cartridge header right). At the multiboot level you do not need to have all of that space in there just start with the bootstrap.
You could get by with -Ttext=0x08000000
as a quick and dirty hack, but with gnu ld that still uses a default linker script that is built into the toolchain you are using, and you are just tweaking that section type. And you start to get into strange things if you push that with more complicated stuff. So you can see how trivial it is to use a linker script, they do not have to be complicated. So many people tend to grossly over complicate, and to be honest the gnu linker script language can be painful as it does not always work in a sane and consistent manner. I prefer to do most of the work elsewhere and not in the linker script.
Upvotes: 2