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
This commit is contained in:
Devin Moore 2020-12-11 15:11:17 -08:00
parent f5c89f5911
commit c03e3aa3cd
12 changed files with 351 additions and 105 deletions

View file

@ -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",

View file

@ -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) +

View file

@ -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;

View file

@ -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,

View file

@ -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;
}));

View file

@ -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",
],
}

View file

@ -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

View file

@ -0,0 +1,7 @@
{
"presubmit": [
{
"name": "libbinderdebug_test"
}
]
}

View file

@ -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

View file

@ -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,
}

View file

@ -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();
}

View file

@ -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