Modern SDK: Linkerscripts

Commands

Code example

A linkerscript first begins with these lines:

OUTPUT_ARCH (mips)
    
    SECTIONS
    {
       __romPos = 0;
    

OUTPUT_ARCH (mips) simply tells ld that the output is in the MIPS architecture.

SECTIONS tells ld that we will be defining the segments and their order.

__romPos is an internal variable used by the default macros to define the current location in the ROM.

    BEGIN_SEG(boot, 0x04000000)
    {
      BUILD_DIR/asm/rom_header.o(.text); /* ROM Header */
      BUILD_DIR/boot.6102.o(.data); /* CIC 6102 bootcode */
    }
    END_SEG(boot)
    

This is the first segment which every script has. This defines the ROM header, and the CIC bootcode. Specifics of these contents are out of scope for this doc.

Now we get to our initial codesegment:

    BEGIN_SEG(code, 0x80000400)
    {
        BUILD_DIR/asm/entry.o(.start);
        BUILD_DIR/src/main*.o(.text);
    
        */libultra_rom.a:*.o(.text);
    

BEGIN_SEG declares our segment and the address. Then we get to the lines that include the code itself. The basic format is PATH/TO/FILE.o(SECTION);. SECTION is the section of the object file, for example .text, .data, .rodata, etc. For including folders of files, the format is PATH/TO/FOLDER*.o(SECTION);. In all the demos, BUILD_DIR is a variable changed by cpp to be your build directory where built artifacts are stored.

The final line in this example tells ld to check the libultra_rom.a library for needed code. When running ld -lultra_rom is one of the arguments, so ld knows where to find the library. To include code from a different library, change libultra_rom to whatever the other library is.

In the demos, the order of inclusion is .text, .data, and .rodata in that order. Here's a condensed example:

        BUILD_DIR/src/main*.o(.text);
        */libultra_rom.a:*.o(.text);
    
        /* data */
        BUILD_DIR/src/main*.o(.data*);
        */libultra_rom.a:*.o(.data*);
    
        /* rodata */
        BUILD_DIR/src/main*.o(.*rodata*);
        */libultra_rom.a:*.o(.*rodata*);
    }
    END_SEG(code)
    

After the segment, include your .bss data in a NOLOAD segment like so:

    BEGIN_NOLOAD(code)
    {
        BUILD_DIR/src/main*.o(.*bss*);
        */libultra_rom.a:*.o(.*bss*);
    }
    END_NOLOAD(code)
    

Data/assets/binaries example

ld does not support including binary blobs. To include binary data, the binaries must be converted into object files. There are multiple methods of doing this, and I recommend you automate this in your makefile or w/e build system you're using.

I know of two methods, which are:

objcopy:

mips-n64-objcopy -I binary -B mips -O elf32-bigmips INPUTFILE OUTPUTFILE.o

ld:

mips-n64-ld -r -b binary INPUTFILE -o OUTPUTFILE.o

Then you can include your binary into the script like so:

    BEGIN_SEG(rawdata, __romPos)
    {
        BUILD_DIR/assets/BINARY.o(.data*);
    }
    END_SEG(rawdata)
    

In this case, __romPos is used as the address argument as the binary is supposed to be in ROM only and not given a memory address in RAM.

Back to Linkerscripts section