Uniformized macros: push save all registers, and use only GAS macros, no CPP
This commit is contained in:
@@ -56,6 +56,8 @@ Minimal operating systems to learn low level programming.
|
|||||||
1. [UEFI](uefi/)
|
1. [UEFI](uefi/)
|
||||||
1. Misc
|
1. Misc
|
||||||
1. [hajji](hajji/)
|
1. [hajji](hajji/)
|
||||||
|
1. Tests
|
||||||
|
1. [PRINT_BYTES](test_print_bytes.S)
|
||||||
1. Theory
|
1. Theory
|
||||||
1. [Modes of operation](modes-of-operation.md)
|
1. [Modes of operation](modes-of-operation.md)
|
||||||
1. [Segmentation](segmentation.md)
|
1. [Segmentation](segmentation.md)
|
||||||
|
|||||||
2
TODO.md
2
TODO.md
@@ -114,4 +114,4 @@
|
|||||||
|
|
||||||
- mouse
|
- mouse
|
||||||
|
|
||||||
|
- DOS question: http://stackoverflow.com/questions/23043732/accessing-the-mouse-via-assembly-x86
|
||||||
|
|||||||
12
about.md
12
about.md
@@ -11,3 +11,15 @@ This is not meant provide a template from which you can write a real OS, but ins
|
|||||||
Minimal examples are useful because it is easier to observe the requirements for a given concept to be observable.
|
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.
|
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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ mov $0x0B, %ah
|
|||||||
mov $0x0034, %bx
|
mov $0x0034, %bx
|
||||||
int $0x10
|
int $0x10
|
||||||
|
|
||||||
PUTC($0x61)
|
PUTC $'a
|
||||||
|
|
||||||
hlt
|
hlt
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Carriage returns are needed just like in old days.
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
BEGIN
|
BEGIN
|
||||||
PRINT($msg)
|
PRINT $msg
|
||||||
hlt
|
hlt
|
||||||
msg:
|
msg:
|
||||||
.asciz "hello\n\rworld"
|
.asciz "hello\n\rworld"
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
/* Clear screen by scrolling. */
|
/*
|
||||||
|
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
|
||||||
@@ -8,7 +15,7 @@ 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($0x61)
|
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
|
||||||
@@ -29,7 +36,7 @@ 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(0, 0)
|
CURSOR_POSITION
|
||||||
PUTC($0x62)
|
PUTC $'b
|
||||||
|
|
||||||
hlt
|
hlt
|
||||||
|
|||||||
11
bios_color.S
11
bios_color.S
@@ -1,6 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
Write a character N times with given color.
|
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?
|
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
|
Color codes: https://en.wikipedia.org/wiki/BIOS_color_attributes
|
||||||
@@ -23,9 +28,9 @@ 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($0x62)
|
PUTC $'b
|
||||||
PUTC($0x63)
|
PUTC $'c
|
||||||
|
|
||||||
hlt
|
hlt
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
|
/*
|
||||||
|
Change the current cursor position.
|
||||||
|
|
||||||
|
Expected output: "cb"
|
||||||
|
*/
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
BEGIN
|
BEGIN
|
||||||
CLEAR
|
CLEAR
|
||||||
|
|
||||||
/* Print "ab" */
|
/* Print "ab" */
|
||||||
PUTC($0x61)
|
PUTC $'a
|
||||||
PUTC($0x62)
|
PUTC $'b
|
||||||
|
|
||||||
/* Move back to 0, 0.*/
|
/* Move back to 0, 0.*/
|
||||||
mov $0x02, %ah
|
mov $0x02, %ah
|
||||||
@@ -15,6 +21,6 @@ mov $0x0, %dx
|
|||||||
int $0x10
|
int $0x10
|
||||||
|
|
||||||
/* Overwrite 'a' with c'. */
|
/* Overwrite 'a' with c'. */
|
||||||
PUTC($0x63)
|
PUTC $'c
|
||||||
|
|
||||||
hlt
|
hlt
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
# int 15
|
# int 15
|
||||||
|
|
||||||
|
TODO Seems to be outputting trash :-)
|
||||||
|
|
||||||
http://wiki.osdev.org/Detecting_Memory_%28x86%29
|
http://wiki.osdev.org/Detecting_Memory_%28x86%29
|
||||||
|
|
||||||
Determine how much memory you've got, and how much of it is low memory.
|
Determine how much memory you've got, and how much of it is low memory.
|
||||||
@@ -11,11 +13,21 @@ This is important in particular so that you can start your stack there
|
|||||||
when you enter protected mode, since the stack grows down.
|
when you enter protected mode, since the stack grows down.
|
||||||
|
|
||||||
In 16-bit mode, it does not matter much,
|
In 16-bit mode, it does not matter much,
|
||||||
since most modern machines have all addressable memory there.
|
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:
|
`int 15` returns a list:
|
||||||
each time you call it a new memory region is returned.
|
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
|
## Low memory
|
||||||
|
|
||||||
TODO what is it?
|
TODO what is it?
|
||||||
@@ -23,7 +35,74 @@ TODO what is it?
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
BEGIN
|
BEGIN
|
||||||
mov $0xE820, %eax
|
CLEAR
|
||||||
int $0x15
|
STAGE2
|
||||||
|
mov $output, %di
|
||||||
|
call do_e820
|
||||||
|
|
||||||
|
/* Debug aid. */
|
||||||
|
PRINT_WORD_HEX <%bp>
|
||||||
|
PRINT_NEWLINE
|
||||||
|
|
||||||
|
mov %bp, %ax
|
||||||
|
mov $0, %dx
|
||||||
|
/* Each entry is 24 bytes wide. */
|
||||||
|
mov $24, %cx
|
||||||
|
mul %cx
|
||||||
|
PRINT_BYTES $output, <%ax>
|
||||||
hlt
|
hlt
|
||||||
|
|
||||||
|
/*
|
||||||
|
This was copy pasted from:
|
||||||
|
http://wiki.osdev.org/Detecting_Memory_%28x86%29#Getting_an_E820_Memory_Map
|
||||||
|
|
||||||
|
use the INT 0x15, eax= 0xE820 BIOS function to get a memory map
|
||||||
|
inputs: es:di -> destination buffer for 24 byte entries
|
||||||
|
outputs: bp = entry count, trashes all registers except esi
|
||||||
|
*/
|
||||||
|
do_e820:
|
||||||
|
xorl %ebx,%ebx # ebx must be 0 to start
|
||||||
|
xorw %bp,%bp # keep an entry count in bp
|
||||||
|
movl $0x0534D4150,%edx # Place "SMAP" into edx
|
||||||
|
movl $0xe820,%eax
|
||||||
|
movl $1, %es:20(%di)
|
||||||
|
movl $24,%ecx # ask for 24 bytes
|
||||||
|
int $0x15
|
||||||
|
jc do_e820.failed # carry set on first call means "unsupported function"
|
||||||
|
movl $0x0534D4150,%edx # Some BIOSes apparently trash this register?
|
||||||
|
cmpl %edx,%eax # on success, eax must have been reset to "SMAP"
|
||||||
|
jne do_e820.failed
|
||||||
|
testl %ebx,%ebx # ebx = 0 implies list is only 1 entry long (worthless)
|
||||||
|
je do_e820.failed
|
||||||
|
jmp do_e820.jmpin
|
||||||
|
do_e820.e820lp:
|
||||||
|
movl $0xe820,%eax # eax, ecx get trashed on every int 0x15 call
|
||||||
|
movl $1, %es:20(%di)
|
||||||
|
movl $24,%ecx # ask for 24 bytes again
|
||||||
|
int $0x15
|
||||||
|
jc do_e820.e820f # carry set means "end of list already reached"
|
||||||
|
movl $0x0534D4150,%edx # repair potentially trashed register
|
||||||
|
do_e820.jmpin:
|
||||||
|
jcxz do_e820.skipent # skip any 0 length entries
|
||||||
|
cmpb $20,%cl # got a 24 byte ACPI 3.X response?
|
||||||
|
jbe do_e820.notext
|
||||||
|
testb $1, %es:29(%di)
|
||||||
|
je do_e820.skipent
|
||||||
|
do_e820.notext:
|
||||||
|
mov %ecx, %es:8(%di)
|
||||||
|
or %ecx, %es:12(%di)
|
||||||
|
jz do_e820.skipent # if length uint64_t is 0, skip entry
|
||||||
|
incw %bp # got a good entry: ++count, move to next storage spot
|
||||||
|
addw $24,%di
|
||||||
|
do_e820.skipent:
|
||||||
|
testl %ebx,%ebx # if ebx resets to 0, list is complete
|
||||||
|
jne do_e820.e820lp
|
||||||
|
do_e820.e820f:
|
||||||
|
#movw %bp,mmap_ent # store the entry count
|
||||||
|
clc # there is "jc" on end of list to this point, so the carry must be cleared
|
||||||
|
ret
|
||||||
|
do_e820.failed:
|
||||||
|
stc # "function unsupported" error exit
|
||||||
|
ret
|
||||||
|
|
||||||
|
output:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
Load one more sector from the disk
|
Load one more sector from the disk
|
||||||
besides the first 512 bytes and do something with it.
|
besides the first 512 bytes and do something with it.
|
||||||
|
|
||||||
Expected outcome: `@` gets printed to the screen.
|
Expected output: "a"
|
||||||
|
|
||||||
Grub 2.0 makes several calls to it under `grub-core/boot/i386/pc`
|
Grub 2.0 makes several calls to it under `grub-core/boot/i386/pc`
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ just after the magic bytes.
|
|||||||
*/
|
*/
|
||||||
.section .stage2
|
.section .stage2
|
||||||
stage2:
|
stage2:
|
||||||
PUTC($0x40)
|
PUTC $'a
|
||||||
hlt
|
hlt
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
BEGIN
|
BEGIN
|
||||||
CLEAR
|
CLEAR
|
||||||
STAGE2
|
STAGE2
|
||||||
PUTC($0x61)
|
PUTC $'a
|
||||||
jmp sector3
|
jmp sector3
|
||||||
.org 512
|
.org 512
|
||||||
sector3:
|
sector3:
|
||||||
PUTC($0x62)
|
PUTC $'b
|
||||||
hlt
|
hlt
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ http://stackoverflow.com/questions/4113250/how-to-handle-keyboard-in-real-mode-t
|
|||||||
BEGIN
|
BEGIN
|
||||||
mov $0x00, %ah
|
mov $0x00, %ah
|
||||||
int $0x16
|
int $0x16
|
||||||
PUTC(%al)
|
PUTC <%al>
|
||||||
hlt
|
hlt
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ BEGIN
|
|||||||
start:
|
start:
|
||||||
mov $0x00, %ah
|
mov $0x00, %ah
|
||||||
int $0x16
|
int $0x16
|
||||||
PUTC(%al)
|
PUTC <%al>
|
||||||
jmp start
|
jmp start
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Carriage returns are needed just like in old days.
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
BEGIN
|
BEGIN
|
||||||
PRINT($msg)
|
PRINT $msg
|
||||||
hlt
|
hlt
|
||||||
msg:
|
msg:
|
||||||
.asciz "hello\nworld"
|
.asciz "hello\nworld"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ which gets filled with the background color in bh.
|
|||||||
BEGIN
|
BEGIN
|
||||||
|
|
||||||
CLEAR
|
CLEAR
|
||||||
PRINT($stair)
|
PRINT $stair
|
||||||
|
|
||||||
/* Function ID. */
|
/* Function ID. */
|
||||||
mov $0x06, %ah
|
mov $0x06, %ah
|
||||||
|
|||||||
280
common.h
280
common.h
@@ -1,39 +1,124 @@
|
|||||||
/*
|
/*
|
||||||
Using macros for everything to make linking simpler.
|
Using macros for now instead of functions because it simplifies the linker script.
|
||||||
|
|
||||||
The big ones do bloat the executable.
|
But the downsides are severe:
|
||||||
|
|
||||||
## Calling convention
|
- 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.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
Every "function-like macro" should maintain 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 */
|
||||||
|
|
||||||
|
/* Push regiesters ax, bx, cx and dx. Lightweight `pusha`. */
|
||||||
|
.macro PUSH_ADX
|
||||||
|
push %ax
|
||||||
|
push %bx
|
||||||
|
push %cx
|
||||||
|
push %dx
|
||||||
|
.endm
|
||||||
|
|
||||||
|
/*
|
||||||
|
Pop registers dx, cx, bx, ax. Inverse order from PUSH_ADX,
|
||||||
|
so this cancels that one.
|
||||||
|
*/
|
||||||
|
.macro POP_DAX
|
||||||
|
pop %dx
|
||||||
|
pop %cx
|
||||||
|
pop %bx
|
||||||
|
pop %ax
|
||||||
|
.endm
|
||||||
|
|
||||||
|
/*
|
||||||
|
Convert the low nibble of a r8 reg to ASCII of 8-bit in-place.
|
||||||
|
reg: r8 to be converted
|
||||||
|
Output: stored in reg itself. Letters are uppercase.
|
||||||
|
*/
|
||||||
|
.macro HEX_NIBBLE reg
|
||||||
|
LOCAL letter, end
|
||||||
|
cmp $10, \reg
|
||||||
|
jae letter
|
||||||
|
add $'0, \reg
|
||||||
|
jmp end
|
||||||
|
letter:
|
||||||
|
/* 0x37 == 'A' - 10 */
|
||||||
|
add $0x37, \reg
|
||||||
|
end:
|
||||||
|
.endm
|
||||||
|
|
||||||
|
/*
|
||||||
|
Convert a byte to hex ASCII value.
|
||||||
|
c: r/m8 byte to be converted
|
||||||
|
Output: two ASCII characters, is stored in `ah:al`
|
||||||
|
http://stackoverflow.com/questions/3853730/printing-hexadecimal-digits-with-assembly
|
||||||
|
*/
|
||||||
|
.macro HEX c
|
||||||
|
mov \c, %al
|
||||||
|
mov \c, %ah
|
||||||
|
shr $4, %al
|
||||||
|
HEX_NIBBLE <%al>
|
||||||
|
and $0x0F, %ah
|
||||||
|
HEX_NIBBLE <%ah>
|
||||||
|
.endm
|
||||||
|
|
||||||
|
/* Structural. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
Setup a sane initial state.
|
||||||
|
|
||||||
|
Should be the first thing in every file.
|
||||||
|
*/
|
||||||
.macro BEGIN
|
.macro BEGIN
|
||||||
.code16
|
.code16
|
||||||
cli
|
cli
|
||||||
/* Set %cs to 0. TODO Is that really needed? */ ;\
|
/* Set %cs to 0. TODO Is that really needed? */
|
||||||
ljmp $0, $1f
|
ljmp $0, $1f
|
||||||
1:
|
1:
|
||||||
xor %ax, %ax
|
xor %ax, %ax
|
||||||
/* We must zero %ds for any data access. */ \
|
/* We must zero %ds for any data access. */
|
||||||
mov %ax, %ds
|
mov %ax, %ds
|
||||||
/* TODO is it really need to clear all those segment registers, e.g. for BIOS calls? */ \
|
/* TODO is it really need to clear all those segment registers, e.g. for BIOS calls? */
|
||||||
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? http://stackoverflow.com/questions/10598802/which-value-should-be-used-for-sp-for-booting-process */ \
|
/*
|
||||||
|
TODO What to move into BP and SP?
|
||||||
|
http://stackoverflow.com/questions/10598802/which-value-should-be-used-for-sp-for-booting-process
|
||||||
|
*/
|
||||||
mov 0x0000, %bp
|
mov 0x0000, %bp
|
||||||
/* Automatically disables interrupts until the end of the next instruction. */ \
|
/* Automatically disables interrupts until the end of the next instruction. */
|
||||||
mov %ax, %ss
|
mov %ax, %ss
|
||||||
/* We should set SP because BIOS calls may depend on that. TODO confirm. */ \
|
/* We should set SP because BIOS calls may depend on that. TODO confirm. */
|
||||||
mov %bp, %sp
|
mov %bp, %sp
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Load stage2 from disk to memory, and jump to it.
|
Load stage2 from disk to memory, and jump to it.
|
||||||
|
|
||||||
TODO not working.
|
|
||||||
|
|
||||||
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:
|
||||||
@@ -44,7 +129,7 @@ Sample usage:
|
|||||||
.macro STAGE2
|
.macro STAGE2
|
||||||
mov $2, %ah
|
mov $2, %ah
|
||||||
/*
|
/*
|
||||||
TODO get working on linker script.
|
TODO get working with linker script.
|
||||||
Above my paygrade for now, so I just load a bunch of sectors instead.
|
Above my paygrade for now, so I just load a bunch of sectors instead.
|
||||||
*/
|
*/
|
||||||
/* mov __stage2_size, %al;\ */
|
/* mov __stage2_size, %al;\ */
|
||||||
@@ -83,7 +168,16 @@ Use the simplest GDT possible.
|
|||||||
mov %eax, %cr0
|
mov %eax, %cr0
|
||||||
|
|
||||||
ljmp $CODE_SEG, $protected_mode
|
ljmp $CODE_SEG, $protected_mode
|
||||||
.code32
|
/*
|
||||||
|
Our GDT contains:
|
||||||
|
- 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
|
||||||
|
- 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,
|
||||||
|
allowing us to access anything without problems.
|
||||||
|
*/
|
||||||
gdt_start:
|
gdt_start:
|
||||||
gdt_null:
|
gdt_null:
|
||||||
.long 0x0
|
.long 0x0
|
||||||
@@ -108,6 +202,7 @@ gdt_descriptor:
|
|||||||
.long gdt_start
|
.long gdt_start
|
||||||
vga_current_line:
|
vga_current_line:
|
||||||
.long 0
|
.long 0
|
||||||
|
.code32
|
||||||
protected_mode:
|
protected_mode:
|
||||||
/*
|
/*
|
||||||
Setup the other segments.
|
Setup the other segments.
|
||||||
@@ -120,115 +215,103 @@ protected_mode:
|
|||||||
mov %ax, %fs
|
mov %ax, %fs
|
||||||
mov %ax, %gs
|
mov %ax, %gs
|
||||||
mov %ax, %ss
|
mov %ax, %ss
|
||||||
/* TODO detect memory properly. */
|
/*
|
||||||
|
TODO detect the last memory address available properly.
|
||||||
|
It depends on how much RAM we have.
|
||||||
|
*/
|
||||||
mov $0X7000, %ebp
|
mov $0X7000, %ebp
|
||||||
mov %ebp, %esp
|
mov %ebp, %esp
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
/* BIOS */
|
/* BIOS */
|
||||||
|
|
||||||
#define CURSOR_POSITION(x, y) \
|
.macro CURSOR_POSITION x=$0, y=$0
|
||||||
mov $0x02, %ah;\
|
PUSH_ADX
|
||||||
mov $0x00, %bh;\
|
mov $0x02, %ah
|
||||||
mov $0x ## x ## y, %dx;\
|
mov $0x00, %bh
|
||||||
|
mov \x, %dh
|
||||||
|
mov \y, %dl
|
||||||
int $0x10
|
int $0x10
|
||||||
|
POP_DAX
|
||||||
|
.endm
|
||||||
|
|
||||||
/* Clear the screen, move to position 0, 0. */
|
/* Clear the screen, move to position 0, 0. */
|
||||||
.macro CLEAR
|
.macro CLEAR
|
||||||
|
PUSH_ADX
|
||||||
mov $0x0600, %ax
|
mov $0x0600, %ax
|
||||||
mov $0x7, %bh
|
mov $0x7, %bh
|
||||||
mov $0x0, %cx
|
mov $0x0, %cx
|
||||||
mov $0x184f, %dx
|
mov $0x184f, %dx
|
||||||
int $0x10
|
int $0x10
|
||||||
CURSOR_POSITION(0, 0)
|
CURSOR_POSITION
|
||||||
|
POP_DAX
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Print a single immediate byte or 8 bit register.
|
Print a 8 bit ASCII value at current cursor position.
|
||||||
|
|
||||||
`c` is it's value in hex.
|
- c r/m/imm8 ASCII value to be printed.
|
||||||
|
|
||||||
Usage: character 'A' (ASCII 61):
|
Usage:
|
||||||
|
|
||||||
PUTS(61)
|
PUTC $'a
|
||||||
|
|
||||||
Clobbers: ax
|
prints `'a'` to the screen.
|
||||||
*/
|
*/
|
||||||
#define PUTC(c) \
|
.macro PUTC c=$0x20
|
||||||
mov $0x0E, %ah;\
|
push %ax
|
||||||
mov c, %al;\
|
|
||||||
int $0x10
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convert a byte to hex ASCII value.
|
|
||||||
c: r/m8 byte to be converted
|
|
||||||
Output: two ASCII characters, is stored in `ah:al`
|
|
||||||
http://stackoverflow.com/questions/3853730/printing-hexadecimal-digits-with-assembly
|
|
||||||
*/
|
|
||||||
#define HEX(c) GAS_HEX c
|
|
||||||
.macro GAS_HEX c
|
|
||||||
mov \c, %al
|
mov \c, %al
|
||||||
mov \c, %ah
|
mov $0x0E, %ah
|
||||||
shr $4, %al
|
int $0x10
|
||||||
GAS_HEX_NIBBLE al
|
pop %ax
|
||||||
and $0x0F, %ah
|
|
||||||
GAS_HEX_NIBBLE ah
|
|
||||||
.endm
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convert the low nibble of a r8 reg to ASCII of 8-bit in-place.
|
|
||||||
reg: r8 to be converted
|
|
||||||
Clobbered registers: none
|
|
||||||
Output: stored in reg itself. Letters are uppercase.
|
|
||||||
*/
|
|
||||||
.macro GAS_HEX_NIBBLE reg
|
|
||||||
LOCAL letter, end
|
|
||||||
cmp $10, %\reg
|
|
||||||
jae letter
|
|
||||||
/* 0x30 == '0' */
|
|
||||||
add $0x30, %\reg
|
|
||||||
jmp end
|
|
||||||
letter:
|
|
||||||
/* 0x37 == 'A' - 10 */
|
|
||||||
add $0x37, %\reg
|
|
||||||
end:
|
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Print a byte as two hexadecimal digits.
|
Print a byte as two hexadecimal digits.
|
||||||
|
|
||||||
reg: 1 byte register.
|
- reg: 1 byte register.
|
||||||
|
|
||||||
Clobbers: ax, dl
|
|
||||||
*/
|
*/
|
||||||
.macro PRINT_HEX reg
|
.macro PRINT_HEX reg=<%al>
|
||||||
HEX(<\reg>)
|
push %ax
|
||||||
mov %ah, %dl
|
HEX <\reg>
|
||||||
PUTC(%al)
|
PUTC <%al>
|
||||||
PUTC(%dl)
|
PUTC <%ah>
|
||||||
|
pop %ax
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
#define PRINT_NEWLINE \
|
/*
|
||||||
PUTC($0x0A);\
|
Print a 16-bit number
|
||||||
PUTC($0x0D)
|
|
||||||
|
- in: r/m/imm16
|
||||||
|
*/
|
||||||
|
.macro PRINT_WORD_HEX in=<%ax>
|
||||||
|
push %ax
|
||||||
|
mov \in, %ax
|
||||||
|
PRINT_HEX <%ah>
|
||||||
|
PRINT_HEX <%al>
|
||||||
|
pop %ax
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro PRINT_NEWLINE
|
||||||
|
PUTC $'\n
|
||||||
|
PUTC $'\r
|
||||||
|
.endm
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Print a null terminated string.
|
Print a null terminated string.
|
||||||
|
|
||||||
Use as:
|
Use as:
|
||||||
|
|
||||||
PRINT($s)
|
PRINT $s
|
||||||
hlt
|
hlt
|
||||||
s:
|
s:
|
||||||
.asciz "string"
|
.asciz "string"
|
||||||
|
|
||||||
We use this `cpp` macro to allow writing `PRINT(S)` with parenthesis.
|
|
||||||
*/
|
*/
|
||||||
#define PRINT(s) GAS_PRINT s
|
.macro PRINT s
|
||||||
.macro GAS_PRINT s
|
LOCAL end, loop
|
||||||
LOCAL loop, end
|
|
||||||
mov s, %si
|
mov s, %si
|
||||||
mov $0x0e, %ah
|
mov $0x0e, %ah
|
||||||
|
cld
|
||||||
loop:
|
loop:
|
||||||
lodsb
|
lodsb
|
||||||
or %al, %al
|
or %al, %al
|
||||||
@@ -238,6 +321,43 @@ loop:
|
|||||||
end:
|
end:
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump memory.
|
||||||
|
|
||||||
|
- s: starting address
|
||||||
|
- n: number of bytes to dump
|
||||||
|
*/
|
||||||
|
.macro PRINT_BYTES s, n=$16
|
||||||
|
LOCAL end, loop, no_newline
|
||||||
|
PUSH_ADX
|
||||||
|
push %di
|
||||||
|
mov s, %si
|
||||||
|
mov \n, %cx
|
||||||
|
mov $0, %di
|
||||||
|
cld
|
||||||
|
loop:
|
||||||
|
cmp $0, %cx
|
||||||
|
je end
|
||||||
|
dec %cx
|
||||||
|
lodsb
|
||||||
|
PRINT_HEX
|
||||||
|
PUTC
|
||||||
|
/* Print a newline for every 8 bytes. */
|
||||||
|
mov $0, %dx
|
||||||
|
mov %di, %ax
|
||||||
|
mov $8, %bx
|
||||||
|
div %bx
|
||||||
|
cmp $7, %dx
|
||||||
|
jne no_newline
|
||||||
|
PRINT_NEWLINE
|
||||||
|
no_newline:
|
||||||
|
inc %di
|
||||||
|
jmp loop
|
||||||
|
end:
|
||||||
|
pop %di
|
||||||
|
POP_DAX
|
||||||
|
.endm
|
||||||
|
|
||||||
/* VGA */
|
/* VGA */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -311,10 +431,10 @@ Expected output on screen:
|
|||||||
push $0
|
push $0
|
||||||
mov $2, %ebx
|
mov $2, %ebx
|
||||||
loop:
|
loop:
|
||||||
GAS_HEX <%cl>
|
HEX <%cl>
|
||||||
mov %ax, %dx
|
mov %ax, %dx
|
||||||
shl $16, %edx
|
shl $16, %edx
|
||||||
GAS_HEX <%ch>
|
HEX <%ch>
|
||||||
mov %ax, %dx
|
mov %ax, %dx
|
||||||
push %edx
|
push %edx
|
||||||
shr $16, %ecx
|
shr $16, %ecx
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Same as the kernel version.
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
BEGIN
|
BEGIN
|
||||||
start:
|
start:
|
||||||
PUTC($0x61)
|
PUTC $'a
|
||||||
|
|
||||||
mov $0xb6, %al
|
mov $0xb6, %al
|
||||||
out %al, $0x43
|
out %al, $0x43
|
||||||
|
|||||||
4
in_pit.S
4
in_pit.S
@@ -103,7 +103,7 @@ BEGIN
|
|||||||
mov %ah, %al
|
mov %ah, %al
|
||||||
out %al, $0x40
|
out %al, $0x40
|
||||||
|
|
||||||
PUTC($0x61)
|
PUTC $'a
|
||||||
sti
|
sti
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
@@ -111,7 +111,7 @@ loop:
|
|||||||
jmp loop
|
jmp loop
|
||||||
|
|
||||||
handler:
|
handler:
|
||||||
PUTC($0x61)
|
PUTC $'a
|
||||||
iret
|
iret
|
||||||
|
|
||||||
/* TODO turn off. */
|
/* TODO turn off. */
|
||||||
|
|||||||
10
in_rtc.S
10
in_rtc.S
@@ -51,35 +51,35 @@ update_in_progress:
|
|||||||
mov %al, %cl
|
mov %al, %cl
|
||||||
|
|
||||||
PRINT_HEX <%al>
|
PRINT_HEX <%al>
|
||||||
PUTC($0x20)
|
PUTC
|
||||||
|
|
||||||
/* Minute. */
|
/* Minute. */
|
||||||
mov $0x02, %al
|
mov $0x02, %al
|
||||||
out %al, $RTCaddress
|
out %al, $RTCaddress
|
||||||
in $RTCdata, %al
|
in $RTCdata, %al
|
||||||
PRINT_HEX <%al>
|
PRINT_HEX <%al>
|
||||||
PUTC($0x20)
|
PUTC
|
||||||
|
|
||||||
/* Hour. */
|
/* Hour. */
|
||||||
mov $0x04, %al
|
mov $0x04, %al
|
||||||
out %al, $RTCaddress
|
out %al, $RTCaddress
|
||||||
in $RTCdata, %al
|
in $RTCdata, %al
|
||||||
PRINT_HEX <%al>
|
PRINT_HEX <%al>
|
||||||
PUTC($0x20)
|
PUTC
|
||||||
|
|
||||||
/* Day. */
|
/* Day. */
|
||||||
mov $0x07, %al
|
mov $0x07, %al
|
||||||
out %al, $RTCaddress
|
out %al, $RTCaddress
|
||||||
in $RTCdata, %al
|
in $RTCdata, %al
|
||||||
PRINT_HEX <%al>
|
PRINT_HEX <%al>
|
||||||
PUTC($0x20)
|
PUTC
|
||||||
|
|
||||||
/* Month. */
|
/* Month. */
|
||||||
mov $0x08, %al
|
mov $0x08, %al
|
||||||
out %al, $RTCaddress
|
out %al, $RTCaddress
|
||||||
in $RTCdata, %al
|
in $RTCdata, %al
|
||||||
PRINT_HEX <%al>
|
PRINT_HEX <%al>
|
||||||
PUTC($0x20)
|
PUTC
|
||||||
|
|
||||||
/* Year. */
|
/* Year. */
|
||||||
mov $0x09, %al
|
mov $0x09, %al
|
||||||
|
|||||||
@@ -6,52 +6,35 @@ Could be done with GDB on the emulator, but this will also work on real hardware
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#define INITIAL_STORE(x) mov % ## x, x
|
.macro INITIAL_STORE x
|
||||||
|
mov %\()\x, \x
|
||||||
|
.endm
|
||||||
|
|
||||||
#define INITIAL_DATA(x) \
|
.macro INITIAL_DATA x
|
||||||
x: .word 0;\
|
\x: .word 0
|
||||||
x ## s: .ascii #x " = \0"
|
\x\()s: .ascii "\x = \0"
|
||||||
|
.endm
|
||||||
|
|
||||||
#define INITIAL_PRINT(x) \
|
.macro INITIAL_PRINT x
|
||||||
PRINT($x ## s);\
|
PRINT $\x\()s
|
||||||
PRINT_HEX <x>;\
|
PRINT_HEX <\x>
|
||||||
PRINT_NEWLINE
|
PRINT_NEWLINE
|
||||||
|
.endm
|
||||||
|
|
||||||
INITIAL_STORE(ax)
|
.irp reg, bx, cx, dx, cs, ds, es, fs, gs, ss
|
||||||
INITIAL_STORE(bx)
|
INITIAL_STORE \reg
|
||||||
INITIAL_STORE(cx)
|
.endr
|
||||||
INITIAL_STORE(dx)
|
|
||||||
INITIAL_STORE(cs)
|
|
||||||
INITIAL_STORE(ds)
|
|
||||||
INITIAL_STORE(es)
|
|
||||||
INITIAL_STORE(fs)
|
|
||||||
INITIAL_STORE(gs)
|
|
||||||
INITIAL_STORE(ss)
|
|
||||||
|
|
||||||
BEGIN
|
BEGIN
|
||||||
|
|
||||||
STAGE2
|
STAGE2
|
||||||
|
|
||||||
INITIAL_PRINT(ax)
|
.irp reg, bx, cx, dx, cs, ds, es, fs, gs, ss
|
||||||
INITIAL_PRINT(bx)
|
INITIAL_PRINT \reg
|
||||||
INITIAL_PRINT(cx)
|
.endr
|
||||||
INITIAL_PRINT(dx)
|
|
||||||
INITIAL_PRINT(cs)
|
|
||||||
INITIAL_PRINT(ds)
|
|
||||||
INITIAL_PRINT(es)
|
|
||||||
INITIAL_PRINT(fs)
|
|
||||||
INITIAL_PRINT(gs)
|
|
||||||
INITIAL_PRINT(ss)
|
|
||||||
|
|
||||||
hlt
|
hlt
|
||||||
|
|
||||||
INITIAL_DATA(ax)
|
.irp reg, bx, cx, dx, cs, ds, es, fs, gs, ss
|
||||||
INITIAL_DATA(bx)
|
INITIAL_DATA \reg
|
||||||
INITIAL_DATA(cx)
|
.endr
|
||||||
INITIAL_DATA(dx)
|
|
||||||
INITIAL_DATA(cs)
|
|
||||||
INITIAL_DATA(ds)
|
|
||||||
INITIAL_DATA(es)
|
|
||||||
INITIAL_DATA(fs)
|
|
||||||
INITIAL_DATA(gs)
|
|
||||||
INITIAL_DATA(ss)
|
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ BEGIN
|
|||||||
movw $handler, 0x00
|
movw $handler, 0x00
|
||||||
mov %cs, 0x02
|
mov %cs, 0x02
|
||||||
int $0
|
int $0
|
||||||
PUTC($0x62)
|
PUTC $'b
|
||||||
hlt
|
hlt
|
||||||
handler:
|
handler:
|
||||||
PUTC($0x61)
|
PUTC $'a
|
||||||
iret
|
iret
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ BEGIN
|
|||||||
movw $handler, 0x04
|
movw $handler, 0x04
|
||||||
mov %cs, 0x06
|
mov %cs, 0x06
|
||||||
int $1
|
int $1
|
||||||
PUTC($0x62)
|
PUTC $'b
|
||||||
hlt
|
hlt
|
||||||
handler:
|
handler:
|
||||||
PUTC($0x61)
|
PUTC $'a
|
||||||
iret
|
iret
|
||||||
|
|||||||
@@ -15,6 +15,6 @@ BEGIN
|
|||||||
loop:
|
loop:
|
||||||
jmp loop
|
jmp loop
|
||||||
handler:
|
handler:
|
||||||
PUTC($0x61)
|
PUTC $'a
|
||||||
iret
|
iret
|
||||||
jmp loop
|
jmp loop
|
||||||
|
|||||||
@@ -8,5 +8,5 @@ BEGIN
|
|||||||
mov %cs, 0x02
|
mov %cs, 0x02
|
||||||
int $0
|
int $0
|
||||||
handler:
|
handler:
|
||||||
PUTC($0x61)
|
PUTC $'a
|
||||||
int $0
|
int $0
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ BEGIN
|
|||||||
div %ax
|
div %ax
|
||||||
jmp fail
|
jmp fail
|
||||||
handler:
|
handler:
|
||||||
PUTC($0x61)
|
PUTC $'a
|
||||||
fail:
|
fail:
|
||||||
hlt
|
hlt
|
||||||
|
|||||||
4
lidt.S
4
lidt.S
@@ -26,7 +26,7 @@ BEGIN
|
|||||||
mov %cs, 0x06
|
mov %cs, 0x06
|
||||||
|
|
||||||
int $0
|
int $0
|
||||||
PUTC($0x62)
|
PUTC $'b
|
||||||
hlt
|
hlt
|
||||||
|
|
||||||
idt:
|
idt:
|
||||||
@@ -39,5 +39,5 @@ idt_descriptor:
|
|||||||
.long idt
|
.long idt
|
||||||
|
|
||||||
handler:
|
handler:
|
||||||
PUTC($0x61)
|
PUTC $'a
|
||||||
iret
|
iret
|
||||||
|
|||||||
@@ -5,3 +5,5 @@ While NASM is a bit more convenient than GAS to write a boot sector, I think 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.
|
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.
|
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.
|
||||||
|
|||||||
@@ -81,34 +81,34 @@ BEGIN
|
|||||||
mov $1, %ax
|
mov $1, %ax
|
||||||
mov %ax, %ds
|
mov %ax, %ds
|
||||||
mov %ds:msg, %al
|
mov %ds:msg, %al
|
||||||
PUTC(%al)
|
PUTC <%al>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
%ds is the default segment for GAS memory operations
|
%ds is the default segment for GAS memory operations
|
||||||
if we don't write it explicitly.
|
if we don't write it explicitly.
|
||||||
*/
|
*/
|
||||||
mov msg, %al
|
mov msg, %al
|
||||||
PUTC(%al)
|
PUTC <%al>
|
||||||
|
|
||||||
mov $1, %ax
|
mov $1, %ax
|
||||||
mov %ax, %es
|
mov %ax, %es
|
||||||
mov %es:msg, %al
|
mov %es:msg, %al
|
||||||
PUTC(%al)
|
PUTC <%al>
|
||||||
|
|
||||||
mov $1, %ax
|
mov $1, %ax
|
||||||
mov %ax, %fs
|
mov %ax, %fs
|
||||||
mov %fs:msg, %al
|
mov %fs:msg, %al
|
||||||
PUTC(%al)
|
PUTC <%al>
|
||||||
|
|
||||||
mov $1, %ax
|
mov $1, %ax
|
||||||
mov %ax, %gs
|
mov %ax, %gs
|
||||||
mov %gs:msg, %al
|
mov %gs:msg, %al
|
||||||
PUTC(%al)
|
PUTC <%al>
|
||||||
|
|
||||||
mov $1, %ax
|
mov $1, %ax
|
||||||
mov %ax, %ss
|
mov %ax, %ss
|
||||||
mov %ss:msg, %al
|
mov %ss:msg, %al
|
||||||
PUTC(%al)
|
PUTC <%al>
|
||||||
|
|
||||||
hlt
|
hlt
|
||||||
msg:
|
msg:
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
BEGIN
|
BEGIN
|
||||||
hlt
|
hlt
|
||||||
|
|||||||
17
test_print_bytes.S
Normal file
17
test_print_bytes.S
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
Test PRINT_BYTES
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
|
||||||
|
40 41 42 43 44 45 46 47
|
||||||
|
48 49 4A 4B 4C 4D 4E 4F
|
||||||
|
50
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
BEGIN
|
||||||
|
CLEAR
|
||||||
|
PRINT_BYTES $s, $17
|
||||||
|
hlt
|
||||||
|
s:
|
||||||
|
.ascii "@ABCDEFGHIJKLMNOP"
|
||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
Successor for BIOS.
|
Successor for BIOS.
|
||||||
|
|
||||||
|
Made by Intel, mostly MIT open source, but vendors do modify it.
|
||||||
|
|
||||||
|
Matthew Garrett says it is huge: larger than Linux without drivers. Like BIOS, it is a "mini-OS".
|
||||||
|
|
||||||
|
Since it is huge, it inevitably contains bugs. Garret says that Intel sometimes does not feel like updating the firmware with bugfixes.
|
||||||
|
|
||||||
|
ARM is considering an implementation <https://wiki.linaro.org/ARM/UEFI>
|
||||||
|
|
||||||
TODO get a hello world program working:
|
TODO get a hello world program working:
|
||||||
|
|
||||||
- http://www.rodsbooks.com/efi-programming/hello.html Best source so far: allowed me to compile the hello world! TODO: how to run it now on QEMU and real hardware?
|
- http://www.rodsbooks.com/efi-programming/hello.html Best source so far: allowed me to compile the hello world! TODO: how to run it now on QEMU and real hardware?
|
||||||
|
|||||||
Reference in New Issue
Block a user