MediaUtils: Use delayed / lazy library loading
Postpone loading libutilscallstack until needed
to save memory.
Test: atest library_tests
Test: adb shell 'showmap $(pgrep mediametrics)'
Bug: 227536784
Change-Id: I3d4c9e9b6d854b013185e18fc20d4bec320df14e
diff --git a/media/utils/Android.bp b/media/utils/Android.bp
index 1756e47..3b079c6 100644
--- a/media/utils/Android.bp
+++ b/media/utils/Android.bp
@@ -28,7 +28,9 @@
"AImageReaderUtils.cpp",
"BatteryNotifier.cpp",
"ISchedulingPolicyService.cpp",
+ "Library.cpp",
"LimitProcessMemory.cpp",
+ "MediaUtilsDelayed.cpp",
"MemoryLeakTrackUtil.cpp",
"MethodStatistics.cpp",
"ProcessInfo.cpp",
@@ -49,7 +51,6 @@
"libcutils",
"liblog",
"libutils",
- "libutilscallstack",
"libhidlbase",
"libpermission",
"android.hardware.graphics.bufferqueue@1.0",
@@ -77,6 +78,10 @@
"libpermission",
],
+ required: [
+ "libmediautils_delayed", // lazy loaded
+ ],
+
include_dirs: [
// For DEBUGGER_SIGNAL
"system/core/debuggerd/include",
@@ -86,6 +91,23 @@
}
cc_library {
+ name: "libmediautils_delayed", // match with MEDIAUTILS_DELAYED_LIBRARY_NAME
+ srcs: [
+ "MediaUtilsDelayedLibrary.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libutilscallstack",
+ ],
+}
+
+cc_library {
name: "libmediautils_vendor",
vendor_available: true, // required for platform/hardware/interfaces
srcs: [
diff --git a/media/utils/Library.cpp b/media/utils/Library.cpp
new file mode 100644
index 0000000..c1e22bf
--- /dev/null
+++ b/media/utils/Library.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "Library"
+#include <utils/Log.h>
+#include <mediautils/Library.h>
+
+namespace {
+
+std::string dlerrorIfPresent() {
+ const char *dlerr = dlerror();
+ if (dlerr == nullptr) return "dlerror: none";
+ return std::string("dlerror: '").append(dlerr).append("'");
+}
+
+}
+namespace android::mediautils {
+
+std::shared_ptr<void> loadLibrary(const char *libraryName, int flags) {
+ std::shared_ptr<void> library{
+ dlopen(libraryName, flags),
+ [](void *lib) {
+ if (lib != nullptr) {
+ const int ret = dlclose(lib);
+ ALOGW_IF(ret !=0, "%s: dlclose(%p) == %d, %s",
+ __func__, lib, ret, dlerrorIfPresent().c_str());
+ }
+ }
+ };
+
+ if (!library) {
+ ALOGW("%s: cannot load libraryName %s, %s",
+ __func__, libraryName, dlerrorIfPresent().c_str());
+ return {};
+ }
+ return library;
+}
+
+std::shared_ptr<void> getUntypedObjectFromLibrary(
+ const char *objectName, const std::shared_ptr<void>& library) {
+ if (!library) {
+ ALOGW("%s: null library, cannot load objectName %s", __func__, objectName);
+ return {};
+ }
+ void *ptr = dlsym(library.get(), objectName);
+ if (ptr == nullptr) {
+ ALOGW("%s: cannot load objectName %s, %s",
+ __func__, objectName, dlerrorIfPresent().c_str());
+ return {};
+ }
+
+ // Note: we use the "aliasing" constructor of the std:shared_ptr.
+ //
+ // https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
+ //
+ return { library, ptr }; // returns shared_ptr to ptr, but ref counted on library.
+}
+
+} // namespace android::mediautils
diff --git a/media/utils/MediaUtilsDelayed.cpp b/media/utils/MediaUtilsDelayed.cpp
new file mode 100644
index 0000000..c6c092d
--- /dev/null
+++ b/media/utils/MediaUtilsDelayed.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 <mediautils/Library.h>
+#include <mediautils/MediaUtilsDelayed.h>
+#include "MediaUtilsDelayedLibrary.h"
+
+#define LOG_TAG "MediaUtilsDelayed"
+#include <utils/Log.h>
+#include <memory>
+
+namespace android::mediautils {
+
+namespace {
+// Specific implementation details for MediaUtils Delayed Library.
+
+// The following use static Meyer's singleton caches instead of letting
+// refcounted management as provided above. This is for speed.
+std::shared_ptr<void> getDelayedLibrary() {
+ static std::shared_ptr<void> library = loadLibrary(MEDIAUTILS_DELAYED_LIBRARY_NAME);
+ return library;
+}
+
+// Get the delayed dispatch table. This is refcounted and keeps the underlying library alive.
+std::shared_ptr<delayed_library::DelayedDispatchTable> getDelayedDispatchTable() {
+ static auto delayedDispatchTable =
+ getObjectFromLibrary<delayed_library::DelayedDispatchTable>(
+ MEDIAUTILS_DELAYED_DISPATCH_TABLE_SYMBOL_NAME, getDelayedLibrary());
+ return delayedDispatchTable;
+}
+
+} // namespace
+
+// Public implementations of methods here.
+
+std::string getCallStackStringForTid(pid_t tid) {
+ auto delayedDispatchTable = getDelayedDispatchTable();
+ if (!delayedDispatchTable) return {}; // on failure, return empty string
+ return delayedDispatchTable->getCallStackStringForTid(tid);
+}
+
+} // android::mediautils
diff --git a/media/utils/MediaUtilsDelayedLibrary.cpp b/media/utils/MediaUtilsDelayedLibrary.cpp
new file mode 100644
index 0000000..9054c1a
--- /dev/null
+++ b/media/utils/MediaUtilsDelayedLibrary.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 "MediaUtilsDelayedLibrary.h"
+#include <utils/CallStack.h>
+
+// Methods that are dynamically linked.
+namespace {
+
+std::string getCallStackStringForTid(pid_t tid) {
+ android::CallStack cs{};
+ cs.update(0 /* ignoreDepth */, tid);
+ return cs.toString().c_str();
+}
+
+} // namespace
+
+// leave global, this is picked up from dynamic linking
+android::mediautils::delayed_library::DelayedDispatchTable gDelayedDispatchTable {
+ getCallStackStringForTid,
+};
diff --git a/media/utils/MediaUtilsDelayedLibrary.h b/media/utils/MediaUtilsDelayedLibrary.h
new file mode 100644
index 0000000..3d72a3a
--- /dev/null
+++ b/media/utils/MediaUtilsDelayedLibrary.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <unistd.h>
+
+// This should not be directly included by clients.
+// Use MediaUtilsDelayed.h instead.
+
+namespace android::mediautils::delayed_library {
+
+// Use a dispatch table to return methods from the delayed library
+struct DelayedDispatchTable {
+ std::string (*getCallStackStringForTid)(pid_t tid);
+};
+
+// Match with Android.bp and MediaUtilsDelayed.cpp.
+#define MEDIAUTILS_DELAYED_LIBRARY_NAME "libmediautils_delayed.so"
+
+// Match with MediaUtilsDelayed.cpp and MediaUtilsDelayedLibrary.cpp
+#define MEDIAUTILS_DELAYED_DISPATCH_TABLE_SYMBOL_NAME "gDelayedDispatchTable"
+
+} // namespace android::mediautils::delayed_library
diff --git a/media/utils/TimerThread.cpp b/media/utils/TimerThread.cpp
index 3556d7d..6de6b13 100644
--- a/media/utils/TimerThread.cpp
+++ b/media/utils/TimerThread.cpp
@@ -21,8 +21,8 @@
#include <unistd.h>
#include <vector>
+#include <mediautils/MediaUtilsDelayed.h>
#include <mediautils/TimerThread.h>
-#include <utils/CallStack.h>
#include <utils/ThreadDefs.h>
namespace android::mediautils {
@@ -73,13 +73,13 @@
if (analysis.timeoutTid != -1) {
timeoutStack = std::string("\ntimeout(")
.append(std::to_string(analysis.timeoutTid)).append(") callstack [\n")
- .append(tidCallStackString(analysis.timeoutTid)).append("]");
+ .append(getCallStackStringForTid(analysis.timeoutTid)).append("]");
}
std::string blockedStack;
if (analysis.HALBlockedTid != -1) {
blockedStack = std::string("\nblocked(")
.append(std::to_string(analysis.HALBlockedTid)).append(") callstack [\n")
- .append(tidCallStackString(analysis.HALBlockedTid)).append("]");
+ .append(getCallStackStringForTid(analysis.HALBlockedTid)).append("]");
}
return std::string("now ")
@@ -213,13 +213,6 @@
return requestsToString(timeoutRequests);
}
-/* static */
-std::string TimerThread::tidCallStackString(pid_t tid) {
- CallStack cs{};
- cs.update(0 /* ignoreDepth */, tid);
- return cs.toString().c_str();
-}
-
std::string TimerThread::Request::toString() const {
const auto scheduledString = formatTime(scheduled);
const auto deadlineString = formatTime(deadline);
diff --git a/media/utils/include/mediautils/Library.h b/media/utils/include/mediautils/Library.h
new file mode 100644
index 0000000..19cfc11
--- /dev/null
+++ b/media/utils/include/mediautils/Library.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <dlfcn.h>
+#include <string>
+#include <unistd.h>
+
+namespace android::mediautils {
+
+/**
+ * Returns a shared pointer to the library instance.
+ *
+ * When the last reference to the library is removed, the library will be dlclose().
+ *
+ * Notes:
+ * 1) The Android bionic linker always uses RTLD_GLOBAL for executable linking
+ * which provides the symbols for other subsequent libraries.
+ *
+ * 2) RTLD_GLOBAL like RTLD_NODELETE disables unloading of the library
+ * when the reference count drops to zero.
+ *
+ * 3) RTLD_LOCAL is the default in the absence of RTLD_GLOBAL.
+ * RTLD_LOCAL may be ignored in some situations, for example:
+ * https://stackoverflow.com/questions/56808889/static-objects-destructed-before-dlclose
+ *
+ * 4) We default to use RTLD_LAZY to delay symbol relocations until needed.
+ * This flag may be ignored by Android. RTLD_LAZY may allow
+ * unresolved symbols if not accessed, or symbols added later with another library
+ * loaded with RTLD_GLOBAL. See RTLD_NOW for comparison.
+ *
+ * 5) Avoid both staticly loading and dynamically loading the same library.
+ * This is known to cause double free issues as library symbols may map to
+ * the same location. RTLD_DEEPBIND does not appear supported as of T.
+ * https://stackoverflow.com/questions/34073051/when-we-are-supposed-to-use-rtld-deepbind
+ * https://stackoverflow.com/questions/31209693/static-library-linked-two-times
+ *
+ * Details on Android linker and debugging here:
+ * See: adb shell setprop debug.ld.all dlerror,dlopen,dlsym
+ * See: https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md
+ *
+ * Some other relevant info:
+ * See: Soong double_loadable:true go/double_loadable
+ * See: https://en.wikipedia.org/wiki/One_Definition_Rule#Summary
+ *
+ * TODO(b/228093151): Consider moving to platform/system.
+ *
+ * \param libraryName
+ * \param flags one of the dlopen RTLD_* flags. https://linux.die.net/man/3/dlopen
+ * \return shared_ptr to the library. This will be nullptr if it isn't found.
+ */
+std::shared_ptr<void> loadLibrary(const char *libraryName, int flags = RTLD_LAZY);
+
+/**
+ * Returns a shared pointer to an object in the library
+ *
+ * The object will be a global variable or method in the library.
+ * The object reference counting is aliased to the library shared ptr.
+ *
+ * Note: If any internals of the shared library are exposed, for example by
+ * a method returning a pointer to library globals,
+ * or returning an object whose class definition is from the library,
+ * then the shared_ptr must be kept alive while such references to
+ * library internals exist to prevent library unloading.
+ *
+ * See usage of RTLD_NODELETE as a flag to prevent unloading.
+ *
+ * \param objectName of the library object.
+ * \param library a shared pointer to the library returned by loadLibrary().
+ * \return shared_ptr to the object, but whose refcount is
+ * aliased to the library shared ptr.
+ */
+std::shared_ptr<void> getUntypedObjectFromLibrary(
+ const char *objectName, const std::shared_ptr<void>& library);
+
+/**
+ * Returns a shared pointer to an object in the library
+ *
+ * This is the template typed version of getUntypedObjectFromLibrary().
+ *
+ * \param objectName of the library object.
+ * \param library a shared pointer to the library
+ * \return shared_ptr to the object, but whose refcount is
+ * aliased to the library shared ptr.
+ */
+template <typename T>
+std::shared_ptr<T> getObjectFromLibrary(
+ const char *objectName, const std::shared_ptr<void>& library) {
+ return std::static_pointer_cast<T>(getUntypedObjectFromLibrary(objectName, library));
+}
+
+} // android::mediautils
diff --git a/media/utils/include/mediautils/MediaUtilsDelayed.h b/media/utils/include/mediautils/MediaUtilsDelayed.h
new file mode 100644
index 0000000..1583e60
--- /dev/null
+++ b/media/utils/include/mediautils/MediaUtilsDelayed.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <unistd.h>
+
+namespace android::mediautils {
+
+// These methods use lazy library loading.
+
+/**
+ * Returns a string callstack from the thread id tid.
+ */
+std::string getCallStackStringForTid(pid_t tid);
+
+} // android::mediautils
diff --git a/media/utils/include/mediautils/TimerThread.h b/media/utils/include/mediautils/TimerThread.h
index b69e02c..ffee602 100644
--- a/media/utils/include/mediautils/TimerThread.h
+++ b/media/utils/include/mediautils/TimerThread.h
@@ -125,11 +125,6 @@
return s;
}
- /**
- * Returns callstack of tid as a string.
- */
- static std::string tidCallStackString(pid_t tid);
-
private:
// To minimize movement of data, we pass around shared_ptrs to Requests.
// These are allocated and deallocated outside of the lock.
diff --git a/media/utils/tests/Android.bp b/media/utils/tests/Android.bp
index 5498ac5..30c10b7 100644
--- a/media/utils/tests/Android.bp
+++ b/media/utils/tests/Android.bp
@@ -7,6 +7,62 @@
default_applicable_licenses: ["frameworks_av_license"],
}
+cc_test_library {
+ name: "libsharedtest",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+
+ sanitize:{
+ address: true,
+ cfi: true,
+ integer_overflow: true,
+ memtag_heap: true,
+ },
+
+ shared_libs: [
+ "liblog",
+ ],
+
+ srcs: [
+ "sharedtest.cpp",
+ ]
+}
+
+cc_test {
+ name: "library_tests",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+
+ sanitize:{
+ address: true,
+ cfi: true,
+ integer_overflow: true,
+ memtag_heap: true,
+ },
+
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libmediautils",
+ "libutils",
+ ],
+
+ data_libs: [
+ "libsharedtest",
+ ],
+
+ srcs: [
+ "library_tests.cpp",
+ ],
+}
+
cc_test {
name: "media_synchronization_tests",
diff --git a/media/utils/tests/library_tests.cpp b/media/utils/tests/library_tests.cpp
new file mode 100644
index 0000000..c5c500c
--- /dev/null
+++ b/media/utils/tests/library_tests.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "library_tests"
+#include <utils/Log.h>
+
+#include <mediautils/Library.h>
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+
+using namespace android::mediautils;
+
+namespace {
+
+static int32_t here = 0; // accessed on same thread.
+
+TEST(library_tests, basic) {
+ std::string path = android::base::GetExecutableDirectory() + "/libsharedtest.so";
+ // The flags to loadLibrary should not include RTLD_GLOBAL or RTLD_NODELETE
+ // which prevent unloading.
+ std::shared_ptr<void> library = loadLibrary(path.c_str(), RTLD_LAZY);
+ ASSERT_TRUE(library);
+ ASSERT_EQ(1, library.use_count());
+
+ std::shared_ptr<int32_t*> ptr = getObjectFromLibrary<int32_t*>("gPtr", library);
+ ASSERT_TRUE(ptr);
+ ASSERT_EQ(2, library.use_count());
+
+ ASSERT_EQ(nullptr, *ptr); // original contents are nullptr.
+
+ // There is a static object destructor in libsharedtest.so that will set the
+ // contents of the integer pointer (if non-null) to 1 when called.
+ // This is used to detect that the library is unloaded.
+ *ptr = &here;
+
+ ptr.reset(); // Note: this shared pointer uses library's refcount.
+ ASSERT_EQ(1, library.use_count()); // Verify library's refcount goes down by 1.
+ ASSERT_EQ(0, here); // the shared library's object destructor hasn't been called.
+
+ // use weak_ptr to investigate whether the library is gone.
+ std::weak_ptr<void> wlibrary = library;
+ ASSERT_EQ(1, wlibrary.use_count());
+ library.reset();
+
+ // we should have released the last reference.
+ ASSERT_EQ(0, wlibrary.use_count());
+
+ // The library should unload and the global object destroyed.
+ // Note on Android, specifying RTLD_GLOBAL or RTLD_NODELETE in the flags
+ // will prevent unloading libraries.
+ ASSERT_EQ(1, here);
+}
+
+TEST(library_tests, sad_library) {
+ std::string path = android::base::GetExecutableDirectory()
+ + "/something_random_library_that_doesn't_exit.so";
+
+ std::shared_ptr<void> library = loadLibrary(path.c_str(), RTLD_LAZY);
+ // We shouldn't crash on an invalid library path, just return an empty shared pointer.
+ // Check the logcat for any error details.
+ ASSERT_FALSE(library);
+}
+
+} // namespace
diff --git a/media/utils/tests/sharedtest.cpp b/media/utils/tests/sharedtest.cpp
new file mode 100644
index 0000000..3888874
--- /dev/null
+++ b/media/utils/tests/sharedtest.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 <cstdint>
+#define LOG_TAG "sharedtest"
+#include <utils/Log.h>
+
+// Test library which is dynamicly loaded by library_tests.
+
+// Static variable construction.
+// Calls A constructor on library load, A destructor on library unload.
+
+int32_t *gPtr = nullptr; // this pointer is filled with the location to set memory
+ // when ~A() is called.
+ // we cannot use anything internal to this file as the
+ // data segment may no longer exist after unloading the library.
+struct A {
+ A() {
+ ALOGD("%s: gPtr:%p", __func__, gPtr);
+ }
+
+ ~A() {
+ ALOGD("%s: gPtr:%p", __func__, gPtr);
+ if (gPtr != nullptr) {
+ *gPtr = 1;
+ }
+ }
+} gA;
+
+// __attribute__((constructor)) methods occur before any static variable construction.
+// Libraries that use __attribute__((constructor)) should not rely on global constructors
+// before method call because they will not be initialized before use.
+// See heapprofd_client_api.
+// NOTE: is this right? Shouldn't it occur after construction?
+ __attribute__((constructor))
+void onConstruction() {
+ ALOGD("%s: in progress", __func__); // for logcat analysis
+}
+
+// __attribute__((destructor)) methods occur before any static variable destruction.
+ __attribute__((destructor))
+void onDestruction() {
+ ALOGD("%s: in progress", __func__); // for logcat analysis
+}