REX — Relocatable Executable Format

The REX (Relocatable Executable) format is a compact binary format with a built-in relocation table, allowing programs to be loaded at any memory address. Unlike flat binaries (which are fixed to a specific address), REX executables can be relocated by the loader at runtime.

PropertyValue
Magic bytes"HC" (0x48, 0x43)
Header size16 bytes
Relocation granularity16-bit words (2 bytes per entry)
Supported CPUsZ80, 8080, 8085, 8086
Produced byhclink-rex

File Layout

+-----------------------+  offset 0x0000
|     Header            |  16 bytes
|   (magic, sizes,      |
|    entry, CPU ID)     |
+-----------------------+
|                       |
|     Text Section      |  text_size bytes
|    (executable code)  |
|                       |
+-----------------------+
|                       |
|     Data Section      |  data_size bytes
|  (initialized data)   |
|                       |
+-----------------------+
|                       |
|     BSS Section       |  bss_size bytes (zero-filled at load)
|  (uninitialized data) |
|                       |
+-----------------------+
|                       |
|   Relocation Table    |  reloc_count entries × 2 bytes
|                       |
+-----------------------+

Header Format (16 bytes)

OffsetSizeFieldDescription
0x002MagicSignature bytes: 'H' 'C' (0x48, 0x43)
0x022Text sizeSize of executable code section in bytes
0x042Data sizeSize of initialized data section in bytes
0x062BSS sizeSize of uninitialized data section in bytes (allocated and zeroed at load time)
0x082Entry offsetOffset of _start label relative to text section base
0x0A2Reloc countNumber of entries in relocation table
0x0C3ReservedPadding bytes (must be zero)
0x0F1CPU IDTarget processor identifier

CPU Identifiers

CPU IDProcessorAssemblerCompiler
0xF0Intel 8080hcasm-8080hcbcomp-8080
0xF1Intel 8085hcasm-8085hcbcomp-8085
0xF2Zilog Z80hcasm-z80hcbcomp-z80
0xF3Intel 8086hcasm-8086hcbcomp-8086

Relocation Table

The relocation table follows the DATA section on file. Each entry is a 2-byte offset relative to the text section base. The total size of the table is reloc_count × 2 bytes.

How Relocation Works

During compilation, instructions that reference absolute addresses (e.g., ld hl, label on Z80 or mov ax, label on 8086) are emitted with placeholder values. The linker records the offset of each such reference in the relocation table.

At load time, the loader:

  1. Allocates a memory block of text_size + data_size + bss_size bytes at base address B
  2. Copies the text and data sections into the block
  3. Zero-fills the BSS section
  4. For each relocation entry E:
  5. Jumps to B + entry_offset to begin execution

Example: Z80 Relocation

Consider this Z80 code:

section text
global _start
_start:
    ld hl, message      ; This absolute address must be relocated
    ld c, 9
    call 5
    ret

section data
message: db "Hello!$"

The ld hl, message instruction is encoded as 21 XX XX where XX XX is the address of message. The linker records the offset of the XX XX bytes (offset 1 from _start) in the relocation table.

If the loader places the program at base address 0x4000:

; Before relocation:
0x4000: 21 00 10    ; ld hl, 0x1000 (placeholder)

; Relocation entry: offset 1
; Word at B+1 = 0x1000 (original offset)
; New value = 0x1000 + 0x4000 = 0x5000

; After relocation:
0x4000: 21 00 50    ; ld hl, 0x5000 (actual message address)
0x5000: "Hello!$"

Example: 8086 Relocation

section text
global _start
_start:
    mov ax, message     ; B8 XX XX — immediate to AX
    ; ...

section data
message: db "Hello!$"

Same principle as Z80, but the instruction is B8 XX XX. The relocation entry points to the XX XX bytes (offset 1).

Loading Algorithm (C pseudocode)

struct rex_header {
    uint16_t magic;        // 'HC'
    uint16_t text_size;
    uint16_t data_size;
    uint16_t bss_size;
    uint16_t entry_offset;
    uint16_t reloc_count;
    uint8_t  reserved[3];
    uint8_t  cpu_id;
};

void load_rex(FILE *f, uint16_t base_addr) {
    struct rex_header hdr;
    fread(&hdr, 18, 1, f);

    if (hdr.magic != 0x4348) error("bad magic");

    uint16_t total = hdr.text_size + hdr.data_size + hdr.bss_size;
    uint8_t *mem = malloc(total);
    memset(mem, 0, total);

    // Copy sections
    fread(mem, hdr.text_size, 1, f);              // text
    fread(mem + hdr.text_size, hdr.data_size, 1, f); // data
    // BSS already zeroed by memset

    // Apply relocations
    for (int i = 0; i < hdr.reloc_count; i++) {
        uint16_t offset;
        fread(&offset, 2, 1, f);
        uint16_t *word = (uint16_t *)(mem + offset);
        *word += base_addr;
    }

    // Execute
    void (*entry)(void) = (void (*)(void))(mem + hdr.entry_offset);
    entry();
}

Creating REX Executables

Use hclink-rex:

hclink-rex -o program.rex main.obj lib.obj
hclink-rex -sym symbols.txt -o program.rex main.obj

Or via the project builder with format = rex:

[link:release]
format = rex
filename = program.rex

Comparison with Other Formats

FeatureFlat Binary (COM)MZ EXEREX
RelocatableNoYes (segment-based)Yes (word-based)
Header size0 bytes28+ bytes16 bytes
SegmentsSingle flatIndependent (TEXT/DATA/BSS)Contiguous
Loader complexityTrivialModerate (MZ header parsing)Simple (add base)
Use caseCP/M .COM, ROMsMS-DOS .EXECustom OS, embedded
Produced byhclink-binhclink-mzhclink-rex

REX File Dump Example

Here's how a simple "Hello World" Z80 program looks as a REX file (hex dump):

00000000: 48 43 0E 00 0C 00 00 00 00 00 01 00 00 00 00 F2  HC..............
00000010: 00 00 21 0E 00 0E 0E CD 05 00 C9 48 65 6C 6C  ..!........Hell
00000020: 6F 21 24 00 00 00 00 00 00 00 01 00              o!$.........

Header breakdown:
  48 43       = Magic "HC"
  0E 00       = Text size: 14 bytes (code)
  0C 00       = Data size: 12 bytes ("Hello!$\0\0\0\0\0\0")
  00 00       = BSS size: 0 bytes
  00 00       = Entry offset: 0 (execution starts at text base)
  01 00       = Reloc count: 1 entry
  00 00 00    = Reserved
  F2          = CPU ID: Z80

  00 00       = Text section (14 bytes of Z80 code)
  ...
  48 65 ...   = Data section ("Hello!$")

  01 00       = Reloc entry: offset 1 (patch the address in ld hl, XXXX)

[Linker Documentation] · [Assembler Documentation] · [Target CPUs]


HC SDK for Retro Computing v2.1 R8 Changelog