| 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.
| Property | Value |
|---|---|
| Magic bytes | "HC" (0x48, 0x43) |
| Header size | 16 bytes |
| Relocation granularity | 16-bit words (2 bytes per entry) |
| Supported CPUs | Z80, 8080, 8085, 8086 |
| Produced by | hclink-rex |
+-----------------------+ 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 | | +-----------------------+
| Offset | Size | Field | Description |
|---|---|---|---|
| 0x00 | 2 | Magic | Signature bytes: 'H' 'C' (0x48, 0x43) |
| 0x02 | 2 | Text size | Size of executable code section in bytes |
| 0x04 | 2 | Data size | Size of initialized data section in bytes |
| 0x06 | 2 | BSS size | Size of uninitialized data section in bytes (allocated and zeroed at load time) |
| 0x08 | 2 | Entry offset | Offset of _start label relative to text section base |
| 0x0A | 2 | Reloc count | Number of entries in relocation table |
| 0x0C | 3 | Reserved | Padding bytes (must be zero) |
| 0x0F | 1 | CPU ID | Target processor identifier |
| CPU ID | Processor | Assembler | Compiler |
|---|---|---|---|
| 0xF0 | Intel 8080 | hcasm-8080 | hcbcomp-8080 |
| 0xF1 | Intel 8085 | hcasm-8085 | hcbcomp-8085 |
| 0xF2 | Zilog Z80 | hcasm-z80 | hcbcomp-z80 |
| 0xF3 | Intel 8086 | hcasm-8086 | hcbcomp-8086 |
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.
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:
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!$"
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).
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();
}
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
| Feature | Flat Binary (COM) | MZ EXE | REX |
|---|---|---|---|
| Relocatable | No | Yes (segment-based) | Yes (word-based) |
| Header size | 0 bytes | 28+ bytes | 16 bytes |
| Segments | Single flat | Independent (TEXT/DATA/BSS) | Contiguous |
| Loader complexity | Trivial | Moderate (MZ header parsing) | Simple (add base) |
| Use case | CP/M .COM, ROMs | MS-DOS .EXE | Custom OS, embedded |
| Produced by | hclink-bin | hclink-mz | hclink-rex |
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 |