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:
Joshua Peraza 2017-07-14 10:00:28 -07:00
parent a79791969d
commit 041a50d75c
7 changed files with 473 additions and 0 deletions

View 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

View 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_

View 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

View File

@ -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>`.

View File

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

View File

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

View File

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