Add ModuleSnapshotLinux

Bug: crashpad:30
Change-Id: Ibf1f62b82a4926e1dfd9ad92231bfff44b811d78
Reviewed-on: https://chromium-review.googlesource.com/842187
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Joshua Peraza 2018-01-10 11:23:56 -08:00 committed by Commit Bot
parent 878af9cbbd
commit fb379a9242
11 changed files with 538 additions and 13 deletions

View File

@ -51,17 +51,24 @@ static_assert(std::is_standard_layout<CrashpadInfo>::value,
// This may result in a static module initializer in debug-mode builds, but
// because its POD, no code should need to run to initialize this under
// release-mode optimization.
// Platforms that use ELF objects need to locate this structure via the dynamic
// symbol table, so avoid name mangling.
#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_FUCHSIA)
extern "C" {
#endif
#if defined(OS_POSIX)
__attribute__((
#if defined(OS_MACOSX)
// Put the structure in a well-known section name where it can be easily
// found without having to consult the symbol table.
#if defined(OS_MACOSX)
section(SEG_DATA ",crashpad_info"),
#elif defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_FUCHSIA)
section("crashpad_info"),
#else
#error Port
// There's no need to expose this as a public symbol from the symbol table.
// All accesses from the outside can locate the well-known section name.
visibility("hidden"),
#endif
#if defined(ADDRESS_SANITIZER)
@ -74,11 +81,7 @@ __attribute__((
#endif // defined(ADDRESS_SANITIZER)
// The “used” attribute prevents the structure from being dead-stripped.
used,
// Theres no need to expose this as a public symbol from the symbol table.
// All accesses from the outside can locate the well-known section name.
visibility("hidden")))
used))
#elif defined(OS_WIN)
@ -93,6 +96,10 @@ __declspec(allocate("CPADinfo"))
CrashpadInfo g_crashpad_info;
#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_FUCHSIA)
} // extern "C"
#endif
// static
CrashpadInfo* CrashpadInfo::GetCrashpadInfo() {
return &g_crashpad_info;

View File

@ -118,6 +118,8 @@ static_library("snapshot") {
"linux/exception_snapshot_linux.h",
"linux/memory_snapshot_linux.cc",
"linux/memory_snapshot_linux.h",
"linux/module_snapshot_linux.cc",
"linux/module_snapshot_linux.h",
"linux/process_reader.cc",
"linux/process_reader.h",
"linux/process_snapshot_linux.cc",

View File

@ -202,6 +202,11 @@ class ElfImageReader {
NoteReader::NoteType type,
ssize_t max_note_size);
//! \brief Return a ProcessMemoryRange restricted to the range of this image.
//!
//! The caller does not take ownership of the returned object.
const ProcessMemoryRange* Memory() const;
private:
template <typename PhdrType>
class ProgramHeaderTableSpecific;

View File

@ -0,0 +1,188 @@
// Copyright 2018 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "snapshot/linux/module_snapshot_linux.h"
#include <algorithm>
#include "base/files/file_path.h"
#include "snapshot/crashpad_types/image_annotation_reader.h"
namespace crashpad {
namespace internal {
ModuleSnapshotLinux::ModuleSnapshotLinux()
: ModuleSnapshot(),
name_(),
elf_reader_(nullptr),
crashpad_info_(),
type_(kModuleTypeUnknown),
initialized_() {}
ModuleSnapshotLinux::~ModuleSnapshotLinux() = default;
bool ModuleSnapshotLinux::Initialize(
const ProcessReader::Module& process_reader_module) {
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
if (!process_reader_module.elf_reader) {
LOG(ERROR) << "no elf reader";
return false;
}
name_ = process_reader_module.name;
elf_reader_ = process_reader_module.elf_reader;
type_ = process_reader_module.type;
VMAddress info_address;
VMSize info_size;
if (elf_reader_->GetDynamicSymbol(
"g_crashpad_info", &info_address, &info_size)) {
ProcessMemoryRange range;
if (range.Initialize(*elf_reader_->Memory()) &&
range.RestrictRange(info_address, info_size)) {
auto info = std::make_unique<CrashpadInfoReader>();
if (info->Initialize(&range, info_address)) {
crashpad_info_ = std::move(info);
}
}
}
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
}
bool ModuleSnapshotLinux::GetCrashpadOptions(
CrashpadInfoClientOptions* options) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
if (!crashpad_info_) {
return false;
}
options->crashpad_handler_behavior =
crashpad_info_->CrashpadHandlerBehavior();
options->system_crash_reporter_forwarding =
crashpad_info_->SystemCrashReporterForwarding();
options->gather_indirectly_referenced_memory =
crashpad_info_->GatherIndirectlyReferencedMemory();
options->indirectly_referenced_memory_cap =
crashpad_info_->IndirectlyReferencedMemoryCap();
return true;
}
std::string ModuleSnapshotLinux::Name() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return name_;
}
uint64_t ModuleSnapshotLinux::Address() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return elf_reader_->Address();
}
uint64_t ModuleSnapshotLinux::Size() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return elf_reader_->Size();
}
time_t ModuleSnapshotLinux::Timestamp() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return 0;
}
void ModuleSnapshotLinux::FileVersion(uint16_t* version_0,
uint16_t* version_1,
uint16_t* version_2,
uint16_t* version_3) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
*version_0 = 0;
*version_1 = 0;
*version_2 = 0;
*version_3 = 0;
}
void ModuleSnapshotLinux::SourceVersion(uint16_t* version_0,
uint16_t* version_1,
uint16_t* version_2,
uint16_t* version_3) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
*version_0 = 0;
*version_1 = 0;
*version_2 = 0;
*version_3 = 0;
}
ModuleSnapshot::ModuleType ModuleSnapshotLinux::GetModuleType() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return type_;
}
void ModuleSnapshotLinux::UUIDAndAge(crashpad::UUID* uuid,
uint32_t* age) const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
*age = 0;
std::unique_ptr<ElfImageReader::NoteReader> notes =
elf_reader_->NotesWithNameAndType(ELF_NOTE_GNU, NT_GNU_BUILD_ID, 64);
std::string desc;
notes->NextNote(nullptr, nullptr, &desc);
desc.insert(desc.end(), 16 - std::min(desc.size(), size_t{16}), '\0');
uuid->InitializeFromBytes(reinterpret_cast<const uint8_t*>(&desc[0]));
}
std::string ModuleSnapshotLinux::DebugFileName() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return base::FilePath(Name()).BaseName().value();
}
std::vector<std::string> ModuleSnapshotLinux::AnnotationsVector() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return std::vector<std::string>();
}
std::map<std::string, std::string> ModuleSnapshotLinux::AnnotationsSimpleMap()
const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
std::map<std::string, std::string> annotations;
if (crashpad_info_ && crashpad_info_->SimpleAnnotations()) {
ImageAnnotationReader reader(elf_reader_->Memory());
reader.SimpleMap(crashpad_info_->SimpleAnnotations(), &annotations);
}
return annotations;
}
std::vector<AnnotationSnapshot> ModuleSnapshotLinux::AnnotationObjects() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
std::vector<AnnotationSnapshot> annotations;
if (crashpad_info_ && crashpad_info_->AnnotationsList()) {
ImageAnnotationReader reader(elf_reader_->Memory());
reader.AnnotationsList(crashpad_info_->AnnotationsList(), &annotations);
}
return annotations;
}
std::set<CheckedRange<uint64_t>> ModuleSnapshotLinux::ExtraMemoryRanges()
const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return std::set<CheckedRange<uint64_t>>();
}
std::vector<const UserMinidumpStream*>
ModuleSnapshotLinux::CustomMinidumpStreams() const {
return std::vector<const UserMinidumpStream*>();
}
} // namespace internal
} // namespace crashpad

View File

@ -0,0 +1,96 @@
// Copyright 2018 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_SNAPSHOT_LINUX_MODULE_SNAPSHOT_LINUX_H_
#define CRASHPAD_SNAPSHOT_LINUX_MODULE_SNAPSHOT_LINUX_H_
#include <stdint.h>
#include <sys/types.h>
#include <map>
#include <string>
#include <vector>
#include "base/macros.h"
#include "client/crashpad_info.h"
#include "snapshot/crashpad_info_client_options.h"
#include "snapshot/crashpad_types/crashpad_info_reader.h"
#include "snapshot/elf/elf_image_reader.h"
#include "snapshot/linux/process_reader.h"
#include "snapshot/module_snapshot.h"
#include "util/misc/initialization_state_dcheck.h"
namespace crashpad {
namespace internal {
//! \brief A ModuleSnapshot of a code module (binary image) loaded into a
//! running (or crashed) process on a Linux system.
class ModuleSnapshotLinux final : public ModuleSnapshot {
public:
ModuleSnapshotLinux();
~ModuleSnapshotLinux() override;
//! \brief Initializes the object.
//!
//! \param[in] process_reader_module The module within the ProcessReader for
//! which the snapshot should be created.
//!
//! \return `true` if the snapshot could be created, `false` otherwise with
//! an appropriate message logged.
bool Initialize(const ProcessReader::Module& process_reader_module);
//! \brief Returns options from the modules CrashpadInfo structure.
//!
//! \param[out] options Options set in the modules CrashpadInfo structure.
//! \return `true` if there were options returned. Otherwise `false`.
bool GetCrashpadOptions(CrashpadInfoClientOptions* options);
// ModuleSnapshot:
std::string Name() const override;
uint64_t Address() const override;
uint64_t Size() const override;
time_t Timestamp() const override;
void FileVersion(uint16_t* version_0,
uint16_t* version_1,
uint16_t* version_2,
uint16_t* version_3) const override;
void SourceVersion(uint16_t* version_0,
uint16_t* version_1,
uint16_t* version_2,
uint16_t* version_3) const override;
ModuleType GetModuleType() const override;
void UUIDAndAge(crashpad::UUID* uuid, uint32_t* age) const override;
std::string DebugFileName() const override;
std::vector<std::string> AnnotationsVector() const override;
std::map<std::string, std::string> AnnotationsSimpleMap() const override;
std::vector<AnnotationSnapshot> AnnotationObjects() const override;
std::set<CheckedRange<uint64_t>> ExtraMemoryRanges() const override;
std::vector<const UserMinidumpStream*> CustomMinidumpStreams() const override;
private:
std::string name_;
ElfImageReader* elf_reader_;
std::unique_ptr<CrashpadInfoReader> crashpad_info_;
ModuleType type_;
InitializationStateDcheck initialized_;
DISALLOW_COPY_AND_ASSIGN(ModuleSnapshotLinux);
};
} // namespace internal
} // namespace crashpad
#endif // CRASHPAD_SNAPSHOT_LINUX_MODULE_SNAPSHOT_LINUX_H_

View File

@ -14,6 +14,7 @@
#include "snapshot/linux/process_reader.h"
#include <elf.h>
#include <errno.h>
#include <sched.h>
#include <stdio.h>
@ -26,7 +27,9 @@
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "snapshot/linux/debug_rendezvous.h"
#include "util/file/directory_reader.h"
#include "util/linux/auxiliary_vector.h"
#include "util/linux/proc_stat_reader.h"
#include "util/misc/as_underlying_type.h"
@ -166,14 +169,22 @@ void ProcessReader::Thread::InitializeStack(ProcessReader* reader) {
}
}
ProcessReader::Module::Module()
: name(), elf_reader(nullptr), type(ModuleSnapshot::kModuleTypeUnknown) {}
ProcessReader::Module::~Module() = default;
ProcessReader::ProcessReader()
: connection_(),
process_info_(),
memory_map_(),
threads_(),
modules_(),
elf_readers_(),
process_memory_(),
is_64_bit_(false),
initialized_threads_(false),
initialized_modules_(false),
initialized_() {}
ProcessReader::~ProcessReader() {}
@ -250,6 +261,14 @@ const std::vector<ProcessReader::Thread>& ProcessReader::Threads() {
return threads_;
}
const std::vector<ProcessReader::Module>& ProcessReader::Modules() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
if (!initialized_modules_) {
InitializeModules();
}
return modules_;
}
void ProcessReader::InitializeThreads() {
DCHECK(threads_.empty());
@ -307,4 +326,78 @@ void ProcessReader::InitializeThreads() {
DCHECK(main_thread_found);
}
void ProcessReader::InitializeModules() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
AuxiliaryVector aux;
if (!aux.Initialize(ProcessID(), is_64_bit_)) {
return;
}
LinuxVMAddress phdrs;
if (!aux.GetValue(AT_PHDR, &phdrs)) {
return;
}
const MemoryMap::Mapping* exe_mapping;
if (!(exe_mapping = GetMemoryMap()->FindMapping(phdrs)) &&
!(exe_mapping = GetMemoryMap()->FindFileMmapStart(*exe_mapping))) {
return;
}
ProcessMemoryRange range;
if (!range.Initialize(Memory(), is_64_bit_)) {
return;
}
auto exe_reader = std::make_unique<ElfImageReader>();
if (!exe_reader->Initialize(range, exe_mapping->range.Base())) {
return;
}
LinuxVMAddress debug_address;
if (!exe_reader->GetDebugAddress(&debug_address)) {
return;
}
DebugRendezvous debug;
if (!debug.Initialize(range, debug_address)) {
return;
}
Module exe = {};
exe.name = !debug.Executable()->name.empty() ? debug.Executable()->name
: exe_mapping->name;
exe.elf_reader = exe_reader.get();
exe.type = ModuleSnapshot::ModuleType::kModuleTypeExecutable;
modules_.push_back(exe);
elf_readers_.push_back(std::move(exe_reader));
LinuxVMAddress loader_base = 0;
aux.GetValue(AT_BASE, &loader_base);
for (const DebugRendezvous::LinkEntry& entry : debug.Modules()) {
const MemoryMap::Mapping* mapping;
if (!(mapping = memory_map_.FindMapping(entry.dynamic_array)) ||
!(mapping = memory_map_.FindFileMmapStart(*mapping))) {
continue;
}
auto elf_reader = std::make_unique<ElfImageReader>();
if (!elf_reader->Initialize(range, mapping->range.Base())) {
continue;
}
Module module = {};
module.name = !entry.name.empty() ? entry.name : mapping->name;
module.elf_reader = elf_reader.get();
module.type = loader_base && elf_reader->Address() == loader_base
? ModuleSnapshot::kModuleTypeDynamicLoader
: ModuleSnapshot::kModuleTypeSharedLibrary;
modules_.push_back(module);
elf_readers_.push_back(std::move(elf_reader));
}
}
} // namespace crashpad

View File

@ -18,9 +18,13 @@
#include <sys/time.h>
#include <sys/types.h>
#include <memory>
#include <string>
#include <vector>
#include "base/macros.h"
#include "snapshot/elf/elf_image_reader.h"
#include "snapshot/module_snapshot.h"
#include "util/linux/address_types.h"
#include "util/linux/memory_map.h"
#include "util/linux/ptrace_connection.h"
@ -56,6 +60,27 @@ class ProcessReader {
void InitializeStack(ProcessReader* reader);
};
//! \brief Contains information about a module loaded into a process.
struct Module {
Module();
~Module();
//! \brief The pathname used to load the module from disk.
std::string name;
//! \brief An image reader for the module.
//!
//! The lifetime of this ElfImageReader is scoped to the lifetime of the
//! ProcessReader that created it.
//!
//! This field may be `nullptr` if a reader could not be created for the
//! module.
ElfImageReader* elf_reader;
//! \brief The module's type.
ModuleSnapshot::ModuleType type;
};
ProcessReader();
~ProcessReader();
@ -107,16 +132,24 @@ class ProcessReader {
//! index `0`.
const std::vector<Thread>& Threads();
//! \return The modules loaded in the process. The first element (at index
//! `0`) corresponds to the main executable.
const std::vector<Module>& Modules();
private:
void InitializeThreads();
void InitializeModules();
PtraceConnection* connection_; // weak
ProcessInfo process_info_;
MemoryMap memory_map_;
std::vector<Thread> threads_;
std::vector<Module> modules_;
std::vector<std::unique_ptr<ElfImageReader>> elf_readers_;
ProcessMemoryLinux process_memory_;
bool is_64_bit_;
bool initialized_threads_;
bool initialized_modules_;
InitializationStateDcheck initialized_;
DISALLOW_COPY_AND_ASSIGN(ProcessReader);

View File

@ -15,6 +15,7 @@
#include "snapshot/linux/process_reader.h"
#include <errno.h>
#include <link.h>
#include <pthread.h>
#include <sched.h>
#include <stdlib.h>
@ -440,6 +441,89 @@ TEST(ProcessReader, ChildWithSplitStack) {
test.Run();
}
int ExpectFindModule(dl_phdr_info* info, size_t size, void* data) {
SCOPED_TRACE(
base::StringPrintf("module %s at 0x%" PRIx64 " phdrs 0x%" PRIx64,
info->dlpi_name,
LinuxVMAddress{info->dlpi_addr},
FromPointerCast<LinuxVMAddress>(info->dlpi_phdr)));
auto modules =
reinterpret_cast<const std::vector<ProcessReader::Module>*>(data);
auto phdr_addr = FromPointerCast<LinuxVMAddress>(info->dlpi_phdr);
#if defined(OS_ANDROID)
// Bionic includes a null entry.
if (!phdr_addr) {
EXPECT_EQ(info->dlpi_name, nullptr);
EXPECT_EQ(info->dlpi_addr, 0u);
EXPECT_EQ(info->dlpi_phnum, 0u);
return 0;
}
#endif
// TODO(jperaza): This can use a range map when one is available.
bool found = false;
for (const auto& module : *modules) {
if (module.elf_reader && phdr_addr >= module.elf_reader->Address() &&
phdr_addr < module.elf_reader->Address() + module.elf_reader->Size()) {
found = true;
break;
}
}
EXPECT_TRUE(found);
return 0;
}
void ExpectModulesFromSelf(const std::vector<ProcessReader::Module>& modules) {
for (const auto& module : modules) {
EXPECT_FALSE(module.name.empty());
EXPECT_NE(module.type, ModuleSnapshot::kModuleTypeUnknown);
}
EXPECT_EQ(dl_iterate_phdr(
ExpectFindModule,
reinterpret_cast<void*>(
const_cast<std::vector<ProcessReader::Module>*>(&modules))),
0);
}
TEST(ProcessReader, SelfModules) {
FakePtraceConnection connection;
connection.Initialize(getpid());
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(&connection));
ExpectModulesFromSelf(process_reader.Modules());
}
class ChildModuleTest : public Multiprocess {
public:
ChildModuleTest() : Multiprocess() {}
~ChildModuleTest() = default;
private:
void MultiprocessParent() override {
DirectPtraceConnection connection;
ASSERT_TRUE(connection.Initialize(ChildPID()));
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(&connection));
ExpectModulesFromSelf(process_reader.Modules());
}
void MultiprocessChild() override { CheckedReadFileAtEOF(ReadPipeHandle()); }
DISALLOW_COPY_AND_ASSIGN(ChildModuleTest);
};
TEST(ProcessReader, ChildModules) {
ChildModuleTest test;
test.Run();
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -49,6 +49,7 @@ bool ProcessSnapshotLinux::Initialize(PtraceConnection* connection) {
system_.Initialize(&process_reader_, &snapshot_time_);
InitializeThreads();
InitializeModules();
INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
@ -136,9 +137,11 @@ std::vector<const ThreadSnapshot*> ProcessSnapshotLinux::Threads() const {
std::vector<const ModuleSnapshot*> ProcessSnapshotLinux::Modules() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
// TODO(jperaza): do this.
LOG(ERROR) << "Not implemented";
return std::vector<const ModuleSnapshot*>();
std::vector<const ModuleSnapshot*> modules;
for (const auto& module : modules_) {
modules.push_back(module.get());
}
return modules;
}
std::vector<UnloadedModuleSnapshot> ProcessSnapshotLinux::UnloadedModules()
@ -182,4 +185,13 @@ void ProcessSnapshotLinux::InitializeThreads() {
}
}
void ProcessSnapshotLinux::InitializeModules() {
for (const ProcessReader::Module& reader_module : process_reader_.Modules()) {
auto module = std::make_unique<internal::ModuleSnapshotLinux>();
if (module->Initialize(reader_module)) {
modules_.push_back(std::move(module));
}
}
}
} // namespace crashpad

View File

@ -25,6 +25,7 @@
#include "base/macros.h"
#include "snapshot/linux/exception_snapshot_linux.h"
#include "snapshot/linux/module_snapshot_linux.h"
#include "snapshot/linux/process_reader.h"
#include "snapshot/linux/system_snapshot_linux.h"
#include "snapshot/linux/thread_snapshot_linux.h"
@ -108,12 +109,14 @@ class ProcessSnapshotLinux final : public ProcessSnapshot {
private:
void InitializeThreads();
void InitializeModules();
std::map<std::string, std::string> annotations_simple_map_;
timeval snapshot_time_;
UUID report_id_;
UUID client_id_;
std::vector<std::unique_ptr<internal::ThreadSnapshotLinux>> threads_;
std::vector<std::unique_ptr<internal::ModuleSnapshotLinux>> modules_;
std::unique_ptr<internal::ExceptionSnapshotLinux> exception_;
internal::SystemSnapshotLinux system_;
ProcessReader process_reader_;

View File

@ -60,6 +60,8 @@
'linux/exception_snapshot_linux.h',
'linux/memory_snapshot_linux.cc',
'linux/memory_snapshot_linux.h',
'linux/module_snapshot_linux.cc',
'linux/module_snapshot_linux.h',
'linux/process_reader.cc',
'linux/process_reader.h',
'linux/process_snapshot_linux.cc',