Page Fault and General Protection Fault

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Ethin
Member
Member
Posts: 625
Joined: Sun Jun 23, 2019 5:36 pm
Location: North Dakota, United States

Re: Page Fault and General Protection Fault

Post by Ethin »

Octocontrabass wrote:
Ethin wrote:

Code: Select all

    .stack(&STACK.0[MAX_STACK_SIZE - 1] as *const u8)
Subtracting 1 results in a value that's 1 below what you should use for the stack pointer, and misaligns your stack. I don't know if Rust allows pointers past the end of an array the way C does, but that's basically what you need here.

It looks like your stack will be correctly 16-byte aligned once you fix that.
Ethin wrote:The odd thing is that I (think) that ffffffffb99008c7h is beyond my stack, and I don't understand why. I've even reserved it properly in my linker script (I think):
Other than the off-by-one error above, it looks like everything is set up correctly. Your stack just isn't big enough for whatever it is you're doing. You could make it bigger, but I'm really curious why 256kB isn't enough already.
I managed to get it working like this:

Code: Select all

#[link_section = ".stivale2hdr"]
#[used]
static BOOT_LOADER_HEADER: StivaleHeader = StivaleHeader::new()
    .stack(STACK.0.as_ptr_range().end)
    .flags(0x1E)
    .tags(&FB_TAG as *const StivaleFramebufferHeaderTag as *const ());
The as_ptr_range() function returns a pointer to the beginning and end of the array (or, in this case, the bottom and top of the stack). The function isn't stable on Rust beta or stable, but I'm using nightly so was able to use it in a constant-evaluation context. (I could probably make it do it in some other way but it'd be a lot messier).

As for the lack of space for the 256Kbyte stack, I figured out why. My memory manager contains a stack-allocated vector for the memory map. Since I don't know how large a memory map could be (or, rather, how many entries one might have), I took a gander and chose 16384 as the amount of space to allocate. However, I didn't take into account the size of the struct for each memory region, which is 20 bytes, plus padding/ABI stuff. Thus, 16384 entries times 20 bytes gives me an approximate stack usage of about 330Kbytes. Lowering it to 1024 entries (which I'm assuming is a good amount for pretty much every machine in the world bar NUMA setups) lowers it to about 5 KB exactly. Still a significant number, for sure, but I needed some way of storing the memory map so I could reference it later.

Now I'm just running into heap location issues. I'm trying to smartly allocate my heap in such a way that it doesn't overwrite my kernel and doesn't get set up in the higher half memory range where physical memory is located (that would be bad). Previously, before I switched boot loaders, I chose a memory location (specifically 0x100000000000) plus a random 64-bit offset. Now though I'm not really sure how to go about it. This is my (very inefficient!) initial implementation, which I need to figure out a better version of:

Code: Select all

    let mut idx = 0usize;
    let addrs = loop {
        let entry = mmap.iter().nth(idx);
        if entry.is_none() {
            break (0, 0);
        }
        let entry = entry.unwrap();
        if entry.entry_type() != StivaleMemoryMapEntryType::Usable {
            idx += 1;
            continue;
        }
        if entry.length >= MAX_HEAP_SIZE {
            let base = entry.base;
            let mut offset = rdrand.get_u64().unwrap() % entry.end_address();
            loop {
                if (base + offset + MAX_HEAP_SIZE) > entry.end_address() {
                    offset = rdrand.get_u64().unwrap() % entry.end_address();
                    continue;
                }
                break;
            }
            break (base + offset, entry.end_address());
        }
    };
    if addrs == (0, 0) {
        panic!("Can't find a memory region for the heap!");
    }
However, if my page tables are completely empty, which appears to be the case if qemu's "info mem" command isn't outputting anything, I assume I can just let my kernel put it anywhere within the virtual memory space? Hell, could I just declare another static array for the heap, point my memory allocator at it, and be done with it that way? Or would that break paging?
Edit: solved the algorithm problem. Apparently the boot loader set up huge pages in my page table. When I go to map pages when the parent entry is a huge page, do I just skip those that are already mapped because the (parent) page is a huge page?
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: Page Fault and General Protection Fault

Post by Octocontrabass »

Ethin wrote:I'm trying to smartly allocate my heap in such a way that it doesn't overwrite my kernel and doesn't get set up in the higher half memory range where physical memory is located (that would be bad).
You're talking about allocating virtual address space for the heap, right? Is overlapping MMIO okay? If it is, then you just need to find a higher-half address that doesn't overlap a memory map entry (after adding the HHDM offset) or your kernel. You might also want to avoid the first 4GB of the HHDM area just because it's already mapped and you would need to flush the TLB after changing the mapping to accommodate your heap.
Ethin wrote:This is my (very inefficient!) initial implementation, which I need to figure out a better version of:
It looks like this code is specifically looking for an address that overlaps a memory map entry that isn't free memory?
Ethin wrote:However, if my page tables are completely empty, which appears to be the case if qemu's "info mem" command isn't outputting anything, I assume I can just let my kernel put it anywhere within the virtual memory space?
If your page tables are completely empty, you're triple-faulting. I've found QEMU's "info mem" and "info tlb" are not 100% accurate, but they usually fail in different ways so they're still useful for debugging.
Ethin wrote:Hell, could I just declare another static array for the heap, point my memory allocator at it, and be done with it that way? Or would that break paging?
I'm still not sure if you're talking about allocating physical memory or virtual address space. Declaring a static array would accomplish both, though.
Ethin wrote:When I go to map pages when the parent entry is a huge page, do I just skip those that are already mapped because the (parent) page is a huge page?
Only if the huge page already corresponds to the correct virtual-to-physical translation. But why are you trying to map something at an address where the bootloader has already mapped something?
Ethin
Member
Member
Posts: 625
Joined: Sun Jun 23, 2019 5:36 pm
Location: North Dakota, United States

Re: Page Fault and General Protection Fault

Post by Ethin »

Octocontrabass wrote:
Ethin wrote:I'm trying to smartly allocate my heap in such a way that it doesn't overwrite my kernel and doesn't get set up in the higher half memory range where physical memory is located (that would be bad).
You're talking about allocating virtual address space for the heap, right? Is overlapping MMIO okay? If it is, then you just need to find a higher-half address that doesn't overlap a memory map entry (after adding the HHDM offset) or your kernel. You might also want to avoid the first 4GB of the HHDM area just because it's already mapped and you would need to flush the TLB after changing the mapping to accommodate your heap.
Ethin wrote:This is my (very inefficient!) initial implementation, which I need to figure out a better version of:
It looks like this code is specifically looking for an address that overlaps a memory map entry that isn't free memory?
Ethin wrote:However, if my page tables are completely empty, which appears to be the case if qemu's "info mem" command isn't outputting anything, I assume I can just let my kernel put it anywhere within the virtual memory space?
If your page tables are completely empty, you're triple-faulting. I've found QEMU's "info mem" and "info tlb" are not 100% accurate, but they usually fail in different ways so they're still useful for debugging.
Ethin wrote:Hell, could I just declare another static array for the heap, point my memory allocator at it, and be done with it that way? Or would that break paging?
I'm still not sure if you're talking about allocating physical memory or virtual address space. Declaring a static array would accomplish both, though.
Ethin wrote:When I go to map pages when the parent entry is a huge page, do I just skip those that are already mapped because the (parent) page is a huge page?
Only if the huge page already corresponds to the correct virtual-to-physical translation. But why are you trying to map something at an address where the bootloader has already mapped something?
Overlapping MMIO probably isn't okay. I don't know if that would deny me access to the actual region later. And yes, we're talking about virtual addresses, not physical. I (think) that Stivale performs an identity mapping... The spec isn't exactly clear on how the page tables are set up. And the Limine documentation doesn't specify that either from what I can find.
The code searches for a memory entry that's marked as usable. So not bad RAM, not reserved memory, and not kernel/module memory. And not bootloader reclaimable because (for now) I'd like to keep all that around since I don't know if I'll need it later. I've changed it slightly so it just finds the first available entry that's larger than the maximum heap size and just uses that one. I was attempting to do something smarter for security reasons, but I'm pretty sure that the randomize limine option and KASLR already are doing a pretty good job. (Granted, both can be overcome, but I don't want to get overly bogged down in security and not kernel advancement. Tough line to walk, that is.)
My page tables aren't empty (anymore); info mem still doesn't say anything but info tlb is full of stuff. Not sure why info mem is still failing when I'm not suffering either a triple fault or exception of any kind. Just an assertion failure that I can't seem to find the cause of that happens when I go working with ACPI now. And GDB still not working either. That's the most annoying part of this venture -- that I can't use a damn debugger to set breakpoints, and I have absolutely no idea where to even begin implementing support for software breakpoints. There's a crate for implementing GDB support on the software side but its very architecture-agnostic and I don't know how to adapt it appropriately for me to use the debug registers and interrupts in a coordinated way to make a smooth debugger experience.
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: Page Fault and General Protection Fault

Post by Octocontrabass »

Ethin wrote:Overlapping MMIO probably isn't okay. I don't know if that would deny me access to the actual region later.
You can always map the MMIO to a different virtual address later, you just wouldn't be able to use the HHDM area to access it.
Ethin wrote:I (think) that Stivale performs an identity mapping... The spec isn't exactly clear on how the page tables are set up. And the Limine documentation doesn't specify that either from what I can find.
In the lower half, the entire first 4GB of the physical address space is identity-mapped, as well as any memory map entries above 4GB.

In the higher half, all of those mappings are repeated. The difference between physical and virtual address is a fixed value. This makes it possible to translate a physical address to a virtual address, but only if the physical address is below 4GB or described in the memory map.

The memory containing your kernel is mapped a third time somewhere in the highest 2GB.
Ethin wrote:The code searches for a memory entry that's marked as usable. So not bad RAM, not reserved memory, and not kernel/module memory.
Okay, so I was just reading it wrong. Is there any particular reason why you chose direct-mapped memory for the heap instead of a randomized virtual address and random pages of physical memory?
Ethin wrote:And not bootloader reclaimable because (for now) I'd like to keep all that around since I don't know if I'll need it later.
You need to preserve it as long as you're using the bootloader's page tables, or the information structures it provides, or the runtime terminal it provides. You can reclaim it as ordinary memory once you're done with those things.
Ethin wrote:I was attempting to do something smarter for security reasons, but I'm pretty sure that the randomize limine option and KASLR already are doing a pretty good job. (Granted, both can be overcome, but I don't want to get overly bogged down in security and not kernel advancement. Tough line to walk, that is.)
If you limit the range of the random number using the highest usable offset instead of the highest offset within the memory area, you won't need a loop.
Ethin wrote:And GDB still not working either. That's the most annoying part of this venture -- that I can't use a damn debugger to set breakpoints, and I have absolutely no idea where to even begin implementing support for software breakpoints.
Out of curiosity, what command do you type into GDB to set the breakpoint?
Ethin
Member
Member
Posts: 625
Joined: Sun Jun 23, 2019 5:36 pm
Location: North Dakota, United States

Re: Page Fault and General Protection Fault

Post by Ethin »

Octocontrabass wrote:
Ethin wrote:Overlapping MMIO probably isn't okay. I don't know if that would deny me access to the actual region later.
You can always map the MMIO to a different virtual address later, you just wouldn't be able to use the HHDM area to access it.
Ah, okay.
Ethin wrote:I (think) that Stivale performs an identity mapping... The spec isn't exactly clear on how the page tables are set up. And the Limine documentation doesn't specify that either from what I can find.
In the lower half, the entire first 4GB of the physical address space is identity-mapped, as well as any memory map entries above 4GB.

In the higher half, all of those mappings are repeated. The difference between physical and virtual address is a fixed value. This makes it possible to translate a physical address to a virtual address, but only if the physical address is below 4GB or described in the memory map.

The memory containing your kernel is mapped a third time somewhere in the highest 2GB.
So what your saying is that anything that's in the memory map, or anything below the 4GB area, is identity mapped. Those mappings are repeated again in the higher half, with a special offset (I'm assuming that's the HHDM offset). So as long as I access anything below address 0x100000000 (I think), or as long as the address I'm accessing is within the memory map, I can use the HHDM address to access the physical address, without having to explicitly map the address first?
Ethin wrote:The code searches for a memory entry that's marked as usable. So not bad RAM, not reserved memory, and not kernel/module memory.
Okay, so I was just reading it wrong. Is there any particular reason why you chose direct-mapped memory for the heap instead of a randomized virtual address and random pages of physical memory?
The only reason I didn't do this (via, say, just an execution of RDRAND and then random address + size) was so I could ensure that the random address wasn't in the higher half where I was risking accessing physical memory, and the random address wasn't within my kernel where I might accidentally overwrite code or data. Maybe I'm over-complicating it though.
Ethin wrote:And not bootloader reclaimable because (for now) I'd like to keep all that around since I don't know if I'll need it later.
You need to preserve it as long as you're using the bootloader's page tables, or the information structures it provides, or the runtime terminal it provides. You can reclaim it as ordinary memory once you're done with those things.
I'm not exactly good at setting up page tables manually, even though I've tried to grasp all the concepts; the bootloader-provided ones might not be the best but they're simpler, I feel. I know I'll have to set up page tables eventually when I go to run executables and loading libraries and such though.
Ethin wrote:I was attempting to do something smarter for security reasons, but I'm pretty sure that the randomize limine option and KASLR already are doing a pretty good job. (Granted, both can be overcome, but I don't want to get overly bogged down in security and not kernel advancement. Tough line to walk, that is.)
If you limit the range of the random number using the highest usable offset instead of the highest offset within the memory area, you won't need a loop.
Can you explain this more? I assume a modulo is involved somewhere?
Ethin wrote:And GDB still not working either. That's the most annoying part of this venture -- that I can't use a damn debugger to set breakpoints, and I have absolutely no idea where to even begin implementing support for software breakpoints.
Out of curiosity, what command do you type into GDB to set the breakpoint?
I just type hb <file>:<line>. I also use target remote :1234, so my workflow is something like:
  1. gdb target/boot/EFI/BOOT/kernel
  2. target remote :1234
  3. hb libk/src/acpi.rs:20 (for example)
  4. c
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: Page Fault and General Protection Fault

Post by Octocontrabass »

Ethin wrote:So as long as I access anything below address 0x100000000 (I think), or as long as the address I'm accessing is within the memory map, I can use the HHDM address to access the physical address, without having to explicitly map the address first?
Correct. Typical OSes use lower-half addresses for userspace, so the HHDM area provides the convenience of identity-mapping without getting in the way of that.
Ethin wrote:The only reason I didn't do this (via, say, just an execution of RDRAND and then random address + size) was so I could ensure that the random address wasn't in the higher half where I was risking accessing physical memory, and the random address wasn't within my kernel where I might accidentally overwrite code or data. Maybe I'm over-complicating it though.
It would be more complicated to compare your random address against the addresses you need to avoid, but I don't think it would be too much more difficult.
Ethin wrote:Can you explain this more? I assume a modulo is involved somewhere?
Yep. Instead of doing modulo by the size of the range, you'd do modulo by the size of the range minus the size of the heap.
Ethin wrote:I just type hb <file>:<line>. I also use target remote :1234, so my workflow is something like:
So you're not telling GDB the randomized base address of your kernel?
Ethin
Member
Member
Posts: 625
Joined: Sun Jun 23, 2019 5:36 pm
Location: North Dakota, United States

Re: Page Fault and General Protection Fault

Post by Ethin »

Octocontrabass wrote:
Ethin wrote:So as long as I access anything below address 0x100000000 (I think), or as long as the address I'm accessing is within the memory map, I can use the HHDM address to access the physical address, without having to explicitly map the address first?
Correct. Typical OSes use lower-half addresses for userspace, so the HHDM area provides the convenience of identity-mapping without getting in the way of that.
Ethin wrote:The only reason I didn't do this (via, say, just an execution of RDRAND and then random address + size) was so I could ensure that the random address wasn't in the higher half where I was risking accessing physical memory, and the random address wasn't within my kernel where I might accidentally overwrite code or data. Maybe I'm over-complicating it though.
It would be more complicated to compare your random address against the addresses you need to avoid, but I don't think it would be too much more difficult.
Ethin wrote:Can you explain this more? I assume a modulo is involved somewhere?
Yep. Instead of doing modulo by the size of the range, you'd do modulo by the size of the range minus the size of the heap.
Ethin wrote:I just type hb <file>:<line>. I also use target remote :1234, so my workflow is something like:
So you're not telling GDB the randomized base address of your kernel?
No, I assumed that it would know that. It seemed to just know that before I switched bootloaders... Though it was built without PIC at that time, and now it isn't. And the symbols aren't separate, so I'm not sure how I'd do that anyway. I know how to do it with UEFI binaries but not with ELF ones.
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: Page Fault and General Protection Fault

Post by Octocontrabass »

Ethin wrote:It seemed to just know that before I switched bootloaders...
Were you using ASLR before you switched bootloaders?
Ethin wrote:And the symbols aren't separate, so I'm not sure how I'd do that anyway.
The GDB manual suggests loading the binary using the "symbol-file" command with the "-o" parameter to specify the runtime offset. You could also temporarily disable ASLR to verify GDB works correctly when your kernel is located where GDB expects to find it.
Post Reply