From 8388ba3e2ddaf703192946b0b7874cc2431f3dd7 Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Tue, 9 May 2017 18:49:45 -0700 Subject: [PATCH] Lshal: support commands. Support the following commands: lshal list lshal list -itrpc lshal help lshal help list lshal list -h lshal list --help Test: run these commands Bug: 37725279 Change-Id: I970fbc8d250d43f57e92f783229e0645d7e8df4e Merged-In: I970fbc8d250d43f57e92f783229e0645d7e8df4e --- cmds/lshal/Android.bp | 4 +- cmds/lshal/ListCommand.cpp | 710 +++++++++++++++++++++++++++++++++++++ cmds/lshal/ListCommand.h | 101 ++++++ cmds/lshal/Lshal.cpp | 866 +++++++-------------------------------------- cmds/lshal/Lshal.h | 89 +---- cmds/lshal/utils.cpp | 53 +++ cmds/lshal/utils.h | 80 +++++ 7 files changed, 1093 insertions(+), 810 deletions(-) create mode 100644 cmds/lshal/ListCommand.cpp create mode 100644 cmds/lshal/ListCommand.h create mode 100644 cmds/lshal/utils.cpp create mode 100644 cmds/lshal/utils.h diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp index 4740202ac7..13d3170931 100644 --- a/cmds/lshal/Android.bp +++ b/cmds/lshal/Android.bp @@ -26,6 +26,8 @@ cc_binary { ], srcs: [ "Lshal.cpp", - "PipeRelay.cpp" + "ListCommand.cpp", + "PipeRelay.cpp", + "utils.cpp", ], } diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp new file mode 100644 index 0000000000..5696843336 --- /dev/null +++ b/cmds/lshal/ListCommand.cpp @@ -0,0 +1,710 @@ +/* + * Copyright (C) 2017 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 "ListCommand.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Lshal.h" +#include "PipeRelay.h" +#include "Timeout.h" +#include "utils.h" + +using ::android::hardware::hidl_string; +using ::android::hidl::manager::V1_0::IServiceManager; + +namespace android { +namespace lshal { + +ListCommand::ListCommand(Lshal &lshal) : mLshal(lshal), mErr(lshal.err()), mOut(lshal.out()) { +} + +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 &ListCommand::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 ListCommand::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 ListCommand::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; +} + +// Must process hwbinder services first, then passthrough services. +void ListCommand::forEachTable(const std::function &f) { + f(mServicesTable); + f(mPassthroughRefTable); + f(mImplementationsTable); +} +void ListCommand::forEachTable(const std::function &f) const { + f(mServicesTable); + f(mPassthroughRefTable); + f(mImplementationsTable); +} + +void ListCommand::postprocess() { + forEachTable([this](Table &table) { + if (mSortColumn) { + std::sort(table.begin(), table.end(), mSortColumn); + } + for (TableEntry &entry : table) { + entry.serverCmdline = getCmdline(entry.serverPid); + removeDeadProcesses(&entry.clientPids); + for (auto pid : entry.clientPids) { + entry.clientCmdlines.push_back(this->getCmdline(pid)); + } + } + }); + // use a double for loop here because lshal doesn't care about efficiency. + for (TableEntry &packageEntry : mImplementationsTable) { + std::string packageName = packageEntry.interfaceName; + FQName fqPackageName{packageName.substr(0, packageName.find("::"))}; + if (!fqPackageName.isValid()) { + continue; + } + for (TableEntry &interfaceEntry : mPassthroughRefTable) { + if (interfaceEntry.arch != ARCH_UNKNOWN) { + continue; + } + FQName interfaceName{splitFirst(interfaceEntry.interfaceName, '/').first}; + if (!interfaceName.isValid()) { + continue; + } + if (interfaceName.getPackageAndVersion() == fqPackageName) { + interfaceEntry.arch = packageEntry.arch; + } + } + } +} + +void ListCommand::printLine( + const std::string &interfaceName, + const std::string &transport, + const std::string &arch, + 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_ARCH) + mOut << std::setw(5) << arch << "\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 ListCommand::dumpVintf() const { + mOut << "" << std::endl; + + vintf::HalManifest manifest; + forEachTable([this, &manifest] (const Table &table) { + for (const TableEntry &entry : table) { + + std::string fqInstanceName = entry.interfaceName; + + if (&table == &mImplementationsTable) { + // Quick hack to work around *'s + replaceAll(&fqInstanceName, '*', 'D'); + } + auto splittedFqInstanceName = splitFirst(fqInstanceName, '/'); + FQName fqName(splittedFqInstanceName.first); + if (!fqName.isValid()) { + mErr << "Warning: '" << splittedFqInstanceName.first + << "' is not a valid FQName." << std::endl; + continue; + } + // Strip out system libs. + if (fqName.inPackage("android.hidl") || + fqName.inPackage("android.frameworks") || + fqName.inPackage("android.system")) { + continue; + } + std::string interfaceName = + &table == &mImplementationsTable ? "" : fqName.name(); + std::string instanceName = + &table == &mImplementationsTable ? "" : splittedFqInstanceName.second; + + vintf::Version version{fqName.getPackageMajorVersion(), + fqName.getPackageMinorVersion()}; + vintf::Transport transport; + vintf::Arch arch; + if (entry.transport == "hwbinder") { + transport = vintf::Transport::HWBINDER; + arch = vintf::Arch::ARCH_EMPTY; + } else if (entry.transport == "passthrough") { + transport = vintf::Transport::PASSTHROUGH; + switch (entry.arch) { + case lshal::ARCH32: + arch = vintf::Arch::ARCH_32; break; + case lshal::ARCH64: + arch = vintf::Arch::ARCH_64; break; + case lshal::ARCH_BOTH: + arch = vintf::Arch::ARCH_32_64; break; + case lshal::ARCH_UNKNOWN: // fallthrough + default: + mErr << "Warning: '" << fqName.package() + << "' doesn't have bitness info, assuming 32+64." << std::endl; + arch = vintf::Arch::ARCH_32_64; + } + } else { + mErr << "Warning: '" << entry.transport << "' is not a valid transport." << std::endl; + continue; + } + + bool done = false; + for (vintf::ManifestHal *hal : manifest.getHals(fqName.package())) { + if (hal->transport() != transport) { + if (transport != vintf::Transport::PASSTHROUGH) { + mErr << "Fatal: should not reach here. Generated result may be wrong." + << std::endl; + } + done = true; + break; + } + if (hal->hasVersion(version)) { + if (&table != &mImplementationsTable) { + hal->interfaces[interfaceName].name = interfaceName; + hal->interfaces[interfaceName].instances.insert(instanceName); + } + done = true; + break; + } + } + if (done) { + continue; // to next TableEntry + } + decltype(vintf::ManifestHal::interfaces) interfaces; + if (&table != &mImplementationsTable) { + interfaces[interfaceName].name = interfaceName; + interfaces[interfaceName].instances.insert(instanceName); + } + if (!manifest.add(vintf::ManifestHal{ + .format = vintf::HalFormat::HIDL, + .name = fqName.package(), + .versions = {version}, + .transportArch = {transport, arch}, + .interfaces = interfaces})) { + mErr << "Warning: cannot add hal '" << fqInstanceName << "'" << std::endl; + } + } + }); + mOut << vintf::gHalManifestConverter(manifest); +} + +static const std::string &getArchString(Architecture arch) { + static const std::string sStr64 = "64"; + static const std::string sStr32 = "32"; + static const std::string sStrBoth = "32+64"; + static const std::string sStrUnknown = ""; + switch (arch) { + case ARCH64: + return sStr64; + case ARCH32: + return sStr32; + case ARCH_BOTH: + return sStrBoth; + case ARCH_UNKNOWN: // fall through + default: + return sStrUnknown; + } +} + +static Architecture fromBaseArchitecture(::android::hidl::base::V1_0::DebugInfo::Architecture a) { + switch (a) { + case ::android::hidl::base::V1_0::DebugInfo::Architecture::IS_64BIT: + return ARCH64; + case ::android::hidl::base::V1_0::DebugInfo::Architecture::IS_32BIT: + return ARCH32; + case ::android::hidl::base::V1_0::DebugInfo::Architecture::UNKNOWN: // fallthrough + default: + return ARCH_UNKNOWN; + } +} + +void ListCommand::dumpTable() { + mServicesTable.description = + "All binderized services (registered services through hwservicemanager)"; + mPassthroughRefTable.description = + "All interfaces that getService() has ever return as a passthrough interface;\n" + "PIDs / processes shown below might be inaccurate because the process\n" + "might have relinquished the interface or might have died.\n" + "The Server / Server CMD column can be ignored.\n" + "The Clients / Clients CMD column shows all process that have ever dlopen'ed \n" + "the library and successfully fetched the passthrough implementation."; + mImplementationsTable.description = + "All available passthrough implementations (all -impl.so files)"; + forEachTable([this] (const Table &table) { + mOut << table.description << std::endl; + mOut << std::left; + printLine("Interface", "Transport", "Arch", "Server", "Server CMD", + "PTR", "Clients", "Clients CMD"); + + // We're only interested in dumping debug info for already + // instantiated services. There's little value in dumping the + // debug info for a service we create on the fly, so we only operate + // on the "mServicesTable". + sp serviceManager; + if (mEmitDebugInfo && &table == &mServicesTable) { + serviceManager = ::android::hardware::defaultServiceManager(); + } + + for (const auto &entry : table) { + printLine(entry.interfaceName, + entry.transport, + getArchString(entry.arch), + 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, ";")); + + if (serviceManager != nullptr) { + auto pair = splitFirst(entry.interfaceName, '/'); + mLshal.emitDebugInfo(serviceManager, pair.first, pair.second, {}, mOut.buf()); + } + } + mOut << std::endl; + }); + +} + +void ListCommand::dump() { + if (mVintf) { + dumpVintf(); + if (!!mFileOutput) { + mFileOutput.buf().close(); + delete &mFileOutput.buf(); + mFileOutput = nullptr; + } + mOut = std::cout; + } else { + dumpTable(); + } +} + +void ListCommand::putEntry(TableEntrySource source, TableEntry &&entry) { + Table *table = nullptr; + switch (source) { + case HWSERVICEMANAGER_LIST : + table = &mServicesTable; break; + case PTSERVICEMANAGER_REG_CLIENT : + table = &mPassthroughRefTable; break; + case LIST_DLLIB : + table = &mImplementationsTable; break; + default: + mErr << "Error: Unknown source of entry " << source << std::endl; + } + if (table) { + table->entries.push_back(std::forward(entry)); + } +} + +Status ListCommand::fetchAllLibraries(const sp &manager) { + using namespace ::android::hardware; + using namespace ::android::hidl::manager::V1_0; + using namespace ::android::hidl::base::V1_0; + auto ret = timeoutIPC(manager, &IServiceManager::debugDump, [&] (const auto &infos) { + std::map entries; + for (const auto &info : infos) { + std::string interfaceName = std::string{info.interfaceName.c_str()} + "/" + + std::string{info.instanceName.c_str()}; + entries.emplace(interfaceName, TableEntry{ + .interfaceName = interfaceName, + .transport = "passthrough", + .serverPid = NO_PID, + .serverObjectAddress = NO_PTR, + .clientPids = {}, + .arch = ARCH_UNKNOWN + }).first->second.arch |= fromBaseArchitecture(info.arch); + } + for (auto &&pair : entries) { + putEntry(LIST_DLLIB, std::move(pair.second)); + } + }); + if (!ret.isOk()) { + mErr << "Error: Failed to call list on getPassthroughServiceManager(): " + << ret.description() << std::endl; + return DUMP_ALL_LIBS_ERROR; + } + return OK; +} + +Status ListCommand::fetchPassthrough(const sp &manager) { + using namespace ::android::hardware; + using namespace ::android::hardware::details; + using namespace ::android::hidl::manager::V1_0; + using namespace ::android::hidl::base::V1_0; + auto ret = timeoutIPC(manager, &IServiceManager::debugDump, [&] (const auto &infos) { + for (const auto &info : infos) { + if (info.clientPids.size() <= 0) { + continue; + } + putEntry(PTSERVICEMANAGER_REG_CLIENT, { + .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, + .arch = fromBaseArchitecture(info.arch) + }); + } + }); + if (!ret.isOk()) { + mErr << "Error: Failed to call debugDump on defaultServiceManager(): " + << ret.description() << std::endl; + return DUMP_PASSTHROUGH_ERROR; + } + return OK; +} + +Status ListCommand::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"; + + hidl_vec fqInstanceNames; + // copying out for timeoutIPC + auto listRet = timeoutIPC(manager, &IServiceManager::list, [&] (const auto &names) { + fqInstanceNames = names; + }); + if (!listRet.isOk()) { + mErr << "Error: Failed to list services for " << mode << ": " + << listRet.description() << std::endl; + return DUMP_BINDERIZED_ERROR; + } + + Status status = OK; + // server pid, .ptr value of binder object, child pids + std::map allDebugInfos; + std::map> allPids; + for (const auto &fqInstanceName : fqInstanceNames) { + const auto pair = splitFirst(fqInstanceName, '/'); + const auto &serviceName = pair.first; + const auto &instanceName = pair.second; + auto getRet = timeoutIPC(manager, &IServiceManager::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)" + << std::endl; + status |= DUMP_BINDERIZED_ERROR; + continue; + } + auto debugRet = timeoutIPC(service, &IBase::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(HWSERVICEMANAGER_LIST, { + .interfaceName = fqInstanceName, + .transport = mode, + .serverPid = NO_PID, + .serverObjectAddress = NO_PTR, + .clientPids = {}, + .arch = ARCH_UNKNOWN + }); + continue; + } + const DebugInfo &info = it->second; + putEntry(HWSERVICEMANAGER_LIST, { + .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], + .arch = fromBaseArchitecture(info.arch), + }); + } + return status; +} + +Status ListCommand::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; +} + +Status ListCommand::parseArgs(const std::string &command, const Arg &arg) { + static struct option longOptions[] = { + // long options with short alternatives + {"help", no_argument, 0, 'h' }, + {"interface", no_argument, 0, 'i' }, + {"transport", no_argument, 0, 't' }, + {"arch", no_argument, 0, 'r' }, + {"pid", no_argument, 0, 'p' }, + {"address", no_argument, 0, 'a' }, + {"clients", no_argument, 0, 'c' }, + {"cmdline", no_argument, 0, 'm' }, + {"debug", optional_argument, 0, 'd' }, + + // long options without short alternatives + {"sort", required_argument, 0, 's' }, + {"init-vintf",optional_argument, 0, 'v' }, + { 0, 0, 0, 0 } + }; + + int optionIndex; + int c; + // Lshal::parseArgs has set optind to the next option to parse + for (;;) { + // using getopt_long in case we want to add other options in the future + c = getopt_long(arg.argc, arg.argv, + "hitrpacmd", 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; + mLshal.usage(command); + return USAGE; + } + break; + } + case 'v': { + if (optarg) { + mFileOutput = new std::ofstream{optarg}; + mOut = mFileOutput; + if (!mFileOutput.buf().is_open()) { + mErr << "Could not open file '" << optarg << "'." << std::endl; + return IO_ERROR; + } + } + mVintf = true; + } + case 'i': { + mSelectedColumns |= ENABLE_INTERFACE_NAME; + break; + } + case 't': { + mSelectedColumns |= ENABLE_TRANSPORT; + break; + } + case 'r': { + mSelectedColumns |= ENABLE_ARCH; + 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 'd': { + mEmitDebugInfo = true; + + if (optarg) { + mFileOutput = new std::ofstream{optarg}; + mOut = mFileOutput; + if (!mFileOutput.buf().is_open()) { + mErr << "Could not open file '" << optarg << "'." << std::endl; + return IO_ERROR; + } + chown(optarg, AID_SHELL, AID_SHELL); + } + break; + } + case 'h': // falls through + default: // see unrecognized options + mLshal.usage(command); + return USAGE; + } + } + if (optind < arg.argc) { + // see non option + mErr << "Unrecognized option `" << arg.argv[optind] << "`" << std::endl; + } + + if (mSelectedColumns == 0) { + mSelectedColumns = ENABLE_INTERFACE_NAME | ENABLE_SERVER_PID | ENABLE_CLIENT_PIDS; + } + return OK; +} + +Status ListCommand::main(const std::string &command, const Arg &arg) { + Status status = parseArgs(command, arg); + if (status != OK) { + return status; + } + status = fetch(); + postprocess(); + dump(); + return status; +} + +} // namespace lshal +} // namespace android + diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h new file mode 100644 index 0000000000..42c965fb28 --- /dev/null +++ b/cmds/lshal/ListCommand.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 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_LIST_COMMAND_H_ +#define FRAMEWORK_NATIVE_CMDS_LSHAL_LIST_COMMAND_H_ + +#include + +#include +#include +#include + +#include +#include + +#include "NullableOStream.h" +#include "TableEntry.h" +#include "utils.h" + +namespace android { +namespace lshal { + +class Lshal; + +class ListCommand { +public: + ListCommand(Lshal &lshal); + Status main(const std::string &command, const Arg &arg); +private: + Status parseArgs(const std::string &command, const Arg &arg); + Status fetch(); + void postprocess(); + void dump(); + void putEntry(TableEntrySource source, 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; + void dumpTable(); + void dumpVintf() const; + void printLine( + const std::string &interfaceName, + const std::string &transport, + const std::string &arch, + 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); + void forEachTable(const std::function &f); + void forEachTable(const std::function &f) const; + + Lshal &mLshal; + + Table mServicesTable{}; + Table mPassthroughRefTable{}; + Table mImplementationsTable{}; + + NullableOStream mErr; + NullableOStream mOut; + NullableOStream mFileOutput = nullptr; + TableEntryCompare mSortColumn = nullptr; + TableEntrySelect mSelectedColumns = 0; + // If true, cmdlines will be printed instead of pid. + bool mEnableCmdlines = false; + + // If true, calls IBase::debug(...) on each service. + bool mEmitDebugInfo = false; + + bool mVintf = false; + // 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; + + DISALLOW_COPY_AND_ASSIGN(ListCommand); +}; + + +} // namespace lshal +} // namespace android + +#endif // FRAMEWORK_NATIVE_CMDS_LSHAL_LIST_COMMAND_H_ diff --git a/cmds/lshal/Lshal.cpp b/cmds/lshal/Lshal.cpp index 85d89389cc..43108b7925 100644 --- a/cmds/lshal/Lshal.cpp +++ b/cmds/lshal/Lshal.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 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. @@ -16,379 +16,108 @@ #include "Lshal.h" -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include "ListCommand.h" #include "PipeRelay.h" -#include "Timeout.h" - -using ::android::hardware::hidl_string; -using ::android::hidl::manager::V1_0::IServiceManager; namespace android { namespace lshal { -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(); -} - -template -static std::pair splitFirst(const String &s, char c) { - const char *pos = strchr(s.c_str(), c); - if (pos == nullptr) { - return {s, {}}; - } - return {String(s.c_str(), pos - s.c_str()), 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; -} - -static void replaceAll(std::string *s, char from, char to) { - for (size_t i = 0; i < s->size(); ++i) { - if (s->at(i) == from) { - s->at(i) = to; - } - } -} - -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 { - - 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; -} - -// Must process hwbinder services first, then passthrough services. -void Lshal::forEachTable(const std::function &f) { - f(mServicesTable); - f(mPassthroughRefTable); - f(mImplementationsTable); -} -void Lshal::forEachTable(const std::function &f) const { - f(mServicesTable); - f(mPassthroughRefTable); - f(mImplementationsTable); -} - -void Lshal::postprocess() { - forEachTable([this](Table &table) { - if (mSortColumn) { - std::sort(table.begin(), table.end(), mSortColumn); - } - for (TableEntry &entry : table) { - entry.serverCmdline = getCmdline(entry.serverPid); - removeDeadProcesses(&entry.clientPids); - for (auto pid : entry.clientPids) { - entry.clientCmdlines.push_back(this->getCmdline(pid)); - } - } - }); - // use a double for loop here because lshal doesn't care about efficiency. - for (TableEntry &packageEntry : mImplementationsTable) { - std::string packageName = packageEntry.interfaceName; - FQName fqPackageName{packageName.substr(0, packageName.find("::"))}; - if (!fqPackageName.isValid()) { - continue; - } - for (TableEntry &interfaceEntry : mPassthroughRefTable) { - if (interfaceEntry.arch != ARCH_UNKNOWN) { - continue; - } - FQName interfaceName{splitFirst(interfaceEntry.interfaceName, '/').first}; - if (!interfaceName.isValid()) { - continue; - } - if (interfaceName.getPackageAndVersion() == fqPackageName) { - interfaceEntry.arch = packageEntry.arch; - } - } - } -} +using ::android::hidl::manager::V1_0::IServiceManager; -void Lshal::printLine( - const std::string &interfaceName, - const std::string &transport, - const std::string &arch, - 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_ARCH) - mOut << std::setw(5) << arch << "\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; - } +Lshal::Lshal() { +} + +void Lshal::usage(const std::string &command) const { + static const std::string helpSummary = + "lshal: List and debug HALs.\n" + "\n" + "commands:\n" + " help Print help message\n" + " list list HALs\n" + " debug debug a specified HAL\n" + "\n" + "If no command is specified, `list` is the default.\n"; + + static const std::string list = + "list:\n" + " lshal\n" + " lshal list\n" + " List all hals with default ordering and columns (`lshal list -ipc`)\n" + " lshal list [-h|--help]\n" + " -h, --help: Print help message for list (`lshal help list`)\n" + " lshal [list] [--interface|-i] [--transport|-t] [-r|--arch]\n" + " [--pid|-p] [--address|-a] [--clients|-c] [--cmdline|-m]\n" + " [--sort={interface|i|pid|p}] [--init-vintf[=]]\n" + " [--debug|-d[=]]\n" + " -i, --interface: print the interface name column\n" + " -n, --instance: print the instance name column\n" + " -t, --transport: print the transport mode column\n" + " -r, --arch: print if the HAL is in 64-bit or 32-bit\n" + " -p, --pid: print the server PID, or server cmdline if -m is set\n" + " -a, --address: print the server object address column\n" + " -c, --clients: print the client PIDs, or client cmdlines if -m is set\n" + " -m, --cmdline: print cmdline instead of PIDs\n" + " -d[=], --debug[=]: emit debug info from \n" + " IBase::debug with empty options\n" + " --sort=i, --sort=interface: sort by interface name\n" + " --sort=p, --sort=pid: sort by server pid\n" + " --init-vintf=: form a skeleton HAL manifest to specified\n" + " file, or stdout if no file specified.\n"; + + static const std::string debug = + "debug:\n" + " lshal debug [options [options [...]]] \n" + " Print debug information of a specified interface.\n" + " : Format is `android.hardware.foo@1.0::IFoo/default`.\n" + " If instance name is missing `default` is used.\n" + " options: space separated options to IBase::debug.\n"; + + static const std::string help = + "help:\n" + " lshal -h\n" + " lshal --help\n" + " lshal help\n" + " Print this help message\n" + " lshal help list\n" + " Print help message for list\n" + " lshal help debug\n" + " Print help message for debug\n"; + + if (command == "list") { + mErr << list; + return; } - mOut << std::endl; -} - -void Lshal::dumpVintf() const { - mOut << "" << std::endl; - - vintf::HalManifest manifest; - forEachTable([this, &manifest] (const Table &table) { - for (const TableEntry &entry : table) { - - std::string fqInstanceName = entry.interfaceName; - - if (&table == &mImplementationsTable) { - // Quick hack to work around *'s - replaceAll(&fqInstanceName, '*', 'D'); - } - auto splittedFqInstanceName = splitFirst(fqInstanceName, '/'); - FQName fqName(splittedFqInstanceName.first); - if (!fqName.isValid()) { - mErr << "Warning: '" << splittedFqInstanceName.first - << "' is not a valid FQName." << std::endl; - continue; - } - // Strip out system libs. - if (fqName.inPackage("android.hidl") || - fqName.inPackage("android.frameworks") || - fqName.inPackage("android.system")) { - continue; - } - std::string interfaceName = - &table == &mImplementationsTable ? "" : fqName.name(); - std::string instanceName = - &table == &mImplementationsTable ? "" : splittedFqInstanceName.second; - - vintf::Version version{fqName.getPackageMajorVersion(), - fqName.getPackageMinorVersion()}; - vintf::Transport transport; - vintf::Arch arch; - if (entry.transport == "hwbinder") { - transport = vintf::Transport::HWBINDER; - arch = vintf::Arch::ARCH_EMPTY; - } else if (entry.transport == "passthrough") { - transport = vintf::Transport::PASSTHROUGH; - switch (entry.arch) { - case lshal::ARCH32: - arch = vintf::Arch::ARCH_32; break; - case lshal::ARCH64: - arch = vintf::Arch::ARCH_64; break; - case lshal::ARCH_BOTH: - arch = vintf::Arch::ARCH_32_64; break; - case lshal::ARCH_UNKNOWN: // fallthrough - default: - mErr << "Warning: '" << fqName.package() - << "' doesn't have bitness info, assuming 32+64." << std::endl; - arch = vintf::Arch::ARCH_32_64; - } - } else { - mErr << "Warning: '" << entry.transport << "' is not a valid transport." << std::endl; - continue; - } - - bool done = false; - for (vintf::ManifestHal *hal : manifest.getHals(fqName.package())) { - if (hal->transport() != transport) { - if (transport != vintf::Transport::PASSTHROUGH) { - mErr << "Fatal: should not reach here. Generated result may be wrong." - << std::endl; - } - done = true; - break; - } - if (hal->hasVersion(version)) { - if (&table != &mImplementationsTable) { - hal->interfaces[interfaceName].name = interfaceName; - hal->interfaces[interfaceName].instances.insert(instanceName); - } - done = true; - break; - } - } - if (done) { - continue; // to next TableEntry - } - decltype(vintf::ManifestHal::interfaces) interfaces; - if (&table != &mImplementationsTable) { - interfaces[interfaceName].name = interfaceName; - interfaces[interfaceName].instances.insert(instanceName); - } - if (!manifest.add(vintf::ManifestHal{ - .format = vintf::HalFormat::HIDL, - .name = fqName.package(), - .versions = {version}, - .transportArch = {transport, arch}, - .interfaces = interfaces})) { - mErr << "Warning: cannot add hal '" << fqInstanceName << "'" << std::endl; - } - } - }); - mOut << vintf::gHalManifestConverter(manifest); -} - -static const std::string &getArchString(Architecture arch) { - static const std::string sStr64 = "64"; - static const std::string sStr32 = "32"; - static const std::string sStrBoth = "32+64"; - static const std::string sStrUnknown = ""; - switch (arch) { - case ARCH64: - return sStr64; - case ARCH32: - return sStr32; - case ARCH_BOTH: - return sStrBoth; - case ARCH_UNKNOWN: // fall through - default: - return sStrUnknown; + if (command == "debug") { + mErr << debug; + return; } -} -static Architecture fromBaseArchitecture(::android::hidl::base::V1_0::DebugInfo::Architecture a) { - switch (a) { - case ::android::hidl::base::V1_0::DebugInfo::Architecture::IS_64BIT: - return ARCH64; - case ::android::hidl::base::V1_0::DebugInfo::Architecture::IS_32BIT: - return ARCH32; - case ::android::hidl::base::V1_0::DebugInfo::Architecture::UNKNOWN: // fallthrough - default: - return ARCH_UNKNOWN; - } + mErr << helpSummary << "\n" << list << "\n" << debug << "\n" << help; } // A unique_ptr type using a custom deleter function. template using deleted_unique_ptr = std::unique_ptr >; +static hardware::hidl_vec convert(const std::vector &v) { + hardware::hidl_vec hv; + hv.resize(v.size()); + for (size_t i = 0; i < v.size(); ++i) { + hv[i].setToExternal(v[i].c_str(), v[i].size()); + } + return hv; +} + +// static void Lshal::emitDebugInfo( const sp &serviceManager, const std::string &interfaceName, - const std::string &instanceName) const { + const std::string &instanceName, + const std::vector &options, + std::ostream &out) { using android::hidl::base::V1_0::IBase; hardware::Return> retBase = @@ -396,16 +125,14 @@ void Lshal::emitDebugInfo( sp base; if (!retBase.isOk() || (base = retBase) == nullptr) { - // There's a small race, where a service instantiated while collecting - // the list of services has by now terminated, so this isn't anything - // to be concerned about. + mErr << interfaceName << "/" << instanceName << " does not exist." << std::endl; return; } - PipeRelay relay(mOut.buf()); + PipeRelay relay(out); if (relay.initCheck() != OK) { - LOG(ERROR) << "PipeRelay::initCheck() FAILED w/ " << relay.initCheck(); + mErr << "PipeRelay::initCheck() FAILED w/ " << relay.initCheck() << std::endl; return; } @@ -415,8 +142,7 @@ void Lshal::emitDebugInfo( fdHandle->data[0] = relay.fd(); - hardware::hidl_vec options; - hardware::Return ret = base->debug(fdHandle.get(), options); + hardware::Return ret = base->debug(fdHandle.get(), convert(options)); if (!ret.isOk()) { LOG(ERROR) @@ -427,395 +153,56 @@ void Lshal::emitDebugInfo( } } -void Lshal::dumpTable() { - mServicesTable.description = - "All binderized services (registered services through hwservicemanager)"; - mPassthroughRefTable.description = - "All interfaces that getService() has ever return as a passthrough interface;\n" - "PIDs / processes shown below might be inaccurate because the process\n" - "might have relinquished the interface or might have died.\n" - "The Server / Server CMD column can be ignored.\n" - "The Clients / Clients CMD column shows all process that have ever dlopen'ed \n" - "the library and successfully fetched the passthrough implementation."; - mImplementationsTable.description = - "All available passthrough implementations (all -impl.so files)"; - forEachTable([this] (const Table &table) { - mOut << table.description << std::endl; - mOut << std::left; - printLine("Interface", "Transport", "Arch", "Server", "Server CMD", - "PTR", "Clients", "Clients CMD"); - - // We're only interested in dumping debug info for already - // instantiated services. There's little value in dumping the - // debug info for a service we create on the fly, so we only operate - // on the "mServicesTable". - sp serviceManager; - if (mEmitDebugInfo && &table == &mServicesTable) { - serviceManager = ::android::hardware::defaultServiceManager(); - } - - for (const auto &entry : table) { - printLine(entry.interfaceName, - entry.transport, - getArchString(entry.arch), - 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, ";")); - - if (serviceManager != nullptr) { - auto pair = splitFirst(entry.interfaceName, '/'); - emitDebugInfo(serviceManager, pair.first, pair.second); - } - } - mOut << std::endl; - }); - -} - -void Lshal::dump() { - if (mVintf) { - dumpVintf(); - if (!!mFileOutput) { - mFileOutput.buf().close(); - delete &mFileOutput.buf(); - mFileOutput = nullptr; - } - mOut = std::cout; - } else { - dumpTable(); - } -} - -void Lshal::putEntry(TableEntrySource source, TableEntry &&entry) { - Table *table = nullptr; - switch (source) { - case HWSERVICEMANAGER_LIST : - table = &mServicesTable; break; - case PTSERVICEMANAGER_REG_CLIENT : - table = &mPassthroughRefTable; break; - case LIST_DLLIB : - table = &mImplementationsTable; break; - default: - mErr << "Error: Unknown source of entry " << source << std::endl; +Status Lshal::parseArgs(const Arg &arg) { + static std::set sAllCommands{"list", "debug", "help"}; + optind = 1; + if (optind >= arg.argc) { + // no options at all. + return OK; } - if (table) { - table->entries.push_back(std::forward(entry)); + mCommand = arg.argv[optind]; + if (sAllCommands.find(mCommand) != sAllCommands.end()) { + ++optind; + return OK; // mCommand is set correctly } -} -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 = timeoutIPC(manager, &IServiceManager::debugDump, [&] (const auto &infos) { - std::map entries; - for (const auto &info : infos) { - std::string interfaceName = std::string{info.interfaceName.c_str()} + "/" + - std::string{info.instanceName.c_str()}; - entries.emplace(interfaceName, TableEntry{ - .interfaceName = interfaceName, - .transport = "passthrough", - .serverPid = NO_PID, - .serverObjectAddress = NO_PTR, - .clientPids = {}, - .arch = ARCH_UNKNOWN - }).first->second.arch |= fromBaseArchitecture(info.arch); - } - for (auto &&pair : entries) { - putEntry(LIST_DLLIB, std::move(pair.second)); - } - }); - if (!ret.isOk()) { - mErr << "Error: Failed to call list on getPassthroughServiceManager(): " - << ret.description() << std::endl; - return DUMP_ALL_LIBS_ERROR; + if (mCommand.size() > 0 && mCommand[0] == '-') { + // first argument is an option, set command to "" (which is recognized as "list") + mCommand = ""; + return OK; } - return OK; -} -Status Lshal::fetchPassthrough(const sp &manager) { - using namespace ::android::hardware; - using namespace ::android::hardware::details; - using namespace ::android::hidl::manager::V1_0; - using namespace ::android::hidl::base::V1_0; - auto ret = timeoutIPC(manager, &IServiceManager::debugDump, [&] (const auto &infos) { - for (const auto &info : infos) { - if (info.clientPids.size() <= 0) { - continue; - } - putEntry(PTSERVICEMANAGER_REG_CLIENT, { - .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, - .arch = fromBaseArchitecture(info.arch) - }); - } - }); - if (!ret.isOk()) { - mErr << "Error: Failed to call debugDump on defaultServiceManager(): " - << ret.description() << std::endl; - return DUMP_PASSTHROUGH_ERROR; - } - return OK; + mErr << arg.argv[0] << ": unrecognized option `" << arg.argv[optind] << "`" << std::endl; + usage(); + return USAGE; } -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"; - - hidl_vec fqInstanceNames; - // copying out for timeoutIPC - auto listRet = timeoutIPC(manager, &IServiceManager::list, [&] (const auto &names) { - fqInstanceNames = names; - }); - if (!listRet.isOk()) { - mErr << "Error: Failed to list services for " << mode << ": " - << listRet.description() << std::endl; - return DUMP_BINDERIZED_ERROR; - } - - Status status = OK; - // server pid, .ptr value of binder object, child pids - std::map allDebugInfos; - std::map> allPids; - for (const auto &fqInstanceName : fqInstanceNames) { - const auto pair = splitFirst(fqInstanceName, '/'); - const auto &serviceName = pair.first; - const auto &instanceName = pair.second; - auto getRet = timeoutIPC(manager, &IServiceManager::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 = timeoutIPC(service, &IBase::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; - } +Status Lshal::main(const Arg &arg) { + Status status = parseArgs(arg); + if (status != OK) { + return status; } - for (const auto &fqInstanceName : fqInstanceNames) { - auto it = allDebugInfos.find(fqInstanceName); - if (it == allDebugInfos.end()) { - putEntry(HWSERVICEMANAGER_LIST, { - .interfaceName = fqInstanceName, - .transport = mode, - .serverPid = NO_PID, - .serverObjectAddress = NO_PTR, - .clientPids = {}, - .arch = ARCH_UNKNOWN - }); - continue; - } - const DebugInfo &info = it->second; - putEntry(HWSERVICEMANAGER_LIST, { - .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], - .arch = fromBaseArchitecture(info.arch), - }); + if (mCommand == "help") { + usage(optind < arg.argc ? arg.argv[optind] : ""); + return USAGE; } - 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); + // Default command is list + if (mCommand == "list" || mCommand == "") { + return ListCommand{*this}.main(mCommand, arg); } - - auto pManager = ::android::hardware::getPassthroughServiceManager(); - if (pManager == nullptr) { - mErr << "Failed to get getPassthroughServiceManager()!" << std::endl; - status |= NO_PASSTHROUGH_MANAGER; - } else { - status |= fetchAllLibraries(pManager); + if (mCommand == "debug") { + // TODO(b/37725279) implement this + return OK; } - return status; -} - -void Lshal::usage() const { - mErr - << "usage: lshal" << std::endl - << " Dump all hals with default ordering and columns [-ipc]." << std::endl - << " lshal [--interface|-i] [--transport|-t] [-r|--arch]" << std::endl - << " [--pid|-p] [--address|-a] [--clients|-c] [--cmdline|-m]" << std::endl - << " [--sort={interface|i|pid|p}] [--init-vintf[=path]]" << 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 - << " -r, --arch: print if the HAL is in 64-bit or 32-bit" << 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 - << " --init-vintf=path: form a skeleton HAL manifest to specified file " << std::endl - << " (stdout if no file specified)" << std::endl - << " lshal [-h|--help]" << std::endl - << " -h, --help: show this help information." << std::endl; + usage(); + return USAGE; } -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' }, - {"arch", no_argument, 0, 'r' }, - {"pid", no_argument, 0, 'p' }, - {"address", no_argument, 0, 'a' }, - {"clients", no_argument, 0, 'c' }, - {"cmdline", no_argument, 0, 'm' }, - {"debug", optional_argument, 0, 'd' }, - - // long options without short alternatives - {"sort", required_argument, 0, 's' }, - {"init-vintf",optional_argument, 0, 'v' }, - { 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, "hitrpacmd", 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 'v': { - if (optarg) { - mFileOutput = new std::ofstream{optarg}; - mOut = mFileOutput; - if (!mFileOutput.buf().is_open()) { - mErr << "Could not open file '" << optarg << "'." << std::endl; - return IO_ERROR; - } - } - mVintf = true; - } - case 'i': { - mSelectedColumns |= ENABLE_INTERFACE_NAME; - break; - } - case 't': { - mSelectedColumns |= ENABLE_TRANSPORT; - break; - } - case 'r': { - mSelectedColumns |= ENABLE_ARCH; - 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 'd': { - mEmitDebugInfo = true; - - if (optarg) { - mFileOutput = new std::ofstream{optarg}; - mOut = mFileOutput; - if (!mFileOutput.buf().is_open()) { - mErr << "Could not open file '" << optarg << "'." << std::endl; - return IO_ERROR; - } - chown(optarg, AID_SHELL, AID_SHELL); - } - break; - } - case 'h': // falls through - default: // see unrecognized options - usage(); - return USAGE; - } - } - - if (mSelectedColumns == 0) { - mSelectedColumns = ENABLE_INTERFACE_NAME | ENABLE_SERVER_PID | ENABLE_CLIENT_PIDS; - } - return OK; +NullableOStream Lshal::err() const { + return mErr; } - -int Lshal::main(int argc, char **argv) { - Status status = parseArgs(argc, argv); - if (status != OK) { - return status; - } - status = fetch(); - postprocess(); - dump(); - return status; +NullableOStream Lshal::out() const { + return mOut; } void signalHandler(int sig) { @@ -829,6 +216,7 @@ void signalHandler(int sig) { } // namespace android int main(int argc, char **argv) { - signal(SIGINT, ::android::lshal::signalHandler); - return ::android::lshal::Lshal{}.main(argc, argv); + using namespace ::android::lshal; + signal(SIGINT, signalHandler); + return Lshal{}.main(Arg{argc, argv}); } diff --git a/cmds/lshal/Lshal.h b/cmds/lshal/Lshal.h index a21e86cf0f..cabf50399c 100644 --- a/cmds/lshal/Lshal.h +++ b/cmds/lshal/Lshal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 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. @@ -17,94 +17,43 @@ #ifndef FRAMEWORK_NATIVE_CMDS_LSHAL_LSHAL_H_ #define FRAMEWORK_NATIVE_CMDS_LSHAL_LSHAL_H_ -#include - -#include +#include #include -#include +#include #include +#include #include "NullableOStream.h" -#include "TableEntry.h" +#include "utils.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, - IO_ERROR = 1 << 6, -}; -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(); - void usage() const; - void putEntry(TableEntrySource source, 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; - void dumpTable(); - void dumpVintf() const; - void printLine( - const std::string &interfaceName, - const std::string &transport, - const std::string &arch, - 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); - void forEachTable(const std::function &f); - void forEachTable(const std::function &f) const; + Lshal(); + Status main(const Arg &arg); + void usage(const std::string &command = "") const; + NullableOStream err() const; + NullableOStream out() const; - void emitDebugInfo( + static void emitDebugInfo( const sp &serviceManager, const std::string &interfaceName, - const std::string &instanceName) const; - - Table mServicesTable{}; - Table mPassthroughRefTable{}; - Table mImplementationsTable{}; - + const std::string &instanceName, + const std::vector &options, + std::ostream &out); +private: + Status parseArgs(const Arg &arg); + std::string mCommand; + Arg mCmdArgs; NullableOStream mErr = std::cerr; NullableOStream mOut = std::cout; - NullableOStream mFileOutput = nullptr; - TableEntryCompare mSortColumn = nullptr; - TableEntrySelect mSelectedColumns = 0; - // If true, cmdlines will be printed instead of pid. - bool mEnableCmdlines = false; - // If true, calls IBase::debug(...) on each service. - bool mEmitDebugInfo = false; - - bool mVintf = false; - // 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; + DISALLOW_COPY_AND_ASSIGN(Lshal); }; - } // namespace lshal } // namespace android diff --git a/cmds/lshal/utils.cpp b/cmds/lshal/utils.cpp new file mode 100644 index 0000000000..555072185b --- /dev/null +++ b/cmds/lshal/utils.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 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 "utils.h" + +namespace android { +namespace lshal { + +std::string toHexString(uint64_t t) { + std::ostringstream os; + os << std::hex << std::setfill('0') << std::setw(16) << t; + return os.str(); +} + +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; +} + +void replaceAll(std::string *s, char from, char to) { + for (size_t i = 0; i < s->size(); ++i) { + if (s->at(i) == from) { + s->at(i) = to; + } + } +} + +} // namespace lshal +} // namespace android + diff --git a/cmds/lshal/utils.h b/cmds/lshal/utils.h new file mode 100644 index 0000000000..972009453f --- /dev/null +++ b/cmds/lshal/utils.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 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_UTILS_H_ +#define FRAMEWORK_NATIVE_CMDS_LSHAL_UTILS_H_ + +#include +#include +#include +#include +#include +#include + +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, + IO_ERROR = 1 << 6, +}; +using Status = unsigned int; + +struct Arg { + int argc; + char **argv; +}; + +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); + +template +std::pair splitFirst(const String &s, char c) { + const char *pos = strchr(s.c_str(), c); + if (pos == nullptr) { + return {s, {}}; + } + return {String(s.c_str(), pos - s.c_str()), String(pos + 1)}; +} + +std::vector split(const std::string &s, char c); + +void replaceAll(std::string *s, char from, char to); + +} // namespace lshal +} // namespace android + +#endif // FRAMEWORK_NATIVE_CMDS_LSHAL_UTILS_H_ -- 2.11.0