[Solved] If Rust reorders structs, how do you review struct values debugging release targets?

I have a buggy release target. The debug target doesn't present any errors, so I'm stuck picking apart registers and memory offset looking for broken values, except I can't rely on the struct definitions to tell me where the fields are from the struct's base address.

Is there a way to turn off struct reordering during compilation?
Is there a way to print the struct order used during compilation?

mbrubeck recommended rustc -Z print-type-sizes, but I can't determine whether it's indicating definition order, or compilation order.

adding #[repr(C)] to the stuct will prevent reordering.

1 Like

Sorry, I should have mentioned. These are not my structs that I'm peeking at.

  • ::mysql_async::proto::NewPacket
  • ::std::collections::VecDeque
  • ::alloc::raw_vec::RawVec
  • ::core::ptr::Unique

I was told on IRC that #[repr(C)] will not recursively work on inner structs.

This is a workaround rather than a solution, but struct field reordering was introduced in the latest release a few days ago, so maybe try building your code with an older version?

Thankfully, rustup makes it super easy to install and use several versions of the toolchain.

2 Likes

Just curious, what do you need ordered structs for? I can hardly think of a usecase, and I'm fairly sure it can be done in a better way.

If you need to provide data to C, you can provide your own repr(C) struct with exactly the same layout specified in C header that provides information that is needed. You need to be able to specify struct layout on C level anyway, and there is no such thing as VecDeque in C.

If you need serialization, use serde, in particular bincode.

Note that struct layout for non-repr(C) types isn't guaranteed in any way. It's even more so when you use an external type - the author is allowed to change the struct layout in struct with all private fields at any point, even in patch release - it's implementation detail after all - safe Rust doesn't care about struct ordering, A code that depends on layout of those types is extremely fragile, as you have noticed updating Rust.

I don't need ordered structs for FFI or serialization. I need to know the order of structs to debug why this basic example of Rust code using the Tokio async library flakes out on the release target.

Whether it's disabling the reorder optimization, or just passing a flag to rustc to print the new order each struct is optimized to, it doesn't matter. What matters is that I'm not wasting my time wondering if I found the right memory address or register for a value I'm trying to watch.

Just curious, what do you need ordered structs for? I can hardly think of a usecase, and I'm fairly sure it can be done in a better way.

Sorry, but I feel like I'm repeating myself here. Was this not clear in the OP?

2 Likes

What about doing your release build with debug info? That will have the struct ordering info but it will depend on your debugger on how to extract that. Although you shouldn't really need to know it as the debugger should handle it seamlessly anyway.

1 Like

Interesting idea. I think I already enabled debug info in the release build with

[profile.release]
debug = true

in the Cargo.toml. If not, please let me know.

I'm using GDB to debug. When I try to print var, GDB just tells me the the variable is optimized away, which is why I've resorted to tracking down values using registers and offsets. I may be reading the assembly wrong, but it seems like one struct I've encountered did not exist in contiguous memory (...I hope I'm wrong about this). Rather the fields were in registers r10, r12, r14, and r15. and arbitrarily offloaded into memory--on the stack, I think, if ($rbp-0xf0) is a stack expression on x86_64.

whatis var does tell me the type of a variable (e.g. std::collections::VecDeque), but no futher info about the struct.

So, I don't think I'll achieve the seamless experience without more configuration. Is there a command in GDB that will print struct info with the ordering?

I hadn't considered the debug info would contain details on struct ordering. Maybe on of the other linux bin-utils (objdump, readelf) might print that info. Thanks for that.

You can make gdb interpret any memory address as your struct, which will display all the fields:

print *(struct Foo*)($rbp-0xf0)

I was trying to use the same syntax with Rust program, but it resulted with a syntax error. So I tried:

print *(($rbp-0xf0) as &Foo<u32>)

And it worked!

If you want to ask about the offset of a field, you could type eg.:

print &(0x0 as &Foo<u32>).b
2 Likes

print &(0x0 as &Foo<u32>).b

Clever. It'll be tedious, but it'll do. Thanks!

If the struct is only being used locally and isn't passed anywhere (that isn't inlined) as a unit, then it very likely doesn't exist contiguously in memory. LLVM has an optimization pass that splits up structs into individual fields. This allows the fields to be individually optimized, and if necessary have register allocation performed on them individually.

2 Likes

Other options include:

  • rustc -Z print-type-sizes does indeed print the in-memory order.
  • On Linux, the program pahole from dwarves can print the layout information from DWARF debug info, including the actual offset value for each field.
1 Like