UEFI read file strange things are happening

Hello all,

I recently started writing a somehow basic bootloader. Now, the most things are working, but while reading the kernel file, UEFI greets me with BdsDxe: failed to start Boot0001 'UEFI QEMU HARDDISK QM00004' from PciRoot(0x0) and Pci(0x1,0x1)/Ata(Secondary,Slave,0x0): Aborted. After that, it tries to start PXE via IPv4 and after that IPv6 and then it starts the UEFI Interactive Shell.
My current code for the file reading:

const std = @import("std");
const uefi = std.os.uefi;
const puts = @import("./puts.zig").puts;

pub fn usizeToString(value: usize) [20]u8 {
    var buffer: [20]u8 = undefined;
    var index: u8 = 19;
    var value_working = value;
    while (value_working > 0) : (index -= 1) {
        const digit = @as(u8, @intCast(value_working % 10));
        buffer[index] = digit + '0';
        value_working /= 10;
    }
    return buffer;
}

pub fn loadKernel(path: [*:0]const u16, image: *[]const u8) uefi.Status {
    // locate file system protocol
    const boot_services = uefi.system_table.boot_services.?;
    var file_system_protocol: ?*uefi.protocol.SimpleFileSystem = undefined;
    var status: uefi.Status = uefi.Status.Success;
    puts("DEBUG: Locating file system protocol\r\n");
    status = boot_services.locateProtocol(&uefi.protocol.SimpleFileSystem.guid, null, @as(*?*anyopaque, @ptrCast(&file_system_protocol)));
    if (status != uefi.Status.Success) {
        puts("ERROR: Cannot locate file system protocol\r\n");
        return status;
    }
    // open file system root
    puts("DEBUG: Opening file system root\r\n");
    var root: *const uefi.protocol.File = undefined;
    status = file_system_protocol.?.openVolume(&root);
    if (status != uefi.Status.Success) {
        puts("ERROR: Cannot open root volume\r\n");
        return status;
    }
    // locate file
    puts("DEBUG: Locating file\r\n");
    var file: *const uefi.protocol.File = undefined;
    status = root.open(&file, path, uefi.protocol.File.efi_file_mode_read, uefi.protocol.File.efi_file_read_only);
    if (status != uefi.Status.Success) {
        puts("ERROR: Cannot locate file\r\n");
        return status;
    }
    // get file info
    puts("DEBUG: Getting file info\r\n");
    const file_info_buf_len = @sizeOf(uefi.FileInfo) + 512;
    var file_info_size: usize = file_info_buf_len;
    var file_info_buf: [file_info_buf_len]u8 align(@alignOf(uefi.FileInfo)) = undefined;
    status = file.getInfo(&uefi.FileInfo.guid, &file_info_size, &file_info_buf);
    switch (status) {
        uefi.Status.Success => {
            puts("DEBUG: Getting file info worked\r\n");
        },
        uefi.Status.BufferTooSmall => {
            puts("DEBUG: BufferTooSmall; Handling comes later\r\n");
        },
        else => {
            puts("ERROR: Cannot get file info\r\n");
            return status;
        }
    }
    // if (status != uefi.Status.Success) {
    //     puts("ERROR: Cannot get file info\r\n");
    //     return status;
    // }
    const file_info: *uefi.FileInfo = @ptrCast(@alignCast(&file_info_buf));
    var image_buffer: [*]u8 = undefined;
    // allocate space
    puts("DEBUG: Allocating space for file\r\n");
    status = boot_services.allocatePool(uefi.tables.MemoryType.LoaderData, @as(u64, @intCast(file_info.file_size)), @as(*[*]align(8) u8, @ptrCast(&image_buffer)));
    puts("DEBUG: Allocated ");
    puts(&usizeToString(file_info.file_size));
    puts(" bytes of RAM for the file\r\n");
    if (status != uefi.Status.Success) {
        puts("ERROR: Cannot allocate RAM for file\r\n");
        return status;
    }
    // read file
    puts("DEBUG: Reading file\r\n");
    status = file.read(@as(*usize, @ptrFromInt(@as(usize, @intCast(file_info.file_size)))), image_buffer);
    if (status != uefi.Status.Success) {
        puts("ERROR: Cannot read file\r\n");
        return status;
    }
    // save in pointers
    image.* = image_buffer[0 .. file_info.file_size];

    return status;
}

In the main function, the return code of the bootloader function (which starts the loadKernel function) is put out. But somehow, the bootloader does not even get there (it also would not get there if everything would work, but then it would display some other messages). So just while reading the file something strange happens…
What am I doing wrong?

I already wrote about this in a slightly older topic, but I did a new topic to just mark that the old topic is solved.

Looking forwards,
Samuel Fiedler

pub fn usizeToString(value: usize) [20]u8 {
    var buffer: [20]u8 = undefined;
    var index: u8 = 19;
    var value_working = value;
    while (value_working > 0) : (index -= 1) {
        const digit = @as(u8, @intCast(value_working % 10));
        buffer[index] = digit + '0';
        value_working /= 10;
    }
    return buffer;
}

This is probably not the cause for your problems, but this function is not correct. If you need your string to be zero-terminated (to interoperate with C?), you need to put that \0 in there on pos 19. Independent of that, you should change the return type to return a slice, and correctly slice the buffer. Something like

return buffer[index+1 .. 19-index];

Beware of off-by-one and border conditions. This code will not work if value is zero.

The zero-terminated thing comes important when the outputString function from UEFI is called, but that is already handled in my puts.zig:

const std = @import("std");
const uefi = std.os.uefi;

var con_out: *uefi.protocol.SimpleTextOutput = undefined;
var alreadyCalledPuts: u8 = 0;

pub fn puts(msg: []const u8) void {
    if (alreadyCalledPuts == 0) {
        con_out = uefi.system_table.con_out.?;
        _ = con_out.reset(false);
        alreadyCalledPuts = 1;
    }
    for (msg) |c| {
        const c_ = [1:0]u16{ c };
        _ = con_out.outputString(@as(*const [1:0]u16, &c_));
    }
}

I updated the usizeToString function, it looks now as follows:

pub fn usizeToString(value: usize) []u8 {
    var buffer: [20]u8 = undefined;
    var index: u8 = 19;
    var value_working = value;
    if (value > 0) {
        while (value_working > 0) : (index -= 1) {
            const digit = @as(u8, @intCast(value_working % 10));
            buffer[index] = digit + '0';
            value_working /= 10;
        }
    } else {
        index = 18;
        buffer[index] = '0';
    }
    return buffer[index+1 .. 19-index];
}

And it’s called just puts(usizeToString()) without the &. But now, the strange things described above are just happening while trying to put out the file_info.file_size. It looks like the following screenshot:

What is this all about?
Does it just mean that I should not try to program a bootloader + a (very simple hello world) kernel? :wink:

Sorry I cannot help you with the UEFI part, I have zero experience with that.

You are still not null-terminating your string in usizeToString; again, if this is required somewhere else, you need to do buffer[19] = 0 somewhere in the function. Also, the function return type should be const []u8.

    var buffer: [20]u8 = undefined;
    ...
    return buffer[index+1 .. 19-index];

I can’t speak for the memory guarantees regarding the situation you’re in, but it seems to me like you’re returning a slice to a temporary buffer?

Since a slice is just a fat pointer, is there a reason to believe (in your situation) that this memory is still valid after you leave the function scope?

Very good point. Perhaps that was the problem for a segfault or whatever and then this happened…

Right so, if I were you, I would flip it around:

pub fn usizeToString(value: usize, buffer: [] u8) void { // possibly !void

You could even make it a pointer to a fixed size buffer… something like *[20]u8 to be more explicit about the size requirement.

And just use this buffer for everything?
But it was just a debugging function to ensure that the file size is recognized properly…
So principally we could just leave that out.

1 Like

I may not be understanding your last question, but typically for char style conversions, I’ve seen it done something like this for non-allocating functions:

    var value: usize = 42;
    var buffer: [20]u8 = undefined;
    usizeToString(value, &buffer);
    // use buffer for stuff...

You can see a similiar calling convention in C++ for their “std::to_chars” function. Much the same idea.

1 Like

You actually pointed me to the issue, I think.
I have exactly the same problem as in the usizeToString in the loadKernel function because the kernel image is stored in a function internal buffer and then there is just the reference…
I didn’t even get that idea…
The first programming language I learned was JavaScript. Garbage collectors by the browser. I really need to get away from that JavaScript stuff :sweat_smile:
Thank you so much for even enriching my ZIG language comprehension. Let me guess now, allocators keep things even after the function and delete them only explicitely with freePool or whatever?

1 Like

That is indeed one of the things they do :slight_smile:

See also:

  1. Optionally reducing memory allocations

  2. Allocator Questions

  3. Need clarification regarding allocators

Nothin’ wrong with starting from somewhere - glad I could help you with your question.

LOL. You are absolutely right, @AndrewCodeDev – I am a doofus for not seeing this. Good catch!

1 Like

By the power of Grayskull, I have changed the space-time-continuum to edit this quote:

2 Likes