Loading cmds/lshal/Android.bp +1 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,6 @@ cc_binary { "libhidltransport", ], srcs: [ "lshal.cpp" "Lshal.cpp" ], } cmds/lshal/lshal.cpp→cmds/lshal/Lshal.cpp +477 −0 Original line number Diff line number Diff line Loading @@ -14,13 +14,14 @@ * limitations under the License. */ #include "Lshal.h" #include <getopt.h> #include <map> #include <fstream> #include <iomanip> #include <iostream> #include <map> #include <sstream> #include <regex> Loading @@ -28,24 +29,13 @@ #include <android/hidl/manager/1.0/IServiceManager.h> #include <hidl/ServiceManagement.h> using ::android::sp; #include "Timeout.h" using ::android::hardware::hidl_string; using ::android::hidl::manager::V1_0::IServiceManager; template <typename A, typename B, typename C, typename D, typename E, typename F> 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; } namespace android { namespace lshal { template <typename A> std::string join(const A &components, const std::string &separator) { Loading @@ -62,13 +52,13 @@ std::string join(const A &components, const std::string &separator) { return out.str(); } std::string toHexString(uint64_t t) { static std::string toHexString(uint64_t t) { std::ostringstream os; os << std::hex << std::setfill('0') << std::setw(16) << t; return os.str(); } std::pair<hidl_string, hidl_string> split(const hidl_string &s, char c) { static std::pair<hidl_string, hidl_string> split(const hidl_string &s, char c) { const char *pos = strchr(s.c_str(), c); if (pos == nullptr) { return {s, {}}; Loading @@ -76,8 +66,49 @@ std::pair<hidl_string, hidl_string> split(const hidl_string &s, char c) { return {hidl_string(s.c_str(), pos - s.c_str()), hidl_string(pos + 1)}; } bool getReferencedPids( pid_t serverPid, std::map<uint64_t, std::string> *objects) { static std::vector<std::string> split(const std::string &s, char c) { std::vector<std::string> 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; } 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<uint64_t, Pids> *objects) const { std::ifstream ifs("/d/binder/proc/" + std::to_string(serverPid)); if (!ifs.is_open()) { Loading @@ -97,192 +128,273 @@ bool getReferencedPids( 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; mErr << "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()); 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 dumpAllLibraries(std::stringstream &stream, const std::string &mode, const sp<IServiceManager> &manager) { using namespace ::std; 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 &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) { 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) { 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", "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.clientCmdlines, ";")); } } void Lshal::putEntry(TableEntry &&entry) { mTable.push_back(std::forward<TableEntry>(entry)); } Status Lshal::fetchAllLibraries(const sp<IServiceManager> &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) { 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"); putEntry({ .interfaceName = fqInstanceName, .transport = "passthrough", .serverPid = NO_PID, .serverObjectAddress = NO_PTR, .clientPids = {} }); } }); if (!ret.isOk()) { cerr << "Error: Failed to call debugDump on defaultServiceManager(): " << ret.description() << endl; mErr << "Error: Failed to call list on getPassthroughServiceManager(): " << ret.description() << std::endl; return DUMP_ALL_LIBS_ERROR; } return OK; } void dumpPassthrough(std::stringstream &stream, const std::string &mode, const sp<IServiceManager> &manager) { using namespace ::std; Status Lshal::fetchPassthrough(const sp<IServiceManager> &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) { printColumn(stream, info.interfaceName, info.instanceName, mode, info.clientPids.size() == 1 ? std::to_string(info.clientPids[0]) : "N/A", "N/A", join(info.clientPids, " ")); 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()) { cerr << "Error: Failed to call debugDump on defaultServiceManager(): " << ret.description() << endl; mErr << "Error: Failed to call debugDump on defaultServiceManager(): " << ret.description() << std::endl; return DUMP_PASSTHROUGH_ERROR; } return OK; } void dumpBinderized(std::stringstream &stream, const std::string &mode, const sp<IServiceManager> &manager) { Status Lshal::fetchBinderized(const sp<IServiceManager> &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) { const std::string mode = "hwbinder"; Status status = OK; auto listRet = timeoutIPC(manager, &IServiceManager::list, [&] (const auto &fqInstanceNames) { // server pid, .ptr value of binder object, child pids std::map<std::string, DebugInfo> allDebugInfos; std::map<pid_t, std::map<uint64_t, std::string>> allPids; std::map<pid_t, std::map<uint64_t, Pids>> 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); auto getRet = timeoutIPC(manager, &IServiceManager::get, serviceName, instanceName); if (!getRet.isOk()) { cerr << "Warning: Skipping \"" << fqInstanceName << "\": " mErr << "Warning: Skipping \"" << fqInstanceName << "\": " << "cannot be fetched from service manager:" << getRet.description() << endl; << getRet.description() << std::endl; status |= DUMP_BINDERIZED_ERROR; continue; } sp<IBase> service = getRet; if (service == nullptr) { cerr << "Warning: Skipping \"" << fqInstanceName << "\": " mErr << "Warning: Skipping \"" << fqInstanceName << "\": " << "cannot be fetched from service manager (null)"; 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<pid_t>(debugInfo.pid)].clear(); } }); if (!debugRet.isOk()) { cerr << "Warning: Skipping \"" << fqInstanceName << "\": " mErr << "Warning: Skipping \"" << fqInstanceName << "\": " << "debugging information cannot be retrieved:" << debugRet.description() << endl; << debugRet.description() << std::endl; status |= DUMP_BINDERIZED_ERROR; } } for (auto &pair : allPids) { pid_t serverPid = pair.first; if (!getReferencedPids(serverPid, &allPids[serverPid])) { std::cerr << "Warning: no information for PID " << serverPid mErr << "Warning: no information for PID " << serverPid << ", are you root?" << std::endl; status |= DUMP_BINDERIZED_ERROR; } } 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", "" ); putEntry({ .interfaceName = fqInstanceName, .transport = mode, .serverPid = NO_PID, .serverObjectAddress = NO_PTR, .clientPids = {} }); 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] ); 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()) { cerr << "Error: Failed to list services for " << mode << ": " << listRet.description() << endl; mErr << "Error: Failed to list services for " << mode << ": " << listRet.description() << std::endl; status |= DUMP_BINDERIZED_ERROR; } return status; } 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(); Status Lshal::fetch() { Status status = OK; auto bManager = ::android::hardware::defaultServiceManager(); if (bManager == nullptr) { cerr << "Failed to get defaultServiceManager()!" << endl; mErr << "Failed to get defaultServiceManager()!" << std::endl; status |= NO_BINDERIZED_MANAGER; } else { dumpBinderized(stream, "hwbinder", bManager); status |= fetchBinderized(bManager); // Passthrough PIDs are registered to the binderized manager as well. dumpPassthrough(stream, "passthrough", bManager); status |= fetchPassthrough(bManager); } auto pManager = getPassthroughServiceManager(); auto pManager = ::android::hardware::getPassthroughServiceManager(); if (pManager == nullptr) { cerr << "Failed to get getPassthroughServiceManager()!" << endl; mErr << "Failed to get getPassthroughServiceManager()!" << std::endl; status |= NO_PASSTHROUGH_MANAGER; } else { dumpAllLibraries(stream, "passthrough", pManager); status |= fetchAllLibraries(pManager); } cout << stream.rdbuf(); return 0; return status; } 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; void Lshal::usage() const { mErr << "usage: lshal" << 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, 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, 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 << " -h, --help: show this help information." << std::endl; } int main(int argc, char **argv) { Status Lshal::parseArgs(int argc, char **argv) { static struct option longOptions[] = { // 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' }, {"cmdline", no_argument, 0, 'm' }, // long options without short alternatives {"sort", required_argument, 0, 's' }, { 0, 0, 0, 0 } }; Loading @@ -291,16 +403,75 @@ int main(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, "hitpacm", 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 'm': { mEnableCmdlines = true; break; } case 'h': // falls through default: // see unrecognized options return usage(); usage(); return USAGE; } } if (mSelectedColumns == 0) { mSelectedColumns = ENABLE_INTERFACE_NAME | ENABLE_TRANSPORT | ENABLE_SERVER_PID | ENABLE_CLIENT_PIDS; } return OK; } return dump(); int Lshal::main(int argc, char **argv) { Status status = parseArgs(argc, argv); if (status != OK) { return status; } status = fetch(); postprocess(); dump(); return status; } } // namespace lshal } // namespace android int main(int argc, char **argv) { return ::android::lshal::Lshal{}.main(argc, argv); } cmds/lshal/Lshal.h 0 → 100644 +89 −0 Original line number Diff line number Diff line /* * 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 <stdint.h> #include <iostream> #include <string> #include <vector> #include <android/hidl/manager/1.0/IServiceManager.h> #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 postprocess(); 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<uint64_t, Pids> *objects) const; void printLine( const std::string &interfaceName, const std::string &transport, const std::string &server, 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<pid_t, std::string> mCmdlines; }; } // namespace lshal } // namespace android #endif // FRAMEWORK_NATIVE_CMDS_LSHAL_LSHAL_H_ Loading
cmds/lshal/Android.bp +1 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,6 @@ cc_binary { "libhidltransport", ], srcs: [ "lshal.cpp" "Lshal.cpp" ], }
cmds/lshal/lshal.cpp→cmds/lshal/Lshal.cpp +477 −0 Original line number Diff line number Diff line Loading @@ -14,13 +14,14 @@ * limitations under the License. */ #include "Lshal.h" #include <getopt.h> #include <map> #include <fstream> #include <iomanip> #include <iostream> #include <map> #include <sstream> #include <regex> Loading @@ -28,24 +29,13 @@ #include <android/hidl/manager/1.0/IServiceManager.h> #include <hidl/ServiceManagement.h> using ::android::sp; #include "Timeout.h" using ::android::hardware::hidl_string; using ::android::hidl::manager::V1_0::IServiceManager; template <typename A, typename B, typename C, typename D, typename E, typename F> 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; } namespace android { namespace lshal { template <typename A> std::string join(const A &components, const std::string &separator) { Loading @@ -62,13 +52,13 @@ std::string join(const A &components, const std::string &separator) { return out.str(); } std::string toHexString(uint64_t t) { static std::string toHexString(uint64_t t) { std::ostringstream os; os << std::hex << std::setfill('0') << std::setw(16) << t; return os.str(); } std::pair<hidl_string, hidl_string> split(const hidl_string &s, char c) { static std::pair<hidl_string, hidl_string> split(const hidl_string &s, char c) { const char *pos = strchr(s.c_str(), c); if (pos == nullptr) { return {s, {}}; Loading @@ -76,8 +66,49 @@ std::pair<hidl_string, hidl_string> split(const hidl_string &s, char c) { return {hidl_string(s.c_str(), pos - s.c_str()), hidl_string(pos + 1)}; } bool getReferencedPids( pid_t serverPid, std::map<uint64_t, std::string> *objects) { static std::vector<std::string> split(const std::string &s, char c) { std::vector<std::string> 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; } 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<uint64_t, Pids> *objects) const { std::ifstream ifs("/d/binder/proc/" + std::to_string(serverPid)); if (!ifs.is_open()) { Loading @@ -97,192 +128,273 @@ bool getReferencedPids( 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; mErr << "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()); 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 dumpAllLibraries(std::stringstream &stream, const std::string &mode, const sp<IServiceManager> &manager) { using namespace ::std; 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 &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) { 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) { 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", "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.clientCmdlines, ";")); } } void Lshal::putEntry(TableEntry &&entry) { mTable.push_back(std::forward<TableEntry>(entry)); } Status Lshal::fetchAllLibraries(const sp<IServiceManager> &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) { 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"); putEntry({ .interfaceName = fqInstanceName, .transport = "passthrough", .serverPid = NO_PID, .serverObjectAddress = NO_PTR, .clientPids = {} }); } }); if (!ret.isOk()) { cerr << "Error: Failed to call debugDump on defaultServiceManager(): " << ret.description() << endl; mErr << "Error: Failed to call list on getPassthroughServiceManager(): " << ret.description() << std::endl; return DUMP_ALL_LIBS_ERROR; } return OK; } void dumpPassthrough(std::stringstream &stream, const std::string &mode, const sp<IServiceManager> &manager) { using namespace ::std; Status Lshal::fetchPassthrough(const sp<IServiceManager> &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) { printColumn(stream, info.interfaceName, info.instanceName, mode, info.clientPids.size() == 1 ? std::to_string(info.clientPids[0]) : "N/A", "N/A", join(info.clientPids, " ")); 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()) { cerr << "Error: Failed to call debugDump on defaultServiceManager(): " << ret.description() << endl; mErr << "Error: Failed to call debugDump on defaultServiceManager(): " << ret.description() << std::endl; return DUMP_PASSTHROUGH_ERROR; } return OK; } void dumpBinderized(std::stringstream &stream, const std::string &mode, const sp<IServiceManager> &manager) { Status Lshal::fetchBinderized(const sp<IServiceManager> &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) { const std::string mode = "hwbinder"; Status status = OK; auto listRet = timeoutIPC(manager, &IServiceManager::list, [&] (const auto &fqInstanceNames) { // server pid, .ptr value of binder object, child pids std::map<std::string, DebugInfo> allDebugInfos; std::map<pid_t, std::map<uint64_t, std::string>> allPids; std::map<pid_t, std::map<uint64_t, Pids>> 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); auto getRet = timeoutIPC(manager, &IServiceManager::get, serviceName, instanceName); if (!getRet.isOk()) { cerr << "Warning: Skipping \"" << fqInstanceName << "\": " mErr << "Warning: Skipping \"" << fqInstanceName << "\": " << "cannot be fetched from service manager:" << getRet.description() << endl; << getRet.description() << std::endl; status |= DUMP_BINDERIZED_ERROR; continue; } sp<IBase> service = getRet; if (service == nullptr) { cerr << "Warning: Skipping \"" << fqInstanceName << "\": " mErr << "Warning: Skipping \"" << fqInstanceName << "\": " << "cannot be fetched from service manager (null)"; 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<pid_t>(debugInfo.pid)].clear(); } }); if (!debugRet.isOk()) { cerr << "Warning: Skipping \"" << fqInstanceName << "\": " mErr << "Warning: Skipping \"" << fqInstanceName << "\": " << "debugging information cannot be retrieved:" << debugRet.description() << endl; << debugRet.description() << std::endl; status |= DUMP_BINDERIZED_ERROR; } } for (auto &pair : allPids) { pid_t serverPid = pair.first; if (!getReferencedPids(serverPid, &allPids[serverPid])) { std::cerr << "Warning: no information for PID " << serverPid mErr << "Warning: no information for PID " << serverPid << ", are you root?" << std::endl; status |= DUMP_BINDERIZED_ERROR; } } 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", "" ); putEntry({ .interfaceName = fqInstanceName, .transport = mode, .serverPid = NO_PID, .serverObjectAddress = NO_PTR, .clientPids = {} }); 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] ); 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()) { cerr << "Error: Failed to list services for " << mode << ": " << listRet.description() << endl; mErr << "Error: Failed to list services for " << mode << ": " << listRet.description() << std::endl; status |= DUMP_BINDERIZED_ERROR; } return status; } 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(); Status Lshal::fetch() { Status status = OK; auto bManager = ::android::hardware::defaultServiceManager(); if (bManager == nullptr) { cerr << "Failed to get defaultServiceManager()!" << endl; mErr << "Failed to get defaultServiceManager()!" << std::endl; status |= NO_BINDERIZED_MANAGER; } else { dumpBinderized(stream, "hwbinder", bManager); status |= fetchBinderized(bManager); // Passthrough PIDs are registered to the binderized manager as well. dumpPassthrough(stream, "passthrough", bManager); status |= fetchPassthrough(bManager); } auto pManager = getPassthroughServiceManager(); auto pManager = ::android::hardware::getPassthroughServiceManager(); if (pManager == nullptr) { cerr << "Failed to get getPassthroughServiceManager()!" << endl; mErr << "Failed to get getPassthroughServiceManager()!" << std::endl; status |= NO_PASSTHROUGH_MANAGER; } else { dumpAllLibraries(stream, "passthrough", pManager); status |= fetchAllLibraries(pManager); } cout << stream.rdbuf(); return 0; return status; } 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; void Lshal::usage() const { mErr << "usage: lshal" << 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, 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, 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 << " -h, --help: show this help information." << std::endl; } int main(int argc, char **argv) { Status Lshal::parseArgs(int argc, char **argv) { static struct option longOptions[] = { // 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' }, {"cmdline", no_argument, 0, 'm' }, // long options without short alternatives {"sort", required_argument, 0, 's' }, { 0, 0, 0, 0 } }; Loading @@ -291,16 +403,75 @@ int main(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, "hitpacm", 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 'm': { mEnableCmdlines = true; break; } case 'h': // falls through default: // see unrecognized options return usage(); usage(); return USAGE; } } if (mSelectedColumns == 0) { mSelectedColumns = ENABLE_INTERFACE_NAME | ENABLE_TRANSPORT | ENABLE_SERVER_PID | ENABLE_CLIENT_PIDS; } return OK; } return dump(); int Lshal::main(int argc, char **argv) { Status status = parseArgs(argc, argv); if (status != OK) { return status; } status = fetch(); postprocess(); dump(); return status; } } // namespace lshal } // namespace android int main(int argc, char **argv) { return ::android::lshal::Lshal{}.main(argc, argv); }
cmds/lshal/Lshal.h 0 → 100644 +89 −0 Original line number Diff line number Diff line /* * 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 <stdint.h> #include <iostream> #include <string> #include <vector> #include <android/hidl/manager/1.0/IServiceManager.h> #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 postprocess(); 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<uint64_t, Pids> *objects) const; void printLine( const std::string &interfaceName, const std::string &transport, const std::string &server, 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<pid_t, std::string> mCmdlines; }; } // namespace lshal } // namespace android #endif // FRAMEWORK_NATIVE_CMDS_LSHAL_LSHAL_H_