QEMU Multiboot Invalid Memory Map

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.
Post Reply
brandonto
Posts: 3
Joined: Thu Apr 28, 2016 9:18 am

QEMU Multiboot Invalid Memory Map

Post by brandonto »

I've been struggling for this for over a week and a half...

I'm trying to parse the multiboot structure passed by the bootloader to my kernel in order to implement memory allocation for paging. I've followed the example from the multiboot specifications (https://www.gnu.org/software/grub/manua ... rnel_002ec), but the memory map from GRUB is invalid... all I'm getting is type = 0. I've tried booting from both bin (qemu-system-i386 -kernel myos.bin) and iso (qemu-system-i386 -cdrom myos.iso) but none of them give me a correct memory map.

Here is the output

Image

Here is the assembly that calls kernel_main:

Code: Select all

_start:
    ; To set up a stack, we simply set the esp register to point to the top of
    ; our stack (as it grows downwards).
    mov esp, stack_top

    ; Push pointer to the Multiboot information structure
    push ebx

    extern kernel_main
    call kernel_main

    cli
.hang:
    hlt
    jmp .hang

Here is how I'm parsing the multiboot header:

Code: Select all

    if (mbi->flags & MULTIBOOT_INFO_MEMORY)
    {
        vga_printf("mem_lower = %uKB, mem_upper = %uKB\n",
                (uint32_t)mbi->mem_lower, (uint32_t)mbi->mem_upper);
    }

    if (mbi->flags & MULTIBOOT_INFO_MEM_MAP)
    {
        vga_printf("mmap_addr = 0x%x, mmap_length = 0x%x\n",
                (uint32_t)mbi->mmap_addr, (uint32_t)mbi->mmap_length);

        for (mmap = (struct multiboot_mmap_entry*)mbi->mmap_addr;
                (uint32_t)mmap < (mbi->mmap_addr + mbi->mmap_length);
                mmap = (struct multiboot_mmap_entry*)((uint32_t)mmap
                    + mmap->size + sizeof(mmap->size)))
        {
            vga_printf("base_addr_high = 0x%x, base_addr_low = 0x%x, "
                    "length_high = 0x%x, length_low = 0x%x, type = 0x%x\n",
                    mmap->addr >> 32,
                    mmap->addr & 0xFFFFFFFF,
                    mmap->len >> 32,
                    mmap->len & 0xFFFFFFFF,
                    (uint32_t)mmap->type);
        }
    }
Anybody have any ideas? I've looked everywhere, but I can't seem to find a solution for this (as I said, I've been struggling on this for a week and a half).
MDenham
Member
Member
Posts: 62
Joined: Sat Nov 10, 2012 1:16 pm

Re: QEMU Multiboot Invalid Memory Map

Post by MDenham »

I suspect your multiboot_mmap_entry struct is aligning uint64_t to 8-byte multiples, which means fields are all getting put in the wrong spots.

Evidence for this is that sane values for length_low are showing up in length_high instead.

You'll want to pack the struct.
brandonto
Posts: 3
Joined: Thu Apr 28, 2016 9:18 am

Re: QEMU Multiboot Invalid Memory Map

Post by brandonto »

MDenham wrote:I suspect your multiboot_mmap_entry struct is aligning uint64_t to 8-byte multiples, which means fields are all getting put in the wrong spots.

Evidence for this is that sane values for length_low are showing up in length_high instead.

You'll want to pack the struct.
Hey, thanks for the response! I took the multiboot header from the specifications... here is the part where they defined the multiboot_mmap_entry structure:

Code: Select all

struct multiboot_mmap_entry
{
    multiboot_uint32_t size;
    multiboot_uint64_t addr;
    multiboot_uint64_t len;
#define MULTIBOOT_MEMORY_AVAILABLE              1
#define MULTIBOOT_MEMORY_RESERVED               2
    multiboot_uint32_t type;
} __attribute__((packed));
typedef struct multiboot_mmap_entry multiboot_memory_map_t;
It seems like they did the structure aligning like you suggested. Is there anything that I'm missing?
MDenham
Member
Member
Posts: 62
Joined: Sat Nov 10, 2012 1:16 pm

Re: QEMU Multiboot Invalid Memory Map

Post by MDenham »

This kind of thing came up in another thread recently, where the compiler didn't appear to be obeying __attribute__((packed)) correctly with respect to uint64_t (and __attribute__((aligned (4))) didn't seem to fix the issue either).

The only suggestion I have is making a copy of the structure that uses 2x uint32_t in place of one uint64_t and then dropping the bitshift/mask stuff. You'll need to put values back together if you're planning on long mode support, but that's easy enough.
brandonto
Posts: 3
Joined: Thu Apr 28, 2016 9:18 am

Re: QEMU Multiboot Invalid Memory Map

Post by brandonto »

MDenham wrote:This kind of thing came up in another thread recently, where the compiler didn't appear to be obeying __attribute__((packed)) correctly with respect to uint64_t (and __attribute__((aligned (4))) didn't seem to fix the issue either).

The only suggestion I have is making a copy of the structure that uses 2x uint32_t in place of one uint64_t and then dropping the bitshift/mask stuff. You'll need to put values back together if you're planning on long mode support, but that's easy enough.
Beautiful. This did the trick! Thanks so much... I can't believe how long I struggled on this for. I can finally get back to developing paging for my kernel. Do you have any insight on why the compiler incorrectly packs the unsigned long long?

Here is the memory map parsed correctly:

Image
MDenham
Member
Member
Posts: 62
Joined: Sat Nov 10, 2012 1:16 pm

Re: QEMU Multiboot Invalid Memory Map

Post by MDenham »

I don't know why it's doing it, to be honest. The closest thing I've found to anything relevant is something involving alignof() and that was declared "not a bug".

I suspect the reason for it might have to do with C++11/14 support, but I'm just spitballing at that point.

EDIT: Not sure what exactly the gcc switches are to get it to give you "here's what your C++ code looks like in assembly", but I can at least confirm that, despite all its other shortcomings, Visual Studio 2015 does not cause this problem to happen by looking at the assembly it produces for essentially that code (different variable names is the main difference). (It does, however, produce ridiculously bloated assembly. Instructions that are effective no-ops like "shl ecx, 0" show up, for example.)
Hellbender
Member
Member
Posts: 63
Joined: Fri May 01, 2015 2:23 am
Libera.chat IRC: Hellbender

Re: QEMU Multiboot Invalid Memory Map

Post by Hellbender »

Are you using MinGW by any change? "IA-32/x86-64 Windows mingw targets are using the -mms-bitfields option by default." Which means packing obeys m$ rules. To force it otherwise, use __attribute__((packed,gcc_struct)) or -mno-ms-bitfields at command line.
Hellbender OS at github.
MDenham
Member
Member
Posts: 62
Joined: Sat Nov 10, 2012 1:16 pm

Re: QEMU Multiboot Invalid Memory Map

Post by MDenham »

Hellbender wrote:Are you using MinGW by any change? "IA-32/x86-64 Windows mingw targets are using the -mms-bitfields option by default." Which means packing obeys m$ rules. To force it otherwise, use __attribute__((packed,gcc_struct)) or -mno-ms-bitfields at command line.
See my edit above: if it were using MS packing rules, it wouldn't be forcing the uint64_t onto an 8-byte boundary despite __attribute__((packed)) (which is what the problem is), because VS2015 code doesn't force that unless requested.

Hell, I don't even think my structure is packed and VS2015's still letting uint64_t's be unaligned. I should go make sure it's marked as packed anyway, just in case.

(That said, I'm pretty sure this is gcc's doing, just because 99% of the people writing code here are compiling with gcc.)
zdz
Member
Member
Posts: 47
Joined: Tue Feb 10, 2015 3:36 pm

Re: QEMU Multiboot Invalid Memory Map

Post by zdz »

MDenham wrote:I don't know why it's doing it, to be honest. The closest thing I've found to anything relevant is something involving alignof() and that was declared "not a bug".

I suspect the reason for it might have to do with C++11/14 support, but I'm just spitballing at that point.

EDIT: Not sure what exactly the gcc switches are to get it to give you "here's what your C++ code looks like in assembly", but I can at least confirm that, despite all its other shortcomings, Visual Studio 2015 does not cause this problem to happen by looking at the assembly it produces for essentially that code (different variable names is the main difference). (It does, however, produce ridiculously bloated assembly. Instructions that are effective no-ops like "shl ecx, 0" show up, for example.)
Careful there, it depends on how you set the optimizations options in the project properties. And some nops may appear because the compiler tries to align the code in some way. It saves you from a lot of trouble to use #pragma pack() on structures that must respect a certain layout.
Note that on some settings the compiler may generate SSE instructions (if you build for x64 it considers that SSE is a given thing) and that might crush your kernel in the early stages, also take care on that.
MDenham
Member
Member
Posts: 62
Joined: Sat Nov 10, 2012 1:16 pm

Re: QEMU Multiboot Invalid Memory Map

Post by MDenham »

zdz wrote:
MDenham wrote:I don't know why it's doing it, to be honest. The closest thing I've found to anything relevant is something involving alignof() and that was declared "not a bug".

I suspect the reason for it might have to do with C++11/14 support, but I'm just spitballing at that point.

EDIT: Not sure what exactly the gcc switches are to get it to give you "here's what your C++ code looks like in assembly", but I can at least confirm that, despite all its other shortcomings, Visual Studio 2015 does not cause this problem to happen by looking at the assembly it produces for essentially that code (different variable names is the main difference). (It does, however, produce ridiculously bloated assembly. Instructions that are effective no-ops like "shl ecx, 0" show up, for example.)
Careful there, it depends on how you set the optimizations options in the project properties. And some nops may appear because the compiler tries to align the code in some way. It saves you from a lot of trouble to use #pragma pack() on structures that must respect a certain layout.
Note that on some settings the compiler may generate SSE instructions (if you build for x64 it considers that SSE is a given thing) and that might crush your kernel in the early stages, also take care on that.
Yeah, right now it's set with no optimizations at all. Didn't think about it trying to align the code, though. That'd explain doubled jumps as well.

I'm still planning on going through the assembly it says it's producing and hand-tuning that, but that's more out of OCD than anything else. :-)
Kevin
Member
Member
Posts: 1071
Joined: Sun Feb 01, 2009 6:11 am
Location: Germany
Contact:

Re: QEMU Multiboot Invalid Memory Map

Post by Kevin »

MDenham wrote:I'm still planning on going through the assembly it says it's producing and hand-tuning that, but that's more out of OCD than anything else. :-)
When you hand-tune the compiler output, never forget to actually measure the results. Because if you don't, chances are that you're making things worse rather than better. (Well, you always make things worse in terms of maintainability, but you seem to care more about performance or code size, and there it's only almost always. Assuming that you do enable optimisations before you criticise the compiler output, of course.)
Developer of tyndur - community OS of Lowlevel (German)
zdz
Member
Member
Posts: 47
Joined: Tue Feb 10, 2015 3:36 pm

Re: QEMU Multiboot Invalid Memory Map

Post by zdz »

Kevin wrote:
MDenham wrote:I'm still planning on going through the assembly it says it's producing and hand-tuning that, but that's more out of OCD than anything else. :-)
When you hand-tune the compiler output, never forget to actually measure the results. Because if you don't, chances are that you're making things worse rather than better. (Well, you always make things worse in terms of maintainability, but you seem to care more about performance or code size, and there it's only almost always. Assuming that you do enable optimisations before you criticise the compiler output, of course.)
Except for some corner cases I don't think one can do better than the compiler. And even then, the gain in speed / size is almost irrelevant.
Apart from curiosity and to rule out some bugs (or to make sure that a specific sequence of instructions is in the order I want it do be) I never look at the generated assembly (it's interesting to compare it for different optimization settings when you first start to play with that). If I need something done in assembly by me I do it in assembly (it's usually profiling code, or some weird stuff).
Post Reply