Skip to content

Commit

Permalink
Fix ExtractGolangInterface to handle go 1.20 binaries and later
Browse files Browse the repository at this point in the history
Signed-off-by: Dom Del Nano <[email protected]>
  • Loading branch information
ddelnano committed Jan 29, 2025
1 parent 845b3d5 commit 9ffd687
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 29 deletions.
1 change: 1 addition & 0 deletions src/stirling/obj_tools/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 10 additions & 3 deletions src/stirling/obj_tools/go_syms.cc
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,20 @@ StatusOr<absl::flat_hash_map<std::string, std::vector<IntfImplTypeInfo>>> Extrac
ElfReader* elf_reader) {
absl::flat_hash_map<std::string, std::vector<IntfImplTypeInfo>> 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.<type_name>,<interface_name> as the symbol name.
// Go 1.19 binaries and earlier use go.itab.<type_name>,<interface_name> as the symbol name.
// Optimistically try the newer format first.
std::string_view kITablePrefix("go:itab.");

PX_ASSIGN_OR_RETURN(std::vector<ElfReader::SymbolInfo> 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.<type_name>,<interface_name>
Expand Down
109 changes: 83 additions & 26 deletions src/stirling/obj_tools/go_syms_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include "src/stirling/obj_tools/go_syms.h"

#include <memory>
#include <tuple>
#include <utility>

#include "src/common/testing/testing.h"

Expand All @@ -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";
Expand All @@ -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.
Expand Down Expand Up @@ -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<std::tuple<
std::string,
Matcher<const std::vector<std::pair<std::string, std::vector<IntfImplTypeInfo>>>>>> {};

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<ElfReader> 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<std::pair<std::string, std::vector<IntfImplTypeInfo>>> 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
}

Expand Down

0 comments on commit 9ffd687

Please sign in to comment.