Move all documentation to README.adoc

This includes both separate .md files, and documentation that was on the
head of the .S source files.

Retest everything as this was done, and fix a few easy things.
This commit is contained in:
Ciro Santilli
2018-05-06 14:33:33 +01:00
parent ce9636f324
commit fb3c7e04c4
110 changed files with 2945 additions and 3287 deletions

3
LICENSE.adoc Normal file
View File

@@ -0,0 +1,3 @@
= LICENSE
See: link:README.adoc#license[]

View File

@@ -1,17 +0,0 @@
# LICENSE
Copyright Ciro Santilli <http://www.cirosantilli.com/>
[GPL v3](https://www.gnu.org/licenses/gpl-3.0.txt) for executable computer program usage.
[CC BY-SA v4](https://creativecommons.org/licenses/by-sa/4.0/) for human consumption usage in learning material, e.g. `.md` files, source code comments, using source code excerpts in tutorials. Recommended attribution:
- Single file adaptations:
Based on https://github.com/cirosantilli/x86-bare-metal-examples/blob/<commit-id>/path/to/file.md under CC BY-SA v4
- Multi-file adaptations:
Based on https://github.com/cirosantilli/x86-bare-metal-examples/tree/<commit-id> under CC BY-SA v4
If you want to use this work under a different license, contact the copyright owner, and he might make a good price.

View File

@@ -39,7 +39,7 @@ clean:
rm -fr *$(OBJ_EXT) *$(OUT_EXT) *$(TMP_EXT) rm -fr *$(OBJ_EXT) *$(OUT_EXT) *$(TMP_EXT)
run: $(RUN_FILE) run: $(RUN_FILE)
$(QEMU) -drive 'file=$(RUN_FILE),format=raw' -smp 2 $(QEMU) -drive 'file=$(RUN_FILE),format=raw' -smp 2 -soundhw pcspk
debug: $(RUN_FILE) debug: $(RUN_FILE)
$(QEMU) -hda '$(RUN_FILE)' -S -s & $(QEMU) -hda '$(RUN_FILE)' -S -s &

2019
README.adoc Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,88 +0,0 @@
# x86 Bare Metal Examples
Dozens of minimal operating systems to learn x86 system programming. Tested on Ubuntu 17.10 host. Userland cheat at: <https://github.com/cirosantilli/x86-assembly-cheat>
1. [**Getting started**](getting-started.md)
1. [About](about.md)
1. Examples
1. [printf](printf/)
1. [min](min.S)
1. [No ld script](no-ld-script/)
1. BIOS
1. [putc](bios_putc.S)
1. [hello world](bios_hello_world.S)
1. [NASM](nasm/)
1. [newline](bios_newline.S)
1. [carriage return](bios_carriage_return.S)
1. [cursor position](bios_cursor_position.S)
1. [color](bios_color.S)
1. [background](bios_background.S)
1. [scroll](bios_scroll.S)
1. [clear screen](bios_clear_screen.S)
1. [pixel](bios_pixel.S)
1. [pixel line](bios_pixel_line.S)
1. [keyboard](bios_keyboard.S)
1. [keyboard loop](bios_keyboard_loop.S)
1. [disk load](bios_disk_load.S)
1. [disk load 2](bios_disk_load2.S)
1. [detect memory](bios_detect_memory.S)
1. [tick count](bios_tick_count.S)
1. [Initial state](initial_state.S)
1. [CPU](cpu.md)
1. [Segment registers](segment_registers.S)
1. [SS](ss.S)
1. [CS](cs.S)
1. [Interrupt](interrupt.S)
1. [int \$1](interrupt1.S)
1. [Interrupt zero divide](interrupt_zero_divide.S)
1. [Interrupt loop](interrupt_loop.S)
1. [in](in.md)
1. [in keyboard](in_keyboard.S)
1. [RTC](rtc.S)
1. [PIT](pit.S)
1. [PIT once](pit_once.S)
1. [in beep](in_beep.S)
1. [in beep_illinois](in_beep_illinois.S)
1. [in mouse (TODO)](in_mouse.S)
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. [PIT protected](pit_protected.S)
1. Segmentation fault handler: memory bound, ring, RWX violations
1. [Paging](paging.S)
1. [Page fault](page_fault.S)
1. Power
1. [reboot](reboot.S)
1. APM
1. [APM shutdown](apm_shutdown.S)
1. [APM shutdown 2](apm_shutdown2.S)
1. SMP
1. [Theory](smp.md)
1. [Example](smp.S)
1. Libraries
1. [Multiboot](multiboot/)
1. [GRUB](grub/)
1. TODO not working
1. [UEFI](uefi/)
1. Misc
1. [hajji](hajji/)
1. Theory
1. [Modes of operation](modes-of-operation.md)
1. [Segmentation](segmentation.md)
1. [Formats](formats.md)
1. [MBR](mbr.md)
1. [IO](io.md)
1. [BIOS](bios.md)
1. [APM](apm.md)
1. [PIC](pic.md)
1. [Debug](debug.md)
1. [Bibliography](bibliography.md)
1. Tests
1. [PRINT_BYTES](test_print_bytes.S)
1. [PRINT_BYTES](test_pit_sleep.S)
1. [LICENSE](LICENSE.md)
1. [TODO](TODO.md)
1. [ring](ring.md)

144
TODO.md
View File

@@ -1,144 +0,0 @@
# TODO
- SMP sync
- MONITOR/MWAIT
- LOCK prefix
- MFENCE
- LFENCE
- SFENCE
- http://stackoverflow.com/a/33651438/895245
- cache:
- http://stackoverflow.com/questions/1756825/how-can-i-do-a-cpu-cache-flush
- http://stackoverflow.com/questions/10989403/how-is-x86-instruction-cache-synchronized
- wbinvd: write back from cache to memory, and invalidate all cache
- invd: just invalidate, but don't write back
- clflush: flush only selected cache lines
- lidt, interrupts, IDTR:
- https://en.wikipedia.org/wiki/Double_fault
- https://en.wikipedia.org/wiki/Triple_fault
- PIC example
- paging
Page fault:
- http://stackoverflow.com/questions/5684365/what-causes-page-faults/5690636#5690636
- http://stackoverflow.com/questions/15275059/whats-the-purpose-of-x86-cr0-wp-bit
Dirty:
- http://stackoverflow.com/questions/7924031/how-prompt-is-x86-at-setting-the-page-dirty-bit/7926931#7926931
- http://stackoverflow.com/questions/21211942/x86-page-fault-description
- 64-bit
http://stackoverflow.com/questions/22962251/how-to-enter-64-bit-mode-on-a-x86-64/22963701#22963701
- Segment registers /segmentation and protected mode. Then try to answer all of: GDT
Bunch of basic dupes:
- answered: http://stackoverflow.com/questions/4119504/real-mode-memory-addressing-explanation
- http://stackoverflow.com/questions/3819699/what-does-ds40207a-mean-in-assembly
In actual OSes:
- http://stackoverflow.com/questions/22446104/do-the-x86-segment-registers-have-special-meaning-usage-on-modern-cpus-and-oses?lq=1
- http://reverseengineering.stackexchange.com/questions/2006/how-are-the-segment-registers-fs-gs-cs-ss-ds-es-used-in-linux
- http://stackoverflow.com/questions/14480579/when-does-segment-registers-change
- http://stackoverflow.com/questions/12760109/how-is-the-x86-data-segment-used-in-real-operating-systems-and-processes
- http://stackoverflow.com/questions/6611346/amd64-fs-gs-registers-in-linux
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
- http://stackoverflow.com/questions/21165678/why-64-bit-mode-long-mode-doesnt-use-segment-registers
- 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
Local descriptor table:
- http://stackoverflow.com/questions/14299893/any-tutorials-on-how-to-set-up-a-ldt?rq=1
- WRMSR https://en.wikipedia.org/wiki/Model-specific_register http://x86.renejeschke.de/html/file_module_x86_id_326.html
- BIOS
- pages
- ACPI
- play with hardware
- set a pixel on screen in protected mode http://stackoverflow.com/questions/14419088/assembly-draw-a-pixel-on-the-screen-in-protected-mode
- USB: http://stackoverflow.com/questions/11810736/usb-control-in-x86-real-mode?rq=1
- networking
- GPU...
- how to setup arbitrary hardware: ports and IRQs http://stackoverflow.com/questions/773199/how-do-i-identify-device-specific-interrupts-on-x86
- PCI: 0xCF8 <http://stackoverflow.com/questions/15574717/pci-device-check-in-assembly-language>
- POST https://en.wikipedia.org/wiki/Power-on_self-test
- exemplify all CPU modes
- http://stackoverflow.com/questions/20848412/modes-of-intel-64-cpu
- https://en.wikipedia.org/wiki/Task_state_segment
Not used by Linux: <http://stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss>
- keyboard through interrupt (high level BIOS int 16 that waits for input done)
Almost there! pit_protected almost works, the problem is that it only fires once: <http://board.flatassembler.net/topic.php?t=17437>
Needs some resetting fixed it seems.
- happens on IRQ 1 as mentioned at: https://en.wikipedia.org/wiki/Interrupt_request_%28PC_architecture%29#Master_PIC
- keyboard protected mode: http://stackoverflow.com/questions/219120/x86-assembly-protected-mode-keyboard-access
- oszur does it with the i8042: http://stackoverflow.com/questions/22744624/keyboard-interrupt-handler-for-own-kernel-c
- https://github.com/arjun024/mkeykernel contains a small example that is easy to dissect
- mouse
Does not seem to be an easy BIOS way:
- DOS question: http://stackoverflow.com/questions/23043732/accessing-the-mouse-via-assembly-x86
- http://stackoverflow.com/questions/5754233/int-33h-doesnt-work
- Linux questions:
- 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
- HPET https://en.wikipedia.org/wiki/High_Precision_Event_Timer
- BIOS memory detect:
http://stackoverflow.com/questions/21820814/what-happens-with-a-processor-when-it-tries-to-access-a-nonexistent-physical-add
- `40h:6Ch` a bios timer incremented at 18.2 Hz: `

View File

@@ -1,47 +0,0 @@
# About
## System vs userland
This repository covers only things that can only be done from ring 0 (system) and not ring 3 (userland).
Ring 3 is covered at: <https://github.com/cirosantilli/x86-assembly-cheat>
An overview of rings 0 and 3 can be found at: <https://stackoverflow.com/questions/18717016/what-are-ring-0-and-ring-3-in-the-context-of-operating-systems/44483439#44483439>
## One minimal concept per OS
There are a few tutorials that explain how to make an operating system and give examples of increasing complexity with more and more functionality added.
This is not one of them.
The goal of this repository is to use the minimal setup possible to be able to observe *a single* low-level programming concept for each minimal operating system we create.
This is not meant provide a template from which you can write a real OS, but instead to illustrate how those low-level concepts work in isolation, so that you can use that knowledge to implement operating systems or drivers.
Minimal examples are useful because it is easier to observe the requirements for a given concept to be observable.
Another advantage is that it is easier to DRY up minimal examples (here done simply through `#include` and macros), which is much harder on progressive OS template tutorials, which tend to repeat big chunks of code between the examples.
## To C or not to C
Using C or not is a hard choice.
It does make it much easier to express higher level ideas, and gives portability.
But in the end, it increases the complexity that one has to understand, so we've stayed away from it.
## Linux is open source
Always try looking into the Linux kernel to find how those CPU capabilities are used in a "real" OS.
## Pre-requisites
OS dev is one of the most insanely hard programming tasks a person can undertake, and will push your knowledge of several domains to the limit.
Knowing the following will help a lot:
- userland x86 assembly: https://github.com/cirosantilli/assembly-cheat
- compilation, linking and ELF format basics
- GDB debugging
While it is possible to learn those topics as you go along, and it is almost certain that you will end up learning more about them, we will not explain them here in detail.

11
apm.md
View File

@@ -1,11 +0,0 @@
# APM
<https://en.wikipedia.org/wiki/Advanced_Power_Management>
<http://wiki.osdev.org/APM>
Older than ACPI and simpler.
By Microsoft in 1995. Spec seems to be in RTF format...
Can't find the URL. A Google cache: <https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CB0QFjAAahUKEwj7qpLN_4XIAhWCVxoKHa_nAxY&url=http%3A%2F%2Fdownload.microsoft.com%2Fdownload%2F1%2F6%2F1%2F161ba512-40e2-4cc9-843a-923143f3456c%2FAPMV12.rtf&usg=AFQjCNHoCx8gHv-w08Dn_Aoy6Q3K3DLWRg&sig2=D_66xvI7Y2n1cvyB8d2Mmg>

View File

@@ -1,11 +1,3 @@
/*
http://wiki.osdev.org/Shutdown
http://stackoverflow.com/questions/21463908/x86-instructions-to-power-off-computer-in-real-mode
http://stackoverflow.com/questions/678458/shutdown-the-computer-using-assembly
http://stackoverflow.com/questions/3145569/how-to-power-down-the-computer-from-a-freestanding-environment
*/
#include "common.h" #include "common.h"
BEGIN BEGIN

View File

@@ -1,9 +1,3 @@
/*
Example from: http://wiki.osdev.org/APM
Is all of this really needed? Compare to apm_shutdown.S.
*/
#include "common.h" #include "common.h"
BEGIN BEGIN

View File

@@ -1,120 +0,0 @@
# Bibliography
- <https://github.com/cirosantilli/assembly-cheat> Information about assembly in general.
README.md explain `make qemu`
- <http://stackoverflow.com/questions/22054578/run-a-program-without-an-operating-system>
## Small educational projects
Fun, educational and useless:
- <https://github.com/programble/bare-metal-tetris> tested on Ubuntu 14.04. Just works.
Has Multiboot and El Torito. Uses custom linker script.
Almost entirely in C `-nostdlib`, with very few inline `asm` commands, and a small assembly entry point. So a good tutorial in how to do the bridge.
- <https://github.com/daniel-e/tetros> Tetris that fits into bootloader.
- <https://github.com/arjun024/mkeykernel>, <https://github.com/arjun024/mkernel>
Worked, but bad build system: not `Makefile` or `.gitignore`.
- <https://github.com/Overv/MineAssemble>
The following did not work on my machine out of the box:
- <https://github.com/apparentlymart/ToyOS>
- <https://github.com/rde1024/toyos>
## Tutorials
- <https://arobenko.gitbooks.io/bare_metal_cpp/content/>
### Educational NIXes
One complexity order above the minimal tutorials, one below actual kernels
- <http://www.xinu.cs.purdue.edu/>
- <https://pdos.csail.mit.edu/6.828/2014/xv6.html>
- <https://en.wikipedia.org/wiki/MINIX>, influenced Linux
### Educational non-NIXes
- <https://github.com/intermezzOS/book>
- <https://github.com/flosse/rust-os-comparison>
## Multi collaborator websites
- osdev.org is a major source for this.
- <http://wiki.osdev.org/C%2B%2B_Bare_Bones>
- <http://wiki.osdev.org/Text_UI>
- <http://wiki.osdev.org/GUI>
- <http://www.osdever.net/>
- <https://courses.engr.illinois.edu/ece390/books/labmanual/index.html> Illinois course from 2004
## Progressive tutorials
- <http://www.jamesmolloy.co.uk/tutorial_html/index.html>
Highly recommended.
Multiboot based kernels of increasing complexity, one example builds on the last one. Non DRY as a result.
Cleaned up source code: <https://github.com/cirosantilli/jamesmolloy-kernel-development-tutorials>
Well known bugs: <http://wiki.osdev.org/James_Molloy's_Tutorial_Known_Bugs> That's what happens when you don't use GitHub.
Good tutorials, author seems to master the subject.
But he could learn more about version control and build automation: source code inside ugly tar.gz with output files.
- <https://sourceforge.net/p/oszur11/code/ci/master/tree/>
GitHub mirror: <https://github.com/cirosantilli/oszur11-operating-system-examples>
Several examples of increasing complexity. Found at: <http://stackoverflow.com/questions/7130726/writing-a-hello-world-kernel>
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>
- <http://skelix.net/skelixos/index_en.html>
Cleaned up version: <https://github.com/cirosantilli/skelix-os>
Not tested yet.
GAS based, no multiboot used.
- <https://github.com/littleosbook/littleosbook>
## Actually useful
These are not meant as learning resources but rather as useful programs:
- <https://github.com/scanlime/metalkit> A more automated / general bare metal compilation system. Untested, but looks promising.
- Python without an "OS": <https://us.pycon.org/2015/schedule/presentation/378/>
## Other archs
For when we decide to port this tutorial:
ARM:
- <https://github.com/bravegnu/gnu-eprog>
Raspberry PI:
- <https://github.com/dwelch67/raspberrypi>
- <https://github.com/BrianSidebotham/arm-tutorial-rpi>

80
bios.md
View File

@@ -1,80 +0,0 @@
# BIOS
Old, non-standardized, limited API that allows you to do quick and dirty IO.
If you are making a serious OS, use it as little as possible.
<https://en.wikipedia.org/wiki/BIOS>
<http://wiki.osdev.org/BIOS>
Can only be used in real mode.
## Documentation
Does any documentation or portability exist??
<http://www.ctyme.com/intr/int.htm> Ralf Brown's Interrupt List. Everyone says that this is the ultimate unofficial compilation.
<https://en.wikipedia.org/wiki/INT_10H> good quick summary
<http://www.scs.stanford.edu/nyu/04fa/lab/specsbbs101.pdf> says little about interrupts, I don't understand it's scope.
## Video mode
There are several video modes.
Modes determine what interrupt functions can be used.
There are 2 main types of modes:
- text, where we operate character-wise
- video, operate byte-wise
Modes can be set with `int 0x10` and `AH = 0x00`, and get with `AH = 0x0F`
The most common modes seem to be:
- 0x01: 40x25 Text, 16 colors, 8 pages
- 0x03: 80x25 Text, 16 colors, 8 pages
- 0x13: 320x200 Graphics, 256 colors, 1 page
You can add 128 to the modes to prevent them from clearing the screen.
Taken from: <https://courses.engr.illinois.edu/ece390/books/labmanual/graphics-int10h.html>
A larger list: <http://www.columbia.edu/~em36/wpdos/videomodes.txt>
### VESA
<https://en.wikipedia.org/wiki/VESA_BIOS_Extensions>
TODO use it.
## Colors
## Text properties
<https://en.wikipedia.org/wiki/BIOS_color_attributes>
## Get BIOS information
## SMBIOS
## dmidecode
<http://stackoverflow.com/questions/20604644/how-to-check-the-bios-version-or-name-in-linux-through-command-prompt>
<https://en.wikipedia.org/wiki/System_Management_BIOS>
Standardized by: <https://en.wikipedia.org/wiki/Distributed_Management_Task_Force>
TODO: how is it obtained at the low level?
## SeaBIOS
<http://www.seabios.org/SeaBIOS>
Open source x86 BIOS implementation.
Default BIOS for QEMU and KVM.

View File

@@ -1,14 +1,3 @@
/*
What happens when a newline is output with bios.
Outcome:
hello
world
Carriage returns are needed just like in old days.
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
PRINT_STRING $msg PRINT_STRING $msg

View File

@@ -1,41 +1,29 @@
/*
Clear screen by scrolling.
Expected output:
- "b" with red foreground at (0, 0)
- the entire screen background is green
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
/* /* Print one 'a' char to ensure that something will be cleared.
Print one 'a' char to ensure that something will be cleared. *
* On some systems, BIOS messages get automatically cleared. Not the case for QEMU 2.0.0.
On some systems, BIOS messages get automatically cleared. Not the case for QEMU 2.0.0. * */
*/
PUTC $'a PUTC $'a
/* Scroll 0 is magic, and scrolls the entire selected rectangle. */ /* Scroll 0 is magic, and scrolls the entire selected rectangle. */
mov $0x0600, %ax mov $0x0600, %ax
mov $0xA4, %bh mov $0xA4, %bh
/* /* Pick the entire screen.
Pick the entire screen. * Bottom right is at (24,79) == (0x18,0x4F),
Bottom right is at (24,79) == (0x18,0x4F), * since we are on the default mode.
since we are on the default mode. */
*/
mov $0x0000, %cx mov $0x0000, %cx
mov $0X184F, %dx mov $0X184F, %dx
int $0x10 int $0x10
/* /* Print a 'b' char to see where we are now.
Print a 'b' char to see where we are now. *
* TODO, on ThinkPad T400, the cursor gets put back to the initial position. But QEMU 2.0.0 leaves it in the middle ofthe screen. Thus we reset the position to make them work the same way.
TODO, on ThinkPad T400, the cursor gets put back to the initial position. But QEMU 2.0.0 leaves it in the middle ofthe screen. Thus we reset the position to make them work the same way. */
*/
CURSOR_POSITION CURSOR_POSITION
PUTC $'b PUTC $'b

View File

@@ -1,36 +1,22 @@
/*
Write a character N times with given color.
Expected output: "bc", where:
- `b` has red foreground, and green background
- `c` has the default color (gray on black)
TODO: is this the only way? How to set the current color for ah = 0E?
Color codes: https://en.wikipedia.org/wiki/BIOS_color_attributes
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
/* ID, character to print. */ /* ID of character to print. */
mov $0x0961, %ax mov $0x0961, %ax
/* Page, color, */ /* Page, color, */
mov $0x0034, %bx mov $0x0034, %bx
/* /* How many times to write.
How many times to write. * If too big, wraps around screen.
If too big, wraps around screen. */
*/ mov $0x0002, %cx
mov $0x0001, %cx
int $0x10 int $0x10
/* /* The new color is reused only for character that overwrite the writen region.
The new color is reused only for character that overwrite the writen region. *
* Cursor is not moved by the previous interrupt, so this produces a colored 'a'.
Cursor is not moved by the previous interrupt, so this produces a colored 'a'. */
*/
PUTC $'b PUTC $'b
PUTC $'c PUTC $'c
PUTC $'d
hlt hlt

View File

@@ -1,9 +1,3 @@
/*
Change the current cursor position.
Expected output: "cb"
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
CLEAR CLEAR

View File

@@ -1,38 +1,3 @@
/*
# Detecte memory
# int 15
TODO Seems to be outputting trash :-)
http://wiki.osdev.org/Detecting_Memory_%28x86%29
Determine how much memory you've got, and how much of it is low memory.
This is important in particular so that you can start your stack there
when you enter protected mode, since the stack grows down.
In 16-bit mode, it does not matter much,
since most modern machines have all addressable memory there,
but in 32-bit protected it does, as our emulator usually does not have all 4Gb.
And of course, 64-bit RAM is currently larger than the total RAM in the world.
`int 15` returns a list:
each time you call it a new memory region is returned.
The format is not too complicated, and documented at:
http://wiki.osdev.org/Detecting_Memory_%28x86%29#Detecting_Upper_Memory
- 8 bytes: base address of region.
- 8 bytes: length of region.
- 4 bytes: type or region. 1 for usable RAM.
- 4 bytes: some ACPI stuff that no one uses?
## Low memory
TODO what is it?
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
CLEAR CLEAR

View File

@@ -1,35 +1,3 @@
/*
# Bios disk load
# int 13h
Load one more sector from the disk
besides the first 512 bytes and do something with it.
Expected output: "a"
Grub 2.0 makes several calls to it under `grub-core/boot/i386/pc`
TODO: not working on:
- Bochs: `BOUND_GdMa: fails bounds test`.
- GRUB `chainloader` through big.img
Does work on QEMU and ThinkPad T400.
## int 13
BIOS call used for disk operations.
## Bibliography
- https://en.wikipedia.org/wiki/INT_13H
- http://wiki.osdev.org/ATA_in_x86_RealMode_%28BIOS%29
- https://thiscouldbebetter.wordpress.com/2011/03/15/creating-a-bootable-program-in-assembly-language/
- http://stackoverflow.com/questions/19381434/cannot-read-disk-sectors-in-assembly-language
- http://stackoverflow.com/questions/15497842/read-a-sector-from-hard-drive-with-int-13h
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
CLEAR CLEAR

View File

@@ -1,5 +1,6 @@
#include "common.h" #include "common.h"
BEGIN BEGIN
DBG
mov $msg, %si mov $msg, %si
mov $0x0e, %ah mov $0x0e, %ah
loop: loop:

View File

@@ -1,24 +1,3 @@
/*
# 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" #include "common.h"
.macro INITIAL_STORE x .macro INITIAL_STORE x

View File

@@ -1,10 +1,7 @@
/*
http://stackoverflow.com/questions/4113250/how-to-handle-keyboard-in-real-mode-through-bios-interrupts
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
mov $0x00, %ah mov $0x00, %ah
int $0x16 int $0x16
inc %al
PUTC <%al> PUTC <%al>
hlt hlt

View File

@@ -1,9 +1,3 @@
/*
This just begs for a non-minimal for-fun infinite loop version.
Do try Ctrl-key combinations.
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
start: start:

View File

@@ -1,14 +1,3 @@
/*
What happens when a newline is output with bios.
Outcome:
hello
world
Carriage returns are needed just like in old days.
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
PRINT_STRING $msg PRINT_STRING $msg

View File

@@ -1,41 +1,17 @@
/*
Set pixel at position (1, 1) to a clear red color (0Ch) in 13h video mode.
You have to look a bit hard to see it.
Mode 13h has: 320x200 Graphics, 256 colors, 1 page.
https://en.wikipedia.org/wiki/Mode_13h describes it a bit.
TODO Color encoding: is there any logic?
Shown on wiki page: https://en.wikipedia.org/wiki/Mode_13h
Does not seem to be R R R G G G B B mentioned at: https://en.wikipedia.org/wiki/8-bit_color
Asked at: http://stackoverflow.com/questions/14233437/convert-normal-256-color-to-mode-13h-version-color
Things get much more involved from protected mode:
http://stackoverflow.com/questions/14419088/how-to-draw-a-pixel-on-the-screen-in-protected-mode-in-x86-assembly
TODO do it!
And for hardware accelaration, you need to make real drivers
(to semi-documented complex GPU hardware :-) )
http://wiki.osdev.org/How_do_I_set_a_graphics_mode
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
/* Enter video mode 13h: */ /* Enter video mode 13h. */
mov $0x0013, %ax mov $0x0013, %ax
int $0x10 int $0x10
start: start:
/* /* Draw the pixel:
Draw the pixel pixel. *
* * AH = 0Ch
AH = 0Ch * * AL = Color
AL = Color * * BH = Page Number
BH = Page Number * * CX = x
CX = x * * DX = y
DX = y */
*/
mov $0x0C0C, %ax mov $0x0C0C, %ax
mov $0x01, %bh mov $0x01, %bh
mov $0x0001, %cx mov $0x0001, %cx

View File

@@ -1,7 +1,3 @@
/*
Draw a 45 degree line of pixels to the screen on 13h video mode.
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
mov $0x0013, %ax mov $0x0013, %ax

View File

@@ -1,9 +1,3 @@
/*
# BIOS putc
Print a single `@` character with the BIOS.
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
/* /*

View File

@@ -1,38 +1,3 @@
/*
BIOS has a scroll function!
Very convenient, otherwise that would be hard to implement.
How it works:
Before scroll:
a
b
c
d
We then choose to act on the rectangle with corners
(1, 1) and (2, 2)} given by cx and dx:
a
XX
XX
d
and scroll that rectangle down by one line (al).
The final outcome is:
a
c
NN
d
where `N` are new squares generated by the scroll,
which gets filled with the background color in bh.
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
@@ -41,23 +6,21 @@ PRINT_STRING $stair
/* Function ID. */ /* Function ID. */
mov $0x06, %ah mov $0x06, %ah
/* nr. of lines to scroll */ /* Number. of lines to scroll */
mov $0x01, %al mov $0x01, %al
/* /* BIOS color attributes.
BIOS color attributes. * Background is the clear color.
Background is the clear color. * Foreground is set as the new foreground color.
Foreground is set as the new foreground color. */
*/
mov $0xA4, %bh mov $0xA4, %bh
/* /*
CH,CL: row,column upper left corner (00:00) CH,CL: row,column upper left corner (00:00)
TODO what does that mean? TODO what does that mean?
*/ */
mov $0x0101, %cx mov $0x0101, %cx
/* /* DH,DL: row,column lower right corner (24:79).
DH,DL: row,column lower right corner (24:79). * TODO what does it mean?
TODO what does it mean? */
*/
mov $0x0202, %dx mov $0x0202, %dx
int $0x10 int $0x10

20
bios_sleep.S Normal file
View File

@@ -0,0 +1,20 @@
#include "common.h"
BEGIN
/* Must enable interrupts, since BIOS uses
* them to increment the timer.
*/
sti
mov $0, %dx
infinite:
mov $18, %cx
mov 0x046C, %bx
one_sec:
mov 0x046C, %ax
cmp %ax, %bx
je one_sec
mov %ax, %bx
loop one_sec
PRINT_WORD_HEX <%dx>
PRINT_NEWLINE
inc %dx
jmp infinite

View File

@@ -1,20 +0,0 @@
/*
# BIOS tick count
Method mentioned at: http://stackoverflow.com/a/9973442/895245
TODO Not working on QEMU 2.3.0: the value does not get updated!
But if I run qemu many times in a sequence, it does seem to get incremented...
*/
#include "common.h"
BEGIN
start:
mov 0x046C, %ax
#cmp %ax, %bx
#je start
PRINT_WORD_HEX
PRINT_NEWLINE
#mov %ax, %bx
jmp start

485
common.h
View File

@@ -1,44 +1,16 @@
/* /* I really want this for the local labels.
Using macros for now instead of functions because it simplifies the linker script. *
* The major downside is that every register passed as argument requires `<>`:
But the downsides are severe: * http://stackoverflow.com/questions/19776992/gas-altmacro-macro-with-a-percent-sign-in-a-default-parameter-fails-with-oper/
*/
- no symbols to help debugging
- impossible to step over method calls: you have to step into everything
- larger output, supposing I can get linker gc for unused functions working,
see --gc-section, which is for now uncertain.
If I can get this working, I'll definitely move to function calls.
The problem is that if I don't, every image will need a stage 2 loader.
That is not too serious though, it could be added to BEGIN.
It seems that ld can only remove sections, not individual symbols:
http://stackoverflow.com/questions/6687630/c-c-gcc-ld-remove-unused-symbols
With GCC we can use `-ffunction-sections -fdata-sections`
to quickly generate a ton of sections, but I don't thing GAS supports that...
## Conventions
Every "function-like macro" should maintain GP register state
(flags currently not maintained).
%sp cannot be used to pass most arguments.
We don't care about setting %bp.
*/
/*
I really want this for the local labels.
The major downside is that every register passed as argument requires `<>`:
http://stackoverflow.com/questions/19776992/gas-altmacro-macro-with-a-percent-sign-in-a-default-parameter-fails-with-oper/
*/
.altmacro .altmacro
/* Helpers */ /* Helpers */
.macro DBG
mov %ax, 0x9000
.endm
/* Push registers ax, bx, cx and dx. Lightweight `pusha`. */ /* Push registers ax, bx, cx and dx. Lightweight `pusha`. */
.macro PUSH_ADX .macro PUSH_ADX
push %ax push %ax
@@ -47,10 +19,9 @@ http://stackoverflow.com/questions/19776992/gas-altmacro-macro-with-a-percent-si
push %dx push %dx
.endm .endm
/* /* Pop registers dx, cx, bx, ax. Inverse order from PUSH_ADX,
Pop registers dx, cx, bx, ax. Inverse order from PUSH_ADX, * so this cancels that one.
so this cancels that one. */
*/
.macro POP_DAX .macro POP_DAX
pop %dx pop %dx
pop %cx pop %cx
@@ -72,11 +43,10 @@ so this cancels that one.
pop %eax pop %eax
.endm .endm
/* /* Convert the low nibble of a r8 reg to ASCII of 8-bit in-place.
Convert the low nibble of a r8 reg to ASCII of 8-bit in-place. * reg: r8 to be converted
reg: r8 to be converted * Output: stored in reg itself. Letters are uppercase.
Output: stored in reg itself. Letters are uppercase. */
*/
.macro HEX_NIBBLE reg .macro HEX_NIBBLE reg
LOCAL letter, end LOCAL letter, end
cmp $10, \reg cmp $10, \reg
@@ -89,12 +59,11 @@ letter:
end: end:
.endm .endm
/* /* Convert a byte to hex ASCII value.
Convert a byte to hex ASCII value. * c: r/m8 byte to be converted
c: r/m8 byte to be converted * Output: two ASCII characters, is stored in `ah:al`
Output: two ASCII characters, is stored in `ah:al` * http://stackoverflow.com/questions/3853730/printing-hexadecimal-digits-with-assembly
http://stackoverflow.com/questions/3853730/printing-hexadecimal-digits-with-assembly */
*/
.macro HEX c .macro HEX c
mov \c, %al mov \c, %al
mov \c, %ah mov \c, %ah
@@ -106,15 +75,12 @@ http://stackoverflow.com/questions/3853730/printing-hexadecimal-digits-with-asse
/* Structural. */ /* Structural. */
/* /* Setup a sane initial state.
Setup a sane initial state. *
* Should be the first thing in every file.
Should be the first thing in every file. *
* Discussion of what is needed exactly: http://stackoverflow.com/a/32509555/895245
Discussion of what is needed exactly: http://stackoverflow.com/a/32509555/895245 */
*/
.macro BEGIN .macro BEGIN
LOCAL after_locals LOCAL after_locals
.code16 .code16
@@ -129,10 +95,9 @@ Discussion of what is needed exactly: http://stackoverflow.com/a/32509555/895245
mov %ax, %es mov %ax, %es
mov %ax, %fs mov %ax, %fs
mov %ax, %gs mov %ax, %gs
/* /* TODO What to move into BP and SP?
TODO What to move into BP and SP? * http://stackoverflow.com/questions/10598802/which-value-should-be-used-for-sp-for-booting-process
http://stackoverflow.com/questions/10598802/which-value-should-be-used-for-sp-for-booting-process */
*/
mov %ax, %bp mov %ax, %bp
/* Automatically disables interrupts until the end of the next instruction. */ /* Automatically disables interrupts until the end of the next instruction. */
mov %ax, %ss mov %ax, %ss
@@ -145,16 +110,17 @@ Discussion of what is needed exactly: http://stackoverflow.com/a/32509555/895245
after_locals: after_locals:
.endm .endm
/* /* Load stage2 from disk to memory, and jump to it.
Load stage2 from disk to memory, and jump to it. *
* To be used when the program does not fit in the 512 bytes.
To be used when the program does not fit in the 512 bytes. *
* Sample usage:
Sample usage: *
* ....
STAGE2 * STAGE2
Stage 2 code here. * Stage 2 code here.
*/ * ....
*/
.macro STAGE2 .macro STAGE2
/* Defined in the linker script. */ /* Defined in the linker script. */
mov $__stage2_nsectors, %al mov $__stage2_nsectors, %al
@@ -169,11 +135,7 @@ Sample usage:
1: 1:
.endm .endm
/* /* Enter protected mode. Use the simplest GDT possible. */
Enter protected mode.
Use the simplest GDT possible.
*/
.macro PROTECTED_MODE .macro PROTECTED_MODE
/* Must come before they are used. */ /* Must come before they are used. */
.equ CODE_SEG, 8 .equ CODE_SEG, 8
@@ -182,29 +144,35 @@ Use the simplest GDT possible.
/* Tell the processor where our Global Descriptor Table is in memory. */ /* Tell the processor where our Global Descriptor Table is in memory. */
lgdt gdt_descriptor lgdt gdt_descriptor
/* /* Set PE (Protection Enable) bit in CR0 (Control Register 0),
Set PE (Protection Enable) bit in CR0 (Control Register 0), * effectively entering protected mode.
effectively entering protected mode. */
*/
mov %cr0, %eax mov %cr0, %eax
orl $0x1, %eax orl $0x1, %eax
mov %eax, %cr0 mov %eax, %cr0
ljmp $CODE_SEG, $protected_mode ljmp $CODE_SEG, $protected_mode
/* /* Our GDT contains:
Our GDT contains: *
- a null entry to fill the unusable entry 0: * * a null entry to fill the unusable entry 0:
http://stackoverflow.com/questions/33198282/why-have-the-first-segment-descriptor-of-the-global-descriptor-table-contain-onl * http://stackoverflow.com/questions/33198282/why-have-the-first-segment-descriptor-of-the-global-descriptor-table-contain-onl
- a code and data. Both are necessary, because: * * a code and data. Both are necessary, because:
- it is impossible to write to the code segment * +
- it is impossible execute the data segment * --
Both start at 0 and span the entire memory, * ** it is impossible to write to the code segment
allowing us to access anything without problems. * ** it is impossible execute the data segment
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 * Both start at 0 and span the entire memory,
as we'd have to reload it several times, losing cache. * 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.
*/
gdt_start: gdt_start:
gdt_null: gdt_null:
.long 0x0 .long 0x0
@@ -231,30 +199,27 @@ vga_current_line:
.long 0 .long 0
.code32 .code32
protected_mode: protected_mode:
/* /* Setup the other segments.
Setup the other segments. * Those movs are mandatory because they update the descriptor cache:
Those movs are mandatory because they update the descriptor cache: * http://wiki.osdev.org/Descriptor_Cache
http://wiki.osdev.org/Descriptor_Cache */
*/
mov $DATA_SEG, %ax mov $DATA_SEG, %ax
mov %ax, %ds mov %ax, %ds
mov %ax, %es mov %ax, %es
mov %ax, %fs mov %ax, %fs
mov %ax, %gs mov %ax, %gs
mov %ax, %ss mov %ax, %ss
/* /* TODO detect the last memory address available properly.
TODO detect the last memory address available properly. * It depends on how much RAM we have.
It depends on how much RAM we have. */
*/
mov $0X7000, %ebp mov $0X7000, %ebp
mov %ebp, %esp mov %ebp, %esp
.endm .endm
/* /* Setup a single page directory, which give us 2^10 * 2^12 == 4MiB
Setup a single page directory, which give us 2^10 * 2^12 == 4MiB * of identity memory starting at address 0.
of identity memory starting at address 0. * The currently executing code is inside that range, or else we'd jump somewhere and die.
The currently executing code is inside that range, or else we'd jump somewhere and die. */
*/
.equ page_directory, __end_align_4k .equ page_directory, __end_align_4k
.equ page_table, __end_align_4k + 0x1000 .equ page_table, __end_align_4k + 0x1000
.macro SETUP_PAGING_4M .macro SETUP_PAGING_4M
@@ -280,12 +245,11 @@ page_setup_start:
/* Top 20 address bits. */ /* Top 20 address bits. */
mov %eax, %edx mov %eax, %edx
shl $12, %edx shl $12, %edx
/* /* Set flag bits 0-7. We only set to 1:
Set flag bits 0-7. We only set to 1: * * bit 0: Page present
- bit 0: Page present * * bit 1: Page is writable.
- bit 1: Page is writable. * Might work without this as the permission also depends on CR0.WP.
Might work without this as the permission also depends on CR0.WP. */
*/
mov $0b00000011, %dl mov $0b00000011, %dl
/* Zero flag bits 8-11 */ /* Zero flag bits 8-11 */
and $0xF0, %dh and $0xF0, %dh
@@ -297,24 +261,23 @@ page_setup_end:
POP_EDAX POP_EDAX
.endm .endm
/* /* * Turn paging on.
Turn paging on. * Registers are not saved because memory will be all messed up.
Registers are not saved because memory will be all messed up. *
* ## cr3
## 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,
- 20 top bits: 4KiB address. Since those are the only address bits, * this implies that the page directory must be aligned to 4Kib.
this implies that the page directory must be aligned to 4Kib. * * bits 3 and 4: TODO some function I don't understand yet
- bits 3 and 4: TODO some function I don't understand yet * * all others: ignored
- all others: ignored *
* Many tutorials simply ignore bits 3 and 4, and do a direct address mov to `cr3`.
Many tutorials simply ignore bits 3 and 4, and do a direct address mov to `cr3`. *
* This sets the 20 top address bits to their correct value, and puts trash in bits 3 and 4,
This sets the 20 top address bits to their correct value, and puts trash in bits 3 and 4, * but it generally works.
but it generally works. */
*/
.macro PAGING_ON .macro PAGING_ON
/* Tell the CPU where the page directory is. */ /* Tell the CPU where the page directory is. */
mov $page_directory, %eax mov $page_directory, %eax
@@ -348,27 +311,25 @@ idt_descriptor:
.endm .endm
.macro IDT_ENTRY .macro IDT_ENTRY
/* /* Low handler address.
Low handler address. * It is impossible to write:
It is impossible to write: * .word (handler & 0x0000FFFF)
.word (handler & 0x0000FFFF) * as we would like:
as we would like: * http://stackoverflow.com/questions/18495765/invalid-operands-for-binary-and
http://stackoverflow.com/questions/18495765/invalid-operands-for-binary-and * because this address has to be split up into two.
because this address has to be split up into two. * So this must be done at runtime.
So this must be done at runtime. * Why this design choice from Intel?! Weird.
Why this design choice from Intel?! Weird. */
*/
.word 0 .word 0
/* Segment selector: byte offset into the GDT. */ /* Segment selector: byte offset into the GDT. */
.word CODE_SEG .word CODE_SEG
/* Reserved 0. */ /* Reserved 0. */
.byte 0 .byte 0
/* /* Flags. Format:
Flags. Format: * 1 bit: present. If 0 and this happens, triple fault.
- 1 bit: present. If 0 and this happens, triple fault. * 2 bits: ring level we will be called from.
- 2 bits: ring level we will be called from. * 5 bits: fixed to 0xE.
- 5 bits: fixed to 0xE. */
*/
.byte 0x8E .byte 0x8E
/* High word of base. */ /* High word of base. */
.word 0 .word 0
@@ -379,10 +340,9 @@ idt_descriptor:
.skip n * 8 .skip n * 8
.endm .endm
/* /* * index: r/m/imm32 Index of the entry to setup.
- index: r/m/imm32 Index of the entry to setup. * * handler: r/m/imm32 Address of the handler function.
- handler: r/m/imm32 Address of the handler function. */
*/
.macro IDT_SETUP_ENTRY index, handler .macro IDT_SETUP_ENTRY index, handler
push %eax push %eax
push %edx push %edx
@@ -399,10 +359,9 @@ idt_descriptor:
.macro ISR_NOERRCODE i .macro ISR_NOERRCODE i
isr\()\i: isr\()\i:
cli cli
/* /* Push a dummy 0 for interrupts that don't push any code.
Push a dummy 0 for interrupts that don't push any code. * http://stackoverflow.com/questions/10581224/why-does-iret-from-a-page-fault-handler-generate-interrupt-13-general-protectio/33398064#33398064
http://stackoverflow.com/questions/10581224/why-does-iret-from-a-page-fault-handler-generate-interrupt-13-general-protectio/33398064#33398064 */
*/
push $0 push $0
push $\i push $\i
jmp interrupt_handler_stub jmp interrupt_handler_stub
@@ -415,18 +374,15 @@ idt_descriptor:
jmp interrupt_handler_stub jmp interrupt_handler_stub
.endm .endm
/* /* Protected mode PIT number after remapping it. */
Protected mode PIT number after remapping it.
*/
#define PIT_ISR_NUMBER $0x20 #define PIT_ISR_NUMBER $0x20
/* /* Entries and handlers.
Entries and handlers. * 48 = 32 processor built-ins + 16 PIC interrupts.
48 = 32 processor built-ins + 16 PIC interrupts. * In addition to including this, you should also call
In addition to including this, you should also call * * call IDT_SETUP_48_ISRS to setup the handler addreses.
- call IDT_SETUP_48_ISRS to setup the handler addreses. * * define an `interrupt_handler(uint32 number, uint32 error)` function
- define an `interrupt_handler(uint32 number, uint32 error)` function */
*/
.macro IDT_48_ENTRIES .macro IDT_48_ENTRIES
/* IDT. */ /* IDT. */
IDT_START IDT_START
@@ -501,17 +457,18 @@ In addition to including this, you should also call
POP_DAX POP_DAX
.endm .endm
/* /* Print a 8 bit ASCII value at current cursor position.
Print a 8 bit ASCII value at current cursor position. *
* * `c`: r/m/imm8 ASCII value to be printed.
- c r/m/imm8 ASCII value to be printed. *
* Usage:
Usage: *
* ....
PUTC $'a * PUTC $'a
* ....
prints `'a'` to the screen. *
*/ * prints `a` to the screen.
*/
.macro PUTC c=$0x20 .macro PUTC c=$0x20
push %ax push %ax
mov \c, %al mov \c, %al
@@ -520,11 +477,10 @@ prints `'a'` to the screen.
pop %ax pop %ax
.endm .endm
/* /* Print a byte as two hexadecimal digits.
Print a byte as two hexadecimal digits. *
* * reg: 1 byte register.
- reg: 1 byte register. */
*/
.macro PRINT_HEX reg=<%al> .macro PRINT_HEX reg=<%al>
push %ax push %ax
HEX <\reg> HEX <\reg>
@@ -533,11 +489,10 @@ Print a byte as two hexadecimal digits.
pop %ax pop %ax
.endm .endm
/* /* Print a 16-bit number
Print a 16-bit number *
* * in: r/m/imm16
- in: r/m/imm16 */
*/
.macro PRINT_WORD_HEX in=<%ax> .macro PRINT_WORD_HEX in=<%ax>
push %ax push %ax
mov \in, %ax mov \in, %ax
@@ -551,16 +506,17 @@ Print a 16-bit number
PUTC $'\r PUTC $'\r
.endm .endm
/* /* Print a null terminated string.
Print a null terminated string. *
* Use as:
Use as: *
* ....
PRINT_STRING $s * PRINT_STRING $s
hlt * hlt
s: * s:
.asciz "string" * .asciz "string"
*/ * ....
*/
.macro PRINT_STRING s .macro PRINT_STRING s
LOCAL end, loop LOCAL end, loop
mov s, %si mov s, %si
@@ -575,12 +531,11 @@ loop:
end: end:
.endm .endm
/* /* Dump memory:
Dump memory. *
* * s: starting address
- s: starting address * * n: number of bytes to dump
- n: number of bytes to dump */
*/
.macro PRINT_BYTES s, n=$16 .macro PRINT_BYTES s, n=$16
LOCAL end, loop, no_newline LOCAL end, loop, no_newline
PUSH_ADX PUSH_ADX
@@ -614,16 +569,15 @@ end:
/* VGA */ /* VGA */
/* /* Print a NULL terminated string to position 0 in 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.
s: 32-bit register or memory containing the address of the string to print. *
* Clobbers: none.
Clobbers: none. *
* Uses and updates vga_current_line to decide the current line.
Uses and updates vga_current_line to decide the current line. * Loops around the to the top.
Loops around the to the top. */
*/
.macro VGA_PRINT_STRING s .macro VGA_PRINT_STRING s
LOCAL loop, end LOCAL loop, end
PUSH_EADX PUSH_EADX
@@ -654,18 +608,21 @@ end:
POP_EDAX POP_EDAX
.endm .endm
/* /* Print a 32-bit r/m/immm in hex.
Print a 32-bit r/m/immm in hex. *
* Sample usage:
Sample usage: *
* ....
mov $12345678, %eax * mov $12345678, %eax
VGA_PRINT_HEX_4 <%eax> * VGA_PRINT_HEX_4 <%eax>
* ....
Expected output on screen: *
* Expected output on screen:
12345678 *
*/ * ....
* 12345678
* ....
*/
.macro VGA_PRINT_HEX_4 in=<%eax> .macro VGA_PRINT_HEX_4 in=<%eax>
LOCAL loop LOCAL loop
PUSH_EADX PUSH_EADX
@@ -696,14 +653,13 @@ loop:
POP_EDAX POP_EDAX
.endm .endm
/* /* Dump memory.
Dump memory. *
* * s: starting address
- s: starting address * * n: number of bytes to dump
- n: number of bytes to dump *
* TODO implement. This is just a stub.
TODO implement */
*/
.macro VGA_PRINT_BYTES s, n=$16 .macro VGA_PRINT_BYTES s, n=$16
LOCAL end, loop, no_newline LOCAL end, loop, no_newline
PUSH_ADX PUSH_ADX
@@ -762,10 +718,9 @@ end:
.endm .endm
.macro REMAP_PIC_32 .macro REMAP_PIC_32
/* /* Remap the PIC interrupts to start at 32.
Remap the PIC interrupts to start at 32. * TODO understand.
TODO understand. */
*/
OUTB $0x11, PORT_PIC_MASTER_CMD OUTB $0x11, PORT_PIC_MASTER_CMD
OUTB $0x11, PORT_PIC_SLAVE_CMD OUTB $0x11, PORT_PIC_SLAVE_CMD
OUTB $0x20, PORT_PIC_MASTER_DATA OUTB $0x20, PORT_PIC_MASTER_DATA
@@ -782,11 +737,10 @@ end:
#define PIT_FREQ 0x1234DD #define PIT_FREQ 0x1234DD
/* /* Set the minimum possible PIT frequency = 0x1234DD / 0xFFFF =~ 18.2 Hz
Set the minimum possible PIT frequency = 0x1234DD / 0xFFFF =~ 18.2 Hz * This is a human friendly frequency: you can see individual events,
This is a human friendly frequency: you can see individual events, * but you don't have to wait much for each one.
but you don't have to wait much for each one. */
*/
.macro PIT_SET_MIN_FREQ .macro PIT_SET_MIN_FREQ
push %eax push %eax
mov $0xFF, %al mov $0xFF, %al
@@ -795,12 +749,11 @@ but you don't have to wait much for each one.
pop %eax pop %eax
.endm .endm
/* /* We have to split the 2 ax bytes,
We have to split the 2 ax bytes, * as we can only communicate one byte at a time here.
as we can only communicate one byte at a time here. * - freq: 16 bit compile time constant desired frequency.
- freq: 16 bit compile time constant desired frequency. * Range: 19 - 0x1234DD.
Range: 19 - 0x1234DD. */
*/
.macro PIT_SET_FREQ freq .macro PIT_SET_FREQ freq
push %eax push %eax
mov $(PIT_FREQ / \freq), %ax mov $(PIT_FREQ / \freq), %ax
@@ -810,11 +763,10 @@ as we can only communicate one byte at a time here.
pop %eax pop %eax
.endm .endm
/* /* Sleep for `ticks` ticks of the PIT at current frequency.
Sleep for `ticks` ticks of the PIT at current frequency. * PIT_SLEEP_HANDLER_UPDATE must be placed in the PIT handler for this to work.
PIT_SLEEP_HANDLER_UPDATE must be placed in the PIT handler for this to work. * Currently only one can be used at a given time.
Currently only one can be used at a given time. */
*/
.macro PIT_SLEEP_TICKS ticks .macro PIT_SLEEP_TICKS ticks
LOCAL loop LOCAL loop
movb $1, pit_sleep_ticks_locked movb $1, pit_sleep_ticks_locked
@@ -825,9 +777,7 @@ loop:
jne loop jne loop
.endm .endm
/* /* Must be placed in the PIT handler for PIT_SLEEP_TICKS to work. */
Must be placed in the PIT handler for PIT_SLEEP_TICKS to work.
*/
.macro PIT_SLEEP_TICKS_HANDLER_UPDATE .macro PIT_SLEEP_TICKS_HANDLER_UPDATE
LOCAL dont_unlock LOCAL dont_unlock
decl pit_sleep_ticks_count decl pit_sleep_ticks_count
@@ -844,14 +794,13 @@ pit_sleep_ticks_locked:
.byte 0 .byte 0
.endm .endm
/* /* Define the properties of the wave:
Define the properties of the wave: *
* * Channel: 0
- Channel: 0 * * access mode: lobyte/hibyte
- access mode: lobyte/hibyte * * operating mode: rate generator
- operating mode: rate generator * * BCD/binary: binary
- BCD/binary: binary */
*/
.macro PIT_GENERATE_FREQUENCY .macro PIT_GENERATE_FREQUENCY
OUTB $0b00110100, PORT_PIT_MODE OUTB $0b00110100, PORT_PIT_MODE
.endm .endm

12
cpu.md
View File

@@ -1,12 +0,0 @@
# CPU
This section contains concepts that depend only on the CPU, but that cannot be tested on userland because they'd require ring 0.
Examples:
- real mode concepts. Userland cannot switch to real mode.
- segment registers.
This section does not include concepts that depend on hardware other than the CPU itself, e.g. BIOS.
Concepts that *can* be tested from userland will be tested at: <https://github.com/cirosantilli/assembly-cheat>

22
cs.S
View File

@@ -1,31 +1,25 @@
/*
# CS segment register
# ljmp
Expected outcome: "0102" get printed. Those are the 2 that CS takes in this example.
Explanation: http://stackoverflow.com/a/33177253/895245
TODO is ljmp encodable except with a constant:
- http://stackoverflow.com/questions/1685654/ljmp-syntax-in-gcc-inline-assembly
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
CLEAR CLEAR
mov %cs, %ax
PRINT_HEX <%al>
PRINT_NEWLINE
/* CS = 1 */
ljmp $1, $1f ljmp $1, $1f
1: 1:
.skip 0x10 .skip 0x10
mov %cs, %ax mov %cs, %ax
PRINT_HEX <%al> PRINT_HEX <%al>
PRINT_NEWLINE
/* CS = 2 */
ljmp $2, $1f ljmp $2, $1f
1: 1:
.skip 0x20 .skip 0x20
mov %cs, %ax mov %cs, %ax
PRINT_HEX <%al> PRINT_HEX <%al>
PRINT_NEWLINE
hlt hlt

View File

@@ -1,17 +0,0 @@
# Debug
How to debug with QEMU + GDB?
This will only cover specifics, you have to know GDB debugging already.
First read the `make debug` target to get started. Those points will not be repeated here.
How to have debug symbols: <http://stackoverflow.com/a/32960272/895245> TODO implement here. Needs to point GDB to an ELF file in addition to the remote listen.
How to step over `int` calls: <http://stackoverflow.com/questions/24491516/how-to-step-over-interrupt-calls-when-debugging-a-bootloader-bios-with-gdb-and-q>
Single stepping until a given opcode can be helpful sometimes: <http://stackoverflow.com/a/31249378/895245>
TODO: detect if we are on 16 or 32 bit automatically from control registers. Now I'm using 2 functions `16` and `32` to switch manually, but that sucks. The problem is that it's not possible to read them directly: <http://stackoverflow.com/a/31340294/895245> If we had `cr0`, it would be easy to do with an `if cr0 & 1` inside a hook-stop.
TODO: Take segmentation offsets into account: <http://stackoverflow.com/questions/10354063/how-to-use-a-logical-address-in-gdb>

View File

@@ -1,19 +0,0 @@
# Formats
When we create a regular Linux program, we generate an ELF file, which is read by the OS.
Without an OS, we can use the following formats:
- boot sector, of which MBR is an important type.
- El Torito for CDs: <https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29>
- multiboot
- hybrid boot sector / El Torito. It is possible to generate images that can be burnt either to USBs or optic disks.
The Linux kernel 4.2 for example does that by default upon `make isoimage`.
- PXE: <https://en.wikipedia.org/wiki/Preboot_Execution_Environment> and its implementation <https://en.wikipedia.org/wiki/IPXE>
Boot from the network. TODO how does it work exactly? I suppose there is a server, and then the BIOS can download the boot sector from it.

12
gdb.gdb
View File

@@ -15,10 +15,14 @@ break *0x7c00
continue continue
# Magic address. Add a: # Magic address. Add a:
# mov %ax, 0x1234 #
# to your program to break there. # mov %ax, 0x9000
#
# to your program to break there. Shortcut macro on common.h:
#
# DBG
awatch *0x9000 awatch *0x9000
commands commands
silent silent
printf "0x9000 awatch access debug break\n" printf "0x9000 awatch access debug break\n"
end end

View File

@@ -1,91 +0,0 @@
# Getting started
Ubuntu:
./configure
Make all operating systems:
make
Each `.S` file on the top-level is an operating system!
## Emulator
Run the default OS on QEMU:
make run
Run a given OS:
make run RUN=min
make run RUN=bios_one_char
Use Bochs instead of QEMU:
make bochs RUN=min
## Real hardware
Insert an USB, determine its device (`/dev/sdX`) with:
sudo lsblk
sudo fdisk -l
Pick the `.img` file that you wan to run and:
sudo dd if=bios_hello_world.img of=/dev/sdX
Then:
- insert the USB in a computer
- during boot, hit some special hardware dependant key, usually F12, Esc
- choose to boot from the USB
When you are done, just hit the power button to shutdown.
Tested on: ThinkPad T400.
### Big image
Create a `big.img` that contains all examples that can be booted from GRUB:
make big.img
Now if you do:
sudo dd if=big.img of=/dev/sdX
you can test several examples with a single USB burn, which is much faster.
You can also try out the big image on QEMU for fun with:
qemu-system-i386 -hda big.img
You will also want to change the boot order to put the USB first from the F12 BIOS menu. This way you don't have to hit F12 like a madman every time.
TODO: boot sectors that load STAGE2 are not working with the big image chainloader. TODO why?
## Docker
If you don't have an Ubuntu box, this is an easy alternative:
sudo docker run -it --net=host ubuntu:14.04 bash
Then proceed normally in the guest: install packages, and build:
apt-get update
apt-get install git
git clone https://github.com/cirosantilli/x86-bare-metal-examples
cd x86-bare-metal-examples
./configure
make
To overcome the lack of GUI, we can use QEMU's VNC implementation instead of the default SDL, which is visible on the host due to `--net=host`:
qemu-system-i386 -hda main.img -vnc :0
and then on host:
sudo apt-get install vinagre
vinagre localhost:5900

335
grub/README.adoc Normal file
View File

@@ -0,0 +1,335 @@
= GRUB
:idprefix:
:idseparator: -
:sectanchors:
:sectlinks:
:sectnumlevels: 6
:sectnums:
:toc: macro
:toclevels: 6
:toc-title:
toc::[]
== Symlinks
This directory relies on the following symlinks to make directory structure modifiable in the future:
. mbrs: directory that contains the Makefile for `bios_hello_world.img` and other MBRs
. bios_hello_world.img.sym: boot sector that says hello world with BIOS
+
The `.sym` extension must be used because otherwise this symlink would be gitignored.
=== Introduction
If you have a Linux dual boot, and you see a menu prompting you to choose the OS, there is a good chance that this is GRUB, since it is the most popular bootloader today.
It allows you basic graphical interaction even before starting any OS.
Everything is configurable, from the menu entries to the background image. This is why Ubuntu's GRUB is purple.
The main job for GRUB userspace utilities such as `grub-install` and `update-grub` is to look at the input configuration files, interpret them and write the output configuration information to the correct locations on the hard disk so that they can be found at boot time.
GRUB has knowledge about filesystems, and is able to read configuration files and the disk image from it.
=== GRUB versions
GRUB has 2 versions
* 0.97, usually known just as GRUB, or Legacy GRUB.
* GRUB >= 2, which is backwards incompatible, and has more features.
+
GRUB 2 is still beta.
Some distros like Ubuntu have already adopted GRUB 2, while others are still using GRUB for stability concerns.
Determine your GRUB version with:
....
grub-install -v
....
Here we discuss GRUB 2.
=== Supported architectures
x86 is of course the primary... ARM was recently added in 2.0.2 it seems: https://wiki.linaro.org/LEG/Engineering/Kernel/GRUB
=== Configuration files
Input files:
* `/etc/grub.d/*`
* `/etc/default/grub`
Generated files and data after `sudo update-grub`:
* `/boot/grub/grub.cfg`
* MBR bootstrap code
==== /etc/default/grub
Shell script sourced by `grub-mkconfig`.
Can defined some variables which configure grub, but is otherwise an arbitrary shell script:
....
sudo vim /etc/default/grub
....
* `GRUB_DEFAULT`: default OS choice if cursor is not moved:
+
Starts from 0, the order is the same as shown at grub OS choice menu:
+
....
GRUB_DEFAULT=0
....
+
The order can be found on the generated `/boot/grub/grub.cfg`: you have to count the number of `menuentry` calls.
+
To select sub-menus, which are created with the `submenu` call on `/boot/grub/grub.cfg`, use:
+
....
GRUB_DEFAULT='0>1'
....
+
You can also use OS name instead of a number, e.g.:
+
....
GRUB_DEFAULT='Ubuntu'
....
+
For a line from `/boot/grub/grub.cfg` of type:
+
....
menuentry 'Ubuntu'
....
* `GRUB_TIMEOUT` : time before auto OS choice in seconds
* `GRUB_CMDLINE_LINUX_DEFAULT`: space separated list of Kernel boot parameters.
+
Sample:
+
....
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
....
+
The parameters will not be discussed here.
+
Those parameters can also be edited from the boot menu for a single session by selecting the partition and clicking `e`.
** useless options on by default on Ubuntu 12.04 which you should really remove because they hide kernel state and potentially useful debug information:
*** `quiet`: suppress kernel messages.
*** `splash`: shows nice and useless image while the kernel is booting. On by default on Ubuntu 12.04. Remove this useless option,
==== /etc/grub.d/
Contains executables.
Each one is called in alphabetical order, and its stdout is used by GRUB.
A common choice for custom scripts in Ubuntu 14.04 is `40_custom`.
Create a menu entry:
....
#!/bin/sh -e
echo "stdout"
echo "stderr" >&2
cat << EOF
menuentry "menuentry title" {
set root=(hd0,1)
-- boot parameters --
}
EOF
....
You will see `stdout` when running `update-grub`. stderr is ignored.
`set root=(hd0,1)` specifies the partition, here `sda1`. `hd0` means first device, `1` means first partition. Yes, one if 0 based, and the other is 1 based.
`-- boot parameters --` depends on your OS.
Linux example:
....
linux /boot/vmlinuz
initrd /boot/initrd.img
....
Windows example:
....
chainloader (hdX,Y)+1
....
It is common to add one OS menu entry per file so that it is easy to change their order (just change alphabetical order).
=== Configuration scripts
==== update-grub
Just calls:
....
grub-mkconfig -o /boot/grub/grub.cfg
....
==== grub-mkconfig
Called by `update-grub` as:
....
grub-mkconfig -o /boot/grub/grub.cfg
....
Important actions:
* sources `/etc/default/grub`
* sources `/etc/default/grub.d/*.cfg`, which may override options in `/etc/default/grub`
* runs scripts under `/etc/grub.d`, which use the variables defined in the above sourced files
==== grub-install
Given a `/boot/grub/grub.cfg` in some filesystem, install GRUB to some hard disk.
Interpret input configuration files and update the MBR on the given disk:
....
sudo grub-install /dev/sda
....
If for example you install a new Linux distro, and you want to restore your old distro's GRUB configuration, you must log into the old distro and do `grub-install`, therefore telling your system via the MBR to use the installation parameters given on the old distro.
TODO get a minimal example working using a minimal kernel from: https://github.com/cirosantilli/x86-bare-metal-examples:
....
img="a.img"
dd if=/dev/zero of="$img" bs=1024 count=64
loop="$(sudo losetup -f --show "$img")"
printf 'o\nn\np\n1\n\n\nw\n' | sudo fdisk "$loop"
sudo kpartx -av "$img"
ls /dev/mapper
echo y | mke2fs -t ext4
sudo mount "/dev/mapper/${loop}p1" d
# Need a new Ubuntu.
#sudo losetup --show -f -P test.img
sudo grub-install /dev/loop0
mkdir -p d
mount /dev/loop0 d
#grub-install --boot-directory=d /dev/sdb
....
==== grub-mkrescue
Generates a rescue image from a root filesystem.
Example: https://github.com/cirosantilli/x86-bare-metal-examples/blob/48614b45fa6edeb97adbaad942595a4c25216113/multiboot/hello-world/Makefile#L6
You can then burn the output to an USB or CD
Vs `grub-install`: generates a live boot USB / CD, but does not use the USB as a filesystem.
Easier to setup however.
==== os_prober
Looks for several OS and adds them automatically to GRUB menu.
Recognizes Linux and Windows.
TODO how to use it
=== rescue prompt
If things fail really badly, you may be put on a `rescue >` prompt.
You are likely better off reinstalling things correctly in practice. But here go a few commands you can use from there.
https://www.linux.com/learn/tutorials/776643-how-to-rescue-a-non-booting-grub-2-on-linux/
* `ls`
* `ls (hd0,1)/`
* `cat (hd0,1)/etc/issue`
* Boot:
+
....
set root=(hd0,1)
linux /boot/vmlinuz-3.13.0-29-generic root=/dev/sda1
initrd /boot/initrd.img-3.13.0-29-generic
boot
....
==== timeout
No timeout on boot menu:
....
set timeout=0
....
==== default
Default no Nth (zero based) entry of boot menu:
....
set default="0"
....
==== menuentry
The following commands can be used inside a menu entry, e.g.:
....
menuentry "main" {
}
....
Point to a multiboot file:
....
multiboot /boot/main.elf
....
E.g.: https://github.com/cirosantilli/x86-bare-metal-examples/blob/48614b45fa6edeb97adbaad942595a4c25216113/multiboot/hello-world/iso/boot/grub/grub.cfg
Load a linux kernel with a given root filesystem:
....
linux /boot/bzImage
initrd /boot/rootfs.cpio.gz
....
You can pass kernel command line arguments with:
....
linux /boot/bzImage BOOT_IMAGE=/boot/vmlinuz-3.19.0-28-generic root=UUID=2a49bac4-b9dd-466d-9c0c-c432aa4ca086 ro loop.max_part=15
....
You can then check that they've appeared under `cat /proc/cmdline`.
=== Alternatives
* `syslinux`: Linux specific. Used by default by the kernel, e.g. on 4.2 `make isoimage`.
* LILO: old popular bootloader, largely replaced by GRUB now.
=== Legacy
Documentation: http://www.gnu.org/software/grub/manual/legacy/grub.html
==== kernel
Directive used to boot _both_ multiboot and Linux.
Got split up more or less into `multiboot` and `linux` directives.
=== Bibliography
* https://www.gnu.org/software/grub/grub-documentation.html
* http://www.dedoimedo.com/computers/grub-2.html
+
Great configuration tutorial.

View File

@@ -1,305 +0,0 @@
# GRUB
cd chainloader
make run
# Symlinks
This directory relies on the following symlinks to make directory structure modifiable in the future:
1. [mbrs](mbrs): directory that contains the Makefile for `bios_hello_world.img` and other MBRs
1. [bios_hello_world.img.sym](bios_hello_world.img.sym): boot sector that says hello world with BIOS
The `.sym` extension must be used because otherwise this symlink would be gitignored.
## Introduction
If you have a Linux dual boot, and you see a menu prompting you to choose the OS, there is a good chance that this is GRUB, since it is the most popular bootloader today.
It allows you basic graphical interaction even before starting any OS.
Everything is configurable, from the menu entries to the background image. This is why Ubuntu's GRUB is purple.
The main job for GRUB userspace utilities such as `grub-install` and `update-grub` is to look at the input configuration files, interpret them and write the output configuration information to the correct locations on the hard disk so that they can be found at boot time.
GRUB has knowledge about filesystems, and is able to read configuration files and the disk image from it.
## GRUB versions
GRUB has 2 versions
- 0.97, usually known just as GRUB, or Legacy GRUB.
- GRUB >= 2, which is backwards incompatible, and has more features.
GRUB 2 is still beta.
Some distros like Ubuntu have already adopted GRUB 2, while others are still using GRUB for stability concerns.
Determine your GRUB version with:
grub-install -v
Here we discuss GRUB 2.
## Supported architectures
x86 is of course the primary... ARM was recently added in 2.0.2 it seems: <https://wiki.linaro.org/LEG/Engineering/Kernel/GRUB>
## Configuration files
Input files:
- `/etc/grub.d/*`
- `/etc/default/grub`
Generated files and data after `sudo update-grub`:
- `/boot/grub/grub.cfg`
- MBR bootstrap code
### /etc/default/grub
Shell script sourced by `grub-mkconfig`.
Can defined some variables which configure grub, but is otherwise an arbitrary shell script:
sudo vim /etc/default/grub
- `GRUB_DEFAULT`: default OS choice if cursor is not moved:
Starts from 0, the order is the same as shown at grub OS choice menu:
GRUB_DEFAULT=0
The order can be found on the generated `/boot/grub/grub.cfg`: you have to count the number of `menuentry` calls.
To select sub-menus, which are created with the `submenu` call on `/boot/grub/grub.cfg`, use:
GRUB_DEFAULT='0>1'
You can also use OS name instead of a number, e.g.:
GRUB_DEFAULT='Ubuntu'
For a line from `/boot/grub/grub.cfg` of type:
menuentry 'Ubuntu'
- `GRUB_TIMEOUT` : time before auto OS choice in seconds
- `GRUB_CMDLINE_LINUX_DEFAULT`: space separated list of Kernel boot parameters.
Sample:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
The parameters will not be discussed here.
Those parameters can also be edited from the boot menu for a single session by selecting the partition and clicking `e`.
- useless options on by default on Ubuntu 12.04 which you should really remove because they hide kernel state and potentially useful debug information:
- `quiet`: suppress kernel messages.
- `splash`: shows nice and useless image while the kernel is booting. On by default on Ubuntu 12.04. Remove this useless option,
### /etc/grub.d/
Contains executables.
Each one is called in alphabetical order, and its stdout is used by GRUB.
A common choice for custom scripts in Ubuntu 14.04 is `40_custom`.
Create a menu entry:
#!/bin/sh -e
echo "stdout"
echo "stderr" >&2
cat << EOF
menuentry "menuentry title" {
set root=(hd0,1)
-- boot parameters --
}
EOF
You will see `stdout` when running `update-grub`. stderr is ignored.
`set root=(hd0,1)` specifies the partition, here `sda1`. `hd0` means first device,
`1` means first partition. Yes, one if 0 based, and the other is 1 based.
`-- boot parameters --` depends on your OS.
Linux example:
linux /boot/vmlinuz
initrd /boot/initrd.img
Windows example:
chainloader (hdX,Y)+1
It is common to add one OS menu entry per file so that it is easy to change their order (just change alphabetical order).
## Configuration scripts
### update-grub
Just calls:
grub-mkconfig -o /boot/grub/grub.cfg
### grub-mkconfig
Called by `update-grub` as:
grub-mkconfig -o /boot/grub/grub.cfg
Important actions:
- sources `/etc/default/grub`
- sources `/etc/default/grub.d/*.cfg`, which may override options in `/etc/default/grub`
- runs scripts under `/etc/grub.d`, which use the variables defined in the above sourced files
### grub-install
Given a `/boot/grub/grub.cfg` in some filesystem, install GRUB to some hard disk.
Interpret input configuration files and update the MBR on the given disk:
sudo grub-install /dev/sda
If for example you install a new Linux distro, and you want to restore your old distro's GRUB configuration, you must log into the old distro and do `grub-install`, therefore telling your system via the MBR to use the installation parameters given on the old distro.
TODO get a minimal example working using a minimal kernel from: <https://github.com/cirosantilli/x86-bare-metal-examples>:
img="a.img"
dd if=/dev/zero of="$img" bs=1024 count=64
loop="$(sudo losetup -f --show "$img")"
printf 'o\nn\np\n1\n\n\nw\n' | sudo fdisk "$loop"
sudo kpartx -av "$img"
ls /dev/mapper
echo y | mke2fs -t ext4
sudo mount "/dev/mapper/${loop}p1" d
# Need a new Ubuntu.
#sudo losetup --show -f -P test.img
sudo grub-install /dev/loop0
mkdir -p d
mount /dev/loop0 d
#grub-install --boot-directory=d /dev/sdb
### grub-mkrescue
Generates a rescue image from a root filesystem.
Example: <https://github.com/cirosantilli/x86-bare-metal-examples/blob/48614b45fa6edeb97adbaad942595a4c25216113/multiboot/hello-world/Makefile#L6>
You can then burn the output to an USB or CD
Vs `grub-install`: generates a live boot USB / CD, but does not use the USB as a filesystem.
Easier to setup however.
### os_prober
Looks for several OS and adds them automatically to GRUB menu.
Recognizes Linux and Windows.
TODO how to use it
## rescue prompt
If things fail really badly, you may be put on a `rescue >` prompt.
You are likely better off reinstalling things correctly in practice. But here go a few commands you can use from there.
<https://www.linux.com/learn/tutorials/776643-how-to-rescue-a-non-booting-grub-2-on-linux/>
- `ls`
- `ls (hd0,1)/`
- `cat (hd0,1)/etc/issue`
- Boot:
set root=(hd0,1)
linux /boot/vmlinuz-3.13.0-29-generic root=/dev/sda1
initrd /boot/initrd.img-3.13.0-29-generic
boot
## grub.cfg
TODO:
- where is the format documented?
- what is set? No relation to the Bash version: <http://unix.stackexchange.com/questions/197578/linux-set-command-for-local-variables>
Stuff I've deduced for 2.0:
### timeout
No timeout on boot menu:
set timeout=0
### default
Default no Nth (zero based) entry of boot menu:
set default="0"
### menuentry
The following commands can be used inside a menu entry, e.g.:
menuentry "main" {
}
Point to a multiboot file:
multiboot /boot/main.elf
E.g.: <https://github.com/cirosantilli/x86-bare-metal-examples/blob/48614b45fa6edeb97adbaad942595a4c25216113/multiboot/hello-world/iso/boot/grub/grub.cfg>
Load a linux kernel with a given root filesystem:
linux /boot/bzImage
initrd /boot/rootfs.cpio.gz
You can pass kernel command line arguments with:
linux /boot/bzImage BOOT_IMAGE=/boot/vmlinuz-3.19.0-28-generic root=UUID=2a49bac4-b9dd-466d-9c0c-c432aa4ca086 ro loop.max_part=15
You can then check that they've appeared under `cat /proc/cmdline`.
## Alternatives
- `syslinux`: Linux specific. Used by default by the kernel, e.g. on 4.2 `make isoimage`.
- LILO: old popular bootloader, largely replaced by GRUB now.
## Legacy
Documentation: <http://www.gnu.org/software/grub/manual/legacy/grub.html>
### kernel
Directive used to boot *both* multiboot and Linux.
Got split up more or less into `multiboot` and `linux` directives.
## Bibliography
- <https://www.gnu.org/software/grub/grub-documentation.html>
- <http://www.dedoimedo.com/computers/grub-2.html>
Great configuration tutorial.

View File

@@ -10,4 +10,4 @@ clean:
rm -f iso/boot/*.img *.img rm -f iso/boot/*.img *.img
run: main.img run: main.img
qemu-system-i386 -hda '$<' qemu-system-i386 -drive 'file=$<,format=raw'

View File

@@ -1,15 +0,0 @@
# chainloader
Takes another boot sector as argument.
This simply forwards the boot sector to another bootloader.
This is what you need to boot unsupported systems like Windows: just point to their partition and let them do the job.
This example uses a file, but the most common way to use it is with:
chainloader +1
which uses the first sector of some partition instead of a file.
TODO: why does it fail for hybrid ISO images? <http://superuser.com/questions/154134/grub-how-to-boot-into-iso-partition#comment1337357_154271>

View File

@@ -1,7 +1,6 @@
menuentry "hello-world" { menuentry "hello-world" {
chainloader /boot/main.img chainloader /boot/main.img
} }
# Reload ourselves again.
menuentry "self +1" { menuentry "self +1" {
chainloader +1 chainloader +1
} }

3
grub/linux/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/iso/boot/bzImage
/out/
/*.zip*

19
grub/linux/Makefile Normal file
View File

@@ -0,0 +1,19 @@
.POSIX:
.PHONY: clean run
BZIMAGE := iso/boot/bzImage
main.img: $(BZIMAGE)
grub-mkrescue -o '$@' iso
$(BZIMAGE):
wget https://github.com/cirosantilli/linux-kernel-module-cheat/releases/download/uploads/images-ab21ef58deed8536bc159c2afd680a4fabd68510.zip
unzip images-*.zip
cp out/x86_64/buildroot/images/bzImage '$@'
clean:
rm -f iso/boot/*.img *.img
run: main.img
qemu-system-i386 -drive 'file=$<,format=raw'

View File

@@ -0,0 +1,3 @@
menuentry "Buildroot" {
linux /boot/bzImage root=/dev/sda1 console=tty1 printk.time=y
}

View File

@@ -1,9 +0,0 @@
# Symlinks
This directory relies on the following symlinks to make directory structure modifiable in the future:
1. [mbrs](mbrs): directory that contains the Makefile for `bios_hello_world.img` and other MBRs
1. [bios_hello_world.img.sym](bios_hello_world.img.sym): boot sector that says hello world with BIOS
The `.sym` extension must be used because otherwise this symlink would be gitignored.

View File

@@ -1,19 +0,0 @@
RUN = hello.img
INC = biosfunc.S
all: $(INC) $(RUN)
$(RUN): hello.o
ld -N -e start -Ttext 0x7c00 --oformat binary -o $(RUN) hello.o
hello.o: hello.S $(INC)
as -o hello.o hello.S
disassemble: $(RUN)
objdump --disassemble-all --target=binary --architecture=i8086 $(RUN)
run: $(RUN)
qemu-system-i386 -hda $(RUN)
clean:
rm -f *.o a.out $(RUN)

View File

@@ -1,7 +0,0 @@
# Hajji hello world
Originally taken from <http://farid.hajji.name/blog/2010/05/25/hello-world-on-the-bare-metal/>
GAS hello world example.
TODO: minimize into multiple BIOS examples.

View File

@@ -1,81 +0,0 @@
/* biosfunc.S -- real-mode BIOS and convenience functions. */
.file "biosfunc.S"
.code16
/*
* The following convenience functions are only available
* in real mode through BIOS:
*
* void clrscr() # clear display
* void curshome() # move cursor home (0:0)
* void puts(%si) # display string
* void putc(%al) # display char
*
* use this libary like this:
* .include biosfunc.S
*/
/* clrscr() -- clear dislay */
clrscr:
/*
* clrscr() clears the video buffer, using a special case in
* the BIOS function "SCROLL UP WINDOW". Note that this
* function is only available in real mode, and that some
* buggy BIOSes destroy the base pointer %bp, so we better
* temporarily save it on the stack.
*/
pushw %bp # BIOS call below *can* destroy %BP
movb $0x06, %ah # BIOS function "SCROLL UP WINDOW"
movb $0x0, %al # nr. of lines to scroll (00=clear window)
movb $0x7, %bh # attr. to fill new lines at bottom
movw $0x0, %cx # CH,CL: row,column upper left corner (00:00)
movw $0x184f, %dx # DH,DL: row,column lower right corner (24:79)
int $0x10 # call BIOS
popw %bp
retw
/* curshome() -- set cursor position to 0:0 */
curshome:
/*
* curshome() moves the cursor to position 0:0 (top:left),
* using the BIOS function "SET CURSOR POSITION". This
* function is only available in real mode.
*/
movb $0x02, %ah # BIOS function "SET CURSOR POSITION"
movb $0x0, %bh # page number 0
movw $0x0, %dx # DH=0 row, DL=0 col
int $0x10 # call BIOS
retw
/* puts(%si) -- display 0-terminated string via putc() */
puts:
/*
* puts() repeatedly loads a byte from the buffer pointed
* to by %si into %al, and displays that byte by calling
* putc(%al), until a \0-byte is encountered. The buffer
* should thus be \0-terminated, like a regular C-string.
*/
lodsb # Load next byte from %si buffer into %al
cmpb $0x0, %al # %al == 0?
je puts1 # Yes: end of string!
callw putc # No: Display current char
jmp puts # Proceed next char
puts1: retw
/* putc(%al) -- output char %al via BIOS call int 10h, func 0Eh */
putc:
/*
* putc(%al) displays the byte %al on the default video
* buffer, using the BIOS function "TELETYPE OUTPUT".
* This function interprets some but not all control
* characters correctly, but it doesn't matter all too
* much in this simple example. This BIOS function is
* only available in real mode.
*/
movw $0x7, %bx # BH: page 0, BL: attribute 7 (normal white)
movb $0xe, %ah # BIOS function "TELETYPE OUTPUT"
int $0x10 # call BIOS
retw

View File

@@ -1,120 +0,0 @@
/* hello.S -- Hello, World on bare metal, just after BIOS boot. x86 */
.file "hello.S"
/*
* A couple of constants.
*
* These can't be changed, because they are set by the
* firmware (BIOS).
*/
.set LOAD, 0x7c00 # BIOS loads and jumps here
.set MAGIC, 0xaa55 # Must be at the end of the 512-byte block
.set BLOCKSIZE, 512 # Boot block is BLOCKSIZE bytes long
/*
* The .text section contains the opcodes (code) for our
* program.
*/
.section .text # This is a code (text) section.
.code16 # Boot code runs in 16-bit real mode
.globl start # Entry point is public, for the linker.
start:
/*
* The processor starts in real mode and executes the first
* instruction at address $0xFFFF:FFF0. System designers
* usually map BIOS at this address, so the CPU starts running
* BIOS code. The BIOS initializes RAM and other components.
* Then, it loads $BLOCKSIZE bytes from the first boot device
* in RAM, starting at address $0x0:$LOAD.
*
* If that block finishes with the $MAGIC sequence 0x55, 0xaa
* (it is reversed, because IA-32 arch is little endian), BIOS
* considers this block a valid boot block, and jumps right here.
*/
/*
* Initialize segment descriptors %ds, %es, and %ss to 0x0.
* %cs:%ip is already set by the BIOS to 0x0:$LOAD.
*/
xorw %ax, %ax
movw %ax, %es
movw %ax, %ds
/*
* Initialize the stack.
*
* Since the stack on x86 grows towards *lower* addresses,
* we anchor it at $LOAD. Note that we don't collide with
* the code because the stack will always remain below
* (i.e. less than) $LOAD and grows downwards from there.
* disable intterupts when setting up the stack. If an
* interrupt occurs between the two MOVs then the stack
* may point at the wrong memory location and the interrupt
* may crash the system
*/
cli
movw %ax, %ss
movw $LOAD, %sp
sti
/*
* This is the "main" program:
*
* Clear screen, move cursor to the top:left,
* and display a friendly greetings.
*/
callw clrscr # clear screen
callw curshome # move cursor home - top:left
callw greeting # display a greeting string
/*
* That's all, folks!
*
* We could run a tight loop here, but it's better to halt
* the processor. When run on bare metal, a halted processor
* consumes less power (especially useful if ran on battery).
* When run under an emulator, the emulator doesn't consume
* further CPU cycles. Turn off interrupts before caling HLT
* because execution will only HLT until the next intterupt
* occurs. Once an interrupt occurs execution continues at
* the next instruction after HLT
*/
cli
hlt
/* greeting() -- display a little message. */
greeting:
/*
* greeting dislays the string located at label msg,
* using the convenience function puts() defined below.
* We pass the *address* of that string (thus $msg instead
* of msg) in the %si register.
*/
movw $msg, %si
callw puts
retw
/*
* Finally, include the BIOS convenience functions used above.
*/
.include "biosfunc.S" # BIOS convenience functions.
.file "hello.S"
/* msg: the string buffer to be displayed. */
msg:
.asciz "Hello, World!\r\n" # must be \0-terminated!
/*
* The boot block MUST end with a MAGIC sequence.
*
* The BIOS checks this, and would refuse to boot unless
* MAGIC is there. The last two bytes of the BLOCKSIZE
* long block must contain the magic sequence 0x55, 0xaa.
* We move the assembler pointer .org there, and emit the
* word MAGIC. Note that MAGIC is set to 0xaa55, and not
* 0x55aa, because the IA-32 platform is little endian.
*/
.org BLOCKSIZE - 2
.word MAGIC

26
idt.S
View File

@@ -1,30 +1,4 @@
/*
# IDT
# Interrupt Descriptor Table
Expected output: "int 0 handled"
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
https://github.com/torvalds/linux/blob/v4.2/arch/x86/entry/entry_64.S
sets them all up: each `idtentry divide_error` call sets up a new one.
## Bibliography
- http://www.jamesmolloy.co.uk/tutorial_html/4.-The%20GDT%20and%20IDT.html
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
STAGE2 STAGE2
CLEAR CLEAR

9
idt1.S
View File

@@ -1,13 +1,4 @@
/*
# IDT 1
Sanity check that we can also handle int 1 besides just int 0.
Expected output: "int 1 handled"
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
STAGE2 STAGE2
CLEAR CLEAR

View File

@@ -1,20 +1,4 @@
/*
# IDT Zero Divide
Division by zero causes a Divide Error which Intel notes as `#DE`.
Expected output: "division by zero handled"
It is then handled by IDT 0.
Remember that DE is not *only* for division by zero: it also happens on overflow!
Thus the name: Division Error, and not Division by zero.
- http://stackoverflow.com/questions/33029457/what-to-do-in-interrupt-handler-for-divide-by-zero
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
STAGE2 STAGE2
CLEAR CLEAR
@@ -24,7 +8,7 @@ BEGIN
mov $0, %edx mov $0, %edx
mov $1, %eax mov $1, %eax
mov $0, %ecx mov $0, %ecx
/* The iret jumps back here! */ /* The iret jumps back here. */
div %ecx div %ecx
jmp . jmp .
IDT_START IDT_START
@@ -32,7 +16,7 @@ IDT_ENTRY
IDT_END IDT_END
handler: handler:
VGA_PRINT_STRING $message VGA_PRINT_STRING $message
/* If we don't do this, we get an infinite loop! */ /* If we don't do this, we get an infinite loop. */
mov $1, %ecx mov $1, %ecx
iret iret
message: message:

32
in.md
View File

@@ -1,32 +0,0 @@
# in
# out
IO operations.
Do not work with immediates: only registers or memory locations.
http://stackoverflow.com/questions/14194798/is-there-a-specification-of-x86-i-o-port-assignment
## Memory mapped vs Port mapped IO
<http://superuser.com/questions/703695/difference-between-port-mapped-and-memory-mapped-access>
`in` and `out` are used for port mapped IO.
In memory mapped IO, IO is done just like RAM IO.
TODO: see some circuit schematics.
## Bit 0x80
TODO http://wiki.osdev.org/CMOS#Getting_Current_Date_and_Time_from_RTC says:
/* since the 0x80 bit of al is not set, NMI is active */
out 0x70, al
What does it mean?
## Linux kernel
man outb

View File

@@ -1,64 +0,0 @@
/*
TODO not working on QEMU, but does produce some horrible sound on real hard.
Maybe because I cannot get the beep working on my Ubuntu host?
http://askubuntu.com/questions/19906/beep-in-shell-script-not-working
It looks like the beep (port 0x61) just uses
the PIT Channel 2 to generate the frequency, so understand the PIT first.
Extracted from:
https://github.com/torvalds/linux/blob/v4.2/arch/x86/realmode/rm/wakemain.c#L38
The kernel has a Morse code encoder with it!
Not using io_delay here, maybe would sound better with it?
See also:
- http://fly.srk.fer.hr/GDM/articles/sndmus/speaker1.html
## Port 0x61
http://wiki.osdev.org/PC_Speaker
Speaker specifics are there.
The 0x4X IO are the PIT.
*/
#include "common.h"
BEGIN
/* Chanel 2, square wave, load TODO?, binary */
mov $0xb6, %al
out %al, $0x43
/* Set frequency of Channel 2. */
.equ div, 1193181 / 1000
mov div, %ax
out %al, $0x42
mov %ah, %al
out %al, $0x42
/* Dummy read of System Control Port B. TODO why? */
in $0x61, %al
/*
Enable timer 2 output to speaker.
THIS is where the sound begins.
*/
mov $0x03, %al
out %al, $0x61
/* Loop forever to keep hearing it. */
loop:
nop
jmp loop
/*
This is how a sound can be stopped.
This code never runs in this example.
*/
in $0x61, %al
mov $0x00, %al
out %al, $0x61

View File

@@ -1,47 +0,0 @@
/*
Originally from: https://courses.engr.illinois.edu/ece390/books/labmanual/io-devices-speaker.html
Same as the kernel version.
*/
#include "common.h"
BEGIN
start:
PUTC $'a
mov $0xb6, %al
out %al, $0x43
mov $4560, %ax
out %al, $0x42
mov %ah, %al
out %al, $0x42
in $0x61, %al
/* TODO why or, while Linux kernel sets it to 3? */
or $0b00000011, %al
out %al, $0x61
/*
Pause for duration of note.
Busy loop of `25 * 2 ^ 16 - 1`
*/
mov $25, %bx
.pause1:
mov $65535, %cx
.pause2:
dec %cx
jne .pause2
dec %bx
jne .pause1
in $0x61, %al
/* TODO why Reset bits 1 and 0. */
and $0b11111100, %al
out %al, $0x61
jmp start

View File

@@ -1,31 +0,0 @@
/*
Whenever you press a key up or down,
the keyboard hex scancode is printed to the screen.
Uses the PS/2 keyboard controller:
http://wiki.osdev.org/%228042%22_PS/2_Controller
Only changes in state are shown.
Scancode tables: TODO: official specs?
- http://flint.cs.yale.edu/cs422/doc/art-of-asm/pdf/APNDXC.PDF
- https://en.wikipedia.org/wiki/Scancode
TODO Possible to do this with the interrupt table instead of `in`?
*/
#include "common.h"
BEGIN
CLEAR
/* TODO why CLI makes no difference? We are not using interrupts? */
/*cli*/
loop:
/* Store the scancode to al. */
in $0x60, %al
cmp %al, %cl
jz loop
mov %al, %cl
PRINT_HEX <%al>
PRINT_NEWLINE
jmp loop

View File

@@ -1,14 +0,0 @@
/*
TODO get working
Bibliography:
- http://wiki.osdev.org/Mouse_Input
- https://courses.engr.illinois.edu/ece390/books/labmanual/io-devices-mouse.html
I am so going to make a pixel drawing program with this.
*/
#include "common.h"
BEGIN
hlt

View File

@@ -1,11 +0,0 @@
# Intel startup
Code taken from the documentation PDF... copy pasting from PDFs is horrendous, so we're trying to make a decent text version here.
<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>
The exact syntax seems to have no implementation... but NASM should be close:
- <http://computer-programming-forum.com/46-asm/6d9e8b7acea2d4cc.htm>
- <http://coding.derkeiler.com/Archive/Assembler/alt.lang.asm/2005-12/msg00028.html>
- <https://groups.google.com/forum/#!topic/comp.lang.asm.x86/9UZPQWwv-mQ> 1994 comp.lang.asm.x86 topic!

View File

@@ -1,76 +1,10 @@
/*
# Interrupt
Minimal interrupt example.
Expected outcome: 'ab' gets printed to the screen.
TODO: is STI not needed because this interrupt is not maskable?
## int
What it does:
- long jumps to the CS : IP found in the corresponding interrupt vector.
- also pushes EFLAGS. Why? To let them be restored by iret?
## iret
Returns to the next instruction to be executed
before the interrupt came in.
I think this is mandatory, e.g. a `jmp` wouldn't be enough because:
- we may have far jumped
- iret also pops EFLAGS restoring. TODO more things also seem restored: CS, EIP, EFLAGS, SS, and ESP
http://stackoverflow.com/questions/10462884/must-iret-be-used-when-returning-from-an-interrupt
## ISR
## Interrupt service routines
Fancy name for the handler.
http://wiki.osdev.org/Interrupt_Service_Routines
## Interrupt descriptor table
## IDTR
## Interrupt descriptor table register
IDTR points to the IDT.
The IDT contains the list of callbacks for each interrupt.
This name seems to be reserved to 32-bit protected mode, IVT is the 16-bit term.
## IVT
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" #include "common.h"
BEGIN BEGIN
CLEAR CLEAR
DBG
/* Set address of the handler for interrupt 0. */
movw $handler, 0x00 movw $handler, 0x00
/* Set code segment of the handler for interrupt 0. */
mov %cs, 0x02 mov %cs, 0x02
int $0 int $0
PUTC $'b PUTC $'b

View File

@@ -1,13 +1,9 @@
/*
Test an interrupt handler different than 0.
Expected outcome: 'ab' gets printed to the screen.
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
CLEAR CLEAR
/* Set address of the handler for interrupt 1. */
movw $handler, 0x04 movw $handler, 0x04
/* Set code segment of the handler for interrupt 1. */
mov %cs, 0x06 mov %cs, 0x06
int $1 int $1
PUTC $'b PUTC $'b

View File

@@ -1,6 +1,3 @@
/*
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
CLEAR CLEAR
@@ -8,5 +5,4 @@ BEGIN
mov %cs, 0x02 mov %cs, 0x02
int $0 int $0
handler: handler:
PUTC $'a
int $0 int $0

View File

@@ -1,7 +1,3 @@
/*
Same as doing an `int $0`.
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
CLEAR CLEAR
@@ -11,5 +7,14 @@ BEGIN
div %ax div %ax
hlt hlt
handler: handler:
PUTC $'a mov myvar, %ax
incw myvar
PRINT_WORD_HEX
PRINT_NEWLINE
iret iret
myvar:
#if 1
.word 0x0000
#else
.word 0x0090
#endif

24
io.md
View File

@@ -1,24 +0,0 @@
# IO
You cannot use any libraries, so how to do IO? Some ways that this can be done:
- BIOS functions: <http://wiki.osdev.org/BIOS>. Not well standardized like it's successor UEFI. Called through interrupts.
- <https://en.wikipedia.org/wiki/VGA-compatible_text_mode>
- VBE <https://en.wikipedia.org/wiki/VESA_BIOS_Extensions>
Showdown and restart can be managed with either:
- APM
- ACPI <https://en.wikipedia.org/wiki/Advanced_Configuration_and_Power_Interface>
Newer and better.
Now managed by the same group that manages UEFI.
Spec:
- current: <http://uefi.org/specifications>
- old: <http://www.uefi.org/acpi/specs>
See also: <http://wiki.osdev.org/Shutdown>

39
lidt.S
View File

@@ -1,43 +1,20 @@
/*
# lidt
TODO get working:
- http://wiki.osdev.org/Real_Mode
Sets the IDTR through a from a descriptor in memory, and tells the CPU where the IDT is on memory.
Expected outcome: 'ab' gets printed to the screen.
osdev says this is not compatible with older CPUs.
# sidt
Read the descriptor register to memory.
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
CLEAR CLEAR
cli
movw $handler, idt_start
mov %cs, idt_start + 2
lidt idt_descriptor lidt idt_descriptor
movw $handler, 0x04
mov %cs, 0x06
int $0 int $0
PUTC $'b PUTC $'b
hlt hlt
idt_start:
idt: .word handler
.word 2 .word
.word 4
idt_end: idt_end:
idt_descriptor: idt_descriptor:
.word idt_end - idt .word idt_end - idt_start
.long idt .long idt_start
handler: handler:
PUTC $'a PUTC $'a
iret iret

14
lidt0.S Normal file
View File

@@ -0,0 +1,14 @@
#include "common.h"
BEGIN
CLEAR
movw $handler, 0
mov %cs, 2
movw $4, 8
movl $0, 0xA
lidt 8
int $0
PUTC $'b
hlt
handler:
PUTC $'a
iret

14
lidt2.S Normal file
View File

@@ -0,0 +1,14 @@
#include "common.h"
BEGIN
CLEAR
movw $handler, 4
mov %cs, 6
movw $4, 8
movl $4, 0xA
lidt 8
int $0
PUTC $'b
hlt
handler:
PUTC $'a
iret

View File

@@ -1,50 +1,44 @@
SECTIONS SECTIONS
{ {
/* /* We could also pass the -Ttext 0x7C00 to as instead of doing this.
We could also pass the -Ttext 0x7C00 to as instead of doing this. * If your program does not have any memory accesses, you can omit this.
*/
If your program does not have any memory accesses, you can omit this.
*/
. = 0x7c00; . = 0x7c00;
.text : .text :
{ {
__start = .; __start = .;
/* /* We are going to stuff everything
We are going to stuff everything * into a text segment for now, including data.
into a text segment for now, including data. * Who cares? Other segments only exist to appease C compilers.
Who cares? Other segments only exist to appease C compilers.
*/ */
*(.text) *(.text)
/* /* Magic bytes. 0x1FE == 510.
Magic bytes. 0x1FE == 510. *
* We could add this on each Gas file separately with `.word`,
We could add this on each Gas file separately with `.word`, * but this is the perfect place to DRY that out.
but this is the perfect place to DRY that out. */
*/
. = 0x1FE; . = 0x1FE;
SHORT(0xAA55) SHORT(0xAA55)
/* /* This is only needed if we are going to use a 2 stage boot process,
This is only needed if we are going to use a 2 stage boot process, * e.g. by reading more disk than the default 512 bytes with BIOS `int 0x13`.
e.g. by reading more disk than the default 512 bytes with BIOS `int 0x13`.
*/ */
*(.stage2) *(.stage2)
/* /* Number of sectors in stage 2. Used by the `int 13` to load it from disk.
Number of sectors in stage 2. Used by the `int 13` to load it from disk. *
* The value gets put into memory as the very last thing
The value gets put into memory as the very last thing * in the `.stage` section if it exists.
in the `.stage` section if it exists. *
* We must put it *before* the final `. = ALIGN(512)`,
We must put it *before* the final `. = ALIGN(512)`, * or else it would fall out of the loaded memory.
or else it would fall out of the loaded memory. *
* This must be absolute, or else it would get converted
This must be absolute, or else it would get converted * to the actual address relative to this section (7c00 + ...)
to the actual address relative to this section (7c00 + ...) * and linking would fail with "Relocation truncated to fit"
and linking would fail with "Relocation truncated to fit" * because we are trying to put that into al for the int 13.
because we are trying to put that into al for the int 13.
*/ */
__stage2_nsectors = ABSOLUTE((. - __start) / 512); __stage2_nsectors = ABSOLUTE((. - __start) / 512);
@@ -54,11 +48,3 @@ SECTIONS
__end_align_4k = ALIGN(4k); __end_align_4k = ALIGN(4k);
} }
} }
/*
The linux kernel 4.2 uses linker scripts like:
- https://github.com/torvalds/linux/blob/v4.2/arch/x86/boot/setup.ld
The kernel also uses the `.lds` extension for its scripts.
*/

27
mbr.md
View File

@@ -1,27 +0,0 @@
# MBR
A major type of boot sector.
Wiki page describes it well enough: <https://en.wikipedia.org/wiki/Master_boot_record>
TODO where is it specified, if at all?
## Gotchas
- bytes 511 and 512 of the boot sector must be `0x55aa` or else the BIOS will refuse to load
- BIOS loads the program into memory at the address `0x7C00`.
Note that this is not the first address that the RIP is set to run: I think all jobs up to now are done by the CPU: the first address seems to be `FFFF:0000` instead: <http://stackoverflow.com/a/32686533/895245>
We must tell that magic number to the linker somehow, either with a linker script, `-tText=-Ttext 0x7C00` or NASM `org 0x7c00`.
This will only matter when you access a memory address, because of relocation.
If you don't know what relocation is, first read this: <http://stackoverflow.com/questions/12122446/how-does-c-linking-work-in-practice/30507725#30507725>
When we link a normal program with an OS, the linker tells where it wants the OS to place it in virtual memory.
But for the boot sector, the BIOS puts the program into memory. So we must tell that to the linker somehow. Otherwise it cannot know what addresses to use for instructions.
- x86 processors start in 16-bit mode.

23
min.S
View File

@@ -1,22 +1,19 @@
/* Minimal example that does nothing, just halts. */
/* Tell GAS to generate 16 bit code. */ /* Tell GAS to generate 16 bit code. */
.code16 .code16
/* Don't listen to interrupts. */ /* Don't listen to interrupts. */
cli cli
/* /* Zero ds.
Zero ds. *
* This is only needed if we are going to access memory.
This is only needed if we are going to access memory. *
* The program might work on QEMU without this, but fail on real hardware:
The program might work on QEMU without this, but fail on real hardware: * http://stackoverflow.com/questions/32508919/how-to-produce-a-minimal-bios-hello-world-boot-sector-with-gcc-that-works-from-a
http://stackoverflow.com/questions/32508919/how-to-produce-a-minimal-bios-hello-world-boot-sector-with-gcc-that-works-from-a *
* You cannot write immediates direclty to it, must pass through ax:
You cannot write immediates direclty to it, must pass through ax: * http://stackoverflow.com/questions/19074666/8086-why-cant-we-move-an-immediate-data-into-segment-register
http://stackoverflow.com/questions/19074666/8086-why-cant-we-move-an-immediate-data-into-segment-register */
*/
xor %ax, %ax xor %ax, %ax
mov %ax, %ds mov %ax, %ds

View File

@@ -1,43 +0,0 @@
# Modes of operation
Covered on Intel Volume 3. Specially useful is the "Transitions Among the Processors Operating Modes" diagram.
- Protected
- Real address
- System management
- IA-32e. Has two sub modes:
- Compatibility
- 64-bit
## Real mode
CPU starts here for backwards compatibility.
<http://wiki.osdev.org/Real_Mode>
It is possible to use 32-bit registers in this mode with the "Operand Size Override Prefix" `0x66`.
TODO: is it possible to access memory above 1M like this:
mov $1, 0xF0000000
mov $1, (%eax)
<http://stackoverflow.com/questions/6917503/is-it-possible-to-use-32-bits-registers-instructions-in-real-mode>
## IA-32e
Wikipedia seems to call it *long mode*: <https://en.wikipedia.org/wiki/Long_mode>
Contains two sub-modes: 64-bit and compatibility.
64-bit is the major mode of operation, compatibility mode emulates IA-32. This is where systems run most of the time.
The other mode is legacy mode, which as the name implies, should not be used on new programs.
### Compatibility mode
Controlled by the `CS.L` bit of the segment descriptor.
It appears that it is possible for user programs to modify that during execution: <http://stackoverflow.com/questions/12716419/can-you-enter-x64-32-bit-long-compatibility-sub-mode-outside-of-kernel-mode>
<http://stackoverflow.com/questions/27868394/switch-from-64-bit-long-mode-to-32-bit-compatibility-mode-on-x64>

View File

@@ -1,40 +0,0 @@
# Multiboot
1. [hello-world](hello-world/)
1. [osdev](osdev/)
## Usage
QEMU supports multiboot natively <https://stackoverflow.com/questions/25469396/how-to-use-qemu-properly-with-multi-boot-headers/32550281#32550281>:
cd hello-world
make
qemu-system-x86_64 -kernel main.elf
Outcome: `hello world` shows on screen.
Or you can use `grub-mkrescue` to make a multiboot file into a bootable ISO or disk:
qemu-system-x86_64 -hda main.img
## Introduction
<https://en.wikipedia.org/wiki/Multiboot_Specification>
Standard created by GRUB for booting OSes.
Multiboot files are an extension of ELF files with a special header.
Advantages: GRUB does housekeeping magic for you:
- you can store the OS as a regular file inside a filesystem
- your program starts in 32-bit mode already, not 16 bit real mode
- it gets the available memory ranges for you
Disadvantages:
- more boilerplate
GRUB leaves the application into a well defined starting state.
It seems that Linux does not implement Multiboot natively, but GRUB supports it as an exception: <http://stackoverflow.com/questions/17909429/booting-a-non-multiboot-kernel-with-grub2>

View File

@@ -1,9 +0,0 @@
# Hello World
Multiboot hello world.
The `main.img` file can be burned to a USB and run on real hardware.
Uses VGA output because we cannot use BIOS calls from protected mode: <http://stackoverflow.com/questions/5794991/why-cant-i-call-bios-interrupts-from-protected-mode>
Originally minimized from <https://github.com/programble/bare-metal-tetris>

View File

@@ -1,9 +0,0 @@
# Hello world multiboot C
Originally from: <http://wiki.osdev.org/Bare_Bones>, should be a reasonable way to start a serious OS.
A hello world, with multiboot and a C interface.
The multiboot interface is prepared in GAS assembly.
Generates a bootable disk image by using `grub-mkrescue` on the multiboot binary.

View File

@@ -106,8 +106,8 @@ void kernel_main() {
terminal_initialize(); terminal_initialize();
/* Since there is no support for newlines in terminal_putchar /* Since there is no support for newlines in terminal_putchar
* yet, '\n' will produce some VGA specific character instead. * yet, '\n' would produce some weird VGA specific character instead.
* This is normal. * This is normal.
*/ */
terminal_writestring("Hello, kernel World!\n"); terminal_writestring("hello world");
} }

View File

@@ -1,9 +0,0 @@
# NASM
While NASM is a bit more convenient than GAS to write a boot sector, I think it is just not worth it.
When writing an OS in C, we are going to use GCC, which already uses GAS. So it's better to reduce the number of assemblers to one and stick to GAS only.
Right now, this directory is not very DRY since NASM is secondary to me, so it contains mostly some copy / paste examples.
On top of that, GAS also supports other architectures besides x86, so learning it is more useful in that sense.

1
nasm/run Symbolic link
View File

@@ -0,0 +1 @@
../run

View File

@@ -1,9 +0,0 @@
# No linker script
Hello world using the default `ld` script, not an explicit one set with `-T`. Uses:
- `-tText`
- `.org` inside each assembly file
- `_start` must be present to avoid a warning, since the default linker script expects it
Less stable, but more convenient for quick and dirty tests.

View File

@@ -1,14 +1,3 @@
/*
# Page fault
Generate and handle a page fault.
Expected output:
Page fault handled. Error code:
00000002
*/
#include "common.h" #include "common.h"
BEGIN BEGIN

View File

@@ -1,20 +1,3 @@
/*
# Paging
Expected output:
00001234
00005678
Verbose beginner's tutorial: http://www.cirosantilli.com/x86-paging/
Keep the following Intel shorthands in mind:
- PTE: Page table
- PDE: Page directory
- PDPTE: Page-directory-
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
@@ -34,18 +17,16 @@ BEGIN
PAGING_ON PAGING_ON
/* /* THIS is what we've been working for!!!
THIS is what we've been working for!!! * Even though we mov to 0, the paging circuit reads that as physical address 0x1000,
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.
so the canary value 0x1234 should be modified to 0x5678. **/
*/
movl $0x5678, 0 movl $0x5678, 0
/* /* Turn paging back off to prevent it from messing with us.
Turn paging back off to prevent it from messing with us. * Remember that VGA does memory accesses, so if paging is still on,
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.
we must identity map up to it, which we have, so this is not mandatory. * */
*/
PAGING_OFF PAGING_OFF
/* Print the (hopefully) modified value 0x5678. */ /* Print the (hopefully) modified value 0x5678. */

35
pc_speaker.S Normal file
View File

@@ -0,0 +1,35 @@
#include "common.h"
BEGIN
/* Chanel 2, square wave, load TODO?, binary */
mov $0xb6, %al
out %al, $0x43
/* Set frequency of Channel 2. */
.equ div, 1193181 / 1000
mov div, %ax
out %al, $0x42
mov %ah, %al
out %al, $0x42
/* Dummy read of System Control Port B. TODO why? */
in $0x61, %al
/* Enable timer 2 output to speaker.
* THIS is where the sound begins.
*/
mov $0x03, %al
out %al, $0x61
/* Loop forever to keep hearing it. */
loop:
nop
jmp loop
/* This is how a sound can be stopped.
* This code never reached in this example.
* unless you hack it up.
*/
in $0x61, %al
mov $0x00, %al
out %al, $0x61

103
pic.md
View File

@@ -1,103 +0,0 @@
# PIC
TODO add some examples.
Programmable interrupt controller:
- <http://wiki.osdev.org/PIC>
- <http://www.jamesmolloy.co.uk/tutorial_html/5.-IRQs%20and%20the%20PIT.html>
How it works:
Hardware -> IRQ -> PIC -> Interrupt handler
Each hardware has an IRQ, e.g. 0 for the PIT.
When an IRQ activated (e.g. PIT sends a signal), the PIC decides:
- whether or not it will call an interrupt handler. For example, without and EOI, further interrupts will not be generated.
- which interrupt handler it will call. This can be modified by programming the PIT.x
For example, Molloy shifts protected mode IRQs from interrupt 0 to 32, so that they won't conflict with the CPU defined exceptions in that area. <https://github.com/cirosantilli/jamesmolloy-kernel-development-tutorials/blob/d15a2dfb721008e2a3df132c8cda37c0e62ad826/5_irq/descriptor_tables.c#L72>
The default IRQ assignment is shown at: <https://en.wikipedia.org/wiki/Interrupt_request_%28PC_architecture%29#x86_IRQs>
Like other external circuits, the PIC is itself also programmed by `in` and `out` instructions.
## EOI
End of interrupt.
We must tell the PIC that we are at the end.
Otherwise new interrupts with equal or lower precedence don't fire again.
<https://en.wikipedia.org/wiki/End_of_interrupt>
## Plug and play
TODO: how does plug and play configure IRQs?
## APIC
APIC vs PIC:
- allows for multithreading
- 24 IRQs instead of 15. The new top 8 are for PCI and deal better with conflicts.
- has a millisecond timer built-in. Different from the HPET.
## Linux
### Shared IRQs
It is possible to use a single IRQ for multiple hardware:
<http://unix.stackexchange.com/questions/47306/how-does-the-linux-kernel-handle-shared-irqs>
### /proc/interrupts
PIC information can be found under:
cat /proc/interrupts
Sample output:
CPU0 CPU1 CPU2 CPU3
0: 19 0 0 0 IO-APIC-edge timer
1: 1032 9717 745 811 IO-APIC-edge i8042
8: 0 1 0 0 IO-APIC-edge rtc0
9: 752 2027 603 895 IO-APIC-fasteoi acpi
12: 139040 766443 108662 100894 IO-APIC-edge i8042
16: 139 802 5766 10382 IO-APIC 16-fasteoi ehci_hcd:usb3, mmc0
17: 216285 493484 25621 65079 IO-APIC 17-fasteoi rtl_pci
23: 27 218 79 318 IO-APIC 23-fasteoi ehci_hcd:usb4
25: 0 0 0 0 PCI-MSI-edge xhci_hcd
26: 41510 101990 49473 124665 PCI-MSI-edge 0000:00:1f.2
27: 10 2 4065 0 PCI-MSI-edge eth0
28: 22 1 1 0 PCI-MSI-edge mei_me
29: 65843 218345 46842 43270 PCI-MSI-edge i915
30: 76 175 17 1 PCI-MSI-edge snd_hda_intel
31: 12 16 5 7 PCI-MSI-edge nouveau
NMI: 71 67 71 65 Non-maskable interrupts
LOC: 1265066 702342 1396277 840420 Local timer interrupts
SPU: 0 0 0 0 Spurious interrupts
PMI: 71 67 71 65 Performance monitoring interrupts
IWI: 0 0 0 0 IRQ work interrupts
RTR: 0 0 0 0 APIC ICR read retries
RES: 180487 200186 197582 204727 Rescheduling interrupts
CAL: 2570 885 1391 1252 Function call interrupts
TLB: 60853 64789 54844 73507 TLB shootdowns
TRM: 0 0 0 0 Thermal event interrupts
THR: 0 0 0 0 Threshold APIC interrupts
MCE: 0 0 0 0 Machine check exceptions
MCP: 29 29 29 29 Machine check polls
HYP: 0 0 0 0 Hypervisor callback interrupts
ERR: 0
MIS: 0
- timer: PIT
- i8042: keyboard
- fasteoi vs edge: <http://stackoverflow.com/questions/7005331/difference-between-io-apic-fasteoi-and-io-apic-edge>
- PCI-MSI-edge: <http://stackoverflow.com/questions/10894702/diff-between-io-apic-level-and-pci-msi-x>
- EHCI http://www.intel.com/content/www/us/en/io/universal-serial-bus/ehci-specification.html

62
pit.S
View File

@@ -1,65 +1,3 @@
/*
# PIT
Programmable interrupt timer.
Expected output: "a\n" is printed infinitely many times.
Generates periodic interrupts (or sound!) with a given frequency to IRQ0,
which on real mode maps to interrupt 8 by default.
Major application: interrupt the running process
to allow the OS to schedule processes.
Read this *now*: http://wiki.osdev.org/PIT
Has 3 channels that can generate 3 independent signals
- channel 0 at port 0x40: generates interrupts
- channel 1 at port 0x41: not to be used for some reason
- channel 2 at port 0x42: linked to the speaker to generate sounds
Port 0x43 is used to control signal properties except frequency
(which goes in the channel ports) for the 3 channels.
See osdev article for details.
## Frequency
## 1193181
We don't control the frequency of the PIT directly,
which is fixed at 1193181.
Instead, we control a frequency divisor.
This is an well known type of discrete electronic circuit:
https://en.wikipedia.org/wiki/Frequency_divider
1193181 has 2 occurrences on Linux 4.2.
The frequency comes from historical reasons to reuse television hardware.
## Maskable interrupts
## sti
http://wiki.osdev.org/Non_Maskable_Interrupt
Interrupts generated by the CPU from 0 - 31 are not maskable: they generate interrupts even with `cli`.
Those from the PIC are maskable however.
QEMU generates many maskable interrupts by default. TODO: where do they come from? What are they excatly?
If we don't set a handler, we usually triple-fault and the machine restarts, thus going into a restart loop.
## Bibliography
- https://en.wikipedia.org/wiki/Intel_8253 That is the circuit ID for the PIT.
- http://kernelx.weebly.com/programmable-interval-timer.html
TODO learn to turn off the PIT after some iterations
*/
#include "common.h" #include "common.h"
BEGIN BEGIN

View File

@@ -1,15 +1,3 @@
/*
# PIT once
Make the PIT generate a single interrupt instead of a frequency.
Expected output: "a".
TODO how does this mode work exactly?
I think it counts down from the value in PORT_PIT_CHANNEL0,
and fires when it reaches 0 the first time.
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
IVT_PIT_SETUP IVT_PIT_SETUP

View File

@@ -1,13 +1,3 @@
/*
# PIT protected mode
Expected output:
00000020\n
is printed to the screen infinitely with the minimum PIT frequency.
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
STAGE2 STAGE2

View File

@@ -16,4 +16,4 @@ clean:
rm -f '$(MAIN)' rm -f '$(MAIN)'
run: $(MAIN) run: $(MAIN)
qemu-system-i386 -hda '$(MAIN)' qemu-system-i386 -drive file='$(MAIN)',format=raw

View File

@@ -1,7 +0,0 @@
# printf
Minimal boot sector example that does nothing, just halts immediately, generated with `printf` byte by byte.
You can't get more minimal than this.
Also described at: <https://stackoverflow.com/questions/22054578/how-to-run-a-program-without-an-operating-system/32483545#32483545>

View File

@@ -1,35 +1,4 @@
/*
# Protected mode
Major changes from real moe:
- BIOS cannot be used anymore
- GDT and segmentation take effect immediately so we *have*
to deal with it now.
- 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.
- 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
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
CLEAR CLEAR
PROTECTED_MODE PROTECTED_MODE

14
ps2_keyboard.S Normal file
View File

@@ -0,0 +1,14 @@
#include "common.h"
BEGIN
CLEAR
in $0x60, %al
mov %al, %cl
loop:
/* Store the scancode to al. */
in $0x60, %al
cmp %al, %cl
jz loop
mov %al, %cl
PRINT_HEX <%al>
PRINT_NEWLINE
jmp loop

48
real_segmentation.S Normal file
View File

@@ -0,0 +1,48 @@
#include "common.h"
BEGIN
CLEAR
/* It is not possible to encode moving immediates
* to segment registers: we must either:
*
* * pass through a general register ax
* * pop from the stack
*/
mov $1, %ax
mov %ax, %ds
mov %ds:msg, %al
PUTC <%al>
/* %ds is the default segment for GAS memory operations
* if we don't write it explicitly.
*/
mov msg, %al
PUTC <%al>
mov $1, %ax
mov %ax, %es
mov %es:msg, %al
PUTC <%al>
mov $1, %ax
mov %ax, %fs
mov %fs:msg, %al
PUTC <%al>
mov $1, %ax
mov %ax, %gs
mov %gs:msg, %al
PUTC <%al>
mov $1, %ax
mov %ax, %ss
mov %ss:msg, %al
PUTC <%al>
hlt
msg:
/* Push the correct A forward 16 bytes in memory
* to compensate for the segments.
*/
.fill 0x10
.byte 'A'

View File

@@ -1,11 +1,3 @@
/*
http://stackoverflow.com/questions/32682152/how-to-reboot-in-x86-assembly-from-16-bit-real-mode
Infinite reboot loop on emulator!
TODO why does it work?
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
ljmpw $0xF000, $0XFFF0 ljmpw $0xF000, $0XFFF0

26
ring.md
View File

@@ -1,26 +0,0 @@
# Ring
IA-32 implements a hardware protection privilege system.
Rings are implemented together with the segmentation system.
There are in total 4 privilege levels, also called rings, from 0 to 3, 0 being the one with the highest privilege.
Most operating systems use only 2: kernel space and user space, usually with values 0 and 3. This is the case for Linux.
For each ring, a certain set of operations is allowed by the processor.
Rings are useful for OS programmers. The OS lets user programs run a restricted set of operations limiting the amount of damage that a badly working or badly intentioned program can do. Obviously this only works because user programs are then in a state in which they cannot modify their own privilege levels without the OS intervening.
Certain operations such are only allowed if certain privileges are given.
Privilege control is only available on protected mode, and is managed by segmentation and paging.
## Negative rings
TODO: officially documented, possibly with other names?
- <https://en.wikipedia.org/wiki/Popek_and_Goldberg_virtualization_requirements>
- <https://www.quora.com/Computer-Science-What-does-In-x86-beyond-ring-0-lie-the-more-privileged-realms-of-execution-where-our-code-is-invisible-to-AV-we-have-unfettered-access-to-hardware-and-can-trivially-preempt-and-modify-the-OS-mean>
Some of them were introduced to satisfy <https://en.wikipedia.org/wiki/Popek_and_Goldberg_virtualization_requirements>

26
rtc.S
View File

@@ -1,29 +1,3 @@
/*
# RTC
Real time clock.
Gives wall time with precision of seconds.
Uses a separate battery to keep going.
http://stackoverflow.com/questions/1465927/how-can-i-access-system-time-using-nasm
http://wiki.osdev.org/RTC
http://wiki.osdev.org/CMOS
Kenrel 4.2 usage: https://github.com/torvalds/linux/blob/v4.2/arch/x86/kernel/rtc.c#L121
## Milliseconds
Not possible. Consider the PIT, or the HPET.
## Time zone
QEMU uses UTC.
*/
/* TODO what do those numbers mean? Where is this all documented? */ /* TODO what do those numbers mean? Where is this all documented? */
.equ RTCaddress, 0x70 .equ RTCaddress, 0x70
.equ RTCdata, 0x71 .equ RTCdata, 0x71

7
run Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
img="${1:-}"
if [ -n "$img" ]; then
img="RUN=${img%.*}"
fi
type="${2:-run}"
make "${type}" $img

0
run-bios_hello_world Normal file
View File

View File

@@ -1,117 +0,0 @@
/*
# Segment registers
Show how most segment registers work in 16-bit real mode.
Expected outcome: 'A' character gets printed 6 times to screen.
On failure, trash is shows.
I think their goal was to implement process virtualization in the past.
The special semantics of other registers will be covered in other files.
Rationale of the registers:
- extend other registers. For e.g., http://stackoverflow.com/questions/17777146/what-is-the-purpose-of-cs-and-ip-registers-intel-8086
- rudimentary virtual process spaces
## ES
TODO: this does seem to have special properties as used by string instructions.
- BIOS calls: `int 13h` disk read, `int 15` memory detection
## FS
## GS
FS and GS are general purpose: they don't generate or are affected implicitly by instructions.
## Vs protected mode
In protected mode, the segment registers point to entries on the GDT:
- http://reverseengineering.stackexchange.com/questions/2006/how-are-the-segment-registers-fs-gs-cs-ss-ds-es-used-in-linux
- http://wiki.osdev.org/Segmentation
Those entries still contain an analogous offset to the real mode offset,
but also much more data, like segment width and permission.
## How they work
What they do is simple: the full addressing syntax is:
%segment:a(b, c, d)
and the final address is calculated at:
%segment * 16 + a + b * c + d
So if we set a segment to 1, it just adds 16 to addresses.
## Instruction encoding
The command:
objdump -D -b binary -m i8086 segment_registers_real.img
Shows that non ds encodings are achieved through a prefix, except for `ds`:
20: a0 63 7c mov 0x7c63,%al
34: 26 a0 63 7c mov %es:0x7c63,%al
40: 64 a0 63 7c mov %fs:0x7c63,%al
4c: 65 a0 63 7c mov %gs:0x7c63,%al
58: 36 a0 63 7c mov %ss:0x7c63,%al
This makes `ds` the most efficient one for data access, and thus a good default.
*/
#include "common.h"
BEGIN
CLEAR
/*
It is not possible to encode moving immediates
to segment registers: we must either:
- pass through a general register ax
- pop from the stack
*/
mov $1, %ax
mov %ax, %ds
mov %ds:msg, %al
PUTC <%al>
/*
%ds is the default segment for GAS memory operations
if we don't write it explicitly.
*/
mov msg, %al
PUTC <%al>
mov $1, %ax
mov %ax, %es
mov %es:msg, %al
PUTC <%al>
mov $1, %ax
mov %ax, %fs
mov %fs:msg, %al
PUTC <%al>
mov $1, %ax
mov %ax, %gs
mov %gs:msg, %al
PUTC <%al>
mov $1, %ax
mov %ax, %ss
mov %ss:msg, %al
PUTC <%al>
hlt
msg:
/* We push the correct A forward 16 bytes in memory to compensate for the segments. */
.fill 0x10
.byte 'A'

View File

@@ -1,13 +1,3 @@
/*
TODO get working. All tutorials I've seen so far just set it to 0 like real OSes :-(
Example of the effect on a memory access of changing the segment base address .
Expected output: "x\na\nb".
Without segment manipulation, the output would be "a".
*/
#include "common.h" #include "common.h"
BEGIN BEGIN
@@ -37,18 +27,16 @@ BEGIN
xor $1, %al xor $1, %al
mov %al, (%edx) mov %al, (%edx)
/* /* We must re-set ds because the segment descriptor is cached
We must re-set ds because the segment descriptor is cached * and this updates it:
and this updates it: * http://wiki.osdev.org/Descriptor_Cache
http://wiki.osdev.org/Descriptor_Cache */
*/
mov $DATA_SEG, %ax mov $DATA_SEG, %ax
mov %ax, %ds mov %ax, %ds
/* /* This is the only memory access we will make with
This is the only memory access we will make with * the modified segment, to minimize the effect on our IO.
the modified segment, to minimize the effect on our IO. */
*/
mov message, %cl mov message, %cl
/* Restore the old segment. */ /* Restore the old segment. */
@@ -57,17 +45,16 @@ BEGIN
mov %bl, (%edx) mov %bl, (%edx)
mov %ax, %ds mov %ax, %ds
/* /* TODO this sanity check is not printing "ab".
TODO this sanity check is not printing "ab". * It fails, so we're not restoring the old state properly.
It fails, so we're not restoring the old state properly. * Maybe blows up because video memory going wrong?
Likely blows up because video memory going wrong. */
*/
VGA_PRINT_STRING $message VGA_PRINT_STRING $message
mov %cl, output mov %cl, output
VGA_PRINT_STRING $output VGA_PRINT_STRING $output
jmp . hlt
message: message:
.asciz "ab" .asciz "ab"

Some files were not shown because too many files have changed in this diff Show More