Android 調(diào)試系列-Dumpsys源碼篇

Dumpsys這個(gè)命令了解或者用過的人肯定都知道,他可以提供很多有用的信息,供開發(fā)者來定位問題。是一個(gè)很強(qiáng)大的debug工具。接下來埠偿,會從源碼的角度來介紹一下這個(gè)命令的實(shí)現(xiàn)方式透罢。
注:已Android 8.0源碼來分析
在系統(tǒng)編譯時(shí)會編譯到如下Android.bp文件。
frameworks/native/cmds/dumpsys/Android.bp

cc_defaults {
    name: "dumpsys_defaults",

    cflags: [
        "-Wall",
        "-Werror",
    ],

    srcs: [
        "dumpsys.cpp",
    ],

    shared_libs: [
        "libbase",
        "libutils",
        "liblog",
        "libbinder",
    ],

    clang: true,
}

//
// Static library used in testing and executable
//

cc_library_static {
    name: "libdumpsys",

    defaults: ["dumpsys_defaults"],

    export_include_dirs: ["."],
}


//
// Executable
//

cc_binary {
    name: "dumpsys",

    defaults: ["dumpsys_defaults"],

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

subdirs = ["tests"]

/frameworks/native/cmds/dumpsys/main.cpp
接下來會執(zhí)行main.cpp的main函數(shù)

int main(int argc, char* const argv[]) {
    signal(SIGPIPE, SIG_IGN);
    //獲取ServiceManager
    sp<IServiceManager> sm = defaultServiceManager();
    fflush(stdout);
    if (sm == nullptr) {
        ALOGE("Unable to get default service manager!");
        aerr << "dumpsys: Unable to get default service manager!" << endl;
        return 20;
    }

    Dumpsys dumpsys(sm.get());
    return dumpsys.main(argc, argv);
}

/frameworks/native/cmds/dumpsys/dumpsys.cpp
最終會執(zhí)行到dumpsys.cpp的main函數(shù)冠蒋,如下:


int Dumpsys::main(int argc, char* const argv[]) {
    Vector<String16> services;
    Vector<String16> args;
    Vector<String16> skippedServices;
    bool showListOnly = false;
    bool skipServices = false;
    int timeoutArg = 10;
    static struct option longOptions[] = {
        {"skip", no_argument, 0,  0 },
        {"help", no_argument, 0,  0 },
        {     0,           0, 0,  0 }
    };

    // 必須重置optind羽圃,否則后續(xù)調(diào)用將失敗(不會發(fā)生在main.cpp上抖剿,但會發(fā)生在測試用例上).
    optind = 1;
    while (1) {
        int c;
        int optionIndex = 0;

        c = getopt_long(argc, argv, "+t:l", longOptions, &optionIndex);

        if (c == -1) {
            break;
        }

        switch (c) {
        case 0:
            if (!strcmp(longOptions[optionIndex].name, "skip")) {
                skipServices = true; //skip some service
            } else if (!strcmp(longOptions[optionIndex].name, "help")) {
                usage();
                return 0;
            }
            break;

        case 't':
            {
                char *endptr;
                timeoutArg = strtol(optarg, &endptr, 10);
                if (*endptr != '\0' || timeoutArg <= 0) {
                    fprintf(stderr, "Error: invalid timeout number: '%s'\n", optarg);
                    return -1;
                }
            }
            break;

        case 'l':
            showListOnly = true;
            break;

        default:
            fprintf(stderr, "\n");
            usage();
            return -1;
        }
    }

    for (int i = optind; i < argc; i++) {
        if (skipServices) {
            //如果命令里跟了--skip some service朽寞,則加到skippedServices里
            skippedServices.add(String16(argv[i]));
        } else {
            if (i == optind) {
                services.add(String16(argv[i]));
            } else {
                args.add(String16(argv[i]));
            }
        }
    }

    if ((skipServices && skippedServices.empty()) ||
            (showListOnly && (!services.empty() || !skippedServices.empty()))) {
        usage();
        return -1;
    }

    if (services.empty() || showListOnly) {
        // gets all services
        services = sm_->listServices();
        services.sort(sort_func);
        args.add(String16("-a"));
    }

    const size_t N = services.size();

    if (N > 1) {
        // 開始打印第一行日志
        aout << "Currently running services:" << 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]);
                aout << "  " << services[i] << (skipped ? " (skipped)" : "") << endl;
            }
        }
    }

    if (showListOnly) {
        return 0;
    }

    for (size_t i = 0; i < N; i++) {
        String16 service_name = std::move(services[i]);
        if (IsSkipped(skippedServices, service_name)) continue;
        //獲取相關(guān)service
        sp<IBinder> service = sm_->checkService(service_name);
        if (service != nullptr) {
            int sfd[2];

            if (pipe(sfd) != 0) {
                aerr << "Failed to create pipe to dump service info for " << service_name
                     << ": " << strerror(errno) << endl;
                continue;
            }

            unique_fd local_end(sfd[0]);
            unique_fd remote_end(sfd[1]);
            sfd[0] = sfd[1] = -1;

            if (N > 1) {
                aout << "------------------------------------------------------------"
                        "-------------------" << endl;
                aout << "DUMP OF SERVICE " << service_name << ":" << endl;
            }

            // dump blocks until completion, so spawn a thread..
            //這里另開了線程來做處理
            std::thread dump_thread([=, remote_end { std::move(remote_end) }]() mutable {
                //調(diào)用相關(guān)service的dump()方法。
                int err = service->dump(remote_end.get(), args);

                // It'd be nice to be able to close the remote end of the socketpair before the dump
                // call returns, to terminate our reads if the other end closes their copy of the
                // file descriptor, but then hangs for some reason. There doesn't seem to be a good
                // way to do this, though.
                remote_end.reset();

                if (err != 0) {
                    aerr << "Error dumping service info: (" << strerror(err) << ") " << service_name
                         << endl;
                }
            });

            auto timeout = std::chrono::seconds(timeoutArg);
            auto start = std::chrono::steady_clock::now();
            auto end = start + timeout;

            struct pollfd pfd = {
                .fd = local_end.get(),
                .events = POLLIN
            };

            bool timed_out = false;
            bool error = false;
            while (true) {
                // Wrap this in a lambda so that TEMP_FAILURE_RETRY recalculates the timeout.
                auto time_left_ms = [end]() {
                    auto now = std::chrono::steady_clock::now();
                    auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(end - now);
                    return std::max(diff.count(), 0ll);
                };

                int rc = TEMP_FAILURE_RETRY(poll(&pfd, 1, time_left_ms()));
                if (rc < 0) {
                    aerr << "Error in poll while dumping service " << service_name << " : "
                         << strerror(errno) << endl;
                    error = true;
                    break;
                } else if (rc == 0) {
                    timed_out = true;
                    break;
                }

                char buf[4096];
                rc = TEMP_FAILURE_RETRY(read(local_end.get(), buf, sizeof(buf)));
                if (rc < 0) {
                    aerr << "Failed to read while dumping service " << service_name << ": "
                         << strerror(errno) << endl;
                    error = true;
                    break;
                } else if (rc == 0) {
                    // EOF.
                    break;
                }

                if (!WriteFully(STDOUT_FILENO, buf, rc)) {
                    aerr << "Failed to write while dumping service " << service_name << ": "
                         << strerror(errno) << endl;
                    error = true;
                    break;
                }
            }

            if (timed_out) {
                aout << endl
                     << "*** SERVICE '" << service_name << "' DUMP TIMEOUT (" << timeoutArg
                     << "s) EXPIRED ***" << endl
                     << endl;
            }

            if (timed_out || error) {
                dump_thread.detach();
            } else {
                dump_thread.join();
            }

            if (N > 1) {
              std::chrono::duration<double> elapsed_seconds =
                  std::chrono::steady_clock::now() - start;
              aout << StringPrintf("--------- %.3fs ", elapsed_seconds.count()).c_str()
                   << "was the duration of dumpsys " << service_name << endl;
            }
        } else {
            aerr << "Can't find service: " << service_name << endl;
        }
    }

    return 0;
}

從代碼的執(zhí)行順序來看斩郎,dumpsys的主要工作順序分為以下4個(gè)步驟:

  1. main.cpp中defaultServiceManager()函數(shù)用來獲取ServiceManager對象脑融,并傳遞到dumpsys.cpp中。
  2. sm_->listServices()缩宜,獲取系統(tǒng)中所有向ServiceManager中注冊過的服務(wù)肘迎。
  3. 如果命令加入了--skip SERVICES.則加入到skippedServices中,過濾service_name锻煌,最后sm_->checkService(service_name)用來獲取指定的service妓布。
  4. 最后調(diào)用service->dump()。這是最核心的方法宋梧,主要是service去掉用各自的dump()方法來獲取相關(guān)dump信息匣沼。

最后再附上dumpsys的命令 --help信息定義

static void usage() {
    fprintf(stderr,
        "usage: dumpsys\n"
            "         To dump all services.\n"
            "or:\n"
            "       dumpsys [-t TIMEOUT] [--help | -l | --skip SERVICES | SERVICE [ARGS]]\n"
            "         --help: shows this help\n"
            "         -l: only list services, do not dump them\n"
            "         -t TIMEOUT: TIMEOUT to use in seconds instead of default 10 seconds\n"
            "         --skip SERVICES: dumps all services but SERVICES (comma-separated list)\n"
            "         SERVICE [ARGS]: dumps only service SERVICE, optionally passing ARGS to it\n");
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市捂龄,隨后出現(xiàn)的幾起案子释涛,更是在濱河造成了極大的恐慌,老刑警劉巖倦沧,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枢贿,死亡現(xiàn)場離奇詭異,居然都是意外死亡刀脏,警方通過查閱死者的電腦和手機(jī)局荚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人耀态,你說我怎么就攤上這事轮傍。” “怎么了首装?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵创夜,是天一觀的道長。 經(jīng)常有香客問我仙逻,道長驰吓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任系奉,我火速辦了婚禮檬贰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缺亮。我一直安慰自己翁涤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布萌踱。 她就那樣靜靜地躺著葵礼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪并鸵。 梳的紋絲不亂的頭發(fā)上鸳粉,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機(jī)與錄音园担,去河邊找鬼赁严。 笑死,一個(gè)胖子當(dāng)著我的面吹牛粉铐,可吹牛的內(nèi)容都是我干的疼约。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼蝙泼,長吁一口氣:“原來是場噩夢啊……” “哼程剥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起汤踏,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤织鲸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后溪胶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搂擦,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年哗脖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瀑踢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扳还。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖橱夭,靈堂內(nèi)的尸體忽然破棺而出氨距,到底是詐尸還是另有隱情,我是刑警寧澤棘劣,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布俏让,位于F島的核電站,受9級特大地震影響茬暇,放射性物質(zhì)發(fā)生泄漏首昔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一糙俗、第九天 我趴在偏房一處隱蔽的房頂上張望勒奇。 院中可真熱鬧,春花似錦臼节、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蟋定,卻和暖如春粉臊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驶兜。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工扼仲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抄淑。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓屠凶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肆资。 傳聞我的和親對象是個(gè)殘疾皇子矗愧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容