aboutsummaryrefslogtreecommitdiff
path: root/sources/android/crazy_linker
diff options
context:
space:
mode:
authorSimon Baldwin <simonb@google.com>2014-04-30 17:10:09 +0100
committerSimon Baldwin <simonb@google.com>2014-04-30 17:10:09 +0100
commit6788f450cf7ef93fe2fe0c20ec4b44a38e049bc7 (patch)
tree3818a8ece7116d686f3da95d2301f6e108158b41 /sources/android/crazy_linker
parent0d76530947278a8c3d623811fc1ce42871f2920d (diff)
downloadndk-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')
-rw-r--r--sources/android/crazy_linker/include/crazy_linker.h62
-rw-r--r--sources/android/crazy_linker/src/crazy_linker_api.cpp69
-rw-r--r--sources/android/crazy_linker/src/crazy_linker_rdebug.cpp62
-rw-r--r--sources/android/crazy_linker/src/crazy_linker_rdebug.h73
-rw-r--r--sources/android/crazy_linker/tests/Android.mk6
-rw-r--r--sources/android/crazy_linker/tests/test_load_library_callbacks.cpp91
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;
+}