Bug 1847098 - Report library version from filename at crash r=gsvelto

Differential Revision: https://phabricator.services.mozilla.com/D202249
This commit is contained in:
Alexandre Lissy 2024-03-04 14:00:26 +00:00
parent 2f2061d413
commit 4b63bf2834
13 changed files with 317 additions and 8 deletions

View file

@ -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<uintptr_t>(mapping.start_addr));

View file

@ -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() {

View file

@ -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();

View file

@ -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))

View file

@ -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;
}

View file

@ -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 <stdio.h>
#include <stdint.h>
#include <string>
#include <vector>
bool ElfFileSoVersion(const char* mapping_name, uint32_t* version);
#endif /* toolkit_breakpad_linux_utils_h__ */

View file

@ -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"]

View file

@ -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);
}

View file

@ -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"

View file

@ -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 += [

View file

@ -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
);
});

View file

@ -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"]

View file

@ -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",