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:
3
LICENSE.adoc
Normal file
3
LICENSE.adoc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
= LICENSE
|
||||||
|
|
||||||
|
See: link:README.adoc#license[]
|
||||||
17
LICENSE.md
17
LICENSE.md
@@ -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.
|
|
||||||
2
Makefile
2
Makefile
@@ -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
2019
README.adoc
Normal file
File diff suppressed because it is too large
Load Diff
88
README.md
88
README.md
@@ -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
144
TODO.md
@@ -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: `
|
|
||||||
47
about.md
47
about.md
@@ -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
11
apm.md
@@ -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>
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
120
bibliography.md
120
bibliography.md
@@ -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
80
bios.md
@@ -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.
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
34
bios_color.S
34
bios_color.S
@@ -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
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/*
|
|
||||||
Change the current cursor position.
|
|
||||||
|
|
||||||
Expected output: "cb"
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
BEGIN
|
BEGIN
|
||||||
CLEAR
|
CLEAR
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
42
bios_pixel.S
42
bios_pixel.S
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/*
|
|
||||||
# BIOS putc
|
|
||||||
|
|
||||||
Print a single `@` character with the BIOS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
BEGIN
|
BEGIN
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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
20
bios_sleep.S
Normal 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
|
||||||
@@ -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
485
common.h
@@ -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
12
cpu.md
@@ -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
22
cs.S
@@ -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
|
||||||
|
|||||||
17
debug.md
17
debug.md
@@ -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>
|
|
||||||
19
formats.md
19
formats.md
@@ -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
12
gdb.gdb
@@ -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
|
||||||
|
|||||||
@@ -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
335
grub/README.adoc
Normal 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.
|
||||||
305
grub/README.md
305
grub/README.md
@@ -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.
|
|
||||||
@@ -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'
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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
3
grub/linux/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/iso/boot/bzImage
|
||||||
|
/out/
|
||||||
|
/*.zip*
|
||||||
19
grub/linux/Makefile
Normal file
19
grub/linux/Makefile
Normal 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'
|
||||||
3
grub/linux/iso/boot/grub/grub.cfg
Normal file
3
grub/linux/iso/boot/grub/grub.cfg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
menuentry "Buildroot" {
|
||||||
|
linux /boot/bzImage root=/dev/sda1 console=tty1 printk.time=y
|
||||||
|
}
|
||||||
@@ -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.
|
|
||||||
@@ -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)
|
|
||||||
@@ -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.
|
|
||||||
@@ -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
|
|
||||||
120
hajji/hello.S
120
hajji/hello.S
@@ -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
26
idt.S
@@ -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
9
idt1.S
@@ -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
|
||||||
|
|||||||
@@ -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
32
in.md
@@ -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
|
|
||||||
64
in_beep.S
64
in_beep.S
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
14
in_mouse.S
14
in_mouse.S
@@ -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
|
|
||||||
@@ -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!
|
|
||||||
72
interrupt.S
72
interrupt.S
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
24
io.md
@@ -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
39
lidt.S
@@ -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
14
lidt0.S
Normal 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
14
lidt2.S
Normal 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
|
||||||
64
linker.ld
64
linker.ld
@@ -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
27
mbr.md
@@ -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
23
min.S
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
# Modes of operation
|
|
||||||
|
|
||||||
Covered on Intel Volume 3. Specially useful is the "Transitions Among the Processor’s 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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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.
|
|
||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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.
|
|
||||||
11
page_fault.S
11
page_fault.S
@@ -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
|
||||||
|
|||||||
35
paging.S
35
paging.S
@@ -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
35
pc_speaker.S
Normal 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
103
pic.md
@@ -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
62
pit.S
@@ -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
|
||||||
|
|||||||
12
pit_once.S
12
pit_once.S
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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
14
ps2_keyboard.S
Normal 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
48
real_segmentation.S
Normal 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'
|
||||||
8
reboot.S
8
reboot.S
@@ -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
26
ring.md
@@ -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
26
rtc.S
@@ -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
7
run
Executable 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
0
run-bios_hello_world
Normal 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'
|
|
||||||
@@ -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
Reference in New Issue
Block a user