#Memory Address seems to bypass nullcheck

1 messages · Page 1 of 1 (latest)

gloomy quarry
#

A lot of this is OS-Dev specific, so bear with me here. I'm on the freestanding target for 32 bit x86 using the multiboot1 specification to boot up my kernel. The important part there if you're unfamiliar with the spec (tbh so am I) is that once my entry point is called by the boot loader, EBX is guaranteed to have a pointer to a multiboot structure. When I use grub-legacy, this works surprisingly well (more explanation incoming)

#

However, when I use limine (just a different bootloader that follows the same spec) I get a pointer val of 0 passed to ⁨setup⁩. The super weird thing is that manually looking at EBX in the ⁨trampoline⁩ fn, I can clearly see ebx has the proper address of a struct (0x10000 here, grub just works differently AFAIK). There are 2 weird things here (the title will start to make sense now)

#

The pointer being set to 0x0 makes no sense, as that bypasses safe mode's null cast check. I know this to be true because 1) I have kernel panic properly set up, and this doesn't trigger the panic, and 2) I can see the null check in the assembly

#

Even weirder, is I hardcode that 0x1000 address when using limine (again, which I got from manually breakpoint-ing at ⁨trampoline⁩ and looking at ebx), I get proper behavior in terms of getting the proper pointer (and ofc the structure being pointed to makes sense now)

#

Am I somehow pulling the EBX value in ⁨trampoline⁩ incorrectly?

vestal totem
#

the compiler seems to just not safety check the result of inline assembly, which tbh is fair since its unsafe to begin with.

But it probably should check that the output(s) are valid values for their type since it is akin to a cast/reinterpreting of the data.

you can get safety checks by changing the result type to be ?* or usize then converting that to a non optional pointer.

wide otter
#

As in, I would store ebx as a usize first, then cast to ptr, and same thing

#

I would even see the nullcheck pass at that call site (trampoline) but when passed to setup, it was zero

vestal totem
#

I have to ask what mode you are compiling with, also what target you are using

wide otter
#

ReleaseSafe, x86-freestanding-none

wide otter
night osprey
#

Does it work if you relax the ={ecx} constraint to =r

wide otter
#

Same issue

night osprey
#

But for the "correct solution" you shouldn’t expect any register to be in any state in a non-naked function

#

The best choice is usually to make trampoline a callconv something well defined (like sysv) and then in your inline asm to jump out of naked, place the arguments in the right registers and use trampoline like a normal function

gloomy quarry
#

and sanity checked even using usize and casting into a ptr

gloomy quarry
#

I get ebx can't be guaranteed to be preserved

night osprey
#

nothing wrong, but zig makes no guarantee that ebx will not be clobbered before the first line of trampoline

gloomy quarry
#

but I can see it is preserved in the generated asm

#

so yeah I should probably address that, but I'm still perplexed with why my bound variable isn't keeping the value in ebx

night osprey
#

without the disassembly of setup theres really no way to say for certain

gloomy quarry
#

and also that the null check isn't triggering a panic

#

setup is quite beefy now, but I check the address at this point in setup

night osprey
#

I'd recommend reducing that down to the minimal code that reproduces the issue because 6500 lines of asm is not exactly quickly debuggable

wide otter
#

I’ll give that a shot in the AM

night osprey
#

also, as for how I would clean up the trampoline:

export fn boot() linksection(".boot") callconv(.naked) noreturn {
  asm volatile(
    \\ movl %[stack_top], %esp
    \\ pushl %ebx
    \\ pushl %eax
    \\ jmp *%[trampoline]
    :
    : [stack_top] "i" (stack_top),
      [trampoline] "r" (&trampoline),
  );
}

fn trampoline(mbmagic: u32, mbinfo: *const MultibootInfo) linksection(".trampoline") callconv(.{ .x86_sysv = .{} }) noreturn {
  assert(mbmagic == 0x2BADB002);

  @import("setup.zig").setup(mbinfo);
}
wide otter
#

Oh the magic num

#

I have the whole mb structure baked in at compile time anyway

#
/// Header to mark our kernel as bootable. Will be placed at the beginning of
/// our kernel's binary, and will be interpretted by the bootloader as the header
/// of bytes defining how the kernel will be booted.
pub export const multiboot_header linksection(".multiboot") = switch (bootoptions.boot_specification) {
    .MultibootOne => bootutils.MultiBoot.V1.init(
        .{
            .flags = .{
                .enforce_all_4kb_alignment = false,
                .include_memory_information = false,
                .include_video_mode_info = true,
                .activate_address_configurations = false,
            },
            .header_addr = undefined,
            .load_addr = undefined,
            .load_end_addr = undefined,
            .bss_end_addr = undefined,
            .entry_addr = undefined,
        },
        .{ .height = 25, .width = 80, .mode_type = .ega_text, .depth = 0 },
    ),
    else => |e| @compileError(
        "(Currently) Unsupported boot specification for x86: " ++ @tagName(e),
    ),
};
night osprey
#

the magic number comes from the bootloader, you should generally check it to ensure that you actually got multiboot loaded

wide otter
#

Oh the bootloader side

#

I thought you were referencing the kernel side, gotcha

gloomy quarry
#

So getting rid of all that code between the start of ⁨setup⁩ and when I first use the address masks the problem

#

That code being setting up the IDT and GDT

#

Though I don't see anything fishy with either of these, at least immediately

gloomy quarry
#

... what the, it was my SSE causing me issues, limine works just fine like this. I can't see what's wrong with my enabling of SSE, I pulled this from the wiki