UEFI Memory Map and standard library (and strided arrays)

I’ve been looking into getting GitHub - nrdmn/uefi-examples: UEFI examples in Zig to compile on 0.13. Looks fairly straightforward, but memory map failed because of unknown enum and the memory map printout looks like complete garbage. So I looked into it some more and turns out zig standard library misinterprets UEFI memory map table.
The standard library defines:

pub const MemoryDescriptor = extern struct {
    type: MemoryType, //enum(u32)
    physical_start: u64,
    virtual_start: u64,
    number_of_pages: u64,
    attribute: MemoryDescriptorAttribute, //packed struct(u64) 
};
//...
getMemoryMap: *const fn (mmap_size: *usize, mmap: ?[*]MemoryDescriptor, mapKey: *usize, descriptor_size: *usize, descriptor_version: *u32) callconv(cc) Status

But as UEFI specs goes, mmap isn’t really [*]MemoryDescriptor, because new UEFI versions may (and do) introduce extra fields. That’s why there is an extra output argument, descriptor_size, that would contain actual stride of mmap.

zig std.os.uefi implicitly assumes

descriptor_size==@sizeOf(MemoryDescriptor)==40

which doesn’t hold on QEMU OVMF (descriptor_size==48) and probably not on real hardware either.

My question is, how do I express this idiomatically with zig? On C I would just use something like

(MemoryDescriptor*)(((uint8_t*)mmap)+i*descriptor_size)

But zig complains about possible misalignment (maybe rightfully so). So I’m forced to write:


@memcpy( @as(*[@sizeOf(std.os.uefi.tables.MemoryDescriptor)]u8, @ptrCast(&d)),
    @as(*[@sizeOf(std.os.uefi.tables.MemoryDescriptor)]u8,
    @ptrFromInt(@intFromPtr(memory_map)+i*descriptor_size)));

Which is honestly not very fun.

1 Like

This looks like something that would probably be best handled as a git issue, tbh. That @memcpy looks like it should work because of the extern on the struct, but you could probably take this up with the standard library implementers.

However, in the meantime, you could also look at using std.mem.asBytes:

/// Given a pointer to a single item, returns a slice of the underlying bytes, preserving pointer attributes.
pub fn asBytes(ptr: anytype) AsBytesReturnType(@TypeOf(ptr)) {
    return @ptrCast(@alignCast(ptr));
}

Thanks for the tip, Andrew.
I mean yes, the right solution would be to fix the std. But is there a way to express strided array indexing idiomatically, or would std just have to do the same asBytes kludge internally as well? (might be too ambitious of me, but I’m aiming for opening a pull requests rather than an issue just because it doesn’t looks like this piece of std gets much attention).

That’s a great question, really. Yes, there are ways to do this in a more idiomatic style. As you’ve probably noticed, raw pointer arithmetic is harder than one might think. However, assume that you’ve converted both pointers to bytes - you can use slice manipulation to get you there. Here’s an example of doing a double slice over the bytes:

const bytes_a = std.mem.asBytes(&a);
// you can now offset into a using slices
// begin at byte offset m and copy up to n:
const sub_slice = bytes_a[m..][0..n];

That’s one way to express something similar to:

const sub_slice = bytes_a[m..m+n];

In this case, the second one is a bit more brief, but we’re just talking options here.

I’d recommend this over going to an integer first then casting back up to a pointer if you want to advance through bytes. As you’ve noticed, the language gets quite cumbersome otherwise.

And of course, you can combine this:

const sub_slice = std.mem.asBytes(&a)[m..m+n];
3 Likes

Thank you so much.

1 Like

Of course - and welcome to Ziggit.