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..3fea60a7e34e 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, + VersionComponents* 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..9532951daa1b 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, + VersionComponents* 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..6cca89f30033 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)); + VersionComponents version; + 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.size() >= 1 ? version[0] : 0; + mod->version_info.file_version_lo = version.size() >= 2 ? version[1] : 0; + mod->version_info.product_version_hi = version.size() >= 3 ? version[2] : 0; + mod->version_info.product_version_lo = version.size() == 4 ? version[3] : 0; 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..1a3162e0d6ee --- /dev/null +++ b/toolkit/crashreporter/linux_utils.cc @@ -0,0 +1,62 @@ +/* -*- 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 "linux_utils.h" + +bool ElfFileSoVersion(const char* mapping_name, + VersionComponents* version_components) { + if (!version_components) { + return false; + } + + std::string path = std::string(mapping_name); + std::string filename = path.substr(path.find_last_of('/') + 1); + + std::string dot_so_dot(".so."); + // We found no version so just report 0 + size_t has_dot_so_dot = filename.find(dot_so_dot); + if (has_dot_so_dot == std::string::npos) { + return true; + } + + std::string so_version = + filename.substr(has_dot_so_dot + dot_so_dot.length()); + std::string tmp; + for (std::string::iterator it = so_version.begin(); it != so_version.end(); + ++it) { + // We can't have more than four: MAJOR.minor.release.patch + if (version_components->size() == 4) { + break; + } + + char c = *it; + if (c != '.') { + if (isdigit(c)) { + tmp += c; + } + + if (std::next(it) != so_version.end()) { + continue; + } + } + + if (tmp.length() > 0) { + int t = std::stoi(tmp); // assume tmp is < UINT32T_MAX + if (t < 0) { + return false; + } + + uint32_t casted_tmp = static_cast(t); + // We have lost some information we should warn. + if ((unsigned int)t != casted_tmp) { + return false; + } + version_components->push_back(casted_tmp); + } + tmp = ""; + } + + return true; +} diff --git a/toolkit/crashreporter/linux_utils.h b/toolkit/crashreporter/linux_utils.h new file mode 100644 index 000000000000..5226bd8164cb --- /dev/null +++ b/toolkit/crashreporter/linux_utils.h @@ -0,0 +1,18 @@ +/* -*- 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 + +using VersionComponents = std::vector; + +bool ElfFileSoVersion(const char* mapping_name, VersionComponents* 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..3c46a297d8ba --- /dev/null +++ b/toolkit/crashreporter/test/gtest/TestElfSoVersion.cpp @@ -0,0 +1,146 @@ +/* -*- 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) { + VersionComponents version; + bool rv = ElfFileSoVersion("libdbus1.so", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 0); +} + +TEST_F(CrashReporter, ElfSo6) { + VersionComponents version; + bool rv = ElfFileSoVersion("libm.so.6", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 1); + ASSERT_EQ_UNSIGNED(version[0], 6); +} + +TEST_F(CrashReporter, ElfSoNormalShort) { + VersionComponents version; + bool rv = ElfFileSoVersion("libdbus1.so.1.2", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 2); + ASSERT_EQ_UNSIGNED(version[0], 1); + ASSERT_EQ_UNSIGNED(version[1], 2); +} + +TEST_F(CrashReporter, ElfSoNormalComplete) { + VersionComponents version; + bool rv = ElfFileSoVersion("libdbus1.so.1.2.3", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 3); + ASSERT_EQ_UNSIGNED(version[0], 1); + ASSERT_EQ_UNSIGNED(version[1], 2); + ASSERT_EQ_UNSIGNED(version[2], 3); +} + +TEST_F(CrashReporter, ElfSoNormalPrerelease) { + VersionComponents version; + bool rv = ElfFileSoVersion("libdbus1.so.1.2.3.98", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 4); + 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) { + VersionComponents version; + bool rv = ElfFileSoVersion("libdbus1.so.1.2.3.98.9.2.3", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 4); + 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) { + VersionComponents version; + bool rv = ElfFileSoVersion("libatk-1.0.so.0.25009.1", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 3); + ASSERT_EQ_UNSIGNED(version[0], 0); + ASSERT_EQ_UNSIGNED(version[1], 25009); + ASSERT_EQ_UNSIGNED(version[2], 1); +} + +TEST_F(CrashReporter, ElfSoCairo) { + VersionComponents version; + bool rv = ElfFileSoVersion("libcairo.so.2.11800.3", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 3); + ASSERT_EQ_UNSIGNED(version[0], 2); + ASSERT_EQ_UNSIGNED(version[1], 11800); + ASSERT_EQ_UNSIGNED(version[2], 3); +} + +TEST_F(CrashReporter, ElfSoMax) { + VersionComponents version; + bool rv = ElfFileSoVersion( + "libcairo.so.2147483647.2147483647.2147483647.2147483647", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 4); + 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) { + VersionComponents version; + bool rv = ElfFileSoVersion("libabsl_time_zone.so.20220623.0.0", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 3); + ASSERT_EQ_UNSIGNED(version[0], 20220623); + ASSERT_EQ_UNSIGNED(version[1], 0); + ASSERT_EQ_UNSIGNED(version[2], 0); +} + +TEST_F(CrashReporter, ElfSoChars) { + VersionComponents version; + bool rv = ElfFileSoVersion("libabsl_time_zone.so.1.2.3rc4", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 3); + ASSERT_EQ_UNSIGNED(version[0], 1); + ASSERT_EQ_UNSIGNED(version[1], 2); + ASSERT_EQ_UNSIGNED(version[2], 34); +} + +TEST_F(CrashReporter, ElfSoCharsMore) { + VersionComponents version; + bool rv = ElfFileSoVersion("libabsl_time_zone.so.1.2.3rc4.9", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 4); + 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) { + VersionComponents version; + bool rv = ElfFileSoVersion("libabsl_time_zone.so.final", &version); + ASSERT_TRUE(rv); + ASSERT_EQ_UNSIGNED(version.size(), 0); +} + +TEST_F(CrashReporter, ElfSoNullVersion) { + bool rv = ElfFileSoVersion("libabsl_time_zone.so.1", nullptr); + ASSERT_FALSE(rv); +} 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",