Last active
September 8, 2024 00:17
-
-
Save gnarayan81/2446cf392f012f31a2b3 to your computer and use it in GitHub Desktop.
Viewing object layout in clang.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The intent of this class is to display the layout of an object in | |
// memory. | |
// Compile with: | |
// clang -cc1 -x c++ -v -fdump-record-layouts test.cc -emit-llvm -o /dev/null | |
// | |
// | |
//*** Dumping AST Record Layout | |
//0 | struct Top | |
//0 | int a | |
//| [sizeof=4, dsize=4, align=4 | |
//| nvsize=4, nvalign=4] | |
//*** Dumping AST Record Layout | |
//0 | struct Left | |
//0 | struct Top (base) | |
//0 | int a | |
//4 | int l | |
//| [sizeof=8, dsize=8, align=4 | |
//| nvsize=8, nvalign=4] | |
//*** Dumping AST Record Layout | |
//0 | struct Right | |
//0 | struct Top (base) | |
//0 | int a | |
//4 | int r | |
//| [sizeof=8, dsize=8, align=4 | |
//| nvsize=8, nvalign=4] | |
//*** Dumping AST Record Layout | |
//0 | struct Bottom | |
//0 | struct Left (base) | |
//0 | struct Top (base) | |
//0 | int a | |
//4 | int l | |
//8 | struct Right (base) | |
//8 | struct Top (base) | |
//8 | int a | |
//12 | int r | |
//16 | int b | |
//| [sizeof=20, dsize=20, align=4 | |
//| nvsize=20, nvalign=4] | |
#undef CHECKING_LAYOUT | |
#ifndef CHECKING_LAYOUT | |
# include <stdio.h> | |
#endif | |
struct Top { | |
const int a = 16; | |
}; | |
struct Left : virtual public Top { | |
const int l = 32; | |
}; | |
struct Right : virtual public Top { | |
const int r = 64; | |
}; | |
struct Bottom : public Left, public Right { | |
const int b = 128; | |
}; | |
int main() { | |
Bottom b; | |
Left* l = &b; | |
Right* r = &b; | |
#ifndef CHECKING_LAYOUT | |
printf("&b = %p\n", &b); | |
printf(" l = %p\n", l); | |
printf(" r = %p\n", r); | |
#endif | |
// On a single run, | |
// &b = 0x7fff59df1988 | |
// l = 0x7fff59df1988 | |
// r = 0x7fff59df1990 | |
// ... | |
// Clearly l and r are separated by h'8 bytes. Does this reconcile | |
// with what we have from the AST ? | |
// ... | |
//*** Dumping AST Record Layout | |
//0 | struct Bottom | |
//0 | struct Left (base) | |
//0 | struct Top (base) | |
//0 | int a | |
//4 | int l | |
//8 | struct Right (base) | |
//8 | struct Top (base) | |
//8 | int a | |
//12 | int r | |
//16 | int b | |
//| [sizeof=20, dsize=20, align=4 | |
//| nvsize=20, nvalign=4] | |
// ... | |
// That sure seems to be the case, if you pay attention to struct Right | |
// at offset 8. | |
// Now what is of interest is that 'int a', while quoted twice | |
// has storage only 8 bytes in, while, rather confusingly, int r | |
// is at 12 bytes in. Now consider, | |
#ifndef CHECKING_LAYOUT | |
printf(" &(l->a) = %p\n", &(l->a)); | |
printf(" &(r->a) = %p\n", &(r->a)); | |
#endif | |
// &b = 0x7ffffdb5bda8 | |
// l = 0x7ffffdb5bda8 | |
// r = 0x7ffffdb5bdb0 | |
// &(l->a) = 0x7ffffdb5bda8 | |
// &(r->a) = 0x7ffffdb5bdb0 | |
// ... | |
// Okay now, l->a is at an offset of 0 from &b, which is fine, from the | |
// AST record on top. r->a is at an offset of 8 bytes from the top, which | |
// is expected since it is at the base of 'b'. | |
// ... | |
// The problem comes in when trying to access top. | |
//Top* t1 = (Top*) &b; | |
//Top* t2 = (Top*) &b; | |
// Clang now whinges: | |
// ... | |
// object_memory_layout.cc:112:19: error: ambiguous conversion from derived class 'Bottom' to base class 'Top': | |
// struct Bottom -> struct Left -> struct Top | |
//struct Bottom -> struct Right -> struct Top | |
// Top* t1 = (Top*) &b; | |
// ^~ | |
// object_memory_layout.cc:113:19: error: ambiguous conversion from derived class 'Bottom' to base class 'Top': | |
// struct Bottom -> struct Left -> struct Top | |
// struct Bottom -> struct Right -> struct Top | |
// Top* t2 = (Top*) &b; | |
// ... | |
// In fact, it is quite explicitly saying that it doesn't know how to | |
// get to Top, due to the ugly diamond. | |
// ... | |
// It is of course possible to do this: | |
Top* t1 = l; | |
Top* t2 = r; | |
// And that's fine, because the conversion is no longer ambiguous. | |
// ... | |
// The point that is made here is that the conversion ambiguity arises | |
// from the table layout above. Notice that getting to top could be | |
// either offset 0 or offset 8. | |
// A suggested solution to this would be to simply have a single | |
// copy of Top in the object, via virtual inheritance. | |
// ... | |
// With that done. | |
//*** Dumping AST Record Layout | |
//0 | struct Bottom | |
//0 | struct Left (primary base) | |
//0 | (Left vtable pointer) | |
//8 | int l | |
//16 | struct Right (base) | |
//16 | (Right vtable pointer) | |
//24 | int r | |
//28 | int b | |
//32 | struct Top (virtual base) | |
//32 | int a | |
//| [sizeof=40, dsize=36, align=8 | |
//| nvsize=32, nvalign=8] | |
// Okay, now there's vtable pointers. The vtable pointer should provide | |
// the offset necessary to get to Top. | |
// Let's get that value. | |
#ifndef CHECKING_LAYOUT | |
printf(" left vtable pointer: *(%p) => *(%016llx) => %u\n", &b, | |
*((unsigned long long *) &b), *((unsigned *)(*((unsigned long long *) &b)))); | |
unsigned long long *rvt = ((unsigned long long *) &b) + 0x2; | |
printf("right vtable pointer: *(%p) => *(%016llx) => %#x\n", rvt, | |
*rvt, *((unsigned *)(*rvt))); | |
// Oddly, the right vptr is busted ? The left one is okay, but it still | |
// is off by a factor of 2. | |
// left vtable pointer: *(0x7fff0c6add50) => *(0000000000400ae8) => 16 | |
// right vtable pointer: *(0x7fff0c6add60) => *(0000000000400b00) => 0x400ae8 | |
#endif | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment