From 9ffd68713b99de2e06929f73f71e67455db973c5 Mon Sep 17 00:00:00 2001 From: Dom Del Nano Date: Tue, 21 Jan 2025 19:11:36 +0000 Subject: [PATCH] Fix ExtractGolangInterface to handle go 1.20 binaries and later Signed-off-by: Dom Del Nano --- src/stirling/obj_tools/BUILD.bazel | 1 + src/stirling/obj_tools/go_syms.cc | 13 ++- src/stirling/obj_tools/go_syms_test.cc | 109 +++++++++++++++++++------ 3 files changed, 94 insertions(+), 29 deletions(-) diff --git a/src/stirling/obj_tools/BUILD.bazel b/src/stirling/obj_tools/BUILD.bazel index 5ce263f4ad6..e49289e3a18 100644 --- a/src/stirling/obj_tools/BUILD.bazel +++ b/src/stirling/obj_tools/BUILD.bazel @@ -137,6 +137,7 @@ pl_cc_test( "//src/stirling/obj_tools/testdata/go:test_binaries", "//src/stirling/obj_tools/testdata/go:test_go_1_17_binary", "//src/stirling/obj_tools/testdata/go:test_go_1_19_binary", + "//src/stirling/obj_tools/testdata/go:test_go_1_21_binary", ], deps = [ ":cc_library", diff --git a/src/stirling/obj_tools/go_syms.cc b/src/stirling/obj_tools/go_syms.cc index c828ebc0e0a..9e6df7f2c8b 100644 --- a/src/stirling/obj_tools/go_syms.cc +++ b/src/stirling/obj_tools/go_syms.cc @@ -148,13 +148,20 @@ StatusOr>> Extrac ElfReader* elf_reader) { absl::flat_hash_map> interface_types; - // All itable objects in the symbols are prefixed with this string. - const std::string_view kITablePrefix("go.itab."); + // Go 1.20 binaries and later use go:itab., as the symbol name. + // Go 1.19 binaries and earlier use go.itab., as the symbol name. + // Optimistically try the newer format first. + std::string_view kITablePrefix("go:itab."); PX_ASSIGN_OR_RETURN(std::vector itable_symbols, elf_reader->SearchSymbols(kITablePrefix, SymbolMatchType::kPrefix, /*symbol_type*/ ELFIO::STT_OBJECT)); - + if (itable_symbols.empty()) { + kITablePrefix = "go.itab."; + PX_ASSIGN_OR_RETURN(itable_symbols, + elf_reader->SearchSymbols(kITablePrefix, SymbolMatchType::kPrefix, + /*symbol_type*/ ELFIO::STT_OBJECT)); + } for (const auto& sym : itable_symbols) { // Expected format is: // go.itab., diff --git a/src/stirling/obj_tools/go_syms_test.cc b/src/stirling/obj_tools/go_syms_test.cc index 2551da2a6a0..e5f653d6ad7 100644 --- a/src/stirling/obj_tools/go_syms_test.cc +++ b/src/stirling/obj_tools/go_syms_test.cc @@ -19,6 +19,8 @@ #include "src/stirling/obj_tools/go_syms.h" #include +#include +#include #include "src/common/testing/testing.h" @@ -27,7 +29,9 @@ namespace stirling { namespace obj_tools { using ::testing::Field; +using ::testing::Matcher; using ::testing::StrEq; +using ::testing::UnorderedElementsAre; constexpr std::string_view kTestGoLittleEndiani386BinaryPath = "src/stirling/obj_tools/testdata/go/test_go1_13_i386_binary"; @@ -37,6 +41,8 @@ constexpr std::string_view kTestGoLittleEndianBinaryPath = constexpr std::string_view kTestGoBinaryPath = "src/stirling/obj_tools/testdata/go/test_go_1_19_binary"; +constexpr std::string_view kTestGo1_21BinaryPath = + "src/stirling/obj_tools/testdata/go/test_go_1_21_binary"; // The "endian agnostic" case refers to where the Go version data is varint encoded // directly within the buildinfo header. See the following reference for more details. @@ -68,44 +74,95 @@ TEST(IsGoExecutableTest, WorkingOnBasicGoBinary) { EXPECT_TRUE(IsGoExecutable(elf_reader.get())); } -TEST(ElfGolangItableTest, ExtractInterfaceTypes) { - const std::string kPath = px::testing::BazelRunfilePath(kTestGoBinaryPath); +class ElfGolangItableTest + : public ::testing::TestWithParam>>>>> {}; + +INSTANTIATE_TEST_SUITE_P( + ElfGolangItableTestSuite, ElfGolangItableTest, + ::testing::Values( + std::make_tuple( + kTestGo1_21BinaryPath, + UnorderedElementsAre( + Pair("fmt.State", + UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*fmt.pp"))), + Pair("internal/bisect.Writer", + UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, + "*internal/godebug.runtimeStderr"))), + Pair("internal/reflectlite.Type", + UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, + "internal/reflectlite.rtype"))), + Pair("error", + UnorderedElementsAre( + Field(&IntfImplTypeInfo::type_name, "main.IntStruct"), + Field(&IntfImplTypeInfo::type_name, "*errors.errorString"), + Field(&IntfImplTypeInfo::type_name, "syscall.Errno"), + Field(&IntfImplTypeInfo::type_name, "*io/fs.PathError"), + Field(&IntfImplTypeInfo::type_name, "runtime.errorString"), + Field(&IntfImplTypeInfo::type_name, "internal/poll.errNetClosing"), + Field(&IntfImplTypeInfo::type_name, + "*internal/poll.DeadlineExceededError"), + Field(&IntfImplTypeInfo::type_name, "*internal/bisect.parseError"))), + Pair("io.Writer", + UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*os.File"))), + Pair("sort.Interface", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, + "*internal/fmtsort.SortedMap"))), + Pair("reflect.Type", + UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*reflect.rtype"))), + Pair("math/rand.Source64", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, + "*math/rand.fastSource"))), + Pair("math/rand.Source", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, + "*math/rand.lockedSource"), + Field(&IntfImplTypeInfo::type_name, + "*math/rand.fastSource"))))), + std::make_tuple( + kTestGoBinaryPath, + UnorderedElementsAre( + Pair("error", + UnorderedElementsAre( + Field(&IntfImplTypeInfo::type_name, "main.IntStruct"), + Field(&IntfImplTypeInfo::type_name, "*errors.errorString"), + Field(&IntfImplTypeInfo::type_name, "*io/fs.PathError"), + Field(&IntfImplTypeInfo::type_name, + "*internal/poll.DeadlineExceededError"), + Field(&IntfImplTypeInfo::type_name, "internal/poll.errNetClosing"), + Field(&IntfImplTypeInfo::type_name, "runtime.errorString"), + Field(&IntfImplTypeInfo::type_name, "syscall.Errno"))), + Pair("sort.Interface", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, + "*internal/fmtsort.SortedMap"))), + Pair("math/rand.Source", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, + "*math/rand.lockedSource"))), + Pair("io.Writer", + UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*os.File"))), + Pair("internal/reflectlite.Type", + UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, + "*internal/reflectlite.rtype"))), + Pair("reflect.Type", + UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*reflect.rtype"))), + Pair("fmt.State", + UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*fmt.pp"))))))); + +TEST_P(ElfGolangItableTest, ExtractInterfaceTypes) { + const std::string kPath = px::testing::BazelRunfilePath(std::get<0>(GetParam())); ASSERT_OK_AND_ASSIGN(std::unique_ptr elf_reader, ElfReader::Create(kPath)); - ASSERT_OK_AND_ASSIGN(const auto interfaces, ExtractGolangInterfaces(elf_reader.get())); + + ASSERT_OK_AND_ASSIGN(const auto interfaces_map, ExtractGolangInterfaces(elf_reader.get())); + std::vector>> interfaces( + interfaces_map.begin(), interfaces_map.end()); // Check for `bazel coverage` so we can bypass the final checks. // Note that we still get accurate coverage metrics, because this only skips the final check. // Ideally, we'd get bazel to deterministically build test_go_binary, // but it's not easy to tell bazel to use a different config for just one target. + #ifdef PL_COVERAGE LOG(INFO) << "Whoa...`bazel coverage` is messaging with test_go_binary. Shame on you bazel. " "Ending this test early."; return; #else - EXPECT_THAT( - interfaces, - UnorderedElementsAre( - Pair("error", - UnorderedElementsAre( - Field(&IntfImplTypeInfo::type_name, "main.IntStruct"), - Field(&IntfImplTypeInfo::type_name, "*errors.errorString"), - Field(&IntfImplTypeInfo::type_name, "*io/fs.PathError"), - Field(&IntfImplTypeInfo::type_name, "*internal/poll.DeadlineExceededError"), - Field(&IntfImplTypeInfo::type_name, "internal/poll.errNetClosing"), - Field(&IntfImplTypeInfo::type_name, "runtime.errorString"), - Field(&IntfImplTypeInfo::type_name, "syscall.Errno"))), - Pair("sort.Interface", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, - "*internal/fmtsort.SortedMap"))), - Pair("math/rand.Source", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, - "*math/rand.lockedSource"))), - Pair("io.Writer", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*os.File"))), - Pair("internal/reflectlite.Type", - UnorderedElementsAre( - Field(&IntfImplTypeInfo::type_name, "*internal/reflectlite.rtype"))), - Pair("reflect.Type", - UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*reflect.rtype"))), - Pair("fmt.State", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*fmt.pp"))))); + EXPECT_THAT(interfaces, std::get<1>(GetParam())); #endif }