DRY up protected mode
This commit is contained in:
@@ -42,6 +42,7 @@ Hello world programs that run without an operating system.
|
||||
1. [in beep_illinois](in_beep_illinois.S)
|
||||
1. [in mouse (TODO)](in_mouse.S)
|
||||
1. [Protected mode](protected-mode.S)
|
||||
1. [Segmentation](segmentation.md)
|
||||
1. Segmentation offset
|
||||
1. Segmentation fault handler: memory bound, ring, RWX violations
|
||||
1. APM
|
||||
|
||||
18
TODO.md
18
TODO.md
@@ -23,14 +23,6 @@
|
||||
- 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
|
||||
|
||||
- lgdt:
|
||||
|
||||
- http://stackoverflow.com/questions/21128311/the-physical-address-of-global-descriptor-table
|
||||
- http://stackoverflow.com/questions/7415515/problem-accessing-control-registers-cr0-cr2-cr3
|
||||
- http://stackoverflow.com/questions/10671147/how-do-x86-page-tables-work?rq=1
|
||||
- http://stackoverflow.com/questions/14354626/how-to-create-two-separate-segments-in-global-descriptor-table
|
||||
- http://stackoverflow.com/questions/14812160/near-and-far-jmps
|
||||
|
||||
- lidt, interrupts, IDTR:
|
||||
|
||||
- http://stackoverflow.com/questions/3392831/what-happens-in-an-interrupt-service-routine
|
||||
@@ -43,7 +35,7 @@
|
||||
|
||||
- WRMSR https://en.wikipedia.org/wiki/Model-specific_register http://x86.renejeschke.de/html/file_module_x86_id_326.html
|
||||
|
||||
- Segment registers and protected mode. Then try to answer all of:
|
||||
- Segment registers /segmentation and protected mode. Then try to answer all of:
|
||||
|
||||
http://stackoverflow.com/questions/18736663/what-does-the-colon-mean-in-x86-assembly-gas-syntax-as-in-dsbx
|
||||
- http://reverseengineering.stackexchange.com/questions/2006/how-are-the-segment-registers-fs-gs-cs-ss-ds-es-used-in-linux
|
||||
@@ -75,6 +67,14 @@
|
||||
- http://stackoverflow.com/questions/19502868/meaning-of-cs-and-ss-registers-on-x86-64-linux-in-userland
|
||||
- http://stackoverflow.com/questions/7844963/how-to-interpret-segment-register-accesses-on-x86-64
|
||||
|
||||
lgdt:
|
||||
|
||||
- http://stackoverflow.com/questions/21128311/the-physical-address-of-global-descriptor-table
|
||||
- http://stackoverflow.com/questions/7415515/problem-accessing-control-registers-cr0-cr2-cr3
|
||||
- http://stackoverflow.com/questions/10671147/how-do-x86-page-tables-work?rq=1
|
||||
- http://stackoverflow.com/questions/14354626/how-to-create-two-separate-segments-in-global-descriptor-table
|
||||
- http://stackoverflow.com/questions/14812160/near-and-far-jmps
|
||||
|
||||
- timer, IPT
|
||||
|
||||
- BIOS
|
||||
|
||||
@@ -53,6 +53,8 @@ The following did not work on my machine out of the box:
|
||||
|
||||
Just works, but examples are non-minimal, lots of code duplication and blobs. There must be around 20 El Torito blobs in that repo.
|
||||
|
||||
Multiboot based.
|
||||
|
||||
- <https://github.com/SamyPesse/How-to-Make-a-Computer-Operating-System>
|
||||
|
||||
- <http://www.brokenthorn.com/Resources/OSDevIndex.html>
|
||||
|
||||
162
common.h
162
common.h
@@ -2,29 +2,32 @@
|
||||
Using macros for everything to make linking simpler.
|
||||
|
||||
The big ones do bloat the executable.
|
||||
|
||||
## Calling convention
|
||||
*/
|
||||
|
||||
.altmacro
|
||||
|
||||
#define BEGIN \
|
||||
.code16 ;\
|
||||
cli ;\
|
||||
/* This sets %cs to 0. TODO Is that really needed? */ ;\
|
||||
ljmp $0, $1f;\
|
||||
1:;\
|
||||
xor %ax, %ax ;\
|
||||
.macro BEGIN
|
||||
.code16
|
||||
cli
|
||||
/* Set %cs to 0. TODO Is that really needed? */ ;\
|
||||
ljmp $0, $1f
|
||||
1:
|
||||
xor %ax, %ax
|
||||
/* We must zero %ds for any data access. */ \
|
||||
mov %ax, %ds ;\
|
||||
/* TODO is this really need to clear all those segment registers, e.g. for BIOS calls? */ \
|
||||
mov %ax, %es ;\
|
||||
mov %ax, %fs ;\
|
||||
mov %ax, %gs ;\
|
||||
mov %ax, %ds
|
||||
/* TODO is it really need to clear all those segment registers, e.g. for BIOS calls? */ \
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
/* TODO What to move into BP and SP? http://stackoverflow.com/questions/10598802/which-value-should-be-used-for-sp-for-booting-process */ \
|
||||
mov 0x0000, %bp ;\
|
||||
mov 0x0000, %bp
|
||||
/* Automatically disables interrupts until the end of the next instruction. */ \
|
||||
mov %ax, %ss ;\
|
||||
mov %ax, %ss
|
||||
/* We should set SP because BIOS calls may depend on that. TODO confirm. */ \
|
||||
mov %bp, %sp
|
||||
.endm
|
||||
|
||||
/*
|
||||
Load stage2 from disk to memory, and jump to it.
|
||||
@@ -38,20 +41,86 @@ Sample usage:
|
||||
STAGE2
|
||||
Stage 2 code here.
|
||||
*/
|
||||
#define STAGE2 \
|
||||
mov $2, %ah;\
|
||||
/* TODO get working on linker script. Above my paygrade for now, so I just load a bunch of sectors instead. */;\
|
||||
/* mov __stage2_size, %al;\ */;\
|
||||
mov $9, %al;\
|
||||
mov $0x80, %dl;\
|
||||
mov $0, %ch;\
|
||||
mov $0, %dh;\
|
||||
mov $2, %cl;\
|
||||
mov $1f, %bx;\
|
||||
int $0x13;\
|
||||
jmp 1f;\
|
||||
.section .stage2;\
|
||||
.macro STAGE2
|
||||
mov $2, %ah
|
||||
/*
|
||||
TODO get working on linker script.
|
||||
Above my paygrade for now, so I just load a bunch of sectors instead.
|
||||
*/
|
||||
/* mov __stage2_size, %al;\ */
|
||||
mov $9, %al
|
||||
mov $0x80, %dl
|
||||
mov $0, %ch
|
||||
mov $0, %dh
|
||||
mov $2, %cl
|
||||
mov $1f, %bx
|
||||
int $0x13
|
||||
jmp 1f
|
||||
.section .stage2
|
||||
1:
|
||||
.endm
|
||||
|
||||
/*
|
||||
Enter protected mode.
|
||||
|
||||
Use the simplest GDT possible.
|
||||
*/
|
||||
.macro PROTECTED_MODE
|
||||
cli
|
||||
/* Tell the processor where our Global Descriptor Table is in memory. */
|
||||
lgdt gdt_descriptor
|
||||
|
||||
/* Set PE (Protection Enable) bit in CR0 (Control Register 0) */
|
||||
mov %cr0, %eax
|
||||
orl $0x1, %eax
|
||||
mov %eax, %cr0
|
||||
|
||||
/* TODO why does equ not work? What is the alternative? */
|
||||
/*ljmp $CODE_SEG, $b32*/
|
||||
/*
|
||||
This is needed to set `%cs` to 8.
|
||||
|
||||
8 means the second entry, since each entry is 8 bytes wide,
|
||||
and we have an initial null entry.
|
||||
*/
|
||||
ljmp $0x08, $protected_mode
|
||||
.code32
|
||||
gdt_start:
|
||||
gdt_null:
|
||||
.long 0x0
|
||||
.long 0x0
|
||||
gdt_code:
|
||||
.word 0xffff
|
||||
.word 0x0
|
||||
.byte 0x0
|
||||
.byte 0b10011010
|
||||
.byte 0b11001111
|
||||
.byte 0x0
|
||||
gdt_data:
|
||||
.word 0xffff
|
||||
.word 0x0
|
||||
.byte 0x0
|
||||
.byte 0b10010010
|
||||
.byte 0b11001111
|
||||
.byte 0x0
|
||||
gdt_end:
|
||||
gdt_descriptor:
|
||||
.word gdt_end - gdt_start
|
||||
.long gdt_start
|
||||
.equ CODE_SEG, gdt_code - gdt_start
|
||||
.equ DATA_SEG, gdt_data - gdt_start
|
||||
protected_mode:
|
||||
/* Setup the other segments. */
|
||||
mov $DATA_SEG, %ax
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
mov %ax, %ss
|
||||
/* TODO: what is the maximum we can put were? Why does `FFFFFFFF` fail? */
|
||||
mov $0XFFFFFF00, %ebp
|
||||
mov %ebp, %esp
|
||||
.endm
|
||||
|
||||
/* BIOS */
|
||||
|
||||
@@ -62,13 +131,14 @@ Sample usage:
|
||||
int $0x10
|
||||
|
||||
/* Clear the screen, move to position 0, 0. */
|
||||
#define CLEAR \
|
||||
mov $0x0600, %ax;\
|
||||
mov $0x7, %bh;\
|
||||
mov $0x0, %cx;\
|
||||
mov $0x184f, %dx;\
|
||||
int $0x10;\
|
||||
.macro CLEAR
|
||||
mov $0x0600, %ax
|
||||
mov $0x7, %bh
|
||||
mov $0x0, %cx
|
||||
mov $0x184f, %dx
|
||||
int $0x10
|
||||
CURSOR_POSITION(0, 0)
|
||||
.endm
|
||||
|
||||
/*
|
||||
Print a single immediate byte or 8 bit register.
|
||||
@@ -152,7 +222,6 @@ Use as:
|
||||
We use this `cpp` macro to allow writing `PRINT(S)` with parenthesis.
|
||||
*/
|
||||
#define PRINT(s) GAS_PRINT s
|
||||
/* We need a Gas macro for the LOCAL labels. */
|
||||
.macro GAS_PRINT s
|
||||
LOCAL loop, end
|
||||
mov s, %si
|
||||
@@ -167,3 +236,28 @@ end:
|
||||
.endm
|
||||
|
||||
/* VGA */
|
||||
|
||||
/*
|
||||
Print a NULL terminated string to position 0 in VGA.
|
||||
|
||||
s: 32-bit register or memory containing the address of the string to print.
|
||||
|
||||
Clobbers: eax, ecx, edx
|
||||
*/
|
||||
.macro VGA_PRINT s
|
||||
LOCAL loop, end
|
||||
mov s, %ecx
|
||||
/* Video memory address. */
|
||||
mov $0xb8000, %edx
|
||||
/* White on black. */
|
||||
mov $0x0f, %ah
|
||||
loop:
|
||||
mov (%ecx), %al
|
||||
cmp $0, %al
|
||||
je end
|
||||
mov %ax, (%edx)
|
||||
add $1, %ecx
|
||||
add $2, %edx
|
||||
jmp loop
|
||||
end:
|
||||
.endm
|
||||
|
||||
160
protected_mode.S
160
protected_mode.S
@@ -1,170 +1,32 @@
|
||||
/*
|
||||
# Protected mode
|
||||
|
||||
Major changes:
|
||||
Major changes from real moe:
|
||||
|
||||
- BIOS cannot be used anymore
|
||||
|
||||
- GDT takes effect immediately so we *have* to deal with it now,
|
||||
and we will later turn on paging
|
||||
- GDT and segmentation take effect immediately so we *have*
|
||||
to deal with it now.
|
||||
|
||||
- we have to encode instructions differently.
|
||||
Note that in 16-bit 32-bit instructions were encodable, but with a prefix.
|
||||
- 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.
|
||||
|
||||
## Bibliography
|
||||
|
||||
- http://stackoverflow.com/questions/28645439/how-do-i-enter-32-bit-protected-mode-in-nasm-assembly
|
||||
- http://stackoverflow.com/questions/28645439/how-do-i-enter-32-bit-protected-mode-in-nasm-assembly Initially adapted from this.
|
||||
- http://wiki.osdev.org/Journey_To_The_Protected_Land
|
||||
- http://wiki.osdev.org/Protected_Mode
|
||||
- https://github.com/chrisdew/xv6/blob/master/bootasm.S
|
||||
- https://thiscouldbebetter.wordpress.com/2011/03/17/entering-protected-mode-from-assembly/ FASM based. Did not word on first try, but looks real clean.
|
||||
- http://skelix.net/skelixos/tutorial02_en.html
|
||||
|
||||
## GDT
|
||||
|
||||
Table in memory that gives properties of segment registers.
|
||||
|
||||
Segment registers in protected mode point to entries of that table.
|
||||
|
||||
GDT is used as soon as we enter protected mode, so that's why we have to deal with it, but the preferred way of managing program memory spaces is paging.
|
||||
|
||||
### Effect on memory access
|
||||
|
||||
The GDT modifies every memory access of a given segment by:
|
||||
|
||||
- adding an offset to it
|
||||
- limiting how big the segment is
|
||||
|
||||
If an access is made at an offset larger than allowed: TODO some exception happens, which is like an interrupt, and gets handled by a previously registered handler.
|
||||
|
||||
The GDT could be used to implement virtual memory by using one segment per program:
|
||||
|
||||
+-----------+--------+--------------------------+
|
||||
| Program 1 | Unused | Program 2 |
|
||||
+-----------+--------+--------------------------+
|
||||
^ ^ ^ ^
|
||||
| | | |
|
||||
Start1 End1 Start2 End2
|
||||
|
||||
The problem with that is that each program must have one segment, so if we have too many programs, fragmentation will be very large.
|
||||
|
||||
Paging gets around this by allowing discontinuous memory ranges of fixed size for each program.
|
||||
|
||||
The format of the GDT is given at: http://wiki.osdev.org/Global_Descriptor_Table
|
||||
|
||||
### Effect on permissions
|
||||
|
||||
Besides fixing segment sizes, the GDT also specifies permissions to the program that is running:
|
||||
|
||||
- ring level: limits several things that can or not be done, in particular:
|
||||
- instructions: e.g. no in / out in ring 3
|
||||
- register access: e.g. cannot modify control registers like the GDTR in ring 3. Otherwise user programs could just escape restrictions by changing that!
|
||||
- executable, readable and writable bits: which operations can be done
|
||||
|
||||
## GDTR
|
||||
|
||||
## GDT register
|
||||
|
||||
In 32-bit, a 6 byte register that holds:
|
||||
|
||||
- 2 byte length of the GDT (TODO in bytes or number of entries?)
|
||||
- 4 byte address of the GDT in memory
|
||||
|
||||
In 64 bit, makes 10 bytes, with the address having 8 bytes
|
||||
|
||||
GRUB seems to setup one for you: http://www.jamesmolloy.co.uk/tutorial_html/4.-The%20GDT%20and%20IDT.html
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
BEGIN
|
||||
|
||||
CLEAR
|
||||
|
||||
cli
|
||||
/* Tell the processor where our Global Descriptor Table is in memory. */
|
||||
lgdt gdt_descriptor
|
||||
|
||||
/* Set PE (Protection Enable) bit in CR0 (Control Register 0) */
|
||||
mov %cr0, %eax
|
||||
orl $0x1, %eax
|
||||
mov %eax, %cr0
|
||||
|
||||
/* TODO why does equ not work? What is the alternative? */
|
||||
/*ljmp $CODE_SEG, $b32*/
|
||||
/*
|
||||
This is needed to set `%cs` to 8.
|
||||
|
||||
8 means the second entry, since each entry is 8 bytes wide,
|
||||
and we have an initial null entry.
|
||||
*/
|
||||
ljmp $0x08, $b32
|
||||
|
||||
.code32
|
||||
|
||||
print32:
|
||||
pusha
|
||||
# Video memory address.
|
||||
mov $0xb8000, %edx
|
||||
print32.loop:
|
||||
mov (%ebx), %al
|
||||
# White on black.
|
||||
mov $0x0f, %ah
|
||||
cmp $0, %al
|
||||
je print32.done
|
||||
mov %ax, (%edx)
|
||||
add $1, %ebx
|
||||
add $2, %edx
|
||||
jmp print32.loop
|
||||
print32.done:
|
||||
popa
|
||||
ret
|
||||
|
||||
b32:
|
||||
/* Setup the other segments. */
|
||||
mov $DATA_SEG, %ax
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
mov %ax, %ss
|
||||
/* TODO: what is the maximum we can put were? Why does `FFFFFFFF` fail? */
|
||||
mov $0XFFFFFF00, %ebp
|
||||
mov %ebp, %esp
|
||||
|
||||
mov $message, %ebx
|
||||
call print32
|
||||
|
||||
PROTECTED_MODE
|
||||
VGA_PRINT $message
|
||||
jmp .
|
||||
|
||||
gdt_start:
|
||||
|
||||
gdt_null:
|
||||
.long 0x0
|
||||
.long 0x0
|
||||
|
||||
gdt_code:
|
||||
.word 0xffff
|
||||
.word 0x0
|
||||
.byte 0x0
|
||||
.byte 0b10011010
|
||||
.byte 0b11001111
|
||||
.byte 0x0
|
||||
|
||||
gdt_data:
|
||||
.word 0xffff
|
||||
.word 0x0
|
||||
.byte 0x0
|
||||
.byte 0b10010010
|
||||
.byte 0b11001111
|
||||
.byte 0x0
|
||||
|
||||
gdt_end:
|
||||
|
||||
gdt_descriptor:
|
||||
.word gdt_end - gdt_start
|
||||
.long gdt_start
|
||||
|
||||
.equ CODE_SEG, gdt_code - gdt_start
|
||||
.equ DATA_SEG, gdt_data - gdt_start
|
||||
|
||||
message: .asciz "hello world"
|
||||
message:
|
||||
.asciz "hello world"
|
||||
|
||||
86
segmentation.md
Normal file
86
segmentation.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Segmentation
|
||||
|
||||
This is about protected mode segmentation, which coverts liner to global addresses.
|
||||
|
||||
## GDT
|
||||
|
||||
Table in memory that gives properties of segment registers.
|
||||
|
||||
Segment registers in protected mode point to entries of that table.
|
||||
|
||||
GDT is used as soon as we enter protected mode, so that's why we have to deal with it, but the preferred way of managing program memory spaces is paging.
|
||||
|
||||
Format:
|
||||
|
||||
- Intel Manual 325384-053US Volume 3, 3.4.5 Segment Descriptors
|
||||
- https://en.wikipedia.org/wiki/Global_Descriptor_Table
|
||||
- http://wiki.osdev.org/GDT
|
||||
|
||||
+-------------------------------------------------+
|
||||
| segment address 24-31 | flags #2 | len 16-19 |
|
||||
+-------------------------------------------------+
|
||||
| flags #1 | segment address 16-23 |
|
||||
+-------------------------------------------------+
|
||||
| segment address bits 0-15 |
|
||||
+-------------------------------------------------+
|
||||
| segment length bits 0-15 |
|
||||
+-------------------------------------------------+
|
||||
|
||||
### Null segment selector
|
||||
|
||||
### Null descriptor
|
||||
|
||||
Intel manual 3.4.2 Segment Selectors says:
|
||||
|
||||
> The first entry of the GDT is not used by the processor. A segment selector that points to this entry of the GDT (that
|
||||
is, a segment selector with an index of 0 and the TI flag set to 0) is used as a “null segment selector.” The processor
|
||||
does not generate an exception when a segment register (other than the CS or SS registers) is loaded with a null
|
||||
selector. It does, however, generate an exception when a segment register holding a null selector is used to access
|
||||
memory. A null selector can be used to initialize unused segment registers. Loading the CS or SS register with a null
|
||||
segment selector causes a general-protection exception (#GP) to be generated.
|
||||
|
||||
### Effect on memory access
|
||||
|
||||
The GDT modifies every memory access of a given segment by:
|
||||
|
||||
- adding an offset to it
|
||||
- limiting how big the segment is
|
||||
|
||||
If an access is made at an offset larger than allowed: TODO some exception happens, which is like an interrupt, and gets handled by a previously registered handler.
|
||||
|
||||
The GDT could be used to implement virtual memory by using one segment per program:
|
||||
|
||||
+-----------+--------+--------------------------+
|
||||
| Program 1 | Unused | Program 2 |
|
||||
+-----------+--------+--------------------------+
|
||||
^ ^ ^ ^
|
||||
| | | |
|
||||
Start1 End1 Start2 End2
|
||||
|
||||
The problem with that is that each program must have one segment, so if we have too many programs, fragmentation will be very large.
|
||||
|
||||
Paging gets around this by allowing discontinuous memory ranges of fixed size for each program.
|
||||
|
||||
The format of the GDT is given at: http://wiki.osdev.org/Global_Descriptor_Table
|
||||
|
||||
### Effect on permissions
|
||||
|
||||
Besides fixing segment sizes, the GDT also specifies permissions to the program that is running:
|
||||
|
||||
- ring level: limits several things that can or not be done, in particular:
|
||||
- instructions: e.g. no in / out in ring 3
|
||||
- register access: e.g. cannot modify control registers like the GDTR in ring 3. Otherwise user programs could just escape restrictions by changing that!
|
||||
- executable, readable and writable bits: which operations can be done
|
||||
|
||||
## GDTR
|
||||
|
||||
## GDT register
|
||||
|
||||
In 32-bit, a 6 byte register that holds:
|
||||
|
||||
- 2 byte length of the GDT (TODO in bytes or number of entries?)
|
||||
- 4 byte address of the GDT in memory
|
||||
|
||||
In 64 bit, makes 10 bytes, with the address having 8 bytes
|
||||
|
||||
GRUB seems to setup one for you: http://www.jamesmolloy.co.uk/tutorial_html/4.-The%20GDT%20and%20IDT.html
|
||||
Reference in New Issue
Block a user