@@ -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.
73107StatusOr<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
163205StatusOr<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>
263318constexpr std::array<std::string_view, 2 > kITablePrefixes = {
0 commit comments