ARM binaries

From WiiBrew
(Redirected from Starlet ELF Loader)
Jump to navigation Jump to search

ARM binaries are contained inside .wad files in update partitions. The .wad format and how to decrypt it is described in WAD Files.

ELF format

Individual IOS modules use bare ELF files. The files seem to be compiled with GCC 3.4.3, and they are EABI compliant. The file is in big endian format. All ELF files are stripped and don't include function names or symbols.

ELF Note Structure

IOS modules in the main binary are loaded by the main kernel thread according to an ELF note. There are exactly as many IOSELFNoteData as process main threads in IOS.

struct IOSELFNoteData
  u32 version_maybe; // always 0x0b?
  u32 pid;
  u32 unk2; // unused
  u32 entry_point;
  u32 unk4; // unused
  u32 priority;
  u32 unk6; // unused
  u32 stack_size;
  u32 unk8; // unused
  u32 stack_top;

struct IOSELFNote
  // Standard ELF note header
  u32 name_size; // always 0x0
  u32 descriptor_size;
  u32 note_type; // always 0x6

  // IOS specific data
  IOSELFNoteData data[];


The ELFLOADER ARM binary format is used for the "bootup" files, including the IOS kernel (or the entirety of the IOS in earlier versions which are monolithic) and BOOT2. Once decrypted, the data has the following format:

Start End Length Description
0x000 0x004 0x004 Header size = 0x0010
0x004 0x008 0x004 Offset to ELF file after header
0x008 0x00C 0x004 Size of ELF file
0x00C 0x010 0x004 Unknown "ddrInit", set to 0 on boot
0x010 variable variable ELF file stub loader binary
variable variable variable ELF file

Official ELF loaders

The boot2 and IOS elf stub loaders seem to be position-independent: they can be loaded at any address and will still work, as long as it doesn't overlap with the destination of the ELF load.

The official ELF loaders set up a stack and calculate their own address, switches to THUMB mode, and then they do the following:

 if( ! (*((u32 *)0xD800060) & 0x20) ) {
   *((u32 *)0xD800060) |= 0x20;

This sets the HW_SRNPROT register to enable the SRAM mirror at 0xFFFE0000.

After this, it loads the ELF file, and then zeroes out the memory area where most of the ELF loader resides. Then it goes back to ARM mode and vectors to 0xFFFF0000 (the entrypoint of the ARM / vector table). The entire BOOT2 file cleartext is loaded and then the loader is called, so the loader can calculate the offset of the header simply by subtracting 0x10 from the PC at its entrypoint.

Embedded Broadway Code

Some ARM binaries include PowerPC code for the Broadway. For example MIOS include code at address 0x00003400 and 0x01300000. The first code sets the BATs up, initializes the powerpc and vectors to the second code (which can be addressed at 0x81300000 then) then. This code is executed by writing a small stub to the EXI boot buffer and setting a bit in another register then.

Dynamic Linker

In later IOS versions (IOS28 and later) the single monolithic IOS ARM binary was split into individual dynamically loaded modules. The main kernel is the only file to include the ELFLOADER header and stub; each individual module is a regular ELF file, loaded dynamically by the kernel to a fixed address specified in the ELF header. No functions are called directly between modules - Syscalls are used for individual modules to communicate with the main kernel, and resource managers are used for modules to communicate between each other (such as DI interfacing with /dev/es for ticket verification)

Extract ELF file

The following program extracts the ELF from the ARM binary. Normal ELF tools can handle the generated output.

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <netinet/in.h>

/** Header for Wii ARM binaries. */
typedef struct {
	/** Size of this header. */
	uint32_t headerSize;
	/** Offset to ELF file. */
	uint32_t offset;
	/** Size of ELF file. */
	uint32_t size;
	/** Padded with zeroes. */
	uint32_t resevered;
} arm_binary_header_t;

int main(int argc, char *argv[])
	const char *inFilename;
	const char *outFilename;
	FILE *fin = NULL;
	FILE *fout = NULL;
	arm_binary_header_t header;
	char *buffer = NULL;

	if (argc != 3) {
		fprintf(stderr, "%s: [Wii ARM Binary] [ELF output file]\n\n", argv[0]);
		fprintf(stderr, "Extract ELF from Wii ARM binary.\n");
		fprintf(stderr, "Error: Parameter wrong.\n");
		return -1;
	inFilename = argv[1];
	outFilename = argv[2];

	fin = fopen(inFilename, "rb");
	if (fin == NULL) {
		fprintf(stderr, "Error: Failed to open input file \"%s\".\n", inFilename);
		return -2;
	if (fread(&header, sizeof(header), 1, fin) != 1) {
		fprintf(stderr, "Error: Input file \"%s\" is too small.\n", inFilename);
		return -3;
	header.headerSize = ntohl(header.headerSize);
	header.offset = ntohl(header.offset);
	header.size = ntohl(header.size);

	if (header.headerSize != sizeof(arm_binary_header_t)) {
		fprintf(stderr, "Error: Input file \"%s\" is not a Wii ARM binary.\n", inFilename);
		return -4;
	if (fseek(fin, header.offset, SEEK_CUR) != 0) {
		fprintf(stderr, "Error: Input file \"%s\" is too small (seek to %d failed).\n", inFilename, header.offset);
		return -5;
	buffer = malloc(header.size);
	if (buffer == NULL) {
		fprintf(stderr, "Error: Out of memory.\n");
		return -6;
	if (fread(buffer, header.size, 1, fin) != 1) {
		fprintf(stderr, "Error: Input file \"%s\" is too small.\n", inFilename);
		return -7;

	fout = fopen(outFilename, "wb");
	if (fout == NULL) {
		fprintf(stderr, "Error: Failed to open output file \"%s\".\n", outFilename);
		return -8;
	if (fwrite(buffer, header.size, 1, fout) != 1) {
		fprintf(stderr, "Error: Output file \"%s\" write error (disc full?).\n", outFilename);
		return -9;
	return 0;