Android Dumpsys命令使用以及实现原理详解


引言

在我们日常的开发Debug过程中,经常需要去加一些Log进行打印。
APP层面的还好,但是Framework的层面,编译时长,版本匹配,OAT的Push,经常会带来较大的困扰。
为了方便Debug,Android系统本身设计了dumpsys系统,来帮助开发者进行快速定位。
可以使用这个命令,检查系统运行的services,运行时的一些变量,图层等内容。
熟练的使用dumpsys命令,可以极大的减少我们的开发成本,节约开发时间。
本文我们将会讲解dumpsys的使用,以及其详细的实现。


使用命令详解

首先我们来看一些系统的使用帮助:
可以使用adb shell dumpsys -h命令来进行操作,可以获得如下提示:

usage: dumpsys
To dump all services.
or:
dumpsys [-t TIMEOUT] [–priority LEVEL] [–pid] [–help | -l | --skip SERVICES | SERVICE [ARGS]]
–help: shows this help
-l: only list services, do not dump them
-t TIMEOUT_SEC: TIMEOUT to use in seconds instead of default 10 seconds
-T TIMEOUT_MS: TIMEOUT to use in milliseconds instead of default 10 seconds
–pid: dump PID instead of usual dump
–proto: filter services that support dumping data in proto format. Dumps
will be in proto format.
–priority LEVEL: filter services based on specified priority
LEVEL must be one of CRITICAL | HIGH | NORMAL
–skip SERVICES: dumps all services but SERVICES (comma-separated list)
SERVICE [ARGS]: dumps only service SERVICE, optionally passing ARGS to it

针对上述命令的解释如下:

dumpsys -l         -->  将会列出系统中当前正在运行的services.
dumpsys -t         -->10s的默认超时时间设置为-t后所跟时间,单位seconds
dumpsys -T         -->10s的默认超时时间设置为-T后所跟时间,单位milliseconds
dumpsys --pid      -->  将会打印系统中的services及其对应的PID
dumpsys --proto    -->  将会调用系统中services的dumpProto方法,并进行Log输出
dumpsys --priority -->  将会根据后面跟的priority的等级,打印对应的services的信息
dumpsys --skip     -->  将会在打印
dumpsys [SERVICE]  -->  打印对应Services的DUMP信息**

使用方法举例

举例:
adb shell dumpsys adb
查看adb的dump信息。

Current Battery Service state:
  AC powered: false
  USB powered: true
  Wireless powered: false
  Max charging current: 500000
  Max charging voltage: 5000000
  Charge counter: 4020390
  status: 5
  health: 2
  present: true
  level: 100
  scale: 100
  voltage: 4372
  temperature: 260

而这个显示对应的代码在如下的位置:
frameworks/base/services/core/java/com/android/server/BatteryService.java
Code的实现为:

    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);
            }
        }

        @Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
                FileDescriptor err, String[] args, ShellCallback callback,
                ResultReceiver resultReceiver) {
            (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
        }
    }

因为我们没有去加–proto的参数,所以会走dumpInternal的方法。

    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));
            }
        }
    }

实现原理分析

代码路径:
frameworks/native/cmds/dumpsys/
我们先看一下bp文件中对dumpsys的定义:

cc_binary {
    name: "dumpsys",

    defaults: ["dumpsys_defaults"],

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

从这里我们可以看到,dumpsys是被编译成了一个可执行的文件,并放在了系统中。
我们在手机中,执行adb shell启动命令端,用which dumpsys可以看到具体可执行文件的路径。

adb shell which dumpsys
/system/bin/dumpsys

那么,我们接下来主要看的是main.cpp的实现了。

int main(int argc, char* const argv[]) {
    signal(SIGPIPE, SIG_IGN);
    sp<IServiceManager> sm = defaultServiceManager();
    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);
}

main函数的实现也非常简单,首先获取了IServiceManager对象。
获取"IServiceManager对象"的目的是为了和"ServiceManager进程"进行通信。
然后就去初始化了dumpsys的对象。

    explicit Dumpsys(android::IServiceManager* sm) : sm_(sm) {
    }

将获取到的IServiceManager对象,复制到Dumpsys的构造函数,并赋给sm_.
接下来就是dumpsys.main(argc, argv)的执行了。

int Dumpsys::main(int argc, char* const argv[]) {
    Vector<String16> services;
    Vector<String16> args;
    String16 priorityType;
    Vector<String16> skippedServices;
    Vector<String16> protoServices;
    bool showListOnly = false;
    bool skipServices = false;
    bool asProto = false;
    Type type = Type::DUMP;
    int timeoutArgMs = 10000;
    int priorityFlags = IServiceManager::DUMP_FLAG_PRIORITY_ALL;
    static struct option longOptions[] = {{"pid", no_argument, 0, 0},
                                          {"priority", required_argument, 0, 0},
                                          {"proto", no_argument, 0, 0},
                                          {"skip", no_argument, 0, 0},
                                          {"help", no_argument, 0, 0},
                                          {0, 0, 0, 0}};

    // Must reset optind, otherwise subsequent calls will fail (wouldn't happen on main.cpp, but
    // happens on test cases).
    optind = 1;
    while (1) {
        int c;
        int optionIndex = 0;

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

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

        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")) {
                priorityType = String16(String8(optarg));
                if (!ConvertPriorityTypeToBitmask(priorityType, priorityFlags)) {
                    fprintf(stderr, "\n");
                    usage();
                    return -1;
                }
            } else if (!strcmp(longOptions[optionIndex].name, "pid")) {
                type = Type::PID;
            }
            break;

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

        case 'T':
            {
                char* endptr;
                timeoutArgMs = strtol(optarg, &endptr, 10);
                if (*endptr != '\0' || timeoutArgMs <= 0) {
                    fprintf(stderr, "Error: invalid timeout(milliseconds) 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) {
            skippedServices.add(String16(argv[i]));
        } else {
            if (i == optind) {
                services.add(String16(argv[i]));
            } else {
                const String16 arg(argv[i]);
                args.add(arg);
                // For backward compatible, if the proto argument is passed to the service, the
                // dump request is also considered to use proto.
                if (!asProto && !arg.compare(String16(PriorityDumper::PROTO_ARG))) {
                    asProto = true;
                }
            }
        }
    }

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

    if (services.empty() || showListOnly) {
        services = listServices(priorityFlags, asProto);
        setServiceArgs(args, asProto, priorityFlags);
    }

    const size_t N = services.size();
    if (N > 1) {
        // 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;
    }

    for (size_t i = 0; i < N; i++) {
        const String16& serviceName = services[i];
        if (IsSkipped(skippedServices, serviceName)) continue;

        if (startDumpThread(type, serviceName, args) == OK) {
            bool addSeparator = (N > 1);
            if (addSeparator) {
                writeDumpHeader(STDOUT_FILENO, serviceName, priorityFlags);
            }
            std::chrono::duration<double> elapsedDuration;
            size_t bytesWritten = 0;
            status_t status =
                writeDump(STDOUT_FILENO, serviceName, std::chrono::milliseconds(timeoutArgMs),
                          asProto, elapsedDuration, bytesWritten);

            if (status == TIMED_OUT) {
                std::cout << std::endl
                     << "*** SERVICE '" << serviceName << "' DUMP TIMEOUT (" << timeoutArgMs
                     << "ms) EXPIRED ***" << std::endl
                     << std::endl;
            }

            if (addSeparator) {
                writeDumpFooter(STDOUT_FILENO, serviceName, elapsedDuration);
            }
            bool dumpComplete = (status == OK);
            stopDumpThread(dumpComplete);
        }
    }

    return 0;
}

这个函数有一些长,我们来对其进行分解。
分为三部分来看:

Dumpsys的Option处理

我们在前文提到了dumpsys可以传递的参数,那么这边就是处理的地方。
再重温一下刚才的一些关键命令:

dumpsys -l         -->  将会列出系统中当前正在运行的services.
dumpsys -t         -->10s的默认超时时间设置为-t后所跟时间,单位seconds
dumpsys -T         -->10s的默认超时时间设置为-T后所跟时间,单位milliseconds
dumpsys --pid      -->  将会打印系统中的services及其对应的PID
dumpsys --proto    -->  将会调用系统中services的dumpProto方法,并进行Log输出
dumpsys --priority -->  将会根据后面跟的priority的等级,打印对应的services的信息
dumpsys --skip     -->  将会在打印
dumpsys [SERVICE]  -->  打印对应Services的DUMP信息**

这里面,当我们使用不同参数的时候,会首先判断是不是使用了 -l,-t,-T。
并且是否有longOptions的参数:

    static struct option longOptions[] = {{"pid", no_argument, 0, 0},
                                          {"priority", required_argument, 0, 0},
                                          {"proto", no_argument, 0, 0},
                                          {"skip", no_argument, 0, 0},
                                          {"help", no_argument, 0, 0},
                                          {0, 0, 0, 0}};

对参数的详细处理,可以看到下面的swich case中进行完成。

        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")) {
                priorityType = String16(String8(optarg));
                if (!ConvertPriorityTypeToBitmask(priorityType, priorityFlags)) {
                    fprintf(stderr, "\n");
                    usage();
                    return -1;
                }
            } else if (!strcmp(longOptions[optionIndex].name, "pid")) {
                type = Type::PID;
            }
            break;

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

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

        case 'l':
            showListOnly = true;
            break;

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

Dumpsys services的处理

我们在传递dumpsys的services时,提到了可以skip一些services的操作。
也可以再dumpsys后,跟一些services来进行特定的打印。
那么就需要将其用标志位标记,并且放到相应的list中进行保存。
详细逻辑如下:

    for (int i = optind; i < argc; i++) {
        if (skipServices) {
            skippedServices.add(String16(argv[i]));
        } else {
            if (i == optind) {
                services.add(String16(argv[i]));
            } else {
                const String16 arg(argv[i]);
                args.add(arg);
                // For backward compatible, if the proto argument is passed to the service, the
                // dump request is also considered to use proto.
                if (!asProto && !arg.compare(String16(PriorityDumper::PROTO_ARG))) {
                    asProto = true;
                }
            }
        }
    }

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

    if (services.empty() || showListOnly) {
        services = listServices(priorityFlags, asProto);
        setServiceArgs(args, asProto, priorityFlags);
    }

    const size_t N = services.size();
    if (N > 1) {
        // 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;
    }

这里需要注意的是:我们会在这边打印出所有便利出来的services。

std::cout << “Currently running services:” << std::endl;
std::cout << " " << services[i] << (skipped ? " (skipped)" : “”) << std::endl;

因为dumpsys会有一个参数是-l,即只列出对应的services list。
检查之前的swtich case的函数,可以看到-l的定义为:

        case 'l':
            showListOnly = true;
            break;

设置了showListOnly为true,那么在这边我们就不需要执行后续的操作了。
即在本文的第二段,进行了return 0的操作。

    if (showListOnly) {
        return 0;
    }

dumpsys services的实现

接下来的第三段,其实也比较简单。

    for (size_t i = 0; i < N; i++) {
        const String16& serviceName = services[i];
        if (IsSkipped(skippedServices, serviceName)) continue;

        if (startDumpThread(type, serviceName, args) == OK) {
            bool addSeparator = (N > 1);
            if (addSeparator) {
                writeDumpHeader(STDOUT_FILENO, serviceName, priorityFlags);
            }
            std::chrono::duration<double> elapsedDuration;
            size_t bytesWritten = 0;
            status_t status =
                writeDump(STDOUT_FILENO, serviceName, std::chrono::milliseconds(timeoutArgMs),
                          asProto, elapsedDuration, bytesWritten);

            if (status == TIMED_OUT) {
                std::cout << std::endl
                     << "*** SERVICE '" << serviceName << "' DUMP TIMEOUT (" << timeoutArgMs
                     << "ms) EXPIRED ***" << std::endl
                     << std::endl;
            }

            if (addSeparator) {
                writeDumpFooter(STDOUT_FILENO, serviceName, elapsedDuration);
            }
            bool dumpComplete = (status == OK);
            stopDumpThread(dumpComplete);
        }
    }

首先会使用startDumpThread去检查是否状态值为OK,实现如下:

status_t Dumpsys::startDumpThread(Type type, const String16& serviceName,
                                  const Vector<String16>& args) {
    sp<IBinder> service = sm_->checkService(serviceName);
    if (service == nullptr) {
        std::cerr << "Can't find service: " << serviceName << std::endl;
        return NAME_NOT_FOUND;
    }

    int sfd[2];
    if (pipe(sfd) != 0) {
        std::cerr << "Failed to create pipe to dump service info for " << serviceName << ": "
             << strerror(errno) << std::endl;
        return -errno;
    }

    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);
            break;
        case Type::PID:
            err = dumpPidToFd(service, remote_end);
            break;
        default:
            std::cerr << "Unknown dump type" << static_cast<int>(type) << std::endl;
            return;
        }

        if (err != OK) {
            std::cerr << "Error dumping service info status_t: " << statusToString(err) << " "
                 << serviceName << std::endl;
        }
    });
    return OK;
}

首先会去再检查一遍当前的services是否存在。

sm_->checkService(serviceName);

使用管道创建进程间的通信通道。

pipe(sfd) != 0

启用线程完成dump的打印

    // 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);
            break;
        case Type::PID:
            err = dumpPidToFd(service, remote_end);
            break;
        default:
            std::cerr << "Unknown dump type" << static_cast<int>(type) << std::endl;
            return;
        }

        if (err != OK) {
            std::cerr << "Error dumping service info status_t: " << statusToString(err) << " "
                 << serviceName << std::endl;
        }
    });

真正的打印会通过如下方法,来调用对应services的dump函数。

service->dump(remote_end.get(), args);

接下来回到之前的函数中,当读取ok后,我们要检查一些状态,比如输出超时,即会有:

            if (status == TIMED_OUT) {
                std::cout << std::endl
                     << "*** SERVICE '" << serviceName << "' DUMP TIMEOUT (" << timeoutArgMs
                     << "ms) EXPIRED ***" << std::endl
                     << std::endl;
            }

以上,就是dumpsys的实现原理,和具体使用。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值