-
Notifications
You must be signed in to change notification settings - Fork 0
Build BootImageBuilder Internals
The BootImageBuilder (JNode Linker) combines precompiled Java classes, native kernel code, and bootstrap data into a single bootable executable image.
The BootImageBuilder (org.jnode.build.x86.BootImageBuilder) is JNode's equivalent of a static linker. It takes the nano-kernel (pre-assembled x86 code from kernel.asm, vm.asm), pre-compiled Java classes, and bootstrap data structures, and combines them into a single binary that can be booted by GRUB. The resulting image follows the Multiboot specification and is loaded at LOAD_ADDR (1MB / 0x100000).
The build process operates in two modes (controlled by enableJNasm):
- ELF mode (default): Copies a pre-assembled native kernel ELF file
-
JNasm mode: Assembles
.asmsource files at build time using JNode's custom assembler
| Class / File | Role |
|---|---|
builder/src/builder/org/jnode/build/x86/BootImageBuilder.java |
x86-specific builder - combines kernel + classes + bootstrap data |
builder/src/builder/org/jnode/build/AbstractBootImageBuilder.java |
Base class - class loading, compilation, emission orchestration |
builder/src/builder/org/jnode/build/BuildObjectResolver.java |
Object resolution during build - resolves object references to image offsets |
core/src/core/org/jnode/vm/classmgr/ObjectLayout.java |
Object header size constants (HEADER_SLOTS=2, OBJECT_ALIGN=8) |
core/src/core/org/jnode/vm/classmgr/VmType.java |
Class representation with initializeForBootImage(), loadFromBootClassArray()
|
core/src/core/org/jnode/boot/Main.java |
Entry point vmMain() called by boot image |
core/src/native/x86/kernel.asm |
Nano-kernel source with Multiboot header |
core/src/native/x86/vm.asm |
VM support routines (context switch, exception handling) |
The boot image follows this memory layout (low to high addresses):
+------------------+ <- LOAD_ADDR (0x100000 / 1MB)
| Multiboot Header | Magic: 0x1BADB002, flags, checksums, header fields
+------------------+ (must fit within first 8KB)
| Native Kernel | <- Copied from ELF (kernel.asm, vm.asm, mm32/64.asm)
| (vm.asm, etc.) | Contains entry point, context switch, paging setup
+------------------+
| JUMP_MAIN_OFFSET | <- Exact offset: (HEADER_SLOTS+1) * SLOT_SIZE
| Bootstrap Code | Intro code: JMP instruction to Main.vmMain
+------------------+
| VmSystemObject | Initial stack: low address, high address, then zeros
| (Stack Object) | Grows downward; stack ptr = high address
+------------------+ <- bootHeapStart (after page alignment)
| Boot Heap | Pre-allocated objects, class data, compiled code
+------------------+ <- bootHeapEnd, imageEnd
| Page Alignment | Aligned to 4KB boundary
+------------------+
Key offsets computed by BootImageBuilder:
-
JUMP_MAIN_OFFSET32=ObjectLayout.objectAlign((HEADER_SLOTS + 1) * VmX86Architecture32.SLOT_SIZE)= 12 bytes on 32-bit -
JUMP_MAIN_OFFSET64= same formula for 64-bit = 16 bytes - The
VmMethodCodeobject starts atJUMP_MAIN_OFFSET, with its JMP instruction at the exact offset of a normal object header (2 slots = 8/16 bytes), followed by 4/8 bytes for the JMP = 12/24 bytes total -
INITIALIZE_METHOD_OFFSET= 8 (offset within the VmMethodCode object where native code begins)
Multiboot Header patching (lines 619-640):
- The header is embedded at the start of
kernel.asm -
patchHeader()fills inMB_LOAD_END_ADDRandMB_BSS_END_ADDRwith the final image size - This allows GRUB to know where the image ends and zero out BSS
Classes are pre-loaded and pre-resolved during build time using a multi-pass compilation loop:
-
VmType.initializeForBootImage(clsMgr) (VmType.java:347-417):
- Creates the 21 primitive/array/system classes (
Object,Class,Cloneable,Serializable,boolean,byte, ...,Object[]) - Links all of them (resolves their vtables)
- Called once at AbstractBootImageBuilder.java:482
- Creates the 21 primitive/array/system classes (
-
compileClasses() loop (AbstractBootImageBuilder.java:217-268):
- Iterates until no new classes are loaded and no new methods are compiled
- For each loaded class:
-
vmClass.link()- Resolves constant pool entries to method/field offsets - If
!vmClass.isCpRefsResolved()and high optimization level:vmClass.resolveCpRefs()- Fully resolves class hierarchy -
vmClass.compileBootstrap(compiler, os, optLevel)- Compiles all methods to native x86 code
-
-
prepareAfterBootstrap() (VmSystemClassLoader.java:240):
- Finalizes the boot class array passed to
loadFromBootClassArray() - Returns a
VmType[]of all loaded classes
- Finalizes the boot class array passed to
-
emitStaticInitializerCalls() (BootImageBuilder.java:526-565):
- Emits a
VmMethodCodeobject containing a call toVmType.loadFromBootClassArray(bootClasses) - This sets the static
VmTypefields (BooleanClass,ObjectClass, etc.) at boot time
- Emits a
// From BootImageBuilder.java:536-548
VmMethod lfbcaMethod = vmClassClass.getMethod("loadFromBootClassArray", "([Lorg/jnode/vm/classmgr/VmType;)V");
os.writeMOV_Const(aax, bootClasses);
os.writePUSH(aax);
os.writeMOV_Const(aax, lfbcaMethod);
os.writeMOV(abx.getSize(), abx, aax, nativeCodeField.getOffset());
os.writeCALL(abx);The kernel entry sequence transfers control from GRUB to Main.vmMain():
GRUB (multiboot)
-> kernel.asm (Multiboot entry point)
-> _start (arch-specific init: paging, GDT, IDT)
-> _$$introCode (in boot image, at JUMP_MAIN_OFFSET)
JMP to bootstrap code (sets up VM and thread state)
-> _$$clInitCaller (calls loadFromBootClassArray)
-> _$$Initial_call_to_clInitCaller (invokes clInitCaller method)
-> initCallMain (calls Main.vmMain)
-> Main.vmMain() (Java entry point)
Setup sequence in initImageHeader() (BootImageBuilder.java:263-313):
-
initVm() (lines 480-497): Sets up the
STATICSregister (EDI in 32-bit, RDI in 64-bit) to point to theVmSharedStaticstable, and initializesVmUtils.VM_INSTANCEto theVmImplobject -
initMain() (lines 507-524): Sets
Main.pluginRegistryto thePluginRegistryobject built at compile time -
initVmThread() (lines 416-441): Sets up the initial
VmThreadobject with its stack pointer from_$$initialStackPtr -
initCallMain() (lines 395-407): Calls
Main.vmMain()by:- Loading the
VmMethodforMain.vmMaininto a register - Accessing the
nativeCodefield of theVmMethodto get the compiled code address - CALLing that address
- Loading the
The STATICS register (EDI/RDI) is the root of all static field access. All static fields are stored in the VmSharedStatics table at fixed offsets, and the JIT compiler generates mov [EDI + offset], value for static stores.
Objects are emitted in a carefully orchestrated order (AbstractBootImageBuilder.java:546-610):
- First pass: Emit all objects except blocked ones (VM, processor, class manager, statics tables)
-
VM emission: Emit
VmImpl(twice, intentionally — line 555-556) -
Compiled methods list: Emit
CompiledMethods(twice — lines 561-565) -
Boot class array: Emit the
VmType[](twice — lines 569-573) -
Class manager: Emit
VmSystemClassLoader(twice — lines 577-582) -
Statics tables: Emit
VmSharedStaticsandVmIsolatedStatics(twice — lines 585-590) - Remaining objects: All others (line 594)
-
Static initializer calls: Emit the
_$$clInitCallermethod object -
Alignment: Page-align the end of the image, mark
_$$image_endand_$$bootHeapEnd
Each object in the image consists of:
- Object header (2 slots): Flags word (offset -2) and TIB pointer (offset -1)
- Instance fields: Follow the header, aligned to 8 bytes
-
Compiled code: Stored in separate
VmMethodCodeobjects, linked via thenativeCodefield
The x86 jump table (X86JumpTable.TABLE_LENGTH entries) is initialized by both initializeStatics() and copyKernel()/compileKernel():
- On 32-bit: Entry
iis at statics indexi(4-byte address per entry) - On 64-bit: Entry
iis at statics indexi * 2(each entry is 8 bytes) - Labels like
VmProcessor_reschedule,VmThread_runThreadare linked from native code to Java methodVmMethodobjects
-
JUMP_MAIN_OFFSET must equal object header size: The bootstrap jump MUST be placed at exactly the object header offset. This is verified at lines 268-274 — if the offset doesn't match, the build fails with a message to "set to Object headersize".
-
Emit phase runs twice for critical objects: Each critical object (VM, compiled methods list, boot classes, class manager, statics) is emitted twice to handle cross-references. The second pass resolves any references created during the first pass.
-
Statics indices are architecture-dependent: Jump table entries use index
ion 32-bit,i * 2on 64-bit (lines 719-730). This is because reference size differs (4 vs 8 bytes). -
Multiboot header must be within first 8KB: GRUB searches only the first 8KB of the image for the Multiboot magic. The header is embedded at the start of
kernel.asm. -
enableJNasm controls kernel source: If
true, kernel is assembled at build time from.asmfiles using JNasm. Iffalse, a pre-built ELF is copied (line 488-491 in AbstractBootImageBuilder). -
Blocked objects prevent premature emission: The
blockedObjectsset delays critical objects until all references are ready. VM, shared statics, class manager, and isolated statics tables are all blocked initially. -
INITIAL_OBJREFS_CAPACITY pre-allocation: The assembler pre-allocates space for 750,000 object references. If exceeded, a warning is logged to increase this constant for faster builds (line 664).
- Build-System - Overview of the build system
- Boot-Sequence - GRUB → assembly → VM init → plugin startup
- Object-Layout - Object header, TIB, field layout, alignment
- Type-System-Internals - VmType hierarchy, class loading
- JNasm-Assembler-Design - JNode's custom x86 assembler
- Plugin-System - Plugin loading in boot image
- Stack-Frame-Layout - x86 stack frame conventions used by compiled methods