diff --git a/src/dwarf_parser.cpp b/src/dwarf_parser.cpp index 0756091d643..c0e2ba9449d 100644 --- a/src/dwarf_parser.cpp +++ b/src/dwarf_parser.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace bpftrace { @@ -221,31 +222,10 @@ SizedType Dwarf::get_stype(const std::string &type_name) return CreateNone(); } -void Dwarf::resolve_fields(std::shared_ptr str, lldb::SBType type) -{ - if (!type.IsValid()) - return; - - for (uint32_t i = 0; i < type.GetNumberOfVirtualBaseClasses(); i++) { - auto parent = type.GetVirtualBaseClassAtIndex(i); - resolve_fields(str, parent.GetType()); - } - - for (uint32_t i = 0; i < type.GetNumberOfDirectBaseClasses(); i++) { - auto parent = type.GetDirectBaseClassAtIndex(i); - resolve_fields(str, parent.GetType()); - } - - for (uint32_t i = 0; i < type.GetNumberOfFields(); i++) { - auto field = type.GetFieldAtIndex(i); - auto field_type = get_stype(field.GetType()); - str->AddField(field.GetName() ?: "", - get_stype(field.GetType()), - field.GetOffsetInBytes(), - resolve_bitfield(field), - false); - } -} +struct Subobject { + lldb::SBType type; + size_t offset; +}; void Dwarf::resolve_fields(const SizedType &type) { @@ -258,7 +238,33 @@ void Dwarf::resolve_fields(const SizedType &type) return; auto type_dbg = target_.FindFirstType(type_name.c_str()); - resolve_fields(str, type_dbg); + if (!type_dbg.IsValid()) + return; + + std::queue subobjects{ std::deque{ Subobject{ type_dbg, 0 } } }; + while (!subobjects.empty()) { + auto &subobject = subobjects.front(); + + // Collect the fields into str, duplicates will be ignored. + for (uint32_t i = 0; i < subobject.type.GetNumberOfFields(); i++) { + auto field = subobject.type.GetFieldAtIndex(i); + str->AddField(field.GetName() ?: "", + get_stype(field.GetType()), + subobject.offset + field.GetOffsetInBytes(), + resolve_bitfield(field), + false); + } + + // Queue the bases for further processing. + for (uint32_t i = 0; i < subobject.type.GetNumberOfDirectBaseClasses(); + i++) { + auto base = subobject.type.GetDirectBaseClassAtIndex(i); + subobjects.push(Subobject{ base.GetType(), + subobject.offset + base.GetOffsetInBytes() }); + } + + subobjects.pop(); + } } std::optional Dwarf::resolve_bitfield(lldb::SBTypeMember field) diff --git a/src/dwarf_parser.h b/src/dwarf_parser.h index 442b5e4bbb7..72019b1fa01 100644 --- a/src/dwarf_parser.h +++ b/src/dwarf_parser.h @@ -40,7 +40,6 @@ class Dwarf { std::string get_type_name(lldb::SBType type); SizedType get_stype(lldb::SBType type, bool resolve_structs = true); - void resolve_fields(std::shared_ptr str, lldb::SBType type); std::optional resolve_bitfield(lldb::SBTypeMember field); BPFtrace *bpftrace_; diff --git a/src/struct.cpp b/src/struct.cpp index f8abb2e42cd..35fb4d2764d 100644 --- a/src/struct.cpp +++ b/src/struct.cpp @@ -142,10 +142,10 @@ void Struct::AddField(const std::string &field_name, { if (!HasField(field_name)) fields.push_back(Field{ .name = field_name, - .type = type, - .offset = offset, - .bitfield = bitfield, - .is_data_loc = is_data_loc }); + .type = type, + .offset = offset, + .bitfield = bitfield, + .is_data_loc = is_data_loc }); } bool Struct::HasFields() const diff --git a/tests/README.md b/tests/README.md index c802f9f67c0..af9fcfb48d0 100644 --- a/tests/README.md +++ b/tests/README.md @@ -127,9 +127,9 @@ not known until test time. The following runtime variables are available for the ### Test programs -You can add test programs for your runtime tests by placing a `.c` file corresponding to your test program in `tests/testprogs`. +You can add test programs for your runtime tests by placing a `.c` or `.cpp` file corresponding to your test program in `tests/testprogs`. -You can add test libraries for your runtime tests by placing a `.c` file corresponding to your test library in `tests/testlibs`. +You can add test libraries for your runtime tests by placing a `.c` or `.cpp` file corresponding to your test library in `tests/testlibs`. The test file `tests/testprogs/my_test.c` will result in an executable that you can call and probe in your runtime test at `./testprogs/my_test` diff --git a/tests/data/CMakeLists.txt b/tests/data/CMakeLists.txt index 41f388514f2..6952ebca55d 100644 --- a/tests/data/CMakeLists.txt +++ b/tests/data/CMakeLists.txt @@ -99,5 +99,7 @@ add_custom_command( add_custom_target(debuginfo_btf_data DEPENDS ${BTF_DATA_H}) +# BTF doesn't support C++, so we only generate a data_source_cxx executable +# to run the semantic_analyser tests on. add_executable(data_source_cxx data_source_cxx.cpp) target_compile_options(data_source_cxx PRIVATE ${DATA_SOURCE_CFLAGS}) diff --git a/tests/data/data_source_cxx.cpp b/tests/data/data_source_cxx.cpp index c6cd3b2b07a..69311411931 100644 --- a/tests/data/data_source_cxx.cpp +++ b/tests/data/data_source_cxx.cpp @@ -1,49 +1,105 @@ class Parent { - private: - int a; - protected: - int b; - public: - int c; - int d; // Shadowed by Child::d, but should be reachable with a cast +private: + int a; - Parent(int a, int b, int c, int d) : a(a), b(b), c(c), d(d) {} +protected: + int b; + +public: + int c; + int d; // Shadowed by Child::d, but should be reachable with a cast + + Parent(int a, int b, int c, int d) : a(a), b(b), c(c), d(d) + { + } }; class Child : public Parent { - public: - int d; - int e; - int f; +public: + int d; + int e; + int f; - Child(int a, int b, int c, int d, int e, int f) : Parent(a, b, c, d), d(d + 1), e(e), f(f) {} + Child(int a, int b, int c, int d, int e, int f) + : Parent(a, b, c, d), d(d + 1), e(e), f(f) + { + } }; class LittleChild : public Child { - public: - int g; +public: + int g; - LittleChild(int a, int b, int c, int d, int e, int f, int g) : Child(a, b, c, d, e, f), g(g) {} + LittleChild(int a, int b, int c, int d, int e, int f, int g) + : Child(a, b, c, d, e, f), g(g) + { + } +}; + +struct Top { + int x; +}; + +struct Left : public Top { + int y; +}; + +struct Right : public Top { + int z; +}; + +struct Bottom : public Left, public Right { + int w; +}; + +struct Multi : public Parent, public Top { + int abc; + + Multi(int a, int b, int c, int d, int e) + : Parent{ a, b, c, d }, Top{ e }, abc{ e + 1 } + { + } }; int func_1(Child &c, Parent &p __attribute__((unused))) { - return dynamic_cast(c).d; + return dynamic_cast(c).d; } int func_2(LittleChild &lc) { - return dynamic_cast(lc).d; + return dynamic_cast(lc).d; +} + +int func_3(Multi &m, Bottom &b __attribute__((unused))) +{ + return m.abc; } int main(void) { - Parent p{1, 2, 3, 4}; - Child c{1, 2, 3, 4, 5, 6}; + Parent p{ 1, 2, 3, 4 }; + Child c{ 1, 2, 3, 4, 5, 6 }; func_1(c, p); - LittleChild lc{1, 2, 3, 4, 5, 6, 7}; + LittleChild lc{ 1, 2, 3, 4, 5, 6, 7 }; func_2(lc); + Multi m{ 1, 2, 3, 4, 5 }; + Bottom b{ + { + // Left + { 1 }, // Left's Top + 2 // Left's y + }, + { + // Right + { 3 }, // Right's Top + 4 // Right's z + }, + 5 // Bottom's w + }; + func_3(m, b); + return 0; } diff --git a/tests/field_analyser.cpp b/tests/field_analyser.cpp index 7bae67b69cb..a6a1942f8f4 100644 --- a/tests/field_analyser.cpp +++ b/tests/field_analyser.cpp @@ -478,7 +478,8 @@ TEST_F(field_analyser_dwarf, parse_arrays) EXPECT_EQ(arrs->GetField("flexible").offset, 64); } -static void CheckParentFields(const std::shared_ptr &cls, bool is_d_shadowed = false) +static void CheckParentFields(const std::shared_ptr &cls, + bool is_d_shadowed = false) { EXPECT_TRUE(cls->HasField("a")); EXPECT_TRUE(cls->GetField("a").type.IsIntTy()); @@ -589,6 +590,32 @@ TEST_F(field_analyser_dwarf, parse_inheritance_chain) CheckLittleChildFields(cls); } +TEST_F(field_analyser_dwarf, parse_inheritance_multi) +{ + BPFtrace bpftrace; + std::string uprobe = "uprobe:" + std::string(cxx_bin_); + test(bpftrace, uprobe + ":cpp:func_3 { $x = args.m->abc; }", 0); + + ASSERT_TRUE(bpftrace.structs.Has("struct Multi")); + auto cls = bpftrace.structs.Lookup("struct Multi").lock(); + + ASSERT_TRUE(cls->HasFields()); + ASSERT_EQ(cls->fields.size(), 6); + ASSERT_EQ(cls->size, 24); + + CheckParentFields(cls); + + EXPECT_TRUE(cls->HasField("x")); + EXPECT_TRUE(cls->GetField("x").type.IsIntTy()); + EXPECT_EQ(cls->GetField("x").type.GetSize(), 4); + EXPECT_EQ(cls->GetField("x").offset, 16); + + EXPECT_TRUE(cls->HasField("abc")); + EXPECT_TRUE(cls->GetField("abc").type.IsIntTy()); + EXPECT_EQ(cls->GetField("abc").type.GetSize(), 4); + EXPECT_EQ(cls->GetField("abc").offset, 20); +} + TEST_F(field_analyser_dwarf, parse_struct_anonymous_fields) { GTEST_SKIP() << "Anonymous fields not supported #3084"; diff --git a/tests/testlibs/CMakeLists.txt b/tests/testlibs/CMakeLists.txt index 37d3106cb04..41e8817a734 100644 --- a/tests/testlibs/CMakeLists.txt +++ b/tests/testlibs/CMakeLists.txt @@ -1,4 +1,4 @@ -file(GLOB testlib_sources CONFIGURE_DEPENDS *.c) +file(GLOB testlib_sources CONFIGURE_DEPENDS *.c *.cpp) set(testlibtargets "") foreach(testlib_source ${testlib_sources}) get_filename_component(testlib_name ${testlib_source} NAME_WE) diff --git a/tests/testprogs/uprobe_test_cxx.cpp b/tests/testprogs/uprobe_test_cxx.cpp index 8152f2c8b86..45b64d61557 100644 --- a/tests/testprogs/uprobe_test_cxx.cpp +++ b/tests/testprogs/uprobe_test_cxx.cpp @@ -1,29 +1,36 @@ #include class Parent { - private: - int a; - protected: - int b; - public: - int c; - int d; // Shadowed by Child::d, but should be reachable with a cast - - Parent(int a, int b, int c, int d) : a(a), b(b), c(c), d(d) {} +private: + int a; + +protected: + int b; + +public: + int c; + int d; // Shadowed by Child::d, but should be reachable with a cast + + Parent(int a, int b, int c, int d) : a(a), b(b), c(c), d(d) + { + } }; class Child : public Parent { - public: - int d; - int e; - int f; - - Child(int a, int b, int c, int d, int e, int f) : Parent(a, b, c, d), d(d + 1), e(e), f(f) {} +public: + int d; + int e; + int f; + + Child(int a, int b, int c, int d, int e, int f) + : Parent(a, b, c, d), d(d + 1), e(e), f(f) + { + } }; int uprobeFunction3(Child &c, Parent &p __attribute__((unused))) { - return dynamic_cast(c).d; + return dynamic_cast(c).d; } int uprobeArray(int (&array)[10]) @@ -35,8 +42,8 @@ int main(int argc __attribute__((unused)), char **argv __attribute__((unused))) { // usleep(1000000); - Parent p{1, 2, 3, 4}; - Child c{1, 2, 3, 4, 5, 6}; + Parent p{ 1, 2, 3, 4 }; + Child c{ 1, 2, 3, 4, 5, 6 }; return 0; }