diff options
author | Simon Baldwin <simonb@google.com> | 2014-04-30 17:10:09 +0100 |
---|---|---|
committer | Simon Baldwin <simonb@google.com> | 2014-04-30 17:10:09 +0100 |
commit | 6788f450cf7ef93fe2fe0c20ec4b44a38e049bc7 (patch) | |
tree | 3818a8ece7116d686f3da95d2301f6e108158b41 /sources/android/crazy_linker | |
parent | 0d76530947278a8c3d623811fc1ce42871f2920d (diff) | |
download | ndk-6788f450cf7ef93fe2fe0c20ec4b44a38e049bc7.tar.gz |
crazy_linker: Add a callback feature.
Add support for callbacks and callback posters. Callbacks can execute
code later and/or on different threads. Use callbacks, where enabled,
to update r_map pages used by gdb.
Change-Id: If7e7f565248d8f83095c7bd78f1dd294216ba631
Diffstat (limited to 'sources/android/crazy_linker')
6 files changed, 354 insertions, 9 deletions
diff --git a/sources/android/crazy_linker/include/crazy_linker.h b/sources/android/crazy_linker/include/crazy_linker.h index 9462b59c0..419d8cde9 100644 --- a/sources/android/crazy_linker/include/crazy_linker.h +++ b/sources/android/crazy_linker/include/crazy_linker.h @@ -21,6 +21,7 @@ // loaded at the same address in two distinct processes. // #include <dlfcn.h> +#include <stdbool.h> #include <stddef.h> #ifdef __cplusplus @@ -132,6 +133,63 @@ void crazy_context_get_java_vm(crazy_context_t* context, // Destroy a given context object. void crazy_context_destroy(crazy_context_t* context) _CRAZY_PUBLIC; +// Some operations performed by the crazy linker might conflict with the +// system linker if they are used concurrently in different threads +// (e.g. modifying the list of shared libraries seen by GDB). To work +// around this, the crazy linker provides a way to delay these conflicting +// operations for a later time. +// +// This works by wrapping each of these operations in a small data structure +// (crazy_callback_t), which can later be passed to crazy_callback_run() +// to execute it. +// +// The user must provide a function to record these callbacks during +// library loading, by calling crazy_linker_set_callback_poster(). +// +// Once all libraries are loaded, the callbacks can be later called either +// in a different thread, or when it is safe to assume the system linker +// cannot be running in parallel. + +// Callback handler. +typedef void (*crazy_callback_handler_t)(void* opaque); + +// A small structure used to model a callback provided by the crazy linker. +// Use crazy_callback_run() to run the callback. +typedef struct { + crazy_callback_handler_t handler; + void* opaque; +} crazy_callback_t; + +// Function to call to enable a callback into the crazy linker when delayed +// operations are enabled (see crazy_context_set_callback_poster). A call +// to crazy_callback_poster_t returns true if the callback was successfully +// set up and will occur later, false if callback could not be set up (and +// so will never occur). +typedef bool (*crazy_callback_poster_t)( + crazy_callback_t* callback, void* poster_opaque); + +// Enable delayed operation, by passing the address of a +// |crazy_callback_poster_t| function, that will be called during library +// loading to let the user record callbacks for delayed operations. +// Callers must copy the |crazy_callback_t| passed to |poster|. +// |poster_opaque| is an opaque value for client code use, passed back +// on each call to |poster|. +// |poster| can be NULL to disable the feature. +void crazy_context_set_callback_poster(crazy_context_t* context, + crazy_callback_poster_t poster, + void* poster_opaque); + +// Return the address of the function that the crazy linker can use to +// request callbacks, and the |poster_opaque| passed back on each call +// to |poster|. |poster| is NULL if the feature is disabled. +void crazy_context_get_callback_poster(crazy_context_t* context, + crazy_callback_poster_t* poster, + void** poster_opaque); + +// Run a given |callback| in the current thread. Must only be called once +// per callback. +void crazy_callback_run(crazy_callback_t* callback); + // Opaque handle to a library as seen/loaded by the crazy linker. typedef struct crazy_library_t crazy_library_t; @@ -266,6 +324,10 @@ crazy_status_t crazy_library_find_from_address( // zero, the library be unloaded from the process. void crazy_library_close(crazy_library_t* library) _CRAZY_PUBLIC; +// Close a library, with associated context to support delayed operations. +void crazy_library_close_with_context(crazy_library_t* library, + crazy_context_t* context) _CRAZY_PUBLIC; + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/sources/android/crazy_linker/src/crazy_linker_api.cpp b/sources/android/crazy_linker/src/crazy_linker_api.cpp index b360e413e..17cd85fa9 100644 --- a/sources/android/crazy_linker/src/crazy_linker_api.cpp +++ b/sources/android/crazy_linker/src/crazy_linker_api.cpp @@ -37,7 +37,9 @@ struct crazy_context_t { error(), search_paths(), java_vm(NULL), - minimum_jni_version(0) { + minimum_jni_version(0), + callback_poster(NULL), + callback_poster_opaque(NULL) { ResetSearchPaths(); } @@ -49,6 +51,8 @@ struct crazy_context_t { SearchPathList search_paths; void* java_vm; int minimum_jni_version; + crazy_callback_poster_t callback_poster; + void* callback_poster_opaque; }; void crazy_context_t::ResetSearchPaths() { @@ -133,12 +137,68 @@ void crazy_context_get_java_vm(crazy_context_t* context, *minimum_jni_version = context->minimum_jni_version; } +void crazy_context_set_callback_poster(crazy_context_t* context, + crazy_callback_poster_t poster, + void* poster_opaque) { + context->callback_poster = poster; + context->callback_poster_opaque = poster_opaque; +} + +void crazy_context_get_callback_poster(crazy_context_t* context, + crazy_callback_poster_t* poster, + void** poster_opaque) { + *poster = context->callback_poster; + *poster_opaque = context->callback_poster_opaque; +} + +void crazy_callback_run(crazy_callback_t* callback) { + (*callback->handler)(callback->opaque); +} + void crazy_context_destroy(crazy_context_t* context) { delete context; } +// Scoped delayed execution, removes RDebug callbacks on scope exit. No-op +// if callback is NULL. +class ScopedDelayedCallbackPoster { + public: + ScopedDelayedCallbackPoster(crazy_context_t* context) { + if (context && context->callback_poster) { + crazy::Globals::GetRDebug()->SetDelayedCallbackPoster(&PostFromContext, + context); + set_delayed_callback_poster_ = true; + } else { + set_delayed_callback_poster_ = false; + } + } + + ~ScopedDelayedCallbackPoster() { + if (set_delayed_callback_poster_) + crazy::Globals::GetRDebug()->SetDelayedCallbackPoster(NULL, NULL); + } + + private: + // Wrap callback hander and opaque into a call to a crazy_context_poster_t. + static bool PostFromContext(void* crazy_context, + crazy_callback_handler_t handler, + void* opaque) { + crazy_context_t* context = static_cast<crazy_context_t*>(crazy_context); + crazy_callback_t callback; + callback.handler = handler; + callback.opaque = opaque; + return context->callback_poster(&callback, + context->callback_poster_opaque); + } + + // True if the context offered a callback_poster, otherwise false. + bool set_delayed_callback_poster_; +}; + crazy_status_t crazy_library_open(crazy_library_t** library, const char* lib_name, crazy_context_t* context) { + ScopedDelayedCallbackPoster poster(context); ScopedGlobalLock lock; + LibraryView* wrap = crazy::Globals::GetLibraries()->LoadLibrary(lib_name, RTLD_NOW, @@ -146,6 +206,7 @@ crazy_status_t crazy_library_open(crazy_library_t** library, context->file_offset, &context->search_paths, &context->error); + if (!wrap) return CRAZY_STATUS_FAILURE; @@ -280,7 +341,13 @@ crazy_status_t crazy_library_find_from_address(void* address, } void crazy_library_close(crazy_library_t* library) { + crazy_library_close_with_context(library, NULL); +} + +void crazy_library_close_with_context(crazy_library_t* library, + crazy_context_t* context) { if (library) { + ScopedDelayedCallbackPoster poster(context); ScopedGlobalLock lock; LibraryView* wrap = reinterpret_cast<LibraryView*>(library); diff --git a/sources/android/crazy_linker/src/crazy_linker_rdebug.cpp b/sources/android/crazy_linker/src/crazy_linker_rdebug.cpp index 1461bc98a..c92974fa7 100644 --- a/sources/android/crazy_linker/src/crazy_linker_rdebug.cpp +++ b/sources/android/crazy_linker/src/crazy_linker_rdebug.cpp @@ -261,8 +261,63 @@ bool RDebug::Init() { return false; } -void RDebug::AddEntry(link_map_t* entry) { - LOG("%s\n", __FUNCTION__); +namespace { + +// 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) + : handler_(handler), rdebug_(rdebug), entry_(entry) { } + + static void Run(void* opaque); + + private: + rdebug_callback_handler_t handler_; + RDebug* rdebug_; + link_map_t* entry_; +}; + +// 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_); + 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) { + if (!post_for_later_execution_) { + LOG("%s: Deferred execution disabled\n", __FUNCTION__); + return false; + } + + RDebugRunnable* runnable = new RDebugRunnable(handler, this, entry); + 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); + return true; +} + +void RDebug::AddEntryImpl(link_map_t* entry) { + LOG("%s: Adding: %s\n", __FUNCTION__, entry->l_name); if (!init_) Init(); @@ -331,7 +386,8 @@ void RDebug::AddEntry(link_map_t* entry) { r_debug_->r_brk(); } -void RDebug::DelEntry(link_map_t* entry) { +void RDebug::DelEntryImpl(link_map_t* entry) { + LOG("%s: Deleting: %s\n", __FUNCTION__, entry->l_name); if (!r_debug_) return; diff --git a/sources/android/crazy_linker/src/crazy_linker_rdebug.h b/sources/android/crazy_linker/src/crazy_linker_rdebug.h index 520c08953..886e021b1 100644 --- a/sources/android/crazy_linker/src/crazy_linker_rdebug.h +++ b/sources/android/crazy_linker/src/crazy_linker_rdebug.h @@ -112,6 +112,16 @@ // it for GDB), it only uses 'solist / sonext' to actually perform its // operations. That's ok if our custom linker completely wraps and // re-implements these. +// +// The system linker expects to be the only item modifying the 'r_map' +// list, and as such it may set the pages that contain the list readonly +// outside of its own modifications. In threaded environments where the +// system linker and the crazy linker are operating simultaneously on +// different threads this may be a problem; we need these pages to be +// writable when we have to update the list. We cannot track the system +// linker's actions, so to avoid clashing with it we may need to try and +// move 'r_map' updates to a different thread, to serialize them with +// other system linker activity. namespace crazy { struct link_map_t { @@ -137,16 +147,42 @@ struct r_debug { uintptr_t r_ldbase; }; +// A callback poster is a function that can be called to request a later +// callback. Poster arguments are: an opaque pointer to the caller's +// context, a pointer to a function with a single void* argument that will +// handle the callback, and the opaque void* argument value to send with +// the callback. +typedef void (*crazy_callback_handler_t)(void* opaque); +typedef bool (*rdebug_callback_poster_t)(void* context, + crazy_callback_handler_t, + void* opaque); + +// A callback handler is a static function, either AddEntryInternal() or +// DelEntryInternal(). It calls the appropriate r_map update member +// function, AddEntryImpl() or DelEntryImpl(). +class RDebug; +typedef void (*rdebug_callback_handler_t)(RDebug*, link_map_t*); + class RDebug { public: - RDebug() : r_debug_(NULL), init_(false), readonly_entries_(false) {} + RDebug() : r_debug_(NULL), init_(false), + readonly_entries_(false), post_for_later_execution_(NULL), + post_for_later_execution_context_(NULL) {} ~RDebug() {} - // Add a new entry to the list. - void AddEntry(link_map_t* entry); + // Add entries to and remove entries from the list. If post for later + // execution is enabled, schedule callbacks and return. Otherwise + // action immediately. + void AddEntry(link_map_t* entry) { RunOrDelay(&AddEntryInternal, entry); } + void DelEntry(link_map_t* entry) { RunOrDelay(&DelEntryInternal, entry); } - // Remove an entry from the list. - void DelEntry(link_map_t* entry); + // Assign the function used to request a callback from another thread. + // The context here is opaque, but is the API's crazy_context. + void SetDelayedCallbackPoster(rdebug_callback_poster_t poster, + void* context) { + post_for_later_execution_ = poster; + post_for_later_execution_context_ = context; + } r_debug* GetAddress() { return r_debug_; } @@ -155,12 +191,39 @@ class RDebug { // though there is no symbol for it. Returns true on success. bool Init(); + // Support for scheduling list manipulation through callbacks. + // AddEntry() and DelEntry() pass the addresses of static functions to + // to RunOrDelay(). This requests a callback if later execution + // is enabled, otherwise it runs immediately on the current thread. + // AddEntryImpl() and DelEntryImpl() are the member functions called + // by the static ones to do the actual work. + void AddEntryImpl(link_map_t* entry); + void DelEntryImpl(link_map_t* entry); + static void AddEntryInternal(RDebug* rdebug, link_map_t* entry) { + rdebug->AddEntryImpl(entry); + } + static void DelEntryInternal(RDebug* rdebug, link_map_t* entry) { + rdebug->DelEntryImpl(entry); + } + + // Post handler for delayed execution. Return true if delayed execution + // is enabled and posting succeeded. + bool PostCallback(rdebug_callback_handler_t handler, link_map_t* entry); + + // Run handler as a callback if enabled, otherwise immediately. + void RunOrDelay(rdebug_callback_handler_t handler, link_map_t* entry) { + if (!PostCallback(handler, entry)) + (*handler)(this, entry); + } + RDebug(const RDebug&); RDebug& operator=(const RDebug&); r_debug* r_debug_; bool init_; bool readonly_entries_; + rdebug_callback_poster_t post_for_later_execution_; + void* post_for_later_execution_context_; }; } // namespace crazy diff --git a/sources/android/crazy_linker/tests/Android.mk b/sources/android/crazy_linker/tests/Android.mk index 7dbc758a8..7ad600019 100644 --- a/sources/android/crazy_linker/tests/Android.mk +++ b/sources/android/crazy_linker/tests/Android.mk @@ -67,6 +67,12 @@ LOCAL_STATIC_LIBRARIES := crazy_linker include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) +LOCAL_MODULE := test_load_library_callbacks +LOCAL_SRC_FILES := test_load_library_callbacks.cpp +LOCAL_STATIC_LIBRARIES := crazy_linker +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) LOCAL_MODULE := test_dl_wrappers LOCAL_SRC_FILES := test_dl_wrappers.cpp LOCAL_STATIC_LIBRARIES := crazy_linker diff --git a/sources/android/crazy_linker/tests/test_load_library_callbacks.cpp b/sources/android/crazy_linker/tests/test_load_library_callbacks.cpp new file mode 100644 index 000000000..f6fc50091 --- /dev/null +++ b/sources/android/crazy_linker/tests/test_load_library_callbacks.cpp @@ -0,0 +1,91 @@ +// Copyright (c) 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. + +// A crazy linker test to test callbacks for delayed execution. + +#include <stdio.h> +#include <crazy_linker.h> + +#include "test_util.h" + +typedef void (*FunctionPtr)(); + +namespace { + +bool PostCallback(crazy_callback_t* callback, void* poster_opaque) { + printf("Post callback, poster_opaque %p, handler %p, opaque %p\n", + poster_opaque, + callback->handler, + callback->opaque); + + // Copy callback to the address given in poster_opaque. + crazy_callback_t* request = + reinterpret_cast<crazy_callback_t*>(poster_opaque); + *request = *callback; + + return true; +} + +void CheckAndRunCallback(crazy_callback_t* callback) { + printf("Run callback, handler %p, opaque %p\n", + callback->handler, + callback->opaque); + + if (!callback->handler) { + Panic("Post for delayed execution not invoked\n"); + } + + // Run the callback, then clear it for re-use. + crazy_callback_run(callback); + memset(callback, 0, sizeof(*callback)); +} + +} // namespace + +int main() { + crazy_context_t* context = crazy_context_create(); + crazy_library_t* library; + + // DEBUG + crazy_context_set_load_address(context, 0x20000000); + + // Create a new callback, initialized to NULLs. + crazy_callback_t callback = {NULL, NULL}; + + // Set a callback poster that copies its callback to &callback. + crazy_context_set_callback_poster(context, &PostCallback, &callback); + + crazy_callback_poster_t poster; + void* poster_opaque; + + // Check that the API returns the values we set. + crazy_context_get_callback_poster(context, &poster, &poster_opaque); + if (poster != &PostCallback || poster_opaque != &callback) { + Panic("Get callback poster error\n"); + } + + // Load libfoo.so. + if (!crazy_library_open(&library, "libfoo.so", context)) { + Panic("Could not open library: %s\n", crazy_context_get_error(context)); + } + CheckAndRunCallback(&callback); + + // Find the "Foo" symbol. + FunctionPtr foo_func; + if (!crazy_library_find_symbol( + library, "Foo", reinterpret_cast<void**>(&foo_func))) { + Panic("Could not find 'Foo' in libfoo.so\n"); + } + + // Call it. + (*foo_func)(); + + // Close the library. + crazy_library_close_with_context(library, context); + CheckAndRunCallback(&callback); + + crazy_context_destroy(context); + + return 0; +} |