From b0dde932ad8989a393fad148c17817bcb2ff7a43 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Fri, 10 Feb 2017 17:49:58 -0800 Subject: lshal: Refactor lshal to use an Lshal class; combined instance column with interface column We need to be able to select columns, sort columns, and do IPC calls fault-tolerently. Refactoring lshal.cpp into an Lshal class for more objective-oriented programming, so that global variables can be avoided and less parameters will need to be passed around. Test: lshal outputs identical output as without this CL, except with the column merged. Change-Id: I9668c998da692222aef96ae67bbab8066172543d --- cmds/lshal/Android.bp | 2 +- cmds/lshal/Lshal.cpp | 354 ++++++++++++++++++++++++++++++++++++++++++++++++ cmds/lshal/Lshal.h | 70 ++++++++++ cmds/lshal/TableEntry.h | 48 +++++++ cmds/lshal/lshal.cpp | 306 ----------------------------------------- 5 files changed, 473 insertions(+), 307 deletions(-) create mode 100644 cmds/lshal/Lshal.cpp create mode 100644 cmds/lshal/Lshal.h create mode 100644 cmds/lshal/TableEntry.h delete mode 100644 cmds/lshal/lshal.cpp diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp index c3805985c4..5aab35a450 100644 --- a/cmds/lshal/Android.bp +++ b/cmds/lshal/Android.bp @@ -22,6 +22,6 @@ cc_binary { "libhidltransport", ], srcs: [ - "lshal.cpp" + "Lshal.cpp" ], } diff --git a/cmds/lshal/Lshal.cpp b/cmds/lshal/Lshal.cpp new file mode 100644 index 0000000000..e6d10bfc06 --- /dev/null +++ b/cmds/lshal/Lshal.cpp @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2016 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 "Lshal.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using ::android::hardware::hidl_string; +using ::android::hidl::manager::V1_0::IServiceManager; + +namespace android { +namespace lshal { + +template +void printColumn(std::ostream &stream, + const A &a, const C &c, const D &d, const E &, const F &f) { + using namespace ::std; + stream << left + << setw(80) << a << "\t" + << setw(10) << c << "\t" + << setw(5) << d << "\t" + // TODO(b/34984175): enable selecting columns + // << setw(16) << e << "\t" + << setw(0) << f + << endl; +} + +template +std::string join(const A &components, const std::string &separator) { + std::stringstream out; + bool first = true; + for (const auto &component : components) { + if (!first) { + out << separator; + } + out << component; + + first = false; + } + return out.str(); +} + +static std::string toHexString(uint64_t t) { + std::ostringstream os; + os << std::hex << std::setfill('0') << std::setw(16) << t; + return os.str(); +} + +static std::pair split(const hidl_string &s, char c) { + const char *pos = strchr(s.c_str(), c); + if (pos == nullptr) { + return {s, {}}; + } + return {hidl_string(s.c_str(), pos - s.c_str()), hidl_string(pos + 1)}; +} + +static std::vector split(const std::string &s, char c) { + std::vector components{}; + size_t startPos = 0; + size_t matchPos; + while ((matchPos = s.find(c, startPos)) != std::string::npos) { + components.push_back(s.substr(startPos, matchPos - startPos)); + startPos = matchPos + 1; + } + + if (startPos <= s.length()) { + components.push_back(s.substr(startPos)); + } + return components; +} + +bool Lshal::getReferencedPids( + pid_t serverPid, std::map *objects) const { + + std::ifstream ifs("/d/binder/proc/" + std::to_string(serverPid)); + if (!ifs.is_open()) { + return false; + } + + static const std::regex prefix("^\\s*node \\d+:\\s+u([0-9a-f]+)\\s+c([0-9a-f]+)\\s+"); + + std::string line; + std::smatch match; + while(getline(ifs, line)) { + if (!std::regex_search(line, match, prefix)) { + // the line doesn't start with the correct prefix + continue; + } + 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. + mErr << "Could not parse number " << ptrString << std::endl; + continue; + } + 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)) { + mErr << "Could not parse number " << pidStr << std::endl; + continue; + } + (*objects)[ptr].push_back(pid); + } + } + } + return true; +} + +void Lshal::dump() const { + mOut << "All services:" << std::endl; + printColumn(mOut, "Interface", "Transport", "Server", "PTR", "Clients"); + for (const auto &entry : mTable) { + printColumn(mOut, entry.interfaceName, + entry.transport, + entry.serverPid == NO_PID ? "N/A" : std::to_string(entry.serverPid), + entry.serverObjectAddress == NO_PTR ? "N/A" : toHexString(entry.serverObjectAddress), + join(entry.clientPids, " ")); + } +} + +void Lshal::putEntry(TableEntry &&entry) { + mTable.push_back(std::forward(entry)); +} + +Status Lshal::fetchAllLibraries(const sp &manager) { + using namespace ::android::hardware; + using namespace ::android::hidl::manager::V1_0; + using namespace ::android::hidl::base::V1_0; + auto ret = manager->list([&] (const auto &fqInstanceNames) { + for (const auto &fqInstanceName : fqInstanceNames) { + putEntry({ + .interfaceName = fqInstanceName, + .transport = "passthrough", + .serverPid = NO_PID, + .serverObjectAddress = NO_PTR, + .clientPids = {} + }); + } + }); + if (!ret.isOk()) { + mErr << "Error: Failed to call list on getPassthroughServiceManager(): " + << ret.description() << std::endl; + return DUMP_ALL_LIBS_ERROR; + } + return OK; +} + +Status Lshal::fetchPassthrough(const sp &manager) { + using namespace ::android::hardware; + using namespace ::android::hidl::manager::V1_0; + using namespace ::android::hidl::base::V1_0; + auto ret = manager->debugDump([&] (const auto &infos) { + for (const auto &info : infos) { + putEntry({ + .interfaceName = + std::string{info.interfaceName.c_str()} + "/" + + std::string{info.instanceName.c_str()}, + .transport = "passthrough", + .serverPid = info.clientPids.size() == 1 ? info.clientPids[0] : NO_PID, + .serverObjectAddress = NO_PTR, + .clientPids = info.clientPids + }); + } + }); + if (!ret.isOk()) { + mErr << "Error: Failed to call debugDump on defaultServiceManager(): " + << ret.description() << std::endl; + return DUMP_PASSTHROUGH_ERROR; + } + return OK; +} + +Status Lshal::fetchBinderized(const sp &manager) { + using namespace ::std; + using namespace ::android::hardware; + using namespace ::android::hidl::manager::V1_0; + using namespace ::android::hidl::base::V1_0; + const std::string mode = "hwbinder"; + Status status = OK; + auto listRet = manager->list([&] (const auto &fqInstanceNames) { + // server pid, .ptr value of binder object, child pids + std::map allDebugInfos; + std::map> allPids; + for (const auto &fqInstanceName : fqInstanceNames) { + const auto pair = split(fqInstanceName, '/'); + const auto &serviceName = pair.first; + const auto &instanceName = pair.second; + auto getRet = manager->get(serviceName, instanceName); + if (!getRet.isOk()) { + mErr << "Warning: Skipping \"" << fqInstanceName << "\": " + << "cannot be fetched from service manager:" + << getRet.description() << std::endl; + status |= DUMP_BINDERIZED_ERROR; + continue; + } + sp service = getRet; + if (service == nullptr) { + mErr << "Warning: Skipping \"" << fqInstanceName << "\": " + << "cannot be fetched from service manager (null)"; + status |= DUMP_BINDERIZED_ERROR; + continue; + } + auto debugRet = service->getDebugInfo([&] (const auto &debugInfo) { + allDebugInfos[fqInstanceName] = debugInfo; + if (debugInfo.pid >= 0) { + allPids[static_cast(debugInfo.pid)].clear(); + } + }); + if (!debugRet.isOk()) { + mErr << "Warning: Skipping \"" << fqInstanceName << "\": " + << "debugging information cannot be retrieved:" + << debugRet.description() << std::endl; + status |= DUMP_BINDERIZED_ERROR; + } + } + for (auto &pair : allPids) { + pid_t serverPid = pair.first; + if (!getReferencedPids(serverPid, &allPids[serverPid])) { + mErr << "Warning: no information for PID " << serverPid + << ", are you root?" << std::endl; + status |= DUMP_BINDERIZED_ERROR; + } + } + for (const auto &fqInstanceName : fqInstanceNames) { + auto it = allDebugInfos.find(fqInstanceName); + if (it == allDebugInfos.end()) { + putEntry({ + .interfaceName = fqInstanceName, + .transport = mode, + .serverPid = NO_PID, + .serverObjectAddress = NO_PTR, + .clientPids = {} + }); + continue; + } + const DebugInfo &info = it->second; + putEntry({ + .interfaceName = fqInstanceName, + .transport = mode, + .serverPid = info.pid, + .serverObjectAddress = info.ptr, + .clientPids = info.pid == NO_PID || info.ptr == NO_PTR + ? Pids{} : allPids[info.pid][info.ptr] + }); + } + + }); + if (!listRet.isOk()) { + mErr << "Error: Failed to list services for " << mode << ": " + << listRet.description() << std::endl; + status |= DUMP_BINDERIZED_ERROR; + } + return status; +} + +Status Lshal::fetch() { + Status status = OK; + auto bManager = ::android::hardware::defaultServiceManager(); + if (bManager == nullptr) { + mErr << "Failed to get defaultServiceManager()!" << std::endl; + status |= NO_BINDERIZED_MANAGER; + } else { + status |= fetchBinderized(bManager); + // Passthrough PIDs are registered to the binderized manager as well. + status |= fetchPassthrough(bManager); + } + + auto pManager = ::android::hardware::getPassthroughServiceManager(); + if (pManager == nullptr) { + mErr << "Failed to get getPassthroughServiceManager()!" << std::endl; + status |= NO_PASSTHROUGH_MANAGER; + } else { + status |= fetchAllLibraries(pManager); + } + return status; +} + +void Lshal::usage() const { + mErr + << "usage: lshal" << std::endl + << " To dump all hals." << std::endl + << "or:" << std::endl + << " lshal [-h|--help]" << std::endl + << " -h, --help: show this help information." << std::endl; +} + +Status Lshal::parseArgs(int argc, char **argv) { + static struct option longOptions[] = { + {"help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + + int optionIndex; + int c; + optind = 1; + for (;;) { + // using getopt_long in case we want to add other options in the future + c = getopt_long(argc, argv, "h", longOptions, &optionIndex); + if (c == -1) { + break; + } + switch (c) { + case 'h': // falls through + default: // see unrecognized options + usage(); + return USAGE; + } + } + return OK; +} + +int Lshal::main(int argc, char **argv) { + Status status = parseArgs(argc, argv); + if (status != OK) { + return status; + } + status = fetch(); + dump(); + return status; +} + +} // namespace lshal +} // namespace android + +int main(int argc, char **argv) { + return ::android::lshal::Lshal{}.main(argc, argv); +} diff --git a/cmds/lshal/Lshal.h b/cmds/lshal/Lshal.h new file mode 100644 index 0000000000..c87ebc3b98 --- /dev/null +++ b/cmds/lshal/Lshal.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef FRAMEWORK_NATIVE_CMDS_LSHAL_LSHAL_H_ +#define FRAMEWORK_NATIVE_CMDS_LSHAL_LSHAL_H_ + +#include + +#include +#include +#include + +#include + +#include "TableEntry.h" + +namespace android { +namespace lshal { + +enum : unsigned int { + OK = 0, + USAGE = 1 << 0, + NO_BINDERIZED_MANAGER = 1 << 1, + NO_PASSTHROUGH_MANAGER = 1 << 2, + DUMP_BINDERIZED_ERROR = 1 << 3, + DUMP_PASSTHROUGH_ERROR = 1 << 4, + DUMP_ALL_LIBS_ERROR = 1 << 5, +}; +using Status = unsigned int; + +class Lshal { +public: + int main(int argc, char **argv); + +private: + Status parseArgs(int argc, char **argv); + Status fetch(); + void dump() const; + void usage() const; + void putEntry(TableEntry &&entry); + Status fetchPassthrough(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager); + Status fetchBinderized(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager); + Status fetchAllLibraries(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager); + bool getReferencedPids( + pid_t serverPid, std::map *objects) const; + + + Table mTable; + std::ostream &mErr = std::cerr; + std::ostream &mOut = std::cout; +}; + + +} // namespace lshal +} // namespace android + +#endif // FRAMEWORK_NATIVE_CMDS_LSHAL_LSHAL_H_ diff --git a/cmds/lshal/TableEntry.h b/cmds/lshal/TableEntry.h new file mode 100644 index 0000000000..484df3fbf2 --- /dev/null +++ b/cmds/lshal/TableEntry.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef FRAMEWORK_NATIVE_CMDS_LSHAL_TABLE_ENTRY_H_ +#define FRAMEWORK_NATIVE_CMDS_LSHAL_TABLE_ENTRY_H_ + +#include + +#include +#include + +namespace android { +namespace lshal { + +using Pids = std::vector; + +struct TableEntry { + std::string interfaceName; + std::string transport; + int32_t serverPid; + uint64_t serverObjectAddress; + Pids clientPids; +}; + +using Table = std::vector; + +enum { + NO_PID = -1, + NO_PTR = 0 +}; + +} // namespace lshal +} // namespace android + +#endif // FRAMEWORK_NATIVE_CMDS_LSHAL_TABLE_ENTRY_H_ diff --git a/cmds/lshal/lshal.cpp b/cmds/lshal/lshal.cpp deleted file mode 100644 index 9998a462a3..0000000000 --- a/cmds/lshal/lshal.cpp +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2016 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 - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -using ::android::sp; -using ::android::hardware::hidl_string; -using ::android::hidl::manager::V1_0::IServiceManager; - -template -void printColumn(std::stringstream &stream, - const A &a, const B &b, const C &c, const D &d, const E &, const F &f) { - using namespace ::std; - stream << left - << setw(70) << a << "\t" - << setw(20) << b << "\t" - << setw(10) << c << "\t" - << setw(5) << d << "\t" - // TODO(b/34984175): enable selecting columns - // << setw(16) << e << "\t" - << setw(0) << f - << endl; -} - -template -std::string join(const A &components, const std::string &separator) { - std::stringstream out; - bool first = true; - for (const auto &component : components) { - if (!first) { - out << separator; - } - out << component; - - first = false; - } - return out.str(); -} - -std::string toHexString(uint64_t t) { - std::ostringstream os; - os << std::hex << std::setfill('0') << std::setw(16) << t; - return os.str(); -} - -std::pair split(const hidl_string &s, char c) { - const char *pos = strchr(s.c_str(), c); - if (pos == nullptr) { - return {s, {}}; - } - return {hidl_string(s.c_str(), pos - s.c_str()), hidl_string(pos + 1)}; -} - -bool getReferencedPids( - pid_t serverPid, std::map *objects) { - - std::ifstream ifs("/d/binder/proc/" + std::to_string(serverPid)); - if (!ifs.is_open()) { - return false; - } - - static const std::regex prefix("^\\s*node \\d+:\\s+u([0-9a-f]+)\\s+c([0-9a-f]+)\\s+"); - - std::string line; - std::smatch match; - while(getline(ifs, line)) { - if (!std::regex_search(line, match, prefix)) { - // the line doesn't start with the correct prefix - continue; - } - 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. - std::cerr << "Could not parse number " << ptrString << std::endl; - continue; - } - const std::string proc = " proc "; - auto pos = line.rfind(proc); - if (pos != std::string::npos) { - (*objects)[ptr] += line.substr(pos + proc.size()); - } - } - return true; -} - -void dumpAllLibraries(std::stringstream &stream, const std::string &mode, - const sp &manager) { - using namespace ::std; - using namespace ::android::hardware; - using namespace ::android::hidl::manager::V1_0; - using namespace ::android::hidl::base::V1_0; - auto ret = manager->list([&] (const auto &fqInstanceNames) { - for (const auto &fqInstanceName : fqInstanceNames) { - const auto pair = split(fqInstanceName, '/'); - const auto &serviceName = pair.first; - const auto &instanceName = pair.second; - printColumn(stream, - serviceName, - instanceName, - mode, - "N/A", - "N/A", - "N/A"); - } - }); - if (!ret.isOk()) { - cerr << "Error: Failed to call debugDump on defaultServiceManager(): " - << ret.description() << endl; - } -} - -void dumpPassthrough(std::stringstream &stream, const std::string &mode, - const sp &manager) { - using namespace ::std; - using namespace ::android::hardware; - using namespace ::android::hidl::manager::V1_0; - using namespace ::android::hidl::base::V1_0; - auto ret = manager->debugDump([&] (const auto &infos) { - for (const auto &info : infos) { - - printColumn(stream, - info.interfaceName, - info.instanceName, - mode, - info.clientPids.size() == 1 ? std::to_string(info.clientPids[0]) : "N/A", - "N/A", - join(info.clientPids, " ")); - } - }); - if (!ret.isOk()) { - cerr << "Error: Failed to call debugDump on defaultServiceManager(): " - << ret.description() << endl; - } -} - -void dumpBinderized(std::stringstream &stream, const std::string &mode, - const sp &manager) { - using namespace ::std; - using namespace ::android::hardware; - using namespace ::android::hidl::manager::V1_0; - using namespace ::android::hidl::base::V1_0; - auto listRet = manager->list([&] (const auto &fqInstanceNames) { - // server pid, .ptr value of binder object, child pids - std::map allDebugInfos; - std::map> allPids; - for (const auto &fqInstanceName : fqInstanceNames) { - const auto pair = split(fqInstanceName, '/'); - const auto &serviceName = pair.first; - const auto &instanceName = pair.second; - auto getRet = manager->get(serviceName, instanceName); - if (!getRet.isOk()) { - cerr << "Warning: Skipping \"" << fqInstanceName << "\": " - << "cannot be fetched from service manager:" - << getRet.description() << endl; - continue; - } - sp service = getRet; - if (service == nullptr) { - cerr << "Warning: Skipping \"" << fqInstanceName << "\": " - << "cannot be fetched from service manager (null)"; - continue; - } - auto debugRet = service->getDebugInfo([&] (const auto &debugInfo) { - allDebugInfos[fqInstanceName] = debugInfo; - if (debugInfo.pid >= 0) { - allPids[static_cast(debugInfo.pid)].clear(); - } - }); - if (!debugRet.isOk()) { - cerr << "Warning: Skipping \"" << fqInstanceName << "\": " - << "debugging information cannot be retrieved:" - << debugRet.description() << endl; - } - } - for (auto &pair : allPids) { - pid_t serverPid = pair.first; - if (!getReferencedPids(serverPid, &allPids[serverPid])) { - std::cerr << "Warning: no information for PID " << serverPid - << ", are you root?" << std::endl; - } - } - for (const auto &fqInstanceName : fqInstanceNames) { - const auto pair = split(fqInstanceName, '/'); - const auto &serviceName = pair.first; - const auto &instanceName = pair.second; - auto it = allDebugInfos.find(fqInstanceName); - if (it == allDebugInfos.end()) { - printColumn(stream, - serviceName, - instanceName, - mode, - "N/A", - "N/A", - "" - ); - continue; - } - const DebugInfo &info = it->second; - printColumn(stream, - serviceName, - instanceName, - mode, - info.pid < 0 ? "N/A" : std::to_string(info.pid), - info.ptr == 0 ? "N/A" : toHexString(info.ptr), - info.pid < 0 || info.ptr == 0 ? "" : allPids[info.pid][info.ptr] - ); - } - - }); - if (!listRet.isOk()) { - cerr << "Error: Failed to list services for " << mode << ": " - << listRet.description() << endl; - } -} - -int dump() { - using namespace ::std; - using namespace ::android::hardware; - - std::stringstream stream; - - stream << "All services:" << endl; - stream << left; - printColumn(stream, "Interface", "Instance", "Transport", "Server", "PTR", "Clients"); - - auto bManager = defaultServiceManager(); - if (bManager == nullptr) { - cerr << "Failed to get defaultServiceManager()!" << endl; - } else { - dumpBinderized(stream, "hwbinder", bManager); - // Passthrough PIDs are registered to the binderized manager as well. - dumpPassthrough(stream, "passthrough", bManager); - } - - auto pManager = getPassthroughServiceManager(); - if (pManager == nullptr) { - cerr << "Failed to get getPassthroughServiceManager()!" << endl; - } else { - dumpAllLibraries(stream, "passthrough", pManager); - } - - cout << stream.rdbuf(); - return 0; -} - -int usage() { - using namespace ::std; - cerr - << "usage: lshal" << endl - << " To dump all hals." << endl - << "or:" << endl - << " lshal [-h|--help]" << endl - << " -h, --help: show this help information." << endl; - return -1; -} - -int main(int argc, char **argv) { - static struct option longOptions[] = { - {"help", no_argument, 0, 'h' }, - { 0, 0, 0, 0 } - }; - - int optionIndex; - int c; - optind = 1; - for (;;) { - // using getopt_long in case we want to add other options in the future - c = getopt_long(argc, argv, "h", longOptions, &optionIndex); - if (c == -1) { - break; - } - switch (c) { - case 'h': // falls through - default: // see unrecognized options - return usage(); - } - } - return dump(); - -} -- cgit v1.2.3-59-g8ed1b From 38d53e0327f200a8a16f99d1e1a09e33c2be8e81 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Mon, 13 Feb 2017 17:51:59 -0800 Subject: lshal: Allow selecting columns and sorting by column. Examples: see tests below. Test: lshal Test: lshal -intpac Test: lshal --sort=i Test: lshal --sort=p Bug: 34984175 Change-Id: Ia09823776a206868a5d58a2ede153a4538147756 --- cmds/lshal/Lshal.cpp | 109 ++++++++++++++++++++++++++++++++++++++---------- cmds/lshal/Lshal.h | 10 ++++- cmds/lshal/TableEntry.h | 19 +++++++++ 3 files changed, 115 insertions(+), 23 deletions(-) diff --git a/cmds/lshal/Lshal.cpp b/cmds/lshal/Lshal.cpp index e6d10bfc06..d213fd314f 100644 --- a/cmds/lshal/Lshal.cpp +++ b/cmds/lshal/Lshal.cpp @@ -35,20 +35,6 @@ using ::android::hidl::manager::V1_0::IServiceManager; namespace android { namespace lshal { -template -void printColumn(std::ostream &stream, - const A &a, const C &c, const D &d, const E &, const F &f) { - using namespace ::std; - stream << left - << setw(80) << a << "\t" - << setw(10) << c << "\t" - << setw(5) << d << "\t" - // TODO(b/34984175): enable selecting columns - // << setw(16) << e << "\t" - << setw(0) << f - << endl; -} - template std::string join(const A &components, const std::string &separator) { std::stringstream out; @@ -133,11 +119,35 @@ bool Lshal::getReferencedPids( return true; } +void Lshal::postprocess() { + if (mSortColumn) { + std::sort(mTable.begin(), mTable.end(), mSortColumn); + } +} + +void Lshal::printLine( + const std::string &interfaceName, + const std::string &transport, const std::string &server, + const std::string &address, const std::string &clients) const { + if (mSelectedColumns & ENABLE_INTERFACE_NAME) + mOut << std::setw(80) << interfaceName << "\t"; + if (mSelectedColumns & ENABLE_TRANSPORT) + mOut << std::setw(10) << transport << "\t"; + if (mSelectedColumns & ENABLE_SERVER_PID) + mOut << std::setw(5) << server << "\t"; + if (mSelectedColumns & ENABLE_SERVER_ADDR) + mOut << std::setw(16) << address << "\t"; + if (mSelectedColumns & ENABLE_CLIENT_PIDS) + mOut << std::setw(0) << clients; + mOut << std::endl; +} + void Lshal::dump() const { mOut << "All services:" << std::endl; - printColumn(mOut, "Interface", "Transport", "Server", "PTR", "Clients"); + mOut << std::left; + printLine("Interface", "Transport", "Server", "PTR", "Clients"); for (const auto &entry : mTable) { - printColumn(mOut, entry.interfaceName, + printLine(entry.interfaceName, entry.transport, entry.serverPid == NO_PID ? "N/A" : std::to_string(entry.serverPid), entry.serverObjectAddress == NO_PTR ? "N/A" : toHexString(entry.serverObjectAddress), @@ -305,16 +315,35 @@ Status Lshal::fetch() { void Lshal::usage() const { mErr << "usage: lshal" << std::endl - << " To dump all hals." << std::endl - << "or:" << std::endl + << " Dump all hals with default ordering and columns [-itpc]." << std::endl + << " lshal [--interface|-i] [--transport|-t]" << std::endl + << " [--pid|-p] [--address|-a] [--clients|-c] [--cmdline|-m]" << std::endl + << " [--sort={interface|i|pid|p}]" << std::endl + << " -i, --interface: print the interface name column" << std::endl + << " -n, --instance: print the instance name column" << std::endl + << " -t, --transport: print the transport mode column" << std::endl + << " -p, --pid: print the server PID column" << std::endl + << " -a, --address: print the server object address column" << std::endl + << " -c, --clients: print the client PIDs column" << std::endl + << " --sort=i, --sort=interface: sort by interface name" << std::endl + << " --sort=p, --sort=pid: sort by server pid" << std::endl << " lshal [-h|--help]" << std::endl << " -h, --help: show this help information." << std::endl; } Status Lshal::parseArgs(int argc, char **argv) { static struct option longOptions[] = { - {"help", no_argument, 0, 'h' }, - { 0, 0, 0, 0 } + // long options with short alternatives + {"help", no_argument, 0, 'h' }, + {"interface", no_argument, 0, 'i' }, + {"transport", no_argument, 0, 't' }, + {"pid", no_argument, 0, 'p' }, + {"address", no_argument, 0, 'a' }, + {"clients", no_argument, 0, 'c' }, + + // long options without short alternatives + {"sort", required_argument, 0, 's' }, + { 0, 0, 0, 0 } }; int optionIndex; @@ -322,17 +351,54 @@ Status Lshal::parseArgs(int argc, char **argv) { optind = 1; for (;;) { // using getopt_long in case we want to add other options in the future - c = getopt_long(argc, argv, "h", longOptions, &optionIndex); + c = getopt_long(argc, argv, "hitpac", longOptions, &optionIndex); if (c == -1) { break; } switch (c) { + case 's': { + if (strcmp(optarg, "interface") == 0 || strcmp(optarg, "i") == 0) { + mSortColumn = TableEntry::sortByInterfaceName; + } else if (strcmp(optarg, "pid") == 0 || strcmp(optarg, "p") == 0) { + mSortColumn = TableEntry::sortByServerPid; + } else { + mErr << "Unrecognized sorting column: " << optarg << std::endl; + usage(); + return USAGE; + } + break; + } + case 'i': { + mSelectedColumns |= ENABLE_INTERFACE_NAME; + break; + } + case 't': { + mSelectedColumns |= ENABLE_TRANSPORT; + break; + } + case 'p': { + mSelectedColumns |= ENABLE_SERVER_PID; + break; + } + case 'a': { + mSelectedColumns |= ENABLE_SERVER_ADDR; + break; + } + case 'c': { + mSelectedColumns |= ENABLE_CLIENT_PIDS; + break; + } case 'h': // falls through default: // see unrecognized options usage(); return USAGE; } } + + if (mSelectedColumns == 0) { + mSelectedColumns = ENABLE_INTERFACE_NAME + | ENABLE_TRANSPORT | ENABLE_SERVER_PID | ENABLE_CLIENT_PIDS; + } return OK; } @@ -342,6 +408,7 @@ int Lshal::main(int argc, char **argv) { return status; } status = fetch(); + postprocess(); dump(); return status; } diff --git a/cmds/lshal/Lshal.h b/cmds/lshal/Lshal.h index c87ebc3b98..d7bcab2929 100644 --- a/cmds/lshal/Lshal.h +++ b/cmds/lshal/Lshal.h @@ -48,6 +48,7 @@ public: private: Status parseArgs(int argc, char **argv); Status fetch(); + void postprocess(); void dump() const; void usage() const; void putEntry(TableEntry &&entry); @@ -56,11 +57,16 @@ private: Status fetchAllLibraries(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager); bool getReferencedPids( pid_t serverPid, std::map *objects) const; + void printLine( + const std::string &interfaceName, + const std::string &transport, const std::string &server, + const std::string &address, const std::string &clients) const; - - Table mTable; + Table mTable{}; std::ostream &mErr = std::cerr; std::ostream &mOut = std::cout; + TableEntryCompare mSortColumn = nullptr; + TableEntrySelect mSelectedColumns = 0; }; diff --git a/cmds/lshal/TableEntry.h b/cmds/lshal/TableEntry.h index 484df3fbf2..864b41b3f2 100644 --- a/cmds/lshal/TableEntry.h +++ b/cmds/lshal/TableEntry.h @@ -21,6 +21,7 @@ #include #include +#include namespace android { namespace lshal { @@ -33,9 +34,27 @@ struct TableEntry { int32_t serverPid; uint64_t serverObjectAddress; Pids clientPids; + + static bool sortByInterfaceName(const TableEntry &a, const TableEntry &b) { + return a.interfaceName < b.interfaceName; + }; + static bool sortByServerPid(const TableEntry &a, const TableEntry &b) { + return a.serverPid < b.serverPid; + }; }; using Table = std::vector; +using TableEntryCompare = std::function; + +enum : unsigned int { + ENABLE_INTERFACE_NAME = 1 << 0, + ENABLE_TRANSPORT = 1 << 1, + ENABLE_SERVER_PID = 1 << 2, + ENABLE_SERVER_ADDR = 1 << 3, + ENABLE_CLIENT_PIDS = 1 << 4 +}; + +using TableEntrySelect = unsigned int; enum { NO_PID = -1, -- cgit v1.2.3-59-g8ed1b From e2dadf0c4f132d3a39309f4e274f1a35f7caaaed Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Tue, 14 Feb 2017 15:43:31 -0800 Subject: lshal: Add timeout for IPC calls. IPC calls into interfaces should be done fault-torelently. Add a timeout for each IPC call made so that lshal won't be indefinitely blocked even if the interface don't reply promptly. Bug: 35317039 Test: lshal Change-Id: Icb8157716ad68bddb5b33304b9063aa6f233985d --- cmds/lshal/Lshal.cpp | 12 ++++---- cmds/lshal/Timeout.h | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 cmds/lshal/Timeout.h diff --git a/cmds/lshal/Lshal.cpp b/cmds/lshal/Lshal.cpp index d213fd314f..a2dabce90f 100644 --- a/cmds/lshal/Lshal.cpp +++ b/cmds/lshal/Lshal.cpp @@ -29,6 +29,8 @@ #include #include +#include "Timeout.h" + using ::android::hardware::hidl_string; using ::android::hidl::manager::V1_0::IServiceManager; @@ -163,7 +165,7 @@ Status Lshal::fetchAllLibraries(const sp &manager) { using namespace ::android::hardware; using namespace ::android::hidl::manager::V1_0; using namespace ::android::hidl::base::V1_0; - auto ret = manager->list([&] (const auto &fqInstanceNames) { + auto ret = timeoutIPC(manager, &IServiceManager::list, [&] (const auto &fqInstanceNames) { for (const auto &fqInstanceName : fqInstanceNames) { putEntry({ .interfaceName = fqInstanceName, @@ -186,7 +188,7 @@ Status Lshal::fetchPassthrough(const sp &manager) { using namespace ::android::hardware; using namespace ::android::hidl::manager::V1_0; using namespace ::android::hidl::base::V1_0; - auto ret = manager->debugDump([&] (const auto &infos) { + auto ret = timeoutIPC(manager, &IServiceManager::debugDump, [&] (const auto &infos) { for (const auto &info : infos) { putEntry({ .interfaceName = @@ -214,7 +216,7 @@ Status Lshal::fetchBinderized(const sp &manager) { using namespace ::android::hidl::base::V1_0; const std::string mode = "hwbinder"; Status status = OK; - auto listRet = manager->list([&] (const auto &fqInstanceNames) { + auto listRet = timeoutIPC(manager, &IServiceManager::list, [&] (const auto &fqInstanceNames) { // server pid, .ptr value of binder object, child pids std::map allDebugInfos; std::map> allPids; @@ -222,7 +224,7 @@ Status Lshal::fetchBinderized(const sp &manager) { const auto pair = split(fqInstanceName, '/'); const auto &serviceName = pair.first; const auto &instanceName = pair.second; - auto getRet = manager->get(serviceName, instanceName); + auto getRet = timeoutIPC(manager, &IServiceManager::get, serviceName, instanceName); if (!getRet.isOk()) { mErr << "Warning: Skipping \"" << fqInstanceName << "\": " << "cannot be fetched from service manager:" @@ -237,7 +239,7 @@ Status Lshal::fetchBinderized(const sp &manager) { status |= DUMP_BINDERIZED_ERROR; continue; } - auto debugRet = service->getDebugInfo([&] (const auto &debugInfo) { + auto debugRet = timeoutIPC(service, &IBase::getDebugInfo, [&] (const auto &debugInfo) { allDebugInfos[fqInstanceName] = debugInfo; if (debugInfo.pid >= 0) { allPids[static_cast(debugInfo.pid)].clear(); diff --git a/cmds/lshal/Timeout.h b/cmds/lshal/Timeout.h new file mode 100644 index 0000000000..bf883c0a4c --- /dev/null +++ b/cmds/lshal/Timeout.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 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 +#include +#include +#include +#include + +#include + +namespace android { +namespace lshal { + +static constexpr std::chrono::milliseconds IPC_CALL_WAIT{500}; + +class BackgroundTaskState { +public: + BackgroundTaskState(){} + void notify() { + std::unique_lock lock(mMutex); + mFinished = true; + lock.unlock(); + mCondVar.notify_all(); + } + template + bool wait(std::chrono::time_point end) { + std::unique_lock lock(mMutex); + mCondVar.wait_until(lock, end, [this](){ return this->mFinished; }); + return mFinished; + } +private: + std::mutex mMutex; + std::condition_variable mCondVar; + bool mFinished = false; +}; + +template +bool timeout(std::chrono::duration delay, const std::function &func) { + auto now = std::chrono::system_clock::now(); + BackgroundTaskState state{}; + std::thread t([&state, &func] { + func(); + state.notify(); + }); + t.detach(); + bool success = state.wait(now + delay); + return success; +} + +template +typename std::result_of::type +timeoutIPC(const sp &interfaceObject, Function &&func, Args &&... args) { + using ::android::hardware::Status; + typename std::result_of::type ret{Status::ok()}; + auto boundFunc = std::bind(std::forward(func), + interfaceObject.get(), std::forward(args)...); + bool success = timeout(IPC_CALL_WAIT, [&ret, &boundFunc] { + ret = boundFunc(); + }); + if (!success) { + return Status::fromStatusT(TIMED_OUT); + } + return ret; +} + +} // namespace lshal +} // namespace android -- cgit v1.2.3-59-g8ed1b From ae09a3ddfec51c82257e338346e39ee472470061 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Tue, 14 Feb 2017 17:33:50 -0800 Subject: lshal: Add option to print cmd lines instead of pids. Add -m to arguments of lshal. When this flag is set, /proc/{pid}/cmdline is printed instead of a plain PID. If the file doesn't exist, it will be striped out from the PID column as well (the process is considered died and won't hold a reference to the binder object.) Test: lshal -icm Bug: 35160832 Change-Id: I4345bf06112a1f87ce91bec6f6f787703e46cd17 --- cmds/lshal/Lshal.cpp | 74 ++++++++++++++++++++++++++++++++++++++++++------- cmds/lshal/Lshal.h | 15 +++++++++- cmds/lshal/TableEntry.h | 2 ++ 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/cmds/lshal/Lshal.cpp b/cmds/lshal/Lshal.cpp index a2dabce90f..ce058c8a47 100644 --- a/cmds/lshal/Lshal.cpp +++ b/cmds/lshal/Lshal.cpp @@ -81,6 +81,32 @@ static std::vector split(const std::string &s, char c) { return components; } +std::string getCmdline(pid_t pid) { + std::ifstream ifs("/proc/" + std::to_string(pid) + "/cmdline"); + std::string cmdline; + if (!ifs.is_open()) { + return ""; + } + ifs >> cmdline; + return cmdline; +} + +const std::string &Lshal::getCmdline(pid_t pid) { + auto pair = mCmdlines.find(pid); + if (pair != mCmdlines.end()) { + return pair->second; + } + mCmdlines[pid] = ::android::lshal::getCmdline(pid); + return mCmdlines[pid]; +} + +void Lshal::removeDeadProcesses(Pids *pids) { + static const pid_t myPid = getpid(); + std::remove_if(pids->begin(), pids->end(), [this](auto pid) { + return pid == myPid || this->getCmdline(pid).empty(); + }); +} + bool Lshal::getReferencedPids( pid_t serverPid, std::map *objects) const { @@ -125,35 +151,56 @@ void Lshal::postprocess() { if (mSortColumn) { std::sort(mTable.begin(), mTable.end(), mSortColumn); } + for (TableEntry &entry : mTable) { + entry.serverCmdline = getCmdline(entry.serverPid); + removeDeadProcesses(&entry.clientPids); + for (auto pid : entry.clientPids) { + entry.clientCmdlines.push_back(this->getCmdline(pid)); + } + } } void Lshal::printLine( const std::string &interfaceName, const std::string &transport, const std::string &server, - const std::string &address, const std::string &clients) const { + const std::string &serverCmdline, + const std::string &address, const std::string &clients, + const std::string &clientCmdlines) const { if (mSelectedColumns & ENABLE_INTERFACE_NAME) mOut << std::setw(80) << interfaceName << "\t"; if (mSelectedColumns & ENABLE_TRANSPORT) mOut << std::setw(10) << transport << "\t"; - if (mSelectedColumns & ENABLE_SERVER_PID) - mOut << std::setw(5) << server << "\t"; + if (mSelectedColumns & ENABLE_SERVER_PID) { + if (mEnableCmdlines) { + mOut << std::setw(15) << serverCmdline << "\t"; + } else { + mOut << std::setw(5) << server << "\t"; + } + } if (mSelectedColumns & ENABLE_SERVER_ADDR) mOut << std::setw(16) << address << "\t"; - if (mSelectedColumns & ENABLE_CLIENT_PIDS) - mOut << std::setw(0) << clients; + if (mSelectedColumns & ENABLE_CLIENT_PIDS) { + if (mEnableCmdlines) { + mOut << std::setw(0) << clientCmdlines; + } else { + mOut << std::setw(0) << clients; + } + } mOut << std::endl; } void Lshal::dump() const { mOut << "All services:" << std::endl; mOut << std::left; - printLine("Interface", "Transport", "Server", "PTR", "Clients"); + printLine("Interface", "Transport", "Server", "Server CMD", "PTR", "Clients", "Clients CMD"); for (const auto &entry : mTable) { printLine(entry.interfaceName, entry.transport, entry.serverPid == NO_PID ? "N/A" : std::to_string(entry.serverPid), + entry.serverCmdline, entry.serverObjectAddress == NO_PTR ? "N/A" : toHexString(entry.serverObjectAddress), - join(entry.clientPids, " ")); + join(entry.clientPids, " "), + join(entry.clientCmdlines, ";")); } } @@ -324,9 +371,11 @@ void Lshal::usage() const { << " -i, --interface: print the interface name column" << std::endl << " -n, --instance: print the instance name column" << std::endl << " -t, --transport: print the transport mode column" << std::endl - << " -p, --pid: print the server PID column" << std::endl + << " -p, --pid: print the server PID, or server cmdline if -m is set" << std::endl << " -a, --address: print the server object address column" << std::endl - << " -c, --clients: print the client PIDs column" << std::endl + << " -c, --clients: print the client PIDs, or client cmdlines if -m is set" + << std::endl + << " -m, --cmdline: print cmdline instead of PIDs" << std::endl << " --sort=i, --sort=interface: sort by interface name" << std::endl << " --sort=p, --sort=pid: sort by server pid" << std::endl << " lshal [-h|--help]" << std::endl @@ -342,6 +391,7 @@ Status Lshal::parseArgs(int argc, char **argv) { {"pid", no_argument, 0, 'p' }, {"address", no_argument, 0, 'a' }, {"clients", no_argument, 0, 'c' }, + {"cmdline", no_argument, 0, 'm' }, // long options without short alternatives {"sort", required_argument, 0, 's' }, @@ -353,7 +403,7 @@ Status Lshal::parseArgs(int argc, char **argv) { optind = 1; for (;;) { // using getopt_long in case we want to add other options in the future - c = getopt_long(argc, argv, "hitpac", longOptions, &optionIndex); + c = getopt_long(argc, argv, "hitpacm", longOptions, &optionIndex); if (c == -1) { break; } @@ -390,6 +440,10 @@ Status Lshal::parseArgs(int argc, char **argv) { mSelectedColumns |= ENABLE_CLIENT_PIDS; break; } + case 'm': { + mEnableCmdlines = true; + break; + } case 'h': // falls through default: // see unrecognized options usage(); diff --git a/cmds/lshal/Lshal.h b/cmds/lshal/Lshal.h index d7bcab2929..ead99dc068 100644 --- a/cmds/lshal/Lshal.h +++ b/cmds/lshal/Lshal.h @@ -60,13 +60,26 @@ private: void printLine( const std::string &interfaceName, const std::string &transport, const std::string &server, - const std::string &address, const std::string &clients) const; + const std::string &serverCmdline, + const std::string &address, const std::string &clients, + const std::string &clientCmdlines) const ; + // Return /proc/{pid}/cmdline if it exists, else empty string. + const std::string &getCmdline(pid_t pid); + // Call getCmdline on all pid in pids. If it returns empty string, the process might + // have died, and the pid is removed from pids. + void removeDeadProcesses(Pids *pids); Table mTable{}; std::ostream &mErr = std::cerr; std::ostream &mOut = std::cout; TableEntryCompare mSortColumn = nullptr; TableEntrySelect mSelectedColumns = 0; + // If true, cmdlines will be printed instead of pid. + bool mEnableCmdlines; + // If an entry does not exist, need to ask /proc/{pid}/cmdline to get it. + // If an entry exist but is an empty string, process might have died. + // If an entry exist and not empty, it contains the cached content of /proc/{pid}/cmdline. + std::map mCmdlines; }; diff --git a/cmds/lshal/TableEntry.h b/cmds/lshal/TableEntry.h index 864b41b3f2..4ec3a0ced1 100644 --- a/cmds/lshal/TableEntry.h +++ b/cmds/lshal/TableEntry.h @@ -32,8 +32,10 @@ struct TableEntry { std::string interfaceName; std::string transport; int32_t serverPid; + std::string serverCmdline; uint64_t serverObjectAddress; Pids clientPids; + std::vector clientCmdlines; static bool sortByInterfaceName(const TableEntry &a, const TableEntry &b) { return a.interfaceName < b.interfaceName; -- cgit v1.2.3-59-g8ed1b