diff --git a/libstats/OWNERS b/libstats/OWNERS index 7855774a7..d39167947 100644 --- a/libstats/OWNERS +++ b/libstats/OWNERS @@ -1,6 +1,7 @@ -joeo@google.com +jeffreyhuang@google.com +jtnguyen@google.com muhammadq@google.com -ruchirr@google.com +sharaienko@google.com singhtejinder@google.com tsaichristine@google.com yaochen@google.com diff --git a/libstats/socket_lazy/Android.bp b/libstats/socket_lazy/Android.bp new file mode 100644 index 000000000..ad6b4e003 --- /dev/null +++ b/libstats/socket_lazy/Android.bp @@ -0,0 +1,40 @@ +// Lazy loading version of libstatssocket that can be used by code +// that is running before the statsd APEX is mounted and +// libstatssocket.so is available. +cc_library_static { + name: "libstatssocket_lazy", + header_libs: [ + "libstatssocket_headers", + ], + export_header_lib_headers: [ + "libstatssocket_headers", + ], + apex_available: ["//apex_available:platform"], + srcs: ["libstatssocket_lazy.cpp"], +} + +cc_test { + name: "libstatssocket_lazy_test", + srcs: [ + "tests/libstatssocket_lazy_test.cpp", + ], + static_libs: ["libstatssocket_lazy"], + shared_libs: ["liblog"], + cflags: [ + "-Wall", + "-Werror", + ], + test_suites: ["device-tests", "mts-statsd"], + test_config: "libstatssocket_lazy_test.xml", + // TODO(b/153588990): Remove when the build system properly separates. + // 32bit and 64bit architectures. + compile_multilib: "both", + multilib: { + lib64: { + suffix: "64", + }, + lib32: { + suffix: "32", + }, + }, +} \ No newline at end of file diff --git a/libstats/socket_lazy/TEST_MAPPING b/libstats/socket_lazy/TEST_MAPPING new file mode 100644 index 000000000..13afc0087 --- /dev/null +++ b/libstats/socket_lazy/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit" : [ + { + "name" : "libstatssocket_lazy_test" + } + ] +} \ No newline at end of file diff --git a/libstats/socket_lazy/libstatssocket_lazy.cpp b/libstats/socket_lazy/libstatssocket_lazy.cpp new file mode 100644 index 000000000..dd93eebf0 --- /dev/null +++ b/libstats/socket_lazy/libstatssocket_lazy.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2021 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 "libstatssocket_lazy.h" + +#include + +#include +#include + +#include "log/log.h" + +#include "stats_event.h" +#include "stats_socket.h" + +// This file provides a lazy interface to libstatssocket.so to address early boot dependencies. +// Specifically bootanimation, surfaceflinger, and lmkd run before the statsd APEX is loaded and +// libstatssocket.so is in the statsd APEX. + +// Method pointers to libstatssocket methods are held in an array which simplifies checking +// all pointers are initialized. +enum MethodIndex { + // Stats Event APIs in stats_event.h. + k_AStatsEvent_obtain, + k_AStatsEvent_build, + k_AStatsEvent_write, + k_AStatsEvent_release, + k_AStatsEvent_setAtomId, + k_AStatsEvent_writeInt32, + k_AStatsEvent_writeInt64, + k_AStatsEvent_writeFloat, + k_AStatsEvent_writeBool, + k_AStatsEvent_writeByteArray, + k_AStatsEvent_writeString, + k_AStatsEvent_writeAttributionChain, + k_AStatsEvent_addBoolAnnotation, + k_AStatsEvent_addInt32Annotation, + + // Stats Socket APIs in stats_socket.h. + k_AStatsSocket_close, + + // Marker for count of methods + k_MethodCount +}; + +// Table of methods pointers in libstatssocket APIs. +static void* g_Methods[k_MethodCount]; + +// +// Libstatssocket lazy loading. +// + +static atomic_bool gPreventLibstatssocketLoading = false; // Allows tests to block loading. + +void PreventLibstatssocketLazyLoadingForTests() { + gPreventLibstatssocketLoading.store(true); +} + +static void* LoadLibstatssocket(int dlopen_flags) { + if (gPreventLibstatssocketLoading.load()) { + return nullptr; + } + return dlopen("libstatssocket.so", dlopen_flags); +} + +// +// Initialization and symbol binding. + +static void BindSymbol(void* handle, const char* name, enum MethodIndex index) { + void* symbol = dlsym(handle, name); + LOG_ALWAYS_FATAL_IF(symbol == nullptr, "Failed to find symbol '%s' in libstatssocket.so: %s", + name, dlerror()); + g_Methods[index] = symbol; +} + +static void InitializeOnce() { + void* handle = LoadLibstatssocket(RTLD_NOW); + LOG_ALWAYS_FATAL_IF(handle == nullptr, "Failed to load libstatssocket.so: %s", dlerror()); + +#undef BIND_SYMBOL +#define BIND_SYMBOL(name) BindSymbol(handle, #name, k_##name); + // Methods in stats_event.h. + BIND_SYMBOL(AStatsEvent_obtain); + BIND_SYMBOL(AStatsEvent_build); + BIND_SYMBOL(AStatsEvent_write); + BIND_SYMBOL(AStatsEvent_release); + BIND_SYMBOL(AStatsEvent_setAtomId); + BIND_SYMBOL(AStatsEvent_writeInt32); + BIND_SYMBOL(AStatsEvent_writeInt64); + BIND_SYMBOL(AStatsEvent_writeFloat); + BIND_SYMBOL(AStatsEvent_writeBool); + BIND_SYMBOL(AStatsEvent_writeByteArray); + BIND_SYMBOL(AStatsEvent_writeString); + BIND_SYMBOL(AStatsEvent_writeAttributionChain); + BIND_SYMBOL(AStatsEvent_addBoolAnnotation); + BIND_SYMBOL(AStatsEvent_addInt32Annotation); + + // Methods in stats_socket.h. + BIND_SYMBOL(AStatsSocket_close); +#undef BIND_SYMBOL + + // Check every symbol is bound. + for (int i = 0; i < k_MethodCount; ++i) { + LOG_ALWAYS_FATAL_IF(g_Methods[i] == nullptr, + "Uninitialized method in libstatssocket_lazy at index: %d", i); + } +} + +static void EnsureInitialized() { + static std::once_flag initialize_flag; + std::call_once(initialize_flag, InitializeOnce); +} + +#define INVOKE_METHOD(name, args...) \ + do { \ + EnsureInitialized(); \ + void* method = g_Methods[k_##name]; \ + return reinterpret_cast(method)(args); \ + } while (0) + +// +// Forwarding for methods in stats_event.h. +// + +AStatsEvent* AStatsEvent_obtain() { + INVOKE_METHOD(AStatsEvent_obtain); +} + +void AStatsEvent_build(AStatsEvent* event) { + INVOKE_METHOD(AStatsEvent_build, event); +} + +int AStatsEvent_write(AStatsEvent* event) { + INVOKE_METHOD(AStatsEvent_write, event); +} + +void AStatsEvent_release(AStatsEvent* event) { + INVOKE_METHOD(AStatsEvent_release, event); +} + +void AStatsEvent_setAtomId(AStatsEvent* event, uint32_t atomId) { + INVOKE_METHOD(AStatsEvent_setAtomId, event, atomId); +} + +void AStatsEvent_writeInt32(AStatsEvent* event, int32_t value) { + INVOKE_METHOD(AStatsEvent_writeInt32, event, value); +} + +void AStatsEvent_writeInt64(AStatsEvent* event, int64_t value) { + INVOKE_METHOD(AStatsEvent_writeInt64, event, value); +} + +void AStatsEvent_writeFloat(AStatsEvent* event, float value) { + INVOKE_METHOD(AStatsEvent_writeFloat, event, value); +} + +void AStatsEvent_writeBool(AStatsEvent* event, bool value) { + INVOKE_METHOD(AStatsEvent_writeBool, event, value); +} + +void AStatsEvent_writeByteArray(AStatsEvent* event, const uint8_t* buf, size_t numBytes) { + INVOKE_METHOD(AStatsEvent_writeByteArray, event, buf, numBytes); +} + +void AStatsEvent_writeString(AStatsEvent* event, const char* value) { + INVOKE_METHOD(AStatsEvent_writeString, event, value); +} + +void AStatsEvent_writeAttributionChain(AStatsEvent* event, const uint32_t* uids, + const char* const* tags, uint8_t numNodes) { + INVOKE_METHOD(AStatsEvent_writeAttributionChain, event, uids, tags, numNodes); +} + +void AStatsEvent_addBoolAnnotation(AStatsEvent* event, uint8_t annotationId, bool value) { + INVOKE_METHOD(AStatsEvent_addBoolAnnotation, event, annotationId, value); +} + +void AStatsEvent_addInt32Annotation(AStatsEvent* event, uint8_t annotationId, int32_t value) { + INVOKE_METHOD(AStatsEvent_addInt32Annotation, event, annotationId, value); +} + +// +// Forwarding for methods in stats_socket.h. +// + +void AStatsSocket_close() { + INVOKE_METHOD(AStatsSocket_close); +} \ No newline at end of file diff --git a/libstats/socket_lazy/libstatssocket_lazy.h b/libstats/socket_lazy/libstatssocket_lazy.h new file mode 100644 index 000000000..3ff87cbfa --- /dev/null +++ b/libstats/socket_lazy/libstatssocket_lazy.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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 + +extern "C" void PreventLibstatssocketLazyLoadingForTests(); \ No newline at end of file diff --git a/libstats/socket_lazy/libstatssocket_lazy_test.xml b/libstats/socket_lazy/libstatssocket_lazy_test.xml new file mode 100644 index 000000000..ca6339b13 --- /dev/null +++ b/libstats/socket_lazy/libstatssocket_lazy_test.xml @@ -0,0 +1,37 @@ + + + + \ No newline at end of file diff --git a/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp b/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp new file mode 100644 index 000000000..fe1359860 --- /dev/null +++ b/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 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 "../libstatssocket_lazy.h" + +#include + +#include "stats_event.h" +#include "stats_socket.h" + +// The tests here are just for the case when libstatssocket.so cannot be loaded by +// libstatssocket_lazy. +class LibstatssocketLazyTest : public ::testing::Test { + protected: + virtual void SetUp() { + ::testing::Test::SetUp(); + PreventLibstatssocketLazyLoadingForTests(); + } +}; + +static const char* kLoadFailed = "Failed to load libstatssocket.so"; + +TEST_F(LibstatssocketLazyTest, NoLibstatssocketForStatsEvent) { + AStatsEvent* event = NULL; + EXPECT_DEATH(AStatsEvent_obtain(), kLoadFailed); + EXPECT_DEATH(AStatsEvent_build(event), kLoadFailed); + EXPECT_DEATH(AStatsEvent_write(event), kLoadFailed); + EXPECT_DEATH(AStatsEvent_release(event), kLoadFailed); + + EXPECT_DEATH(AStatsEvent_setAtomId(event, 0), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeInt32(event, 0), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeInt64(event, 0), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeFloat(event, 0), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeBool(event, false), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeByteArray(event, NULL, 0), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeString(event, NULL), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeAttributionChain(event, NULL, NULL, 0), kLoadFailed); + + EXPECT_DEATH(AStatsEvent_addBoolAnnotation(event, 0, false), kLoadFailed); + EXPECT_DEATH(AStatsEvent_addInt32Annotation(event, 0, 0), kLoadFailed); +} + +TEST_F(LibstatssocketLazyTest, NoLibstatssocketForStatsSocket) { + EXPECT_DEATH(AStatsSocket_close(), kLoadFailed); +} \ No newline at end of file