Failed attempt at modifying a segment descriptor, split GDB to separate file
This commit is contained in:
16
Makefile
16
Makefile
@@ -1,5 +1,6 @@
|
||||
.POSIX:
|
||||
|
||||
COMMON ?= common.h
|
||||
LD ?= ld
|
||||
LINKER_SCRIPT ?= linker.ld
|
||||
# Use gcc so that the preprocessor will run first.
|
||||
@@ -21,14 +22,19 @@ RUN_FILE := $(RUN)$(OUT_EXT)
|
||||
all: $(OUTS)
|
||||
|
||||
%$(OUT_EXT): %$(OBJ_EXT) $(LINKER_SCRIPT)
|
||||
@# Failed attempt at getting debug symbols.
|
||||
@#$(LD) -melf_i386 -o '$(@:$(OUT_EXT)=.elf)' -T '$(LINKER_SCRIPT)' '$<'
|
||||
$(LD) --oformat binary -o '$@' -T '$(LINKER_SCRIPT)' '$<'
|
||||
|
||||
%$(OBJ_EXT): %$(GAS_EXT)
|
||||
$(GAS) -c -o '$@' '$<'
|
||||
%$(OBJ_EXT): %$(GAS_EXT) $(COMMON)
|
||||
$(GAS) -c -g -o '$@' '$<'
|
||||
|
||||
%$(OUT_EXT): %$(NASM_EXT)
|
||||
nasm -f bin -o '$@' '$<'
|
||||
|
||||
# So that directories without a common.h can reuse this.
|
||||
$(COMMON):
|
||||
|
||||
clean:
|
||||
rm -fr *$(OBJ_EXT) *$(OUT_EXT) *$(TMP_EXT)
|
||||
|
||||
@@ -37,11 +43,7 @@ run: all
|
||||
|
||||
debug: all
|
||||
$(QEMU) -hda '$(RUN_FILE)' -S -s &
|
||||
gdb \
|
||||
-ex 'target remote localhost:1234' \
|
||||
-ex 'set architecture i8086' \
|
||||
-ex 'break *0x7c00' \
|
||||
-ex 'continue'
|
||||
gdb -x gdb.gdb
|
||||
|
||||
bochs: all
|
||||
# Supposes size is already multiples of 512.
|
||||
|
||||
@@ -41,9 +41,8 @@ Hello world programs that run without an operating system.
|
||||
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. [Segmentation](segmentation.md)
|
||||
1. Segmentation offset
|
||||
1. [Protected mode](protected_mode.S)
|
||||
1. [Segment base](segment_base.S)
|
||||
1. Segmentation fault handler: memory bound, ring, RWX violations
|
||||
1. APM
|
||||
1. [APM shutdown](apm_shutdown.S)
|
||||
@@ -56,11 +55,13 @@ Hello world programs that run without an operating system.
|
||||
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. [Debug](debug.md)
|
||||
1. [Bibliography](bibliography.md)
|
||||
1. [TODO](TODO.md)
|
||||
|
||||
|
||||
40
TODO.md
40
TODO.md
@@ -1,10 +1,5 @@
|
||||
# TODO
|
||||
|
||||
- exemplify all CPU modes
|
||||
|
||||
- move to 32 bit mode. Answer http://stackoverflow.com/questions/7130726/writing-a-hello-world-kernel
|
||||
- http://stackoverflow.com/questions/20848412/modes-of-intel-64-cpu
|
||||
|
||||
- cache: wbinvd
|
||||
|
||||
- inb outb
|
||||
@@ -18,7 +13,7 @@
|
||||
- http://stackoverflow.com/questions/8894241/real-mode-x86-asm-how-are-the-basics-done?rq=1
|
||||
- http://wiki.osdev.org/Text_UI
|
||||
|
||||
Port 0x80 on Linux kenrnel:
|
||||
Port 0x80 on Linux kernel:
|
||||
|
||||
- https://github.com/torvalds/linux/blob/v4.2/arch/x86/boot/boot.h#L78
|
||||
- http://stackoverflow.com/questions/6793899/what-does-the-0x80-port-address-connects
|
||||
@@ -31,21 +26,22 @@
|
||||
- https://en.wikipedia.org/wiki/Double_fault
|
||||
- https://en.wikipedia.org/wiki/Triple_fault
|
||||
|
||||
timer, IPT
|
||||
|
||||
- paging
|
||||
|
||||
- WRMSR https://en.wikipedia.org/wiki/Model-specific_register http://x86.renejeschke.de/html/file_module_x86_id_326.html
|
||||
- Segment registers /segmentation and protected mode. Then try to answer all of: GDT
|
||||
|
||||
- Segment registers /segmentation and protected mode. Then try to answer all of:
|
||||
|
||||
http://stackoverflow.com/questions/18736663/what-does-the-colon-mean-in-x86-assembly-gas-syntax-as-in-dsbx
|
||||
- http://reverseengineering.stackexchange.com/questions/2006/how-are-the-segment-registers-fs-gs-cs-ss-ds-es-used-in-linux
|
||||
- http://stackoverflow.com/questions/10810203/what-is-the-fs-gs-register-intended-for
|
||||
- http://stackoverflow.com/questions/11540104/designing-gdt-for-a-small-kernel
|
||||
- http://stackoverflow.com/questions/12760109/data-segment-in-x86-programs
|
||||
- http://stackoverflow.com/questions/14480579/when-does-segment-registers-change
|
||||
- http://stackoverflow.com/questions/14661916/gdt-segmented-memory
|
||||
- http://stackoverflow.com/questions/15335003/x86-protected-mode-segment-registers-purpose
|
||||
- http://stackoverflow.com/questions/17210620/assembler-calculating-a-memory-address-with-register-base?lq=1
|
||||
- http://stackoverflow.com/questions/18247106/implementing-gdt-with-basic-kernel
|
||||
- http://stackoverflow.com/questions/18736663/what-does-the-colon-mean-in-x86-assembly-gas-syntax-as-in-dsbx
|
||||
- http://stackoverflow.com/questions/20717890/how-to-interpret-gs0x14?lq=1
|
||||
- http://stackoverflow.com/questions/22446104/do-the-x86-segment-registers-have-special-meaning-usage-on-modern-cpus-and-oses?lq=1
|
||||
- http://stackoverflow.com/questions/22962251/how-to-enter-64-bit-mode-on-a-x86-64/22963701#22963701
|
||||
@@ -75,7 +71,11 @@
|
||||
- http://stackoverflow.com/questions/14354626/how-to-create-two-separate-segments-in-global-descriptor-table
|
||||
- http://stackoverflow.com/questions/14812160/near-and-far-jmps
|
||||
|
||||
- timer, IPT
|
||||
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
|
||||
|
||||
@@ -98,20 +98,10 @@
|
||||
|
||||
- POST https://en.wikipedia.org/wiki/Power-on_self-test
|
||||
|
||||
- 2 stage two stage boot: load another part from the disk
|
||||
- exemplify all CPU modes
|
||||
|
||||
- http://stackoverflow.com/a/28645943/895245 contains:
|
||||
- http://stackoverflow.com/questions/20848412/modes-of-intel-64-cpu
|
||||
|
||||
[bits 16]
|
||||
[org 0x7c00]
|
||||
mov ax, 0201h
|
||||
mov cx, 0002h
|
||||
mov dh, 0
|
||||
mov bx, 0
|
||||
mov es, bx
|
||||
mov bx, 2000h
|
||||
int 13h
|
||||
jmp 0:2000h
|
||||
- https://en.wikipedia.org/wiki/Task_state_segment
|
||||
|
||||
[SECTION signature start=0x7dfe]
|
||||
dw 0AA55h
|
||||
Not used by Linux: <http://stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss>
|
||||
|
||||
108
common.h
108
common.h
@@ -66,24 +66,23 @@ Enter protected mode.
|
||||
Use the simplest GDT possible.
|
||||
*/
|
||||
.macro PROTECTED_MODE
|
||||
/* Must come before they are used. */
|
||||
.equ CODE_SEG, 8
|
||||
.equ DATA_SEG, gdt_data - gdt_start
|
||||
|
||||
cli
|
||||
/* Tell the processor where our Global Descriptor Table is in memory. */
|
||||
lgdt gdt_descriptor
|
||||
|
||||
/* Set PE (Protection Enable) bit in CR0 (Control Register 0) */
|
||||
/*
|
||||
Set PE (Protection Enable) bit in CR0 (Control Register 0),
|
||||
effectively entering protected mode.
|
||||
*/
|
||||
mov %cr0, %eax
|
||||
orl $0x1, %eax
|
||||
mov %eax, %cr0
|
||||
|
||||
/* TODO why does equ not work? What is the alternative? */
|
||||
/*ljmp $CODE_SEG, $b32*/
|
||||
/*
|
||||
This is needed to set `%cs` to 8.
|
||||
|
||||
8 means the second entry, since each entry is 8 bytes wide,
|
||||
and we have an initial null entry.
|
||||
*/
|
||||
ljmp $0x08, $protected_mode
|
||||
ljmp $CODE_SEG, $protected_mode
|
||||
.code32
|
||||
gdt_start:
|
||||
gdt_null:
|
||||
@@ -105,20 +104,24 @@ gdt_data:
|
||||
.byte 0x0
|
||||
gdt_end:
|
||||
gdt_descriptor:
|
||||
.word gdt_end - gdt_start
|
||||
.long gdt_start
|
||||
.equ CODE_SEG, gdt_code - gdt_start
|
||||
.equ DATA_SEG, gdt_data - gdt_start
|
||||
.word gdt_end - gdt_start
|
||||
.long gdt_start
|
||||
vga_current_line:
|
||||
.long 0
|
||||
protected_mode:
|
||||
/* Setup the other segments. */
|
||||
/*
|
||||
Setup the other segments.
|
||||
Those movs are mandatory because they update the descriptor cache:
|
||||
http://wiki.osdev.org/Descriptor_Cache
|
||||
*/
|
||||
mov $DATA_SEG, %ax
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
mov %ax, %ss
|
||||
/* TODO: what is the maximum we can put were? Why does `FFFFFFFF` fail? */
|
||||
mov $0XFFFFFF00, %ebp
|
||||
/* TODO detect memory properly. */
|
||||
mov $0X7000, %ebp
|
||||
mov %ebp, %esp
|
||||
.endm
|
||||
|
||||
@@ -159,8 +162,7 @@ Clobbers: ax
|
||||
/*
|
||||
Convert a byte to hex ASCII value.
|
||||
c: r/m8 byte to be converted
|
||||
Output: two ASCII characters, is stored in `al:bl`
|
||||
Clobbers: ax
|
||||
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
|
||||
@@ -242,13 +244,29 @@ Print a NULL terminated string to position 0 in VGA.
|
||||
|
||||
s: 32-bit register or memory containing the address of the string to print.
|
||||
|
||||
Clobbers: eax, ecx, edx
|
||||
Clobbers: none.
|
||||
|
||||
Uses and updates vga_current_line to decide the current line.
|
||||
Loops around the to the top.
|
||||
*/
|
||||
.macro VGA_PRINT s
|
||||
.macro VGA_PRINT_STRING s
|
||||
LOCAL loop, end
|
||||
mov s, %ecx
|
||||
/* Video memory address. */
|
||||
mov $0xb8000, %edx
|
||||
push %eax
|
||||
push %ebx
|
||||
push %ecx
|
||||
push %edx
|
||||
mov \s, %ecx
|
||||
mov vga_current_line, %eax
|
||||
mov $0, %edx
|
||||
/* Number of horizontal lines. */
|
||||
mov $25, %ebx
|
||||
div %ebx
|
||||
mov %edx, %eax
|
||||
/* 160 == 80 * 2 == line width * bytes per character on screen */
|
||||
mov $160, %edx
|
||||
mul %edx
|
||||
/* 0xb8000 == magic video memory address which shows on the screen. */
|
||||
lea 0xb8000(%eax), %edx
|
||||
/* White on black. */
|
||||
mov $0x0f, %ah
|
||||
loop:
|
||||
@@ -260,4 +278,46 @@ loop:
|
||||
add $2, %edx
|
||||
jmp loop
|
||||
end:
|
||||
incl vga_current_line
|
||||
pop %edx
|
||||
pop %ecx
|
||||
pop %ebx
|
||||
pop %eax
|
||||
.endm
|
||||
|
||||
/* Print a 32-bit register in hex. */
|
||||
.macro VGA_PRINT_REG reg=<%eax>
|
||||
LOCAL loop
|
||||
push %eax
|
||||
push %ebx
|
||||
push %ecx
|
||||
push %edx
|
||||
/* Null terminator. */
|
||||
mov \reg, %ecx
|
||||
|
||||
/* Write ASCII representation to memory. */
|
||||
push $0
|
||||
mov $2, %ebx
|
||||
loop:
|
||||
GAS_HEX <%cl>
|
||||
mov %ax, %dx
|
||||
shl $16, %edx
|
||||
GAS_HEX <%ch>
|
||||
mov %ax, %dx
|
||||
push %edx
|
||||
shr $16, %ecx
|
||||
dec %ebx
|
||||
cmp $0, %ebx
|
||||
jne loop
|
||||
|
||||
/* Print it. */
|
||||
mov %esp, %edx
|
||||
VGA_PRINT_STRING <%edx>
|
||||
|
||||
/* Restore the stack. We have pushed 3 * 4 bytes. */
|
||||
add $12, %esp
|
||||
pop %edx
|
||||
pop %ecx
|
||||
pop %ebx
|
||||
pop %eax
|
||||
.endm
|
||||
|
||||
17
debug.md
Normal file
17
debug.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# 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.
|
||||
|
||||
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>
|
||||
24
gdb.gdb
Normal file
24
gdb.gdb
Normal file
@@ -0,0 +1,24 @@
|
||||
target remote localhost:1234
|
||||
set architecture i8086
|
||||
# These would be possible. But they break the UI too much...
|
||||
#layout asm
|
||||
#layout regs
|
||||
|
||||
define hook-stop
|
||||
info registers
|
||||
printf "\n"
|
||||
x/16i $pc - 8
|
||||
printf "\n"
|
||||
end
|
||||
|
||||
break *0x7c00
|
||||
continue
|
||||
|
||||
# 0x1234 is a magic address. Add a:
|
||||
# mov %ax, 0x1234
|
||||
# to your program to break there.
|
||||
awatch *0x9000
|
||||
commands
|
||||
silent
|
||||
printf "0x9000 awatch access debug break\n"
|
||||
end
|
||||
@@ -42,6 +42,10 @@ Determine your GRUB version with:
|
||||
|
||||
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:
|
||||
|
||||
@@ -14,7 +14,8 @@ 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 mde
|
||||
- your program starts in 32-bit mode already, not 16 bit real mode
|
||||
- it gets the available memory ranges for you
|
||||
|
||||
Disadvantages:
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Major changes from real moe:
|
||||
BEGIN
|
||||
CLEAR
|
||||
PROTECTED_MODE
|
||||
VGA_PRINT $message
|
||||
VGA_PRINT_STRING $message
|
||||
jmp .
|
||||
message:
|
||||
.asciz "hello world"
|
||||
|
||||
75
segment_base.S
Normal file
75
segment_base.S
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
TODO get working.
|
||||
|
||||
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"
|
||||
|
||||
BEGIN
|
||||
CLEAR
|
||||
STAGE2
|
||||
PROTECTED_MODE
|
||||
|
||||
/* Sanity check 1: just print the output. */
|
||||
VGA_PRINT_STRING $output
|
||||
|
||||
/* Sanity check 2: make a mov without any segment manipulation. */
|
||||
mov message, %cl
|
||||
mov %cl, output
|
||||
VGA_PRINT_STRING $output
|
||||
|
||||
/* Now for the real action. */
|
||||
|
||||
mov $gdt_data, %edx
|
||||
/* We are touching the 7th byte of the data entry. */
|
||||
add $3, %edx
|
||||
mov (%edx), %al
|
||||
|
||||
/* Cache it for later. */
|
||||
mov %al, %bl
|
||||
|
||||
/* Set the first bit of the descriptor memory. */
|
||||
xor $1, %al
|
||||
mov %al, (%edx)
|
||||
|
||||
/*
|
||||
We must re-set ds because the segment descriptor is cached
|
||||
and this updates it:
|
||||
http://wiki.osdev.org/Descriptor_Cache
|
||||
*/
|
||||
mov $DATA_SEG, %ax
|
||||
mov %ax, %ds
|
||||
|
||||
/*
|
||||
This is the only memory access we will make with
|
||||
the modified segment, to minimize the effect on our IO.
|
||||
*/
|
||||
mov message, %cl
|
||||
|
||||
/* Restore the old segment. */
|
||||
/* TODO is this needed to take into account the new segmentation? */
|
||||
dec %edx
|
||||
mov %bl, (%edx)
|
||||
mov %ax, %ds
|
||||
|
||||
/*
|
||||
TODO this sanity check is not printing "ab",
|
||||
so we're not restoring the old state properly.
|
||||
HOW??
|
||||
*/
|
||||
VGA_PRINT_STRING $message
|
||||
|
||||
mov %cl, output
|
||||
VGA_PRINT_STRING $output
|
||||
|
||||
jmp .
|
||||
|
||||
message:
|
||||
.asciz "ab"
|
||||
output:
|
||||
.asciz "x"
|
||||
@@ -10,22 +10,21 @@ Segment registers in protected mode point to entries of that table.
|
||||
|
||||
GDT is used as soon as we enter protected mode, so that's why we have to deal with it, but the preferred way of managing program memory spaces is paging.
|
||||
|
||||
Format:
|
||||
Format straight from the Linux kernel 4.2: `arch/x86/include/asm/desc_defs.h` in `struct desc_struct`:
|
||||
|
||||
u16 limit0;
|
||||
u16 base0;
|
||||
unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;
|
||||
unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
|
||||
|
||||
- `g`: granularity of the limit. If `0`, 1 byte, if `1`, 4KiB.
|
||||
|
||||
Other sources:
|
||||
|
||||
- Intel Manual 325384-053US Volume 3, 3.4.5 Segment Descriptors
|
||||
- https://en.wikipedia.org/wiki/Global_Descriptor_Table
|
||||
- http://wiki.osdev.org/GDT
|
||||
|
||||
+-------------------------------------------------+
|
||||
| segment address 24-31 | flags #2 | len 16-19 |
|
||||
+-------------------------------------------------+
|
||||
| flags #1 | segment address 16-23 |
|
||||
+-------------------------------------------------+
|
||||
| segment address bits 0-15 |
|
||||
+-------------------------------------------------+
|
||||
| segment length bits 0-15 |
|
||||
+-------------------------------------------------+
|
||||
|
||||
### Null segment selector
|
||||
|
||||
### Null descriptor
|
||||
@@ -39,6 +38,8 @@ selector. It does, however, generate an exception when a segment register holdin
|
||||
memory. A null selector can be used to initialize unused segment registers. Loading the CS or SS register with a null
|
||||
segment selector causes a general-protection exception (#GP) to be generated.
|
||||
|
||||
I think this means that it is impossible to use the first entry. So you can do whatever you want with it?
|
||||
|
||||
### Effect on memory access
|
||||
|
||||
The GDT modifies every memory access of a given segment by:
|
||||
@@ -84,3 +85,14 @@ In 32-bit, a 6 byte register that holds:
|
||||
In 64 bit, makes 10 bytes, with the address having 8 bytes
|
||||
|
||||
GRUB seems to setup one for you: http://www.jamesmolloy.co.uk/tutorial_html/4.-The%20GDT%20and%20IDT.html
|
||||
|
||||
## lgdt
|
||||
|
||||
Loads the segment description register from memory.
|
||||
|
||||
TODO where is it on the Linux kernel?
|
||||
|
||||
Candidates:
|
||||
|
||||
- linux/arch/x86/kernel/head_64.S
|
||||
- linux/arch/x86/boot/compressed/head_64.S
|
||||
|
||||
Reference in New Issue
Block a user