From 4b63bf2834dd97fb1a77346e3987b92e853e4890 Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Mon, 4 Mar 2024 14:00:26 +0000 Subject: [PATCH] Bug 1847098 - Report library version from filename at crash r=gsvelto Differential Revision: https://phabricator.services.mozilla.com/D202249 --- .../linux/microdump_writer/microdump_writer.cc | 4 +- .../linux/minidump_writer/linux_dumper.cc | 12 +- .../linux/minidump_writer/linux_dumper.h | 9 +- .../linux/minidump_writer/minidump_writer.cc | 12 +- toolkit/crashreporter/linux_utils.cc | 59 +++++++++ toolkit/crashreporter/linux_utils.h | 16 +++ toolkit/crashreporter/moz.build | 8 ++ .../crashreporter/test/gtest/TestElfSoVersion.cpp | 143 +++++++++++++++++++++ toolkit/crashreporter/test/gtest/moz.build | 16 +++ toolkit/crashreporter/test/moz.build | 4 + .../test/unit/test_crash_modules_linux.js | 33 +++++ toolkit/crashreporter/test/unit/xpcshell.toml | 4 + tools/crashreporter/injector/moz.build | 5 + 13 files changed, 317 insertions(+), 8 deletions(-) create mode 100644 toolkit/crashreporter/linux_utils.cc create mode 100644 toolkit/crashreporter/linux_utils.h create mode 100644 toolkit/crashreporter/test/gtest/TestElfSoVersion.cpp create mode 100644 toolkit/crashreporter/test/gtest/moz.build create mode 100644 toolkit/crashreporter/test/unit/test_crash_modules_linux.js diff --git a/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc b/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc index 8f25b7be0299..2c2e24e071d7 100644 --- a/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc +++ b/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc @@ -466,8 +466,8 @@ class MicrodumpWriter { char file_name[NAME_MAX]; char file_path[NAME_MAX]; - dumper_->GetMappingEffectiveNameAndPath( - mapping, file_path, sizeof(file_path), file_name, sizeof(file_name)); + dumper_->GetMappingEffectiveNamePathAndVersion( + mapping, file_path, sizeof(file_path), file_name, sizeof(file_name), nullptr); LogAppend("M "); LogAppend(static_cast(mapping.start_addr)); diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc index 3b400ce8ca8b..ef055833977a 100644 --- a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc @@ -468,11 +468,12 @@ bool ElfFileSoName(const LinuxDumper& dumper, } // namespace -void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping, +void LinuxDumper::GetMappingEffectiveNamePathAndVersion(const MappingInfo& mapping, char* file_path, size_t file_path_size, char* file_name, - size_t file_name_size) { + size_t file_name_size, + uint32_t* version) { my_strlcpy(file_path, mapping.name, file_path_size); // Tools such as minidump_stackwalk use the name of the module to look up @@ -487,6 +488,9 @@ void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping, const char* basename = my_strrchr(file_path, '/'); basename = basename == NULL ? file_path : (basename + 1); my_strlcpy(file_name, basename, file_name_size); + if (version) { + ElfFileSoVersion(mapping.name, version); + } return; } @@ -512,6 +516,10 @@ void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping, my_strlcpy(file_path, file_name, file_path_size); } } + + if (version) { + ElfFileSoVersion(mapping.name, version); + } } bool LinuxDumper::ReadAuxv() { diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h index 7155524ffc90..7fd069396871 100644 --- a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h @@ -56,6 +56,10 @@ #include "common/memory_allocator.h" #include "google_breakpad/common/minidump_format.h" +#if defined(XP_LINUX) +# include "linux_utils.h" +#endif // defined(XP_LINUX) + namespace google_breakpad { // Typedef for our parsing of the auxv variables in /proc/pid/auxv. @@ -213,11 +217,12 @@ class LinuxDumper { // other cases, however, a library can be mapped from an archive (e.g., when // loading .so libs from an apk on Android) and this method is able to // reconstruct the original file name. - void GetMappingEffectiveNameAndPath(const MappingInfo& mapping, + void GetMappingEffectiveNamePathAndVersion(const MappingInfo& mapping, char* file_path, size_t file_path_size, char* file_name, - size_t file_name_size); + size_t file_name_size, + uint32_t* version); protected: bool ReadAuxv(); diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc index 03066e9110e3..60d1195949d9 100644 --- a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc @@ -717,8 +717,16 @@ class MinidumpWriter { char file_name[NAME_MAX]; char file_path[NAME_MAX]; - dumper_->GetMappingEffectiveNameAndPath( - mapping, file_path, sizeof(file_path), file_name, sizeof(file_name)); + uint32_t version[4] = { 0, 0, 0, 0 }; + dumper_->GetMappingEffectiveNamePathAndVersion( + mapping, file_path, sizeof(file_path), file_name, sizeof(file_name), version); + + mod->version_info.signature = MD_VSFIXEDFILEINFO_SIGNATURE; + mod->version_info.struct_version |= MD_VSFIXEDFILEINFO_VERSION; + mod->version_info.file_version_hi = version[0]; + mod->version_info.file_version_lo = version[1]; + mod->version_info.product_version_hi = version[2]; + mod->version_info.product_version_lo = version[3]; MDLocationDescriptor ld; if (!minidump_writer_.WriteString(file_path, my_strlen(file_path), &ld)) diff --git a/toolkit/crashreporter/linux_utils.cc b/toolkit/crashreporter/linux_utils.cc new file mode 100644 index 000000000000..cfddecf084e1 --- /dev/null +++ b/toolkit/crashreporter/linux_utils.cc @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "common/linux/linux_libc_support.h" +#include "linux_utils.h" + +bool ElfFileSoVersion(const char* mapping_name, uint32_t* version_components) { + if (!version_components) { + return false; + } + + // We found no version so just report 0 + const char* so_version = my_strstr(mapping_name, ".so."); + if (so_version == nullptr) { + return true; + } + + char tmp[12]; // 11 for maximum representation of UINT32T_MAX + \0 ? + size_t current_position = 0; + size_t next_tmp = 0; + tmp[0] = '\0'; + for (size_t so_version_pos = 0; so_version_pos <= my_strlen(so_version); + ++so_version_pos) { + // We can't have more than four: MAJOR.minor.release.patch + if (current_position == 4) { + break; + } + + char c = so_version[so_version_pos]; + if (c != '.') { + if ((c <= '9' && c >= '0')) { + tmp[next_tmp] = c; + tmp[next_tmp + 1] = '\0'; + ++next_tmp; + } + + if (so_version[so_version_pos + 1] != '\0') { + continue; + } + } + + if (my_strlen(tmp) > 0) { + int t; + if (!my_strtoui(&t, tmp)) { + return false; + } + uint32_t casted_tmp = (uint32_t)t; + version_components[current_position] = casted_tmp; + ++current_position; + } + + tmp[0] = '\0'; + next_tmp = 0; + } + + return true; +} diff --git a/toolkit/crashreporter/linux_utils.h b/toolkit/crashreporter/linux_utils.h new file mode 100644 index 000000000000..2dd4ff785c82 --- /dev/null +++ b/toolkit/crashreporter/linux_utils.h @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef toolkit_breakpad_linux_utils_h__ +#define toolkit_breakpad_linux_utils_h__ + +#include +#include +#include +#include + +bool ElfFileSoVersion(const char* mapping_name, uint32_t* version); + +#endif /* toolkit_breakpad_linux_utils_h__ */ diff --git a/toolkit/crashreporter/moz.build b/toolkit/crashreporter/moz.build index d6f49680e711..952ca405bb10 100644 --- a/toolkit/crashreporter/moz.build +++ b/toolkit/crashreporter/moz.build @@ -55,6 +55,14 @@ if CONFIG["MOZ_CRASHREPORTER"]: "google-breakpad/src/processor", ] + UNIFIED_SOURCES += [ + "linux_utils.cc", + ] + + EXPORTS += [ + "linux_utils.h", + ] + if CONFIG["MOZ_OXIDIZED_BREAKPAD"]: DIRS += ["rust_minidump_writer_linux"] diff --git a/toolkit/crashreporter/test/gtest/TestElfSoVersion.cpp b/toolkit/crashreporter/test/gtest/TestElfSoVersion.cpp new file mode 100644 index 000000000000..a6c91a8dff9f --- /dev/null +++ b/toolkit/crashreporter/test/gtest/TestElfSoVersion.cpp @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "mozilla/SpinEventLoopUntil.h" + +#include "linux_utils.h" + +#define ASSERT_EQ_UNSIGNED(v, e) ASSERT_EQ((v), (uint32_t)(e)) + +using namespace mozilla; + +class CrashReporter : public ::testing::Test {}; + +TEST_F(CrashReporter, ElfSoNoVersion) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion("libdbus1.so", version); + ASSERT_TRUE(rv); +} + +TEST_F(CrashReporter, ElfSo6) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion("libm.so.6", version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version[0], 6); +} + +TEST_F(CrashReporter, ElfSoNormalShort) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion("libdbus1.so.1.2", version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version[0], 1); + ASSERT_EQ_UNSIGNED(version[1], 2); +} + +TEST_F(CrashReporter, ElfSoNormalComplete) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion("libdbus1.so.1.2.3", version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version[0], 1); + ASSERT_EQ_UNSIGNED(version[1], 2); + ASSERT_EQ_UNSIGNED(version[2], 3); +} + +TEST_F(CrashReporter, ElfSoNormalPrerelease) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion("libdbus1.so.1.2.3.98", version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version[0], 1); + ASSERT_EQ_UNSIGNED(version[1], 2); + ASSERT_EQ_UNSIGNED(version[2], 3); + ASSERT_EQ_UNSIGNED(version[3], 98); +} + +TEST_F(CrashReporter, ElfSoNormalPrereleaseToomuch) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion("libdbus1.so.1.2.3.98.9.2.3", version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version[0], 1); + ASSERT_EQ_UNSIGNED(version[1], 2); + ASSERT_EQ_UNSIGNED(version[2], 3); + ASSERT_EQ_UNSIGNED(version[3], 98); +} + +TEST_F(CrashReporter, ElfSoBig) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion("libatk-1.0.so.0.25009.1", version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version[0], 0); + ASSERT_EQ_UNSIGNED(version[1], 25009); + ASSERT_EQ_UNSIGNED(version[2], 1); +} + +TEST_F(CrashReporter, ElfSoCairo) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion("libcairo.so.2.11800.3", version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version[0], 2); + ASSERT_EQ_UNSIGNED(version[1], 11800); + ASSERT_EQ_UNSIGNED(version[2], 3); +} + +TEST_F(CrashReporter, ElfSoMax) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion( + "libcairo.so.2147483647.2147483647.2147483647.2147483647", version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version[0], INT32_MAX); + ASSERT_EQ_UNSIGNED(version[1], INT32_MAX); + ASSERT_EQ_UNSIGNED(version[2], INT32_MAX); + ASSERT_EQ_UNSIGNED(version[3], INT32_MAX); +} + +TEST_F(CrashReporter, ElfSoTimestamp) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion("libabsl_time_zone.so.20220623.0.0", version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version[0], 20220623); + ASSERT_EQ_UNSIGNED(version[1], 0); + ASSERT_EQ_UNSIGNED(version[2], 0); +} + +TEST_F(CrashReporter, ElfSoChars) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion("libabsl_time_zone.so.1.2.3rc4", version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version[0], 1); + ASSERT_EQ_UNSIGNED(version[1], 2); + ASSERT_EQ_UNSIGNED(version[2], 34); +} + +TEST_F(CrashReporter, ElfSoCharsMore) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion("libabsl_time_zone.so.1.2.3rc4.9", version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version[0], 1); + ASSERT_EQ_UNSIGNED(version[1], 2); + ASSERT_EQ_UNSIGNED(version[2], 34); + ASSERT_EQ_UNSIGNED(version[3], 9); +} + +TEST_F(CrashReporter, ElfSoCharsOnly) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion("libabsl_time_zone.so.final", version); + ASSERT_TRUE(rv); +} + +TEST_F(CrashReporter, ElfSoNullVersion) { + bool rv = ElfFileSoVersion("libabsl_time_zone.so.1", nullptr); + ASSERT_FALSE(rv); +} + +TEST_F(CrashReporter, ElfSoFullPath) { + uint32_t version[4] = {0, 0, 0, 0}; + bool rv = ElfFileSoVersion( + "/usr/lib/x86_64-linux-gnu/libabsl_time_zone.so.20220623.0.0", version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version[0], 20220623); + ASSERT_EQ_UNSIGNED(version[1], 0); + ASSERT_EQ_UNSIGNED(version[2], 0); +} diff --git a/toolkit/crashreporter/test/gtest/moz.build b/toolkit/crashreporter/test/gtest/moz.build new file mode 100644 index 000000000000..9aa1c0a0db64 --- /dev/null +++ b/toolkit/crashreporter/test/gtest/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at http://mozilla.org/MPL/2.0/. + +Library("crashreportertest") + +if CONFIG["OS_ARCH"] == "Linux": + UNIFIED_SOURCES = [ + "TestElfSoVersion.cpp", + ] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" diff --git a/toolkit/crashreporter/test/moz.build b/toolkit/crashreporter/test/moz.build index 1ebd9b902937..254d232e0e34 100644 --- a/toolkit/crashreporter/test/moz.build +++ b/toolkit/crashreporter/test/moz.build @@ -9,6 +9,10 @@ XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.toml", "unit_ipc/xpcshell.toml"] if CONFIG["MOZ_PHC"]: XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell-phc.toml", "unit_ipc/xpcshell-phc.toml"] +TEST_DIRS += [ + "gtest", +] + BROWSER_CHROME_MANIFESTS += ["browser/browser.toml"] UNIFIED_SOURCES += [ diff --git a/toolkit/crashreporter/test/unit/test_crash_modules_linux.js b/toolkit/crashreporter/test/unit/test_crash_modules_linux.js new file mode 100644 index 000000000000..9fcf5873087e --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_modules_linux.js @@ -0,0 +1,33 @@ +add_task(async function run_test() { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + dump( + "INFO | test_crash_modules.js | Can't test crashreporter in a non-libxul build.\n" + ); + return; + } + + await do_crash( + function () { + crashType = CrashTestUtils.CRASH_ABORT; + }, + async function (mdump, extra, extraFile) { + runMinidumpAnalyzer(mdump); + + // Refresh updated extra data + extra = await IOUtils.readJSON(extraFile.path); + + // Check modules' versions + const modules = extra.StackTraces.modules; + const version_regexp = /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/; + + for (let module of modules) { + console.debug("module", module); + console.debug("version => ", module.version); + console.debug("version regex => ", version_regexp.exec(module.version)); + Assert.notEqual(version_regexp.exec(module.version), null); + } + }, + // process will exit with a zero exit status + true + ); +}); diff --git a/toolkit/crashreporter/test/unit/xpcshell.toml b/toolkit/crashreporter/test/unit/xpcshell.toml index ffa631d0a12c..6b40a98147c9 100644 --- a/toolkit/crashreporter/test/unit/xpcshell.toml +++ b/toolkit/crashreporter/test/unit/xpcshell.toml @@ -40,6 +40,10 @@ run-if = ["os == 'win'"] reason = "Test covering Windows-specific module handling" run-sequentially = "very high failure rate in parallel" +["test_crash_modules_linux.js"] +run-if = ["os == 'linux'"] +reason = "Test covering Linux-specific module handling" + ["test_crash_moz_crash.js"] ["test_crash_oom.js"] diff --git a/tools/crashreporter/injector/moz.build b/tools/crashreporter/injector/moz.build index d16300fbfbb6..534177b77ea5 100644 --- a/tools/crashreporter/injector/moz.build +++ b/tools/crashreporter/injector/moz.build @@ -19,9 +19,14 @@ UNIFIED_SOURCES += [ "/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.cc", "/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc", "/toolkit/crashreporter/breakpad-client/minidump_file_writer.cc", + "/toolkit/crashreporter/linux_utils.cc", "injector.cc", ] +LOCAL_INCLUDES += [ + "/toolkit/crashreporter/", +] + USE_LIBS += [ "breakpad_common_s", "breakpad_linux_common_s", -- 2.11.4.GIT