Mapping [64]u8 buffer to a struct

Hey, hey.

I have been banging my head how to map a buffer to a struct like you do in C with fread. I have been trying to use mem.copy, doing pointer casting etc. without any luck. Also, since the language is still in development, a lot of examples don’t work with version 0.13.

There must be an intuitive straightforward way of doing this, and I’m just not seeing it :slight_smile: and we all know the tunnel vision you get when something just isn’t working the way you want it to :slight_smile:

The code below reads the first 64 bytes of elf file, and I would like to map this onto a Elf64ExecutionHeader struct. The file is being read, and I can print it out without any issues.

const std = @import("std");

const Elf64ExecutionHeader = struct {
    ident: [16]u8,
    type: u16,
    machine: u16,
    version: u32,
    entry: u64,
    phoff: u64,
    shoff: u64,
    flags: u32,
    ehsize: u16,
    phentsize: u16,
    phnum: u16,
    shentsize: u16,
    shnum: u16,
    shstrndx: u16,
};

pub fn main() !void {
    var file = try std.fs.cwd().openFile("./elf", .{});
    defer file.close();

    var buffer: [64]u8 = undefined;
    _ = try file.read(buffer[0..]);

    std.debug.print("{s}\n", .{buffer[0..]});
}

Any help would be appreciated. Finding up-to-date resources on the interwebs is quite challenging.

You can @bitCast() easily once you have a packed struct.

1 Like

not sure if this is absolutely correct, but works

    const h: *Elf64ExecutionHeader = @ptrCast(@alignCast(&buffer));
    std.debug.print("{}\n", .{h.type});
1 Like

I guess in this case the struct must be packed, but…

$ zig build-exe elf.zig 
elf.zig:4:12: error: packed structs cannot contain fields of type '[16]u8'
    ident: [16]u8,
           ^~~~~~
elf.zig:4:12: note: type has no guaranteed in-memory representation

@dee0xeed I got the same error if I changed struct to packed struct. I am trying to skip first 16 bytes right now :slight_smile:

I tried doing @bitCast() before but got completely lost, so I gave up on that idea :slight_smile:

… incorrectly :frowning:

for itself the program prints

$ ./elf ./elf
64 (0x40)

but bytes 16,17 are 02 00.

When I try with readelf -h elf I seem to have Version what Entry point address should be.

I tried to replace ident: [16]u8 with a: u64, b: u64, to work around this and the program just crashes:

$ ./elf
thread 74865 panic: incorrect alignment
/home/zed/2-coding/zig-lang/elf/elf.zig:28:38: 0x10358f7 in main (elf)
    const h: *Elf64ExecutionHeader = @ptrCast(@alignCast(&buffer));
                                     ^
/opt/zig-0.14/lib/std/start.zig:616:37: 0x103565f in posixCallMainAndExit (elf)
            const result = root.main() catch |err| {
                                    ^
/opt/zig-0.14/lib/std/start.zig:240:5: 0x103524d in _start (elf)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)

That is a clever way of getting around it.

I just tried doing something similar.

const std = @import("std");

const Elf64ExecutionHeader = packed struct {
    // ident: [16]u8,
    ident: u128,
    type: u16,
    machine: u16,
    version: u32,
    entry: u64,
    phoff: u64,
    shoff: u64,
    flags: u32,
    ehsize: u16,
    phentsize: u16,
    phnum: u16,
    shentsize: u16,
    shnum: u16,
    shstrndx: u16,
};

pub fn main() !void {
    var file = try std.fs.cwd().openFile("./elf", .{});
    defer file.close();

    var buffer: [64]u8 = undefined;
    _ = try file.read(buffer[0..]);

    const header: *Elf64ExecutionHeader = @ptrCast(@alignCast(&buffer));

    std.debug.print("Entry point: {any}\n", .{header.entry});
    std.debug.print("Program header offset: {d}\n", .{header.phoff});
    std.debug.print("Section header offset: {d}\n", .{header.shoff});
    std.debug.print("Machine: {d}, 0x{X:0>2}\n", .{ header.machine, header.machine });
    std.debug.print("Version: {d}, 0x{X:0>2}\n", .{ header.version, header.version });
    std.debug.print("Type: {}\n", .{header.type});
}

And this produces promising results

zig run main.zig
Entry point: 4176
Program header offset: 64
Section header offset: 14000
Machine: 62, 0x3E
Version: 1, 0x01
Type: 3

readelf -h ./elf returns

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2s complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Position-Independent Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1050
  Start of program headers:          64 (bytes into file)
  Start of section headers:          14000 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         32
  Section header string table index: 31

This appears to look good.

If I try to print out flags with std.debug.print("Flags: {}\n", .{header.flags}); I get an error.

zig run main.zig
thread 2127465 panic: incorrect alignment
/home/m/Vault/projects/probe/zig-elf/main.zig:31:43: 0x1035b15 in main (main)
    const header: *Elf64ExecutionHeader = @ptrCast(@alignCast(&buffer));
                                          ^
/home/m/.local/bin/zig-linux-x86_64-0.13.0/lib/std/start.zig:524:37: 0x10357e5 in posixCallMainAndExit (main)
            const result = root.main() catch |err| {
                                    ^
/home/m/.local/bin/zig-linux-x86_64-0.13.0/lib/std/start.zig:266:5: 0x1035301 in _start (main)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)

Try removing @alignCast(). I’m looking at std.mem.bytesAsValue() and it’s just a @ptrCast().

Thank you. I am really new to Zig (been using it two days only) so this is still a bit too magical for me at this moment :slight_smile:

I am not connecting these concepts yet.

If I have const header: *Elf64ExecutionHeader = @ptrCast(&buffer); without the @alignCast I get the following error.

zig run main.zig
main.zig:32:43: error: @ptrCast increases pointer alignment
    const header: *Elf64ExecutionHeader = @ptrCast(&buffer);
                                          ^~~~~~~~~~~~~~~~~
main.zig:32:52: note: '*[64]u8' has alignment '1'
    const header: *Elf64ExecutionHeader = @ptrCast(&buffer);
                                                   ^~~~~~~
main.zig:32:43: note: '*main.Elf64ExecutionHeader' has alignment '16'
main.zig:32:43: note: use @alignCast to assert pointer alignment
referenced by:
    callMain: /home/m/.local/bin/zig-linux-x86_64-0.13.0/lib/std/start.zig:524:32
    callMainWithArgs: /home/m/.local/bin/zig-linux-x86_64-0.13.0/lib/std/start.zig:482:12
    remaining reference traces hidden; use '-freference-trace' to see all reference traces
1 Like

I took only a part of ELF header and observe strange things:

const std = @import("std");

const ElfHdr1 = extern struct {
    sign: u32, // 7F 45 4C 46, '[DEL]ELF'
    clas: u8,  // 2 for 64 bit
    data: u8,  // endianess, 1 for LE
    vers: u8   // == 1
};

const ElfHdr2 = packed struct {
    sign: u32, // 7F 45 4C 46, '[DEL]ELF'
    clas: u8,  // 2 for 64 bit
    data: u8,  // endianess, 1 for LE
    vers: u8   // == 1
};

pub fn main() !void {
    std.debug.print("sizeof(ElfHdr1) = {}\n", .{@sizeOf(ElfHdr1)});
    std.debug.print("sizeof(ElfHdr2) = {}\n", .{@sizeOf(ElfHdr2)});
}

This prints 8 for both variants:

$ ./elf 
sizeof(ElfHdr1) = 8
sizeof(ElfHdr2) = 8

But this must be 7, mustn’t it?

Same in C:

#include <stdio.h>

struct elf_hdr {
    int sign;
    char clas;
    char data;
    char vers;
} __attribute__((packed));

int main(void) {
    printf("sizeof(elf_hdr) = %lu\n", sizeof(struct elf_hdr));
}

Prints 7, as it must be:

$ ./a.out 
sizeof(elf_hdr) = 7

Is it a bug? Or do I misunderstand something completely?

This is strange, right? The struct should be 56 bits, so 7 bytes. Where is the additional byte coming from?

I also don’t understand this to be honest.

1 Like

Taking a look at std impl might help: std.elf.Header.

1 Like

Yes and this means that we can not iterate over array of packed structs received from a file or from net, offsets will be wrong.

I think it deserves new topic.

1 Like

I wrote this little experiment where I save a struct to a file and then read it back. And this seems to work properly.

const std = @import("std");

const Sample = packed struct {
    type: u16,
    machine: u16,
    version: u32,
};

test "write" {
    const s = Sample{
        .type = 54,
        .machine = 72,
        .version = 132,
    };

    std.debug.print("{}\n", .{s});

    var file = try std.fs.cwd().createFile("out.bin", .{});
    defer file.close();

    try file.writeAll(std.mem.asBytes(&s));
}

test "read" {
    var file = try std.fs.cwd().openFile("./out.bin", .{});
    defer file.close();

    var buffer: [8]u8 = undefined;
    _ = try file.read(buffer[0..]);

    const s: *Sample = @ptrCast(@alignCast(&buffer));
    std.debug.print("{}\n", .{s});
}
$ zig test packed.zig
packed.Sample{ .type = 54, .machine = 72, .version = 132 }
packed.Sample{ .type = 54, .machine = 72, .version = 132 }
All 2 tests passed.

Now I am completely lost :smiley: This must have something to do with C vs Zig types or something. I am suspicious of int type. I have no other explanation for this.

So if the packed struct has padding as referred from Wrong sizes of extern/packed structs? - #3 by dee0xeed how do I get around this?

Or am I missing something?

One more little experiment

prog 1

const std = @import("std");

const ElfHdr = packed struct {
    sign: u32, // 7F 45 4C 46, '[DEL]ELF'
    clas: u8,  // 2 for 64 bit
    data: u8,  // endianess, 1 for LE
    vers: u8,  // == 1
};

pub fn main() !void {

    std.debug.print("sizeof(ElfHdr) = {}\n", .{@sizeOf(ElfHdr)});

    var file = try std.fs.cwd().openFile("./elf", .{});
    defer file.close();

    var buffer: [7]u8 = undefined;
    _ = try file.read(buffer[0..]);

    const h: *ElfHdr = @ptrCast(@alignCast(&buffer));
    std.debug.print("class = {}\n", .{h.clas});
}

result (crash):

sizeof(ElfHdr) = 8
thread 80604 panic: incorrect alignment
/home/zed/2-coding/zig-lang/elf/elf.zig:21:24: 0x103582b in main (elf)
    const h: *ElfHdr = @ptrCast(@alignCast(&buffer));
                       ^
/opt/zig-0.14/lib/std/start.zig:616:37: 0x10355df in posixCallMainAndExit (elf)
            const result = root.main() catch |err| {
                                    ^
/opt/zig-0.14/lib/std/start.zig:240:5: 0x10351cd in _start (elf)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)

prog 2 (added one more u8 field to the struct and buffer is 8 bytes long)

const std = @import("std");

const ElfHdr = packed struct {
    sign: u32, // 7F 45 4C 46, '[DEL]ELF'
    clas: u8,  // 2 for 64 bit
    data: u8,  // endianess, 1 for LE
    vers: u8,  // == 1
    xxxx: u8,
};

pub fn main() !void {

    std.debug.print("sizeof(ElfHdr) = {}\n", .{@sizeOf(ElfHdr)});

    var file = try std.fs.cwd().openFile("./elf", .{});
    defer file.close();

    var buffer: [8]u8 = undefined;
    _ = try file.read(buffer[0..]);

    const h: *ElfHdr = @ptrCast(@alignCast(&buffer));
    std.debug.print("class = {}\n", .{h.clas});
}

result (success!):

$ ./elf 
sizeof(ElfHdr) = 8
class = 2

ok, prog 3 (same ElfHdr as in prog 1 but buf as in prog 2, 8 bytes long, not 7):

const std = @import("std");

const ElfHdr = packed struct {
    sign: u32, // 7F 45 4C 46, '[DEL]ELF'
    clas: u8,  // 2 for 64 bit
    data: u8,  // endianess, 1 for LE
    vers: u8,  // == 1
};

pub fn main() !void {

    std.debug.print("sizeof(ElfHdr) = {}\n", .{@sizeOf(ElfHdr)});

    var file = try std.fs.cwd().openFile("./elf", .{});
    defer file.close();

    var buffer: [8]u8 = undefined;
    _ = try file.read(buffer[0..]);

    const h: *ElfHdr = @ptrCast(@alignCast(&buffer));
    std.debug.print("class = {}\n", .{h.clas});
}

result (also success!):

$ ./elf 
sizeof(ElfHdr) = 8
class = 2