c++macrosreverse-engineering

How to declare a struct with fields at specific offsets in C++?


I have a struct that's memory-read from another program. As such I only know specific fields and not the entire layout.

I currently declare the struct by calculating the paddings in between known members, as such:

struct ForeignType_t
{
    void* Field1;   // [+0]
    char  PAD1[24];
    void* Field2;   // [+32]
    char  PAD2[512];
    void* Field3;   // [+552]
}

(This example is using 64 bit pointers. Alignment can also be ignored for the following examples.)

This syntax requires me to manually calculate the offsets between each member. I've already tried just calculating the paddings with a macro taking the previous and the next offset, however this is still ugly.

Ideally there would be a syntax such as this:

struct ForeignType_t
{
    [[offset(0)]]
    void* Field1;   // [+0]
    [[offset(32)]]
    void* Field2;   // [+32]
    [[offset(552)]]
    void* Field3;   // [+552]
}

I've been unsuccessful at finding anything like it.

The closest I've come was recreating this myself via Macros and creating effectively "getter" functions, that would reinterpret a buffer member and read from its index.

#define OFFSET(offset, type, name) inline type name()                         \
                                    {                                         \
                                        return *(type*)(&this->Data[offset]); \
                                    }

struct ForeignType_t
{
    char Data[560];
    
    OFFSET(0, void*, Field1)
    OFFSET(32, void*, Field2)
    OFFSET(552, void*, Field3)
}

This implementation is almost perfect. It is easy to read and declare, however has the downside that it is not possible to easily debug this and accessing a field is a function access. foreignType.Field(); vs foreignType.Field;

Is there any other way of achieving a declaration like this without the downsides?


Solution

  • The final solution I've come up with is using anonymous unions & structs to reinterpret the char Data[#] member.

    /* helper macros for unique padding members */
    #define STRMERGE_INNER(x, y) x ## y
    #define STRMERGE(x, y) STRMERGE_INNER(x, y)
    
    #define OFFSET(offset, type, name) struct {                                    \
                                            char STRMERGE(____, __LINE__)[offset]; \
                                            type name;                             \
                                        };
    
    struct ForeignType_t
    {
        union {
            char Data[560];
        
            void* Field1; /* First member has to be normally declared, explanation below. */
            OFFSET(32, void*, Field2)
            OFFSET(552, void*, Field3)
        }
    }
    
    

    The Data member is not actually necessary, as the size is based on the biggest offset + its typesize.

    The first member (in this case) is at offset 0 therefore it must be declared as normal, because char PAD[0]; is not valid.

    The nested union is still a bit ugly and so is the offset 0 member. Other than that, this fulfills my requirements: