Android dumpsys介紹

2023-10-24 12:00:30

一、需求

  1. 瞭解dumpsys原理,助於我們進一步瞭解Android系統的設計
  2. 幫助我們分析問題,定位系統狀態
  3. 設計新功能的需要

二、環境

  1. 版本:Android 12
  2. 平臺:SL8541E SPRD

三、相關概念

3.1 dumpsys

        dumpsys 是一種在 Android 裝置上執行的工具,可提供有關係統服務的資訊。可以使用 Android 偵錯橋 (adb) 從命令列呼叫 dumpsys,獲取在連線的裝置上執行的所有系統服務的診斷輸出。

3.2 Binder

        Binder是Android提供的一套程序間相互通訊框架。用來實現多程序間傳送訊息,同步和共用記憶體。

3.3 管道

        管道是一種IPC通訊方式,分為有名管道和無名管道,無論是有名管道還是無名管道其原理都是在核心開闢一塊快取空間,這段快取空間的操作是通過檔案讀寫方式進行的。
有名管道與無名管道:
        有名管道: 有名管道的通訊可以通過管道名進行通訊,程序間不需要有關係。
        無名管道: 無名管道就是匿名管道,匿名管道通訊的程序必須是父子程序。
管道為分半雙工和全雙工:
        半雙工: 半雙工管道是單向通訊,程序1只能向管道寫資料,程序2只能從管道讀取資料。只有一個代表讀或者寫的FD(檔案描述符)。
        全雙工: 全雙工管道是雙向通訊,有兩個檔案描述符,代表讀和寫。

四、dumpsys指令的使用

4.1 dumpsys使用

如下為執行"adb shell dumpsys"指令,控制檯列印的內容,其使用如下:

4.2 dumpsys指令語法

(1)使用 dumpsys 的一般語法如下:

adb shell dumpsys [-t timeout] [--help | -l | --skip services | service [arguments] | -c | -h]

(2)如需獲取所連線裝置的所有系統服務的診斷輸出,請執行 adb shell dumpsys。不過,這樣輸出的資訊比您通常想要的資訊多得多。若要使輸出更加可控,您可以通過在命令中新增相應服務來指定要檢查的服務。例如,下面的命令會提供輸入元件(如觸控式螢幕或內建鍵盤)的系統資料:

adb shell dumpsys input

(3)如需檢視可與 dumpsys 配合使用的系統服務的完整列表,請使用以下命令:

adb shell dumpsys -l

(4)命令列選項如下:

選項 說明
-t timeout 指定超時期限(秒)。如果未指定,預設值為 10 秒。
--help 輸出 dumpsys 工具的幫助文字。
-l 輸出可與 dumpsys 配合使用的系統服務的完整列表。
--skip services 指定您不希望包含在輸出中的 services。
service [arguments] 指定您希望輸出的 service。某些服務可能允許您傳遞可選 arguments。如需瞭解這些可選引數,請將 -h 選項與服務一起傳遞:
adb shell dumpsys procstats -h
-c 指定某些服務時,附加此選項能以計算機可讀的格式輸出資料。
-h 對於某些服務,附加此選項可檢視該服務的幫助文字和其他選項。

五、詳細設計

5.1 dumpsys流程圖

5.2 dumpsys檢視電池資訊

5.2.1 dumpsys battery指令

5.2.2 service->dump列印函數

@frameworks\base\services\core\java\com\android\server\BatteryService.java
private final class BinderService extends Binder {
    @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
            if (args.length > 0 && "--proto".equals(args[0])) {
                dumpProto(fd);
            } else {
                dumpInternal(fd, pw, args);
            }
        }
    ...
}

private void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
    synchronized (mLock) {
        if (args == null || args.length == 0 || "-a".equals(args[0])) {
            pw.println("Current Battery Service state:");
            if (mUpdatesStopped) {
                pw.println("  (UPDATES STOPPED -- use 'reset' to restart)");
            }
            pw.println("  AC powered: " + mHealthInfo.chargerAcOnline);
            pw.println("  USB powered: " + mHealthInfo.chargerUsbOnline);
            pw.println("  Wireless powered: " + mHealthInfo.chargerWirelessOnline);
            pw.println("  Max charging current: " + mHealthInfo.maxChargingCurrent);
            pw.println("  Max charging voltage: " + mHealthInfo.maxChargingVoltage);
            pw.println("  Charge counter: " + mHealthInfo.batteryChargeCounter);
            pw.println("  status: " + mHealthInfo.batteryStatus);
            pw.println("  health: " + mHealthInfo.batteryHealth);
            pw.println("  present: " + mHealthInfo.batteryPresent);
            pw.println("  level: " + mHealthInfo.batteryLevel);
            pw.println("  scale: " + BATTERY_SCALE);
            pw.println("  voltage: " + mHealthInfo.batteryVoltage);
            pw.println("  temperature: " + mHealthInfo.batteryTemperature);
            pw.println("  technology: " + mHealthInfo.batteryTechnology);
        } else {
            Shell shell = new Shell();
            shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
        }
    }
}

5.3 dumpsys原始碼分析

5.3.1 dumpsys服務編譯

        dumpsys是個二進位制可執行程式,其通過bp進行編譯,並最終打包到system分割區(system/bin/dumpsys)。

@frameworks\native\cmds\dumpsys\android.bp
cc_binary {
    name: "dumpsys",

    defaults: ["dumpsys_defaults"],

    srcs: [
        "main.cpp",
    ],
}

5.3.2 dumpsys入口函數

        我們通過執行adb指令 "adb shell dumpsys",可以啟動dumpsys服務,其對應的入口函數如下:

@frameworks\native\cmds\dumpsys\main.cpp
int main(int argc, char* const argv[]) {
    signal(SIGPIPE, SIG_IGN);
    sp<IServiceManager> sm = defaultServiceManager();//獲取SM物件
    fflush(stdout);
    if (sm == nullptr) {
        ALOGE("Unable to get default service manager!");
        std::cerr << "dumpsys: Unable to get default service manager!" << std::endl;
        return 20;
    }

    Dumpsys dumpsys(sm.get());
    return dumpsys.main(argc, argv);//進入dumpsys服務
}

        這邊比較關鍵的點是獲取ServiceManager物件
        大家通過列印可以發現,dumpsys指令列印的資料是java程序的dump函數,而dumpsys也是獨立的一個程序,那麼dumpsys程序又是怎麼和多個java程序通訊的呢?沒錯,就是通過ServiceManager物件。
        那麼,ServiceManager物件是什麼呢?ServiceManager是Binder IPC通訊的管家,本身也是一個Binder服務,他相當於 「DNS伺服器」,內部儲存了serviceName與其Binder Service的對應關係,管理Java層和native層的service,支援addService()、getService()、checkService、listServices()等功能。(Binder機制此處就不展開細說)

5.3.3 dumpsys服務列印

5.3.3.1 dumpsys解析引數

        當我們使用dumpsys指令,列印的資料太過冗長,一般會配合相關引數進行使用,例如:"dumpsys -l"、"dumpsys -t 100 battery"、"dumpsys --help",第一步我們會先解析目標引數。

@frameworks\native\cmds\dumpsys\dumpsys.cpp
int Dumpsys::main(int argc, char* const argv[]) {
    ...
    while (1) {
        ...
        c = getopt_long(argc, argv, "+t:T:l", longOptions, &optionIndex);//獲取指令引數
        ...
        switch (c) {
        case 0://長引數
            if (!strcmp(longOptions[optionIndex].name, "skip")) {//跳過某些服務列印
                skipServices = true;
            } else if (!strcmp(longOptions[optionIndex].name, "proto")) {
                asProto = true;
            } else if (!strcmp(longOptions[optionIndex].name, "help")) {//指令幫助
                usage();
                return 0;
            } else if (!strcmp(longOptions[optionIndex].name, "priority")) {
                ...
            } else if (!strcmp(longOptions[optionIndex].name, "pid")) {//只顯示服務的pid
                type = Type::PID;
            } else if (!strcmp(longOptions[optionIndex].name, "thread")) {//僅顯示程序使用情況
                type = Type::THREAD;
            }
            break;
        case 't'://超時時間設定,預設10秒
            ...
            break;
        case 'T'://超時時間設定,預設10秒
            ...
            break;
        case 'l'://顯示支援的服務列表
            showListOnly = true;
            break;
        default://其他引數
            fprintf(stderr, "\n");
            usage();
            return -1;
        }
    }
    ...
}

5.3.3.2 skippedServices列表構造

        dumpsys內部構造了skippedServices集合,用於記錄需要忽略的服務。

@frameworks\native\cmds\dumpsys\dumpsys.cpp
int Dumpsys::main(int argc, char* const argv[]) {
    ...
    for (int i = optind; i < argc; i++) {
    if (skipServices) {
        skippedServices.add(String16(argv[i]));//設定待忽略的服務
    } else {
        ...
    }
    ...
}

5.3.3.3 獲取支援服務列表

        dumpsys通過ServiceManager獲取支援的服務集合,並排序。

@frameworks\native\cmds\dumpsys\dumpsys.cpp
int Dumpsys::main(int argc, char* const argv[]) {
    ...
    if (services.empty() || showListOnly) {
        services = listServices(priorityFlags, asProto);
        setServiceArgs(args, asProto, priorityFlags);
    }
    ...
}

Vector<String16> Dumpsys::listServices(int priorityFilterFlags, bool filterByProto) const {
    Vector<String16> services = sm_->listServices(priorityFilterFlags);//通過sm獲取服務集合
    services.sort(sort_func);//集合排序
    ...
    return services;
}

5.3.3.4 列印支援服務列表

        在獲取了服務集合後,會先檢查服務是否存在,接著列印服務的名稱,且如果當前指令設定了"-l"引數,僅列印服務集合,即流程結束。

@frameworks\native\cmds\dumpsys\dumpsys.cpp
int Dumpsys::main(int argc, char* const argv[]) {
    ...
    const size_t N = services.size();//獲取支援的服務個數
    if (N > 1 || showListOnly) {
        // first print a list of the current services
        std::cout << "Currently running services:" << std::endl;

        for (size_t i=0; i<N; i++) {
            sp<IBinder> service = sm_->checkService(services[i]);//檢查服務狀態

            if (service != nullptr) {
                bool skipped = IsSkipped(skippedServices, services[i]);
                std::cout << "  " << services[i] << (skipped ? " (skipped)" : "") << std::endl;//列印服務名稱
            }
        }
    }

    if (showListOnly) {//如果指令僅需要列印服務集合,則結束。
        return 0;
    }
    ...
}

5.3.3.5 列印目標服務

        先遍歷所有需要列印的服務,如果引數有指定服務名,即N為對應服務的數量,否則N為所有支援的服務數量。接著,開啟執行緒,通過servicemanager呼叫遠端的dump函數,利用管道和poll機制監聽遠端資料。最後如果超時或者dump結束,則關閉執行緒,釋放相關資源。

@frameworks\native\cmds\dumpsys\dumpsys.cpp
int Dumpsys::main(int argc, char* const argv[]) {
    ...
    for (size_t i = 0; i < N; i++) {
        const String16& serviceName = services[i];
        if (IsSkipped(skippedServices, serviceName)) continue;//跳過部分服務

        if (startDumpThread(type, serviceName, args) == OK) {//step 1.建立dump列印的執行緒
            ...
            std::chrono::duration<double> elapsedDuration;
            size_t bytesWritten = 0;
            status_t status =
                writeDump(STDOUT_FILENO, serviceName, std::chrono::milliseconds(timeoutArgMs),
                          asProto, elapsedDuration, bytesWritten);//step 2.dump執行列印操作

            if (status == TIMED_OUT) {//列印超時
                std::cout << std::endl
                     << "*** SERVICE '" << serviceName << "' DUMP TIMEOUT (" << timeoutArgMs
                     << "ms) EXPIRED ***" << std::endl
                     << std::endl;
            }
            ...
            bool dumpComplete = (status == OK);
            stopDumpThread(dumpComplete);//step 3.結束dump列印執行緒
        }
    }
    ...
}

step 1. 建立dumpsys列印執行緒
        建立了一條管道,接著開啟了一個執行緒,通過ServiceManager物件讀取目標服務的dump函數,即dump列印資料。

@frameworks\native\cmds\dumpsys\dumpsys.cpp
status_t Dumpsys::startDumpThread(Type type, const String16& serviceName,
                                  const Vector<String16>& args) {
    sp<IBinder> service = sm_->checkService(serviceName);//通過SM獲取service物件
    int sfd[2];
    if (pipe(sfd) != 0) {//建立管道,用於讀取service端資料
        ...
    }
    ...
    redirectFd_ = unique_fd(sfd[0]);
    unique_fd remote_end(sfd[1]);
    sfd[0] = sfd[1] = -1;

    // dump blocks until completion, so spawn a thread..
    activeThread_ = std::thread([=, remote_end{std::move(remote_end)}]() mutable {//建立執行緒
        status_t err = 0;

        switch (type) {
        case Type::DUMP:
            err = service->dump(remote_end.get(), args);//呼叫dump函數
            break;
        ...
        }
        ...
    });
    return OK;
}

step 2. dumpsys列印到終端
        通過poll機制用來監聽管道的資料,並將讀取到的dump資料,列印至控制檯。同時,通過計算剩餘的時間,來判斷當前是否讀取超時。

@frameworks\native\cmds\dumpsys\dumpsys.cpp
status_t Dumpsys::writeDump(int fd, const String16& serviceName, std::chrono::milliseconds timeout,
                            bool asProto, std::chrono::duration<double>& elapsedDuration,
                            size_t& bytesWritten) const {
    ...
    int serviceDumpFd = redirectFd_.get();
    struct pollfd pfd = {.fd = serviceDumpFd, .events = POLLIN};

    while (true) {
        ...
        int rc = TEMP_FAILURE_RETRY(poll(&pfd, 1, time_left_ms()));//poll機制檢測管道資料
        if (rc < 0) {
           ...
        } else if (rc == 0 || time_left_ms() == 0) {
            status = TIMED_OUT;//計算剩餘時間,來決定是否超時
            break;
        }

        char buf[4096];
        rc = TEMP_FAILURE_RETRY(read(redirectFd_.get(), buf, sizeof(buf)));//讀取遠端的資料
        ...
        if (!WriteFully(fd, buf, rc)) {//列印至控制檯
            ...
            break;
        }
        totalBytes += rc;
    }
    ...
    return status;
}

step 3. 關閉dumpsys列印執行緒
        將dumpsys列印的執行緒detach掉,相關的fd控制程式碼reset掉,釋放資源。

六、dumpsys的應用

        如後續有什麼應用dumpsys,或者有助於日常開發偵錯的場景,再補充,未完待續。

6.1 dumpsys常用指令

服務名 類名 指令 功能
activity ActivityManagerService 獲取某個應用的Activity資訊:
adb shell dumpsys activity a packagename
獲取某個應用的Service資訊:
adb shell dumpsys activity s packagename
獲取某個應用的Broadcast資訊:
adb shell dumpsys activity b packagename
獲取某個應用的Provider資訊:
adb shell dumpsys activity prov packagename
獲取某個應用的程序狀態:
adb shell dumpsys activity p packagename
獲取當前介面的Activity資訊:
adb shell dumpsys activity top | grep ACTIVITY
AMS相關資訊
package PackageManagerService adb shell dumpsys package PMS相關資訊
window WindowManagerService adb shell dumpsys window WMS相關資訊
input InputManagerService adb shell dumpsys input IMS相關資訊
power PowerManagerService adb shell dumpsys power PMS相關資訊
batterystats BatterystatsService adb shell dumpsys batterystats 電池統計資訊
battery BatteryService adb shell dumpsys battery 電池資訊
alarm AlarmManagerService adb shell dumpsys alarm 鬧鐘資訊
dropbox DropboxManagerService adb shell dumpsys dropbox 偵錯相關
procstats ProcessStatsService adb shell dumpsys procstats 程序統計
cpuinfo CpuBinder adb shell dumpsys cpuinfo CPU
meminfo MemBinder adb shell dumpsys meminfo 記憶體
gfxinfo GraphicsBinder adb shell dumpsys gfxinfo 影象
dbinfo DbBinder adb shell dumpsys dbinfo 資料庫

七、參考資料

dumpsys指令介紹:
https://developer.android.google.cn/studio/command-line/dumpsys?hl=zh-cn
管道:
https://www.cnblogs.com/naray/p/15365954.html
Binder:
https://blog.csdn.net/shenxiaolinil/article/details/128972302