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)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.