Failed attempt at modifying a segment descriptor, split GDB to separate file

This commit is contained in:
Ciro Santilli
2015-10-20 12:21:22 +02:00
parent ae4a413b5c
commit d4aae6183b
11 changed files with 258 additions and 72 deletions

View File

@@ -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.

View File

@@ -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
View File

@@ -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
View File

@@ -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
View 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
View 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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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
View 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"

View File

@@ -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