Global variables not working in my kernel

2026-05-19

Stack Overflow: View Question

Tags: c, x86, linker, osdev, watcom

Score: 2 | Views: 169

The asker is building a 16-bit real-mode kernel in C using Open Watcom. Local variables work fine — they can print to VGA memory at 0xB8000 — but as soon as they promote those variables to file-scope globals, the kernel misbehaves. This is one of the most classic OSdev rites of passage, and the answer almost always lives at the seam between the compiler, the linker script, and the boot loader.

Why it's interesting: a hosted C program gets BSS zeroing and DATA initialization for free from the C runtime startup (crt0). A freestanding kernel has no such luxury. When you transition from locals to globals, you suddenly depend on three invariants that nobody set up for you:

Direction toward a solution:

  1. Have Watcom emit a map file (wlink ... option map) and inspect where .data and .bss were placed. Compare those addresses to what your boot loader is actually loading. If the boot loader only reads the .text bytes from disk, globals with initializers will be uninitialized in RAM.
  2. Check your boot loader's DS setup. A common bug: setting CS via a far jump but leaving DS=0, so accesses to a global at linear address 0x10000 get resolved relative to segment 0.
  3. Zero BSS explicitly in your kernel entry stub before calling C code: walk from _bss_start to _bss_end writing zeros.
  4. For Watcom specifically, watch the memory model. -ms (small), -mc (compact), -ml (large) change how the compiler emits pointer references to globals. A model mismatch between the kernel and its boot stub leads to exactly this "locals work, globals don't" symptom.

Gotchas: VGA_MEM 0xB8000000UL in the snippet looks like a far pointer encoding (segment 0xB800, offset 0x0000) — fine if Watcom's far qualifier is honored, but if a global pointer is dereferenced via the default DS, the whole computation collapses. Also: if the boot loader loads the kernel above the 64 KB boundary, any 16-bit near pointer to a global silently truncates.

The challenge: "Locals work, globals don't" in a freestanding 16-bit kernel is rarely a C bug — it's a missing piece of the cooperation between linker section layout, boot-time loading, and segment-register setup.

All newsletters