mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2025-07-21 15:09:10 +00:00
Add an optional root prefix to Linux dumpers
The Linux dumpers use absolute paths for shared libraries referenced by dumps, so they fail to locate them if the crash originated in a chroot. This CL enables callers to specify a root prefix, which is prepended to mapping paths before opening them. BUG=chromium:591792 TEST=make check Review URL: https://codereview.chromium.org/1761023002/
This commit is contained in:
parent
f3d28e9c4a
commit
4d69050717
|
@ -350,7 +350,7 @@ class MicrodumpWriter {
|
||||||
|
|
||||||
char file_name[NAME_MAX];
|
char file_name[NAME_MAX];
|
||||||
char file_path[NAME_MAX];
|
char file_path[NAME_MAX];
|
||||||
LinuxDumper::GetMappingEffectiveNameAndPath(
|
dumper_->GetMappingEffectiveNameAndPath(
|
||||||
mapping, file_path, sizeof(file_path), file_name, sizeof(file_name));
|
mapping, file_path, sizeof(file_path), file_name, sizeof(file_name));
|
||||||
|
|
||||||
LogAppend("M ");
|
LogAppend("M ");
|
||||||
|
|
|
@ -49,8 +49,9 @@ namespace google_breakpad {
|
||||||
|
|
||||||
LinuxCoreDumper::LinuxCoreDumper(pid_t pid,
|
LinuxCoreDumper::LinuxCoreDumper(pid_t pid,
|
||||||
const char* core_path,
|
const char* core_path,
|
||||||
const char* procfs_path)
|
const char* procfs_path,
|
||||||
: LinuxDumper(pid),
|
const char* root_prefix)
|
||||||
|
: LinuxDumper(pid, root_prefix),
|
||||||
core_path_(core_path),
|
core_path_(core_path),
|
||||||
procfs_path_(procfs_path),
|
procfs_path_(procfs_path),
|
||||||
thread_infos_(&allocator_, 8) {
|
thread_infos_(&allocator_, 8) {
|
||||||
|
|
|
@ -47,7 +47,9 @@ class LinuxCoreDumper : public LinuxDumper {
|
||||||
// its proc files at |procfs_path|. If |procfs_path| is a copy of
|
// its proc files at |procfs_path|. If |procfs_path| is a copy of
|
||||||
// /proc/<pid>, it should contain the following files:
|
// /proc/<pid>, it should contain the following files:
|
||||||
// auxv, cmdline, environ, exe, maps, status
|
// auxv, cmdline, environ, exe, maps, status
|
||||||
LinuxCoreDumper(pid_t pid, const char* core_path, const char* procfs_path);
|
// See LinuxDumper for the purpose of |root_prefix|.
|
||||||
|
LinuxCoreDumper(pid_t pid, const char* core_path, const char* procfs_path,
|
||||||
|
const char* root_prefix = "");
|
||||||
|
|
||||||
// Implements LinuxDumper::BuildProcPath().
|
// Implements LinuxDumper::BuildProcPath().
|
||||||
// Builds a proc path for a certain pid for a node (/proc/<pid>/<node>).
|
// Builds a proc path for a certain pid for a node (/proc/<pid>/<node>).
|
||||||
|
|
|
@ -39,6 +39,16 @@
|
||||||
|
|
||||||
using namespace google_breakpad;
|
using namespace google_breakpad;
|
||||||
|
|
||||||
|
TEST(LinuxCoreDumperTest, GetMappingAbsolutePath) {
|
||||||
|
const LinuxCoreDumper dumper(getpid(), "core", "/tmp", "/mnt/root");
|
||||||
|
const MappingInfo mapping = { 0, 0, 0, false, "/usr/lib/libc.so" };
|
||||||
|
|
||||||
|
char path[PATH_MAX];
|
||||||
|
dumper.GetMappingAbsolutePath(mapping, path);
|
||||||
|
|
||||||
|
EXPECT_STREQ("/mnt/root/usr/lib/libc.so", path);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(LinuxCoreDumperTest, BuildProcPath) {
|
TEST(LinuxCoreDumperTest, BuildProcPath) {
|
||||||
const pid_t pid = getpid();
|
const pid_t pid = getpid();
|
||||||
const char procfs_path[] = "/procfs_copy";
|
const char procfs_path[] = "/procfs_copy";
|
||||||
|
|
|
@ -88,14 +88,16 @@ namespace google_breakpad {
|
||||||
// All interesting auvx entry types are below AT_SYSINFO_EHDR
|
// All interesting auvx entry types are below AT_SYSINFO_EHDR
|
||||||
#define AT_MAX AT_SYSINFO_EHDR
|
#define AT_MAX AT_SYSINFO_EHDR
|
||||||
|
|
||||||
LinuxDumper::LinuxDumper(pid_t pid)
|
LinuxDumper::LinuxDumper(pid_t pid, const char* root_prefix)
|
||||||
: pid_(pid),
|
: pid_(pid),
|
||||||
|
root_prefix_(root_prefix),
|
||||||
crash_address_(0),
|
crash_address_(0),
|
||||||
crash_signal_(0),
|
crash_signal_(0),
|
||||||
crash_thread_(pid),
|
crash_thread_(pid),
|
||||||
threads_(&allocator_, 8),
|
threads_(&allocator_, 8),
|
||||||
mappings_(&allocator_),
|
mappings_(&allocator_),
|
||||||
auxv_(&allocator_, AT_MAX + 1) {
|
auxv_(&allocator_, AT_MAX + 1) {
|
||||||
|
assert(root_prefix_ && my_strlen(root_prefix_) < PATH_MAX);
|
||||||
// The passed-in size to the constructor (above) is only a hint.
|
// The passed-in size to the constructor (above) is only a hint.
|
||||||
// Must call .resize() to do actual initialization of the elements.
|
// Must call .resize() to do actual initialization of the elements.
|
||||||
auxv_.resize(AT_MAX + 1);
|
auxv_.resize(AT_MAX + 1);
|
||||||
|
@ -139,14 +141,9 @@ LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
|
||||||
return FileID::ElfFileIdentifierFromMappedFile(linux_gate, identifier);
|
return FileID::ElfFileIdentifierFromMappedFile(linux_gate, identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
char filename[NAME_MAX];
|
char filename[PATH_MAX];
|
||||||
size_t filename_len = my_strlen(mapping.name);
|
if (!GetMappingAbsolutePath(mapping, filename))
|
||||||
if (filename_len >= NAME_MAX) {
|
|
||||||
assert(false);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
my_memcpy(filename, mapping.name, filename_len);
|
|
||||||
filename[filename_len] = '\0';
|
|
||||||
bool filename_modified = HandleDeletedFileInMapping(filename);
|
bool filename_modified = HandleDeletedFileInMapping(filename);
|
||||||
|
|
||||||
MemoryMappedFile mapped_file(filename, mapping.offset);
|
MemoryMappedFile mapped_file(filename, mapping.offset);
|
||||||
|
@ -156,13 +153,19 @@ LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
|
||||||
bool success =
|
bool success =
|
||||||
FileID::ElfFileIdentifierFromMappedFile(mapped_file.data(), identifier);
|
FileID::ElfFileIdentifierFromMappedFile(mapped_file.data(), identifier);
|
||||||
if (success && member && filename_modified) {
|
if (success && member && filename_modified) {
|
||||||
mappings_[mapping_id]->name[filename_len -
|
mappings_[mapping_id]->name[my_strlen(mapping.name) -
|
||||||
sizeof(kDeletedSuffix) + 1] = '\0';
|
sizeof(kDeletedSuffix) + 1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LinuxDumper::GetMappingAbsolutePath(const MappingInfo& mapping,
|
||||||
|
char path[PATH_MAX]) const {
|
||||||
|
return my_strlcpy(path, root_prefix_, PATH_MAX) < PATH_MAX &&
|
||||||
|
my_strlcat(path, mapping.name, PATH_MAX) < PATH_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
bool ElfFileSoNameFromMappedFile(
|
bool ElfFileSoNameFromMappedFile(
|
||||||
const void* elf_base, char* soname, size_t soname_size) {
|
const void* elf_base, char* soname, size_t soname_size) {
|
||||||
|
@ -212,23 +215,16 @@ bool ElfFileSoNameFromMappedFile(
|
||||||
// for |mapping|. If the SONAME is found copy it into the passed buffer
|
// for |mapping|. If the SONAME is found copy it into the passed buffer
|
||||||
// |soname| and return true. The size of the buffer is |soname_size|.
|
// |soname| and return true. The size of the buffer is |soname_size|.
|
||||||
// The SONAME will be truncated if it is too long to fit in the buffer.
|
// The SONAME will be truncated if it is too long to fit in the buffer.
|
||||||
bool ElfFileSoName(
|
bool ElfFileSoName(const LinuxDumper& dumper,
|
||||||
const MappingInfo& mapping, char* soname, size_t soname_size) {
|
const MappingInfo& mapping, char* soname, size_t soname_size) {
|
||||||
if (IsMappedFileOpenUnsafe(mapping)) {
|
if (IsMappedFileOpenUnsafe(mapping)) {
|
||||||
// Not safe
|
// Not safe
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
char filename[NAME_MAX];
|
char filename[PATH_MAX];
|
||||||
size_t filename_len = my_strlen(mapping.name);
|
if (!dumper.GetMappingAbsolutePath(mapping, filename))
|
||||||
if (filename_len >= NAME_MAX) {
|
|
||||||
assert(false);
|
|
||||||
// name too long
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
my_memcpy(filename, mapping.name, filename_len);
|
|
||||||
filename[filename_len] = '\0';
|
|
||||||
|
|
||||||
MemoryMappedFile mapped_file(filename, mapping.offset);
|
MemoryMappedFile mapped_file(filename, mapping.offset);
|
||||||
if (!mapped_file.data() || mapped_file.size() < SELFMAG) {
|
if (!mapped_file.data() || mapped_file.size() < SELFMAG) {
|
||||||
|
@ -242,7 +238,6 @@ bool ElfFileSoName(
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
// static
|
|
||||||
void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
|
void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
|
||||||
char* file_path,
|
char* file_path,
|
||||||
size_t file_path_size,
|
size_t file_path_size,
|
||||||
|
@ -255,8 +250,10 @@ void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
|
||||||
// apk on Android). We try to find the name of the shared object (SONAME) by
|
// apk on Android). We try to find the name of the shared object (SONAME) by
|
||||||
// looking in the file for ELF sections.
|
// looking in the file for ELF sections.
|
||||||
bool mapped_from_archive = false;
|
bool mapped_from_archive = false;
|
||||||
if (mapping.exec && mapping.offset != 0)
|
if (mapping.exec && mapping.offset != 0) {
|
||||||
mapped_from_archive = ElfFileSoName(mapping, file_name, file_name_size);
|
mapped_from_archive =
|
||||||
|
ElfFileSoName(*this, mapping, file_name, file_name_size);
|
||||||
|
}
|
||||||
|
|
||||||
if (mapped_from_archive) {
|
if (mapped_from_archive) {
|
||||||
// Some tools (e.g., stackwalk) extract the basename from the pathname. In
|
// Some tools (e.g., stackwalk) extract the basename from the pathname. In
|
||||||
|
@ -580,10 +577,13 @@ bool LinuxDumper::HandleDeletedFileInMapping(char* path) const {
|
||||||
|
|
||||||
// Check |path| against the /proc/pid/exe 'symlink'.
|
// Check |path| against the /proc/pid/exe 'symlink'.
|
||||||
char exe_link[NAME_MAX];
|
char exe_link[NAME_MAX];
|
||||||
char new_path[NAME_MAX];
|
|
||||||
if (!BuildProcPath(exe_link, pid_, "exe"))
|
if (!BuildProcPath(exe_link, pid_, "exe"))
|
||||||
return false;
|
return false;
|
||||||
if (!SafeReadLink(exe_link, new_path))
|
MappingInfo new_mapping = {0};
|
||||||
|
if (!SafeReadLink(exe_link, new_mapping.name))
|
||||||
|
return false;
|
||||||
|
char new_path[PATH_MAX];
|
||||||
|
if (!GetMappingAbsolutePath(new_mapping, new_path))
|
||||||
return false;
|
return false;
|
||||||
if (my_strcmp(path, new_path) != 0)
|
if (my_strcmp(path, new_path) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -72,7 +72,9 @@ const char kLinuxGateLibraryName[] = "linux-gate.so";
|
||||||
|
|
||||||
class LinuxDumper {
|
class LinuxDumper {
|
||||||
public:
|
public:
|
||||||
explicit LinuxDumper(pid_t pid);
|
// The |root_prefix| is prepended to mapping paths before opening them, which
|
||||||
|
// is useful if the crash originates from a chroot.
|
||||||
|
explicit LinuxDumper(pid_t pid, const char* root_prefix = "");
|
||||||
|
|
||||||
virtual ~LinuxDumper();
|
virtual ~LinuxDumper();
|
||||||
|
|
||||||
|
@ -140,12 +142,17 @@ class LinuxDumper {
|
||||||
pid_t crash_thread() const { return crash_thread_; }
|
pid_t crash_thread() const { return crash_thread_; }
|
||||||
void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; }
|
void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; }
|
||||||
|
|
||||||
|
// Concatenates the |root_prefix_| and |mapping| path. Writes into |path| and
|
||||||
|
// returns true unless the string is too long.
|
||||||
|
bool GetMappingAbsolutePath(const MappingInfo& mapping,
|
||||||
|
char path[PATH_MAX]) const;
|
||||||
|
|
||||||
// Extracts the effective path and file name of from |mapping|. In most cases
|
// Extracts the effective path and file name of from |mapping|. In most cases
|
||||||
// the effective name/path are just the mapping's path and basename. In some
|
// the effective name/path are just the mapping's path and basename. In some
|
||||||
// other cases, however, a library can be mapped from an archive (e.g., when
|
// 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
|
// loading .so libs from an apk on Android) and this method is able to
|
||||||
// reconstruct the original file name.
|
// reconstruct the original file name.
|
||||||
static void GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
|
void GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
|
||||||
char* file_path,
|
char* file_path,
|
||||||
size_t file_path_size,
|
size_t file_path_size,
|
||||||
char* file_name,
|
char* file_name,
|
||||||
|
@ -172,6 +179,9 @@ class LinuxDumper {
|
||||||
// ID of the crashed process.
|
// ID of the crashed process.
|
||||||
const pid_t pid_;
|
const pid_t pid_;
|
||||||
|
|
||||||
|
// Path of the root directory to which mapping paths are relative.
|
||||||
|
const char* const root_prefix_;
|
||||||
|
|
||||||
// Virtual address at which the process crashed.
|
// Virtual address at which the process crashed.
|
||||||
uintptr_t crash_address_;
|
uintptr_t crash_address_;
|
||||||
|
|
||||||
|
|
|
@ -567,7 +567,7 @@ class MinidumpWriter {
|
||||||
|
|
||||||
char file_name[NAME_MAX];
|
char file_name[NAME_MAX];
|
||||||
char file_path[NAME_MAX];
|
char file_path[NAME_MAX];
|
||||||
LinuxDumper::GetMappingEffectiveNameAndPath(
|
dumper_->GetMappingEffectiveNameAndPath(
|
||||||
mapping, file_path, sizeof(file_path), file_name, sizeof(file_name));
|
mapping, file_path, sizeof(file_path), file_name, sizeof(file_name));
|
||||||
|
|
||||||
const size_t file_name_len = my_strlen(file_name);
|
const size_t file_name_len = my_strlen(file_name);
|
||||||
|
|
Loading…
Reference in a new issue