diff options
author | 2020-12-11 15:11:17 -0800 | |
---|---|---|
committer | 2020-12-15 14:16:39 -0800 | |
commit | c03e3aa3cda31f1734529d313bbcda476da3c907 (patch) | |
tree | d6acacd138affc92c6d07cbe41ecfa21366a00c3 | |
parent | f5c89f5911cb24070dd3248d588a0d9d065fbe41 (diff) |
Add libbinderdebug
Allows lshal and dumpsys to share the code to get Binder PID info.
Test: atest libbinderdebug_test lshal_test
Test: diff output of lshal before and after this CL
Bug: 140639610
Change-Id: I04dbe2509673502502ac849ef4ae74147404fc43
-rw-r--r-- | cmds/lshal/Android.bp | 2 | ||||
-rw-r--r-- | cmds/lshal/ListCommand.cpp | 96 | ||||
-rw-r--r-- | cmds/lshal/ListCommand.h | 13 | ||||
-rw-r--r-- | cmds/lshal/TableEntry.h | 2 | ||||
-rw-r--r-- | cmds/lshal/test.cpp | 10 | ||||
-rw-r--r-- | libs/binderdebug/Android.bp | 28 | ||||
-rw-r--r-- | libs/binderdebug/BinderDebug.cpp | 119 | ||||
-rw-r--r-- | libs/binderdebug/TEST_MAPPING | 7 | ||||
-rw-r--r-- | libs/binderdebug/include/binderdebug/BinderDebug.h | 37 | ||||
-rw-r--r-- | libs/binderdebug/tests/Android.bp | 30 | ||||
-rw-r--r-- | libs/binderdebug/tests/android/binderdebug/test/IControl.aidl | 22 | ||||
-rw-r--r-- | libs/binderdebug/tests/binderdebug_test.cpp | 90 |
12 files changed, 351 insertions, 105 deletions
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp index 1994e565f2..0cbb80feb2 100644 --- a/cmds/lshal/Android.bp +++ b/cmds/lshal/Android.bp @@ -16,6 +16,7 @@ cc_library_static { name: "liblshal", shared_libs: [ "libbase", + "libbinderdebug", "libcutils", "libutils", "libhidlbase", @@ -47,6 +48,7 @@ cc_defaults { name: "lshal_defaults", shared_libs: [ "libbase", + "libbinderdebug", "libcutils", "libutils", "libhidlbase", diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp index 92958d957b..22268aceea 100644 --- a/cmds/lshal/ListCommand.cpp +++ b/cmds/lshal/ListCommand.cpp @@ -29,7 +29,6 @@ #include <android-base/file.h> #include <android-base/logging.h> -#include <android-base/parseint.h> #include <android/hidl/manager/1.0/IServiceManager.h> #include <hidl-hash/Hash.h> #include <hidl-util/FQName.h> @@ -203,97 +202,14 @@ VintfInfo ListCommand::getVintfInfo(const std::string& fqInstanceName, lshal::getVintfInfo(getFrameworkMatrix(), fqInstance, ta, FRAMEWORK_MATRIX); } -static bool scanBinderContext(pid_t pid, - const std::string &contextName, - std::function<void(const std::string&)> eachLine) { - std::ifstream ifs("/dev/binderfs/binder_logs/proc/" + std::to_string(pid)); - if (!ifs.is_open()) { - ifs.open("/d/binder/proc/" + std::to_string(pid)); - if (!ifs.is_open()) { - return false; - } - } - - static const std::regex kContextLine("^context (\\w+)$"); - - bool isDesiredContext = false; - std::string line; - std::smatch match; - while(getline(ifs, line)) { - if (std::regex_search(line, match, kContextLine)) { - isDesiredContext = match.str(1) == contextName; - continue; - } - - if (!isDesiredContext) { - continue; - } - - eachLine(line); - } - return true; -} - bool ListCommand::getPidInfo( - pid_t serverPid, PidInfo *pidInfo) const { - static const std::regex kReferencePrefix("^\\s*node \\d+:\\s+u([0-9a-f]+)\\s+c([0-9a-f]+)\\s+"); - static const std::regex kThreadPrefix("^\\s*thread \\d+:\\s+l\\s+(\\d)(\\d)"); - - std::smatch match; - return scanBinderContext(serverPid, "hwbinder", [&](const std::string& line) { - if (std::regex_search(line, match, kReferencePrefix)) { - const std::string &ptrString = "0x" + match.str(2); // use number after c - uint64_t ptr; - if (!::android::base::ParseUint(ptrString.c_str(), &ptr)) { - // Should not reach here, but just be tolerant. - err() << "Could not parse number " << ptrString << std::endl; - return; - } - const std::string proc = " proc "; - auto pos = line.rfind(proc); - if (pos != std::string::npos) { - for (const std::string &pidStr : split(line.substr(pos + proc.size()), ' ')) { - int32_t pid; - if (!::android::base::ParseInt(pidStr, &pid)) { - err() << "Could not parse number " << pidStr << std::endl; - return; - } - pidInfo->refPids[ptr].push_back(pid); - } - } - - return; - } - - if (std::regex_search(line, match, kThreadPrefix)) { - // "1" is waiting in binder driver - // "2" is poll. It's impossible to tell if these are in use. - // and HIDL default code doesn't use it. - bool isInUse = match.str(1) != "1"; - // "0" is a thread that has called into binder - // "1" is looper thread - // "2" is main looper thread - bool isHwbinderThread = match.str(2) != "0"; - - if (!isHwbinderThread) { - return; - } - - if (isInUse) { - pidInfo->threadUsage++; - } - - pidInfo->threadCount++; - return; - } - - // not reference or thread line - return; - }); + pid_t serverPid, BinderPidInfo *pidInfo) const { + const auto& status = getBinderPidInfo(BinderDebugContext::HWBINDER, serverPid, pidInfo); + return status == OK; } -const PidInfo* ListCommand::getPidInfoCached(pid_t serverPid) { - auto pair = mCachedPidInfos.insert({serverPid, PidInfo{}}); +const BinderPidInfo* ListCommand::getPidInfoCached(pid_t serverPid) { + auto pair = mCachedPidInfos.insert({serverPid, BinderPidInfo{}}); if (pair.second /* did insertion take place? */) { if (!getPidInfo(serverPid, &pair.first->second)) { return nullptr; @@ -727,7 +643,7 @@ Status ListCommand::fetchBinderizedEntry(const sp<IServiceManager> &manager, entry->arch = fromBaseArchitecture(debugInfo.arch); if (debugInfo.pid != NO_PID) { - const PidInfo* pidInfo = getPidInfoCached(debugInfo.pid); + const BinderPidInfo* pidInfo = getPidInfoCached(debugInfo.pid); if (pidInfo == nullptr) { handleError(IO_ERROR, "no information for PID " + std::to_string(debugInfo.pid) + diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h index 412aadd7e4..561f9cbc19 100644 --- a/cmds/lshal/ListCommand.h +++ b/cmds/lshal/ListCommand.h @@ -25,6 +25,7 @@ #include <android-base/macros.h> #include <android/hidl/manager/1.0/IServiceManager.h> +#include <binderdebug/BinderDebug.h> #include <hidl-util/FqInstance.h> #include <vintf/HalManifest.h> #include <vintf/VintfObject.h> @@ -40,12 +41,6 @@ namespace lshal { class Lshal; -struct PidInfo { - std::map<uint64_t, Pids> refPids; // pids that are referenced - uint32_t threadUsage; // number of threads in use - uint32_t threadCount; // number of threads total -}; - enum class HalType { BINDERIZED_SERVICES = 0, PASSTHROUGH_CLIENTS, @@ -110,9 +105,9 @@ protected: // Get relevant information for a PID by parsing files under // /dev/binderfs/binder_logs or /d/binder. // It is a virtual member function so that it can be mocked. - virtual bool getPidInfo(pid_t serverPid, PidInfo *info) const; + virtual bool getPidInfo(pid_t serverPid, BinderPidInfo *info) const; // Retrieve from mCachedPidInfos and call getPidInfo if necessary. - const PidInfo* getPidInfoCached(pid_t serverPid); + const BinderPidInfo* getPidInfoCached(pid_t serverPid); void dumpTable(const NullableOStream<std::ostream>& out) const; void dumpVintf(const NullableOStream<std::ostream>& out) const; @@ -191,7 +186,7 @@ protected: std::map<pid_t, std::string> mCmdlines; // Cache for getPidInfo. - std::map<pid_t, PidInfo> mCachedPidInfos; + std::map<pid_t, BinderPidInfo> mCachedPidInfos; // Cache for getPartition. std::map<pid_t, Partition> mPartitions; diff --git a/cmds/lshal/TableEntry.h b/cmds/lshal/TableEntry.h index 3c368132a5..476aa04513 100644 --- a/cmds/lshal/TableEntry.h +++ b/cmds/lshal/TableEntry.h @@ -32,7 +32,7 @@ namespace android { namespace lshal { using android::procpartition::Partition; -using Pids = std::vector<int32_t>; +using Pids = std::vector<pid_t>; enum class TableColumnType : unsigned int { INTERFACE_NAME = 0, diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp index ba6cdf1fe8..b6ff28d416 100644 --- a/cmds/lshal/test.cpp +++ b/cmds/lshal/test.cpp @@ -233,12 +233,12 @@ public: return ListCommand::dumpVintf(out); } void internalPostprocess() { ListCommand::postprocess(); } - const PidInfo* getPidInfoCached(pid_t serverPid) { + const BinderPidInfo* getPidInfoCached(pid_t serverPid) { return ListCommand::getPidInfoCached(serverPid); } MOCK_METHOD0(postprocess, void()); - MOCK_CONST_METHOD2(getPidInfo, bool(pid_t, PidInfo*)); + MOCK_CONST_METHOD2(getPidInfo, bool(pid_t, BinderPidInfo*)); MOCK_CONST_METHOD1(parseCmdline, std::string(pid_t)); MOCK_METHOD1(getPartition, Partition(pid_t)); @@ -299,8 +299,8 @@ static uint64_t getPtr(pid_t serverId) { return 10000 + serverId; } static std::vector<pid_t> getClients(pid_t serverId) { return {serverId + 1, serverId + 3}; } -static PidInfo getPidInfoFromId(pid_t serverId) { - PidInfo info; +static BinderPidInfo getPidInfoFromId(pid_t serverId) { + BinderPidInfo info; info.refPids[getPtr(serverId)] = getClients(serverId); info.threadUsage = 10 + serverId; info.threadCount = 20 + serverId; @@ -363,7 +363,7 @@ public: void initMockList() { mockList = std::make_unique<NiceMock<MockListCommand>>(lshal.get()); ON_CALL(*mockList, getPidInfo(_,_)).WillByDefault(Invoke( - [](pid_t serverPid, PidInfo* info) { + [](pid_t serverPid, BinderPidInfo* info) { *info = getPidInfoFromId(serverPid); return true; })); diff --git a/libs/binderdebug/Android.bp b/libs/binderdebug/Android.bp new file mode 100644 index 0000000000..343246a324 --- /dev/null +++ b/libs/binderdebug/Android.bp @@ -0,0 +1,28 @@ +// Copyright (C) 2020 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. + +cc_library { + name: "libbinderdebug", + vendor_available: true, + shared_libs: [ + "libbase", + "libbinder", + ], + srcs: [ + "BinderDebug.cpp", + ], + export_include_dirs: [ + "include", + ], +} diff --git a/libs/binderdebug/BinderDebug.cpp b/libs/binderdebug/BinderDebug.cpp new file mode 100644 index 0000000000..b435dba197 --- /dev/null +++ b/libs/binderdebug/BinderDebug.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2020 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 <android-base/parseint.h> +#include <android-base/strings.h> +#include <binder/Binder.h> +#include <sys/types.h> +#include <fstream> +#include <regex> + +#include <binderdebug/BinderDebug.h> + +namespace android { + +static std::string contextToString(BinderDebugContext context) { + switch (context) { + case BinderDebugContext::BINDER: + return "binder"; + case BinderDebugContext::HWBINDER: + return "hwbinder"; + case BinderDebugContext::VNDBINDER: + return "vndbinder"; + default: + return std::string(); + } +} + +static status_t scanBinderContext(pid_t pid, const std::string& contextName, + std::function<void(const std::string&)> eachLine) { + std::ifstream ifs("/dev/binderfs/binder_logs/proc/" + std::to_string(pid)); + if (!ifs.is_open()) { + ifs.open("/d/binder/proc/" + std::to_string(pid)); + if (!ifs.is_open()) { + return -errno; + } + } + static const std::regex kContextLine("^context (\\w+)$"); + + bool isDesiredContext = false; + std::string line; + std::smatch match; + while (getline(ifs, line)) { + if (std::regex_search(line, match, kContextLine)) { + isDesiredContext = match.str(1) == contextName; + continue; + } + if (!isDesiredContext) { + continue; + } + eachLine(line); + } + return OK; +} + +status_t getBinderPidInfo(BinderDebugContext context, pid_t pid, BinderPidInfo* pidInfo) { + std::smatch match; + static const std::regex kReferencePrefix("^\\s*node \\d+:\\s+u([0-9a-f]+)\\s+c([0-9a-f]+)\\s+"); + static const std::regex kThreadPrefix("^\\s*thread \\d+:\\s+l\\s+(\\d)(\\d)"); + std::string contextStr = contextToString(context); + status_t ret = scanBinderContext(pid, contextStr, [&](const std::string& line) { + if (std::regex_search(line, match, kReferencePrefix)) { + const std::string& ptrString = "0x" + match.str(2); // use number after c + uint64_t ptr; + if (!::android::base::ParseUint(ptrString.c_str(), &ptr)) { + // Should not reach here, but just be tolerant. + return; + } + const std::string proc = " proc "; + auto pos = line.rfind(proc); + if (pos != std::string::npos) { + for (const std::string& pidStr : base::Split(line.substr(pos + proc.size()), " ")) { + int32_t pid; + if (!::android::base::ParseInt(pidStr, &pid)) { + return; + } + pidInfo->refPids[ptr].push_back(pid); + } + } + + return; + } + if (std::regex_search(line, match, kThreadPrefix)) { + // "1" is waiting in binder driver + // "2" is poll. It's impossible to tell if these are in use. + // and HIDL default code doesn't use it. + bool isInUse = match.str(1) != "1"; + // "0" is a thread that has called into binder + // "1" is looper thread + // "2" is main looper thread + bool isBinderThread = match.str(2) != "0"; + if (!isBinderThread) { + return; + } + if (isInUse) { + pidInfo->threadUsage++; + } + + pidInfo->threadCount++; + return; + } + return; + }); + return ret; +} + +} // namespace android diff --git a/libs/binderdebug/TEST_MAPPING b/libs/binderdebug/TEST_MAPPING new file mode 100644 index 0000000000..2f3353e871 --- /dev/null +++ b/libs/binderdebug/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "libbinderdebug_test" + } + ] +} diff --git a/libs/binderdebug/include/binderdebug/BinderDebug.h b/libs/binderdebug/include/binderdebug/BinderDebug.h new file mode 100644 index 0000000000..14a0ef3672 --- /dev/null +++ b/libs/binderdebug/include/binderdebug/BinderDebug.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 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 <map> +#include <vector> + +namespace android { + +struct BinderPidInfo { + std::map<uint64_t, std::vector<pid_t>> refPids; // cookie -> processes which hold binder + uint32_t threadUsage; // number of threads in use + uint32_t threadCount; // number of threads total +}; + +enum class BinderDebugContext { + BINDER, + HWBINDER, + VNDBINDER, +}; + +status_t getBinderPidInfo(BinderDebugContext context, pid_t pid, BinderPidInfo* pidInfo); + +} // namespace android diff --git a/libs/binderdebug/tests/Android.bp b/libs/binderdebug/tests/Android.bp new file mode 100644 index 0000000000..4c06b1d0d9 --- /dev/null +++ b/libs/binderdebug/tests/Android.bp @@ -0,0 +1,30 @@ +// Copyright (C) 2020 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. + +cc_test { + name: "libbinderdebug_test", + test_suites: ["general-tests"], + srcs: [ + "binderdebug_test.cpp", + "android/binderdebug/test/IControl.aidl", + ], + shared_libs: [ + "libbase", + "libbinder", + "libutils", + ], + static_libs: ["libbinderdebug"], + cflags: ["-Wall", "-Werror"], + require_root: true, +} diff --git a/libs/binderdebug/tests/android/binderdebug/test/IControl.aidl b/libs/binderdebug/tests/android/binderdebug/test/IControl.aidl new file mode 100644 index 0000000000..8efeb6352f --- /dev/null +++ b/libs/binderdebug/tests/android/binderdebug/test/IControl.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2020 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. + */ + +package android.binderdebug.test; + +interface IControl { + // Notifies the service to continue execution + void Continue(); +} diff --git a/libs/binderdebug/tests/binderdebug_test.cpp b/libs/binderdebug/tests/binderdebug_test.cpp new file mode 100644 index 0000000000..ea799c06a2 --- /dev/null +++ b/libs/binderdebug/tests/binderdebug_test.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2020 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 <binder/Binder.h> +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <binder/IPCThreadState.h> +#include <binderdebug/BinderDebug.h> +#include <gtest/gtest.h> +#include <semaphore.h> +#include <thread> + +#include <android/binderdebug/test/BnControl.h> +#include <android/binderdebug/test/IControl.h> + +namespace android { +namespace binderdebug { +namespace test { + +class Control : public BnControl { +public: + Control() {sem_init(&s, 1, 0);}; + ::android::binder::Status Continue() override; + sem_t s; +}; + +::android::binder::Status Control::Continue() { + IPCThreadState::self()->flushCommands(); + sem_post(&s); + return binder::Status::ok(); +} + +TEST(BinderDebugTests, BinderPid) { + BinderPidInfo pidInfo; + const auto& status = getBinderPidInfo(BinderDebugContext::BINDER, getpid(), &pidInfo); + ASSERT_EQ(status, OK); + // There should be one referenced PID for servicemanager + EXPECT_TRUE(!pidInfo.refPids.empty()); +} + +TEST(BinderDebugTests, BinderThreads) { + BinderPidInfo pidInfo; + const auto& status = getBinderPidInfo(BinderDebugContext::BINDER, getpid(), &pidInfo); + ASSERT_EQ(status, OK); + EXPECT_TRUE(pidInfo.threadUsage <= pidInfo.threadCount); + // The second looper thread can sometimes take longer to spawn. + EXPECT_GE(pidInfo.threadCount, 1); +} + +extern "C" { +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + + // Create a child/client process to call into the main process so we can ensure + // looper thread has been registered before attempting to get the BinderPidInfo + pid_t pid = fork(); + if (pid == 0) { + sp<IBinder> binder = android::defaultServiceManager()->getService(String16("binderdebug")); + sp<IControl> service; + if (binder != nullptr) { + service = android::interface_cast<IControl>(binder); + } + service->Continue(); + exit(0); + } + sp<Control> iface = new Control; + android::defaultServiceManager()->addService(String16("binderdebug"), iface); + android::ProcessState::self()->setThreadPoolMaxThreadCount(8); + ProcessState::self()->startThreadPool(); + sem_wait(&iface->s); + + return RUN_ALL_TESTS(); +} +} // extern "C" +} // namespace test +} // namespace binderdebug +} // namespace android |