feat(mm): add early page fault diagnostics
This commit is contained in:
parent
e3f34ac1fe
commit
7df720c57b
1
Makefile
1
Makefile
@ -11,6 +11,7 @@ KERNEL_ELF := $(BUILD_DIR)/image/kernel.elf
|
|||||||
DEBUG_LOG := $(BUILD_DIR)/debug.log
|
DEBUG_LOG := $(BUILD_DIR)/debug.log
|
||||||
SERIAL_LOG := $(BUILD_DIR)/serial.log
|
SERIAL_LOG := $(BUILD_DIR)/serial.log
|
||||||
KERNEL_TEST_TRAP ?= 0
|
KERNEL_TEST_TRAP ?= 0
|
||||||
|
KERNEL_TEST_PAGE_FAULT ?= 0
|
||||||
|
|
||||||
ifeq ($(ARCH),x86_64)
|
ifeq ($(ARCH),x86_64)
|
||||||
ARCH_MAKEFILE := arch/x86/Makefile
|
ARCH_MAKEFILE := arch/x86/Makefile
|
||||||
|
|||||||
@ -30,6 +30,7 @@ KERNEL_CFLAGS := \
|
|||||||
-fno-pic \
|
-fno-pic \
|
||||||
-mno-red-zone \
|
-mno-red-zone \
|
||||||
-DKERNEL_TEST_TRAP=$(KERNEL_TEST_TRAP) \
|
-DKERNEL_TEST_TRAP=$(KERNEL_TEST_TRAP) \
|
||||||
|
-DKERNEL_TEST_PAGE_FAULT=$(KERNEL_TEST_PAGE_FAULT) \
|
||||||
-Wall \
|
-Wall \
|
||||||
-Wextra \
|
-Wextra \
|
||||||
-Werror \
|
-Werror \
|
||||||
|
|||||||
32
arch/x86/include/arch/traps.h
Normal file
32
arch/x86/include/arch/traps.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef ARCH_X86_TRAPS_H
|
||||||
|
#define ARCH_X86_TRAPS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct trap_frame {
|
||||||
|
uint64_t rax;
|
||||||
|
uint64_t rbx;
|
||||||
|
uint64_t rcx;
|
||||||
|
uint64_t rdx;
|
||||||
|
uint64_t rsi;
|
||||||
|
uint64_t rdi;
|
||||||
|
uint64_t rbp;
|
||||||
|
uint64_t r8;
|
||||||
|
uint64_t r9;
|
||||||
|
uint64_t r10;
|
||||||
|
uint64_t r11;
|
||||||
|
uint64_t r12;
|
||||||
|
uint64_t r13;
|
||||||
|
uint64_t r14;
|
||||||
|
uint64_t r15;
|
||||||
|
uint64_t vector;
|
||||||
|
uint64_t error_code;
|
||||||
|
uint64_t rip;
|
||||||
|
uint64_t cs;
|
||||||
|
uint64_t rflags;
|
||||||
|
};
|
||||||
|
|
||||||
|
void trap_dispatch(struct trap_frame *frame);
|
||||||
|
void handle_page_fault(struct trap_frame *frame);
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -7,33 +7,11 @@
|
|||||||
#define KERNEL_DATA_SELECTOR 0x10
|
#define KERNEL_DATA_SELECTOR 0x10
|
||||||
#define TSS_SELECTOR 0x18
|
#define TSS_SELECTOR 0x18
|
||||||
|
|
||||||
struct trap_frame {
|
#include <arch/traps.h>
|
||||||
uint64_t rax;
|
|
||||||
uint64_t rbx;
|
|
||||||
uint64_t rcx;
|
|
||||||
uint64_t rdx;
|
|
||||||
uint64_t rsi;
|
|
||||||
uint64_t rdi;
|
|
||||||
uint64_t rbp;
|
|
||||||
uint64_t r8;
|
|
||||||
uint64_t r9;
|
|
||||||
uint64_t r10;
|
|
||||||
uint64_t r11;
|
|
||||||
uint64_t r12;
|
|
||||||
uint64_t r13;
|
|
||||||
uint64_t r14;
|
|
||||||
uint64_t r15;
|
|
||||||
uint64_t vector;
|
|
||||||
uint64_t error_code;
|
|
||||||
uint64_t rip;
|
|
||||||
uint64_t cs;
|
|
||||||
uint64_t rflags;
|
|
||||||
};
|
|
||||||
|
|
||||||
void gdt_init(void);
|
void gdt_init(void);
|
||||||
void idt_init(void);
|
void idt_init(void);
|
||||||
void idt_set_gate(uint8_t vector, void (*handler)(void));
|
void idt_set_gate(uint8_t vector, void (*handler)(void));
|
||||||
void trap_dispatch(struct trap_frame *frame);
|
|
||||||
|
|
||||||
void exception_0(void);
|
void exception_0(void);
|
||||||
void exception_1(void);
|
void exception_1(void);
|
||||||
|
|||||||
@ -40,39 +40,11 @@ static const char *const exception_names[32] = {
|
|||||||
"reserved",
|
"reserved",
|
||||||
};
|
};
|
||||||
|
|
||||||
static void log_hex_digit(uint8_t digit)
|
|
||||||
{
|
|
||||||
if (digit < 10) {
|
|
||||||
early_log_putc((char)('0' + digit));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
early_log_putc((char)('a' + digit - 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void log_u64_hex(uint64_t value)
|
|
||||||
{
|
|
||||||
int shift;
|
|
||||||
|
|
||||||
early_log_puts("0x");
|
|
||||||
for (shift = 60; shift >= 0; shift -= 4) {
|
|
||||||
log_hex_digit((uint8_t)((value >> shift) & 0x0f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t interrupted_rsp(const struct trap_frame *frame)
|
static uint64_t interrupted_rsp(const struct trap_frame *frame)
|
||||||
{
|
{
|
||||||
return (uint64_t)(uintptr_t)&frame->rflags + sizeof(frame->rflags);
|
return (uint64_t)(uintptr_t)&frame->rflags + sizeof(frame->rflags);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t read_cr2(void)
|
|
||||||
{
|
|
||||||
uint64_t cr2;
|
|
||||||
|
|
||||||
__asm__ volatile("movq %%cr2, %0" : "=r"(cr2));
|
|
||||||
return cr2;
|
|
||||||
}
|
|
||||||
|
|
||||||
void arch_traps_init(void)
|
void arch_traps_init(void)
|
||||||
{
|
{
|
||||||
gdt_init();
|
gdt_init();
|
||||||
@ -95,20 +67,18 @@ void trap_dispatch(struct trap_frame *frame)
|
|||||||
early_log_puts("vector=");
|
early_log_puts("vector=");
|
||||||
early_log_u64_decimal(vector);
|
early_log_u64_decimal(vector);
|
||||||
early_log_puts(" error=");
|
early_log_puts(" error=");
|
||||||
log_u64_hex(frame->error_code);
|
early_log_u64_hex(frame->error_code);
|
||||||
early_log_puts("\n");
|
early_log_puts("\n");
|
||||||
early_log_puts("rip=");
|
early_log_puts("rip=");
|
||||||
log_u64_hex(frame->rip);
|
early_log_u64_hex(frame->rip);
|
||||||
early_log_puts(" rsp=");
|
early_log_puts(" rsp=");
|
||||||
log_u64_hex(interrupted_rsp(frame));
|
early_log_u64_hex(interrupted_rsp(frame));
|
||||||
early_log_puts(" rflags=");
|
early_log_puts(" rflags=");
|
||||||
log_u64_hex(frame->rflags);
|
early_log_u64_hex(frame->rflags);
|
||||||
early_log_puts("\n");
|
early_log_puts("\n");
|
||||||
|
|
||||||
if (vector == 14) {
|
if (vector == 14) {
|
||||||
early_log_puts("fault_address=");
|
handle_page_fault(frame);
|
||||||
log_u64_hex(read_cr2());
|
|
||||||
early_log_puts("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("unhandled CPU exception");
|
panic("unhandled CPU exception");
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
ARCH_MM_SRCS := \
|
ARCH_MM_SRCS := \
|
||||||
|
$(ARCH_DIR)/mm/fault.c \
|
||||||
$(ARCH_DIR)/mm/page_table.c
|
$(ARCH_DIR)/mm/page_table.c
|
||||||
|
|
||||||
ARCH_MM_OBJS := \
|
ARCH_MM_OBJS := \
|
||||||
|
|||||||
53
arch/x86/mm/fault.c
Normal file
53
arch/x86/mm/fault.c
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#include <arch/traps.h>
|
||||||
|
|
||||||
|
#include <tianole/early_log.h>
|
||||||
|
|
||||||
|
#define PF_PRESENT (1ull << 0)
|
||||||
|
#define PF_WRITE (1ull << 1)
|
||||||
|
#define PF_USER (1ull << 2)
|
||||||
|
#define PF_RESERVED (1ull << 3)
|
||||||
|
#define PF_INSTRUCTION_FETCH (1ull << 4)
|
||||||
|
|
||||||
|
static uint64_t read_cr2(void)
|
||||||
|
{
|
||||||
|
uint64_t cr2;
|
||||||
|
|
||||||
|
__asm__ volatile("movq %%cr2, %0" : "=r"(cr2));
|
||||||
|
return cr2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_fault_access(uint64_t error_code)
|
||||||
|
{
|
||||||
|
early_log_puts("access=");
|
||||||
|
|
||||||
|
if ((error_code & PF_INSTRUCTION_FETCH) != 0) {
|
||||||
|
early_log_puts("execute");
|
||||||
|
} else if ((error_code & PF_WRITE) != 0) {
|
||||||
|
early_log_puts("write");
|
||||||
|
} else {
|
||||||
|
early_log_puts("read");
|
||||||
|
}
|
||||||
|
|
||||||
|
early_log_puts(" mode=");
|
||||||
|
early_log_puts((error_code & PF_USER) != 0 ? "user" : "kernel");
|
||||||
|
|
||||||
|
early_log_puts(" reason=");
|
||||||
|
early_log_puts(
|
||||||
|
(error_code & PF_PRESENT) != 0 ? "protection" : "not-present");
|
||||||
|
|
||||||
|
if ((error_code & PF_RESERVED) != 0) {
|
||||||
|
early_log_puts(" reserved-bit");
|
||||||
|
}
|
||||||
|
|
||||||
|
early_log_puts("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_page_fault(struct trap_frame *frame)
|
||||||
|
{
|
||||||
|
early_log_puts("page fault: address=");
|
||||||
|
early_log_u64_hex(read_cr2());
|
||||||
|
early_log_puts(" error=");
|
||||||
|
early_log_u64_hex(frame->error_code);
|
||||||
|
early_log_puts("\n");
|
||||||
|
log_fault_access(frame->error_code);
|
||||||
|
}
|
||||||
@ -55,14 +55,14 @@
|
|||||||
- 已切换到内核自有 PML4,不再直接修改固件页表。
|
- 已切换到内核自有 PML4,不再直接修改固件页表。
|
||||||
- 已提供最小 `map_page()`、`unmap_page()` 和 `virt_to_phys()` 接口。
|
- 已提供最小 `map_page()`、`unmap_page()` 和 `virt_to_phys()` 接口。
|
||||||
- 已加入页表 map/unmap/query selftest。
|
- 已加入页表 map/unmap/query selftest。
|
||||||
- `scripts/check.sh` 已验证 `physical pages free=`、物理页 selftest、页表根切换和页表 selftest 日志。
|
- 已拆出 x86 page fault 诊断路径,能输出 fault address、错误码、访问类型和权限来源。
|
||||||
|
- `scripts/check.sh` 已验证 `physical pages free=`、物理页 selftest、页表根切换、页表 selftest 和 page fault 日志。
|
||||||
|
|
||||||
后续扩展:
|
后续扩展:
|
||||||
|
|
||||||
- 长期内存区域模型,不直接依赖 UEFI memory type。
|
- 长期内存区域模型,不直接依赖 UEFI memory type。
|
||||||
- page metadata。
|
- page metadata。
|
||||||
- buddy allocator。
|
- buddy allocator。
|
||||||
- page fault 专门处理。
|
|
||||||
- 最小内核堆。
|
- 最小内核堆。
|
||||||
- slab/slub 或等价小对象缓存。
|
- slab/slub 或等价小对象缓存。
|
||||||
|
|
||||||
@ -72,7 +72,8 @@
|
|||||||
- 正常启动日志包含 `physical page allocator selftest ok`。
|
- 正常启动日志包含 `physical page allocator selftest ok`。
|
||||||
- 正常启动日志包含 `kernel page table root active`。
|
- 正常启动日志包含 `kernel page table root active`。
|
||||||
- 正常启动日志包含 `page table selftest ok`。
|
- 正常启动日志包含 `page table selftest ok`。
|
||||||
|
- page fault 测试日志包含 `page fault: address=` 和 `access=write mode=kernel reason=not-present`。
|
||||||
|
|
||||||
下一阶段:
|
下一阶段:
|
||||||
|
|
||||||
- 继续在 `03-memory.md` 内推进 page fault 专门处理和最小内核堆。
|
- 继续在 `03-memory.md` 内推进最小内核堆。
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `01-early-debug.md` | 基础完成 | 已有 early log 前端、QEMU debug port、COM1 串口和最小 `panic()`。 |
|
| `01-early-debug.md` | 基础完成 | 已有 early log 前端、QEMU debug port、COM1 串口和最小 `panic()`。 |
|
||||||
| `02-cpu-interrupts.md` | 基础完成 | 已有 GDT/TSS/IDT、exception vector 0-31、`trap_frame` 和 invalid opcode 回归测试。 |
|
| `02-cpu-interrupts.md` | 基础完成 | 已有 GDT/TSS/IDT、exception vector 0-31、`trap_frame` 和 invalid opcode 回归测试。 |
|
||||||
| `03-memory.md` | 进行中 | 已有最小物理页 allocator 和页表 map/unmap/query 基础;page fault 策略和内核堆未完成。 |
|
| `03-memory.md` | 进行中 | 已有最小物理页 allocator、页表 map/unmap/query 和 page fault 诊断基础;内核堆未完成。 |
|
||||||
| `04-time-scheduler.md` | 未开始 | 需要 timer、kernel thread、调度和等待队列。 |
|
| `04-time-scheduler.md` | 未开始 | 需要 timer、kernel thread、调度和等待队列。 |
|
||||||
| `05-input-events.md` | 未开始 | 需要输入事件模型和键盘接入。 |
|
| `05-input-events.md` | 未开始 | 需要输入事件模型和键盘接入。 |
|
||||||
| `06-storage-vfs.md` | 未开始 | 需要块层、缓存、VFS 和基础文件系统。 |
|
| `06-storage-vfs.md` | 未开始 | 需要块层、缓存、VFS 和基础文件系统。 |
|
||||||
@ -36,7 +36,7 @@
|
|||||||
| `09-driver-expansion.md` | 未开始 | 需要设备模型、PCI/ACPI、存储、网络等驱动扩展。 |
|
| `09-driver-expansion.md` | 未开始 | 需要设备模型、PCI/ACPI、存储、网络等驱动扩展。 |
|
||||||
| `10-real-machine.md` | 未开始 | 需要 U 盘真机启动验证和硬件差异记录。 |
|
| `10-real-machine.md` | 未开始 | 需要 U 盘真机启动验证和硬件差异记录。 |
|
||||||
|
|
||||||
当前最合适的下一步仍在 `03-memory.md` 内:先补 page fault 专门诊断,再接最小内核堆。
|
当前最合适的下一步仍在 `03-memory.md` 内:继续做最小内核堆。
|
||||||
|
|
||||||
## Linux 级能力缺口路由
|
## Linux 级能力缺口路由
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
- 物理页验证:`alloc_page/free_page` selftest 已接入启动检查。
|
- 物理页验证:`alloc_page/free_page` selftest 已接入启动检查。
|
||||||
- 页表基础:已切换到内核自有 PML4,并提供最小 `map_page/unmap_page/virt_to_phys`。
|
- 页表基础:已切换到内核自有 PML4,并提供最小 `map_page/unmap_page/virt_to_phys`。
|
||||||
- 页表验证:页表 map/unmap/query selftest 已接入启动检查。
|
- 页表验证:页表 map/unmap/query selftest 已接入启动检查。
|
||||||
|
- page fault 基础:已能输出 fault address、错误码、访问类型和权限来源。
|
||||||
- 构建系统已拆成根 Makefile、`arch/x86/Makefile` 和目录 Makefile。
|
- 构建系统已拆成根 Makefile、`arch/x86/Makefile` 和目录 Makefile。
|
||||||
- `scripts/check.sh` 已验证启动日志、串口日志和 invalid opcode 异常路径,GitHub Actions 已接入。
|
- `scripts/check.sh` 已验证启动日志、串口日志和 invalid opcode 异常路径,GitHub Actions 已接入。
|
||||||
- `.clang-format` 已用于强制当前 C 代码风格。
|
- `.clang-format` 已用于强制当前 C 代码风格。
|
||||||
@ -29,7 +30,7 @@
|
|||||||
|
|
||||||
- 完整日志体系:printk、log level、ring buffer、oops、符号化、crash dump。
|
- 完整日志体系:printk、log level、ring buffer、oops、符号化、crash dump。
|
||||||
- 完整中断体系:PIC/APIC、外部 IRQ、timer interrupt。
|
- 完整中断体系:PIC/APIC、外部 IRQ、timer interrupt。
|
||||||
- 内存管理扩展:长期内存区域模型、page fault 策略、内核堆。
|
- 内存管理扩展:长期内存区域模型、内核堆、后续完整缺页策略。
|
||||||
- 后续主线:调度、进程、文件系统、用户态和 shell。
|
- 后续主线:调度、进程、文件系统、用户态和 shell。
|
||||||
|
|
||||||
## 当前下一步
|
## 当前下一步
|
||||||
@ -41,7 +42,6 @@
|
|||||||
目标:
|
目标:
|
||||||
|
|
||||||
- 建立最小内核堆。
|
- 建立最小内核堆。
|
||||||
- 让 page fault 能进入现有异常路径并输出有效诊断。
|
|
||||||
|
|
||||||
## 任务路由
|
## 任务路由
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
void early_log_init(void);
|
void early_log_init(void);
|
||||||
void early_log_putc(char ch);
|
void early_log_putc(char ch);
|
||||||
void early_log_puts(const char *text);
|
void early_log_puts(const char *text);
|
||||||
|
void early_log_u64_hex(uint64_t value);
|
||||||
void early_log_u64_decimal(uint64_t value);
|
void early_log_u64_decimal(uint64_t value);
|
||||||
void panic(const char *message) __attribute__((noreturn));
|
void panic(const char *message) __attribute__((noreturn));
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,26 @@ void early_log_puts(const char *text)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void early_log_hex_digit(uint8_t digit)
|
||||||
|
{
|
||||||
|
if (digit < 10) {
|
||||||
|
early_log_putc((char)('0' + digit));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
early_log_putc((char)('a' + digit - 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
void early_log_u64_hex(uint64_t value)
|
||||||
|
{
|
||||||
|
int shift;
|
||||||
|
|
||||||
|
early_log_puts("0x");
|
||||||
|
for (shift = 60; shift >= 0; shift -= 4) {
|
||||||
|
early_log_hex_digit((uint8_t)((value >> shift) & 0x0f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void early_log_u64_decimal(uint64_t value)
|
void early_log_u64_decimal(uint64_t value)
|
||||||
{
|
{
|
||||||
char digits[20];
|
char digits[20];
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <tianole/arch.h>
|
#include <tianole/arch.h>
|
||||||
#include <tianole/early_log.h>
|
#include <tianole/early_log.h>
|
||||||
#include <tianole/kernel_init.h>
|
#include <tianole/kernel_init.h>
|
||||||
@ -15,6 +17,10 @@ void kernel_main(const boot_info_t *boot_info)
|
|||||||
__asm__ volatile("ud2");
|
__asm__ volatile("ud2");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if KERNEL_TEST_PAGE_FAULT
|
||||||
|
*(volatile uint64_t *)(uintptr_t)0xffffff1000000000ull = 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
__asm__ volatile("hlt");
|
__asm__ volatile("hlt");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,4 +6,5 @@ cd "$(dirname "$0")/.."
|
|||||||
./scripts/checks/format.sh
|
./scripts/checks/format.sh
|
||||||
./scripts/checks/boot.sh
|
./scripts/checks/boot.sh
|
||||||
./scripts/checks/trap.sh
|
./scripts/checks/trap.sh
|
||||||
|
./scripts/checks/page-fault.sh
|
||||||
./scripts/checks/default-build.sh
|
./scripts/checks/default-build.sh
|
||||||
|
|||||||
21
scripts/checks/page-fault.sh
Executable file
21
scripts/checks/page-fault.sh
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/../.."
|
||||||
|
|
||||||
|
. ./scripts/lib/check-common.sh
|
||||||
|
|
||||||
|
build_kernel KERNEL_TEST_PAGE_FAULT=1
|
||||||
|
run_qemu KERNEL_TEST_PAGE_FAULT=1
|
||||||
|
|
||||||
|
check_lines build/debug.log \
|
||||||
|
"traps initialized" \
|
||||||
|
"kernel page table root active" \
|
||||||
|
"page table selftest ok" \
|
||||||
|
"exception: page fault" \
|
||||||
|
"vector=14 error=0x0000000000000002" \
|
||||||
|
"page fault: address=0xffffff1000000000 error=0x0000000000000002" \
|
||||||
|
"access=write mode=kernel reason=not-present" \
|
||||||
|
"panic: unhandled CPU exception"
|
||||||
|
|
||||||
|
cat build/debug.log
|
||||||
Loading…
Reference in New Issue
Block a user