Boot Chain
Discord-PXE does not begin in a process, a kernel module, or a booted operating system. Its first useful execution environment is the PXE firmware itself.
The boot path therefore has to preserve enough firmware state to keep using PXE services while simultaneously preparing a larger runtime that will eventually take over the machine.
Primary source:
Two-stage model
network boot firmware
│
▼
small PXE NBP / stage 1
│
├─ preserve firmware entry state
├─ obtain cached DHCP/PXE data
├─ TFTP runtime.bin
└─ close transfer
│
▼
runtime.bin @ physical 0x20000
│
├─ platform discovery
├─ memory map
├─ PXE metadata
├─ paging
└─ x86-64 long mode
│
▼
64-bit bare-metal runtime
The split exists because the runtime has grown beyond what is convenient to treat as one tiny PXE network bootstrap program.
Preserving the PXE entry environment
Older PXE ROMs can depend on the stack they used when entering the network bootstrap program.
The stage-1 loader captures the original:
SS
SP
client IP
server IP
gateway IP
subnet
router
DNS
into a fixed handoff structure below the later runtime/thunk region.
This is not decorative metadata. It exists so later code can make firmware calls without guessing which stack or network configuration the PXE ROM expects.
A region that appears unused from the perspective of the boot image may still contain a stack or scratch area used by the firmware implementation. Overwriting it before the firmware call path is finished can produce failures that look unrelated to memory corruption.
Guarding firmware-sensitive RAM
The loader source documents a real example: the target PXE entry stack occupied memory around physical 0x83E68, while the growing runtime image eventually crossed that address during TFTP download.
The fix is not "hope the ROM no longer uses it." The loader explicitly preserves a neighborhood around the PXE stack while the transfer is active.
That is a recurring bare-metal rule in this project:
firmware-visible state must remain valid until the firmware dependency is actually gone.
PXENV+ validation
The loader requests the PXE API through BIOS interrupt 0x1A and checks the returned PXENV+ structure.
Validation includes:
- the
PXENV+signature - a non-zero structure length
- checksum verification
- extraction of the real-mode PXE entry point
A malformed or missing PXE environment is a hard stop, because every later TFTP operation depends on that contract.
Code example of validating a PXENV+ structure
- NASM equivalent
- Execution order
- Rust
- C
- Python
; SI = pointer to PXENV+ structure
cmp byte [si + 0], 'P'
jne .bad
cmp byte [si + 1], 'X'
jne .bad
cmp byte [si + 2], 'E'
jne .bad
cmp byte [si + 3], 'N'
jne .bad
cmp byte [si + 4], 'V'
jne .bad
cmp byte [si + 5], '+'
jne .bad
movzx cx, byte [si + 8] ; declared structure length
test cx, cx
jz .bad
xor ax, ax ; AL accumulates 8-bit checksum
xor bx, bx
.sum:
add al, [si + bx]
inc bx
cmp bx, cx
jb .sum
test al, al ; PXENV+ checksum must wrap to zero
jnz .bad
This is a teaching equivalent of the validation logic, not a verbatim extract from the loader.
1. Verify the six-byte `PXENV+` signature.
2. Read the structure's declared length.
3. Reject a zero or invalid length.
4. Sum every byte in the declared structure with 8-bit wraparound.
5. Accept the structure only when the checksum wraps to zero.
6. Only then trust fields such as the PXE entry point.
fn validate_pxenv(bytes: &[u8]) -> bool {
if bytes.len() < 9 || &bytes[0..6] != b"PXENV+" {
return false;
}
let len = bytes[8] as usize;
if len == 0 || len > bytes.len() {
return false;
}
bytes[..len]
.iter()
.fold(0u8, |sum, byte| sum.wrapping_add(*byte)) == 0
}
int validate_pxenv(const uint8_t *p, size_t available) {
if (available < 9 || memcmp(p, "PXENV+", 6) != 0)
return 0;
uint8_t len = p[8];
if (len == 0 || len > available)
return 0;
uint8_t sum = 0;
for (size_t i = 0; i < len; ++i)
sum = (uint8_t)(sum + p[i]);
return sum == 0;
}
def validate_pxenv(data: bytes) -> bool:
if len(data) < 9 or data[:6] != b"PXENV+":
return False
length = data[8]
if length == 0 or length > len(data):
return False
return sum(data[:length]) & 0xFF == 0
Cached DHCP/PXE state
The stage-1 path uses PXENV_GET_CACHED_INFO to recover network state already negotiated by firmware.
That avoids implementing a second competing DHCP client just to discover values firmware already knows.
The handoff stores network values that the later runtime can reuse after switching execution modes.
Loading runtime.bin
The first-stage image opens a TFTP transfer through PXE services and copies the larger runtime to physical address 0x20000.
The code deliberately protects:
- loader state
- the firmware stack guard area
- the later runtime destination
- fixed handoff structures
while the copy is in progress.
Once the transfer is complete and closed, execution jumps to the runtime segment.
Runtime relocation and bootstrap stack
The larger runtime still begins in 16-bit mode.
It relocates its working segments and chooses a bootstrap stack below the runtime image so continued growth of runtime.bin cannot silently overwrite return addresses or counters.
This was specifically changed after the older high stack began overlapping executable bytes as the runtime grew.
Comments about memory addresses in this source are not historical clutter. They document concrete hardware failures that occurred as the binary layout changed. In a bare-metal project, preserving that context prevents a future "cleanup" from reintroducing an invisible platform dependency.
Platform discovery before long mode
Before leaving the firmware-oriented environment, the runtime collects or validates:
- BIOS E820 memory map
- PXE API information
- cached network information
- x86-64 CPU capability
If long mode support is unavailable, the runtime cannot continue.
Long-mode transition
The runtime then:
- enables A20
- constructs page tables
- loads a GDT
- enables PAE
- loads CR3
- enables EFER.LME
- enables paging/protected mode
- far-jumps into the 64-bit code segment
The page-table setup uses fixed low physical addresses during transition.
Why PXE remains visible after boot
Discord-PXE is not merely "loaded using PXE." PXE affects early memory layout, firmware call safety, cached network state, and the design of the stage-1/runtime boundary.
That is why the project treats the boot chain as an engineering subsystem rather than as a build/deployment detail.
Failure boundaries
Useful boot failures are intentionally distinguishable:
PXE API unavailable
PXENV+ invalid
cached DHCP unavailable
TFTP runtime load failed
x86-64 unsupported
long-mode transition failed
runtime started but later subsystem failed
Serial breadcrumbs are inserted early enough to place the failure before most higher-level systems exist.
See Diagnostics.
Long-mode activation sequence
The following is the real transition sequence from pxe_boot.asm. The Rust, C, and Python tabs are semantic equivalents. The NASM tab is the authoritative implementation.
- NASM original
- Execution order
- Rust equivalent
- C equivalent
- Python model
| Symbol / register | Meaning |
|---|---|
gdt_ptr | Pointer to the temporary GDT used for the mode switch. |
PML4_PHYS | Physical address of the PML4 page, currently 0x90000. |
CR4.PAE | Enables Physical Address Extension, required by IA-32e paging. |
IA32_EFER.LME | Long Mode Enable bit in MSR 0xC0000080. |
CR0.PG | Turns paging on. |
CR0.PE | Protected-mode enable bit. |
0x08 | 64-bit code-segment selector in the GDT. |
long_mode_entry | First instruction stream executed with a 64-bit CS. |
cli ; Stop maskable interrupts during descriptor/control-register changes.
lgdt [gdt_ptr] ; Load GDTR with the temporary GDT descriptor.
mov eax, cr4 ; Read the current CR4 feature bits.
or eax, 1 << 5 ; Set CR4.PAE, required for IA-32e page-table format.
mov cr4, eax ; Commit the updated CR4 value.
mov eax, PML4_PHYS ; EAX = physical address of the prepared PML4.
mov cr3, eax ; Make that PML4 the active paging root.
mov ecx, 0xC0000080 ; ECX = IA32_EFER MSR number.
rdmsr ; Read EFER into EDX:EAX.
or eax, 1 << 8 ; Set EFER.LME (Long Mode Enable).
wrmsr ; Write the modified EFER value back.
mov eax, cr0 ; Read CR0.
or eax, (1 << 31) | 1 ; Set CR0.PG (paging) and CR0.PE (protected mode).
mov cr0, eax ; Activates paging with long mode armed.
db 0x66,0xEA ; Emit operand-size override + far-jump opcode manually.
dd RELOC_BASE + long_mode_entry ; 32-bit offset of the 64-bit entry point.
dw 0x08 ; GDT selector for the 64-bit code segment.
1. Disable maskable interrupts and load the temporary GDT.
2. Enable CR4.PAE so IA-32e page-table format is legal.
3. Point CR3 at the prepared PML4.
4. Set IA32_EFER.LME to arm long mode.
5. Enable CR0.PG and CR0.PE.
6. Far-jump through the 64-bit code-segment selector to complete the transition.
struct CpuControl {
cr0: u64,
cr3: u64,
cr4: u64,
efer: u64,
}
fn enter_long_mode(cpu: &mut CpuControl) {
const CR4_PAE: u64 = 1 << 5;
const EFER_LME: u64 = 1 << 8;
const CR0_PE: u64 = 1 << 0;
const CR0_PG: u64 = 1 << 31;
cpu.cr4 |= CR4_PAE;
cpu.cr3 = 0x90000;
cpu.efer |= EFER_LME;
cpu.cr0 |= CR0_PE | CR0_PG;
far_jump64(0x08, RELOC_BASE + LONG_MODE_ENTRY);
}
void enter_long_mode(void) {
uint64_t cr4 = read_cr4();
cr4 |= (1ull << 5);
write_cr4(cr4);
write_cr3(0x90000);
uint64_t efer = rdmsr(0xC0000080);
efer |= (1ull << 8);
wrmsr(0xC0000080, efer);
uint64_t cr0 = read_cr0();
cr0 |= (1ull << 31) | 1ull;
write_cr0(cr0);
far_jump64(0x08, RELOC_BASE + long_mode_entry);
}
def enter_long_mode(cpu):
CR4_PAE = 1 << 5
EFER_LME = 1 << 8
CR0_PE = 1 << 0
CR0_PG = 1 << 31
cpu.cr4 |= CR4_PAE
cpu.cr3 = 0x90000
cpu.efer |= EFER_LME
cpu.cr0 |= CR0_PE | CR0_PG
cpu.far_jump(selector=0x08, target="long_mode_entry")
This Python is a state-machine model. Python cannot actually write control registers, MSRs, or reload CS.
The CPU is not fully executing 64-bit code merely because EFER.LME is set. Long mode becomes active through the combination of PAE, long-mode-capable paging, CR0.PG, and finally loading a 64-bit code segment with the far jump.
After the far jump, CS references the 64-bit descriptor and execution continues at long_mode_entry. If the descriptor, page tables, or control bits are wrong, the machine typically faults or resets before higher-level diagnostics exist.
State after the block
CR4.PAE = 1CR3 = PML4_PHYSIA32_EFER.LME = 1CR0.PG = 1CR0.PE = 1- CS has been reloaded with selector
0x08 - instruction decoding is now on the 64-bit path
PXE firmware stack handoff
Some PXE ROMs remain dependent on the stack they used when entering the NBP. Discord-PXE temporarily restores that exact stack around firmware calls.
- NASM original
- Execution order
- Rust equivalent
- C equivalent
- Python model
| Symbol / register | Meaning |
|---|---|
pxe_initial_ss:sp | Stack supplied by the PXE ROM when it invoked the NBP. |
loader_ss:sp | Discord-PXE loader stack that must be restored after the firmware call. |
pxe_entry | Real-mode far pointer to the PXE firmware API entry point. |
BX | PXE function opcode supplied by the caller. |
ES:DI | PXE parameter-block pointer supplied by the caller. |
pxe_call: ; BX=function, ES:DI=PXE parameter block.
cli ; Prevent an interrupt from using a half-switched stack.
mov [cs:loader_sp], sp ; Save Discord-PXE's current SP.
mov ax, ss ; AX = Discord-PXE's current SS.
mov [cs:loader_ss], ax ; Save Discord-PXE's current SS.
mov ax, [cs:pxe_initial_ss] ; AX = stack segment originally supplied by PXE ROM.
mov ss, ax ; Restore firmware's expected stack segment.
mov sp, [cs:pxe_initial_sp] ; Restore firmware's expected stack pointer.
sti ; Firmware call may now execute with interrupts enabled.
push ds ; Preserve caller-visible DS across firmware.
push si ; Preserve SI.
push bp ; Preserve BP.
call far [cs:pxe_entry] ; Invoke the PXE ROM through its far real-mode entry point.
pop bp ; Restore BP after firmware returns.
pop si ; Restore SI.
pop ds ; Restore DS.
cli ; Protect the stack transition back to the loader.
mov dx, [cs:loader_ss] ; DX = saved loader SS.
mov ss, dx ; Restore loader stack segment.
mov sp, [cs:loader_sp] ; Restore loader stack pointer.
cld ; Re-establish DF=0 because firmware is not trusted to preserve it.
sti ; Resume normal interrupt handling.
ret ; Return to the loader caller.
1. Disable interrupts before changing the active stack.
2. Save Discord-PXE's current SS:SP.
3. Restore the SS:SP originally supplied by the PXE firmware.
4. Call the PXE ROM through its real-mode far entry point.
5. Restore the loader's SS:SP.
6. Clear DF, re-enable interrupts, and return to the loader.
fn call_pxe(
cpu: &mut Cpu,
entry: PxeEntry,
opcode: u16,
params: &mut Params,
) -> PxeResult {
let loader_stack = cpu.save_stack();
cpu.disable_interrupts();
cpu.load_stack(PXE_INITIAL_SS, PXE_INITIAL_SP);
cpu.enable_interrupts();
let result = entry(opcode, params);
cpu.disable_interrupts();
cpu.restore_stack(loader_stack);
cpu.clear_direction_flag();
cpu.enable_interrupts();
result
}
PxeResult pxe_call(PxeEntry entry, uint16_t opcode, void *params) {
StackState loader = save_stack();
cli();
load_stack(pxe_initial_ss, pxe_initial_sp);
sti();
PxeResult result = entry(opcode, params);
cli();
restore_stack(loader);
cld();
sti();
return result;
}
def call_pxe(cpu, pxe_entry, opcode, params):
loader_stack = cpu.save_stack()
cpu.interrupts_enabled = False
cpu.load_stack(pxe_initial_ss, pxe_initial_sp)
cpu.interrupts_enabled = True
result = pxe_entry(opcode, params)
cpu.interrupts_enabled = False
cpu.restore_stack(loader_stack)
cpu.direction_flag = 0
cpu.interrupts_enabled = True
return result
The Python version models the ownership transition. Real-mode SS:SP replacement is inherently CPU-specific.
CLI surrounds the stack swapAn interrupt between writing SS and restoring the matching SP could push an interrupt frame onto the wrong stack. The critical section keeps the pair coherent.
This is not a normal C ABI call. The firmware contract includes segment registers, a far function pointer, a caller-provided PXE opcode in BX, and a parameter block in ES:DI.
State after the block
Discord-PXE is back on its own loader stack, DF=0, the PXE call has completed, and the caller can inspect the firmware status/result fields without leaving the ROM's stack active.