Merge changes from topic "lshal_released" am: 136c4bc7ab am: 3985d37c0d am: 0cc634548c
am: 1776f7562e

Change-Id: If83d5343b4c1eee91940874cc4cc8fc4d0bdb04b
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp
index 5a87505..6cbe7e2 100644
--- a/cmds/lshal/Android.bp
+++ b/cmds/lshal/Android.bp
@@ -20,6 +20,7 @@
         "libutils",
         "libhidlbase",
         "libhidltransport",
+        "libhidl-gen-hash",
         "libhidl-gen-utils",
         "libvintf",
     ],
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index 8b59fb8..7399692 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -27,6 +27,7 @@
 
 #include <android-base/parseint.h>
 #include <android/hidl/manager/1.0/IServiceManager.h>
+#include <hidl-hash/Hash.h>
 #include <hidl-util/FQName.h>
 #include <private/android_filesystem_config.h>
 #include <sys/stat.h>
@@ -39,6 +40,9 @@
 #include "utils.h"
 
 using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hidl::base::V1_0::DebugInfo;
+using ::android::hidl::base::V1_0::IBase;
 using ::android::hidl::manager::V1_0::IServiceManager;
 
 namespace android {
@@ -85,7 +89,7 @@
     }), pids->end());
 }
 
-bool scanBinderContext(pid_t pid,
+static bool scanBinderContext(pid_t pid,
         const std::string &contextName,
         std::function<void(const std::string&)> eachLine) {
     std::ifstream ifs("/d/binder/proc/" + std::to_string(pid));
@@ -171,6 +175,16 @@
     });
 }
 
+const PidInfo* ListCommand::getPidInfoCached(pid_t serverPid) {
+    auto pair = mCachedPidInfos.insert({serverPid, PidInfo{}});
+    if (pair.second /* did insertion take place? */) {
+        if (!getPidInfo(serverPid, &pair.first->second)) {
+            return nullptr;
+        }
+    }
+    return &pair.first->second;
+}
+
 // Must process hwbinder services first, then passthrough services.
 void ListCommand::forEachTable(const std::function<void(Table &)> &f) {
     f(mServicesTable);
@@ -445,10 +459,7 @@
             entries.emplace(interfaceName, TableEntry{
                 .interfaceName = interfaceName,
                 .transport = "passthrough",
-                .serverPid = NO_PID,
-                .serverObjectAddress = NO_PTR,
                 .clientPids = info.clientPids,
-                .arch = ARCH_UNKNOWN
             }).first->second.arch |= fromBaseArchitecture(info.arch);
         }
         for (auto &&pair : entries) {
@@ -479,7 +490,6 @@
                         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)
             });
@@ -494,10 +504,6 @@
 }
 
 Status ListCommand::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;
     const std::string mode = "hwbinder";
 
     hidl_vec<hidl_string> fqInstanceNames;
@@ -512,80 +518,117 @@
     }
 
     Status status = OK;
-    // server pid, .ptr value of binder object, child pids
-    std::map<std::string, DebugInfo> allDebugInfos;
-    std::map<pid_t, PidInfo> allPids;
+    std::map<std::string, TableEntry> allTableEntries;
     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()) {
-            err() << "Warning: Skipping \"" << fqInstanceName << "\": "
-                 << "cannot be fetched from service manager:"
-                 << getRet.description() << std::endl;
-            status |= DUMP_BINDERIZED_ERROR;
-            continue;
-        }
-        sp<IBase> service = getRet;
-        if (service == nullptr) {
-            err() << "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<pid_t>(debugInfo.pid)] = PidInfo();
-            }
+        // create entry and default assign all fields.
+        TableEntry& entry = allTableEntries[fqInstanceName];
+        entry.interfaceName = fqInstanceName;
+        entry.transport = mode;
+
+        status |= fetchBinderizedEntry(manager, &entry);
+    }
+
+    for (auto& pair : allTableEntries) {
+        putEntry(HWSERVICEMANAGER_LIST, std::move(pair.second));
+    }
+    return status;
+}
+
+Status ListCommand::fetchBinderizedEntry(const sp<IServiceManager> &manager,
+                                         TableEntry *entry) {
+    Status status = OK;
+    const auto handleError = [&](Status additionalError, const std::string& msg) {
+        err() << "Warning: Skipping \"" << entry->interfaceName << "\": " << msg << std::endl;
+        status |= DUMP_BINDERIZED_ERROR | additionalError;
+    };
+
+    const auto pair = splitFirst(entry->interfaceName, '/');
+    const auto &serviceName = pair.first;
+    const auto &instanceName = pair.second;
+    auto getRet = timeoutIPC(manager, &IServiceManager::get, serviceName, instanceName);
+    if (!getRet.isOk()) {
+        handleError(TRANSACTION_ERROR,
+                    "cannot be fetched from service manager:" + getRet.description());
+        return status;
+    }
+    sp<IBase> service = getRet;
+    if (service == nullptr) {
+        handleError(NO_INTERFACE, "cannot be fetched from service manager (null)");
+        return status;
+    }
+
+    // getDebugInfo
+    do {
+        DebugInfo debugInfo;
+        auto debugRet = timeoutIPC(service, &IBase::getDebugInfo, [&] (const auto &received) {
+            debugInfo = received;
         });
         if (!debugRet.isOk()) {
-            err() << "Warning: Skipping \"" << fqInstanceName << "\": "
-                 << "debugging information cannot be retrieved:"
-                 << debugRet.description() << std::endl;
-            status |= DUMP_BINDERIZED_ERROR;
+            handleError(TRANSACTION_ERROR,
+                        "debugging information cannot be retrieved: " + debugRet.description());
+            break; // skip getPidInfo
         }
-    }
 
-    for (auto &pair : allPids) {
-        pid_t serverPid = pair.first;
-        if (!getPidInfo(serverPid, &allPids[serverPid])) {
-            err() << "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 = {},
-                .threadUsage = 0,
-                .threadCount = 0,
-                .arch = ARCH_UNKNOWN
-            });
-            continue;
-        }
-        const DebugInfo &info = it->second;
-        bool writePidInfo = info.pid != NO_PID && info.ptr != NO_PTR;
+        entry->serverPid = debugInfo.pid;
+        entry->serverObjectAddress = debugInfo.ptr;
+        entry->arch = fromBaseArchitecture(debugInfo.arch);
 
-        putEntry(HWSERVICEMANAGER_LIST, {
-            .interfaceName = fqInstanceName,
-            .transport = mode,
-            .serverPid = info.pid,
-            .serverObjectAddress = info.ptr,
-            .clientPids = writePidInfo ? allPids[info.pid].refPids[info.ptr] : Pids{},
-            .threadUsage = writePidInfo ? allPids[info.pid].threadUsage : 0,
-            .threadCount = writePidInfo ? allPids[info.pid].threadCount : 0,
-            .arch = fromBaseArchitecture(info.arch),
+        if (debugInfo.pid != NO_PID) {
+            const PidInfo* pidInfo = getPidInfoCached(debugInfo.pid);
+            if (pidInfo == nullptr) {
+                handleError(IO_ERROR,
+                            "no information for PID " + std::to_string(debugInfo.pid) +
+                            ", are you root?");
+                break;
+            }
+            if (debugInfo.ptr != NO_PTR) {
+                auto it = pidInfo->refPids.find(debugInfo.ptr);
+                if (it != pidInfo->refPids.end()) {
+                    entry->clientPids = it->second;
+                }
+            }
+            entry->threadUsage = pidInfo->threadUsage;
+            entry->threadCount = pidInfo->threadCount;
+        }
+    } while (0);
+
+    // hash
+    do {
+        ssize_t hashIndex = -1;
+        auto ifaceChainRet = timeoutIPC(service, &IBase::interfaceChain, [&] (const auto& c) {
+            for (size_t i = 0; i < c.size(); ++i) {
+                if (serviceName == c[i]) {
+                    hashIndex = static_cast<ssize_t>(i);
+                    break;
+                }
+            }
         });
-    }
+        if (!ifaceChainRet.isOk()) {
+            handleError(TRANSACTION_ERROR,
+                        "interfaceChain fails: " + ifaceChainRet.description());
+            break; // skip getHashChain
+        }
+        if (hashIndex < 0) {
+            handleError(BAD_IMPL, "Interface name does not exist in interfaceChain.");
+            break; // skip getHashChain
+        }
+        auto hashRet = timeoutIPC(service, &IBase::getHashChain, [&] (const auto& hashChain) {
+            if (static_cast<size_t>(hashIndex) >= hashChain.size()) {
+                handleError(BAD_IMPL,
+                            "interfaceChain indicates position " + std::to_string(hashIndex) +
+                            " but getHashChain returns " + std::to_string(hashChain.size()) +
+                            " hashes");
+                return;
+            }
+
+            auto&& hashArray = hashChain[hashIndex];
+            std::vector<uint8_t> hashVec{hashArray.data(), hashArray.data() + hashArray.size()};
+            entry->hash = Hash::hexString(hashVec);
+        });
+        if (!hashRet.isOk()) {
+            handleError(TRANSACTION_ERROR, "getHashChain failed: " + hashRet.description());
+        }
+    } while (0);
     return status;
 }
 
@@ -623,6 +666,10 @@
         thiz->mSelectedColumns.push_back(TableColumnType::INTERFACE_NAME);
         return OK;
     }, "print the instance name column"});
+    mOptions.push_back({'l', "released", no_argument, v++, [](ListCommand* thiz, const char*) {
+        thiz->mSelectedColumns.push_back(TableColumnType::RELEASED);
+        return OK;
+    }, "print the 'is released?' column\n(Y=released, empty=unreleased or unknown)"});
     mOptions.push_back({'t', "transport", no_argument, v++, [](ListCommand* thiz, const char*) {
         thiz->mSelectedColumns.push_back(TableColumnType::TRANSPORT);
         return OK;
@@ -631,6 +678,10 @@
         thiz->mSelectedColumns.push_back(TableColumnType::ARCH);
         return OK;
     }, "print the bitness column"});
+    mOptions.push_back({'s', "hash", no_argument, v++, [](ListCommand* thiz, const char*) {
+        thiz->mSelectedColumns.push_back(TableColumnType::HASH);
+        return OK;
+    }, "print hash of the interface"});
     mOptions.push_back({'p', "pid", no_argument, v++, [](ListCommand* thiz, const char*) {
         thiz->mSelectedColumns.push_back(TableColumnType::SERVER_PID);
         return OK;
@@ -773,7 +824,8 @@
     }
 
     if (mSelectedColumns.empty()) {
-        mSelectedColumns = {TableColumnType::INTERFACE_NAME, TableColumnType::THREADS,
+        mSelectedColumns = {TableColumnType::RELEASED,
+                            TableColumnType::INTERFACE_NAME, TableColumnType::THREADS,
                             TableColumnType::SERVER_PID, TableColumnType::CLIENT_PIDS};
     }
 
@@ -841,7 +893,7 @@
     err() << "list:" << std::endl
           << "    lshal" << std::endl
           << "    lshal list" << std::endl
-          << "        List all hals with default ordering and columns (`lshal list -iepc`)" << std::endl
+          << "        List all hals with default ordering and columns (`lshal list -riepc`)" << std::endl
           << "    lshal list [-h|--help]" << std::endl
           << "        -h, --help: Print help message for list (`lshal help list`)" << std::endl
           << "    lshal [list] [OPTIONS...]" << std::endl;
diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h
index 5bc834c..7e252fc 100644
--- a/cmds/lshal/ListCommand.h
+++ b/cmds/lshal/ListCommand.h
@@ -78,14 +78,21 @@
 protected:
     Status parseArgs(const Arg &arg);
     Status fetch();
-    void postprocess();
+    virtual void postprocess();
     Status 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);
 
+    Status fetchBinderizedEntry(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager,
+                                TableEntry *entry);
+
+    // Get relevant information for a PID by parsing files under /d/binder.
+    // It is a virtual member function so that it can be mocked.
     virtual bool getPidInfo(pid_t serverPid, PidInfo *info) const;
+    // Retrieve from mCachedPidInfos and call getPidInfo if necessary.
+    const PidInfo* getPidInfoCached(pid_t serverPid);
 
     void dumpTable(const NullableOStream<std::ostream>& out) const;
     void dumpVintf(const NullableOStream<std::ostream>& out) const;
@@ -129,6 +136,9 @@
     // If an entry exist and not empty, it contains the cached content of /proc/{pid}/cmdline.
     std::map<pid_t, std::string> mCmdlines;
 
+    // Cache for getPidInfo.
+    std::map<pid_t, PidInfo> mCachedPidInfos;
+
     RegisteredOptions mOptions;
     // All selected columns
     std::vector<TableColumnType> mSelectedColumns;
diff --git a/cmds/lshal/TableEntry.cpp b/cmds/lshal/TableEntry.cpp
index cbcf979..e8792a4 100644
--- a/cmds/lshal/TableEntry.cpp
+++ b/cmds/lshal/TableEntry.cpp
@@ -16,6 +16,8 @@
 #define LOG_TAG "lshal"
 #include <android-base/logging.h>
 
+#include <hidl-hash/Hash.h>
+
 #include "TableEntry.h"
 
 #include "TextTable.h"
@@ -53,8 +55,10 @@
         case TableColumnType::CLIENT_CMDS:      return "Clients CMD";
         case TableColumnType::ARCH:             return "Arch";
         case TableColumnType::THREADS:          return "Thread Use";
+        case TableColumnType::RELEASED:         return "R";
+        case TableColumnType::HASH:             return "Hash";
         default:
-            LOG(FATAL) << "Should not reach here.";
+            LOG(FATAL) << __func__ << "Should not reach here. " << static_cast<int>(type);
             return "";
     }
 }
@@ -79,12 +83,25 @@
             return getArchString(arch);
         case TableColumnType::THREADS:
             return getThreadUsage();
+        case TableColumnType::RELEASED:
+            return isReleased();
+        case TableColumnType::HASH:
+            return hash;
         default:
-            LOG(FATAL) << "Should not reach here.";
+            LOG(FATAL) << __func__ << "Should not reach here. " << static_cast<int>(type);
             return "";
     }
 }
 
+std::string TableEntry::isReleased() const {
+    static const std::string unreleased = Hash::hexString(Hash::kEmptyHash);
+
+    if (hash.empty() || hash == unreleased) {
+        return " "; // unknown or unreleased
+    }
+    return "Y"; // released
+}
+
 TextTable Table::createTextTable(bool neat,
     const std::function<std::string(const std::string&)>& emitDebugInfo) const {
 
diff --git a/cmds/lshal/TableEntry.h b/cmds/lshal/TableEntry.h
index 7a3b22e..69206cc 100644
--- a/cmds/lshal/TableEntry.h
+++ b/cmds/lshal/TableEntry.h
@@ -55,19 +55,28 @@
     CLIENT_CMDS,
     ARCH,
     THREADS,
+    RELEASED,
+    HASH,
+};
+
+enum {
+    NO_PID = -1,
+    NO_PTR = 0
 };
 
 struct TableEntry {
-    std::string interfaceName;
-    std::string transport;
-    int32_t serverPid;
-    uint32_t threadUsage;
-    uint32_t threadCount;
-    std::string serverCmdline;
-    uint64_t serverObjectAddress;
-    Pids clientPids;
-    std::vector<std::string> clientCmdlines;
-    Architecture arch;
+    std::string interfaceName{};
+    std::string transport{};
+    int32_t serverPid{NO_PID};
+    uint32_t threadUsage{0};
+    uint32_t threadCount{0};
+    std::string serverCmdline{};
+    uint64_t serverObjectAddress{NO_PTR};
+    Pids clientPids{};
+    std::vector<std::string> clientCmdlines{};
+    Architecture arch{ARCH_UNKNOWN};
+    // empty: unknown, all zeros: unreleased, otherwise: released
+    std::string hash{};
 
     static bool sortByInterfaceName(const TableEntry &a, const TableEntry &b) {
         return a.interfaceName < b.interfaceName;
@@ -84,6 +93,8 @@
         return std::to_string(threadUsage) + "/" + std::to_string(threadCount);
     }
 
+    std::string isReleased() const;
+
     std::string getField(TableColumnType type) const;
 
     bool operator==(const TableEntry& other) const;
@@ -129,11 +140,6 @@
     std::vector<const Table*> mTables;
 };
 
-enum {
-    NO_PID = -1,
-    NO_PTR = 0
-};
-
 }  // namespace lshal
 }  // namespace android
 
diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp
index 06b6819..9220fc0 100644
--- a/cmds/lshal/test.cpp
+++ b/cmds/lshal/test.cpp
@@ -39,6 +39,7 @@
 using ::android::hidl::base::V1_0::IBase;
 using ::android::hidl::manager::V1_0::IServiceManager;
 using ::android::hidl::manager::V1_0::IServiceNotification;
+using ::android::hardware::hidl_array;
 using ::android::hardware::hidl_death_recipient;
 using ::android::hardware::hidl_handle;
 using ::android::hardware::hidl_string;
@@ -46,6 +47,8 @@
 
 using InstanceDebugInfo = IServiceManager::InstanceDebugInfo;
 
+using hidl_hash = hidl_array<uint8_t, 32>;
+
 namespace android {
 namespace hardware {
 namespace tests {
@@ -185,6 +188,9 @@
 
     Status parseArgs(const Arg& arg) { return ListCommand::parseArgs(arg); }
     Status main(const Arg& arg) { return ListCommand::main(arg); }
+    void forEachTable(const std::function<void(Table &)> &f) {
+        return ListCommand::forEachTable(f);
+    }
     void forEachTable(const std::function<void(const Table &)> &f) const {
         return ListCommand::forEachTable(f);
     }
@@ -192,7 +198,12 @@
     void dumpVintf(const NullableOStream<std::ostream>& out) {
         return ListCommand::dumpVintf(out);
     }
+    void internalPostprocess() { ListCommand::postprocess(); }
+    const PidInfo* getPidInfoCached(pid_t serverPid) {
+        return ListCommand::getPidInfoCached(serverPid);
+    }
 
+    MOCK_METHOD0(postprocess, void());
     MOCK_CONST_METHOD2(getPidInfo, bool(pid_t, PidInfo*));
     MOCK_CONST_METHOD1(parseCmdline, std::string(pid_t));
 };
@@ -210,16 +221,6 @@
     std::stringstream output;
 };
 
-TEST_F(ListParseArgsTest, Default) {
-    // default args
-    EXPECT_EQ(0u, mockList->parseArgs(createArg({})));
-    mockList->forEachTable([](const Table& table) {
-        EXPECT_EQ(SelectedColumns({TableColumnType::INTERFACE_NAME, TableColumnType::THREADS,
-                                   TableColumnType::SERVER_PID, TableColumnType::CLIENT_PIDS}),
-                  table.getSelectedColumns());
-    });
-}
-
 TEST_F(ListParseArgsTest, Args) {
     EXPECT_EQ(0u, mockList->parseArgs(createArg({"lshal", "-p", "-i", "-a", "-c"})));
     mockList->forEachTable([](const Table& table) {
@@ -232,9 +233,14 @@
 TEST_F(ListParseArgsTest, Cmds) {
     EXPECT_EQ(0u, mockList->parseArgs(createArg({"lshal", "-m"})));
     mockList->forEachTable([](const Table& table) {
-        EXPECT_EQ(SelectedColumns({TableColumnType::INTERFACE_NAME, TableColumnType::THREADS,
-                                   TableColumnType::SERVER_CMD, TableColumnType::CLIENT_CMDS}),
-                  table.getSelectedColumns());
+        EXPECT_THAT(table.getSelectedColumns(), Not(Contains(TableColumnType::SERVER_PID)))
+                << "should not print server PID with -m";
+        EXPECT_THAT(table.getSelectedColumns(), Not(Contains(TableColumnType::CLIENT_PIDS)))
+                << "should not print client PIDs with -m";
+        EXPECT_THAT(table.getSelectedColumns(), Contains(TableColumnType::SERVER_CMD))
+                << "should print server cmd with -m";
+        EXPECT_THAT(table.getSelectedColumns(), Contains(TableColumnType::CLIENT_CMDS))
+                << "should print client cmds with -m";
     });
 }
 
@@ -274,17 +280,34 @@
     if (serverId == NO_PID) return "";
     return "command_line_" + std::to_string(serverId);
 }
+static bool getIsReleasedFromId(pid_t p) { return p % 2 == 0; }
+static hidl_hash getHashFromId(pid_t serverId) {
+    hidl_hash hash;
+    bool isReleased = getIsReleasedFromId(serverId);
+    for (size_t i = 0; i < hash.size(); ++i) {
+        hash[i] = isReleased ? static_cast<uint8_t>(serverId) : 0u;
+    }
+    return hash;
+}
 
 // Fake service returned by mocked IServiceManager::get.
 class TestService : public IBase {
 public:
-    TestService(DebugInfo&& info) : mInfo(std::move(info)) {}
+    TestService(pid_t id) : mId(id) {}
     hardware::Return<void> getDebugInfo(getDebugInfo_cb cb) override {
-        cb(mInfo);
+        cb({ mId /* pid */, getPtr(mId), DebugInfo::Architecture::IS_64BIT });
+        return hardware::Void();
+    }
+    hardware::Return<void> interfaceChain(interfaceChain_cb cb) override {
+        cb({getInterfaceName(mId), IBase::descriptor});
+        return hardware::Void();
+    }
+    hardware::Return<void> getHashChain(getHashChain_cb cb) override {
+        cb({getHashFromId(mId), getHashFromId(0xff)});
         return hardware::Void();
     }
 private:
-    DebugInfo mInfo;
+    pid_t mId;
 };
 
 class ListTest : public ::testing::Test {
@@ -303,6 +326,13 @@
                 return true;
             }));
         ON_CALL(*mockList, parseCmdline(_)).WillByDefault(Invoke(&getCmdlineFromId));
+        ON_CALL(*mockList, postprocess()).WillByDefault(Invoke([&]() {
+            mockList->internalPostprocess();
+            size_t i = 0;
+            mockList->forEachTable([&](Table& table) {
+                table.setDescription("[fake description " + std::to_string(i++) + "]");
+            });
+        }));
     }
 
     void initMockServiceManager() {
@@ -318,7 +348,7 @@
         ON_CALL(*serviceManager, get(_, _)).WillByDefault(Invoke(
             [&](const hidl_string&, const hidl_string& instance) {
                 int id = getIdFromInstanceName(instance);
-                return sp<IBase>(new TestService({ id /* pid */, getPtr(id), A::IS_64BIT }));
+                return sp<IBase>(new TestService(id));
             }));
 
         ON_CALL(*serviceManager, debugDump(_)).WillByDefault(Invoke(
@@ -348,6 +378,13 @@
     sp<MockServiceManager> passthruManager;
 };
 
+TEST_F(ListTest, GetPidInfoCached) {
+    EXPECT_CALL(*mockList, getPidInfo(5, _)).Times(1);
+
+    EXPECT_NE(nullptr, mockList->getPidInfoCached(5));
+    EXPECT_NE(nullptr, mockList->getPidInfoCached(5));
+}
+
 TEST_F(ListTest, Fetch) {
     EXPECT_EQ(0u, mockList->fetch());
     std::array<std::string, 6> transports{{"hwbinder", "hwbinder", "passthrough",
@@ -456,24 +493,68 @@
         << vintf::gHalManifestConverter.lastError();
 }
 
+// test default columns
+TEST_F(ListTest, DumpDefault) {
+    const std::string expected =
+        "[fake description 0]\n"
+        "R Interface            Thread Use Server Clients\n"
+        "  a.h.foo1@1.0::IFoo/1 11/21      1      2 4\n"
+        "Y a.h.foo2@2.0::IFoo/2 12/22      2      3 5\n"
+        "\n"
+        "[fake description 1]\n"
+        "R Interface            Thread Use Server Clients\n"
+        "  a.h.foo3@3.0::IFoo/3 N/A        N/A    4 6\n"
+        "  a.h.foo4@4.0::IFoo/4 N/A        N/A    5 7\n"
+        "\n"
+        "[fake description 2]\n"
+        "R Interface            Thread Use Server Clients\n"
+        "  a.h.foo5@5.0::IFoo/5 N/A        N/A    6 8\n"
+        "  a.h.foo6@6.0::IFoo/6 N/A        N/A    7 9\n"
+        "\n";
+
+    optind = 1; // mimic Lshal::parseArg()
+    EXPECT_EQ(0u, mockList->main(createArg({"lshal"})));
+    EXPECT_EQ(expected, out.str());
+    EXPECT_EQ("", err.str());
+}
+
+TEST_F(ListTest, DumpHash) {
+    const std::string expected =
+        "[fake description 0]\n"
+        "Interface            R Hash\n"
+        "a.h.foo1@1.0::IFoo/1   0000000000000000000000000000000000000000000000000000000000000000\n"
+        "a.h.foo2@2.0::IFoo/2 Y 0202020202020202020202020202020202020202020202020202020202020202\n"
+        "\n"
+        "[fake description 1]\n"
+        "Interface            R Hash\n"
+        "a.h.foo3@3.0::IFoo/3   \n"
+        "a.h.foo4@4.0::IFoo/4   \n"
+        "\n"
+        "[fake description 2]\n"
+        "Interface            R Hash\n"
+        "a.h.foo5@5.0::IFoo/5   \n"
+        "a.h.foo6@6.0::IFoo/6   \n"
+        "\n";
+
+    optind = 1; // mimic Lshal::parseArg()
+    EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-ils"})));
+    EXPECT_EQ(expected, out.str());
+    EXPECT_EQ("", err.str());
+}
+
 TEST_F(ListTest, Dump) {
     const std::string expected =
-        "All binderized services (registered services through hwservicemanager)\n"
+        "[fake description 0]\n"
         "Interface            Transport Arch Thread Use Server PTR              Clients\n"
         "a.h.foo1@1.0::IFoo/1 hwbinder  64   11/21      1      0000000000002711 2 4\n"
         "a.h.foo2@2.0::IFoo/2 hwbinder  64   12/22      2      0000000000002712 3 5\n"
         "\n"
-        "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.\n"
+        "[fake description 1]\n"
         "Interface            Transport   Arch Thread Use Server PTR Clients\n"
         "a.h.foo3@3.0::IFoo/3 passthrough 32   N/A        N/A    N/A 4 6\n"
         "a.h.foo4@4.0::IFoo/4 passthrough 32   N/A        N/A    N/A 5 7\n"
         "\n"
-        "All available passthrough implementations (all -impl.so files)\n"
+        "[fake description 2]\n"
         "Interface            Transport   Arch Thread Use Server PTR Clients\n"
         "a.h.foo5@5.0::IFoo/5 passthrough 32   N/A        N/A    N/A 6 8\n"
         "a.h.foo6@6.0::IFoo/6 passthrough 32   N/A        N/A    N/A 7 9\n"
@@ -487,22 +568,17 @@
 
 TEST_F(ListTest, DumpCmdline) {
     const std::string expected =
-        "All binderized services (registered services through hwservicemanager)\n"
+        "[fake description 0]\n"
         "Interface            Transport Arch Thread Use Server CMD     PTR              Clients CMD\n"
         "a.h.foo1@1.0::IFoo/1 hwbinder  64   11/21      command_line_1 0000000000002711 command_line_2;command_line_4\n"
         "a.h.foo2@2.0::IFoo/2 hwbinder  64   12/22      command_line_2 0000000000002712 command_line_3;command_line_5\n"
         "\n"
-        "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.\n"
+        "[fake description 1]\n"
         "Interface            Transport   Arch Thread Use Server CMD PTR Clients CMD\n"
         "a.h.foo3@3.0::IFoo/3 passthrough 32   N/A                   N/A command_line_4;command_line_6\n"
         "a.h.foo4@4.0::IFoo/4 passthrough 32   N/A                   N/A command_line_5;command_line_7\n"
         "\n"
-        "All available passthrough implementations (all -impl.so files)\n"
+        "[fake description 2]\n"
         "Interface            Transport   Arch Thread Use Server CMD PTR Clients CMD\n"
         "a.h.foo5@5.0::IFoo/5 passthrough 32   N/A                   N/A command_line_6;command_line_8\n"
         "a.h.foo6@6.0::IFoo/6 passthrough 32   N/A                   N/A command_line_7;command_line_9\n"
@@ -524,7 +600,7 @@
         "a.h.foo6@6.0::IFoo/6 N/A   N/A 7 9\n";
 
     optind = 1; // mimic Lshal::parseArg()
-    EXPECT_EQ(0u, mockList->main(createArg({"lshal", "--neat"})));
+    EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-iepc", "--neat"})));
     EXPECT_EQ(expected, out.str());
     EXPECT_EQ("", err.str());
 }
diff --git a/cmds/lshal/utils.h b/cmds/lshal/utils.h
index 7eca14e..c09e8b1 100644
--- a/cmds/lshal/utils.h
+++ b/cmds/lshal/utils.h
@@ -44,6 +44,8 @@
     NO_INTERFACE                            = 1 << 7,
     // Transaction error from hwbinder transactions
     TRANSACTION_ERROR                       = 1 << 8,
+    // No transaction error, but return value is unexpected.
+    BAD_IMPL                                = 1 << 9,
 };
 using Status = unsigned int;