Skip to content
Techniques & Technology

Position-Independent Code

Code that runs anywhere

Code that executes correctly regardless of memory location, using PC-relative addressing rather than absolute addresses, pioneered on the 6809 and standard in modern systems.

cross-platform assembly6809addressingrelocation

Overview

Position-independent code (PIC) executes correctly regardless of where it's loaded in memory. Rather than using absolute addresses, PIC uses PC-relative addressing — every reference is PC + offset rather than a fixed location. The 6809 was the first 8-bit CPU to make PIC fully practical; modern operating systems rely on it for shared libraries and ASLR.

Fast Facts

  • Concept: Code that works at any address it's loaded at.
  • Method: PC-relative addressing for both branches and data.
  • 8-bit pioneer: Motorola 6809 (1978) — the first to support PCR for data access, not just branches.
  • Modern use: Shared libraries (.so / .dylib / .dll), ASLR, position-independent executables.

Why It Matters

BenefitApplication
RelocationLoad anywhere in memory; no fix-up table
Shared librariesOne copy of libc.so serves every process at different addresses
Security (ASLR)Randomised code locations defeat ROP attacks
ROM cartridgesSame code works whether mapped to $8000 or $C000
Demo cracks/trainersInject patches at any free address

Support across CPUs

The big difference between CPUs is whether only branches are PC-relative or whether loads/stores can be too:

CPUBranchesPC-relative loadsPIC verdict
6502 / 6510Bxx (signed 8-bit) onlyNoneHard — must use SMC or trampolines for non-trivial PIC
Z80JR, JR cc, DJNZ (signed 8-bit) onlyNoneHard — same workarounds as 6502
6809BRA / LBRA / Bcc (signed 8/16-bit)LDA offset,PCR and similar via PCR addressing modeDesigned in — fully practical
68000Bcc (signed 8/16-bit), BRAMOVE (PC,d),Dn, LEA (label,PC),AnLoads yes, stores no (deliberate ROM-friendly choice)
ARMB / BL (signed 24-bit)LDR Rd,[PC,#offset] ("literal pool" idiom)Native — almost everything is PC-relative-friendly
x86 (16/32)Jcc (relative)None for general data; CALL +0; POP trick used pre-x64Awkward
x86-64Jcc (relative)RIP-relative addressing on most loadsNative — required for OS-level PIC

The 6502 / Z80 lineage's lack of PC-relative loads is the reason 8-bit code in those families is overwhelmingly written for fixed addresses. When relocation is needed, the loader rewrites a table of fix-up offsets — the cost the 6809 pays once at design time, the 6502 pays at every load.

6809 implementation

; PC-relative data access
    LEAX    DATA,PCR        ; X = effective address of DATA, computed as PC+offset
    LDA     ,X              ; Load byte at DATA via X

; Direct PC-relative load
    LDA     COUNTER,PCR     ; Load byte at COUNTER, address resolved against PC

; Relative branching
    BRA     LABEL           ; Always relative on 6809 (signed 8-bit)
    LBRA    FAR_LABEL       ; Long relative branch (signed 16-bit)

The assembler emits the offset between the current PC and the target; the runtime fetch reconstructs the absolute address. Move the entire block of code anywhere in memory and it still works.

PIC vs SMC

PIC and self-modifying code solve overlapping problems but at opposite ends of a spectrum:

Position-independentSelf-modifying
AddressesComputed each time from PCPatched into the instruction stream
CostSlightly more cycles per accessOne write; subsequent accesses are fastest possible
RelocatabilityFree — the code carries its own offsetsNone — addresses bake into instructions on first use
Modern viabilityStandardForbidden by W^X memory protection

On a Z80 or 6502, SMC often replaced PIC because the CPU forced your hand: there was no PCR mode to exploit, and SMC was faster than indexed indirection.

ROM cartridge example

A 6809 cartridge that wants to be mappable at either $8000-$BFFF or $C000-$FFFF writes every internal jump and data reference as PC-relative. The same image runs in both windows without an external relocation table. A 6502 cartridge in the same situation either:

  • Bakes in one address and refuses to work at the other, or
  • Carries a relocation table that the boot routine walks to fix up absolute references at startup.

Modern shared libraries take the same approach as the 6809 cartridge, scaled up: the dynamic linker maps the library at any address and the PC-relative references just work.

Modern relevance

PIC is now the default rather than the exception:

  • Shared libraries load at runtime-chosen addresses; PIC means no copy-and-relocate.
  • ASLR (Address Space Layout Randomisation) randomises code base addresses to defeat exploits that depend on known gadget locations; PIC is what makes ASLR cheap.
  • Dynamic linking relies on the PIC + GOT (Global Offset Table) pattern: code refers to data via PC-relative loads of GOT entries, which the linker fills in at load time.

Legacy

The 6809's PCR mode was revolutionary in 1978. The orthogonality of "any addressing mode can use PC as its base register" prefigured how RISC ISAs would design PIC support a decade later. The 6502 / Z80 / 8086 family did not get clean PIC for data until much later (x86-64 finally added RIP-relative addressing in 2003) — a 25-year lag from a chip that was already shipping in the TRS-80 Color Computer.

See Also