Home > Enterprise >  verify structure layout at build time
verify structure layout at build time

Time:09-09

How can I verify the layout of a (repr(C)) structure without running the code? E.g. when I have

#[repr(C)]
struct Registers {
    urxd:       u32,        // 0x00
    _rsrvd0:    [u32;15],
    utxd:       u32,        // 0x40
    _rsrvd1:    [u32;15],
    ucr1:       u32,        // 0x80
}

how can I make the build process fail when ucr1 is not at positition 0x80 (e.g. due to miscalculated _rsrvd members or target depending padding)?

In C I would write something like

struct foo {
    uint32_t    a;
    uint32_t    b;
    uint32_t    c;
    uint32_t    d;
};

static void _test() {
    _Static_assert(offsetof(struct foo, d) == 12);
}

For _Static_assert there seem to exist crates like static_assertions which implement hacks like these from the good old C times (negative array sizes and so).

But for offsetof() I have found only non-const implementations.

Code is for embedded platforms without #[test] support so I can not test it at runtime. Running #[test] on a std-platform might give wrong results because padding/alignment is different there.

CodePudding user response:

You can use the const_field_offset crate to get the offset and the static_assertions to assert during the build.

use const_field_offset;
use static_assertions as sa;

#[repr(C)]
#[derive(const_field_offset::FieldOffsets)]
struct Registers {
    urxd:       u32,        // 0x00
    _rsrvd0:    [u32;15],
    utxd:       u32,        // 0x40
    _rsrvd1:    [u32;15],
    ucr1:       u32,        // 0x80
}

sa::const_assert!(0x80 == Registers::FIELD_OFFSETS.ucr1.get_byte_offset());

When the assert fails, the error message isn't super helpful, but it does at least fail the build:

sa::const_assert!(0x79 == Registers::FIELD_OFFSETS.ucr1.get_byte_offset());
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to compute `0_usize - 1_usize`, which would overflow

CodePudding user response:

If you want a nicer error message you can roll your own with a little unsafe.

const _ASSERT_OFFSET:() = {
    let val = Registers{
        urxd:0,
        _rsrvd0:[0;15],
        utxd:0,
        _rsrvd1:[0;15],
        ucr1:1,
    };
    let ptr = &val as *const Registers as *const u8;
    let field_ptr = unsafe{ ptr.add(0x80) } as *const u32;
    if unsafe{ *field_ptr } != 1{
        panic!("ucr1 was at the wrong offset")
    }
    
};

Playground

The way this works is pretty simple: write a known value into ucr1, and then read whatever value is in what's supposed to be ucr1. If they match, every things fine, and if they don't, you get a message like

error[E0080]: evaluation of constant value failed
  --> src/lib.rs:22:9
   |
22 |         panic!("ucr1 was at the wrong offset")
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'ucr1 was at the wrong offset', src/lib.rs:22:9
   |
   = note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info)

Bear in mind that if the read goes out of bounds you will get a different, more opaque, error from rustc.

  • Related