Trying to link Nintendo DS binary with Zig instead of gcc

Hi there,

I’m slowly learning zig and embedded development on my spare time and my, long term, goal is to build a simple SDK for the Nintendo DS, based on libnds.

I already manage to get a very basic and hacky zig example (that you can found here) but it still use gcc arm and blocksds SDK (previously devkitpro) for linking.

My goal is to be able to run my code only with zig build system and package manager.

The first step is to be able to use zig for linking instead of gcc.
So I tried to tranform gcc args into zig cc ones, but not sure if I got it the right way.
Here is all the gcc args used for linking and the zig cc equivalent I used :

  • arm-none-eabi-gcc -mthumb => zig cc -target thumb-freestanding-eabi. Is freestanding the equivalent of none ?
  • -mcpu=arm946e-s+nofp => -mcpu=arm946e_s. I don’t know how to specify nofp option, do you have any idea on how to do that ?
  • -Wl,-Map,zig-nds-sample.map => -fmodule-map-file=zig-nds-sample.map. Not sure about this one :thinking:.
  • There was also some .specs file which specify a startfile which is a .o file that I added to the other .o file to link. It’s my understanding of what is a startfile but it may be something else, never worked with .specs files before.
  • There was also link instruction in the spec files and I added them to the args, it was -T options pointing to .mem files.

After a lot of try and error and reading, I still got an error which I don’t fully understand :

ld.lld: error: ./libnds/ds_arm9.ld:7: memory region not defined: ewram
>>> __ewram_end	=	ORIGIN(ewram) + LENGTH(ewram);
>>>      

I never occurred such errors in C, I assumed the EWRAM nds memory region must be somehow initialized. Is this related to the .map file or the .mem files ?

Here is the ds_arm9.specs file :

%include <picolibc.specs>

%rename cc1plus blocksds_cc1plus
%rename cpp blocksds_cpp
%rename link blocksds_link

*cpp:
-D__NDS__ -D__BLOCKSDS__ -DARM9 %(blocksds_cpp)

*cc1_cpu:
-mcpu=arm946e-s+nofp

*cc1plus:
%(cpp) %(blocksds_cc1plus)

*link:
%(blocksds_link) -T %:getenv(BLOCKSDS /sys/crts/ds_arm9.mem) -T %:getenv(BLOCKSDS /sys/crts/ds_arm9.ld) --gc-sections --no-warn-rwx-segments --use-blx

*startfile:
%:getenv(BLOCKSDS /sys/crts/ds_arm9_crt0%O)

*lib:
%(libgcc)

And the picolibc.specs (BLOCKSDS used picolibc instead of newlibc :

%rename link	picolibc_link
%rename cc1	picolibc_cc1
%rename cc1plus	picolibc_cc1plus

*cc1:
%{!ftls-model:-ftls-model=local-exec} %(picolibc_cc1)

*cc1plus:
%{!ftls-model:-ftls-model=local-exec} %(picolibc_cc1plus)  

*link:
%{DPICOLIBC_DOUBLE_PRINTF_SCANF:--defsym=vfprintf=__d_vfprintf} %{DPICOLIBC_DOUBLE_PRINTF_SCANF:--defsym=vfscanf=__d_vfscanf} %{DPICOLIBC_FLOAT_PRINTF_SCANF:--defsym=vfprintf=__f_vfprintf} %{DPICOLIBC_FLOAT_PRINTF_SCANF:--defsym=vfscanf=__f_vfscanf} %{DPICOLIBC_LONG_LONG_PRINTF_SCANF:--defsym=vfprintf=__l_vfprintf} %{DPICOLIBC_LONG_LONG_PRINTF_SCANF:--defsym=vfscanf=__l_vfscanf} %{DPICOLIBC_INTEGER_PRINTF_SCANF:--defsym=vfprintf=__i_vfprintf} %{DPICOLIBC_INTEGER_PRINTF_SCANF:--defsym=vfscanf=__i_vfscanf} %{DPICOLIBC_MINIMAL_PRINTF_SCANF:--defsym=vfprintf=__m_vfprintf} %{DPICOLIBC_MINIMAL_PRINTF_SCANF:--defsym=vfscanf=__m_vfscanf} %(picolibc_link)

*lib:
--start-group %(libgcc) -lc --end-group

And the original command line from a blockds’s sample Makefile :

/opt/wonderful/toolchain/gcc-arm-none-eabi/bin/arm-none-eabi-gcc -o build/console_ansi.elf build/console_ansi/source/main.c.o -mthumb -mcpu=arm946e-s+nofp -L/opt/blocksds/core/libs/libnds/lib -Wl,-Map,build/console_ansi.map -Wl,--start-group -lnds9 -lc -Wl,--end-group -specs=/opt/blocksds/core/sys/crts/ds_arm9.specs

I know that I miss a lot of knowledge in embedded system to fully understand what I’m doing, but if someone can help me it would be awesome.

Thanks for reading me,
Andréas

P.S : Don’t be mad if I don’t answer, I’ll be on vacation in a couple of days.

1 Like

Welcome to Ziggit @AndreasL! Let me see if I can tackle a couple of these:

  • arm-none-eabi-gcc -mthumb => zig cc -target thumb-freestanding-eabi. Is freestanding the equivalent of none ?

Yes, this indicates that you are compiling for a device with no operating system.

  • -mcpu=arm946e-s+nofp => -mcpu=arm946e_s. I don’t know how to specify nofp option, do you have any idea on how to do that ?

I believe the equivalent LLVM feature you’re looking for is +soft_float, so -mcpu=arm946e_s+soft_float

  • -Wl,-Map,zig-nds-sample.map => -fmodule-map-file=zig-nds-sample.map. Not sure about this one :thinking:.

Zig’s (LLVM’s) linker (ld.lld) doesn’t support generating .map files currently! Fortunately this is just informational output, it doesn’t actually affect your program.

  • There was also some .specs file which specify a startfile which is a .o file that I added to the other .o file to link. It’s my understanding of what is a startfile but it may be something else, never worked with .specs files before.
  • There was also link instruction in the spec files and I added them to the args, it was -T options pointing to .mem files.

More can be read on spec files here:

After a lot of try and error and reading, I still got an error which I don’t fully understand :

ld.lld: error: ./libnds/ds_arm9.ld:7: memory region not defined: ewram
>>> __ewram_end	=	ORIGIN(ewram) + LENGTH(ewram);
>>>      

This is just because ld.lld interprets linker scripts differently than GCC. This is an easy fix fortunately, you just have to move the definition __ewram_end = ORIGIN(ewram) + LENGTH(ewram); in your linker script to below where ewram is defined. Currently it’s above it, which ld.lld doesn’t like.

General Advice with Embedded Zig

Coming from someone who has made this mistake more times than I can count, I would maybe take a step back and really learn the system you’re developing for before trying to run actual code on it. For instance, learn the answers to the questions:

  • What does a linker script do for this platform?
  • What is startup code/objects and why is it required?
  • What does it take to get the chip the NDS uses (ARM 946e-s) to actually run the function main() in C?
  • How does the library libnds do all this, what are the utilities they are providing and how do they do what they do?
  • What are some of the things arm-none-eabi-gcc is “doing for me” in the background?

Because (like I have many times), when you immediately try to get something running you’ll often get bit by the code someone else wrote you don’t understand. This is doubly important for running a new language on unique hardware as some of the nitty gritty that was taken care of for you previously in the “intended” language for the platform is no longer included.

Here are various resources I’ve created in regards to embedded Zig that may help you on your journey as well:

Bare Metal Zig

Porting a Project From arm-none-eabi-gcc to Zig

Feel free to keep posting questions and follow-ups! Getting Zig running on an NDS would be super cool! Oh and feel free to join the Zig/Zig Embedded Group Discord!

1 Like

Wow thanks a lot for your reply !

It’s very helpful and thanks for the advises.
I wanted to be able to run code in order to start having something to work on and then slowly going deeper, but I’m starting to understand that it won’t be that easy :slight_smile:.

I’m not very familiar with linker scripts. I tried to put the __ewram_end (and others) definition at the end of ds_arm9.ld file but got the same result.
The ewram section seems to be declare here, am I right ? :

SECTIONS
{
....
	.ewram ALIGN(4) : 
	{
		__ewram_start = ABSOLUTE(.) ;
		*(.ewram)
		*ewram.*(.text)
		. = ALIGN(4);   /* REQUIRED. LD is flaky without it. */
	} >ewram :main = 0xff
...
}

Anyway, will take time to dig deeper and read your examples en nds code, as I said it’s a long term project that I do mostly for learning.

Thanks again,
Andréas

1 Like

I should probably clarify, diving in headfirst and playing with code is absolutely fine and is usually the approach I take to learning any sort of new language/framework/etc. It’s just in this specific case, there aren’t any working examples of Zig on a NDS AFAIK so you will be blazing the trail :slight_smile: And the trail will be much, much harder to blaze without knowing the nitty gritty internals of the processor!