blob: d03121d54fcc6086604ed204a41085618f49424b [file] [log] [blame]
// 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_wrappers.h"
#include <link.h>
#include "crazy_linker_debug.h"
#include "crazy_linker_globals.h"
#include "crazy_linker_library_list.h"
#include "crazy_linker_library_view.h"
#include "crazy_linker_shared_library.h"
#include "crazy_linker_system_linker.h"
#include "crazy_linker_thread_data.h"
#include "crazy_linker_util.h"
#ifdef __arm__
// On ARM, this function is exported by the dynamic linker but never
// declared in any official header. It is used at runtime to
// find the base address of the .ARM.exidx section for the
// shared library containing the instruction at |pc|, as well as
// the number of 8-byte entries in that section, written into |*pcount|
extern "C" _Unwind_Ptr dl_unwind_find_exidx(_Unwind_Ptr, int*);
#else
// On other architectures, this function is exported by the dynamic linker
// but never declared in any official header. It is used at runtime to
// iterate over all loaded libraries and call the |cb|. When the function
// returns non-0, the iteration returns and the function returns its
// value.
extern "C" int dl_iterate_phdr(int (*cb)(dl_phdr_info* info,
size_t size,
void* data),
void* data);
#endif
namespace crazy {
namespace {
#ifndef UNIT_TEST
// LLVM's demangler is large, and we have no need of it. Overriding it with
// our own stub version here stops a lot of code being pulled in from libc++.
// This reduces the on-disk footprint of the crazy linker library by more than
// 70%, down from over 290kb to under 85kb on ARM.
//
// For details, see:
// https://code.google.com/p/chromium/issues/detail?id=502299
// https://llvm.org/svn/llvm-project/libcxxabi/trunk/src/cxa_demangle.cpp
extern "C" char* __cxa_demangle(const char* mangled_name,
char* buf,
size_t* n,
int* status) {
static const int kMemoryAllocFailure = -1; // LLVM's memory_alloc_failure.
if (status)
*status = kMemoryAllocFailure;
return NULL;
}
#endif // UNIT_TEST
#ifdef __arm__
extern "C" int __cxa_atexit(void (*)(void*), void*, void*);
// On ARM, this function is defined as a weak symbol by libc.so.
// Unfortunately its address cannot be found through dlsym(), which will
// always return NULL. To work-around this, define a copy here that does
// exactly the same thing. The ARM EABI mandates the function's behaviour.
// __cxa_atexit() is implemented by the C library, but not declared by
// any official header. It's part of the low-level C++ support runtime.
int __aeabi_atexit(void* object, void (*destructor)(void*), void* dso_handle) {
return __cxa_atexit(destructor, object, dso_handle);
}
#endif
// Used to save the system dlerror() into our thread-specific data.
void SaveSystemError() {
ThreadData* data = GetThreadData();
data->SetError(SystemLinker::Error());
}
char* WrapDlerror() {
ThreadData* data = GetThreadData();
const char* error = data->GetError();
data->SwapErrorBuffers();
// dlerror() returns a 'char*', but no sane client code should ever
// try to write to this location.
return const_cast<char*>(error);
}
void* WrapDlopen(const char* path, int mode) {
ScopedLockedGlobals globals;
// NOTE: If |path| is NULL, the wrapper should return a handle
// corresponding to the current executable. This can't be a crazy
// library, so don't try to handle it with the crazy linker.
if (path) {
Error error;
LibraryView* wrap = globals->libraries()->LoadLibrary(
path, 0U /* load_address */, globals->search_path_list(), &error);
if (wrap) {
globals->valid_handles()->Add(wrap);
return wrap;
}
}
// Try to load the executable with the system dlopen() instead.
void* system_lib = SystemLinker::Open(path, mode);
if (system_lib == NULL) {
SaveSystemError();
return nullptr;
}
auto* wrap_lib = new LibraryView(system_lib, path ? path : "<executable>");
globals->libraries()->AddLibrary(wrap_lib);
globals->valid_handles()->Add(wrap_lib);
return wrap_lib;
}
void* WrapDlsym(void* lib_handle, const char* symbol_name) {
if (!symbol_name) {
SetLinkerError("dlsym: NULL symbol name");
return NULL;
}
if (!lib_handle) {
SetLinkerError("dlsym: NULL library handle");
return NULL;
}
// TODO(digit): Handle RTLD_DEFAULT / RTLD_NEXT
if (lib_handle == RTLD_DEFAULT || lib_handle == RTLD_NEXT) {
SetLinkerError("dlsym: RTLD_DEFAULT/RTLD_NEXT are not implemented!");
return NULL;
}
// NOTE: The Android dlsym() only looks inside the target library,
// while the GNU one will perform a breadth-first search into its
// dependency tree.
// This implementation performs a correct breadth-first search
// when |lib_handle| corresponds to a crazy library, except that
// it stops at system libraries that it depends on.
ScopedLockedGlobals globals;
if (!globals->valid_handles()->Has(lib_handle)) {
// Note: the handle was not opened with the crazy linker, so fall back
// to the system linker. That can happen in rare cases.
SystemLinker::SearchResult sym =
SystemLinker::Resolve(lib_handle, symbol_name);
if (!sym.IsValid()) {
SaveSystemError();
LOG("Could not find symbol '%s' from foreign library [%s]", symbol_name,
GetThreadData()->GetError());
}
return sym.address;
}
auto* wrap_lib = reinterpret_cast<LibraryView*>(lib_handle);
if (wrap_lib->IsSystem()) {
LibraryView::SearchResult sym = wrap_lib->LookupSymbol(symbol_name);
if (!sym.IsValid()) {
SaveSystemError();
LOG("Could not find symbol '%s' from system library %s [%s]", symbol_name,
wrap_lib->GetName(), GetThreadData()->GetError());
}
return sym.address;
}
if (wrap_lib->IsCrazy()) {
void* addr = globals->libraries()->FindSymbolFrom(symbol_name, wrap_lib);
if (addr)
return addr;
SetLinkerError("dlsym: Could not find '%s' from library '%s'",
symbol_name,
wrap_lib->GetName());
return NULL;
}
SetLinkerError("dlsym: Invalid library handle %p looking for '%s'",
lib_handle,
symbol_name);
return NULL;
}
int WrapDladdr(void* address, Dl_info* info) {
// First, perform search in crazy libraries.
{
ScopedLockedGlobals globals;
LibraryView* wrap = globals->libraries()->FindLibraryForAddress(address);
if (wrap && wrap->IsCrazy()) {
size_t sym_size = 0;
SharedLibrary* lib = wrap->GetCrazy();
::memset(info, 0, sizeof(*info));
info->dli_fname = lib->base_name();
info->dli_fbase = reinterpret_cast<void*>(lib->load_address());
// Determine if any symbol in the library contains the specified address.
(void)lib->FindNearestSymbolForAddress(
address, &info->dli_sname, &info->dli_saddr, &sym_size);
return 0;
}
}
// Otherwise, use system version.
int ret = SystemLinker::AddressInfo(address, info);
if (ret != 0)
SaveSystemError();
return ret;
}
int WrapDlclose(void* lib_handle) {
if (!lib_handle) {
SetLinkerError("NULL library handle");
return -1;
}
ScopedLockedGlobals globals;
if (!globals->valid_handles()->Remove(lib_handle)) {
// This is a foreign handle that was not created by the crazy linker.
// Fall-back to the system in this case.
if (SystemLinker::Close(lib_handle) != 0) {
SaveSystemError();
LOG("Could not close foreign library handle %p\n%s", lib_handle,
GetThreadData()->GetError());
return -1;
}
return 0;
}
LibraryView* wrap_lib = reinterpret_cast<LibraryView*>(lib_handle);
if (wrap_lib->IsSystem() || wrap_lib->IsCrazy()) {
globals->libraries()->UnloadLibrary(wrap_lib);
return 0;
}
// Invalid library handle!!
SetLinkerError("Invalid library handle %p", lib_handle);
return -1;
}
#ifdef __arm__
_Unwind_Ptr WrapDl_unwind_find_exidx(_Unwind_Ptr pc, int* pcount) {
// First lookup in crazy libraries.
{
ScopedLockedGlobals globals;
_Unwind_Ptr result =
globals->libraries()->FindArmExIdx(reinterpret_cast<void*>(pc), pcount);
if (result)
return result;
}
// Lookup in system libraries.
return ::dl_unwind_find_exidx(pc, pcount);
}
#else // !__arm__
int WrapDl_iterate_phdr(int (*cb)(dl_phdr_info*, size_t, void*), void* data) {
// First, iterate over crazy libraries.
{
ScopedLockedGlobals globals;
int result = globals->libraries()->IteratePhdr(cb, data);
if (result)
return result;
}
// Then lookup through system ones.
return ::dl_iterate_phdr(cb, data);
}
#endif // !__arm__
} // namespace
// This method should only be called from testing code. It is used by
// one integration test to check that wrapping works correctly within
// libraries loaded through the crazy-linker.
void* GetDlCloseWrapperAddressForTesting() {
return reinterpret_cast<void*>(&WrapDlclose);
}
// This method should only be called from testing code. It is used to return
// the list of valid dlopen() handles created by the crazy-linker. This returns
// the address of a heap-allocated array of pointers, which must be explicitly
// free()-ed by the caller. This returns nullptr is the array is empty.
// On exit, sets |*p_count| to the number of items in the array.
const void** GetValidDlopenHandlesForTesting(size_t* p_count) {
ScopedLockedGlobals globals;
const Vector<const void*>& handles =
globals->valid_handles()->GetValuesForTesting();
*p_count = handles.GetCount();
if (handles.IsEmpty())
return nullptr;
auto* ptr = reinterpret_cast<const void**>(
malloc(handles.GetCount() * sizeof(void*)));
for (size_t n = 0; n < handles.GetCount(); ++n) {
ptr[n] = handles[n];
}
return ptr;
}
void* WrapLinkerSymbol(const char* name) {
// Shortcut, since all names begin with 'dl'
// Take care of __aeabi_atexit on ARM though.
if (name[0] != 'd' || name[1] != 'l') {
#ifdef __arm__
if (name[0] == '_' && !strcmp("__aeabi_atexit", name))
return reinterpret_cast<void*>(&__aeabi_atexit);
#endif
return NULL;
}
static const struct {
const char* name;
void* address;
} kSymbols[] = {
{"dlopen", reinterpret_cast<void*>(&WrapDlopen)},
{"dlclose", reinterpret_cast<void*>(&WrapDlclose)},
{"dlerror", reinterpret_cast<void*>(&WrapDlerror)},
{"dlsym", reinterpret_cast<void*>(&WrapDlsym)},
{"dladdr", reinterpret_cast<void*>(&WrapDladdr)},
#ifdef __arm__
{"dl_unwind_find_exidx",
reinterpret_cast<void*>(&WrapDl_unwind_find_exidx)},
#else
{"dl_iterate_phdr", reinterpret_cast<void*>(&WrapDl_iterate_phdr)},
#endif
};
static const size_t kCount = sizeof(kSymbols) / sizeof(kSymbols[0]);
for (size_t n = 0; n < kCount; ++n) {
if (!strcmp(kSymbols[n].name, name))
return kSymbols[n].address;
}
return NULL;
}
} // namespace crazy