-
Notifications
You must be signed in to change notification settings - Fork 1
Boot Log
JNode's logging system: pre-log4j boot logging, Log4j runtime configuration, and serial debug output.
JNode has a multi-layered logging system that operates from the earliest boot stages through full runtime. Understanding the layers is critical for debugging boot crashes, driver issues, and network problems.
| Layer | Mechanism | When Active | Output Target |
|---|---|---|---|
| Assembly KDB |
kdb_send_char in kdb.asm
|
Earliest boot → forever | VGA + COM1 (if kdb flag) |
| BootLog | BootLogImpl |
After InitialNaming → forever | F7 console / Unsafe.debug()
|
| Log4j | Log4jConfigurePlugin |
Plugin startup → forever | F7 console + active screen + serial (if lkd flag) |
Two kernel command-line flags control serial output:
| Flag | Purpose | Enables |
|---|---|---|
kdb |
Assembly-level kernel debugger serial I/O | All Unsafe.debug() output + every VGA character to COM1 |
lkd |
Log4j serial appender |
SerialAppender on root logger — all Log4j messages to COM1 |
GRUB config (all/conf/x86/menu-cdrom.lst):
serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
kernel /jnode32.gz mp=no kdb lkd
File: core/src/native/x86/kdb.asm
Before Java is initialized, the assembly kernel debugger provides serial I/O:
-
kdb_initreads BIOS Data Area at0x400to detect COM1 port address - Enables RTS and DTR via Modem Control Register
- Scans multiboot command line for
kdb— if found, setskdb_enabledflag -
kdb_send_charwaits for THR Empty (LSR bit 5), writes character to data register
Integration with VGA console (console.asm:149):
sys_do_print_char:
; ... write to VGA screen memory ...
call kdb_send_char ; simultaneously send to serial
Every character printed to VGA (via PRINT_STR, PRINT_INT, Unsafe.debug()) is simultaneously sent to COM1 when kdb is enabled. This is how early boot messages appear in serial logs.
Baud rate: Inherited from BIOS/GRUB settings (typically 9600 or 115200 depending on GRUB's serial --speed= directive).
Files: core/src/core/org/jnode/bootlog/BootLog.java, core/src/core/org/jnode/vm/BootLogImpl.java
After InitialNaming is set up, BootLogImpl.initialize() registers a lightweight logger:
VmSystem.initialize()
└─> InitialNaming.setNameSpace(new DefaultNameSpace())
└─> BootLogImpl.initialize()
└─> BootLogInstance.set(new BootLogImpl())
BootLogImpl formats timestamps using kernel uptime (VmSystem.currentKernelMillis()) rather than wall-clock time (VmSystem.currentTimeMillis()). This avoids an epoch-hours glitch where lines written after the RTC service starts would print absurd timestamps (e.g., 496697:28:26,317). The kernel uptime counter is monotonically increasing from boot and always correct.
| Level | Constant | Output Target |
|---|---|---|
| DEBUG | 1 |
debugOut PrintStream (if set) or Unsafe.debug()
|
| INFO | 2 | System.out |
| WARN | 3 | System.out |
| ERROR | 4 | System.err |
| FATAL | 5 | System.err |
If debugOut is null (before Log4jConfigurePlugin sets it), BootLogImpl.debug() falls back to Unsafe.debug() which goes to serial via KDB:
private void log(int level, PrintStream ps, String levelStr, String msg, Throwable ex) {
if (ps != null) {
writePrefix(ps, levelStr);
ps.println(msg);
} else {
writePrefixUnsafe(levelStr);
Unsafe.debug(msg);
Unsafe.debug("\n");
}
}-
No level filtering — All
debug(),info(),warn(),error(),fatal()calls print unconditionally - No category separation — Single global logger, no per-package filtering
-
setDebugOut()only affects DEBUG — Other levels use System.out/System.err directly
File: core/src/core/org/jnode/log4j/config/Log4jConfigurePlugin.java
Log4jConfigurePlugin is a JNode plugin that completely replaces the logging configuration at runtime.
| Appender | Threshold | Target | Purpose |
|---|---|---|---|
debugApp |
DEBUG | F7 "Log4j" console (hidden) | Captures all messages for debugging |
infoApp |
INFO | Active/visible screen | User-visible output |
serialApp |
DEBUG | COM1 UART (if lkd flag) |
Serial debug output |
UnsafeDebugAppender |
DEBUG |
Unsafe.debug() (fallback) |
Used if SerialAppender fails |
The root logger is set to INFO by default:
root.setLevel(Level.INFO);This means:
- INFO, WARN, ERROR, FATAL messages are processed by all appenders
- DEBUG messages are filtered at the logger level — they never reach appenders
- Exception: loggers with explicit levels (e.g., set via
log4j --setLevel) override this
File: core/src/core/org/jnode/log4j/config/SerialAppender.java
When the lkd boot flag is present, SerialAppender writes to COM1 via SerialPortDriver's API:
-
Port:
serial0(COM1, 0x3F8) - Data format: 8N1
-
Conversion:
\n→\r\n -
Lazy initialization: The serial port device may not be available when this appender is created (plugins start before device finders).
SerialWriter.init()usesDeviceUtils.getAPI()to acquireSerialPortAPI, and buffers pending output (up to 8KB) until the device becomes available. Once the device is acquired, buffered data is drained before new writes go direct. -
Fallback: If
SerialAppenderfails to acquire the device after retries, falls back toUnsafeDebugAppenderwhich routes throughUnsafe.debug()(VGA + serial at assembly level).
The log4j shell command provides runtime control:
# List all loggers and their effective levels
log4j --list
# Set root logger level (systemwide)
log4j --setLevel DEBUG # enable debug everywhere
log4j --setLevel INFO # back to default
# Set specific logger level
log4j --setLevel DEBUG org.jnode.driver.bus.ide
log4j --setLevel WARN org.jnode.driver.net.eepro100
# Load configuration from file
log4j /path/to/log4j.properties| Scenario | What's on COM1 |
|---|---|
Boot with kdb lkd
|
Assembly boot messages + all Log4j INFO+ messages |
log4j --setLevel DEBUG pkg |
Assembly boot + Log4j INFO+ + DEBUG from that package |
log4j --setLevel DEBUG |
Assembly boot + all Log4j DEBUG+ messages |
| Component | File | Purpose |
|---|---|---|
BootLog |
core/src/core/org/jnode/bootlog/BootLog.java |
Interface defining logging methods |
BootLogInstance |
core/src/core/org/jnode/bootlog/BootLogInstance.java |
Singleton accessor |
BootLogImpl |
core/src/core/org/jnode/vm/BootLogImpl.java |
Default implementation |
Log4jConfigurePlugin |
core/src/core/org/jnode/log4j/config/Log4jConfigurePlugin.java |
Runtime Log4j setup |
SerialAppender |
core/src/core/org/jnode/log4j/config/SerialAppender.java |
Log4j appender for COM1 UART |
VirtualConsoleAppender |
core/src/core/org/jnode/log4j/config/VirtualConsoleAppender.java |
Log4j appender for VGA consoles |
UnsafeDebugAppender |
core/src/core/org/jnode/log4j/config/UnsafeDebugAppender.java |
Fallback appender via Unsafe.debug()
|
kdb.asm |
core/src/native/x86/kdb.asm |
Assembly KDB serial I/O |
console.asm |
core/src/native/x86/console.asm |
VGA console + KDB integration |
Log4jCommand |
cli/src/commands/org/jnode/command/system/Log4jCommand.java |
Shell command for log4j control |
BootLogInstance.get().debug("Found " + extensions.length + " device finders");
BootLogInstance.get().warn("Ignoring unrecognised descriptor element: " + elementName);
BootLogInstance.get().error("Cannot find finder class " + className, ex);private static final Logger log = Logger.getLogger(MyClass.class);
log.debug("Detailed state: " + state); // only if DEBUG enabled for this package
log.info("Processing started"); // always visible (INFO+)
log.error("Failed to initialize", ex); // always visible# Enable debug for IDE driver
log4j --setLevel DEBUG org.jnode.driver.bus.ide
# Check what loggers exist
log4j --list
# Reset to default
log4j --setLevel INFO| Component | Port | Baud Rate | Configured By |
|---|---|---|---|
| GRUB serial console | COM1 | 115200 |
serial --unit=0 --speed=115200 in GRUB config |
| KDB assembly | COM1 | Inherited from BIOS/GRUB |
kdb_init in kdb.asm
|
| SerialAppender | COM1 (serial0) | 9600 default | Via SerialPortDriver API |
| SerialPortDriver | COM1-COM4 | 9600 default | SerialPortDriver.java |
| SerialConsolePlugin | COM2 | 115200 | SerialConsolePlugin.java |
Note: There is a baud rate mismatch between GRUB (115200), KDB (inherited), and SerialAppender (9600). If capturing serial at 115200, KDB output will be readable but SerialAppender output may be garbled. The SerialAppender now uses the device API (SerialPortAPI) rather than directly programming UART registers, so the baud rate is inherited from the SerialPortDriver's default.
-
Early boot DEBUG messages — Lines from assembly KDB before
Log4jConfigurePluginloads cannot be filtered by log4j. They always appear whenkdbflag is set. -
Per-sector logging —
IDEReadSectorsCommandandIDEWriteSectorsCommandlog debug messages for every sector. A 1MB read produces 512 debug messages. -
Per-packet logging —
EEPRO100Bufferlogs debug messages for every transmitted packet. -
Hardcoded DEBUG levels — Some classes (e.g.,
AbstractFontProvider,BDFFontContainer) previously hardcodedlog.setLevel(Level.DEBUG), bypassing root logger settings. These have been removed. -
Single serial client — The serial pipe (
/tmp/jnode.serial2) supports only one client at a time. - Socket not recreated — If you delete the serial socket while VM is running, VirtualBox does not recreate it. Restart the VM.
-
System.out routed to loggers —
DefaultAliasManagerandDefaultSyntaxManagernow log refresh messages at DEBUG level via Log4j instead of printing toSystem.out. This prevents shell initialization chatter from appearing on the active screen. -
Compiler warnings via BootLog —
X86BytecodeVisitorandX86StackFramenow report unbalanced item-factory warnings and negative slot counts viaBootLogInstanceinstead ofSystem.out, ensuring these diagnostics appear in the serial log.
- Boot-Sequence — Where logging fits in the boot process
- Serial-Port-Driver — Serial port hardware driver
-
VM-Unsafe —
Unsafe.debug()native method used by KDB and BootLog - InitialNaming-Service-Registry — Service registry BootLogInstance uses
- Architecture — System layers, where logging fits