Skip to content

Commit acb9e85

Browse files
committed
Restore legacy Go version detection for binaries built w/ 1.11 and earlier
Signed-off-by: Dom Del Nano <ddelnano@gmail.com>
1 parent 3cc2cc1 commit acb9e85

5 files changed

Lines changed: 106 additions & 21 deletions

File tree

src/stirling/obj_tools/go_syms.cc

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,40 @@ 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 && str.substr(0, 2) == utils::u8string(reinterpret_cast<const unsigned char*>("go"), 2)) {
99+
str.erase(0, 2); // Remove "go" from the beginning
100+
}
101+
return std::make_pair(std::string(reinterpret_cast<const char*>(str.data()), str.size()),
102+
std::move(build_info));
103+
}
104+
71105
// Extracts the semantic version from a Go version string (e.g., "go1.20.3").
72106
// This is how the version is formatted in the buildinfo header.
73107
StatusOr<std::string> ExtractSemVer(const std::string& input) {
@@ -157,20 +191,32 @@ StatusOr<BuildInfo> ReadModInfo(const std::string& mod) {
157191
return build_info;
158192
}
159193

194+
// Macro that calls ReadBuildVersion on failure before returning
195+
#define PX_ASSIGN_OR_RETURN_WITH_FALLBACK(lhs, rexpr, elf_reader_ptr) \
196+
PX_ASSIGN_OR( \
197+
lhs, rexpr, auto fallback_result = ReadBuildVersion(elf_reader_ptr); \
198+
if (fallback_result.ok()) { \
199+
return fallback_result.ConsumeValueOrDie(); \
200+
} return __s__.status())
201+
160202
// Reads the buildinfo header embedded in the .go.buildinfo ELF section in order to determine the go
161203
// toolchain version. This function emulates what the go version cli performs as seen
162204
// https://github.com/golang/go/blob/cb7a091d729eab75ccfdaeba5a0605f05addf422/src/debug/buildinfo/buildinfo.go#L151-L221
163205
StatusOr<std::pair<std::string, BuildInfo>> ReadGoBuildInfo(ElfReader* elf_reader) {
164-
PX_ASSIGN_OR_RETURN(ELFIO::section * section, elf_reader->SectionWithName(kGoBuildInfoSection));
206+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(ELFIO::section * section,
207+
elf_reader->SectionWithName(kGoBuildInfoSection), elf_reader);
165208
int offset = section->get_offset();
166-
PX_ASSIGN_OR_RETURN(std::string_view buildInfoByteCode,
167-
elf_reader->BinaryByteCode<char>(offset, 64 * 1024));
209+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(std::string_view buildInfoByteCode,
210+
elf_reader->BinaryByteCode<char>(offset, 64 * 1024),
211+
elf_reader);
168212

169213
BinaryDecoder binary_decoder(buildInfoByteCode);
170214

171215
PX_CHECK_OK(binary_decoder.ExtractStringUntil(kGoBuildInfoMagic));
172-
PX_ASSIGN_OR_RETURN(uint8_t ptr_size, binary_decoder.ExtractBEInt<uint8_t>());
173-
PX_ASSIGN_OR_RETURN(uint8_t endianness, binary_decoder.ExtractBEInt<uint8_t>());
216+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(uint8_t ptr_size, binary_decoder.ExtractBEInt<uint8_t>(),
217+
elf_reader);
218+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(uint8_t endianness, binary_decoder.ExtractBEInt<uint8_t>(),
219+
elf_reader);
174220

175221
BuildInfo build_info;
176222
std::string go_version;
@@ -181,11 +227,12 @@ StatusOr<std::pair<std::string, BuildInfo>> ReadGoBuildInfo(ElfReader* elf_reade
181227
// Skip the remaining 16 bytes of buildinfo header
182228
PX_CHECK_OK(binary_decoder.ExtractBufIgnore(16));
183229

184-
PX_ASSIGN_OR_RETURN(uint64_t size, binary_decoder.ExtractUVarInt());
185-
PX_ASSIGN_OR_RETURN(go_version, binary_decoder.ExtractString(size));
230+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(uint64_t size, binary_decoder.ExtractUVarInt(), elf_reader);
231+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(go_version, binary_decoder.ExtractString(size), elf_reader);
186232

187-
PX_ASSIGN_OR_RETURN(uint64_t mod_size, binary_decoder.ExtractUVarInt());
188-
PX_ASSIGN_OR_RETURN(mod_info, binary_decoder.ExtractString(mod_size));
233+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(uint64_t mod_size, binary_decoder.ExtractUVarInt(),
234+
elf_reader);
235+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(mod_info, binary_decoder.ExtractString(mod_size), elf_reader);
189236
} else {
190237
read_ptr_func_t read_ptr;
191238
switch (endianness) {
@@ -227,19 +274,25 @@ StatusOr<std::pair<std::string, BuildInfo>> ReadGoBuildInfo(ElfReader* elf_reade
227274
}
228275

229276
// Reads the virtual address location of the runtime.buildVersion symbol.
230-
PX_ASSIGN_OR_RETURN(auto runtime_version_vaddr,
231-
binary_decoder.ExtractString<u8string_view::value_type>(ptr_size));
232-
PX_ASSIGN_OR_RETURN(auto mod_info_vaddr,
233-
binary_decoder.ExtractString<u8string_view::value_type>(ptr_size));
234-
PX_ASSIGN_OR_RETURN(uint64_t ptr_addr,
235-
elf_reader->VirtualAddrToBinaryAddr(read_ptr(runtime_version_vaddr)));
236-
237-
PX_ASSIGN_OR_RETURN(go_version, ReadGoString(elf_reader, ptr_size, ptr_addr, read_ptr));
277+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(
278+
auto runtime_version_vaddr,
279+
binary_decoder.ExtractString<u8string_view::value_type>(ptr_size), elf_reader);
280+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(
281+
auto mod_info_vaddr, binary_decoder.ExtractString<u8string_view::value_type>(ptr_size),
282+
elf_reader);
283+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(
284+
uint64_t ptr_addr, elf_reader->VirtualAddrToBinaryAddr(read_ptr(runtime_version_vaddr)),
285+
elf_reader);
286+
287+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(
288+
go_version, ReadGoString(elf_reader, ptr_size, ptr_addr, read_ptr), elf_reader);
238289

239290
auto mod_ptr_addr_s = elf_reader->VirtualAddrToBinaryAddr(read_ptr(mod_info_vaddr));
240291
if (mod_ptr_addr_s.ok()) {
241-
PX_ASSIGN_OR_RETURN(mod_info, ReadGoString(elf_reader, ptr_size,
242-
mod_ptr_addr_s.ConsumeValueOrDie(), read_ptr));
292+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(
293+
mod_info,
294+
ReadGoString(elf_reader, ptr_size, mod_ptr_addr_s.ConsumeValueOrDie(), read_ptr),
295+
elf_reader);
243296
}
244297
}
245298

@@ -251,13 +304,15 @@ StatusOr<std::pair<std::string, BuildInfo>> ReadGoBuildInfo(ElfReader* elf_reade
251304
// https://github.com/golang/go/blob/cb7a091d729eab75ccfdaeba5a0605f05addf422/src/debug/buildinfo/buildinfo.go#L214-L215
252305
if (mod_size >= 33 && mod_info.at(mod_size - 17) == '\n') {
253306
mod_info.erase(0, 16);
254-
PX_ASSIGN_OR_RETURN(build_info, ReadModInfo(mod_info));
307+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(build_info, ReadModInfo(mod_info), elf_reader);
255308
}
256309
}
257-
PX_ASSIGN_OR_RETURN(auto s, ExtractSemVer(go_version));
310+
PX_ASSIGN_OR_RETURN_WITH_FALLBACK(auto s, ExtractSemVer(go_version), elf_reader);
258311
return std::make_pair(s, std::move(build_info));
259312
}
260313

314+
#undef PX_ASSIGN_OR_RETURN_WITH_FALLBACK
315+
261316
// Prefixes used to search for itable symbols in the binary. Follows the format:
262317
// <prefix>.<type_name>,<interface_name>. i.e. go.itab.<type_name>,<interface_name>
263318
constexpr std::array<std::string_view, 2> kITablePrefixes = {

src/stirling/obj_tools/go_syms.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ 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 differences.
61+
StatusOr<std::pair<std::string, BuildInfo>> ReadBuildVersion(ElfReader* elf_reader);
62+
5763
// Describes a Golang type that implement an interface.
5864
struct IntfImplTypeInfo {
5965
// 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)