Rust-based kernel isn't drawing anything to the screen

Said kernel is from this repo in src/kernel/core/main.rs

The bit of code that actually draws to the screen is this:

pub extern "C" fn _start(framebuffer_info: FramebufferInfo, memory_map: *const MemoryDescriptor) -> ! {
    let framebuffer = framebuffer_info.base_address as *mut u32;
    let pitch = framebuffer_info.pitch;

    unsafe {
        *framebuffer.offset((4 * pitch * 20 + 4 * 20) as isize) = 0xFFFFFFFF;
        *framebuffer.offset((4 * pitch * 20 + 4 * 21) as isize) = 0xFFFFFFFF;
        *framebuffer.offset((4 * pitch * 20 + 4 * 22) as isize) = 0xFFFFFFFF;
        *framebuffer.offset((4 * pitch * 20 + 4 * 23) as isize) = 0xFFFFFFFF;
        *framebuffer.offset((4 * pitch * 21 + 4 * 20) as isize) = 0xFFFFFFFF;
        *framebuffer.offset((4 * pitch * 22 + 4 * 21) as isize) = 0xFFFFFFFF;
        *framebuffer.offset((4 * pitch * 23 + 4 * 22) as isize) = 0xFFFFFFFF;
        *framebuffer.offset((4 * pitch * 24 + 4 * 23) as isize) = 0xFFFFFFFF;
    }

    loop {}
}

Which is based on the previous C based kernel (in the same repo and directory mentioned earlier)

int k_main(framebuffer_info_s framebuffer, memory_descriptor_s* memory_map)
{
    framebuf = framebuffer;
    memMap = memory_map;

    *((uint32_t*)(framebuf.base_address + 4 * framebuf.pitch * 20 + 4 * 20)) = 0xFFFFFFFF;
    *((uint32_t*)(framebuf.base_address + 4 * framebuf.pitch * 20 + 4 * 21)) = 0xFFFFFFFF;
    *((uint32_t*)(framebuf.base_address + 4 * framebuf.pitch * 20 + 4 * 22)) = 0xFFFFFFFF;
    *((uint32_t*)(framebuf.base_address + 4 * framebuf.pitch * 20 + 4 * 23)) = 0xFFFFFFFF;
    *((uint32_t*)(framebuf.base_address + 4 * framebuf.pitch * 21 + 4 * 20)) = 0xFFFFFFFF;
    *((uint32_t*)(framebuf.base_address + 4 * framebuf.pitch * 22 + 4 * 21)) = 0xFFFFFFFF;
    *((uint32_t*)(framebuf.base_address + 4 * framebuf.pitch * 23 + 4 * 22)) = 0xFFFFFFFF;
    *((uint32_t*)(framebuf.base_address + 4 * framebuf.pitch * 24 + 4 * 23)) = 0xFFFFFFFF;

    while(true) {}
}

Which does work.

What is it about the Rust code that causes the same operation to not work?
I have genuinely no idea. Is it a toolchain problem? (I've had quite a few of those, so I wouldn't be surprised.) Is it me being an idiot and not understanding how to translate the operation in C into Rust syntax?

Edit: I figured out why it isn't displaying anything. In the "serial0" console in QEMU it's displaying a "Invalid Opcode" error. This sounds like a toolchain problem, but what it could be, I have no idea.
Here's a screenshot of the error:

Abort is commonly implemented as an invalid opcode, since that halts the CPU. (I'm not a fan.)

I'd check for paths that could panic, I guess? Hard without any output, of course.

I find it highly unlikely this is an abort, as in a second run I got a difference between the kernel address and the error RIP that was greater than the file size. Only 2 ways that I can think of that could cause that to occur would be execution somehow escaping the kernel, or the bootloader jumping to the wrong address. Which, since I had this bootloader working with the previous C kernel, I find unlikely.

pointer::offset() takes its argument in units of size_of::<T>().

You are multiplying by 4, i.e. size_of::<u32>(), apparently under the impression that the argument is a byte offset, no?

AFAICT, the framebuf.base_address in your C code is just a 64-bit integer, not even a pointer. So pointer arithmetic isn't even in play. The C is just treating a plain old int as a byte pointer. That won't work in Rust with pointer::offset()[1]. The offset method arg is more like indexing an array of type T.


  1. You could get away with it if you did the same "cast this int as a pointer" trick as in the C code. Not that it's recommended, but it's something that can be done with caution. ↩ī¸Ž

1 Like

Then this is much easier, all that mess of pointer arithmetic in the C version is to index a 1d array aligned to u8 offsets (direct access to RAM should behave as if it was that) as a 2d array aligned to u32 offsets. Even then, "Invalid Opcode" error mentioned couldn't have come from that, right? Having a wrong operation with no jumps in it shouldn't cause a "Invalid Opcode" error.

Depending of where your writes end up, you might overwrite some of the code and the written values do not represent valid code anymore.

Although I would assume the kernel to kill your process (executable memory is usually mapped as read only) if this happened.

The UEFI page type I have the kernel loaded in is "EfiLoaderCode", which I assume is marked as executable, but I'm not sure about that.

Wait. Did you just say "the kernel will kill your process"?? This is the kernel.

Ah yeah. I didn't read carefully enough and jumped from " drawing" to a computation kernel usually used in graphics programming ^^' in your case the kernel will obviously not kill the process.

You might however still run into some interrupt that gets called when an unknown instruction is executed or a different interrupt when read only memory is written to. Can you run the code with a debugger?

I actually tried that, I couldn't get GDB to trigger breakpoints anywhere near the error location for some reason. And I have no idea if it's possible to make it so GDB stops execution when int 6 is triggered, so I can backtrace and figure out how execution got there.

I think I would try the two following things:

  1. find out where the interrupt 6 is located on x86 (on arm this is standardized, I'm not sure about x86) and put a breakpoint there. From gdb perspective there shouldn't be a difference between a breakpoint in "normal" code and interrupts.

  2. put a breakpoint into the entry point of the kernel and manually step through the code to find out where things go wrong.

(post deleted by author)