mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
linux: Add DebugRendezvous to read dynamic linker data structures
Dynamic linkers use `struct r_debug` and `struct link_map` (defined in `<link.h>`) to communicate lists of loaded modules to debuggers. Bug: crashpad:30 Change-Id: Id903a1c199288dd85c34e38710cdb4c6b5fedb5b Reviewed-on: https://chromium-review.googlesource.com/534853 Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
parent
a79791969d
commit
041a50d75c
143
snapshot/linux/debug_rendezvous.cc
Normal file
143
snapshot/linux/debug_rendezvous.cc
Normal file
@ -0,0 +1,143 @@
|
||||
// Copyright 2017 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/debug_rendezvous.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Traits32 {
|
||||
using Integer = int32_t;
|
||||
using Address = uint32_t;
|
||||
};
|
||||
|
||||
struct Traits64 {
|
||||
using Integer = int64_t;
|
||||
using Address = uint64_t;
|
||||
};
|
||||
|
||||
template <typename Traits>
|
||||
struct DebugRendezvousSpecific {
|
||||
typename Traits::Integer r_version;
|
||||
typename Traits::Address r_map;
|
||||
typename Traits::Address r_brk;
|
||||
typename Traits::Integer r_state;
|
||||
typename Traits::Address r_ldbase;
|
||||
};
|
||||
|
||||
template <typename Traits>
|
||||
struct LinkEntrySpecific {
|
||||
typename Traits::Address l_addr;
|
||||
typename Traits::Address l_name;
|
||||
typename Traits::Address l_ld;
|
||||
typename Traits::Address l_next;
|
||||
typename Traits::Address l_prev;
|
||||
};
|
||||
|
||||
template <typename Traits>
|
||||
bool ReadLinkEntry(const ProcessMemoryRange& memory,
|
||||
LinuxVMAddress* address,
|
||||
DebugRendezvous::LinkEntry* entry_out) {
|
||||
LinkEntrySpecific<Traits> entry;
|
||||
if (!memory.Read(*address, sizeof(entry), &entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
if (!memory.ReadCStringSizeLimited(entry.l_name, 4096, &name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entry_out->load_bias = entry.l_addr;
|
||||
entry_out->dynamic_array = entry.l_ld;
|
||||
entry_out->name.swap(name);
|
||||
|
||||
*address = entry.l_next;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DebugRendezvous::LinkEntry::LinkEntry()
|
||||
: name(), load_bias(0), dynamic_array(0) {}
|
||||
|
||||
DebugRendezvous::DebugRendezvous()
|
||||
: modules_(), executable_(), initialized_() {}
|
||||
|
||||
DebugRendezvous::~DebugRendezvous() {}
|
||||
|
||||
bool DebugRendezvous::Initialize(const ProcessMemoryRange& memory,
|
||||
LinuxVMAddress address) {
|
||||
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||||
if (!(memory.Is64Bit() ? InitializeSpecific<Traits64>(memory, address)
|
||||
: InitializeSpecific<Traits32>(memory, address))) {
|
||||
return false;
|
||||
}
|
||||
INITIALIZATION_STATE_SET_VALID(initialized_);
|
||||
return true;
|
||||
}
|
||||
|
||||
const DebugRendezvous::LinkEntry* DebugRendezvous::Executable() const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return &executable_;
|
||||
}
|
||||
|
||||
const std::vector<DebugRendezvous::LinkEntry>& DebugRendezvous::Modules()
|
||||
const {
|
||||
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
||||
return modules_;
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
bool DebugRendezvous::InitializeSpecific(const ProcessMemoryRange& memory,
|
||||
LinuxVMAddress address) {
|
||||
DebugRendezvousSpecific<Traits> debug;
|
||||
if (!memory.Read(address, sizeof(debug), &debug)) {
|
||||
return false;
|
||||
}
|
||||
if (debug.r_version != 1) {
|
||||
LOG(ERROR) << "unexpected version " << debug.r_version;
|
||||
return false;
|
||||
}
|
||||
|
||||
LinuxVMAddress link_entry_address = debug.r_map;
|
||||
if (!ReadLinkEntry<Traits>(memory, &link_entry_address, &executable_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::set<LinuxVMAddress> visited;
|
||||
while (link_entry_address) {
|
||||
if (!visited.insert(link_entry_address).second) {
|
||||
LOG(ERROR) << "cycle at address 0x" << std::hex << link_entry_address;
|
||||
return false;
|
||||
}
|
||||
|
||||
LinkEntry entry;
|
||||
if (!ReadLinkEntry<Traits>(memory, &link_entry_address, &entry)) {
|
||||
return false;
|
||||
}
|
||||
modules_.push_back(entry);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
88
snapshot/linux/debug_rendezvous.h
Normal file
88
snapshot/linux/debug_rendezvous.h
Normal file
@ -0,0 +1,88 @@
|
||||
// Copyright 2017 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_DEBUG_RENDEZVOUS_H_
|
||||
#define CRASHPAD_SNAPSHOT_LINUX_DEBUG_RENDEZVOUS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "util/linux/address_types.h"
|
||||
#include "util/linux/process_memory_range.h"
|
||||
#include "util/misc/initialization_state_dcheck.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Reads an `r_debug` struct defined in `<link.h>` via
|
||||
//! ProcessMemoryRange.
|
||||
class DebugRendezvous {
|
||||
public:
|
||||
//! \brief An entry in the dynamic linker's list of loaded objects.
|
||||
//!
|
||||
//! All of these values should be checked before use. Whether and how they are
|
||||
//! populated may vary by dynamic linker.
|
||||
struct LinkEntry {
|
||||
LinkEntry();
|
||||
|
||||
//! \brief A filename identifying the object.
|
||||
std::string name;
|
||||
|
||||
//! \brief The difference between the preferred load address in the ELF file
|
||||
//! and the actual loaded address in memory.
|
||||
LinuxVMOffset load_bias;
|
||||
|
||||
//! \brief The address of the dynamic array for this object.
|
||||
LinuxVMAddress dynamic_array;
|
||||
};
|
||||
|
||||
DebugRendezvous();
|
||||
~DebugRendezvous();
|
||||
|
||||
//! \brief Initializes this object by reading an `r_debug` struct from a
|
||||
//! target process.
|
||||
//!
|
||||
//! This method must be called successfully prior to calling any other method
|
||||
//! in this class.
|
||||
//!
|
||||
//! \param[in] memory A memory reader for the remote process.
|
||||
//! \param[in] address The address of an `r_debug` struct in the remote
|
||||
//! process.
|
||||
//! \return `true` on success. `false` on failure with a message logged.
|
||||
bool Initialize(const ProcessMemoryRange& memory, LinuxVMAddress address);
|
||||
|
||||
//! \brief Returns the LinkEntry for the main executable.
|
||||
const LinkEntry* Executable() const;
|
||||
|
||||
//! \brief Returns a vector of modules found in the link map.
|
||||
//!
|
||||
//! This list excludes the entry for the executable and may include entries
|
||||
//! for the VDSO and loader.
|
||||
const std::vector<LinkEntry>& Modules() const;
|
||||
|
||||
private:
|
||||
template <typename Traits>
|
||||
bool InitializeSpecific(const ProcessMemoryRange& memory,
|
||||
LinuxVMAddress address);
|
||||
|
||||
std::vector<LinkEntry> modules_;
|
||||
LinkEntry executable_;
|
||||
InitializationStateDcheck initialized_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DebugRendezvous);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_SNAPSHOT_LINUX_DEBUG_RENDEZVOUS_H_
|
220
snapshot/linux/debug_rendezvous_test.cc
Normal file
220
snapshot/linux/debug_rendezvous_test.cc
Normal file
@ -0,0 +1,220 @@
|
||||
// Copyright 2017 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/debug_rendezvous.h"
|
||||
|
||||
#include <linux/auxvec.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "base/format_macros.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "build/build_config.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "snapshot/linux/elf_image_reader.h"
|
||||
#include "test/multiprocess.h"
|
||||
#include "util/linux/address_types.h"
|
||||
#include "util/linux/auxiliary_vector.h"
|
||||
#include "util/linux/memory_map.h"
|
||||
#include "util/linux/process_memory.h"
|
||||
#include "util/linux/process_memory_range.h"
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
#include <sys/system_properties.h>
|
||||
#endif
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
int AndroidRuntimeAPI() {
|
||||
char api_string[PROP_VALUE_MAX];
|
||||
int length = __system_property_get("ro.build.version.sdk", api_string);
|
||||
if (length <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int api_level;
|
||||
bool success =
|
||||
base::StringToInt(base::StringPiece(api_string, length), &api_level);
|
||||
return success ? api_level : -1;
|
||||
}
|
||||
#endif // OS_ANDROID
|
||||
|
||||
void TestAgainstTarget(pid_t pid, bool is_64_bit) {
|
||||
// Use ElfImageReader on the main executable which can tell us the debug
|
||||
// address. glibc declares the symbol _r_debug in link.h which we can use to
|
||||
// get the address, but Android does not.
|
||||
AuxiliaryVector aux;
|
||||
ASSERT_TRUE(aux.Initialize(pid, is_64_bit));
|
||||
|
||||
LinuxVMAddress phdrs;
|
||||
ASSERT_TRUE(aux.GetValue(AT_PHDR, &phdrs));
|
||||
|
||||
MemoryMap mappings;
|
||||
ASSERT_TRUE(mappings.Initialize(pid));
|
||||
|
||||
const MemoryMap::Mapping* phdr_mapping = mappings.FindMapping(phdrs);
|
||||
ASSERT_TRUE(phdr_mapping);
|
||||
const MemoryMap::Mapping* exe_mapping =
|
||||
mappings.FindFileMmapStart(*phdr_mapping);
|
||||
LinuxVMAddress elf_address = exe_mapping->range.Base();
|
||||
|
||||
ProcessMemory memory;
|
||||
ASSERT_TRUE(memory.Initialize(pid));
|
||||
ProcessMemoryRange range;
|
||||
ASSERT_TRUE(range.Initialize(&memory, is_64_bit));
|
||||
|
||||
ElfImageReader exe_reader;
|
||||
ASSERT_TRUE(exe_reader.Initialize(range, elf_address));
|
||||
LinuxVMAddress debug_address;
|
||||
ASSERT_TRUE(exe_reader.GetDebugAddress(&debug_address));
|
||||
|
||||
// start the actual tests
|
||||
DebugRendezvous debug;
|
||||
ASSERT_TRUE(debug.Initialize(range, debug_address));
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
const int android_runtime_api = AndroidRuntimeAPI();
|
||||
ASSERT_GE(android_runtime_api, 1);
|
||||
|
||||
EXPECT_NE(debug.Executable()->name.find("crashpad_snapshot_test"),
|
||||
std::string::npos);
|
||||
|
||||
// Android's loader never sets the dynamic array for the executable.
|
||||
EXPECT_EQ(debug.Executable()->dynamic_array, 0u);
|
||||
#else
|
||||
// glibc's loader implements most of the tested features that Android's was
|
||||
// missing but has since gained.
|
||||
const int android_runtime_api = std::numeric_limits<int>::max();
|
||||
|
||||
// glibc's loader does not set the name for the executable.
|
||||
EXPECT_TRUE(debug.Executable()->name.empty());
|
||||
CheckedLinuxAddressRange exe_range(
|
||||
is_64_bit, exe_reader.Address(), exe_reader.Size());
|
||||
EXPECT_TRUE(exe_range.ContainsValue(debug.Executable()->dynamic_array));
|
||||
#endif // OS_ANDROID
|
||||
|
||||
// Android's loader doesn't set the load bias until Android 4.3 (API 18).
|
||||
if (android_runtime_api >= 18) {
|
||||
EXPECT_EQ(debug.Executable()->load_bias, exe_reader.GetLoadBias());
|
||||
} else {
|
||||
EXPECT_EQ(debug.Executable()->load_bias, 0);
|
||||
}
|
||||
|
||||
for (const DebugRendezvous::LinkEntry& module : debug.Modules()) {
|
||||
SCOPED_TRACE(base::StringPrintf("name %s, load_bias 0x%" PRIx64
|
||||
", dynamic_array 0x%" PRIx64,
|
||||
module.name.c_str(),
|
||||
module.load_bias,
|
||||
module.dynamic_array));
|
||||
const bool is_android_loader = (module.name == "/system/bin/linker" ||
|
||||
module.name == "/system/bin/linker64");
|
||||
|
||||
// Android's loader doesn't set its own dynamic array until Android 4.2
|
||||
// (API 17).
|
||||
if (is_android_loader && android_runtime_api < 17) {
|
||||
EXPECT_EQ(module.dynamic_array, 0u);
|
||||
EXPECT_EQ(module.load_bias, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(module.dynamic_array);
|
||||
const MemoryMap::Mapping* dyn_mapping =
|
||||
mappings.FindMapping(module.dynamic_array);
|
||||
ASSERT_TRUE(dyn_mapping);
|
||||
|
||||
const MemoryMap::Mapping* module_mapping =
|
||||
mappings.FindFileMmapStart(*dyn_mapping);
|
||||
ASSERT_TRUE(module_mapping);
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
EXPECT_FALSE(module.name.empty());
|
||||
#else
|
||||
// glibc's loader doesn't set the name in the link map for the vdso.
|
||||
EXPECT_PRED4(
|
||||
[](const std::string mapping_name,
|
||||
int device,
|
||||
int inode,
|
||||
const std::string& module_name) {
|
||||
return module_name.empty() ==
|
||||
(device == 0 && inode == 0 && mapping_name == "[vdso]");
|
||||
},
|
||||
module_mapping->name,
|
||||
module_mapping->device,
|
||||
module_mapping->inode,
|
||||
module.name);
|
||||
#endif // OS_ANDROID
|
||||
|
||||
ElfImageReader module_reader;
|
||||
ASSERT_TRUE(module_reader.Initialize(range, module_mapping->range.Base()));
|
||||
|
||||
// Android's loader stops setting its own load bias after Android 4.4.4
|
||||
// (API 20) until Android 6.0 (API 23).
|
||||
if (is_android_loader && android_runtime_api > 20 &&
|
||||
android_runtime_api < 23) {
|
||||
EXPECT_EQ(module.load_bias, 0);
|
||||
} else {
|
||||
EXPECT_EQ(module.load_bias, module_reader.GetLoadBias());
|
||||
}
|
||||
|
||||
CheckedLinuxAddressRange module_range(
|
||||
is_64_bit, module_reader.Address(), module_reader.Size());
|
||||
EXPECT_TRUE(module_range.ContainsValue(module.dynamic_array));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DebugRendezvous, Self) {
|
||||
#if defined(ARCH_CPU_64_BITS)
|
||||
constexpr bool is_64_bit = true;
|
||||
#else
|
||||
constexpr bool is_64_bit = false;
|
||||
#endif
|
||||
|
||||
TestAgainstTarget(getpid(), is_64_bit);
|
||||
}
|
||||
|
||||
class ChildTest : public Multiprocess {
|
||||
public:
|
||||
ChildTest() {}
|
||||
~ChildTest() {}
|
||||
|
||||
private:
|
||||
void MultiprocessParent() {
|
||||
#if defined(ARCH_CPU_64_BITS)
|
||||
constexpr bool is_64_bit = true;
|
||||
#else
|
||||
constexpr bool is_64_bit = false;
|
||||
#endif
|
||||
|
||||
TestAgainstTarget(ChildPID(), is_64_bit);
|
||||
}
|
||||
|
||||
void MultiprocessChild() { CheckedReadFileAtEOF(ReadPipeHandle()); }
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ChildTest);
|
||||
};
|
||||
|
||||
TEST(DebugRendezvous, Child) {
|
||||
ChildTest test;
|
||||
test.Run();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
@ -49,6 +49,19 @@ class ElfImageReader {
|
||||
//! the ELF image is loaded.
|
||||
bool Initialize(const ProcessMemoryRange& memory, LinuxVMAddress address);
|
||||
|
||||
//! \brief Returns the base address of the image's memory range.
|
||||
//!
|
||||
//! This may differ from the address passed to Initialize() if the ELF header
|
||||
//! is not loaded at the start of the first `PT_LOAD` segment.
|
||||
LinuxVMAddress Address() const { return memory_.Base(); }
|
||||
|
||||
//! \brief Returns the size of the range containing all loaded segments for
|
||||
//! this image.
|
||||
//!
|
||||
//! The size may include memory that is unmapped or mapped to other objects if
|
||||
//! this image's `PT_LOAD` segments are not contiguous.
|
||||
LinuxVMSize Size() const { return memory_.Size(); }
|
||||
|
||||
//! \brief Returns the file type for the image.
|
||||
//!
|
||||
//! Possible values include `ET_EXEC` or `ET_DYN` from `<elf.h>`.
|
||||
|
@ -40,6 +40,8 @@
|
||||
'exception_snapshot.h',
|
||||
'handle_snapshot.cc',
|
||||
'handle_snapshot.h',
|
||||
'linux/debug_rendezvous.cc',
|
||||
'linux/debug_rendezvous.h',
|
||||
'linux/elf_dynamic_array_reader.cc',
|
||||
'linux/elf_dynamic_array_reader.h',
|
||||
'linux/elf_image_reader.cc',
|
||||
|
@ -70,6 +70,7 @@
|
||||
'cpu_context_test.cc',
|
||||
'crashpad_info_client_options_test.cc',
|
||||
'api/module_annotations_win_test.cc',
|
||||
'linux/debug_rendezvous_test.cc',
|
||||
'linux/elf_image_reader_test.cc',
|
||||
'mac/cpu_context_mac_test.cc',
|
||||
'mac/mach_o_image_annotations_reader_test.cc',
|
||||
|
@ -70,6 +70,12 @@ class ProcessMemoryRange {
|
||||
//! \brief Returns whether the range is part of a 64-bit address space.
|
||||
bool Is64Bit() const { return range_.Is64Bit(); }
|
||||
|
||||
//! \brief Returns the base address of the range.
|
||||
LinuxVMAddress Base() const { return range_.Base(); }
|
||||
|
||||
//! \brief Returns the size of the range.
|
||||
LinuxVMSize Size() const { return range_.Size(); }
|
||||
|
||||
//! \brief Shrinks the range to the new base and size.
|
||||
//!
|
||||
//! The new range must be contained within the existing range for this object.
|
||||
|
Loading…
x
Reference in New Issue
Block a user