Compare commits
No commits in common. "8f441135e073d3f86cbd247155f6c0e1a27321a1" and "005a1a149179295b07ab9fe6d44678a4805c8d55" have entirely different histories.
8f441135e0
...
005a1a1491
76
AGENTS.md
76
AGENTS.md
|
|
@ -1,76 +0,0 @@
|
||||||
# AGENTS.md
|
|
||||||
|
|
||||||
## Project
|
|
||||||
|
|
||||||
curiOS is an x86-64 OS kernel written (mostly) from scratch in Rust. It boots via
|
|
||||||
[Limine](https://limine-bootloader.org/) and runs in QEMU. A companion blog in
|
|
||||||
org-mode format lives in `blog/`.
|
|
||||||
|
|
||||||
## Workspace layout
|
|
||||||
|
|
||||||
Cargo workspace with members `crates/*` and `kernel`:
|
|
||||||
|
|
||||||
- `kernel/` — the kernel: `no_std` library crate (`src/lib.rs`) plus a `no_std`/`no_main` binary
|
|
||||||
(`src/main.rs`). Custom target spec `x86_64-unknown-kernel.json`; `.cargo/config.toml`
|
|
||||||
wires up the target, `build-std`, and the QEMU runner.
|
|
||||||
- `crates/rbtree/` — a no_std red-black tree crate used by the kernel's physical memory
|
|
||||||
manager. Unit-tests on the host (it opts into `std` under `cfg(test)`).
|
|
||||||
|
|
||||||
## Toolchain
|
|
||||||
|
|
||||||
- Rust **nightly** (edition 2024) with many unstable features — see the
|
|
||||||
`#![feature(...)]` lists at the top of `kernel/src/lib.rs` and `crates/rbtree/src/lib.rs`.
|
|
||||||
- Development environment is a Nix flake devshell (direnv via `.envrc`). It provides the
|
|
||||||
toolchain plus `qemu`, `limine-full`, `OVMF`, `mtools`, `clang`, `gdb`, `mold`, etc.
|
|
||||||
- The kernel is built with `build-std` and the custom target; do **not** build it for the
|
|
||||||
host target. `kernel/.cargo/config.toml` sets the target and a `json-target-spec`-enabled
|
|
||||||
nightly; rust-analyzer needs `CARGO_UNSTABLE_JSON_TARGET_SPEC=true` (see
|
|
||||||
`kernel/.dir-locals.el`).
|
|
||||||
|
|
||||||
## Build & test
|
|
||||||
|
|
||||||
Run these from inside `kernel/` (the workspace root uses the host target and won't
|
|
||||||
build the kernel binary):
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo build # build the kernel (runs build.rs which links kernel/kernel.lds)
|
|
||||||
cargo clippy # lint
|
|
||||||
cargo fmt --check # format check
|
|
||||||
```
|
|
||||||
|
|
||||||
`cargo run` / `cargo test` invoke `kernel/run.sh` via the runner in
|
|
||||||
`.cargo/config.toml`, which boots the kernel in QEMU. Tests are special `[[test]]`
|
|
||||||
binaries (e.g. `kernel/tests/simple.rs`, `stack_overflow.rs`) that run **inside the
|
|
||||||
kernel** in QEMU with a custom test harness (`harness = false`) and exit via the
|
|
||||||
QEMU `isa-debug-exit` port:
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo test # run all integration tests in QEMU
|
|
||||||
cargo test --test simple
|
|
||||||
```
|
|
||||||
|
|
||||||
`crates/rbtree` runs its unit tests normally on the host (`cargo test` from
|
|
||||||
`crates/rbtree/`).
|
|
||||||
|
|
||||||
The test framework (`#![cfg_attr(test, custom_test_frameworks)]` with `test_runner`
|
|
||||||
from `kernel/src/tests.rs`), QEMU exit handling, and the `Testable` trait live in
|
|
||||||
`kernel/src/testing.rs` and `kernel/src/tests.rs`. A `#[panic_handler]` is defined in
|
|
||||||
the test binaries — don't add another.
|
|
||||||
|
|
||||||
## Conventions
|
|
||||||
|
|
||||||
- `no_std` everywhere in kernel code; `extern crate alloc` where allocation is needed.
|
|
||||||
- Unsafe blocks require a `// SAFETY:` comment justifying the invariant.
|
|
||||||
- Use the `serial_println!` / `serial_print!` macros for logging (defined in
|
|
||||||
`kernel/src/serial.rs`) — there is no std output.
|
|
||||||
- Kernel-wide statics prefer the `sync::LazyLock` wrapper in `kernel/src/sync/mod.rs`
|
|
||||||
over bare `static mut`.
|
|
||||||
- Follow existing patterns in `kernel/src/x86_64/` (GDT, IDT, paging, cpuid) for new
|
|
||||||
arch code.
|
|
||||||
- `#[cfg(test)]` gate test-only modules and items.
|
|
||||||
- No comments unless they convey something non-obvious (e.g. safety justifications).
|
|
||||||
|
|
||||||
## Commit style
|
|
||||||
|
|
||||||
Short lowercase messages, `crate:` prefixed, e.g. `kernel: fix unsafenode impl`,
|
|
||||||
`rbtree: fix compile warnings`.
|
|
||||||
21
Cargo.lock
generated
21
Cargo.lock
generated
|
|
@ -20,35 +20,14 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit_field",
|
"bit_field",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"log",
|
|
||||||
"plain",
|
|
||||||
"rbtree",
|
"rbtree",
|
||||||
"rustc-demangle",
|
|
||||||
"seq-macro",
|
"seq-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.4.33"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plain"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rbtree"
|
name = "rbtree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc-demangle"
|
|
||||||
version = "0.1.28"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "seq-macro"
|
name = "seq-macro"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,3 @@ target = "x86_64-unknown-kernel.json"
|
||||||
|
|
||||||
[target.x86_64-unknown-kernel]
|
[target.x86_64-unknown-kernel]
|
||||||
runner = "./run.sh"
|
runner = "./run.sh"
|
||||||
rustflags = [
|
|
||||||
"-C", "force-frame-pointers=yes",
|
|
||||||
]
|
|
||||||
|
|
@ -24,9 +24,6 @@ harness = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bit_field = "0.10.3"
|
bit_field = "0.10.3"
|
||||||
bitflags = "2.13.1"
|
bitflags = "2.13.1"
|
||||||
log = { version = "0.4", default-features = false }
|
|
||||||
seq-macro = "0.3.6"
|
seq-macro = "0.3.6"
|
||||||
|
|
||||||
rbtree = { path = "../crates/rbtree" }
|
rbtree = { path = "../crates/rbtree" }
|
||||||
plain = "0.2.3"
|
|
||||||
rustc-demangle = "0.1.28"
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ PHDRS {
|
||||||
|
|
||||||
SECTIONS {
|
SECTIONS {
|
||||||
. = 0xffffffff80000000;
|
. = 0xffffffff80000000;
|
||||||
__kernel_start = .;
|
|
||||||
|
|
||||||
.limine_requests : {
|
.limine_requests : {
|
||||||
KEEP(*(.limine_requests_start))
|
KEEP(*(.limine_requests_start))
|
||||||
KEEP(*(.limine_requests))
|
KEEP(*(.limine_requests))
|
||||||
|
|
@ -38,16 +36,6 @@ SECTIONS {
|
||||||
*(COMMON)
|
*(COMMON)
|
||||||
} :data
|
} :data
|
||||||
|
|
||||||
__kernel_end = .;
|
|
||||||
|
|
||||||
.symtab 0: {
|
|
||||||
*(.symtab)
|
|
||||||
}
|
|
||||||
|
|
||||||
.strtab 0: {
|
|
||||||
*(.strtab)
|
|
||||||
}
|
|
||||||
|
|
||||||
/DISCARD/ : {
|
/DISCARD/ : {
|
||||||
*(.eh_frame*)
|
*(.eh_frame*)
|
||||||
*(.note .note.*)
|
*(.note .note.*)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ extern crate alloc;
|
||||||
|
|
||||||
pub mod bits;
|
pub mod bits;
|
||||||
pub mod limine;
|
pub mod limine;
|
||||||
pub mod logger;
|
|
||||||
pub mod serial;
|
pub mod serial;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
pub mod x86_64;
|
pub mod x86_64;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use core::{cell::UnsafeCell, ffi::CStr, fmt::Debug, ptr::NonNull};
|
use core::{cell::UnsafeCell, fmt::Debug, ptr::NonNull};
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct BaseRevision(UnsafeCell<[u64; 3]>);
|
pub struct BaseRevision(UnsafeCell<[u64; 3]>);
|
||||||
|
|
@ -272,60 +272,3 @@ impl HhdmRequest {
|
||||||
self.response().map(|response| response.response.offset)
|
self.response().map(|response| response.response.offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ExecutableFileResponse {
|
|
||||||
file: *const LimineFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for ExecutableFileResponse {}
|
|
||||||
unsafe impl Sync for ExecutableFileResponse {}
|
|
||||||
|
|
||||||
pub type ExecutableFileRequest = Request<ExecutableFileResponse>;
|
|
||||||
|
|
||||||
impl ExecutableFileRequest {
|
|
||||||
const ID: [u64; 2] = [0xad97e90e83f1ed67, 0x31eb5d1c5ff23b69];
|
|
||||||
|
|
||||||
pub const fn new() -> Self {
|
|
||||||
Self::new_raw(Self::ID, 0, ())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file(&self) -> Option<&LimineFile> {
|
|
||||||
self.response()
|
|
||||||
.and_then(|response| unsafe { response.response.file.as_ref() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct LimineFile {
|
|
||||||
pub revision: u64,
|
|
||||||
pub addr: *const u8,
|
|
||||||
pub length: u64,
|
|
||||||
pub path: *const i8,
|
|
||||||
pub string: *const i8,
|
|
||||||
pub media_type: u32,
|
|
||||||
pub _padding: [u8; 4],
|
|
||||||
pub tftp_ipv4_addr: [u8; 4],
|
|
||||||
pub tftp_port: u32,
|
|
||||||
pub partition_index: u32,
|
|
||||||
pub mbr_disk_id: u32,
|
|
||||||
pub gpt_disk_uuid: [u8; 16],
|
|
||||||
pub gpt_part_uuid: [u8; 16],
|
|
||||||
pub part_uuid: [u8; 16],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LimineFile {
|
|
||||||
pub const MEDIA_TYPE_GENERIC: u32 = 0;
|
|
||||||
pub const MEDIA_TYPE_OPTICAL: u32 = 1;
|
|
||||||
pub const MEDIA_TYPE_TFTP: u32 = 2;
|
|
||||||
|
|
||||||
pub fn bytes(&self) -> &'static [u8] {
|
|
||||||
unsafe { core::slice::from_raw_parts(self.addr, self.length as usize) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[used]
|
|
||||||
#[unsafe(link_section = ".limine_requests")]
|
|
||||||
pub static EXECUTABLE_FILE_REQUEST: ExecutableFileRequest = ExecutableFileRequest::new();
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
use log::{LevelFilter, Log, Metadata, Record};
|
|
||||||
|
|
||||||
use crate::serial_println;
|
|
||||||
|
|
||||||
pub struct SerialLogger;
|
|
||||||
|
|
||||||
impl Log for SerialLogger {
|
|
||||||
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
|
|
||||||
metadata.level() <= log::max_level()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log(&self, record: &Record<'_>) {
|
|
||||||
if self.enabled(record.metadata()) {
|
|
||||||
serial_println!("[{:}] {}", record.level(), record.args());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(max_level: LevelFilter) {
|
|
||||||
static LOGGER: SerialLogger = SerialLogger;
|
|
||||||
log::set_logger(&LOGGER).expect("logger already initialized");
|
|
||||||
log::set_max_level(max_level);
|
|
||||||
}
|
|
||||||
|
|
@ -22,7 +22,7 @@ static GDT: LazyLock<GlobalDescriptorTable> = LazyLock::new(GlobalDescriptorTabl
|
||||||
/// Entry point for the kernel
|
/// Entry point for the kernel
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn _start() -> ! {
|
extern "C" fn _start() -> ! {
|
||||||
kernel::logger::init(log::LevelFilter::Trace);
|
kernel::serial_println!("Hello, world!");
|
||||||
assert!(limine_requests::LIMINE_BASE_REVISION.is_supported());
|
assert!(limine_requests::LIMINE_BASE_REVISION.is_supported());
|
||||||
|
|
||||||
_ = kernel::memory::HHDM_BASE
|
_ = kernel::memory::HHDM_BASE
|
||||||
|
|
@ -56,7 +56,6 @@ extern "C" fn _start() -> ! {
|
||||||
GDT.load();
|
GDT.load();
|
||||||
IDT.load();
|
IDT.load();
|
||||||
|
|
||||||
|
|
||||||
kernel::serial_println!("entry point: 0x{:x}", _start as *const () as usize);
|
kernel::serial_println!("entry point: 0x{:x}", _start as *const () as usize);
|
||||||
kernel::serial_println!(
|
kernel::serial_println!(
|
||||||
"entry point phy: {:?}",
|
"entry point phy: {:?}",
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,8 @@ use crate::{serial_println, sync::OnceLock, x86_64::PAGE_SIZE};
|
||||||
|
|
||||||
pub static HHDM_BASE: OnceLock<u64> = OnceLock::new();
|
pub static HHDM_BASE: OnceLock<u64> = OnceLock::new();
|
||||||
|
|
||||||
pub trait VirtAddrTranslationExt: Sized {
|
pub trait VirtAddrTranslationExt {
|
||||||
fn into_phy_addr(self) -> Option<PhyAddr>;
|
fn into_phy_addr(self) -> Option<PhyAddr>;
|
||||||
fn is_mapped(self) -> bool {
|
|
||||||
self.into_phy_addr().is_some()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -54,12 +51,6 @@ impl Debug for PhyAddr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u64> for VirtAddr {
|
|
||||||
fn from(value: u64) -> Self {
|
|
||||||
VirtAddr(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<&T> for VirtAddr {
|
impl<T> From<&T> for VirtAddr {
|
||||||
fn from(ptr: &T) -> Self {
|
fn from(ptr: &T) -> Self {
|
||||||
VirtAddr(ptr as *const T as u64)
|
VirtAddr(ptr as *const T as u64)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#![cfg(target_arch = "x86_64")]
|
#![cfg(target_arch = "x86_64")]
|
||||||
|
|
||||||
pub mod backtrace;
|
|
||||||
pub mod cpuid;
|
pub mod cpuid;
|
||||||
pub mod gdt;
|
pub mod gdt;
|
||||||
pub mod idt;
|
pub mod idt;
|
||||||
|
|
@ -213,67 +212,3 @@ impl VirtAddrExt for VirtAddr {
|
||||||
E::decompose(self.0)
|
E::decompose(self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Context {
|
|
||||||
pub registers: Registers,
|
|
||||||
pub rip: u64,
|
|
||||||
pub rflags: RFlags,
|
|
||||||
pub rsp: u64,
|
|
||||||
/// The code segment selector.
|
|
||||||
pub cs: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::fmt::Display for Context {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
writeln!(f, "RIP - {:>#018x} \tRSP - {:>#018x}", self.rip, self.rsp)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"FLAGS - {:>#018x} \tCS - {:>#018x}",
|
|
||||||
self.rflags.bits(),
|
|
||||||
self.cs
|
|
||||||
)?;
|
|
||||||
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"RAX: {:>#018x} \tRCX: {:>#018x}",
|
|
||||||
self.registers.rax, self.registers.rcx
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"RDX: {:>#018x} \tRBX: {:>#018x}",
|
|
||||||
self.registers.rdx, self.registers.rbx
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"RSP: {:>#018x} \tRBP: {:>#018x}",
|
|
||||||
self.rsp, self.registers.rbp
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"RSI: {:>#018x} \tRDI: {:>#018x}",
|
|
||||||
self.registers.rsi, self.registers.rdi
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"R8 : {:>#018x} \tR9 : {:>#018x}",
|
|
||||||
self.registers.r8, self.registers.r9
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"R10: {:>#018x} \tR11: {:>#018x}",
|
|
||||||
self.registers.r10, self.registers.r11
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"R12: {:>#018x} \tR13: {:>#018x}",
|
|
||||||
self.registers.r12, self.registers.r13
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"R14: {:>#018x} \tR15: {:>#018x}",
|
|
||||||
self.registers.r14, self.registers.r15
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,495 +0,0 @@
|
||||||
use core::ffi::{CStr, c_void};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
limine::LimineFile,
|
|
||||||
memory::{VirtAddr, VirtAddrTranslationExt},
|
|
||||||
serial_println,
|
|
||||||
sync::LazyLock,
|
|
||||||
x86_64::{Context, VirtAddrExt},
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe extern "C" {
|
|
||||||
static __kernel_start: c_void;
|
|
||||||
static __kernel_end: c_void;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Frame {
|
|
||||||
pub rip: u64,
|
|
||||||
pub rbp: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
enum Elf64SymBind {
|
|
||||||
Local = 0,
|
|
||||||
Global = 1,
|
|
||||||
Weak = 2,
|
|
||||||
Loos = 10,
|
|
||||||
Hios = 12,
|
|
||||||
Loproc = 13,
|
|
||||||
Hiproc = 15,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
enum Elf64SymType {
|
|
||||||
Notype = 0,
|
|
||||||
Object = 1,
|
|
||||||
Func = 2,
|
|
||||||
Section = 3,
|
|
||||||
File = 4,
|
|
||||||
Common = 5,
|
|
||||||
Tls = 6,
|
|
||||||
Loos = 10,
|
|
||||||
Hios = 12,
|
|
||||||
Loproc = 13,
|
|
||||||
Hiproc = 15,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Elf64Sym {
|
|
||||||
name: u32,
|
|
||||||
info: u8,
|
|
||||||
other: u8,
|
|
||||||
shndx: u16,
|
|
||||||
value: u64,
|
|
||||||
size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl plain::Plain for Elf64Sym {}
|
|
||||||
|
|
||||||
#[repr(u32)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
enum SectionHeaderType {
|
|
||||||
Null = 0,
|
|
||||||
Progbits = 1,
|
|
||||||
Symtab = 2,
|
|
||||||
Strtab = 3,
|
|
||||||
Rela = 4,
|
|
||||||
Hash = 5,
|
|
||||||
Dynamic = 6,
|
|
||||||
Note = 7,
|
|
||||||
Nobits = 8,
|
|
||||||
Rel = 9,
|
|
||||||
Shlib = 10,
|
|
||||||
Dynsym = 11,
|
|
||||||
InitArray = 14,
|
|
||||||
FiniArray = 15,
|
|
||||||
PreinitArray = 16,
|
|
||||||
Group = 17,
|
|
||||||
SymtabShndx = 18,
|
|
||||||
Num = 19,
|
|
||||||
GnuAttributes = 0x6ffffff5,
|
|
||||||
GnuHash = 0x6ffffff6,
|
|
||||||
GnuLiblist = 0x6ffffff7,
|
|
||||||
Checksum = 0x6ffffff8,
|
|
||||||
Losunwind = 0x6ffffffa,
|
|
||||||
Amd64Unwind = 0x7000001,
|
|
||||||
OsSpecific(u32),
|
|
||||||
ProcSpecific(u32),
|
|
||||||
UserSpecific(u32),
|
|
||||||
Unknown = 0xffffffff,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SectionHeaderType {
|
|
||||||
fn from_u32(value: u32) -> Self {
|
|
||||||
match value {
|
|
||||||
0 => Self::Null,
|
|
||||||
1 => Self::Progbits,
|
|
||||||
2 => Self::Symtab,
|
|
||||||
3 => Self::Strtab,
|
|
||||||
4 => Self::Rela,
|
|
||||||
5 => Self::Hash,
|
|
||||||
6 => Self::Dynamic,
|
|
||||||
7 => Self::Note,
|
|
||||||
8 => Self::Nobits,
|
|
||||||
9 => Self::Rel,
|
|
||||||
10 => Self::Shlib,
|
|
||||||
11 => Self::Dynsym,
|
|
||||||
14 => Self::InitArray,
|
|
||||||
15 => Self::FiniArray,
|
|
||||||
16 => Self::PreinitArray,
|
|
||||||
17 => Self::Group,
|
|
||||||
18 => Self::SymtabShndx,
|
|
||||||
19 => Self::Num,
|
|
||||||
0x6ffffff5 => Self::GnuAttributes,
|
|
||||||
0x6ffffff6 => Self::GnuHash,
|
|
||||||
0x6ffffff7 => Self::GnuLiblist,
|
|
||||||
0x6ffffff8 => Self::Checksum,
|
|
||||||
0x6ffffffa => Self::Losunwind,
|
|
||||||
0x7000001 => Self::Amd64Unwind,
|
|
||||||
0x60000000..=0x6fffffff => Self::OsSpecific(value),
|
|
||||||
0x70000000..=0x7fffffff => Self::ProcSpecific(value),
|
|
||||||
0x80000000..=0x8fffffff => Self::UserSpecific(value),
|
|
||||||
_ => Self::Unknown,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_u32(self) -> u32 {
|
|
||||||
match self {
|
|
||||||
Self::Null => 0,
|
|
||||||
Self::Progbits => 1,
|
|
||||||
Self::Symtab => 2,
|
|
||||||
Self::Strtab => 3,
|
|
||||||
Self::Rela => 4,
|
|
||||||
Self::Hash => 5,
|
|
||||||
Self::Dynamic => 6,
|
|
||||||
Self::Note => 7,
|
|
||||||
Self::Nobits => 8,
|
|
||||||
Self::Rel => 9,
|
|
||||||
Self::Shlib => 10,
|
|
||||||
Self::Dynsym => 11,
|
|
||||||
Self::InitArray => 14,
|
|
||||||
Self::FiniArray => 15,
|
|
||||||
Self::PreinitArray => 16,
|
|
||||||
Self::Group => 17,
|
|
||||||
Self::SymtabShndx => 18,
|
|
||||||
Self::Num => 19,
|
|
||||||
Self::GnuAttributes => 0x6ffffff5,
|
|
||||||
Self::GnuHash => 0x6ffffff6,
|
|
||||||
Self::GnuLiblist => 0x6ffffff7,
|
|
||||||
Self::Checksum => 0x6ffffff8,
|
|
||||||
Self::Losunwind => 0x6ffffffa,
|
|
||||||
Self::Amd64Unwind => 0x7000001,
|
|
||||||
Self::OsSpecific(value) | Self::ProcSpecific(value) | Self::UserSpecific(value) => {
|
|
||||||
value
|
|
||||||
}
|
|
||||||
Self::Unknown => 0xffffffff,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct SectionHeader {
|
|
||||||
name: u32,
|
|
||||||
sh_type: u32,
|
|
||||||
flags: u64,
|
|
||||||
addr: u64,
|
|
||||||
offset: u64,
|
|
||||||
size: u64,
|
|
||||||
link: u32,
|
|
||||||
info: u32,
|
|
||||||
addralign: u64,
|
|
||||||
entsize: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl plain::Plain for SectionHeader {}
|
|
||||||
|
|
||||||
impl SectionHeader {
|
|
||||||
fn bytes<'a>(&self, bytes: &'a [u8]) -> Option<&'a [u8]> {
|
|
||||||
let offset = self.offset as usize;
|
|
||||||
let size = self.size as usize;
|
|
||||||
|
|
||||||
if offset + size > bytes.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(&bytes[offset..offset + size])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Elf64Ehdr {
|
|
||||||
ident: [u8; 16],
|
|
||||||
kind: u16,
|
|
||||||
machine: u16,
|
|
||||||
version: u32,
|
|
||||||
entry: u64,
|
|
||||||
phoff: u64,
|
|
||||||
shoff: u64,
|
|
||||||
flags: u32,
|
|
||||||
ehsize: u16,
|
|
||||||
phentsize: u16,
|
|
||||||
phnum: u16,
|
|
||||||
shentsize: u16,
|
|
||||||
shnum: u16,
|
|
||||||
shstrndx: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl plain::Plain for Elf64Ehdr {}
|
|
||||||
|
|
||||||
struct SectionHeaders<'a> {
|
|
||||||
headers: &'a [SectionHeader],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> SectionHeaders<'a> {
|
|
||||||
fn symtab_hdr(&self) -> Option<&'a SectionHeader> {
|
|
||||||
self.headers
|
|
||||||
.iter()
|
|
||||||
.find(|header| SectionHeaderType::from_u32(header.sh_type) == SectionHeaderType::Symtab)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Strtab<'a> {
|
|
||||||
bytes: &'a [u8],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Strtab<'a> {
|
|
||||||
fn len(&self) -> usize {
|
|
||||||
self.bytes.len()
|
|
||||||
}
|
|
||||||
fn get_str(&self, offset: u32) -> Option<&'a str> {
|
|
||||||
let offset = offset as usize;
|
|
||||||
|
|
||||||
if offset >= self.bytes.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytes = &self.bytes[offset..];
|
|
||||||
|
|
||||||
let nul_pos = bytes.iter().position(|&b| b == 0)?;
|
|
||||||
|
|
||||||
core::str::from_utf8(&bytes[..nul_pos]).ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Elf64Ehdr {
|
|
||||||
fn section_headers<'a>(&self, bytes: &'a [u8]) -> SectionHeaders<'a> {
|
|
||||||
let shoff = self.shoff as usize;
|
|
||||||
// let shentsize = self.shentsize as usize;
|
|
||||||
let shnum = self.shnum as usize;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
SectionHeaders {
|
|
||||||
headers: core::slice::from_raw_parts(
|
|
||||||
bytes.as_ptr().byte_add(shoff).cast::<SectionHeader>(),
|
|
||||||
shnum,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn shstrtab<'a>(&self, bytes: &'a [u8]) -> Option<Strtab<'a>> {
|
|
||||||
let shstrndx = self.shstrndx as usize;
|
|
||||||
let section_headers = self.section_headers(bytes);
|
|
||||||
|
|
||||||
if shstrndx >= section_headers.headers.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let shstrtab_header = §ion_headers.headers[shstrndx];
|
|
||||||
serial_println!("shstrtab_header: {:#?}", shstrtab_header);
|
|
||||||
let offset = shstrtab_header.offset as usize;
|
|
||||||
let size = shstrtab_header.size as usize;
|
|
||||||
|
|
||||||
if offset + size > bytes.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Strtab {
|
|
||||||
bytes: &bytes[offset..offset + size],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_shdr_by_name<'a>(&self, bytes: &'a [u8], name: &str) -> Option<&'a SectionHeader> {
|
|
||||||
let shstrtab = self.shstrtab(bytes)?;
|
|
||||||
let section_headers = self.section_headers(bytes);
|
|
||||||
|
|
||||||
section_headers
|
|
||||||
.headers
|
|
||||||
.iter()
|
|
||||||
.find(|header| shstrtab.get_str(header.name) == Some(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn strtab<'a>(&self, bytes: &'a [u8]) -> Option<Strtab<'a>> {
|
|
||||||
let strtab = self.find_shdr_by_name(bytes, ".strtab")?;
|
|
||||||
|
|
||||||
let offset = strtab.offset as usize;
|
|
||||||
let size = strtab.size as usize;
|
|
||||||
|
|
||||||
if offset + size > bytes.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Strtab {
|
|
||||||
bytes: &bytes[offset..offset + size],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn symtab<'a>(&self, bytes: &'a [u8]) -> Option<&'a [Elf64Sym]> {
|
|
||||||
let section_headers = self.section_headers(bytes);
|
|
||||||
let symtab_header = section_headers.symtab_hdr()?;
|
|
||||||
|
|
||||||
let offset = symtab_header.offset as usize;
|
|
||||||
let size = symtab_header.size as usize;
|
|
||||||
|
|
||||||
if offset + size > bytes.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let num_symbols = size / core::mem::size_of::<Elf64Sym>();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
Some(core::slice::from_raw_parts(
|
|
||||||
bytes.as_ptr().byte_add(offset).cast::<Elf64Sym>(),
|
|
||||||
num_symbols,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Elf64Sym {
|
|
||||||
fn symbols() -> &'static [Elf64Sym] {
|
|
||||||
static SYMBOLS: LazyLock<&'static [Elf64Sym]> = LazyLock::new(|| {
|
|
||||||
if let Some(bytes) = crate::limine::EXECUTABLE_FILE_REQUEST
|
|
||||||
.file()
|
|
||||||
.map(LimineFile::bytes)
|
|
||||||
{
|
|
||||||
if let Ok(ehdr) = plain::from_bytes::<Elf64Ehdr>(bytes) {
|
|
||||||
if let Some(symtab) = ehdr.symtab(bytes) {
|
|
||||||
return symtab;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&[]
|
|
||||||
});
|
|
||||||
|
|
||||||
SYMBOLS.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find(addr: u64) -> Option<&'static Elf64Sym> {
|
|
||||||
Self::symbols()
|
|
||||||
.iter()
|
|
||||||
.find(|&sym| sym.range().contains(&addr))
|
|
||||||
.map(|v| v as _)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_type(addr: u64, kind: Elf64SymType) -> Option<&'static Elf64Sym> {
|
|
||||||
Self::symbols()
|
|
||||||
.iter()
|
|
||||||
.find(|&sym| sym.kind() == kind && sym.range().contains(&addr))
|
|
||||||
.map(|v| v as _)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bind(&self) -> Elf64SymBind {
|
|
||||||
match self.info >> 4 {
|
|
||||||
0 => Elf64SymBind::Local,
|
|
||||||
1 => Elf64SymBind::Global,
|
|
||||||
2 => Elf64SymBind::Weak,
|
|
||||||
10 => Elf64SymBind::Loos,
|
|
||||||
12 => Elf64SymBind::Hios,
|
|
||||||
13 => Elf64SymBind::Loproc,
|
|
||||||
15 => Elf64SymBind::Hiproc,
|
|
||||||
_ => panic!("Invalid bind value"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn kind(&self) -> Elf64SymType {
|
|
||||||
match self.info & 0xf {
|
|
||||||
0 => Elf64SymType::Notype,
|
|
||||||
1 => Elf64SymType::Object,
|
|
||||||
2 => Elf64SymType::Func,
|
|
||||||
3 => Elf64SymType::Section,
|
|
||||||
4 => Elf64SymType::File,
|
|
||||||
5 => Elf64SymType::Common,
|
|
||||||
6 => Elf64SymType::Tls,
|
|
||||||
10 => Elf64SymType::Loos,
|
|
||||||
12 => Elf64SymType::Hios,
|
|
||||||
13 => Elf64SymType::Loproc,
|
|
||||||
15 => Elf64SymType::Hiproc,
|
|
||||||
_ => panic!("Invalid type value"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn range(&self) -> core::ops::Range<u64> {
|
|
||||||
self.value..(self.value.saturating_add(self.size))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> Option<&str> {
|
|
||||||
static STRTAB: LazyLock<Strtab<'static>> = LazyLock::new(|| {
|
|
||||||
if let Some(bytes) = crate::limine::EXECUTABLE_FILE_REQUEST
|
|
||||||
.file()
|
|
||||||
.map(LimineFile::bytes)
|
|
||||||
&& let Ok(ehdr) = plain::from_bytes::<Elf64Ehdr>(bytes)
|
|
||||||
{
|
|
||||||
serial_println!("ELF64 Header: {:#?}", ehdr);
|
|
||||||
return ehdr.strtab(bytes).unwrap_or_default();
|
|
||||||
}
|
|
||||||
|
|
||||||
Strtab::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let len = STRTAB.len();
|
|
||||||
|
|
||||||
if self.name == 0 || self.name as usize >= len {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
STRTAB.get_str(self.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn backtrace(ctx: &Context) -> impl Iterator<Item = Frame> {
|
|
||||||
Backtrace {
|
|
||||||
frame: Some(Frame {
|
|
||||||
rip: ctx.rip,
|
|
||||||
rbp: ctx.registers.rbp,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn debug_backtrace(ctx: &Context) {
|
|
||||||
serial_println!("{ctx}");
|
|
||||||
for frame in backtrace(ctx) {
|
|
||||||
if let Some(sym) = Elf64Sym::find(frame.rip) {
|
|
||||||
if sym.kind() == Elf64SymType::Func
|
|
||||||
&& sym.shndx != 0
|
|
||||||
&& let Some(name) = sym.name()
|
|
||||||
{
|
|
||||||
serial_println!(
|
|
||||||
"0x{:016x} - {} + 0x{:x}",
|
|
||||||
frame.rip,
|
|
||||||
rustc_demangle::demangle(name),
|
|
||||||
frame.rip - sym.value
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
serial_println!("{sym:#?}");
|
|
||||||
serial_println!(
|
|
||||||
"0x{:016x} - <unknown> + 0x{:x}",
|
|
||||||
frame.rip,
|
|
||||||
frame.rip - sym.value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
serial_println!("0x{:016x} - <unknown>", frame.rip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Backtrace {
|
|
||||||
frame: Option<Frame>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for Backtrace {
|
|
||||||
type Item = Frame;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let frame = self.frame.take()?;
|
|
||||||
|
|
||||||
let next_frame = {
|
|
||||||
let rsp = VirtAddr::from(frame.rbp);
|
|
||||||
if rsp.is_canonical() && rsp.is_mapped() {
|
|
||||||
// The stack frame layout is as follows:
|
|
||||||
// | Our Function |
|
|
||||||
// +-----------------+
|
|
||||||
// | Previous RBP | <- RBP points here
|
|
||||||
// +-----------------+
|
|
||||||
// | Return Address | <- RBP + 8 points here
|
|
||||||
// +-----------------+
|
|
||||||
// | Parent Function |
|
|
||||||
//
|
|
||||||
let rip = unsafe { *(rsp.as_ptr::<u64>().add(1)) };
|
|
||||||
let rbp = unsafe { *(rsp.as_ptr::<u64>()) };
|
|
||||||
|
|
||||||
Some(Frame { rip, rbp })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.frame = next_frame;
|
|
||||||
|
|
||||||
Some(frame)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,7 +4,7 @@ use bit_field::BitField;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
serial_println,
|
serial_println,
|
||||||
x86_64::{Context, RFlags, Registers, backtrace::debug_backtrace, halt_loop},
|
x86_64::{RFlags, Registers, halt_loop},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
|
@ -521,13 +521,6 @@ extern "C" fn global_interrupt_handler(
|
||||||
}
|
}
|
||||||
ExceptionVector::PAGE_FAULT => {
|
ExceptionVector::PAGE_FAULT => {
|
||||||
serial_println!("Page fault occurred! Error code: {:#x}", error_code);
|
serial_println!("Page fault occurred! Error code: {:#x}", error_code);
|
||||||
debug_backtrace(&Context {
|
|
||||||
rip: frame.instruction_pointer,
|
|
||||||
registers: *registers,
|
|
||||||
rsp: frame.stack_pointer,
|
|
||||||
rflags: frame.flags,
|
|
||||||
cs: frame.code_segment,
|
|
||||||
});
|
|
||||||
halt_loop()
|
halt_loop()
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use bit_field::BitField;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
memory::{PhyAddr, VirtAddr, VirtAddrTranslationExt},
|
memory::{PhyAddr, VirtAddr, VirtAddrTranslationExt},
|
||||||
|
serial_println,
|
||||||
x86_64::{VirtAddrExt, registers::Cr4},
|
x86_64::{VirtAddrExt, registers::Cr4},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -185,6 +186,8 @@ impl VirtAddrTranslationExt for VirtAddr {
|
||||||
pub fn get_physical_addr(virt: VirtAddr) -> Option<PhyAddr> {
|
pub fn get_physical_addr(virt: VirtAddr) -> Option<PhyAddr> {
|
||||||
let cr3 = crate::x86_64::registers::Cr3::read();
|
let cr3 = crate::x86_64::registers::Cr3::read();
|
||||||
let cr4 = crate::x86_64::registers::Cr4::read();
|
let cr4 = crate::x86_64::registers::Cr4::read();
|
||||||
|
serial_println!("cr3: {:?}", cr3);
|
||||||
|
serial_println!("cr4: {:?}", cr4);
|
||||||
|
|
||||||
let frame = cr3.phy();
|
let frame = cr3.phy();
|
||||||
let page_table = unsafe {
|
let page_table = unsafe {
|
||||||
|
|
@ -205,7 +208,10 @@ pub fn get_physical_addr(virt: VirtAddr) -> Option<PhyAddr> {
|
||||||
page_table[virt.page_table_index::<{ VirtAddr::PML4 }>()]
|
page_table[virt.page_table_index::<{ VirtAddr::PML4 }>()]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
serial_println!("pml4_entry: {:?}", pml4_entry);
|
||||||
|
|
||||||
let pdpt_entry = pml4_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PDPT }>()];
|
let pdpt_entry = pml4_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PDPT }>()];
|
||||||
|
serial_println!("pdpt_entry: {:?}", pdpt_entry);
|
||||||
|
|
||||||
if pdpt_entry.contains(PageTableEntryFlags::HUGE_PAGE) {
|
if pdpt_entry.contains(PageTableEntryFlags::HUGE_PAGE) {
|
||||||
const PHY_MASK_1G: u64 = !((1 << 30) - 1);
|
const PHY_MASK_1G: u64 = !((1 << 30) - 1);
|
||||||
|
|
@ -214,6 +220,7 @@ pub fn get_physical_addr(virt: VirtAddr) -> Option<PhyAddr> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let pd_entry = pdpt_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PD }>()];
|
let pd_entry = pdpt_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PD }>()];
|
||||||
|
serial_println!("pd_entry: {:?}", pd_entry);
|
||||||
|
|
||||||
if pd_entry.contains(PageTableEntryFlags::HUGE_PAGE) {
|
if pd_entry.contains(PageTableEntryFlags::HUGE_PAGE) {
|
||||||
const PHY_MASK_2M: u64 = !((1 << 21) - 1);
|
const PHY_MASK_2M: u64 = !((1 << 21) - 1);
|
||||||
|
|
@ -222,6 +229,7 @@ pub fn get_physical_addr(virt: VirtAddr) -> Option<PhyAddr> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let pt_entry = pd_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PT }>()];
|
let pt_entry = pd_entry.try_as_page_table()?[virt.page_table_index::<{ VirtAddr::PT }>()];
|
||||||
|
serial_println!("pt_entry: {:?}", pt_entry);
|
||||||
|
|
||||||
if !pt_entry.contains(PageTableEntryFlags::PRESENT) {
|
if !pt_entry.contains(PageTableEntryFlags::PRESENT) {
|
||||||
return None;
|
return None;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue