Paging works!!!
This commit is contained in:
@@ -47,7 +47,9 @@ Minimal operating systems to learn low level programming.
|
||||
1. [Segment base (TODO)](segment_base.S)
|
||||
1. [IDT](idt.S)
|
||||
1. [IDT zero divide](idt_zero_divide.S)
|
||||
1. IDT PIT
|
||||
1. Segmentation fault handler: memory bound, ring, RWX violations
|
||||
1. [Paging](paging.S)
|
||||
1. APM
|
||||
1. [APM shutdown](apm_shutdown.S)
|
||||
1. [APM shutdown 2](apm_shutdown2.S)
|
||||
|
||||
6
TODO.md
6
TODO.md
@@ -1,6 +1,6 @@
|
||||
# TODO
|
||||
|
||||
- cache: wbinvd
|
||||
- cache: wbinvd
|
||||
|
||||
- inb outb
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
- https://github.com/torvalds/linux/blob/v4.2/arch/x86/boot/boot.h#L78
|
||||
- http://stackoverflow.com/questions/6793899/what-does-the-0x80-port-address-connects
|
||||
|
||||
- lidt, interrupts, IDTR:
|
||||
- lidt, interrupts, IDTR:
|
||||
|
||||
- http://stackoverflow.com/questions/3392831/what-happens-in-an-interrupt-service-routine
|
||||
- http://stackoverflow.com/questions/1817577/what-does-int-0x80-mean-in-assembly-code
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
timer, IPT
|
||||
|
||||
- paging
|
||||
- paging
|
||||
|
||||
- Segment registers /segmentation and protected mode. Then try to answer all of: GDT
|
||||
|
||||
|
||||
107
common.h
107
common.h
@@ -191,6 +191,7 @@ Our GDT contains:
|
||||
Both start at 0 and span the entire memory,
|
||||
allowing us to access anything without problems.
|
||||
A real OS might have 2 extra segments: user data and code.
|
||||
This is the case for the Linux kernel.
|
||||
This is better than modifying the privilege bit of the GDT
|
||||
as we'd have to reload it several times, losing cache.
|
||||
*/
|
||||
@@ -239,6 +240,59 @@ protected_mode:
|
||||
mov %ebp, %esp
|
||||
.endm
|
||||
|
||||
/* IDT */
|
||||
|
||||
.macro IDT_START
|
||||
idt_start:
|
||||
.endm
|
||||
|
||||
.macro IDT_END
|
||||
idt_end:
|
||||
/* Exact same structure as gdt_descriptor. */
|
||||
idt_descriptor:
|
||||
.word idt_end - idt_start
|
||||
.long idt_start
|
||||
.endm
|
||||
|
||||
.macro IDT_ENTRY
|
||||
/* Low handler address.
|
||||
It is impossible to write:
|
||||
.word (handler & 0x0000FFFF)
|
||||
as we would like:
|
||||
http://stackoverflow.com/questions/18495765/invalid-operands-for-binary-and
|
||||
because this address has to be split up into two.
|
||||
So this must be done at runtime.
|
||||
Why this design choice from Intel?! Weird.
|
||||
*/
|
||||
.word 0
|
||||
/* Segment selector: byte offset into the GDT. */
|
||||
.word CODE_SEG
|
||||
/* Reserved 0. */
|
||||
.byte 0
|
||||
/*
|
||||
Flags. Format:
|
||||
- 1 bit: present. If 0 and this happens, triple fault.
|
||||
- 2 bits: ring level we will be called from.
|
||||
- 5 bits: fixed to 0xE.
|
||||
*/
|
||||
.byte 0x8E
|
||||
/* High word of base. */
|
||||
.word 0
|
||||
.endm
|
||||
|
||||
/*
|
||||
- index: r/m/imm32 Index of the entry to setup.
|
||||
- handler: r/m/imm32 Address of the handler function.
|
||||
*/
|
||||
.macro IDT_SETUP_ENTRY index, handler
|
||||
mov \index, %eax
|
||||
mov \handler, %ebx
|
||||
mov %bx, idt_start(%eax, 8)
|
||||
shr $16, %ebx
|
||||
mov $6, %ecx
|
||||
mov %bx, idt_start(%ecx, %eax, 8)
|
||||
.endm
|
||||
|
||||
/* BIOS */
|
||||
|
||||
.macro CURSOR_POSITION x=$0, y=$0
|
||||
@@ -422,13 +476,13 @@ Print a 32-bit register in hex.
|
||||
Sample usage:
|
||||
|
||||
mov $12345678, %eax
|
||||
VGA_PRINT_REG <%eax>
|
||||
VGA_PRINT_HEX <%eax>
|
||||
|
||||
Expected output on screen:
|
||||
|
||||
12345678
|
||||
*/
|
||||
.macro VGA_PRINT_REG reg=<%eax>
|
||||
.macro VGA_PRINT_HEX reg=<%eax>
|
||||
LOCAL loop
|
||||
PUSH_EADX
|
||||
/* Null terminator. */
|
||||
@@ -457,52 +511,3 @@ loop:
|
||||
add $12, %esp
|
||||
POP_EDAX
|
||||
.endm
|
||||
|
||||
.macro IDT_START
|
||||
idt_start:
|
||||
.endm
|
||||
|
||||
.macro IDT_END
|
||||
idt_end:
|
||||
/* Exact same structure as gdt_descriptor. */
|
||||
idt_descriptor:
|
||||
.word idt_end - idt_start
|
||||
.long idt_start
|
||||
.endm
|
||||
|
||||
.macro IDT_ENTRY
|
||||
/* Low handler address.
|
||||
It is impossible to write:
|
||||
.word (handler & 0x0000FFFF)
|
||||
as we would like:
|
||||
http://stackoverflow.com/questions/18495765/invalid-operands-for-binary-and
|
||||
So this must be done at runtime.
|
||||
*/
|
||||
.word 0
|
||||
/* Segment selector: byte offset into the GDT. */
|
||||
.word CODE_SEG
|
||||
/* Reserved 0. */
|
||||
.byte 0
|
||||
/*
|
||||
Flags. Format:
|
||||
- 1 bit: present. If 0 and this happens, triple fault.
|
||||
- 2 bits: ring level we will be called from.
|
||||
- 5 bits: fixed to 0xE.
|
||||
*/
|
||||
.byte 0x8E
|
||||
/* High word of base. */
|
||||
.word 0
|
||||
.endm
|
||||
|
||||
/*
|
||||
- index: r/m/imm32 Index of the entry to setup.
|
||||
- handler: r/m/imm32 Address of the handler function.
|
||||
*/
|
||||
.macro IDT_SETUP_ENTRY index, handler
|
||||
mov \index, %eax
|
||||
mov \handler, %ebx
|
||||
mov %bx, idt_start(%eax, 8)
|
||||
shr $16, %ebx
|
||||
mov $6, %ecx
|
||||
mov %bx, idt_start(%ecx, %eax, 8)
|
||||
.endm
|
||||
|
||||
@@ -46,6 +46,8 @@ SECTIONS
|
||||
|
||||
/* Ensure that the generated image is a multiple of 512 bytes long. */
|
||||
. = ALIGN(512);
|
||||
__end = .;
|
||||
__end_align_4k = ALIGN(4k);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
191
paging.S
Normal file
191
paging.S
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
# Paging.
|
||||
|
||||
Expected output:
|
||||
|
||||
00001234
|
||||
00005678
|
||||
|
||||
Verbose tutorial: http://www.cirosantilli.com/x86-paging/
|
||||
|
||||
TODO: move all this info to the tutorial.
|
||||
|
||||
Keep the following Intel shorthands in mind:
|
||||
|
||||
- PTE: Page table
|
||||
- PDE: Page directory
|
||||
- PDPTE: Page-directory-
|
||||
|
||||
## Intel manual
|
||||
|
||||
Part 3 has the chapter on Paging.
|
||||
|
||||
## Linux kernel
|
||||
|
||||
4.2: look under arch/x86/:
|
||||
|
||||
- include/asm/pgtable*
|
||||
- include/asm/page*
|
||||
- mm/pgtable*
|
||||
- mm/page*
|
||||
|
||||
There seems to be no structs defined to represent the pages, only macros:
|
||||
`include/asm/page_types.h` is specially interesting. Excerpt:
|
||||
|
||||
#define _PAGE_BIT_PRESENT 0 // is present
|
||||
#define _PAGE_BIT_RW 1 // writeable
|
||||
#define _PAGE_BIT_USER 2 // userspace addressable
|
||||
#define _PAGE_BIT_PWT 3 // page write through
|
||||
|
||||
`arch/x86/include/uapi/asm/processor-flags.h` defines CR0
|
||||
|
||||
## cr3
|
||||
|
||||
The cr3 register does have a format,
|
||||
it is not simply the address of the page directory:
|
||||
|
||||
- 20 top bits: 4KiB address. Since those are the only address bits,
|
||||
this implies that the page directory must be aligned to 4Kib.
|
||||
|
||||
- bits 3 and 4: TODO some function I don't understand yet
|
||||
|
||||
- all others: ignored
|
||||
|
||||
Many tutorials simply ignore bits 3 and 4, and do a direct address mov to `cr3`.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
.equ page_directory, __end_align_4k
|
||||
.equ page_table, __end_align_4k + 0x1000
|
||||
|
||||
BEGIN
|
||||
CLEAR
|
||||
STAGE2
|
||||
PROTECTED_MODE
|
||||
|
||||
/*
|
||||
Make the first page directory entry point to the page table.
|
||||
We must do this at runtime because the first 4 bits are not aligned to bytes.
|
||||
*/
|
||||
mov $page_table, %eax
|
||||
/* Zero out the 4 low flag bits of byte 2 (top 20 are address). */
|
||||
and $0xF000, %ax
|
||||
mov %eax, page_directory
|
||||
/* Flags for byte 0. */
|
||||
mov $0b00100111, %al
|
||||
mov %al, page_directory
|
||||
|
||||
/*
|
||||
Make all pages of the first directory into an identity map (linear address == logical address).
|
||||
This will give us: 2^10 * 2^12 == 4MiB of identity memory.
|
||||
This is particularly important because our code segment is running there.
|
||||
*/
|
||||
mov $0, %eax
|
||||
mov $page_table, %ebx
|
||||
page_setup_start:
|
||||
cmp $0x400, %eax
|
||||
je page_setup_end
|
||||
|
||||
/*
|
||||
Byte 0: fixed flags:
|
||||
- 0: present
|
||||
- 1: RW
|
||||
- 2: user mode can access iff 1
|
||||
- 3: Page-level write-through
|
||||
- 4: Page-level cache disable
|
||||
- 5: accessed
|
||||
- 6: dirty
|
||||
- 7: PAT: TODO
|
||||
*/
|
||||
movb $0b00100111, (%ebx)
|
||||
|
||||
/*
|
||||
Byte 1:
|
||||
- 4 bits of flags:
|
||||
- 8: Global
|
||||
- 9:11: ignored
|
||||
- 4 low bits of page address
|
||||
*/
|
||||
mov %eax, %edx
|
||||
/*
|
||||
4 because the 4 low bits of eax are the 4 high bits of the second byte.
|
||||
The 4 low bits of the second byte are flags / ignored and set to 0.
|
||||
*/
|
||||
shl $4, %edx
|
||||
mov %dl, 1(%ebx)
|
||||
|
||||
/* Bytes 2 and 3: 16 high bits of page address. */
|
||||
mov %eax, %edx
|
||||
shr $4, %edx
|
||||
mov %dx, 2(%ebx)
|
||||
|
||||
inc %eax
|
||||
add $4, %ebx
|
||||
jmp page_setup_start
|
||||
page_setup_end:
|
||||
|
||||
/* Setup a test canary value. */
|
||||
mov $0x1234, %eax
|
||||
mov %eax, 0x1000
|
||||
|
||||
/* Print the canary to make sure it is really there. */
|
||||
VGA_PRINT_HEX 0x1000
|
||||
|
||||
/* Make the page 0 point to page 1. */
|
||||
mov page_table, %eax
|
||||
or $0x00001000, %eax
|
||||
mov %eax, page_table
|
||||
|
||||
/* Tell the CPU where the page directory is. */
|
||||
mov $page_directory, %eax
|
||||
mov %eax, %cr3
|
||||
|
||||
/* Turn paging on. */
|
||||
mov %cr0, %eax
|
||||
or $0x80000000, %eax
|
||||
mov %eax, %cr0
|
||||
|
||||
/*
|
||||
THIS is what we've been working for!!!
|
||||
Even though we mov to 0, the paging circuit reads that as physical address 0x1000,
|
||||
so the canary value 0x1234 should be modified to 0x5678.
|
||||
*/
|
||||
mov $0x5678, %eax
|
||||
mov %eax, 0
|
||||
|
||||
/*
|
||||
Turn paging back off to prevent it from messing with us.
|
||||
Remember that VGA does memory accesses, so if paging is still on,
|
||||
we must identity map up to it, which we have, so this is not mandatory.
|
||||
*/
|
||||
mov %cr0, %eax
|
||||
and $0x7FFFFFFF, %eax
|
||||
mov %eax, %cr0
|
||||
|
||||
/* Print the (hopefully) modified value 0x5678. */
|
||||
VGA_PRINT_HEX 0x1000
|
||||
jmp .
|
||||
|
||||
message:
|
||||
.asciz "hello world"
|
||||
|
||||
/*
|
||||
.align could use aligned symbols here, but that is less good
|
||||
as it blows up the size of the image.
|
||||
The better option is to use the linker script instead.
|
||||
|
||||
For this to work, we MUST use STAGE2:
|
||||
otherwise this align would try to move the location counter to 0x1000,
|
||||
and then when the linker tries to add the magic boot byte at 510 it blows up with
|
||||
cannot move location counter backwards.
|
||||
*/
|
||||
/*
|
||||
.align 0x1000
|
||||
page_directory:
|
||||
.byte 0b00100111
|
||||
.skip 3
|
||||
|
||||
.align 0x1000
|
||||
page_table:
|
||||
*/
|
||||
@@ -11,6 +11,13 @@ Major changes from real moe:
|
||||
- we have to encode instructions differently, thus a `.code32` is needed.
|
||||
Note that in 16-bit, 32-bit instructions were encodable, but with a prefix.
|
||||
|
||||
## Linux kernel
|
||||
|
||||
arch/x86/include/asm/segment.h contains a lot of action:
|
||||
|
||||
- the user privilege level
|
||||
- the segment steup (kernel an user code and data segments)
|
||||
|
||||
## Bibliography
|
||||
|
||||
- http://stackoverflow.com/questions/28645439/how-do-i-enter-32-bit-protected-mode-in-nasm-assembly Initially adapted from this.
|
||||
|
||||
@@ -58,8 +58,8 @@ BEGIN
|
||||
mov %ax, %ds
|
||||
|
||||
/*
|
||||
TODO this sanity check is not printing "ab",
|
||||
so we're not restoring the old state properly.
|
||||
TODO this sanity check is not printing "ab".
|
||||
It fails, so we're not restoring the old state properly.
|
||||
Likely blows up because video memory going wrong.
|
||||
*/
|
||||
VGA_PRINT_STRING $message
|
||||
|
||||
Reference in New Issue
Block a user