Page fault handler works!

This commit is contained in:
Ciro Santilli
2015-10-28 18:36:31 +01:00
parent 24988411ad
commit 1f6af90212
12 changed files with 344 additions and 173 deletions

View File

@@ -46,10 +46,12 @@ Minimal operating systems to learn low level programming.
1. [Protected mode](protected_mode.S)
1. [Segment base (TODO)](segment_base.S)
1. [IDT](idt.S)
1. [IDT 1](idt1.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. [Page fault](page-fault.S)
1. APM
1. [APM shutdown](apm_shutdown.S)
1. [APM shutdown 2](apm_shutdown2.S)

19
TODO.md
View File

@@ -32,6 +32,15 @@
- http://stackoverflow.com/questions/7924031/how-prompt-is-x86-at-setting-the-page-dirty-bit/7926931#7926931
Page fault:
- http://stackoverflow.com/questions/5684365/what-causes-page-faults/5690636#5690636
- http://stackoverflow.com/questions/23899567/distinguishing-between-code-and-data-pages-on-x86-64-mmu/23900691#23900691
- http://stackoverflow.com/questions/12607288/what-happens-when-a-mov-instruction-causes-a-page-fault-with-interrupts-disabled
- http://stackoverflow.com/questions/21211942/x86-page-fault-description
- http://stackoverflow.com/questions/15275059/whats-the-purpose-of-x86-cr0-wp-bit
- http://stackoverflow.com/questions/21820814/what-happens-with-a-processor-when-it-tries-to-access-a-nonexistent-address
- Segment registers /segmentation and protected mode. Then try to answer all of: GDT
- http://reverseengineering.stackexchange.com/questions/2006/how-are-the-segment-registers-fs-gs-cs-ss-ds-es-used-in-linux
@@ -58,6 +67,12 @@
- http://stackoverflow.com/questions/9249315/what-is-gs-in-assembly?rq=1
- http://stackoverflow.com/questions/928082/why-does-the-mov-instruction-have-to-be-used-this-way?lq=1
Segfault:
- http://stackoverflow.com/questions/6950549/segmentation-fault-vs-page-fault
- http://stackoverflow.com/questions/10948930/page-fault-and-segmentation-fault
- http://stackoverflow.com/questions/10948930/page-fault-and-segmentation-fault
64-bit:
- http://stackoverflow.com/questions/22962251/how-to-enter-64-bit-mode-on-a-x86-64/22963701#22963701
@@ -126,3 +141,7 @@
- http://stackoverflow.com/questions/15322892/linux-usb-mouse-drivers
- http://stackoverflow.com/questions/25175960/which-drivers-are-used-by-usb-mouse-in-linux-kernel
- control registers CRX
- why CR1 does not exist, but CR8 does http://www.pagetable.com/?p=364

138
common.h
View File

@@ -16,7 +16,7 @@ But the downsides are severe:
## Conventions
Every "function-like macro" should maintain register state
Every "function-like macro" should maintain GP register state
(flags currently not maintained).
%sp cannot be used to pass most arguments.
@@ -240,6 +240,76 @@ protected_mode:
mov %ebp, %esp
.endm
/*
Setup a single page directory, which give us 2^10 * 2^12 == 4MiB
of identity memory starting at address 0.
The currently executing code is inside that range, or else we'd jump somewhere and die.
*/
.equ page_directory, __end_align_4k
.equ page_table, __end_align_4k + 0x1000
.macro SETUP_PAGING_4M
LOCAL page_setup_start page_setup_end
PUSH_EADX
/* Page directory steup. */
/* Set the top 20 address bits. */
mov $page_table, %eax
/* Zero out the 4 low flag bits of the second byte (top 20 are address). */
and $0xF000, %ax
mov %eax, page_directory
/* Set flags for the first byte. */
mov $0b00100111, %al
mov %al, page_directory
/* Page table setup. */
mov $0, %eax
mov $page_table, %ebx
page_setup_start:
cmp $0x400, %eax
je page_setup_end
/* Top 20 address bits. */
mov %eax, %edx
shl $12, %edx
/*
Set flag bits 0-7. We only set to 1:
- bit 0: Page present
- bit 1: Page is writable.
Might work without this as the permission also depends on CR0.WP.
*/
mov $0b00000011, %dl
/* Zero flag bits 8-11 */
and $0xF0, %dh
mov %edx, (%ebx)
inc %eax
add $4, %ebx
jmp page_setup_start
page_setup_end:
POP_EDAX
.endm
/*
Turn paging on.
Registers are not saved because memory will be all messed up.
*/
.macro PAGING_ON
/* 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
.endm
/* Turn paging off. */
.macro PAGING_OFF
mov %cr0, %eax
and $0x7FFFFFFF, %eax
mov %eax, %cr0
.endm
/* IDT */
.macro IDT_START
@@ -255,7 +325,8 @@ idt_descriptor:
.endm
.macro IDT_ENTRY
/* Low handler address.
/*
Low handler address.
It is impossible to write:
.word (handler & 0x0000FFFF)
as we would like:
@@ -280,17 +351,25 @@ idt_descriptor:
.word 0
.endm
/* Skip n IDT entries, usually to set the Nth one next. */
.macro IDT_SKIP n=1
.skip n * 8
.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
push %eax
push %edx
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)
mov \handler, %edx
mov %dx, idt_start(,%eax, 8)
shr $16, %edx
mov %dx, (idt_start + 6)(,%eax, 8)
pop %edx
pop %eax
.endm
/* BIOS */
@@ -471,18 +550,18 @@ end:
.endm
/*
Print a 32-bit register in hex.
Print a 32-bit r/m/immm in hex.
Sample usage:
mov $12345678, %eax
VGA_PRINT_HEX <%eax>
VGA_PRINT_HEX_4 <%eax>
Expected output on screen:
12345678
*/
.macro VGA_PRINT_HEX reg=<%eax>
.macro VGA_PRINT_HEX_4 reg=<%eax>
LOCAL loop
PUSH_EADX
/* Null terminator. */
@@ -511,3 +590,42 @@ loop:
add $12, %esp
POP_EDAX
.endm
/*
Dump memory.
- s: starting address
- n: number of bytes to dump
TODO implement
*/
.macro VGA_PRINT_BYTES s, n=$16
LOCAL end, loop, no_newline
PUSH_ADX
push %edi
mov s, %esi
mov \n, %ecx
mov $0, %edi
cld
loop:
cmp $0, %ecx
je end
dec %ecx
lodsb
PRINT_HEX
PUTC
/* Print a newline for every 8 bytes. */
mov $0, %edx
mov %di, %eax
mov $8, %ebx
div %ebx
cmp $7, %edx
jne no_newline
/*VGA_PRINT_NEWLINE*/
no_newline:
inc %edi
jmp loop
end:
pop %di
POP_DAX
.endm

14
idt.S
View File

@@ -3,16 +3,20 @@
# Interrupt Descriptor Table
Expected output: "a"
Expected output: "int 0 handled"
The first 32 handlers are reserved by the processor and have predefined meanings.
TODO on Linux kernel.
The first 32 handlers are reserved by the processor and have predefined meanings, as specified in:
https://web.archive.org/web/20151025081259/http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-system-programming-manual-325384.pdf
Table 3-3. "Intel 64 and IA-32 General Exceptions".
## lidt
Analogous to lgdt but for the IDT.
## Linux kernel usage
TODO
## Bibliography
- http://www.jamesmolloy.co.uk/tutorial_html/4.-The%20GDT%20and%20IDT.html
@@ -37,4 +41,4 @@ handler:
VGA_PRINT_STRING $message
iret
message:
.asciz "a"
.asciz "int 0 handled"

27
idt1.S Normal file
View File

@@ -0,0 +1,27 @@
/*
# IDT 1
Sanity check that we can also handle int 1 besides just int 0.
Expected output: "int 1 handled"
*/
#include "common.h"
BEGIN
STAGE2
CLEAR
PROTECTED_MODE
IDT_SETUP_ENTRY $1, $handler
lidt idt_descriptor
int $1
jmp .
IDT_START
IDT_SKIP 1
IDT_ENTRY
IDT_END
handler:
VGA_PRINT_STRING $message
iret
message:
.asciz "int 1 handled"

View File

@@ -3,7 +3,7 @@
Division by zero causes a Divide Error which Intel notes as `#DE`.
Expected output: "a"
Expected output: "division by zero handled"
It is then handled by IDT 0.
@@ -36,4 +36,4 @@ handler:
mov $1, %ecx
iret
message:
.asciz "a"
.asciz "division by zero handled"

View File

@@ -1,7 +1,22 @@
/*
# Initial state.
Check the initial state the firmware leaves us in.
Could be done with GDB on the emulator, but this will also work on real hardware.
## ax
When I don't use ax to zero ds in the initialization,
it has value 0x55AA, which is the magic bytes.
Is that mandatory / does it have a function?
## dx
This looks like the only interesting regular register:
the firmware stores the value of the current disk number (to help with int 0x15) there.
Thus it usually contains 0x80.
*/
#include "common.h"
@@ -11,30 +26,61 @@ Could be done with GDB on the emulator, but this will also work on real hardware
.endm
.macro INITIAL_DATA x
\x: .word 0
\x: .skip 2
\x\()s: .ascii "\x = \0"
.endm
.macro INITIAL_PRINT x
PRINT $\x\()s
PRINT_HEX <\x>
PRINT_BYTES $\x, $2
PRINT_NEWLINE
.endm
.irp reg, bx, cx, dx, cs, ds, es, fs, gs, ss
INITIAL_STORE \reg
.endr
/*
Indispensable initialization.
This initialization is a bit redundant with BEGIN,
and does dirty some registers, but I haven't found a better option.
*/
.code16
cli
xor %ax, %ax
mov %ax, %ds
/*
We want our data do be before STAGE2,
or it will get overwritten during the load.
*/
jmp after_data
.irp reg, ax, bx, cx, dx, cs, ds, es, fs, gs, ss
INITIAL_DATA \reg
.endr
cr0: .long 0
cr0s: .ascii "cr0 = \0"
after_data:
.irp reg, ax, bx, cx, dx, cs, ds, es, fs, gs, ss
INITIAL_STORE \reg
.endr
/* Does not have a 16-bit mov version. */
mov %cr0, %eax
mov %eax, cr0
/*
We delay a full BEGIN as late as possible
to mess with less initial state.
*/
BEGIN
STAGE2
.irp reg, bx, cx, dx, cs, ds, es, fs, gs, ss
.irp reg, ax, bx, cx, dx, cs, ds, es, fs, gs, ss
INITIAL_PRINT \reg
.endr
.endr
PRINT $cr0s
PRINT_BYTES cr0, $4
PRINT_NEWLINE
hlt
.irp reg, bx, cx, dx, cs, ds, es, fs, gs, ss
INITIAL_DATA \reg
.endr
hlt

View File

@@ -7,8 +7,6 @@ Expected outcome: 'ab' gets printed to the screen.
TODO: is STI not needed because this interrupt is not maskable?
TODO: interrupt priority: order looks like: 0, 1, 2, 8, 9, 10, 11, 12, 13, 14, 15, 3, 4, 5, 6, 7. What is that?
## int
What it does:
@@ -54,6 +52,19 @@ http://wiki.osdev.org/IVT
osdev says that the default address is 0:0, and that it shouldn't be changed by LIDT,
as it is incompatible with older CPUs.
## Interrupt priority
Volume 3 6.9 "PRIORITY AMONG SIMULTANEOUS EXCEPTIONS AND INTERRUPTS"
says that interrupts have different priorities that arrive
at the same cycle have different priorities.
TODO make a minimal example.
## Fault vs interrupt vs trap vs abort
Volume 3 Table 6-1. "Protected-Mode Exceptions and Interrupts"
classifies interrupts into multiple types. What is the difference between them?
*/
#include "common.h"

55
page_fault.S Normal file
View File

@@ -0,0 +1,55 @@
/*
# Page fault
Generate and handle a page fault.
Expected output:
Page fault handled. Error code:
00000002
## Error code
*/
#include "common.h"
BEGIN
CLEAR
STAGE2
PROTECTED_MODE
IDT_SETUP_ENTRY $14, $handler
lidt idt_descriptor
SETUP_PAGING_4M
/* Make page 0 not present, so that any access to it will segfault. */
mov page_table, %eax
and $0xFFFFFFFE, %eax
mov %eax, page_table
PAGING_ON
/* Access page 0, generating a segfault. */
mov %eax, 0
PAGING_OFF
jmp .
IDT_START
IDT_SKIP 14
IDT_ENTRY
IDT_END
handler:
VGA_PRINT_STRING $message
/*
Mandatory because page faults push the error code to the stack.
If we don't do this, then the stack will be wrong for iret, likely leading to a general fault exception:
http://stackoverflow.com/questions/10581224/iret-with-13-interruptgeneral-protection-fault-and-error-0x18
*/
pop %eax
VGA_PRINT_HEX_4 <%eax>
/* Make the page present. because iret will return to before the mov, and we'd get and infinite loop. */
mov page_table, %eax
or $1, %eax
mov %eax, page_table
iret
message:
.asciz "Page fault handled. Error code:"

143
paging.S
View File

@@ -1,14 +1,12 @@
/*
# Paging.
# Paging
Expected output:
00001234
00005678
Verbose tutorial: http://www.cirosantilli.com/x86-paging/
TODO: move all this info to the tutorial.
Verbose beginner's tutorial: http://www.cirosantilli.com/x86-paging/
Keep the following Intel shorthands in mind:
@@ -16,33 +14,9 @@ Keep the following Intel shorthands in mind:
- 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:
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.
@@ -56,48 +30,25 @@ 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
call setup_page_table
SETUP_PAGING_4M
/* 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
VGA_PRINT_HEX_4 0x1000
/* Make the page 0 point to page 1. */
/* Make page 0 point to 4KiB. */
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
PAGING_ON
/*
THIS is what we've been working for!!!
@@ -112,84 +63,8 @@ BEGIN
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
PAGING_OFF
/* Print the (hopefully) modified value 0x5678. */
VGA_PRINT_HEX 0x1000
VGA_PRINT_HEX_4 0x1000
jmp .
message:
.asciz "hello world"
setup_page_table:
/*
Setup a single directory: 2^10 * 2^12 == 4MiB of identity memory.
Make all pages of the first directory into an identity map (linear address == logical address).
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:
ret
/*
.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:
*/

View File

@@ -11,7 +11,7 @@ Expected output:
#include "common.h"
BEGIN
CLEAR
PRINT_BYTES $s, $17
PRINT_BYTES $s, $s_len
hlt
s:
.ascii "@ABCDEFGHIJKLMNOP"
s: .ascii "@ABCDEFGHIJKLMNOP"
.equ s_len, . - s

14
test_vga_print_bytes.S Normal file
View File

@@ -0,0 +1,14 @@
/*
Test VGA_PRINT_BYTES
TODO implement
*/
#include "common.h"
BEGIN
CLEAR
PROTECTED_MODE
/* VGA_BYTES $s, $s_len */
hlt
s: .ascii "@ABCDEFGHIJKLMNOP"
.equ s_len, . - s