Skip to content

Commit cce2254

Browse files
authored
Restore legacy Go version detection for binaries built w/ 1.11 and earlier (#2225)
Summary: Restore legacy Go version detection for binaries built w/ 1.11 and earlier This PR reintroduces our original Go version detection logic as a fallback mechanism for edge cases. This was previously removed because it was the source of several bugs: 1. It caused huge memory allocations for 32-bit binaries 2. It didn't work for Go 1.20.5 and later We migrated the version detection to an implementation that was introduced in Go 1.12, supports 32 and 64 bit binaries and is aligned with the Go toolchain. While it's unlikely that end users are running 32 bit, pre Go 1.12 binaries, our existing [ProductCatalogServiceTraceTest.Basic](https://github.com/pixie-io/pixie/blob/3cc2cc19f5a15619d41d59a88b35037f20bcea88/src/stirling/source_connectors/socket_tracer/http2_trace_bpf_test.cc#L186-L190) test case does. I opted to test the 32-bit case's failure instead of fixing it because I believe fixing it will require more extensive testing on the eBPF side. The memory allocations are guaranteed to be bounded now, so this is safe. Relevant Issues: #2210 Type of change: /kind bugfix Test Plan: Existing and new test cases pass --------- Signed-off-by: Dom Del Nano <[email protected]>
1 parent 3a7e3fe commit cce2254

File tree

5 files changed

+74
-1
lines changed

5 files changed

+74
-1
lines changed

src/stirling/obj_tools/go_syms.cc

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,41 @@ StatusOr<std::string> ReadGoString(ElfReader* elf_reader, uint64_t ptr_size, uin
6868
return std::string(go_version_bytecode);
6969
}
7070

71+
// Reads the Go version from the runtime.buildVersion symbol in the binary's data section.
72+
// This function serves as a fallback for older Go binaries (Go 1.11 and earlier) that don't
73+
// have the .go.buildinfo section. It extracts the version string from the runtime.buildVersion
74+
// symbol and strips the "go" prefix to return just the semantic version (e.g., "1.11.13").
75+
// Note: This function does not work correctly with 32-bit binaries due to gostring structure
76+
// size differences (see https://github.com/pixie-io/pixie/issues/1300).
77+
// See https://github.com/pixie-io/pixie/issues/1318 for context.
78+
StatusOr<std::pair<std::string, BuildInfo>> ReadBuildVersion(ElfReader* elf_reader) {
79+
BuildInfo build_info;
80+
PX_ASSIGN_OR_RETURN(ElfReader::SymbolInfo symbol,
81+
elf_reader->SearchTheOnlySymbol(kGoBuildVersionSymbol));
82+
83+
// The address of this symbol points to a Golang string object.
84+
// But the size is for the symbol table entry, not this string object.
85+
symbol.size = sizeof(gostring);
86+
PX_ASSIGN_OR_RETURN(utils::u8string version_code, elf_reader->SymbolByteCode(".data", symbol));
87+
88+
// We can't guarantee the alignment on version_string so we make a copy into an aligned address.
89+
gostring version_string;
90+
std::memcpy(&version_string, version_code.data(), sizeof(version_string));
91+
92+
ElfReader::SymbolInfo version_symbol;
93+
version_symbol.address = reinterpret_cast<uint64_t>(version_string.ptr);
94+
version_symbol.size = version_string.len;
95+
96+
PX_ASSIGN_OR_RETURN(utils::u8string str, elf_reader->SymbolByteCode(".data", version_symbol));
97+
// Strip go prefix from the version string.
98+
if (str.size() >= 2 &&
99+
str.substr(0, 2) == utils::u8string(reinterpret_cast<const unsigned char*>("go"), 2)) {
100+
str.erase(0, 2); // Remove "go" from the beginning
101+
}
102+
return std::make_pair(std::string(reinterpret_cast<const char*>(str.data()), str.size()),
103+
std::move(build_info));
104+
}
105+
71106
// Extracts the semantic version from a Go version string (e.g., "go1.20.3").
72107
// This is how the version is formatted in the buildinfo header.
73108
StatusOr<std::string> ExtractSemVer(const std::string& input) {
@@ -161,7 +196,14 @@ StatusOr<BuildInfo> ReadModInfo(const std::string& mod) {
161196
// toolchain version. This function emulates what the go version cli performs as seen
162197
// https://github.com/golang/go/blob/cb7a091d729eab75ccfdaeba5a0605f05addf422/src/debug/buildinfo/buildinfo.go#L151-L221
163198
StatusOr<std::pair<std::string, BuildInfo>> ReadGoBuildInfo(ElfReader* elf_reader) {
164-
PX_ASSIGN_OR_RETURN(ELFIO::section * section, elf_reader->SectionWithName(kGoBuildInfoSection));
199+
auto build_info_section_s = elf_reader->SectionWithName(kGoBuildInfoSection);
200+
201+
if (!build_info_section_s.ok()) {
202+
// If the section is not found, it means that the binary is either not a Go binary or it was
203+
// built with an older version of Go that does not include the .go.buildinfo section.
204+
return ReadBuildVersion(elf_reader);
205+
}
206+
auto section = build_info_section_s.ConsumeValueOrDie();
165207
int offset = section->get_offset();
166208
PX_ASSIGN_OR_RETURN(std::string_view buildInfoByteCode,
167209
elf_reader->BinaryByteCode<char>(offset, 64 * 1024));

src/stirling/obj_tools/go_syms.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ StatusOr<BuildInfo> ReadModInfo(const std::string& mod);
5454
// the input elf_reader.
5555
StatusOr<std::pair<std::string, BuildInfo>> ReadGoBuildInfo(ElfReader* elf_reader);
5656

57+
// Returns the build version by reading the runtime.buildVersion symbol from a Golang executable.
58+
// This is a fallback method for older Go binaries (Go 1.11 and earlier) that don't have the
59+
// .go.buildinfo section. The version string has the "go" prefix stripped (e.g., "1.11.13").
60+
// Note: This function does not work correctly with 32-bit binaries due to gostring structure size
61+
// differences.
62+
StatusOr<std::pair<std::string, BuildInfo>> ReadBuildVersion(ElfReader* elf_reader);
63+
5764
// Describes a Golang type that implement an interface.
5865
struct IntfImplTypeInfo {
5966
// The name of the type that implements a given interface.

src/stirling/obj_tools/go_syms_test.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ using ::testing::UnorderedElementsAre;
3636
constexpr std::string_view kTestGoLittleEndiani386BinaryPath =
3737
"src/stirling/obj_tools/testdata/go/test_go1_13_i386_binary";
3838

39+
constexpr std::string_view kTestGo1_11BinaryPath =
40+
"src/stirling/obj_tools/testdata/go/test_go_1_11_binary";
41+
3942
constexpr std::string_view kTestGoLittleEndianBinaryPath =
4043
"src/stirling/obj_tools/testdata/go/test_go_1_17_binary";
4144

@@ -186,6 +189,24 @@ TEST(ReadGoBuildInfoTest, BuildinfoLittleEndiani386) {
186189
EXPECT_THAT(buildinfo.main.version, StrEq("(devel)"));
187190
}
188191

192+
TEST(ReadGoBuildInfoTest, BuildVersionLittleEndiani386) {
193+
const std::string kPath = px::testing::BazelRunfilePath(kTestGoLittleEndiani386BinaryPath);
194+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<ElfReader> elf_reader, ElfReader::Create(kPath));
195+
auto result = ReadBuildVersion(elf_reader.get());
196+
197+
EXPECT_FALSE(result.ok());
198+
EXPECT_THAT(result.msg(), ::testing::HasSubstr("Refusing to preallocate that much memory"));
199+
}
200+
201+
TEST(ReadGoBuildInfoTest, BuildVersionGo1_11) {
202+
const std::string kPath = px::testing::BazelRunfilePath(kTestGo1_11BinaryPath);
203+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<ElfReader> elf_reader, ElfReader::Create(kPath));
204+
ASSERT_OK_AND_ASSIGN(auto pair, ReadBuildVersion(elf_reader.get()));
205+
206+
auto version = pair.first;
207+
EXPECT_THAT(version, StrEq("1.11.13"));
208+
}
209+
189210
TEST(IsGoExecutableTest, WorkingOnBasicGoBinary) {
190211
const std::string kPath = px::testing::BazelRunfilePath(kTestGoBinaryPath);
191212
ASSERT_OK_AND_ASSIGN(std::unique_ptr<ElfReader> elf_reader, ElfReader::Create(kPath));

src/stirling/obj_tools/testdata/go/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ filegroup(
6969
# These older 32 bit binaries have been the source of bugs, so this test case verifies we don't
7070
# introduce a regression (https://github.com/pixie-io/pixie/issues/1300).
7171
"test_go1_13_i386_binary",
72+
# This binary was built with go 1.11. This ensures that ReadBuildVersion works for Go 1.11 and earlier
73+
# binaries that don't have the .go.buildinfo section.
74+
"test_go_1_11_binary",
7275
# This binary was built with go 1.17. This ensures that the 64 bit little endian case buildinfo logic is tested.
7376
# (https://github.com/golang/go/blob/1dbbafc70fd3e2c284469ab3e0936c1bb56129f6/src/debug/buildinfo/buildinfo.go#L192-L208).
7477
# Newer versions of go generate the endian agnostic buildinfo header
1.86 MB
Binary file not shown.

0 commit comments

Comments
 (0)