Skip to content

Commit 9dfdcf7

Browse files
authored
Fix ExtractGolangInterface to handle go 1.20 binaries and later (#2093)
Summary: Fix `ExtractGolangInterface` to handle go 1.20 binaries and later This is part of the work to deprecate or fix dynamic logging Relevant Issues: #666 Type of change: /kind bugfix Test Plan: New tests pass --------- Signed-off-by: Dom Del Nano <ddelnano@gmail.com>
1 parent aa097f2 commit 9dfdcf7

File tree

3 files changed

+103
-33
lines changed

3 files changed

+103
-33
lines changed

src/stirling/obj_tools/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ pl_cc_test(
137137
"//src/stirling/obj_tools/testdata/go:test_binaries",
138138
"//src/stirling/obj_tools/testdata/go:test_go_1_17_binary",
139139
"//src/stirling/obj_tools/testdata/go:test_go_1_19_binary",
140+
"//src/stirling/obj_tools/testdata/go:test_go_1_21_binary",
140141
],
141142
deps = [
142143
":cc_library",

src/stirling/obj_tools/go_syms.cc

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,28 @@ StatusOr<std::string> ReadGoBuildVersion(ElfReader* elf_reader) {
144144
return ReadGoString(elf_reader, ptr_size, ptr_addr, read_ptr);
145145
}
146146

147+
// Prefixes used to search for itable symbols in the binary. Follows the format:
148+
// <prefix>.<type_name>,<interface_name>. i.e. go.itab.<type_name>,<interface_name>
149+
constexpr std::array<std::string_view, 2> kITablePrefixes = {
150+
"go:itab.", // Prefix used by Go 1.20 binaries and later.
151+
"go.itab.", // Prefix used by Go 1.19 binaries and earlier.
152+
};
153+
147154
StatusOr<absl::flat_hash_map<std::string, std::vector<IntfImplTypeInfo>>> ExtractGolangInterfaces(
148155
ElfReader* elf_reader) {
149156
absl::flat_hash_map<std::string, std::vector<IntfImplTypeInfo>> interface_types;
150157

151-
// All itable objects in the symbols are prefixed with this string.
152-
const std::string_view kITablePrefix("go.itab.");
153-
154-
PX_ASSIGN_OR_RETURN(std::vector<ElfReader::SymbolInfo> itable_symbols,
155-
elf_reader->SearchSymbols(kITablePrefix, SymbolMatchType::kPrefix,
156-
/*symbol_type*/ ELFIO::STT_OBJECT));
158+
std::vector<ElfReader::SymbolInfo> itable_symbols;
159+
std::string_view iTablePrefix;
160+
for (const auto& prefix : kITablePrefixes) {
161+
PX_ASSIGN_OR_RETURN(itable_symbols,
162+
elf_reader->SearchSymbols(prefix, SymbolMatchType::kPrefix,
163+
/*symbol_type*/ ELFIO::STT_OBJECT));
164+
if (!itable_symbols.empty()) {
165+
iTablePrefix = prefix;
166+
break;
167+
}
168+
}
157169

158170
for (const auto& sym : itable_symbols) {
159171
// Expected format is:
@@ -166,7 +178,7 @@ StatusOr<absl::flat_hash_map<std::string, std::vector<IntfImplTypeInfo>>> Extrac
166178

167179
std::string_view interface_name = sym_split[1];
168180
std::string_view type = sym_split[0];
169-
type.remove_prefix(kITablePrefix.size());
181+
type.remove_prefix(iTablePrefix.size());
170182

171183
IntfImplTypeInfo info;
172184

src/stirling/obj_tools/go_syms_test.cc

Lines changed: 83 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include "src/stirling/obj_tools/go_syms.h"
2020

2121
#include <memory>
22+
#include <tuple>
23+
#include <utility>
2224

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

@@ -27,7 +29,9 @@ namespace stirling {
2729
namespace obj_tools {
2830

2931
using ::testing::Field;
32+
using ::testing::Matcher;
3033
using ::testing::StrEq;
34+
using ::testing::UnorderedElementsAre;
3135

3236
constexpr std::string_view kTestGoLittleEndiani386BinaryPath =
3337
"src/stirling/obj_tools/testdata/go/test_go1_13_i386_binary";
@@ -37,6 +41,8 @@ constexpr std::string_view kTestGoLittleEndianBinaryPath =
3741

3842
constexpr std::string_view kTestGoBinaryPath =
3943
"src/stirling/obj_tools/testdata/go/test_go_1_19_binary";
44+
constexpr std::string_view kTestGo1_21BinaryPath =
45+
"src/stirling/obj_tools/testdata/go/test_go_1_21_binary";
4046

4147
// The "endian agnostic" case refers to where the Go version data is varint encoded
4248
// directly within the buildinfo header. See the following reference for more details.
@@ -68,44 +74,95 @@ TEST(IsGoExecutableTest, WorkingOnBasicGoBinary) {
6874
EXPECT_TRUE(IsGoExecutable(elf_reader.get()));
6975
}
7076

71-
TEST(ElfGolangItableTest, ExtractInterfaceTypes) {
72-
const std::string kPath = px::testing::BazelRunfilePath(kTestGoBinaryPath);
77+
class ElfGolangItableTest
78+
: public ::testing::TestWithParam<std::tuple<
79+
std::string,
80+
Matcher<const std::vector<std::pair<std::string, std::vector<IntfImplTypeInfo>>>>>> {};
81+
82+
INSTANTIATE_TEST_SUITE_P(
83+
ElfGolangItableTestSuite, ElfGolangItableTest,
84+
::testing::Values(
85+
std::make_tuple(
86+
kTestGo1_21BinaryPath,
87+
UnorderedElementsAre(
88+
Pair("fmt.State",
89+
UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*fmt.pp"))),
90+
Pair("internal/bisect.Writer",
91+
UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name,
92+
"*internal/godebug.runtimeStderr"))),
93+
Pair("internal/reflectlite.Type",
94+
UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name,
95+
"internal/reflectlite.rtype"))),
96+
Pair("error",
97+
UnorderedElementsAre(
98+
Field(&IntfImplTypeInfo::type_name, "main.IntStruct"),
99+
Field(&IntfImplTypeInfo::type_name, "*errors.errorString"),
100+
Field(&IntfImplTypeInfo::type_name, "syscall.Errno"),
101+
Field(&IntfImplTypeInfo::type_name, "*io/fs.PathError"),
102+
Field(&IntfImplTypeInfo::type_name, "runtime.errorString"),
103+
Field(&IntfImplTypeInfo::type_name, "internal/poll.errNetClosing"),
104+
Field(&IntfImplTypeInfo::type_name,
105+
"*internal/poll.DeadlineExceededError"),
106+
Field(&IntfImplTypeInfo::type_name, "*internal/bisect.parseError"))),
107+
Pair("io.Writer",
108+
UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*os.File"))),
109+
Pair("sort.Interface", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name,
110+
"*internal/fmtsort.SortedMap"))),
111+
Pair("reflect.Type",
112+
UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*reflect.rtype"))),
113+
Pair("math/rand.Source64", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name,
114+
"*math/rand.fastSource"))),
115+
Pair("math/rand.Source", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name,
116+
"*math/rand.lockedSource"),
117+
Field(&IntfImplTypeInfo::type_name,
118+
"*math/rand.fastSource"))))),
119+
std::make_tuple(
120+
kTestGoBinaryPath,
121+
UnorderedElementsAre(
122+
Pair("error",
123+
UnorderedElementsAre(
124+
Field(&IntfImplTypeInfo::type_name, "main.IntStruct"),
125+
Field(&IntfImplTypeInfo::type_name, "*errors.errorString"),
126+
Field(&IntfImplTypeInfo::type_name, "*io/fs.PathError"),
127+
Field(&IntfImplTypeInfo::type_name,
128+
"*internal/poll.DeadlineExceededError"),
129+
Field(&IntfImplTypeInfo::type_name, "internal/poll.errNetClosing"),
130+
Field(&IntfImplTypeInfo::type_name, "runtime.errorString"),
131+
Field(&IntfImplTypeInfo::type_name, "syscall.Errno"))),
132+
Pair("sort.Interface", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name,
133+
"*internal/fmtsort.SortedMap"))),
134+
Pair("math/rand.Source", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name,
135+
"*math/rand.lockedSource"))),
136+
Pair("io.Writer",
137+
UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*os.File"))),
138+
Pair("internal/reflectlite.Type",
139+
UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name,
140+
"*internal/reflectlite.rtype"))),
141+
Pair("reflect.Type",
142+
UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*reflect.rtype"))),
143+
Pair("fmt.State",
144+
UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*fmt.pp")))))));
145+
146+
TEST_P(ElfGolangItableTest, ExtractInterfaceTypes) {
147+
const std::string kPath = px::testing::BazelRunfilePath(std::get<0>(GetParam()));
73148

74149
ASSERT_OK_AND_ASSIGN(std::unique_ptr<ElfReader> elf_reader, ElfReader::Create(kPath));
75-
ASSERT_OK_AND_ASSIGN(const auto interfaces, ExtractGolangInterfaces(elf_reader.get()));
150+
151+
ASSERT_OK_AND_ASSIGN(const auto interfaces_map, ExtractGolangInterfaces(elf_reader.get()));
152+
std::vector<std::pair<std::string, std::vector<IntfImplTypeInfo>>> interfaces(
153+
interfaces_map.begin(), interfaces_map.end());
76154

77155
// Check for `bazel coverage` so we can bypass the final checks.
78156
// Note that we still get accurate coverage metrics, because this only skips the final check.
79157
// Ideally, we'd get bazel to deterministically build test_go_binary,
80158
// but it's not easy to tell bazel to use a different config for just one target.
159+
81160
#ifdef PL_COVERAGE
82161
LOG(INFO) << "Whoa...`bazel coverage` is messaging with test_go_binary. Shame on you bazel. "
83162
"Ending this test early.";
84163
return;
85164
#else
86-
EXPECT_THAT(
87-
interfaces,
88-
UnorderedElementsAre(
89-
Pair("error",
90-
UnorderedElementsAre(
91-
Field(&IntfImplTypeInfo::type_name, "main.IntStruct"),
92-
Field(&IntfImplTypeInfo::type_name, "*errors.errorString"),
93-
Field(&IntfImplTypeInfo::type_name, "*io/fs.PathError"),
94-
Field(&IntfImplTypeInfo::type_name, "*internal/poll.DeadlineExceededError"),
95-
Field(&IntfImplTypeInfo::type_name, "internal/poll.errNetClosing"),
96-
Field(&IntfImplTypeInfo::type_name, "runtime.errorString"),
97-
Field(&IntfImplTypeInfo::type_name, "syscall.Errno"))),
98-
Pair("sort.Interface", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name,
99-
"*internal/fmtsort.SortedMap"))),
100-
Pair("math/rand.Source", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name,
101-
"*math/rand.lockedSource"))),
102-
Pair("io.Writer", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*os.File"))),
103-
Pair("internal/reflectlite.Type",
104-
UnorderedElementsAre(
105-
Field(&IntfImplTypeInfo::type_name, "*internal/reflectlite.rtype"))),
106-
Pair("reflect.Type",
107-
UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*reflect.rtype"))),
108-
Pair("fmt.State", UnorderedElementsAre(Field(&IntfImplTypeInfo::type_name, "*fmt.pp")))));
165+
EXPECT_THAT(interfaces, std::get<1>(GetParam()));
109166
#endif
110167
}
111168

0 commit comments

Comments
 (0)