DRY up protected mode

This commit is contained in:
Ciro Santilli
2015-10-18 14:56:12 +02:00
parent febbb83254
commit ae4a413b5c
6 changed files with 237 additions and 192 deletions

View File

@@ -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
View File

@@ -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

View File

@@ -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
View File

@@ -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

View File

@@ -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
View 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