diff --git a/src/stirling/obj_tools/BUILD.bazel b/src/stirling/obj_tools/BUILD.bazel index e49289e3a18..03e9bccb7f5 100644 --- a/src/stirling/obj_tools/BUILD.bazel +++ b/src/stirling/obj_tools/BUILD.bazel @@ -135,6 +135,7 @@ pl_cc_test( srcs = ["go_syms_test.cc"], data = [ "//src/stirling/obj_tools/testdata/go:test_binaries", + "//src/stirling/obj_tools/testdata/go:test_buildinfo_with_mods", "//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", diff --git a/src/stirling/obj_tools/elf_reader.h b/src/stirling/obj_tools/elf_reader.h index d492122a26e..6ebfe1cf618 100644 --- a/src/stirling/obj_tools/elf_reader.h +++ b/src/stirling/obj_tools/elf_reader.h @@ -167,7 +167,7 @@ class ElfReader { * ElfAddressConverter::VirtualAddrToBinaryAddr is a more appropriate utility to use. * * Certain use cases may require this function, such as cases where the Go toolchain - * embeds virtual addresses within a binary and must be parsed (See ReadGoBuildVersion and + * embeds virtual addresses within a binary and must be parsed (See ReadGoBuildInfo and * ReadGoString in go_syms.cc). */ StatusOr VirtualAddrToBinaryAddr(uint64_t virtual_addr); diff --git a/src/stirling/obj_tools/go_syms.cc b/src/stirling/obj_tools/go_syms.cc index b57275e9897..6d71e95758d 100644 --- a/src/stirling/obj_tools/go_syms.cc +++ b/src/stirling/obj_tools/go_syms.cc @@ -19,8 +19,6 @@ #include "src/stirling/obj_tools/go_syms.h" #include "src/stirling/utils/binary_decoder.h" -#include - namespace px { namespace stirling { namespace obj_tools { @@ -70,10 +68,99 @@ StatusOr ReadGoString(ElfReader* elf_reader, uint64_t ptr_size, uin return std::string(go_version_bytecode); } +// Extracts the semantic version from a Go version string (e.g., "go1.20.3"). +// This is how the version is formatted in the buildinfo header. +StatusOr ExtractSemVer(const std::string& input) { + size_t go_pos = input.find("go"); // Find "go" + if (go_pos == std::string::npos) { + LOG(ERROR) << "Prefix 'go' not found in input."; + return error::NotFound("Prefix 'go' not found in input."); + } + + size_t start = go_pos + 2; // Move past "go" + size_t end = input.find(" ", start); // Find space delimiter after version + if (end == std::string::npos) { + end = input.size(); // If no space, take the rest of the string + } + + return input.substr(start, end - start); +} + +// This is modeled after go's runtime/debug package +// https://github.com/golang/go/blob/93fb2c90740aef00553c9ce6a7cd4578c2469675/src/runtime/debug/mod.go#L158 +StatusOr ReadModInfo(const std::string& mod) { + BuildInfo build_info; + Module* last_module = nullptr; + + for (std::string_view line : absl::StrSplit(mod, '\n')) { + if (absl::StartsWith(line, "path\t")) { + build_info.path = line.substr(5); + } else if (absl::StartsWith(line, "mod\t")) { + std::vector mod_parts = absl::StrSplit(line.substr(4), '\t'); + + // The sum is optional, so each line must have either 2 or 3 parts. + auto size = mod_parts.size(); + if (size != 2 && size != 3) { + return error::InvalidArgument(absl::Substitute("Invalid mod line format: $0", line)); + } + build_info.main.path = mod_parts[0]; + build_info.main.version = mod_parts[1]; + if (size == 3) { + build_info.main.sum = mod_parts[2]; + } + VLOG(2) << absl::Substitute("mod.path=$0, mod.version=$1, mod.sum=$2", build_info.main.path, + build_info.main.version, build_info.main.sum); + last_module = &build_info.main; + } else if (absl::StartsWith(line, "dep\t")) { + std::vector dep_parts = absl::StrSplit(line.substr(4), '\t'); + + // The sum is optional, so each line must have either 2 or 3 parts. + auto size = dep_parts.size(); + if (size != 2 && size != 3) { + return error::InvalidArgument(absl::Substitute("Invalid dep line format: $0", line)); + } + Module dep; + dep.path = dep_parts[0]; + dep.version = dep_parts[1]; + if (size == 3) { + dep.sum = dep_parts[2]; + } + + build_info.deps.push_back(std::move(dep)); + last_module = &build_info.deps.back(); + + VLOG(2) << absl::Substitute("dep.path=$0, dep.version=$1, dep.sum=$2", dep.path, dep.version, + dep.sum); + + } else if (absl::StartsWith(line, "=>\t")) { + if (last_module == nullptr) { + return error::InvalidArgument( + "Unexpected module replacement line with no preceding module."); + } + std::vector replace_parts = absl::StrSplit(line.substr(3), '\t'); + + if (replace_parts.size() != 3) { + return error::InvalidArgument( + absl::Substitute("Invalid module replacement line format: $0", line)); + } + + std::unique_ptr replacement = std::make_unique(); + replacement->path = replace_parts[0]; + replacement->version = replace_parts[1]; + replacement->sum = replace_parts[2]; + last_module->replace = std::move(replacement); + } + // TODO(ddelnano): Handle the build flags line in the future + // (https://github.com/golang/go/blob/93fb2c90740aef00553c9ce6a7cd4578c2469675/src/runtime/debug/mod.go#L171). + // This is omitted for now since it doesn't help with Go uprobes. + } + return build_info; +} + // Reads the buildinfo header embedded in the .go.buildinfo ELF section in order to determine the go // toolchain version. This function emulates what the go version cli performs as seen // https://github.com/golang/go/blob/cb7a091d729eab75ccfdaeba5a0605f05addf422/src/debug/buildinfo/buildinfo.go#L151-L221 -StatusOr ReadGoBuildVersion(ElfReader* elf_reader) { +StatusOr> ReadGoBuildInfo(ElfReader* elf_reader) { PX_ASSIGN_OR_RETURN(ELFIO::section * section, elf_reader->SectionWithName(kGoBuildInfoSection)); int offset = section->get_offset(); PX_ASSIGN_OR_RETURN(std::string_view buildInfoByteCode, @@ -85,6 +172,9 @@ StatusOr ReadGoBuildVersion(ElfReader* elf_reader) { PX_ASSIGN_OR_RETURN(uint8_t ptr_size, binary_decoder.ExtractBEInt()); PX_ASSIGN_OR_RETURN(uint8_t endianness, binary_decoder.ExtractBEInt()); + BuildInfo build_info; + std::string go_version; + std::string mod_info; // If the endianness has its second bit set, then the go version immediately follows the 32 bit // header specified by the varint encoded string data if ((endianness & 0x2) != 0) { @@ -92,56 +182,80 @@ StatusOr ReadGoBuildVersion(ElfReader* elf_reader) { PX_CHECK_OK(binary_decoder.ExtractBufIgnore(16)); PX_ASSIGN_OR_RETURN(uint64_t size, binary_decoder.ExtractUVarInt()); - PX_ASSIGN_OR_RETURN(std::string_view go_version, binary_decoder.ExtractString(size)); - return std::string(go_version); - } - - read_ptr_func_t read_ptr; - switch (endianness) { - case 0x0: { - if (ptr_size == 4) { - read_ptr = [&](u8string_view str_view) { - return utils::LEndianBytesToInt(str_view); - }; - } else if (ptr_size == 8) { - read_ptr = [&](u8string_view str_view) { - return utils::LEndianBytesToInt(str_view); - }; - } else { - return error::NotFound(absl::Substitute( - "Binary reported pointer size=$0, refusing to parse non go binary", ptr_size)); + PX_ASSIGN_OR_RETURN(go_version, binary_decoder.ExtractString(size)); + + PX_ASSIGN_OR_RETURN(uint64_t mod_size, binary_decoder.ExtractUVarInt()); + PX_ASSIGN_OR_RETURN(mod_info, binary_decoder.ExtractString(mod_size)); + } else { + read_ptr_func_t read_ptr; + switch (endianness) { + case 0x0: { + if (ptr_size == 4) { + read_ptr = [&](u8string_view str_view) { + return utils::LEndianBytesToInt(str_view); + }; + } else if (ptr_size == 8) { + read_ptr = [&](u8string_view str_view) { + return utils::LEndianBytesToInt(str_view); + }; + } else { + return error::NotFound(absl::Substitute( + "Binary reported pointer size=$0, refusing to parse non go binary", ptr_size)); + } + break; } - break; - } - case 0x1: - if (ptr_size == 4) { - read_ptr = [&](u8string_view str_view) { - return utils::BEndianBytesToInt(str_view); - }; - } else if (ptr_size == 8) { - read_ptr = [&](u8string_view str_view) { - return utils::BEndianBytesToInt(str_view); - }; - } else { - return error::NotFound(absl::Substitute( - "Binary reported pointer size=$0, refusing to parse non go binary", ptr_size)); + case 0x1: + if (ptr_size == 4) { + read_ptr = [&](u8string_view str_view) { + return utils::BEndianBytesToInt(str_view); + }; + } else if (ptr_size == 8) { + read_ptr = [&](u8string_view str_view) { + return utils::BEndianBytesToInt(str_view); + }; + } else { + return error::NotFound(absl::Substitute( + "Binary reported pointer size=$0, refusing to parse non go binary", ptr_size)); + } + break; + default: { + auto msg = + absl::Substitute("Invalid endianness=$0, refusing to parse non go binary", endianness); + DCHECK(false) << msg; + return error::NotFound(msg); } - break; - default: { - auto msg = - absl::Substitute("Invalid endianness=$0, refusing to parse non go binary", endianness); - DCHECK(false) << msg; - return error::NotFound(msg); } - } - // Reads the virtual address location of the runtime.buildVersion symbol. - PX_ASSIGN_OR_RETURN(auto runtime_version_vaddr, - binary_decoder.ExtractString(ptr_size)); - PX_ASSIGN_OR_RETURN(uint64_t ptr_addr, - elf_reader->VirtualAddrToBinaryAddr(read_ptr(runtime_version_vaddr))); + // Reads the virtual address location of the runtime.buildVersion symbol. + PX_ASSIGN_OR_RETURN(auto runtime_version_vaddr, + binary_decoder.ExtractString(ptr_size)); + PX_ASSIGN_OR_RETURN(auto mod_info_vaddr, + binary_decoder.ExtractString(ptr_size)); + PX_ASSIGN_OR_RETURN(uint64_t ptr_addr, + elf_reader->VirtualAddrToBinaryAddr(read_ptr(runtime_version_vaddr))); + + PX_ASSIGN_OR_RETURN(go_version, ReadGoString(elf_reader, ptr_size, ptr_addr, read_ptr)); + + auto mod_ptr_addr_s = elf_reader->VirtualAddrToBinaryAddr(read_ptr(mod_info_vaddr)); + if (mod_ptr_addr_s.ok()) { + PX_ASSIGN_OR_RETURN(mod_info, ReadGoString(elf_reader, ptr_size, + mod_ptr_addr_s.ConsumeValueOrDie(), read_ptr)); + } + } - return ReadGoString(elf_reader, ptr_size, ptr_addr, read_ptr); + auto mod_size = mod_info.size(); + if (mod_size > 0) { + // The module info string is delimited by the sentinel strings cmd/go/internal/modload.infoStart + // and infoEnd. These strings are 16 characters long, so first check that the module info + // contains more than the sentinel strings. This check reflects upstream's implementation + // https://github.com/golang/go/blob/cb7a091d729eab75ccfdaeba5a0605f05addf422/src/debug/buildinfo/buildinfo.go#L214-L215 + if (mod_size >= 33 && mod_info.at(mod_size - 17) == '\n') { + mod_info.erase(0, 16); + PX_ASSIGN_OR_RETURN(build_info, ReadModInfo(mod_info)); + } + } + PX_ASSIGN_OR_RETURN(auto s, ExtractSemVer(go_version)); + return std::make_pair(s, std::move(build_info)); } // Prefixes used to search for itable symbols in the binary. Follows the format: diff --git a/src/stirling/obj_tools/go_syms.h b/src/stirling/obj_tools/go_syms.h index 56ffe858abd..bdec9c81cd3 100644 --- a/src/stirling/obj_tools/go_syms.h +++ b/src/stirling/obj_tools/go_syms.h @@ -18,8 +18,10 @@ #pragma once +#include #include #include +#include #include #include @@ -33,11 +35,24 @@ namespace obj_tools { // Returns true if the executable is built by Golang. bool IsGoExecutable(ElfReader* elf_reader); -// Returns the build version of a Golang executable. The executable is read through the input -// elf_reader. -// TODO(yzhao): We'll use this to determine the corresponding Golang executable's TLS data -// structures and their offsets. -StatusOr ReadGoBuildVersion(ElfReader* elf_reader); +struct Module { + std::string path; + std::string version; + std::string sum; + std::unique_ptr replace = nullptr; +}; + +struct BuildInfo { + std::string path; + Module main; + std::vector deps; + std::vector> settings; +}; + +StatusOr ReadModInfo(const std::string& mod); +// Returns the build version and buildinfo of a Golang executable. The executable is read through +// the input elf_reader. +StatusOr> ReadGoBuildInfo(ElfReader* elf_reader); // Describes a Golang type that implement an interface. struct IntfImplTypeInfo { diff --git a/src/stirling/obj_tools/go_syms_test.cc b/src/stirling/obj_tools/go_syms_test.cc index f44f1cebed3..ef82ee31399 100644 --- a/src/stirling/obj_tools/go_syms_test.cc +++ b/src/stirling/obj_tools/go_syms_test.cc @@ -39,6 +39,9 @@ constexpr std::string_view kTestGoLittleEndiani386BinaryPath = constexpr std::string_view kTestGoLittleEndianBinaryPath = "src/stirling/obj_tools/testdata/go/test_go_1_17_binary"; +constexpr std::string_view kTestGoWithModulesBinaryPath = + "src/stirling/obj_tools/testdata/go/test_buildinfo_with_mods"; + constexpr std::string_view kTestGoBinaryPath = "src/stirling/obj_tools/testdata/go/test_go_1_19_binary"; constexpr std::string_view kTestGo1_21BinaryPath = @@ -47,25 +50,140 @@ constexpr std::string_view kTestGo1_21BinaryPath = // 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. // https://github.com/golang/go/blob/1dbbafc70fd3e2c284469ab3e0936c1bb56129f6/src/debug/buildinfo/buildinfo.go#L184C16-L184C16 -TEST(ReadGoBuildVersionTest, BuildinfoEndianAgnostic) { +TEST(ReadGoBuildInfoTest, BuildinfoEndianAgnostic) { const std::string kPath = px::testing::BazelRunfilePath(kTestGoBinaryPath); ASSERT_OK_AND_ASSIGN(std::unique_ptr elf_reader, ElfReader::Create(kPath)); - ASSERT_OK_AND_ASSIGN(std::string version, ReadGoBuildVersion(elf_reader.get())); - EXPECT_THAT(version, StrEq("go1.19.13")); + ASSERT_OK_AND_ASSIGN(auto pair, ReadGoBuildInfo(elf_reader.get())); + auto version = pair.first; + EXPECT_THAT(version, StrEq("1.19.13")); } -TEST(ReadGoBuildVersionTest, BuildinfoLittleEndian) { +TEST(ReadGoBuildInfoTest, BuildinfoLittleEndian) { const std::string kPath = px::testing::BazelRunfilePath(kTestGoLittleEndianBinaryPath); ASSERT_OK_AND_ASSIGN(std::unique_ptr elf_reader, ElfReader::Create(kPath)); - ASSERT_OK_AND_ASSIGN(std::string version, ReadGoBuildVersion(elf_reader.get())); - EXPECT_THAT(version, StrEq("go1.17.13")); + ASSERT_OK_AND_ASSIGN(auto pair, ReadGoBuildInfo(elf_reader.get())); + auto version = pair.first; + EXPECT_THAT(version, StrEq("1.17.13")); +} + +// These tests are modeled off of upstream's +// https://github.com/golang/go/blob/93fb2c90740aef00553c9ce6a7cd4578c2469675/src/runtime/debug/mod_test.go#L23 +TEST(ReadGoBuildInfoTest, BuildinfoPackageBuiltOutsideModule) { + const std::string kBinContent = + "path\trsc.io/fortune\n" + "mod\trsc.io/fortune\tv1.0.0"; + auto build_info_s = ReadModInfo(kBinContent); + EXPECT_OK(build_info_s); + + auto build_info = build_info_s.ConsumeValueOrDie(); + EXPECT_EQ(build_info.path, "rsc.io/fortune"); + EXPECT_EQ(build_info.main.path, "rsc.io/fortune"); + EXPECT_EQ(build_info.main.version, "v1.0.0"); +} + +TEST(ReadGoBuildInfoTest, BuildinfoPackageBuiltStdlib) { + const std::string kBinContent = "path\tcmd/test2json"; + auto build_info_s = ReadModInfo(kBinContent); + EXPECT_OK(build_info_s); + auto build_info = build_info_s.ConsumeValueOrDie(); + EXPECT_EQ(build_info.path, "cmd/test2json"); } -TEST(ReadGoBuildVersionTest, BuildinfoLittleEndiani386) { +TEST(ReadGoBuildInfoTest, BuildinfoPackageBuiltInsideModule) { + const std::string kBinContent = + "go\t1.18\n" + "path\texample.com/m\n" + "mod\texample.com/m\t(devel)\n" + "build\t-compiler=gc"; + auto build_info_s = ReadModInfo(kBinContent); + EXPECT_OK(build_info_s); + + auto build_info = build_info_s.ConsumeValueOrDie(); + EXPECT_EQ(build_info.path, "example.com/m"); + EXPECT_EQ(build_info.main.path, "example.com/m"); + EXPECT_EQ(build_info.main.version, "(devel)"); + EXPECT_EQ(build_info.main.replace, nullptr); + EXPECT_EQ(build_info.deps.size(), 0); +} + +TEST(ReadGoBuildInfoTest, BuildinfoWithModules) { + const std::string kPath = px::testing::BazelRunfilePath(kTestGoWithModulesBinaryPath); + ASSERT_OK_AND_ASSIGN(std::unique_ptr elf_reader, ElfReader::Create(kPath)); + ASSERT_OK_AND_ASSIGN(auto pair, ReadGoBuildInfo(elf_reader.get())); + auto version = pair.first; + EXPECT_THAT(version, StrEq("1.23.0")); + + auto& build_info = pair.second; + // Validate main module path. + EXPECT_THAT(build_info.path, + StrEq("go.opentelemetry.io/auto/internal/tools/inspect/cmd/offsetgen")); + + // Validate main module metadata. + EXPECT_THAT(build_info.main.path, StrEq("go.opentelemetry.io/auto/internal/tools")); + EXPECT_THAT(build_info.main.version, StrEq("(devel)")); + EXPECT_EQ(build_info.main.replace, nullptr); + + // Validate module dependencies. + EXPECT_THAT(build_info.deps, + UnorderedElementsAre( + Field(&Module::path, StrEq("github.com/Masterminds/semver/v3")), + Field(&Module::path, StrEq("github.com/cilium/ebpf")), + Field(&Module::path, StrEq("github.com/distribution/reference")), + Field(&Module::path, StrEq("github.com/docker/docker")), + Field(&Module::path, StrEq("github.com/docker/go-connections")), + Field(&Module::path, StrEq("github.com/docker/go-units")), + Field(&Module::path, StrEq("github.com/felixge/httpsnoop")), + Field(&Module::path, StrEq("github.com/go-logr/logr")), + Field(&Module::path, StrEq("github.com/go-logr/stdr")), + Field(&Module::path, StrEq("github.com/gogo/protobuf")), + Field(&Module::path, StrEq("github.com/moby/docker-image-spec")), + Field(&Module::path, StrEq("github.com/opencontainers/go-digest")), + Field(&Module::path, StrEq("github.com/opencontainers/image-spec")), + Field(&Module::path, StrEq("github.com/pkg/errors")), + Field(&Module::path, StrEq("go.opentelemetry.io/auto")), + Field(&Module::path, StrEq("go.opentelemetry.io/auto/sdk")), + Field(&Module::path, StrEq("go.opentelemetry.io/collector/pdata")), + Field(&Module::path, + StrEq("go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp")), + Field(&Module::path, StrEq("go.opentelemetry.io/otel")), + Field(&Module::path, StrEq("go.opentelemetry.io/otel/metric")), + Field(&Module::path, StrEq("go.opentelemetry.io/otel/trace")), + Field(&Module::path, StrEq("go.uber.org/multierr")), + Field(&Module::path, StrEq("golang.org/x/arch")), + Field(&Module::path, StrEq("golang.org/x/net")), + Field(&Module::path, StrEq("golang.org/x/sync")), + Field(&Module::path, StrEq("golang.org/x/sys")), + Field(&Module::path, StrEq("golang.org/x/text")), + Field(&Module::path, StrEq("google.golang.org/genproto/googleapis/rpc")), + Field(&Module::path, StrEq("google.golang.org/grpc")), + Field(&Module::path, StrEq("google.golang.org/protobuf")))); + + // Validate replaced modules. + EXPECT_THAT(build_info.deps, + Contains(AllOf(Field(&Module::path, StrEq("go.opentelemetry.io/auto")), + Field(&Module::replace, + Pointee(AllOf(Field(&Module::path, StrEq("../../")), + Field(&Module::version, StrEq("(devel)")))))))); + + EXPECT_THAT(build_info.deps, + Contains(AllOf(Field(&Module::path, StrEq("go.opentelemetry.io/auto/sdk")), + Field(&Module::replace, + Pointee(AllOf(Field(&Module::path, StrEq("../../sdk")), + Field(&Module::version, StrEq("(devel)")))))))); +} + +TEST(ReadGoBuildInfoTest, BuildinfoLittleEndiani386) { const std::string kPath = px::testing::BazelRunfilePath(kTestGoLittleEndiani386BinaryPath); ASSERT_OK_AND_ASSIGN(std::unique_ptr elf_reader, ElfReader::Create(kPath)); - ASSERT_OK_AND_ASSIGN(std::string version, ReadGoBuildVersion(elf_reader.get())); - EXPECT_THAT(version, StrEq("go1.13.15")); + ASSERT_OK_AND_ASSIGN(auto pair, ReadGoBuildInfo(elf_reader.get())); + + auto version = pair.first; + EXPECT_THAT(version, StrEq("1.13.15")); + + auto& buildinfo = pair.second; + EXPECT_THAT(buildinfo.path, StrEq("command-line-arguments")); + EXPECT_THAT(buildinfo.main.path, StrEq("px.dev/pixie")); + EXPECT_THAT(buildinfo.main.version, StrEq("(devel)")); } TEST(IsGoExecutableTest, WorkingOnBasicGoBinary) { diff --git a/src/stirling/obj_tools/testdata/go/BUILD.bazel b/src/stirling/obj_tools/testdata/go/BUILD.bazel index 95c4bae03ff..0a5d26505f4 100644 --- a/src/stirling/obj_tools/testdata/go/BUILD.bazel +++ b/src/stirling/obj_tools/testdata/go/BUILD.bazel @@ -75,6 +75,9 @@ filegroup( # (https://github.com/golang/go/blob/1dbbafc70fd3e2c284469ab3e0936c1bb56129f6/src/debug/buildinfo/buildinfo.go#L189-L190) # and so it cannot be tested without compiling against an older Go version. "test_go_1_17_binary", + # TODO(ddelnano): rules_go doesn't support populating .buildinfo with dependency information (https://github.com/bazel-contrib/rules_go/issues/3090). + # Once this is supported, test_buildinfo_with_mods should be replaced with a bazel built binary. + "test_buildinfo_with_mods", ":test_go_1_18_binary", ":test_go_1_19_binary", ":test_go_1_20_binary", diff --git a/src/stirling/obj_tools/testdata/go/test_buildinfo_with_mods b/src/stirling/obj_tools/testdata/go/test_buildinfo_with_mods new file mode 100755 index 00000000000..9af7f76a19b Binary files /dev/null and b/src/stirling/obj_tools/testdata/go/test_buildinfo_with_mods differ diff --git a/src/stirling/source_connectors/socket_tracer/uprobe_symaddrs.cc b/src/stirling/source_connectors/socket_tracer/uprobe_symaddrs.cc index b9cdac27d68..cf30c680e0b 100644 --- a/src/stirling/source_connectors/socket_tracer/uprobe_symaddrs.cc +++ b/src/stirling/source_connectors/socket_tracer/uprobe_symaddrs.cc @@ -468,8 +468,8 @@ Status PopulateHTTP2DebugSymbols(DwarfReader* dwarf_reader, std::string_view ven Status PopulateGoTLSDebugSymbols(ElfReader* elf_reader, DwarfReader* dwarf_reader, struct go_tls_symaddrs_t* symaddrs) { - PX_ASSIGN_OR_RETURN(std::string build_version, ReadGoBuildVersion(elf_reader)); - PX_ASSIGN_OR_RETURN(SemVer go_version, GetSemVer(build_version, false)); + PX_ASSIGN_OR_RETURN(auto build_info, ReadGoBuildInfo(elf_reader)); + PX_ASSIGN_OR_RETURN(SemVer go_version, GetSemVer(build_info.first, false)); std::string retval0_arg = "~r1"; std::string retval1_arg = "~r2";