RSO API

Introduction

The RSO Library is a relocatable module system.

It can use main memory efficiently by dynamically reading and deallocating program modules in the game application.
In this library, unlike the dynamic link library of other operating systems, the game application is responsible for main memory allocation and deallocation and loading modules from the disk.

The RSO library consists of one static module (the .elf file), one static information module (the .sel file), and multiple dynamic (that is, relocatable) modules (the .rso files). After the boot ROM loads the static module of the program, the static module controls the memory location of the dynamic module. The static module is built as a normal .elf file. The static module contains common functions and common variables referenced by the dynamic modules.

Dynamic (relocatable) modules can call functions and refer to the variables defined in static modules. In addition, dynamic modules can call functions and refer to variables in other dynamic modules loaded into main memory. Referencing across modules is resolved by directly revising the code and data sections of a module when it is loaded.

Functions and variables of the dynamic (relocatable) module can be accessed by specifying the symbol name from the static module.
Accordingly, the program must be aware of the dynamic modules that it uses, unlike when calling functions within static modules.

A dynamic module (RSO) application is written in the same manner as the main C/C++ code, except that the dynamic module is built from a partially linked .elf file (PLF). A PLF file contains unresolved external symbols and debug information. The makerso tool, provided with the Revolution SDK, will convert a PLF file into a dynamic module file used exclusively by the Wii console. These Wii-specific dynamic modules include, in addition to the standard application code and data section, a relocation instruction list, import information, and export information.

Import information is used to reference external elements, and export information allows referencing by external elements.

Differences from REL

Advantages

Drawbacks

Procedural Differences

Making Relocatable Modules

1. Create the partially linked ELF files.

The RSO library uses the static linker partial link option. Each dynamic (relocatable) module is made from a separate set of partially linked files. Using the partial link option (-r), the linker can create an .elf file that includes unresolved external reference symbols. The unresolved symbols of the dynamic module are resolved when the application is run.

Three functions can be defined for a dynamic module: _prolog, _epilog, and _unresolved. _prolog and _epilog can be called from static modules or from other dynamic modules loaded in main memory. _unresolved is automatically called when the module makes a function call to an external function that has not been linked into the main memory.

The linker command file for a dynamic module will be the result of adding FORCEFILES and FORCEACTIVE to the <revolution/eppc.$(PLATFORM).lcf> file. FORCEFILES includes the target files, and FORCEACTIVE includes the __global_destructor_chain.

In the sample this file is output in .plf format.

Note: A relocatable module cannot have a small data section. The small data section base registers (r2 and r13) are reserved for the static module. To prevent the compiler from generating small data sections, specify the compiler options -sdata 0 -sdata2 0 when compiling the relocatable modules.

2. Create a dynamic module file from the partially linked ELF files.

Run the makerso tool to create a dynamic module file (.rso) from a partially linked .plf file.


% makerso.exe a.plf -a

3. Create a static module linker command file and symbol list from all dynamic module files.

Run the makelcf tool to create a static module linker command file and symbol list from all dynamic module files.
The linker command file is generated by adding the FORCEACTIVE list to the <revolution/eppc.$(PLATFORM).lcf> file.

The following shows how to generate a static module linker command file as static.lcf and a symbol list as symbol.lst.


% makelcf.exe -o static.lcf -s symbol.lst -t eppc.RVL.lcf a.rso b.rso

4. Build the program static modules.

Link the static modules of the program with the linker command file generated in the previous section. Link all static libraries to the static module.

5. Generate static module information from the static ELF file.

Use the makerso tool to create static module information from the static .elf file and the symbol list generated in section 3.

The following shows how to generate a static module information as a static.sel file.


% makerso.exe static.elf -e symbol.lst

Using Dynamic Modules

Note: The game application performs memory allocation and deallocation for loading dynamic modules and loading dynamic modules from game discs.
The dynamic module loaded into main memory has type RSOObjectHeader data, followed by the module code and data sections.
Static module information has the same data structure as dynamic modules, but without the code, the import information, or the internal processing information.

1. Loading Individual Modules

The following is an example of loading modules.


DVDFileInfo     fileInfo;
s32             length;
RSOObjectHeader* module;

DVDOpen("a.rso", &fileInfo);
length = (s32) OSRoundUp32B(DVDGetLength(&fileInfo));
module = OSAllocFromArenaLo((u32) length, 32);
DVDRead(&fileInfo, module, length, 0);
DVDClose(&fileInfo);

2. Initializing the Linked List

Use RSOListInit to initialize the linked list.
Static module information is set at this time.
The static module information is used to obtain the address of a function or variable inside the static module when it is referenced by a dynamic module.


RSOListInit(staticModule);

3. Registering the Dynamic Module to the Linked List

Use RSOLinkList to link the dynamic module.
The bss argument is a pointer to the section used as a BSS section (a section initialized with 0s). It is allocated and specified by the programmer.
The required BSS section size is given by RSOObjectHeader.bssSize.
After linking a module, the game is given the choice to call the prolog function (a member variable of the RSOObjectHeader structure).


void *bss;
// Allocate BSS region
bss = OSAllocFromArenaLo(module->bssSize, 32);
RSOLinkList(module, bss);
((u32 (*)(void)) module->prolog)();

4. Removing the Dynamic Module from the Linked List

If the loaded module becomes unnecessary, call RSOUnLinkList to free the memory space for another purpose.
Immediately before unlinking a module, the game is given the choice to call the epilog function (a member variable of the RSOObjectHeader structure).


((void (*)(void)) module->epilog)();
RSOUnLinkList(module);

Partial Memory Deallocation of Relocatable Modules

The RSOLinkListFixed function links the specified module and deallocates part of the memory occupied by the dynamic module.
Once the module is linked using the RSOLinkListFixed function, the memory space that comes after (specified by RSOGetFixedSize) can be used for any purpose (for example, for the BSS area).
However, several restrictions may apply, depending on the deallocation timing. (See RSOLinkListFixed for details.)
Also note that the location specified by RSOGetFixedSize is converted from the file offset to the virtual address when the module is linked. 

The following is an example of partial deallocation of module memory and its reuse as a BSS region.


int             fixed_level     // deallocation stage
RSOObjectHeader* module;        // Target dynamic module
u32             fixed_size;     
u32             new_arenaLo;
u32             old_arenaLo;

//
fixed_size = RSOGetFixedSize(module,fixed_level);
//
bss = (u8 *)((u32)module + fixed_size);
// 32 byte alignment
bss = (u8*) OSRoundUp32B(bss);

new_arenaLo = ((u32)bss + (u32)module->bssSize);
new_arenaLo = OSRoundUp32B(new_arenaLo);
old_arenaLo = (u32)OSGetMEM1ArenaLo();
//
if (module->bssSize > 0) {
    if(old_arenaLo < new_arenaLo) {
        // If the region is to increase, increase before RSOLocateObjectFixed
        OSSetMEM1ArenaLo((void*)new_arenaLo);
    }
} else {
    bss = NULL;
}
// Link process
RSOLinkListFixed(module,bss,i_fixed_level);
// If the region is to decrease, decrease after RSOLocateObjectFixed
if(bss == NULL || old_arenaLo > new_arenaLo) {
    OSSetMEM1ArenaLo((void*)new_arenaLo);
}

Required Alignment Conditions for Relocatable Modules

Dynamic modules and bss sections both have address-alignment constraints. The dynamic module must be aligned at the maximum alignment value required by the .init, .text, .ctor, .dtor, .rodata, and .data sections. A bss section must be aligned at the maximum alignment value required by the data item within the bss section. Typically, both dynamic modules and bss sections require 8-byte alignment.

Accessing a Dynamic Module's Functions and Variables from the Static Module

To access dynamic module functions and variables from the static module, the address is acquired by using RSOFindExportSymbolAddr based on the linked module information and label name.

The following example acquires and uses the addresses of the int foo(int a); function and the int g_int; variable from the dynamic module.


int (*foo)(int);
int *g_int;

foo = (int (*)(int)) RSOFindExportSymbolAddr(module,"foo");
g_int = (int *)RSOFindExportSymbolAddr(module,"g_int");

*g_int += foo(3);

Notes

Note: Using C++ Global Constructors in Dynamic Modules

If using C++ global constructors in relocatable modules, you must manually call global constructors and destructors from the module _prolog and _epilog, Code examples for each case follow below.

#ifdef __cplusplus
extern "C" {
#endif

typedef void (*voidfunctionptr) (void); /* ptr to function returning void */
__declspec(section ".init") extern voidfunctionptr _ctors[];
__declspec(section ".init") extern voidfunctionptr _dtors[];

void _prolog(void);
void _epilog(void);
void _unresolved(void);

#ifdef __cplusplus
}
#endif

void _prolog(void)
{
    voidfunctionptr *constructor;

    /*
     *  call static initializers
     */
    for (constructor = _ctors; *constructor; constructor++) {
        (*constructor)();
    }
}

void _epilog(void)
{
    voidfunctionptr *destructor;

    /*
     *  call destructors
     */
    for (destructor = _dtors; *destructor; destructor++) {
        (*destructor)();
    }
}

In addition, make sure that each dynamic module is linked to global_destructor_chain.c (in the $(CWFOLDER)/PowerPC_EABI_Support/Runtime/Src folder). Linking to this file guarantees that each global destructor is called every time the _epilog function of the module is called. Otherwise, module global variables in each module are linked to the global destructor chain in the static module, and if no static module exists, destructors are never called.?Note: Since Runtime.PPCEABI.H.a uses small data sections and contains unnecessary functions, dynamic modules cannot be linked to it.

Note: Use caution when placing a dynamic module in external main memory (the MEM2 region)

Typically, branch instructions (bx) have only a 28-bit offset (±32 MB). Therefore, when a relocatable module is placed in external main memory (MEM2), it cannot access functions or variables in internal main memory (MEM1).
Avoid this issue by using pragma or by using RSOLinkFar and RSOLinkJump.

1. Using Pragma

Indicate to the compiler that the module needs to be branched in a 32-bit absolute address, using pragma.
An example is given of branching the foo function (located in MEM1) to an absolute address. This applies whether the target function is in a static module or a dynamic (relocatable) module.
Also take similar precautions when calling functions in MEM2 from a dynamic module in MEM1.


#pragma section code_type ".text" data_mode=far_abs code_mode=far_abs
#pragma section RX ".init" ".init" data_mode=far_abs code_mode=far_abs  

void foo(void);

#pragma section code_type ".text" data_mode=far_abs code_mode=pc_rel
#pragma section RX ".init" ".init" data_mode=far_abs code_mode=pc_rel

Note as of 2006/06/15:
Certain run-time codes may not be properly called in 32-bit codes in the release version.
As a temporary workaround, use the use_lmw_stmw pragma to avoid using those runtime codes.
The pragma directive should be applied to the source of modules placed in MEM2.
The compile option "-use_lmw_stmw on" may also be used for the same effect.


// Tentatively set "use_lmw_stmw" to ON. 
#pragma use_lmw_stmw on

2. Using RSOLinkFar

Because using just the standard RSOLink causes this issue, re-link the modules located in main memory (both MEM1 and MEM2) using RSOLinkFar. RSOLinkFar places relay jump codes in buff. The linking modules then link to these jump codes to access the referenced modules in MEM2.
In the following example function LinkFar, the linked module (i_rsoImp) and the referenced module (i_rsoEx) are linked using a relay jump code. The return value is a pointer to the allocated buffer. The required buffer size is obtained using RSOGetFarCodeSize and then allocated. It is then linked using RSOLinkFar.


static u32* LinkFar(RSOObjectHeader *i_rsoImp,RSOObjectHeader *i_rsoExp)
{
    int a_size = RSOGetFarCodeSize(i_rsoImp,i_rsoExp);
    u32 *a_buff;

    //
    if(a_size == 0) {
        // not needed
        return NULL;
    }
    //
    a_buff = OSAllocFromArenaLo((u32)a_size,4);
    //
    RSOLinkFar(i_rsoImp,i_rsoExp,a_buff);
    return a_buff;
}

Note: Allocate the buffer from the same main memory (MEM2 or MEM1) as the module being linked (i_rsoImp).
In addition, the code in the module is overwritten normally after linking. Thus, when using RSOLinkListFixed, proceed to RSO_FL_INTERNAL at the release stage.

3. Using RSOLinkJump

RSOLinkJump creates a relay code to the module being referenced. The referencing side then uses it to link.
To create a relay code, use RSOGetJumpCodeSize to obtain the necessary buffer size, and then use RSOMakeJumpCode to create the relay code.


static u32 *MakeJumpCode(RSOObjectHeader *i_rsoExp)
{
    int a_size = RSOGetJumpCodeSize(i_rsoExp);
    u32 *a_buff;

    //
    if(a_size == 0) {
        // not needed
        return NULL;
    }
    //
    a_buff = OSAllocFromArenaLo((u32)a_size,4);
    //
    RSOMakeJumpCode(i_rsoExp,a_buff);
    return a_buff;
}

Link with the created buffer and RSOLinkJump.


{
    u32 *buff = MakeJumpCode(rsoExp);
    RSOLinkJump(rsoImp,rsoExp,buff);
}

Revision History

2006/12/19 Added a description for RSOLinkJump.
2006/10/25 Added a description for RSOLinkFar.
2006/06/14 Initial version.


CONFIDENTIAL