| Index: third_party/android_crazy_linker/src/src/crazy_linker_rdebug.cpp
|
| diff --git a/third_party/android_crazy_linker/src/src/crazy_linker_rdebug.cpp b/third_party/android_crazy_linker/src/src/crazy_linker_rdebug.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..fd410c46f41182c151bebff73171bc9640261848
|
| --- /dev/null
|
| +++ b/third_party/android_crazy_linker/src/src/crazy_linker_rdebug.cpp
|
| @@ -0,0 +1,516 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "crazy_linker_rdebug.h"
|
| +
|
| +#include <elf.h>
|
| +#include <inttypes.h>
|
| +#include <pthread.h>
|
| +#include <sys/mman.h>
|
| +#include <unistd.h>
|
| +
|
| +#include "crazy_linker_debug.h"
|
| +#include "crazy_linker_globals.h"
|
| +#include "crazy_linker_proc_maps.h"
|
| +#include "crazy_linker_util.h"
|
| +#include "crazy_linker_system.h"
|
| +#include "elf_traits.h"
|
| +
|
| +namespace crazy {
|
| +
|
| +namespace {
|
| +
|
| +// Find the full path of the current executable. On success return true
|
| +// and sets |exe_path|. On failure, return false and sets errno.
|
| +bool FindExecutablePath(String* exe_path) {
|
| + // /proc/self/exe is a symlink to the full path. Read it with
|
| + // readlink().
|
| + exe_path->Resize(512);
|
| + ssize_t ret = TEMP_FAILURE_RETRY(
|
| + readlink("/proc/self/exe", exe_path->ptr(), exe_path->size()));
|
| + if (ret < 0) {
|
| + LOG_ERRNO("%s: Could not get /proc/self/exe link", __FUNCTION__);
|
| + return false;
|
| + }
|
| +
|
| + exe_path->Resize(static_cast<size_t>(ret));
|
| + LOG("%s: Current executable: %s\n", __FUNCTION__, exe_path->c_str());
|
| + return true;
|
| +}
|
| +
|
| +// Given an ELF binary at |path| that is _already_ mapped in the process,
|
| +// find the address of its dynamic section and its size.
|
| +// |path| is the full path of the binary (as it appears in /proc/self/maps.
|
| +// |self_maps| is an instance of ProcMaps that is used to inspect
|
| +// /proc/self/maps. The function rewind + iterates over it.
|
| +// On success, return true and set |*dynamic_offset| and |*dynamic_size|.
|
| +bool FindElfDynamicSection(const char* path,
|
| + ProcMaps* self_maps,
|
| + size_t* dynamic_address,
|
| + size_t* dynamic_size) {
|
| + // Read the ELF header first.
|
| + ELF::Ehdr header[1];
|
| +
|
| + crazy::FileDescriptor fd;
|
| + if (!fd.OpenReadOnly(path) ||
|
| + fd.Read(header, sizeof(header)) != static_cast<int>(sizeof(header))) {
|
| + LOG_ERRNO("%s: Could not load ELF binary header", __FUNCTION__);
|
| + return false;
|
| + }
|
| +
|
| + // Sanity check.
|
| + if (header->e_ident[0] != 127 || header->e_ident[1] != 'E' ||
|
| + header->e_ident[2] != 'L' || header->e_ident[3] != 'F' ||
|
| + header->e_ident[4] != ELF::kElfClass) {
|
| + LOG("%s: Not a %d-bit ELF binary: %s\n",
|
| + __FUNCTION__,
|
| + ELF::kElfBits,
|
| + path);
|
| + return false;
|
| + }
|
| +
|
| + if (header->e_phoff == 0 || header->e_phentsize != sizeof(ELF::Phdr)) {
|
| + LOG("%s: Invalid program header values: %s\n", __FUNCTION__, path);
|
| + return false;
|
| + }
|
| +
|
| + // Scan the program header table.
|
| + if (fd.SeekTo(header->e_phoff) < 0) {
|
| + LOG_ERRNO("%s: Could not find ELF program header table", __FUNCTION__);
|
| + return false;
|
| + }
|
| +
|
| + ELF::Phdr phdr_load0 = {0, };
|
| + ELF::Phdr phdr_dyn = {0, };
|
| + bool found_load0 = false;
|
| + bool found_dyn = false;
|
| +
|
| + for (size_t n = 0; n < header->e_phnum; ++n) {
|
| + ELF::Phdr phdr;
|
| + if (fd.Read(&phdr, sizeof(phdr)) != sizeof(phdr)) {
|
| + LOG_ERRNO("%s: Could not read program header entry", __FUNCTION__);
|
| + return false;
|
| + }
|
| +
|
| + if (phdr.p_type == PT_LOAD && !found_load0) {
|
| + phdr_load0 = phdr;
|
| + found_load0 = true;
|
| + } else if (phdr.p_type == PT_DYNAMIC && !found_dyn) {
|
| + phdr_dyn = phdr;
|
| + found_dyn = true;
|
| + }
|
| + }
|
| +
|
| + if (!found_load0) {
|
| + LOG("%s: Could not find loadable segment!?\n", __FUNCTION__);
|
| + return false;
|
| + }
|
| + if (!found_dyn) {
|
| + LOG("%s: Could not find dynamic segment!?\n", __FUNCTION__);
|
| + return false;
|
| + }
|
| +
|
| + LOG("%s: Found first loadable segment [offset=%p vaddr=%p]\n",
|
| + __FUNCTION__,
|
| + (void*)phdr_load0.p_offset,
|
| + (void*)phdr_load0.p_vaddr);
|
| +
|
| + LOG("%s: Found dynamic segment [offset=%p vaddr=%p size=%p]\n",
|
| + __FUNCTION__,
|
| + (void*)phdr_dyn.p_offset,
|
| + (void*)phdr_dyn.p_vaddr,
|
| + (void*)phdr_dyn.p_memsz);
|
| +
|
| + // Parse /proc/self/maps to find the load address of the first
|
| + // loadable segment.
|
| + size_t path_len = strlen(path);
|
| + self_maps->Rewind();
|
| + ProcMaps::Entry entry;
|
| + while (self_maps->GetNextEntry(&entry)) {
|
| + if (!entry.path || entry.path_len != path_len ||
|
| + memcmp(entry.path, path, path_len) != 0)
|
| + continue;
|
| +
|
| + LOG("%s: Found executable segment mapped [%p-%p offset=%p]\n",
|
| + __FUNCTION__,
|
| + (void*)entry.vma_start,
|
| + (void*)entry.vma_end,
|
| + (void*)entry.load_offset);
|
| +
|
| + size_t load_bias = entry.vma_start - phdr_load0.p_vaddr;
|
| + LOG("%s: Load bias is %p\n", __FUNCTION__, (void*)load_bias);
|
| +
|
| + *dynamic_address = load_bias + phdr_dyn.p_vaddr;
|
| + *dynamic_size = phdr_dyn.p_memsz;
|
| + LOG("%s: Dynamic section addr=%p size=%p\n",
|
| + __FUNCTION__,
|
| + (void*)*dynamic_address,
|
| + (void*)*dynamic_size);
|
| + return true;
|
| + }
|
| +
|
| + LOG("%s: Executable is not mapped in current process.\n", __FUNCTION__);
|
| + return false;
|
| +}
|
| +
|
| +// Helper class to temporarily remap a page to readable+writable until
|
| +// scope exit.
|
| +class ScopedPageReadWriteRemapper {
|
| + public:
|
| + ScopedPageReadWriteRemapper(void* address);
|
| + ~ScopedPageReadWriteRemapper();
|
| +
|
| + // Releases the page so that the destructor does not undo the remapping.
|
| + void Release();
|
| +
|
| + private:
|
| + static const uintptr_t kPageSize = 4096;
|
| + uintptr_t page_address_;
|
| + int page_prot_;
|
| +};
|
| +
|
| +ScopedPageReadWriteRemapper::ScopedPageReadWriteRemapper(void* address) {
|
| + page_address_ = reinterpret_cast<uintptr_t>(address) & ~(kPageSize - 1);
|
| + page_prot_ = 0;
|
| + if (!FindProtectionFlagsForAddress(address, &page_prot_)) {
|
| + LOG("Could not find protection flags for %p\n", address);
|
| + page_address_ = 0;
|
| + return;
|
| + }
|
| +
|
| + // Note: page_prot_ may already indicate read/write, but because of
|
| + // possible races with the system linker we cannot be confident that
|
| + // this is reliable. So we always set read/write here.
|
| + //
|
| + // See commentary in WriteLinkMapField for more.
|
| + int new_page_prot = page_prot_ | PROT_READ | PROT_WRITE;
|
| + int ret = mprotect(
|
| + reinterpret_cast<void*>(page_address_), kPageSize, new_page_prot);
|
| + if (ret < 0) {
|
| + LOG_ERRNO("Could not remap page to read/write");
|
| + page_address_ = 0;
|
| + }
|
| +}
|
| +
|
| +ScopedPageReadWriteRemapper::~ScopedPageReadWriteRemapper() {
|
| + if (page_address_) {
|
| + int ret =
|
| + mprotect(reinterpret_cast<void*>(page_address_), kPageSize, page_prot_);
|
| + if (ret < 0)
|
| + LOG_ERRNO("Could not remap page to old protection flags");
|
| + }
|
| +}
|
| +
|
| +void ScopedPageReadWriteRemapper::Release() {
|
| + page_address_ = 0;
|
| + page_prot_ = 0;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +bool RDebug::Init() {
|
| + // The address of '_r_debug' is in the DT_DEBUG entry of the current
|
| + // executable.
|
| + init_ = true;
|
| +
|
| + size_t dynamic_addr = 0;
|
| + size_t dynamic_size = 0;
|
| + String path;
|
| +
|
| + // Find the current executable's full path, and its dynamic section
|
| + // information.
|
| + if (!FindExecutablePath(&path))
|
| + return false;
|
| +
|
| + ProcMaps self_maps;
|
| + if (!FindElfDynamicSection(
|
| + path.c_str(), &self_maps, &dynamic_addr, &dynamic_size)) {
|
| + return false;
|
| + }
|
| +
|
| + // Parse the dynamic table and find the DT_DEBUG entry.
|
| + const ELF::Dyn* dyn_section = reinterpret_cast<const ELF::Dyn*>(dynamic_addr);
|
| +
|
| + while (dynamic_size >= sizeof(*dyn_section)) {
|
| + if (dyn_section->d_tag == DT_DEBUG) {
|
| + // Found it!
|
| + LOG("%s: Found DT_DEBUG entry inside %s at %p, pointing to %p\n",
|
| + __FUNCTION__,
|
| + path.c_str(),
|
| + dyn_section,
|
| + dyn_section->d_un.d_ptr);
|
| + if (dyn_section->d_un.d_ptr) {
|
| + r_debug_ = reinterpret_cast<r_debug*>(dyn_section->d_un.d_ptr);
|
| + LOG("%s: r_debug [r_version=%d r_map=%p r_brk=%p r_ldbase=%p]\n",
|
| + __FUNCTION__,
|
| + r_debug_->r_version,
|
| + r_debug_->r_map,
|
| + r_debug_->r_brk,
|
| + r_debug_->r_ldbase);
|
| + // Only version 1 of the struct is supported.
|
| + if (r_debug_->r_version != 1) {
|
| + LOG("%s: r_debug.r_version is %d, 1 expected.\n",
|
| + __FUNCTION__,
|
| + r_debug_->r_version);
|
| + r_debug_ = NULL;
|
| + }
|
| +
|
| + // The linker of recent Android releases maps its link map entries
|
| + // in read-only pages. Determine if this is the case and record it
|
| + // for later. The first entry in the list corresponds to the
|
| + // executable.
|
| + int prot = self_maps.GetProtectionFlagsForAddress(r_debug_->r_map);
|
| + readonly_entries_ = (prot & PROT_WRITE) == 0;
|
| +
|
| + LOG("%s: r_debug.readonly_entries=%s\n",
|
| + __FUNCTION__,
|
| + readonly_entries_ ? "true" : "false");
|
| + return true;
|
| + }
|
| + }
|
| + dyn_section++;
|
| + dynamic_size -= sizeof(*dyn_section);
|
| + }
|
| +
|
| + LOG("%s: There is no non-0 DT_DEBUG entry in this process\n", __FUNCTION__);
|
| + return false;
|
| +}
|
| +
|
| +namespace {
|
| +
|
| +// Helper class providing a simple scoped pthreads mutex.
|
| +class ScopedMutexLock {
|
| + public:
|
| + explicit ScopedMutexLock(pthread_mutex_t* mutex) : mutex_(mutex) {
|
| + pthread_mutex_lock(mutex_);
|
| + }
|
| + ~ScopedMutexLock() {
|
| + pthread_mutex_unlock(mutex_);
|
| + }
|
| +
|
| + private:
|
| + pthread_mutex_t* mutex_;
|
| +};
|
| +
|
| +// Helper runnable class. Handler is one of the two static functions
|
| +// AddEntryInternal() or DelEntryInternal(). Calling these invokes
|
| +// AddEntryImpl() or DelEntryImpl() respectively on rdebug.
|
| +class RDebugRunnable {
|
| + public:
|
| + RDebugRunnable(rdebug_callback_handler_t handler,
|
| + RDebug* rdebug,
|
| + link_map_t* entry,
|
| + bool is_blocking)
|
| + : handler_(handler), rdebug_(rdebug),
|
| + entry_(entry), is_blocking_(is_blocking), has_run_(false) {
|
| + pthread_mutex_init(&mutex_, NULL);
|
| + pthread_cond_init(&cond_, NULL);
|
| + }
|
| +
|
| + static void Run(void* opaque);
|
| + static void WaitForCallback(void* opaque);
|
| +
|
| + private:
|
| + rdebug_callback_handler_t handler_;
|
| + RDebug* rdebug_;
|
| + link_map_t* entry_;
|
| + bool is_blocking_;
|
| + bool has_run_;
|
| + pthread_mutex_t mutex_;
|
| + pthread_cond_t cond_;
|
| +};
|
| +
|
| +// Callback entry point.
|
| +void RDebugRunnable::Run(void* opaque) {
|
| + RDebugRunnable* runnable = static_cast<RDebugRunnable*>(opaque);
|
| +
|
| + LOG("%s: Callback received, runnable=%p\n", __FUNCTION__, runnable);
|
| + (*runnable->handler_)(runnable->rdebug_, runnable->entry_);
|
| +
|
| + if (!runnable->is_blocking_) {
|
| + delete runnable;
|
| + return;
|
| + }
|
| +
|
| + LOG("%s: Signalling callback, runnable=%p\n", __FUNCTION__, runnable);
|
| + {
|
| + ScopedMutexLock m(&runnable->mutex_);
|
| + runnable->has_run_ = true;
|
| + pthread_cond_signal(&runnable->cond_);
|
| + }
|
| +}
|
| +
|
| +// For blocking callbacks, wait for the call to Run().
|
| +void RDebugRunnable::WaitForCallback(void* opaque) {
|
| + RDebugRunnable* runnable = static_cast<RDebugRunnable*>(opaque);
|
| +
|
| + if (!runnable->is_blocking_) {
|
| + LOG("%s: Non-blocking, not waiting, runnable=%p\n", __FUNCTION__, runnable);
|
| + return;
|
| + }
|
| +
|
| + LOG("%s: Waiting for signal, runnable=%p\n", __FUNCTION__, runnable);
|
| + {
|
| + ScopedMutexLock m(&runnable->mutex_);
|
| + while (!runnable->has_run_)
|
| + pthread_cond_wait(&runnable->cond_, &runnable->mutex_);
|
| + }
|
| +
|
| + delete runnable;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +// Helper function to schedule AddEntry() and DelEntry() calls onto another
|
| +// thread where possible. Running them there avoids races with the system
|
| +// linker, which expects to be able to set r_map pages readonly when it
|
| +// is not using them and which may run simultaneously on the main thread.
|
| +bool RDebug::PostCallback(rdebug_callback_handler_t handler,
|
| + link_map_t* entry,
|
| + bool is_blocking) {
|
| + if (!post_for_later_execution_) {
|
| + LOG("%s: Deferred execution disabled\n", __FUNCTION__);
|
| + return false;
|
| + }
|
| +
|
| + RDebugRunnable* runnable =
|
| + new RDebugRunnable(handler, this, entry, is_blocking);
|
| + void* context = post_for_later_execution_context_;
|
| +
|
| + if (!(*post_for_later_execution_)(context, &RDebugRunnable::Run, runnable)) {
|
| + LOG("%s: Deferred execution enabled, but posting failed\n", __FUNCTION__);
|
| + delete runnable;
|
| + return false;
|
| + }
|
| +
|
| + LOG("%s: Posted for later execution, runnable=%p\n", __FUNCTION__, runnable);
|
| +
|
| + if (is_blocking) {
|
| + RDebugRunnable::WaitForCallback(runnable);
|
| + LOG("%s: Completed execution, runnable=%p\n", __FUNCTION__, runnable);
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +// Helper function for AddEntryImpl and DelEntryImpl.
|
| +// Sets *link_pointer to entry. link_pointer is either an 'l_prev' or an
|
| +// 'l_next' field in a neighbouring linkmap_t. If link_pointer is in a
|
| +// page that is mapped readonly, the page is remapped to be writable before
|
| +// assignment.
|
| +void RDebug::WriteLinkMapField(link_map_t** link_pointer, link_map_t* entry) {
|
| + ScopedPageReadWriteRemapper mapper(link_pointer);
|
| + LOG("%s: Remapped page for %p for read/write\n", __FUNCTION__, link_pointer);
|
| +
|
| + *link_pointer = entry;
|
| +
|
| + // We always mprotect the page containing link_pointer to read/write,
|
| + // then write the entry. The page may already be read/write, but on
|
| + // recent Android release is most likely readonly. Because of the way
|
| + // the system linker operates we cannot tell with certainty what its
|
| + // correct setting should be.
|
| + //
|
| + // Now, we always leave the page read/write. Here is why. If we set it
|
| + // back to readonly at the point between where the system linker sets
|
| + // it to read/write and where it writes to the address, this will cause
|
| + // the system linker to crash. Clearly that is undesirable. From
|
| + // observations this occurs most frequently on the gpu process.
|
| + //
|
| + // TODO(simonb): Revisit this, details in:
|
| + // https://code.google.com/p/chromium/issues/detail?id=450659
|
| + // https://code.google.com/p/chromium/issues/detail?id=458346
|
| + mapper.Release();
|
| + LOG("%s: Released mapper, leaving page read/write\n", __FUNCTION__);
|
| +}
|
| +
|
| +void RDebug::AddEntryImpl(link_map_t* entry) {
|
| + ScopedGlobalLock lock;
|
| + LOG("%s: Adding: %s\n", __FUNCTION__, entry->l_name);
|
| + if (!init_)
|
| + Init();
|
| +
|
| + if (!r_debug_) {
|
| + LOG("%s: Nothing to do\n", __FUNCTION__);
|
| + return;
|
| + }
|
| +
|
| + // Tell GDB the list is going to be modified.
|
| + r_debug_->r_state = RT_ADD;
|
| + r_debug_->r_brk();
|
| +
|
| + // IMPORTANT: GDB expects the first entry in the list to correspond
|
| + // to the executable. So add our new entry just after it. This is ok
|
| + // because by default, the linker is always the second entry, as in:
|
| + //
|
| + // [<executable>, /system/bin/linker, libc.so, libm.so, ...]
|
| + //
|
| + // By design, the first two entries should never be removed since they
|
| + // can't be unloaded from the process (they are loaded by the kernel
|
| + // when invoking the program).
|
| + //
|
| + // TODO(digit): Does GDB expect the linker to be the second entry?
|
| + // It doesn't seem so, but have a look at the GDB sources to confirm
|
| + // this. No problem appear experimentally.
|
| + //
|
| + // What happens for static binaries? They don't have an .interp section,
|
| + // and don't have a r_debug variable on Android, so GDB should not be
|
| + // able to debug shared libraries at all for them (assuming one
|
| + // statically links a linker into the executable).
|
| + if (!r_debug_->r_map || !r_debug_->r_map->l_next ||
|
| + !r_debug_->r_map->l_next->l_next) {
|
| + // Sanity check: Must have at least two items in the list.
|
| + LOG("%s: Malformed r_debug.r_map list\n", __FUNCTION__);
|
| + r_debug_ = NULL;
|
| + return;
|
| + }
|
| +
|
| + link_map_t* before = r_debug_->r_map->l_next;
|
| + link_map_t* after = before->l_next;
|
| +
|
| + // Prepare the new entry.
|
| + entry->l_prev = before;
|
| + entry->l_next = after;
|
| +
|
| + // IMPORTANT: Before modifying the previous and next entries in the
|
| + // list, ensure that they are writable. This avoids crashing when
|
| + // updating the 'l_prev' or 'l_next' fields of a system linker entry,
|
| + // which are mapped read-only.
|
| + WriteLinkMapField(&before->l_next, entry);
|
| + WriteLinkMapField(&after->l_prev, entry);
|
| +
|
| + // Tell GDB that the list modification has completed.
|
| + r_debug_->r_state = RT_CONSISTENT;
|
| + r_debug_->r_brk();
|
| +}
|
| +
|
| +void RDebug::DelEntryImpl(link_map_t* entry) {
|
| + ScopedGlobalLock lock;
|
| + LOG("%s: Deleting: %s\n", __FUNCTION__, entry->l_name);
|
| + if (!r_debug_)
|
| + return;
|
| +
|
| + // Tell GDB the list is going to be modified.
|
| + r_debug_->r_state = RT_DELETE;
|
| + r_debug_->r_brk();
|
| +
|
| + // IMPORTANT: Before modifying the previous and next entries in the
|
| + // list, ensure that they are writable. See comment above for more
|
| + // details.
|
| + if (entry->l_prev)
|
| + WriteLinkMapField(&entry->l_prev->l_next, entry->l_next);
|
| + if (entry->l_next)
|
| + WriteLinkMapField(&entry->l_next->l_prev, entry->l_prev);
|
| +
|
| + if (r_debug_->r_map == entry)
|
| + r_debug_->r_map = entry->l_next;
|
| +
|
| + entry->l_prev = NULL;
|
| + entry->l_next = NULL;
|
| +
|
| + // Tell GDB the list modification has completed.
|
| + r_debug_->r_state = RT_CONSISTENT;
|
| + r_debug_->r_brk();
|
| +}
|
| +
|
| +} // namespace crazy
|
|
|