Segment registers
This commit is contained in:
@@ -19,6 +19,8 @@ Hello world programs that run without an operating system.
|
||||
1. [clear screen](bios_clear_screen.S)
|
||||
1. [pixel](bios_pixel.S)
|
||||
1. [pixel line](bios_pixel_line.S)
|
||||
1. [Not testable in userland](not-testable-in-userland.md)
|
||||
1. [Segment registers real mode](segment_registers_real_mode.S)
|
||||
1. APM
|
||||
1. [APM shutdown](apm_shutdown.S)
|
||||
1. [APM shutdown 2](apm_shutdown2.S)
|
||||
|
||||
14
TODO.md
14
TODO.md
@@ -1,13 +1,23 @@
|
||||
# TODO
|
||||
|
||||
- Segment registers: http://stackoverflow.com/questions/30549526/c-kernel-works-fine-on-vm-but-not-actual-computer?rq=1
|
||||
- Segment registers on protected mode.
|
||||
|
||||
- http://stackoverflow.com/questions/4903906/assembly-using-the-data-segment-register-ds answer with minimal example and mention QEMU vs real hardwre http://wiki.osdev.org/Segmentation
|
||||
- http://stackoverflow.com/questions/3819699/what-does-ds40207a-mean-in-assembly
|
||||
- http://stackoverflow.com/questions/5364270/concept-of-mov-ax-cs-and-mov-ds-ax?lq=1
|
||||
- http://stackoverflow.com/questions/518261/meaning-of-x86-assembler-instruction?lq=1
|
||||
- http://reverseengineering.stackexchange.com/questions/2006/how-are-the-segment-registers-fs-gs-cs-ss-ds-es-used-in-linux
|
||||
|
||||
- move to 32 bit mode. Answer http://stackoverflow.com/questions/7130726/writing-a-hello-world-kernel
|
||||
|
||||
- load a second stage from disk into memory
|
||||
- load a stage 2 from disk into memory
|
||||
|
||||
- http://stackoverflow.com/questions/7716427/loading-2nd-stage-of-bootloader-and-starting-it
|
||||
- http://stackoverflow.com/questions/2065370/how-to-load-second-stage-boot-loader-from-first-stage
|
||||
|
||||
- interrupt
|
||||
|
||||
- timer
|
||||
|
||||
- BIOS
|
||||
|
||||
|
||||
6
bios.md
6
bios.md
@@ -43,6 +43,12 @@ Taken from: <https://courses.engr.illinois.edu/ece390/books/labmanual/graphics-i
|
||||
|
||||
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
|
||||
|
||||
@@ -5,7 +5,7 @@ mov $0x0B, %ah
|
||||
mov $0x0034, %bx
|
||||
int $0x10
|
||||
|
||||
PUTC(61)
|
||||
PUTC($0x61)
|
||||
|
||||
hlt
|
||||
END
|
||||
|
||||
@@ -11,15 +11,7 @@ Carriage returns are needed just like in old days.
|
||||
|
||||
#include "common.h"
|
||||
BEGIN
|
||||
mov $msg, %si
|
||||
mov $0x0e, %ah
|
||||
loop:
|
||||
lodsb
|
||||
or %al, %al
|
||||
jz halt
|
||||
int $0x10
|
||||
jmp loop
|
||||
halt:
|
||||
PRINT($msg)
|
||||
hlt
|
||||
msg:
|
||||
.asciz "hello\n\rworld"
|
||||
|
||||
@@ -8,7 +8,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.
|
||||
*/
|
||||
PUTC(61)
|
||||
PUTC($0x61)
|
||||
|
||||
/* Scroll 0 is magic, and scrolls the entire selected rectangle. */
|
||||
mov $0x0600, %ax
|
||||
@@ -30,7 +30,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.
|
||||
*/
|
||||
CURSOR_POSITION(0, 0)
|
||||
PUTC(62)
|
||||
PUTC($0x62)
|
||||
|
||||
hlt
|
||||
END
|
||||
|
||||
@@ -25,8 +25,8 @@ 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'.
|
||||
*/
|
||||
PUTC(62)
|
||||
PUTC(63)
|
||||
PUTC($0x62)
|
||||
PUTC($0x63)
|
||||
|
||||
hlt
|
||||
END
|
||||
|
||||
@@ -3,8 +3,8 @@ BEGIN
|
||||
CLEAR
|
||||
|
||||
/* Print "ab" */
|
||||
PUTC(61)
|
||||
PUTC(62)
|
||||
PUTC($0x61)
|
||||
PUTC($0x62)
|
||||
|
||||
/* Move back to 0, 0.*/
|
||||
mov $0x02, %ah
|
||||
@@ -15,7 +15,7 @@ mov $0x0, %dx
|
||||
int $0x10
|
||||
|
||||
/* Overwrite 'a' with c'. */
|
||||
PUTC(63)
|
||||
PUTC($0x63)
|
||||
|
||||
hlt
|
||||
END
|
||||
|
||||
@@ -11,15 +11,7 @@ Carriage returns are needed just like in old days.
|
||||
|
||||
#include "common.h"
|
||||
BEGIN
|
||||
mov $msg, %si
|
||||
mov $0x0e, %ah
|
||||
loop:
|
||||
lodsb
|
||||
or %al, %al
|
||||
jz halt
|
||||
int $0x10
|
||||
jmp loop
|
||||
halt:
|
||||
PRINT($msg)
|
||||
hlt
|
||||
msg:
|
||||
.asciz "hello\nworld"
|
||||
|
||||
@@ -38,13 +38,13 @@ BEGIN
|
||||
|
||||
/* Print staircase. */
|
||||
CLEAR
|
||||
PUTC(61)
|
||||
PUTC(0A)
|
||||
PUTC(62)
|
||||
PUTC(0A)
|
||||
PUTC(63)
|
||||
PUTC(0A)
|
||||
PUTC(64)
|
||||
PUTC($0x61)
|
||||
PUTC($0x0A)
|
||||
PUTC($0x62)
|
||||
PUTC($0x0A)
|
||||
PUTC($0x63)
|
||||
PUTC($0x0A)
|
||||
PUTC($0x64)
|
||||
|
||||
/* Function ID. */
|
||||
mov $0x06, %ah
|
||||
|
||||
44
common.h
44
common.h
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
Using macros for everything to make linking simpler.
|
||||
|
||||
The big ones do bloat the executable.
|
||||
*/
|
||||
|
||||
.altmacro
|
||||
|
||||
#define BEGIN \
|
||||
.code16;\
|
||||
cli;\
|
||||
@@ -22,10 +30,42 @@
|
||||
CURSOR_POSITION(0, 0)
|
||||
|
||||
/*
|
||||
Print a single character.
|
||||
Print a single immediate byte or 8 bit register.
|
||||
|
||||
`c` is it's value in hex.
|
||||
|
||||
Usage: character 'A' (ASCII 61):
|
||||
|
||||
PUTS(61)
|
||||
*/
|
||||
#define PUTC(c) \
|
||||
mov $0x0E ## c, %ax;\
|
||||
mov $0x0E, %ah;\
|
||||
mov c, %al;\
|
||||
int $0x10
|
||||
|
||||
/*
|
||||
Print a null terminated string.
|
||||
|
||||
Use as:
|
||||
|
||||
PRINT($s)
|
||||
hlt
|
||||
s:
|
||||
.asciz "string"
|
||||
|
||||
We use this `cpp` macro to allow writing `PRINT(S)` with parenthesis.
|
||||
*/
|
||||
#define PRINT(s) GAS_PRINT s
|
||||
/* We need a Gas macro for the LOCAL labels. */
|
||||
.macro GAS_PRINT s
|
||||
LOCAL halt, loop
|
||||
mov s, %si
|
||||
mov $0x0e, %ah
|
||||
loop:
|
||||
lodsb
|
||||
or %al, %al
|
||||
jz halt
|
||||
int $0x10
|
||||
jmp loop
|
||||
halt:
|
||||
.endm
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
/* Check if the compiler thinks we are targeting the wrong operating system. */
|
||||
#if defined(__linux__)
|
||||
/* TODO what is this check? It was failing. */
|
||||
/* TODO how does this check for a cross compiler? It was failing. */
|
||||
/*#error "You are not using a cross-compiler, you will most certainly run into trouble"*/
|
||||
#endif
|
||||
|
||||
@@ -17,39 +17,39 @@
|
||||
|
||||
/* Hardware text mode color constants. */
|
||||
enum vga_color {
|
||||
COLOR_BLACK = 0,
|
||||
COLOR_BLUE = 1,
|
||||
COLOR_GREEN = 2,
|
||||
COLOR_CYAN = 3,
|
||||
COLOR_RED = 4,
|
||||
COLOR_MAGENTA = 5,
|
||||
COLOR_BROWN = 6,
|
||||
COLOR_LIGHT_GREY = 7,
|
||||
COLOR_DARK_GREY = 8,
|
||||
COLOR_LIGHT_BLUE = 9,
|
||||
COLOR_LIGHT_GREEN = 10,
|
||||
COLOR_LIGHT_CYAN = 11,
|
||||
COLOR_LIGHT_RED = 12,
|
||||
COLOR_LIGHT_MAGENTA = 13,
|
||||
COLOR_LIGHT_BROWN = 14,
|
||||
COLOR_WHITE = 15,
|
||||
COLOR_BLACK = 0,
|
||||
COLOR_BLUE = 1,
|
||||
COLOR_GREEN = 2,
|
||||
COLOR_CYAN = 3,
|
||||
COLOR_RED = 4,
|
||||
COLOR_MAGENTA = 5,
|
||||
COLOR_BROWN = 6,
|
||||
COLOR_LIGHT_GREY = 7,
|
||||
COLOR_DARK_GREY = 8,
|
||||
COLOR_LIGHT_BLUE = 9,
|
||||
COLOR_LIGHT_GREEN = 10,
|
||||
COLOR_LIGHT_CYAN = 11,
|
||||
COLOR_LIGHT_RED = 12,
|
||||
COLOR_LIGHT_MAGENTA = 13,
|
||||
COLOR_LIGHT_BROWN = 14,
|
||||
COLOR_WHITE = 15,
|
||||
};
|
||||
|
||||
uint8_t make_color(enum vga_color fg, enum vga_color bg) {
|
||||
return fg | bg << 4;
|
||||
return fg | bg << 4;
|
||||
}
|
||||
|
||||
uint16_t make_vgaentry(char c, uint8_t color) {
|
||||
uint16_t c16 = c;
|
||||
uint16_t color16 = color;
|
||||
return c16 | color16 << 8;
|
||||
uint16_t c16 = c;
|
||||
uint16_t color16 = color;
|
||||
return c16 | color16 << 8;
|
||||
}
|
||||
|
||||
size_t strlen(const char* str) {
|
||||
size_t ret = 0;
|
||||
while ( str[ret] != 0 )
|
||||
ret++;
|
||||
return ret;
|
||||
size_t ret = 0;
|
||||
while ( str[ret] != 0 )
|
||||
ret++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const size_t VGA_WIDTH = 80;
|
||||
@@ -61,53 +61,53 @@ uint8_t terminal_color;
|
||||
uint16_t* terminal_buffer;
|
||||
|
||||
void terminal_initialize() {
|
||||
terminal_row = 0;
|
||||
terminal_column = 0;
|
||||
terminal_color = make_color(COLOR_LIGHT_GREY, COLOR_BLACK);
|
||||
terminal_buffer = (uint16_t*) 0xB8000;
|
||||
for (size_t y = 0; y < VGA_HEIGHT; y++) {
|
||||
for (size_t x = 0; x < VGA_WIDTH; x++) {
|
||||
const size_t index = y * VGA_WIDTH + x;
|
||||
terminal_buffer[index] = make_vgaentry(' ', terminal_color);
|
||||
}
|
||||
}
|
||||
terminal_row = 0;
|
||||
terminal_column = 0;
|
||||
terminal_color = make_color(COLOR_LIGHT_GREY, COLOR_BLACK);
|
||||
terminal_buffer = (uint16_t*) 0xB8000;
|
||||
for (size_t y = 0; y < VGA_HEIGHT; y++) {
|
||||
for (size_t x = 0; x < VGA_WIDTH; x++) {
|
||||
const size_t index = y * VGA_WIDTH + x;
|
||||
terminal_buffer[index] = make_vgaentry(' ', terminal_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void terminal_setcolor(uint8_t color) {
|
||||
terminal_color = color;
|
||||
terminal_color = color;
|
||||
}
|
||||
|
||||
void terminal_putentryat(char c, uint8_t color, size_t x, size_t y) {
|
||||
const size_t index = y * VGA_WIDTH + x;
|
||||
terminal_buffer[index] = make_vgaentry(c, color);
|
||||
const size_t index = y * VGA_WIDTH + x;
|
||||
terminal_buffer[index] = make_vgaentry(c, color);
|
||||
}
|
||||
|
||||
void terminal_putchar(char c) {
|
||||
terminal_putentryat(c, terminal_color, terminal_column, terminal_row);
|
||||
if (++terminal_column == VGA_WIDTH) {
|
||||
terminal_column = 0;
|
||||
if (++terminal_row == VGA_HEIGHT) {
|
||||
terminal_row = 0;
|
||||
}
|
||||
}
|
||||
terminal_putentryat(c, terminal_color, terminal_column, terminal_row);
|
||||
if (++terminal_column == VGA_WIDTH) {
|
||||
terminal_column = 0;
|
||||
if (++terminal_row == VGA_HEIGHT) {
|
||||
terminal_row = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void terminal_writestring(const char* data) {
|
||||
size_t datalen = strlen(data);
|
||||
for (size_t i = 0; i < datalen; i++)
|
||||
terminal_putchar(data[i]);
|
||||
size_t datalen = strlen(data);
|
||||
for (size_t i = 0; i < datalen; i++)
|
||||
terminal_putchar(data[i]);
|
||||
}
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" /* Use C linkage for kernel_main. */
|
||||
#endif
|
||||
void kernel_main() {
|
||||
/* Initialize terminal interface */
|
||||
terminal_initialize();
|
||||
/* Initialize terminal interface */
|
||||
terminal_initialize();
|
||||
|
||||
/* Since there is no support for newlines in terminal_putchar
|
||||
* yet, '\n' will produce some VGA specific character instead.
|
||||
* This is normal.
|
||||
*/
|
||||
terminal_writestring("Hello, kernel World!\n");
|
||||
/* Since there is no support for newlines in terminal_putchar
|
||||
* yet, '\n' will produce some VGA specific character instead.
|
||||
* This is normal.
|
||||
*/
|
||||
terminal_writestring("Hello, kernel World!\n");
|
||||
}
|
||||
|
||||
12
not-testable-in-userland.md
Normal file
12
not-testable-in-userland.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Not testable in userland
|
||||
|
||||
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>
|
||||
109
segment_registers_real_mode.S
Normal file
109
segment_registers_real_mode.S
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
# Segment registers real mde
|
||||
|
||||
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.
|
||||
|
||||
In protected mode, they do very different things:
|
||||
|
||||
- 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
|
||||
|
||||
FS and GS are general purpose: they don't generate or are affected implicitly by instructions.
|
||||
|
||||
The special semantics of other registers will be covered in other files.
|
||||
|
||||
## 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
|
||||
29: 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 if we don't write it. */
|
||||
mov msg, %al
|
||||
PUTC(%al)
|
||||
|
||||
/* Try using other segment as well. */
|
||||
|
||||
/*
|
||||
CS is the exception: if we do this, the program halts. TODO why?
|
||||
|
||||
Some info: http://wiki.osdev.org/Segmentation#Operations_that_affect_segment_registers
|
||||
*/
|
||||
/*
|
||||
mov $1, %ax
|
||||
mov %ax, %cs
|
||||
mov %cs: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'
|
||||
END
|
||||
Reference in New Issue
Block a user