Protected mode is an operation mode of x86 architectures. It gives access to 4GB of address space, memory protection, and much more. (osdev wiki)
There are different protection levels in the processor. Levels are called Rings. The kernel runs in Ring 0. This is the most privileged mode that can talk with hardware and write data to any memory address. Ring 1 and 2 are generally not used, but can be used for device drivers. Ring 3 is the least privileged level which is used to run user code. This prevents user applications from overwriting kernel memory, talking with hardware directly, accessing other processes' memory, and using privileged instructions (i.e., sti
, cli
).
What we know as segmentation registers in Real Mode become selector registers in Protected Mode. Selectors point to data structures that describe memory ranges and the permissions (ring level) required to access a given range.
Paging memory scheme maps virtual memory addresses to physical memory addresses somewhere entirely different in memory. This allows user processes to believe that they are loaded into the same memory address and makes it impossible for a user program to see the address space of other running programs. As far as a program is concerned, it is the only process running. Note that all virtual and physical addresses need to be divisible by 4096.
In Protected Mode, we gain access to 32-bit instructions, which enable easy access to 32-bit registers, thus 4GB memory.
We create entries for the Global Descriptor Table (GDT) and load its address into the GDT register by lgdt
instruction, with additional parameters to enter Protected Mode (osdev wiki). We will use the GDT default values since we'll be using the paging memory scheme. (git commit)
Segment Descriptor
+-------------------------------------------------------------+
| 63 56 | 55 52 | 51 48 | 47 40 | 39 32 |
+---------+---------+---------+---------------+---------------+
| Base(hi)| Flags | Limit(h)| Access | Base (mid) |
| 31 24 | 3 0 | 19 16 | 7 0 | 23 16 |
+---------+---------+---------+---------------+---------------+
| 31 16 | 15 0 |
+-----------------------------+-------------------------------+
| Base (low) | Limit (low) |
| 15 0 | 15 0 |
+-----------------------------+-------------------------------+
This repo assumes the development is done on an M1 Mac. For that, we need to use LLDB instead of GDB. Here's how:
# Launch QEMU with gdb server enabled
# '-s' shorthand for -gdb tcp::1234
# '-S' freeze CPU at startup (use 'c' to start execution)
> qemu-x86-64 -hda ./boot.bin -s -S &
> lldb
(lldb) gdb-remote 1234 # connects to gdb server on localhost:1234
Process 1 stopped
* thread #1, stop reason = signal SIGTRAP
frame #0: 0x000000000000fff0
-> 0xfff0: addb %al, (%rax)
0xfff2: addb %al, (%rax)
0xfff4: addb %al, (%rax)
0xfff6: addb %al, (%rax)
Target 0: (No executable module.) stopped.
(lldb) c # continue
Process 1 resuming
(lldb) process interrupt # (C-c also works) now the bootloader should come to a halt
Process 1 stopped
* thread #1, stop reason = signal SIGINT
frame #0: 0x0000000000007c68
-> 0x7c68: jmp 0x7c68
0x7c6a: addb %al, (%rax)
0x7c6c: addb %al, (%rax)
0x7c6e: addb %al, (%rax)
Target 0: (No executable module.) stopped.
(lldb) register read
general:
rax = 0x0000000000000011
...
rbp = 0x0000000000200000
rsp = 0x0000000000200000
...
cs = 0x00000008 # Code Segment = 8 means Protected Mode (?)
ss = 0x00000010
ds = 0x00000010
es = 0x00000010
fs = 0x00000010
gs = 0x00000010
A20 Line needs to be enabled to access all memories.