<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Paull Knya-z</title>
    <description>The latest articles on DEV Community by Paull Knya-z (@paullknya).</description>
    <link>https://dev.to/paullknya</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3941647%2F0033944f-d96b-4ef2-aaf4-f78e85af66f3.png</url>
      <title>DEV Community: Paull Knya-z</title>
      <link>https://dev.to/paullknya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/paullknya"/>
    <language>en</language>
    <item>
      <title>My OS has a new name: meet Snorkel‑OS</title>
      <dc:creator>Paull Knya-z</dc:creator>
      <pubDate>Sat, 23 May 2026 14:28:41 +0000</pubDate>
      <link>https://dev.to/paullknya/my-os-has-a-new-name-meet-snorkel-os-162m</link>
      <guid>https://dev.to/paullknya/my-os-has-a-new-name-meet-snorkel-os-162m</guid>
      <description>&lt;p&gt;&lt;strong&gt;Snorkel‑OS&lt;/strong&gt; – unusual, isn't it? Let me explain.&lt;/p&gt;

&lt;p&gt;A snorkel is a tube that lets a diver breathe while staying just below the surface, observing the underwater world without interruption. For me, a computer is a similar “parallel world” full of mysteries. My operating system is a view from the outside, but as close as possible to the hardware and the code. I renamed the project to emphasise this metaphor.&lt;/p&gt;

&lt;p&gt;Technically, the kernel is still called Paull‑kernel (a 32‑bit x86 monolithic kernel written in NASM and C). But the whole operating system is now named Snorkel‑OS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The kernel is what's inside. The OS is how we see it from the outside.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My next technical article (about the FAT12 file system) is coming soon. For now, just remember the new name.&lt;/p&gt;

&lt;p&gt;my OS link:  &lt;a href="https://github.com/Paullknya/Paull-kernel" rel="noopener noreferrer"&gt;https://github.com/Paullknya/Paull-kernel&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;forum(mailing list):  &lt;a href="https://paullknya.github.io/" rel="noopener noreferrer"&gt;https://paullknya.github.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy coding! 🐟&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>systems</category>
    </item>
    <item>
      <title>Booting from FAT12: How I added file reading to my x86 kernel</title>
      <dc:creator>Paull Knya-z</dc:creator>
      <pubDate>Fri, 22 May 2026 14:23:41 +0000</pubDate>
      <link>https://dev.to/paullknya/booting-from-fat12-how-i-added-file-reading-to-my-x86-kernel-65b</link>
      <guid>https://dev.to/paullknya/booting-from-fat12-how-i-added-file-reading-to-my-x86-kernel-65b</guid>
      <description>&lt;p&gt;My kernel &lt;code&gt;Paull-kernel&lt;/code&gt; (32‑bit x86, protected mode) already had a command line and could run built‑in commands like &lt;code&gt;hew&lt;/code&gt; → &lt;code&gt;hello world&lt;/code&gt;. But everything was hardcoded. To load and run external programs, I needed a real file system.&lt;/p&gt;

&lt;p&gt;I chose &lt;strong&gt;FAT12&lt;/strong&gt; – the classic floppy disk file system. Simple, well documented, and perfect for a hobby OS.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 FAT12 structure (short version)
&lt;/h2&gt;

&lt;p&gt;A FAT12 disk looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Boot Sector&lt;/strong&gt; (512 bytes) – contains the BIOS Parameter Block (BPB) with geometry info.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First FAT&lt;/strong&gt; (File Allocation Table) – a table that tells you which clusters belong to a file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Second FAT&lt;/strong&gt; (optional, backup).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Root Directory&lt;/strong&gt; – a list of files (each entry is 32 bytes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Area&lt;/strong&gt; – the actual file content, split into clusters (usually 512 bytes per cluster).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Names in the root directory use the &lt;code&gt;8.3&lt;/code&gt; format (e.g. &lt;code&gt;HELLO  BIN&lt;/code&gt;). Each cluster is a chain: a number in the FAT points to the next cluster; &lt;code&gt;0xFFF&lt;/code&gt; means end of file.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔧 Reading a file step by step
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Read the boot sector first
&lt;/h3&gt;

&lt;p&gt;The boot sector (sector 0) contains the BPB. From there I read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BytesPerSector&lt;/code&gt; (usually 512)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SectorsPerCluster&lt;/code&gt; (usually 1 for floppies)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ReservedSectors&lt;/code&gt; (often 1)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NumFATs&lt;/code&gt; (usually 2)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SectorsPerFAT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RootDirEntries&lt;/code&gt; (224 for a standard 1.44 MB floppy)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These values tell me where the FAT, root directory, and data area start.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Locate the file in the root directory
&lt;/h3&gt;

&lt;p&gt;The root directory starts after the boot sector and both FATs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;root_dir_sector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reserved_sectors&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_fats&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sectors_per_fat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I load the root directory into memory and scan each 32‑byte entry. I compare the name (padded with spaces) to the file I’m looking for, e.g. "HELLO BIN".&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Follow the cluster chain&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once I have the first cluster number from the directory entry, I read that cluster from the data area:&lt;br&gt;
c&lt;/p&gt;

&lt;p&gt;first_data_sector = root_dir_sector + (root_dir_entries * 32 + bytes_per_sector - 1) / bytes_per_sector;&lt;br&gt;
cluster_sector = first_data_sector + (cluster - 2) * sectors_per_cluster;&lt;/p&gt;

&lt;p&gt;Then I use the FAT to find the next cluster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Load the FAT sector that contains the entry for my cluster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read the 12‑bit value (FAT12 uses 12‑bit entries).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the value is 0xFFF (or 0xFF8–0xFFF) – end of file. Otherwise, move to that cluster and repeat.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🐞 Problems I ran into&lt;/p&gt;

&lt;p&gt;Wrong first sector of root directory – I forgot to add the reserved sectors. Always recalculate.&lt;/p&gt;

&lt;p&gt;Cluster numbers start from 2 – I tried to use cluster 0 as the first data cluster. No.&lt;/p&gt;

&lt;p&gt;Reading beyond the sector – I had to handle cases where a cluster spans two FAT sectors.&lt;/p&gt;

&lt;p&gt;Disk geometry – The BIOS int 0x13 expects CHS (cylinder/head/sector). I had to convert LBA to CHS manually. It’s annoying but doable.&lt;/p&gt;

&lt;p&gt;Debugging was a pain. I printed sector numbers to the screen (using video memory at 0xB8000) to see what was actually being read.&lt;br&gt;
🎉 Result&lt;/p&gt;

&lt;p&gt;Now my kernel can load a file from disk:&lt;br&gt;
bash&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;load HELLO.BIN&lt;br&gt;
Loading HELLO.BIN ... done.&lt;br&gt;
Executing...&lt;br&gt;
Hello from user program!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I can write small assembly programs, compile them into a raw binary, and copy them to a FAT12 floppy image. Then Paull‑kernel loads and runs them.&lt;/p&gt;

&lt;p&gt;🔜 What’s next&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A dir command to list all files in the root directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better error handling (file not found, end of disk).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maybe later: support for long file names (LFN) or FAT32.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔗 Links&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Source code of Paull‑kernel: github.com/Paullknya/Paull-kernel&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Discuss this article: Community forum&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;My GitHub profile: github.com/Paullknya&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy low‑level coding! 🦫&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>systems</category>
    </item>
    <item>
      <title>Why my keyboard handler caused #GP (and how I fixed it)</title>
      <dc:creator>Paull Knya-z</dc:creator>
      <pubDate>Wed, 20 May 2026 08:09:03 +0000</pubDate>
      <link>https://dev.to/paullknya/why-my-keyboard-handler-caused-gp-and-how-i-fixed-it-1cl7</link>
      <guid>https://dev.to/paullknya/why-my-keyboard-handler-caused-gp-and-how-i-fixed-it-1cl7</guid>
      <description>&lt;p&gt;&lt;em&gt;— A true story of debugging a 32‑bit kernel&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A few weeks ago I was happily writing my own 32‑bit x86 operating system, &lt;strong&gt;Paull-kernel&lt;/strong&gt;. Everything worked: the bootloader, protected mode, a simple “Hello” message. Then I decided to add keyboard input.&lt;/p&gt;

&lt;p&gt;I wrote an IRQ1 handler, compiled, ran… and QEMU crashed hard with a &lt;strong&gt;General Protection Fault (#GP)&lt;/strong&gt; on every key press. Triple fault. Reboot loop. Frustration.&lt;/p&gt;

&lt;p&gt;After two hours of staring at the code, I finally understood the problem – and it wasn’t inside the handler logic. It was two stupid &lt;code&gt;mov&lt;/code&gt; instructions.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 What I wanted to do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Catch keyboard interrupts (IRQ1) and read scancodes from port &lt;code&gt;0x60&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Convert scancodes to ASCII characters and print them to the screen.&lt;/li&gt;
&lt;li&gt;Simple, classic OS‑dev step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My handler looked (mostly) correct: read scancode, skip key‑release, translate via a table, write to video memory.&lt;/p&gt;




&lt;h2&gt;
  
  
  💥 What went wrong
&lt;/h2&gt;

&lt;p&gt;Every key press caused &lt;strong&gt;#GP&lt;/strong&gt;. The processor reset. No error message, no debugging output – just a black screen.&lt;/p&gt;

&lt;p&gt;Here’s the piece of code that broke everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;keyboard_handler:
    pushad
    mov ax, 0x1000
    mov ds, ax
    mov ax, 0xB800
    mov es, ax
    in al, 0x60
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can you spot the error?&lt;br&gt;
🔍 The investigation&lt;/p&gt;

&lt;p&gt;In real mode (16‑bit), mov ds, ax with ax = 0x1000 would set the data segment to physical address 0x10000.&lt;br&gt;
In protected mode (32‑bit), segment registers hold selectors – indexes into the GDT (Global Descriptor Table).&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x1000 is a garbage selector – it points to no valid descriptor.

0xB800 is also a garbage selector.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Loading them doesn’t trigger an immediate fault, but as soon as the CPU tries to access memory through ds or es (e.g., stosw with es), it throws #GP.&lt;/p&gt;

&lt;p&gt;The handler looked like it should work, but the environment was completely different from real mode.&lt;br&gt;
✅ How I fixed it&lt;/p&gt;

&lt;p&gt;My GDT uses the flat memory model:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code selector: 0x08

Data selector: 0x10 (base = 0, limit = 4 GiB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;So I changed two lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
keyboard_handler:
    pushad
    mov ax, 0x10        ; correct data selector
    mov ds, ax
    mov es, ax
    in al, 0x60
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for video memory, I stopped using segment overrides. Instead, I write directly to the linear address 0xB8000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
mov edi, [cursor_pos]
shl edi, 1
add edi, 0xB8000
mov ah, 0x07
stosw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more es magic. Now the handler works perfectly.&lt;br&gt;
📦 Key takeaways&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;In protected mode, segment registers store GDT selectors, not physical addresses.

Values like 0x1000 or 0xB800 are almost always invalid and will cause #GP when accessed.

Always check your GDT and use the correct selectors (e.g. 0x08 for code, 0x10 for data).

When writing to video memory, use the linear address 0xB8000 and a flat‑mapped ds/es.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;One small mistake can cost you hours of debugging. But once you understand the model, it becomes obvious.&lt;br&gt;
🔗 Links &amp;amp; resources&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source code of AxonOS (full kernel + bootloader):
👉 github.com/Paullknya/AXonOS-kernel

My GitHub profile:
👉 github.com/Paullknya

Discuss this article on the community forum:
👉 paullknya.github.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🚀 What’s next?&lt;/p&gt;

&lt;p&gt;Next article I’ll show how I added ring 0/3 switching and why sudo reboot asks for a password.&lt;br&gt;
If you’re interested, follow me or star the repo – it helps a lot.&lt;/p&gt;

&lt;p&gt;Happy coding, and may your selectors always be valid.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>computerscience</category>
      <category>devjournal</category>
      <category>programming</category>
    </item>
    <item>
      <title>Writing a Bootloader for My OS: Common Pitfalls and How I Fixed Them</title>
      <dc:creator>Paull Knya-z</dc:creator>
      <pubDate>Wed, 20 May 2026 07:41:54 +0000</pubDate>
      <link>https://dev.to/paullknya/writing-a-bootloader-for-my-os-common-pitfalls-and-how-i-fixed-them-136k</link>
      <guid>https://dev.to/paullknya/writing-a-bootloader-for-my-os-common-pitfalls-and-how-i-fixed-them-136k</guid>
      <description>&lt;h1&gt;
  
  
  Writing a Bootloader for My 32-bit x86 OS: Common Pitfalls and How I Fixed Them
&lt;/h1&gt;

&lt;p&gt;I decided to write my own 32-bit x86 operating system from scratch. The first step was a bootloader – the small piece of code that loads the kernel. It’s supposed to be simple, but I ran into several frustrating issues. This post documents the real problems I faced and how I solved them, so you can avoid the same traps.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The BIOS signature
&lt;/h2&gt;

&lt;p&gt;The boot sector must end with the magic bytes &lt;code&gt;0x55AA&lt;/code&gt;. The BIOS checks these two bytes before loading the sector. I forgot them, and nothing happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Add &lt;code&gt;dw 0xAA55&lt;/code&gt; at the end of your bootloader code, after filling the rest of the 512-byte sector with zeros.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;&lt;span class="kd"&gt;times&lt;/span&gt; &lt;span class="mi"&gt;510&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;$&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kc"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;db&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="kd"&gt;dw&lt;/span&gt; &lt;span class="mh"&gt;0xAA55&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Losing the boot drive number&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The BIOS passes the disk number in the dl register. I accidentally overwrote it before using it in the int 0x13 call. The disk read failed silently.&lt;/p&gt;

&lt;p&gt;Fix: Save dl right at the start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;boot_drive&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;dl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restore or use the saved value when reading sectors.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A20 line – the hidden gatekeeper&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To access memory above 1 MB and enter 32‑bit protected mode, the A20 line must be enabled. I wasted hours trying to figure out why my far jump crashed. The quickest way (though not the most universal) is to use port 0x92.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;
&lt;span class="nf"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;al&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x92&lt;/span&gt;
&lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;al&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="nf"&gt;out&lt;/span&gt; &lt;span class="mh"&gt;0x92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;al&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many emulators enable A20 by default, but real hardware doesn’t – always include this step.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Global Descriptor Table (GDT) mistakes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A flat memory model requires at least two descriptors: code and data. I messed up the segment limits and flags, causing general protection faults.&lt;/p&gt;

&lt;p&gt;Correct minimal GDT for flat model (32‑bit):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;
&lt;span class="nl"&gt;gdt_start:&lt;/span&gt;
    &lt;span class="kd"&gt;dq&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;                       &lt;span class="c1"&gt;; null descriptor&lt;/span&gt;
    &lt;span class="kd"&gt;dw&lt;/span&gt; &lt;span class="mh"&gt;0xFFFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x0000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x9A00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00CF&lt;/span&gt;   &lt;span class="c1"&gt;; code segment 0x08&lt;/span&gt;
    &lt;span class="kd"&gt;dw&lt;/span&gt; &lt;span class="mh"&gt;0xFFFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x0000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x9200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00CF&lt;/span&gt;   &lt;span class="c1"&gt;; data segment 0x10&lt;/span&gt;
&lt;span class="nl"&gt;gdt_end:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load it with lgdt [gdt_desc] before switching to protected mode.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The protected mode jump&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After setting the PE bit in cr0, you must perform a far jump to reload CS with the new code segment selector. I initially forgot the jmp and the CPU continued with the old real‑mode CS, causing a triple fault.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="nb"&gt;eax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;cr0&lt;/span&gt;
&lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;eax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="nb"&gt;cr0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;eax&lt;/span&gt;
&lt;span class="nf"&gt;jmp&lt;/span&gt; &lt;span class="mh"&gt;0x08&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;protected_mode&lt;/span&gt;   &lt;span class="c1"&gt;; 0x08 is our code segment selector&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Segment registers in protected mode&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Inside the kernel (or later, in interrupt handlers), I naively loaded ds = 0x1000 and es = 0xB800, assuming they would work like in real mode. That’s wrong – in protected mode they must hold selectors (indices into GDT). This caused instant #GP.&lt;/p&gt;

&lt;p&gt;Fix: Use the data segment selector (0x10) and access video memory directly by its linear address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="nb"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x10&lt;/span&gt;    &lt;span class="c1"&gt;; data selector&lt;/span&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="nb"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ax&lt;/span&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="nb"&gt;es&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ax&lt;/span&gt;

&lt;span class="c1"&gt;; Write character via linear address 0xB8000&lt;/span&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="nb"&gt;edi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;cursor_pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;shl&lt;/span&gt; &lt;span class="nb"&gt;edi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nf"&gt;add&lt;/span&gt; &lt;span class="nb"&gt;edi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xB8000&lt;/span&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="nb"&gt;ah&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x07&lt;/span&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;edi&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;ax&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: a working bootloader + kernel&lt;/p&gt;

&lt;p&gt;After fixing these issues, the bootloader loads the kernel, enters protected mode, and runs a simple command line. Everything is open source.&lt;/p&gt;

&lt;p&gt;GitHub repository: github.com/Paullknya/AXonOS-kernel&lt;br&gt;
Project discussions and updates: paullknya.github.io&lt;/p&gt;

&lt;p&gt;If you’re also writing a hobby OS, I hope this saves you a few hours of debugging. Feel free to ask questions in the discussions – I’ll be happy to help.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;my OS link:  &lt;a href="https://github.com/Paullknya/AXonOS-kernel" rel="noopener noreferrer"&gt;https://github.com/Paullknya/AXonOS-kernel&lt;/a&gt;&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>devjournal</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
