Android 调试系列-Bugreport源码篇

版权声明:本文为作者原创,转载必须注明出处。
转载请注明出处:https://www.jianshu.com/p/2759436e2040

基于Android 8.0,来分析Android系统中Bugreport的实现方式

一、概述

Bugreport包含设备日志,堆栈跟踪和其他诊断信息,可帮助您查找和修复应用中的错误。您可以使用设备上的Take bug report开发人员选项,Android Emulator菜单或adb bugreport开发计算机上的命令从设备捕获错误报告。要获取错误报告,您必须 在设备上启用“ 开发者”选项,以便可以访问“ 获取错误”报告选项。
获取Bugreport的方式有如下几种:

1. 要直接从您的设备获取错误报告,请执行以下操作:

  • 确保已启用开发者选项
  • Developer选项中,点击Take bug report
  • 选择所需的错误报告类型,然后点击“报告”
    片刻之后,您会收到错误报告准备就绪的通知。
  • 要分享错误报告,请点按通知。

2. 使用adb捕获

adb bugreport > bugreport.txt

3. 手机厂商暗码调用
小米手机:
手机拨号键盘输入:*#*#284#*#*

对于Android系统调试分析,bugreport信息量非常之大,几乎涵盖整个系统各个层面内容,对于分析BUG是一大利器,本文先从从源码角度来分析一下Bugreport的实现原理。

二、原理分析

源码路径如图所示
所涉及到的类如下:
framework/native/cmds/bugreport/bugreport.cpp
framework/native/cmds/dumpstate/dumpstate.cpp
framework/native/cmds/dumpstate/utils.c

Android 系统源码中framework/native/cmds/bugreport目录通过Android.bp定义了bugreport项目,在系统编译完成后会生成bugreport的可执行文件,位于系统/system/bin/bugreport.当执行adb bugreport时,便会调用到这个可执行文件,进入bugreport.cpp中的main函数。

Android.bp
cc_binary {
    name: "bugreport",
    srcs: ["bugreport.cpp"],
    cflags: ["-Wall"],
    shared_libs: ["libcutils"],
}

2.1 bugreport.main()函数

bugreport.cpp

// This program will trigger the dumpstate service to start a call to
// dumpstate, then connect to the dumpstate local client to read the
// output. All of the dumpstate output is written to stdout, including
// any errors encountered while reading/writing the output.
int main() {

  fprintf(stderr, "=============================================================================\n");
  fprintf(stderr, "WARNING: flat bugreports are deprecated, use adb bugreport <zip_file> instead\n");
  fprintf(stderr, "=============================================================================\n\n\n");

  // 启动dumpstate服务.
  property_set("ctl.start", "dumpstate");

  // 多次尝试socket连接,直到dumpstate服务启动完成,才正式建立socket连接
  int s;
  for (int i = 0; i < 20; i++) {
    s = socket_local_client("dumpstate", ANDROID_SOCKET_NAMESPACE_RESERVED,
                            SOCK_STREAM);
    if (s >= 0)
      break;
    // Try again in 1 second.
    sleep(1);
  }

  if (s == -1) {
    printf("Failed to connect to dumpstate service: %s\n", strerror(errno));
    return 1;
  }

  //当3分钟没有任何数据可读,则超时停止读取并退出。
  //dumpstate服务中不存在大于1分钟的timetout,因而不可预见的超时的情况下留有很大的回旋余地。
  struct timeval tv;
  tv.tv_sec = 3 * 60;
  tv.tv_usec = 0;
  if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
    printf("WARNING: Cannot set socket timeout: %s\n", strerror(errno));
  }

  while (1) {
    char buffer[65536];
    ssize_t bytes_read = TEMP_FAILURE_RETRY(read(s, buffer, sizeof(buffer)));
    if (bytes_read == 0) {
      break;
    } else if (bytes_read == -1) {
      // EAGAIN 意味着timeout,Bugreport读取异常然后终止打印如下错误信息。
      if (errno == EAGAIN) {
        errno = ETIMEDOUT;
      }
      printf("\nBugreport read terminated abnormally (%s).\n", strerror(errno));
      break;
    }

    ssize_t bytes_to_send = bytes_read;
    ssize_t bytes_written;
    //不断循环得将读取数据输出到stdout
    do {
      bytes_written = TEMP_FAILURE_RETRY(write(STDOUT_FILENO,
                                               buffer + bytes_read - bytes_to_send,
                                               bytes_to_send));
      if (bytes_written == -1) {
        printf("Failed to write data to stdout: read %zd, trying to send %zd (%s)\n",
               bytes_read, bytes_to_send, strerror(errno));
        return 1;
      }
      bytes_to_send -= bytes_written;
    } while (bytes_written != 0 && bytes_to_send > 0);
  }

  close(s);
  return 0;
}

根据代码可知main函数首先通过property_set("ctl.start", "dumpstate")来启动dumpstate服务。dumpstate服务主要是通过init进行通过fork方式来创建进程/system/bin/dumpstate,然后Bugreport再通过socket连接建立与dumpstate的通信,这个过程有尝试20次,每次sleep 1s,直到socket连接建立成功。如果连续3min都没有任何数据可读写,则出发超时机制停止读取并退出。由于dumpstate服务中不存在大于1分钟的timetout,因而不可预见的超时的情况下留有很大的回旋余地。
当从socket读取到数据后,写入到标准时输出或者重定向到文件。可见bugreport数据的来源都是dumpstate服务,那么接下来去看看dumpstate服务的工作。

2.2 dumpstate.main()函数

dumpstate.cpp

int main(int argc, char *argv[]) {
    int do_add_date = 0;
    int do_zip_file = 0;
    int do_vibrate = 1;
    char* use_outfile = 0;
    int use_socket = 0;
    int use_control_socket = 0;
    int do_fb = 0;
    int do_broadcast = 0;
    int is_remote_mode = 0;
    bool show_header_only = false;
    bool do_start_service = false;
    bool telephony_only = false;

    //提高当前进程的优先级,防止被OOM Killer杀死
    setpriority(PRIO_PROCESS, 0, -20);

    FILE* oom_adj = fopen("/proc/self/oom_score_adj", "we");
    if (oom_adj) {
        fputs("-1000", oom_adj);
        fclose(oom_adj);
    } else {
        /* 兼容判断当内核 <= 2.6.35 */
        oom_adj = fopen("/proc/self/oom_adj", "we");
        if (oom_adj) {
            fputs("-17", oom_adj);
            fclose(oom_adj);
        }
    }

    /* 参数解析 */
    int c;
    while ((c = getopt(argc, argv, "dho:svqzpPBRSV:")) != -1) {
        switch (c) {
            // clang-format off
            case 'd': do_add_date = 1;            break;
            case 'z': do_zip_file = 1;            break;
            case 'o': use_outfile = optarg;       break;
            case 's': use_socket = 1;             break;
            case 'S': use_control_socket = 1;     break;
            case 'v': show_header_only = true;    break;
            case 'q': do_vibrate = 0;             break;
            case 'p': do_fb = 1;                  break;
            case 'P': ds.update_progress_ = true; break;
            case 'R': is_remote_mode = 1;         break;
            case 'B': do_broadcast = 1;           break;
            case 'V':                             break; // compatibility no-op
            case 'h':
                ShowUsageAndExit(0);
                break;
            default:
                fprintf(stderr, "Invalid option: %c\n", c);
                ShowUsageAndExit();
                // clang-format on
        }
    }

    // TODO: use helper function to convert argv into a string
    for (int i = 0; i < argc; i++) {
        ds.args_ += argv[i];
        if (i < argc - 1) {
            ds.args_ += " ";
        }
    }

    ds.extra_options_ = android::base::GetProperty(PROPERTY_EXTRA_OPTIONS, "");
    if (!ds.extra_options_.empty()) {
        // Framework 使用一个系统属性来重写一些命令行参数.
        // 目前,它包含所请求的bugreport的类型。
        if (ds.extra_options_ == "bugreportplus") {
            // 目前,dumpstate的binder数据只通过命令行来更新
            do_start_service = true;
            ds.update_progress_ = true;
            do_fb = 0;
        } else if (ds.extra_options_ == "bugreportremote") {
            do_vibrate = 0;
            is_remote_mode = 1;
            do_fb = 0;
        } else if (ds.extra_options_ == "bugreportwear") {
            ds.update_progress_ = true;
        } else if (ds.extra_options_ == "bugreporttelephony") {
            telephony_only = true;
        } else {
            MYLOGE("Unknown extra option: %s\n", ds.extra_options_.c_str());
        }
        // 重置属性PROPERTY_EXTRA_OPTIONS=""
        //static constexpr char PROPERTY_EXTRA_OPTIONS[] = "dumpstate.options";
        android::base::SetProperty(PROPERTY_EXTRA_OPTIONS, "");
    }

    ds.notification_title = android::base::GetProperty(PROPERTY_EXTRA_TITLE, "");
    if (!ds.notification_title.empty()) {
        // 重置属性PROPERTY_EXTRA_TITLE=""
        //static constexpr char PROPERTY_EXTRA_TITLE[] = "dumpstate.options.title";
        android::base::SetProperty(PROPERTY_EXTRA_TITLE, "");

        ds.notification_description = android::base::GetProperty(PROPERTY_EXTRA_DESCRIPTION, "");
        if (!ds.notification_description.empty()) {
            // 重置属性PROPERTY_EXTRA_DESCRIPTION=""
            //static constexpr char PROPERTY_EXTRA_DESCRIPTION[] = "dumpstate.options.description";
            android::base::SetProperty(PROPERTY_EXTRA_DESCRIPTION, "");
        }
        MYLOGD("notification (title:  %s, description: %s)\n",
               ds.notification_title.c_str(), ds.notification_description.c_str());
    }

    if ((do_zip_file || do_add_date || ds.update_progress_ || do_broadcast) && !use_outfile) {
        ExitOnInvalidArgs();
    }

    if (use_control_socket && !do_zip_file) {
        ExitOnInvalidArgs();
    }

    if (ds.update_progress_ && !do_broadcast) {
        ExitOnInvalidArgs();
    }

    if (is_remote_mode && (ds.update_progress_ || !do_broadcast || !do_zip_file || !do_add_date)) {
        ExitOnInvalidArgs();
    }

    if (ds.version_ == VERSION_DEFAULT) {
        ds.version_ = VERSION_CURRENT;
    }

    if (ds.version_ != VERSION_CURRENT && ds.version_ != VERSION_SPLIT_ANR) {
        MYLOGE("invalid version requested ('%s'); suppported values are: ('%s', '%s', '%s')\n",
               ds.version_.c_str(), VERSION_DEFAULT.c_str(), VERSION_CURRENT.c_str(),
               VERSION_SPLIT_ANR.c_str());
        exit(1);
    }

    if (show_header_only) {
        ds.PrintHeader();
        exit(0);
    }

    /* 如果需要就重定向输出 */
    bool is_redirecting = !use_socket && use_outfile;

    // TODO: temporarily set progress until it's part of the Dumpstate constructor
    std::string stats_path =
        is_redirecting ? android::base::StringPrintf("%s/dumpstate-stats.txt", dirname(use_outfile))
                       : "";
    ds.progress_.reset(new Progress(stats_path));

    /* 获取序列 id
    static constexpr char PROPERTY_LAST_ID[] = "dumpstate.last_id" */
    uint32_t last_id = android::base::GetIntProperty(PROPERTY_LAST_ID, 0);
    ds.id_ = ++last_id;
    android::base::SetProperty(PROPERTY_LAST_ID, std::to_string(last_id));

    MYLOGI("begin\n");

    register_sig_handler();

    if (do_start_service) {
        MYLOGI("Starting 'dumpstate' service\n");
        android::status_t ret;
        if ((ret = android::os::DumpstateService::Start()) != android::OK) {
            MYLOGE("Unable to start DumpstateService: %d\n", ret);
        }
    }
    //根据系统属性dumpstate.dry_run来判断
    if (PropertiesHelper::IsDryRun()) {
        MYLOGI("Running on dry-run mode (to disable it, call 'setprop dumpstate.dry_run false')\n");
    }

    MYLOGI("dumpstate info: id=%d, args='%s', extra_options= %s)\n", ds.id_, ds.args_.c_str(),
           ds.extra_options_.c_str());

    MYLOGI("bugreport format version: %s\n", ds.version_.c_str());

    ds.do_early_screenshot_ = ds.update_progress_;

    //简历socket连接
    if (use_socket) {
        redirect_to_socket(stdout, "dumpstate");
    }

    if (use_control_socket) {
        MYLOGD("Opening control socket\n");
        ds.control_socket_fd_ = open_socket("dumpstate");
        ds.update_progress_ = 1;
    }

    if (is_redirecting) {
        ds.bugreport_dir_ = dirname(use_outfile);
        std::string build_id = android::base::GetProperty("ro.build.id", "UNKNOWN_BUILD");
        std::string device_name = android::base::GetProperty("ro.product.name", "UNKNOWN_DEVICE");
        ds.base_name_ = android::base::StringPrintf("%s-%s-%s", basename(use_outfile),
                                                    device_name.c_str(), build_id.c_str());
        if (do_add_date) {
            char date[80];
            strftime(date, sizeof(date), "%Y-%m-%d-%H-%M-%S", localtime(&ds.now_));
            ds.name_ = date;
        } else {
            ds.name_ = "undated";
        }

        if (telephony_only) {
            ds.base_name_ += "-telephony";
        }

        if (do_fb) {
            ds.screenshot_path_ = ds.GetPath(".png");
        }
        ds.tmp_path_ = ds.GetPath(".tmp");
        ds.log_path_ = ds.GetPath("-dumpstate_log-" + std::to_string(ds.pid_) + ".txt");

        MYLOGD(
            "Bugreport dir: %s\n"
            "Base name: %s\n"
            "Suffix: %s\n"
            "Log path: %s\n"
            "Temporary path: %s\n"
            "Screenshot path: %s\n",
            ds.bugreport_dir_.c_str(), ds.base_name_.c_str(), ds.name_.c_str(),
            ds.log_path_.c_str(), ds.tmp_path_.c_str(), ds.screenshot_path_.c_str());

        if (do_zip_file) {
            ds.path_ = ds.GetPath(".zip");
            MYLOGD("Creating initial .zip file (%s)\n", ds.path_.c_str());
            create_parent_dirs(ds.path_.c_str());
            ds.zip_file.reset(fopen(ds.path_.c_str(), "wb"));
            if (ds.zip_file == nullptr) {
                MYLOGE("fopen(%s, 'wb'): %s\n", ds.path_.c_str(), strerror(errno));
                do_zip_file = 0;
            } else {
                ds.zip_writer_.reset(new ZipWriter(ds.zip_file.get()));
            }
            ds.AddTextZipEntry("version.txt", ds.version_);
        }

        if (ds.update_progress_) {
            if (do_broadcast) {
                // clang-format off

                std::vector<std::string> am_args = {
                     "--receiver-permission", "android.permission.DUMP",
                     "--es", "android.intent.extra.NAME", ds.name_,
                     "--ei", "android.intent.extra.ID", std::to_string(ds.id_),
                     "--ei", "android.intent.extra.PID", std::to_string(ds.pid_),
                     "--ei", "android.intent.extra.MAX", std::to_string(ds.progress_->GetMax()),
                };
                // clang-format on
                SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args);
            }
            if (use_control_socket) {
                dprintf(ds.control_socket_fd_, "BEGIN:%s\n", ds.path_.c_str());
            }
        }
    }

    /* 读取 /proc/cmdline 在改变root之前*/
    FILE *cmdline = fopen("/proc/cmdline", "re");
    if (cmdline) {
        fgets(cmdline_buf, sizeof(cmdline_buf), cmdline);
        fclose(cmdline);
    }
    //打开震动
    if (do_vibrate) {
        Vibrate(150);
    }

    if (do_fb && ds.do_early_screenshot_) {
        if (ds.screenshot_path_.empty()) {
            // should not have happened
            MYLOGE("INTERNAL ERROR: skipping early screenshot because path was not set\n");
        } else {
            MYLOGI("taking early screenshot\n");
            ds.TakeScreenshot();
        }
    }

    if (do_zip_file) {
        if (chown(ds.path_.c_str(), AID_SHELL, AID_SHELL)) {
            MYLOGE("Unable to change ownership of zip file %s: %s\n", ds.path_.c_str(),
                   strerror(errno));
        }
    }

    if (is_redirecting) {
        redirect_to_file(stderr, const_cast<char*>(ds.log_path_.c_str()));
        if (chown(ds.log_path_.c_str(), AID_SHELL, AID_SHELL)) {
            MYLOGE("Unable to change ownership of dumpstate log file %s: %s\n",
                   ds.log_path_.c_str(), strerror(errno));
        }
        /* TODO: rather than generating a text file now and zipping it later,
           it would be more efficient to redirect stdout to the zip entry
           directly, but the libziparchive doesn't support that option yet. */
        redirect_to_file(stdout, const_cast<char*>(ds.tmp_path_.c_str()));
        if (chown(ds.tmp_path_.c_str(), AID_SHELL, AID_SHELL)) {
            MYLOGE("Unable to change ownership of temporary bugreport file %s: %s\n",
                   ds.tmp_path_.c_str(), strerror(errno));
        }
    }

    // Don't buffer stdout
    setvbuf(stdout, nullptr, _IONBF, 0);

    // 打印系统build相关信息
    ds.PrintHeader();
    //根据ds.extra_options来dump不同的信息
    if (telephony_only) {
        DumpIpTables();
        if (!DropRootUser()) {
            return -1;
        }
        do_dmesg();
        DoLogcat();
        DoKmsg();
        ds.DumpstateBoard();
        DumpModemLogs();
    } else {
        // Dumps systrace right away, otherwise it will be filled with unnecessary events.
        // First try to dump anrd trace if the daemon is running. Otherwise, dump
        // the raw trace.
        if (!dump_anrd_trace()) {
            dump_systrace();
        }

        // 在dump_traces()之前调用以下dumpsys调用,以尝试使系统统计信息尽可能接近其初始状态。
        RunDumpsys("DUMPSYS MEMINFO", {"meminfo", "-a"},
                   CommandOptions::WithTimeout(90).DropRoot().Build());
        RunDumpsys("DUMPSYS CPUINFO", {"cpuinfo", "-a"},
                   CommandOptions::WithTimeout(10).DropRoot().Build());

        // TODO: Drop root user and move into dumpstate() once b/28633932 is fixed.
       //看起来还有一些需要完善的点,在某些问题修复后
        dump_raft();

        /* 收集虚拟机和native进程的stack traces(需要root权限) */
        dump_traces_path = dump_traces();

        /* 获取tombstone文件描述符. */
        get_tombstone_fds(tombstone_data);
        ds.AddDir(RECOVERY_DIR, true);
        ds.AddDir(RECOVERY_DATA_DIR, true);
        ds.AddDir(LOGPERSIST_DATA_DIR, false);
        if (!PropertiesHelper::IsUserBuild()) {
            ds.AddDir(PROFILE_DATA_DIR_CUR, true);
            ds.AddDir(PROFILE_DATA_DIR_REF, true);
        }
        add_mountinfo();
        DumpIpTables();

        // 捕获正在使用的任何IPSec策略。 这里没有key
        RunCommand("IP XFRM POLICY", {"ip", "xfrm", "policy"},
                   CommandOptions::WithTimeout(10).Build());

        // 以root身份运行ss,以便我们可以看到socket标记.
        RunCommand("DETAILED SOCKET STATE", {"ss", "-eionptu"},
                   CommandOptions::WithTimeout(10).Build());

        if (!DropRootUser()) {
            return -1;
        }
        //这里是真正干活的地方
        dumpstate();
    }

    /* 如果需要关闭输出 */
    if (is_redirecting) {
        fclose(stdout);
    }

    /* 重命名或者压缩.tmp 文件 到最终的地方*/
    if (use_outfile) {

        /* 检查用户是否使用系统属性更改了后缀 */
        std::string name = android::base::GetProperty(
            android::base::StringPrintf("dumpstate.%d.name", ds.pid_), "");
        bool change_suffix= false;
        if (!name.empty()) {
            /* 字符规则匹配 */
            std::regex valid_regex("^[-_a-zA-Z0-9]+$");
            if (std::regex_match(name.c_str(), valid_regex)) {
                change_suffix = true;
            } else {
                MYLOGE("invalid suffix provided by user: %s\n", name.c_str());
            }
        }
        if (change_suffix) {
            MYLOGI("changing suffix from %s to %s\n", ds.name_.c_str(), name.c_str());
            ds.name_ = name;
            if (!ds.screenshot_path_.empty()) {
                std::string new_screenshot_path = ds.GetPath(".png");
                if (rename(ds.screenshot_path_.c_str(), new_screenshot_path.c_str())) {
                    MYLOGE("rename(%s, %s): %s\n", ds.screenshot_path_.c_str(),
                           new_screenshot_path.c_str(), strerror(errno));
                } else {
                    ds.screenshot_path_ = new_screenshot_path;
                }
            }
        }

        bool do_text_file = true;
        if (do_zip_file) {
            if (!ds.FinishZipFile()) {
                MYLOGE("Failed to finish zip file; sending text bugreport instead\n");
                do_text_file = true;
            } else {
                do_text_file = false;
                // 如果之前的压缩文件存在,则要重命名
                std::string new_path = ds.GetPath(".zip");
                if (ds.path_ != new_path) {
                    MYLOGD("Renaming zip file from %s to %s\n", ds.path_.c_str(), new_path.c_str());
                    if (rename(ds.path_.c_str(), new_path.c_str())) {
                        MYLOGE("rename(%s, %s): %s\n", ds.path_.c_str(), new_path.c_str(),
                               strerror(errno));
                    } else {
                        ds.path_ = new_path;
                    }
                }
            }
        }
        if (do_text_file) {
            ds.path_ = ds.GetPath(".txt");
            MYLOGD("Generating .txt bugreport at %s from %s\n", ds.path_.c_str(),
                   ds.tmp_path_.c_str());
            if (rename(ds.tmp_path_.c_str(), ds.path_.c_str())) {
                MYLOGE("rename(%s, %s): %s\n", ds.tmp_path_.c_str(), ds.path_.c_str(),
                       strerror(errno));
                ds.path_.clear();
            }
        }
        if (use_control_socket) {
            if (do_text_file) {
                dprintf(ds.control_socket_fd_,
                        "FAIL:could not create zip file, check %s "
                        "for more details\n",
                        ds.log_path_.c_str());
            } else {
                dprintf(ds.control_socket_fd_, "OK:%s\n", ds.path_.c_str());
            }
        }
    }

    /* 震动告知用户bugreport已经生成 */
    for (int i = 0; i < 3; i++) {
        Vibrate(75);
        usleep((75 + 50) * 1000);
    }

    /* 通过发送广播告知ActivityManager已完成bugreport操作 */
    if (do_broadcast) {
        if (!ds.path_.empty()) {
            MYLOGI("Final bugreport path: %s\n", ds.path_.c_str());
            // clang-format off

            std::vector<std::string> am_args = {
                 "--receiver-permission", "android.permission.DUMP",
                 "--ei", "android.intent.extra.ID", std::to_string(ds.id_),
                 "--ei", "android.intent.extra.PID", std::to_string(ds.pid_),
                 "--ei", "android.intent.extra.MAX", std::to_string(ds.progress_->GetMax()),
                 "--es", "android.intent.extra.BUGREPORT", ds.path_,
                 "--es", "android.intent.extra.DUMPSTATE_LOG", ds.log_path_
            };
            // clang-format on
            if (do_fb) {
                am_args.push_back("--es");
                am_args.push_back("android.intent.extra.SCREENSHOT");
                am_args.push_back(ds.screenshot_path_);
            }
            if (!ds.notification_title.empty()) {
                am_args.push_back("--es");
                am_args.push_back("android.intent.extra.TITLE");
                am_args.push_back(ds.notification_title);
                if (!ds.notification_description.empty()) {
                    am_args.push_back("--es");
                    am_args.push_back("android.intent.extra.DESCRIPTION");
                    am_args.push_back(ds.notification_description);
                }
            }
            if (is_remote_mode) {
                am_args.push_back("--es");
                am_args.push_back("android.intent.extra.REMOTE_BUGREPORT_HASH");
                am_args.push_back(SHA256_file_hash(ds.path_));
                SendBroadcast("com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED",
                              am_args);
            } else {
                SendBroadcast("com.android.internal.intent.action.BUGREPORT_FINISHED", am_args);
            }
        } else {
            MYLOGE("Skipping finished broadcast because bugreport could not be generated\n");
        }
    }

    MYLOGD("Final progress: %d/%d (estimated %d)\n", ds.progress_->Get(), ds.progress_->GetMax(),
           ds.progress_->GetInitialMax());
    ds.progress_->Save();
    MYLOGI("done (id %d)\n", ds.id_);

    if (is_redirecting) {
        fclose(stderr);
    }

    if (use_control_socket && ds.control_socket_fd_ != -1) {
        MYLOGD("Closing control socket\n");
        close(ds.control_socket_fd_);
    }

    return 0;
}

从代码上来看8.0与6.0源码改动并不是很大。
整个工作流程如下:

  1. 提高执行dumpsate所在进程的优先级,防止被OOM Killer杀死,这里针对内核版本做了一些兼容;
  2. 参数解析,可通过命令adb shell dumpstate -h查看dumpstate命令所支持的参数;
  3. 根据PROPERTY_EXTRA_OPTIONS系统属性来做区分,主要类型有bugreportplus,bugreportremote,bugreportwear,bugreporttelephony
    并获取ams出设置的通知的标题,描述,
  4. 打开vibrator,用于在执行bugreport时,手机会先震动一下用于提醒开始抓取系统信息;
  5. 通过dump_traces()来完成收集虚拟机和native进程的stack traces;
  6. 通过get_tombstone_fds来获取tombstone文件描述符;
  7. 根据ds.extra_options来dump不同的信息 telephony_only为判断标识,否则执行dumpstate(),这里是真正干活的地方;
  8. 再次通过震动以提醒dump操作执行完成;
  9. 发送广播,告知ActivityManager已完成bugreport操作。

接下来就重点说说dumpstate()功能:

2.3 dumpstate()

该方法负责整个bugreport内容输出的最为核心的功能。
dumpstate.cpp

static void dumpstate() {
    DurationReporter duration_reporter("DUMPSTATE");

    dump_dev_files("TRUSTY VERSION", "/sys/bus/platform/drivers/trusty", "trusty_version");
    //记录系统运行时长和休眠时长
    RunCommand("UPTIME", {"uptime"});
    //输出mmcblk0设备信息
    dump_files("UPTIME MMC PERF", mmcblk0, skip_not_stat, dump_stat_from_fd);
    dump_emmc_ecsd("/d/mmc0/mmc0:0001/ext_csd");
    DumpFile("MEMORY INFO", "/proc/meminfo");
    RunCommand("CPU INFO", {"top", "-b", "-n", "1", "-H", "-s", "6", "-o",
                            "pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"});
    RunCommand("PROCRANK", {"procrank"}, AS_ROOT_20);
    DumpFile("VIRTUAL MEMORY STATS", "/proc/vmstat");
    DumpFile("VMALLOC INFO", "/proc/vmallocinfo");
    DumpFile("SLAB INFO", "/proc/slabinfo");
    DumpFile("ZONEINFO", "/proc/zoneinfo");
    DumpFile("PAGETYPEINFO", "/proc/pagetypeinfo");
    DumpFile("BUDDYINFO", "/proc/buddyinfo");
    DumpFile("FRAGMENTATION INFO", "/d/extfrag/unusable_index");

    DumpFile("KERNEL WAKE SOURCES", "/d/wakeup_sources");
    DumpFile("KERNEL CPUFREQ", "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state");
    DumpFile("KERNEL SYNC", "/d/sync");

    RunCommand("PROCESSES AND THREADS",
               {"ps", "-A", "-T", "-Z", "-O", "pri,nice,rtprio,sched,pcy"});
    RunCommand("LIBRANK", {"librank"}, CommandOptions::AS_ROOT);

    if (ds.IsZipping()) {
        RunCommand(
                "HARDWARE HALS",
                {"lshal", std::string("--debug=") + kLsHalDebugPath},
                CommandOptions::AS_ROOT);

        ds.AddZipEntry("lshal-debug.txt", kLsHalDebugPath);

        unlink(kLsHalDebugPath.c_str());
    } else {
        RunCommand(
                "HARDWARE HALS", {"lshal", "--debug"}, CommandOptions::AS_ROOT);
    }

    RunCommand("PRINTENV", {"printenv"});
    RunCommand("NETSTAT", {"netstat", "-nW"});
    struct stat s;
    if (stat("/proc/modules", &s) != 0) {
        MYLOGD("Skipping 'lsmod' because /proc/modules does not exist\n");
    } else {
        RunCommand("LSMOD", {"lsmod"});
    }
    //输出kernel log
    do_dmesg();
    //所有已打开文件
    RunCommand("LIST OF OPEN FILES", {"lsof"}, CommandOptions::AS_ROOT);
    //遍历所有进程的show map
    for_each_pid(do_showmap, "SMAPS OF ALL PROCESSES");
    //显示所有线程的blocked位置
    for_each_tid(show_wchan, "BLOCKED PROCESS WAIT-CHANNELS");
    for_each_pid(show_showtime, "PROCESS TIMES (pid cmd user system iowait+percentage)");

    /* Dump Bluetooth HCI logs */
    ds.AddDir("/data/misc/bluetooth/logs", true);

    if (!ds.do_early_screenshot_) {
        MYLOGI("taking late screenshot\n");
        ds.TakeScreenshot();
    }
    //获取main,system,event,radio ,last logcat等信息
    DoLogcat();
    //添加anr trace 文件
    AddAnrTraceFiles();

    int dumped = 0;
    for (size_t i = 0; i < NUM_TOMBSTONES; i++) {
        if (tombstone_data[i].fd != -1) {
            const char *name = tombstone_data[i].name;
            int fd = tombstone_data[i].fd;
            dumped = 1;
            if (ds.IsZipping()) {
                if (!ds.AddZipEntryFromFd(ZIP_ROOT_DIR + name, fd)) {
                    MYLOGE("Unable to add tombstone %s to zip file\n", name);
                }
            } else {
                dump_file_from_fd("TOMBSTONE", name, fd);
            }
            close(fd);
            tombstone_data[i].fd = -1;
        }
    }
    if (!dumped) {
        printf("*** NO TOMBSTONES to dump in %s\n\n", TOMBSTONE_DIR);
    }

    DumpFile("NETWORK DEV INFO", "/proc/net/dev");
    DumpFile("QTAGUID NETWORK INTERFACES INFO", "/proc/net/xt_qtaguid/iface_stat_all");
    DumpFile("QTAGUID NETWORK INTERFACES INFO (xt)", "/proc/net/xt_qtaguid/iface_stat_fmt");
    DumpFile("QTAGUID CTRL INFO", "/proc/net/xt_qtaguid/ctrl");
    DumpFile("QTAGUID STATS INFO", "/proc/net/xt_qtaguid/stats");
    //kernel log
    DoKmsg();

    /* The following have a tendency to get wedged when wifi drivers/fw goes belly-up. */
    //wifi驱动/固件 以及ip相关信息
    RunCommand("NETWORK INTERFACES", {"ip", "link"});

    RunCommand("IPv4 ADDRESSES", {"ip", "-4", "addr", "show"});
    RunCommand("IPv6 ADDRESSES", {"ip", "-6", "addr", "show"});

    RunCommand("IP RULES", {"ip", "rule", "show"});
    RunCommand("IP RULES v6", {"ip", "-6", "rule", "show"});

    dump_route_tables();

    RunCommand("ARP CACHE", {"ip", "-4", "neigh", "show"});
    RunCommand("IPv6 ND CACHE", {"ip", "-6", "neigh", "show"});
    RunCommand("MULTICAST ADDRESSES", {"ip", "maddr"});
    RunCommand("WIFI NETWORKS", {"wpa_cli", "IFNAME=wlan0", "list_networks"},
               CommandOptions::WithTimeout(20).Build());

    RunDumpsys("NETWORK DIAGNOSTICS", {"connectivity", "--diag"},
               CommandOptions::WithTimeout(10).Build());

    RunCommand("SYSTEM PROPERTIES", {"getprop"});

    RunCommand("VOLD DUMP", {"vdc", "dump"});
    RunCommand("SECURE CONTAINERS", {"vdc", "asec", "list"});

    RunCommand("STORAGED TASKIOINFO", {"storaged", "-u"}, CommandOptions::WithTimeout(10).Build());

    RunCommand("FILESYSTEMS & FREE SPACE", {"df"});

    RunCommand("LAST RADIO LOG", {"parse_radio_log", "/proc/last_radio_log"});
    //背光信息
    printf("------ BACKLIGHTS ------\n");
    printf("LCD brightness=");
    DumpFile("", "/sys/class/leds/lcd-backlight/brightness");
    printf("Button brightness=");
    DumpFile("", "/sys/class/leds/button-backlight/brightness");
    printf("Keyboard brightness=");
    DumpFile("", "/sys/class/leds/keyboard-backlight/brightness");
    printf("ALS mode=");
    DumpFile("", "/sys/class/leds/lcd-backlight/als");
    printf("LCD driver registers:\n");
    DumpFile("", "/sys/class/leds/lcd-backlight/registers");
    printf("\n");

    /* Binder state is expensive to look at as it uses a lot of memory.
       binder相关信息 */
    DumpFile("BINDER FAILED TRANSACTION LOG", "/sys/kernel/debug/binder/failed_transaction_log");
    DumpFile("BINDER TRANSACTION LOG", "/sys/kernel/debug/binder/transaction_log");
    DumpFile("BINDER TRANSACTIONS", "/sys/kernel/debug/binder/transactions");
    DumpFile("BINDER STATS", "/sys/kernel/debug/binder/stats");
    DumpFile("BINDER STATE", "/sys/kernel/debug/binder/state");

    ds.DumpstateBoard();

    /* Migrate the ril_dumpstate to a device specific dumpstate? */
    int rilDumpstateTimeout = android::base::GetIntProperty("ril.dumpstate.timeout", 0);
    if (rilDumpstateTimeout > 0) {
        // su does not exist on user builds, so try running without it.
        // This way any implementations of vril-dump that do not require
        // root can run on user builds.
        CommandOptions::CommandOptionsBuilder options =
            CommandOptions::WithTimeout(rilDumpstateTimeout);
        if (!PropertiesHelper::IsUserBuild()) {
            options.AsRoot();
        }
        RunCommand("DUMP VENDOR RIL LOGS", {"vril-dump"}, options.Build());
    }

    //输出framework各种服务的dumpsys信息 
    printf("========================================================\n");
    printf("== Android Framework Services\n");
    printf("========================================================\n");

    RunDumpsys("DUMPSYS", {"--skip", "meminfo", "cpuinfo"}, CommandOptions::WithTimeout(90).Build(),
               10);

    printf("========================================================\n");
    printf("== Checkins\n");
    printf("========================================================\n");

    RunDumpsys("CHECKIN BATTERYSTATS", {"batterystats", "-c"});
    RunDumpsys("CHECKIN MEMINFO", {"meminfo", "--checkin"});
    RunDumpsys("CHECKIN NETSTATS", {"netstats", "--checkin"});
    RunDumpsys("CHECKIN PROCSTATS", {"procstats", "-c"});
    RunDumpsys("CHECKIN USAGESTATS", {"usagestats", "-c"});
    RunDumpsys("CHECKIN PACKAGE", {"package", "--checkin"});

     //输出当前 运行中activity/service/provider信息
    printf("========================================================\n");
    printf("== Running Application Activities\n");
    printf("========================================================\n");

    RunDumpsys("APP ACTIVITIES", {"activity", "-v", "all"});

    printf("========================================================\n");
    printf("== Running Application Services\n");
    printf("========================================================\n");

    RunDumpsys("APP SERVICES", {"activity", "service", "all"});

    printf("========================================================\n");
    printf("== Running Application Providers\n");
    printf("========================================================\n");

    RunDumpsys("APP PROVIDERS", {"activity", "provider", "all"});

    printf("========================================================\n");
    printf("== Dropbox crashes\n");
    printf("========================================================\n");

    RunDumpsys("DROPBOX SYSTEM SERVER CRASHES", {"dropbox", "-p", "system_server_crash"});
    RunDumpsys("DROPBOX SYSTEM APP CRASHES", {"dropbox", "-p", "system_app_crash"});

    //获取modem log
    DumpModemLogs();

    printf("========================================================\n");
    printf("== Final progress (pid %d): %d/%d (estimated %d)\n", ds.pid_, ds.progress_->Get(),
           ds.progress_->GetMax(), ds.progress_->GetInitialMax());
    printf("========================================================\n");
    printf("== dumpstate: done (id %d)\n", ds.id_);
    printf("========================================================\n");
}

按照源码中的调用顺序,依次列出相关的函数如下:

2.3.1 PrintHeader()

打印了系统build相关信息

void Dumpstate::PrintHeader() const {
    std::string build, fingerprint, radio, bootloader, network;
    char date[80];

    build = android::base::GetProperty("ro.build.display.id", "(unknown)");
    fingerprint = android::base::GetProperty("ro.build.fingerprint", "(unknown)");
    radio = android::base::GetProperty("gsm.version.baseband", "(unknown)");
    bootloader = android::base::GetProperty("ro.bootloader", "(unknown)");
    network = android::base::GetProperty("gsm.operator.alpha", "(unknown)");
    strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", localtime(&now_));

    printf("========================================================\n");
    printf("== dumpstate: %s\n", date);
    printf("========================================================\n");

    printf("\n");
    printf("Build: %s\n", build.c_str());
    // NOTE: fingerprint entry format is important for other tools.
    printf("Build fingerprint: '%s'\n", fingerprint.c_str());
    printf("Bootloader: %s\n", bootloader.c_str());
    printf("Radio: %s\n", radio.c_str());
    printf("Network: %s\n", network.c_str());

    printf("Kernel: ");
    DumpFileToFd(STDOUT_FILENO, "", "/proc/version");
    printf("Command line: %s\n", strtok(cmdline_buf, "\n"));
    printf("Bugreport format version: %s\n", version_.c_str());
    printf("Dumpstate info: id=%d pid=%d dry_run=%d args=%s extra_options=%s\n", id_, pid_,
           PropertiesHelper::IsDryRun(), args_.c_str(), extra_options_.c_str());
    printf("\n");
}

2.3.2 do_dmesg()

utils.cpp

void do_dmesg() {
    const char *title = "KERNEL LOG (dmesg)";
    DurationReporter duration_reporter(title);
    printf("------ %s ------\n", title);

    if (PropertiesHelper::IsDryRun()) return;

    /* 获取kernel buffer的大小*/
    int size = klogctl(KLOG_SIZE_BUFFER, NULL, 0);
    if (size <= 0) {
        printf("Unexpected klogctl return value: %d\n\n", size);
        return;
    }
    char *buf = (char *) malloc(size + 1);
    if (buf == NULL) {
        printf("memory allocation failed\n\n");
        return;
    }
    //获取kernel log
    int retval = klogctl(KLOG_READ_ALL, buf, size);
    if (retval < 0) {
        printf("klogctl failure\n\n");
        free(buf);
        return;
    }
    buf[retval] = '\0';
    printf("%s\n\n", buf);
    free(buf);
    return;
}

2.3.3 DoLogcat()

static void DoLogcat() {
    unsigned long timeout;
    // DumpFile("EVENT LOG TAGS", "/etc/event-log-tags");
    // calculate timeout
    timeout = logcat_timeout("main") + logcat_timeout("system") + logcat_timeout("crash");
    if (timeout < 20000) {
        timeout = 20000;
    }
    RunCommand("SYSTEM LOG",
               {"logcat", "-v", "threadtime", "-v", "printable", "-v", "uid",
                        "-d", "*:v"},
               CommandOptions::WithTimeout(timeout / 1000).Build());
    timeout = logcat_timeout("events");
    if (timeout < 20000) {
        timeout = 20000;
    }
    RunCommand("EVENT LOG",
               {"logcat", "-b", "events", "-v", "threadtime", "-v", "printable", "-v", "uid",
                        "-d", "*:v"},
               CommandOptions::WithTimeout(timeout / 1000).Build());
    timeout = logcat_timeout("radio");
    if (timeout < 20000) {
        timeout = 20000;
    }
    RunCommand("RADIO LOG",
               {"logcat", "-b", "radio", "-v", "threadtime", "-v", "printable", "-v", "uid",
                        "-d", "*:v"},
               CommandOptions::WithTimeout(timeout / 1000).Build());

    RunCommand("LOG STATISTICS", {"logcat", "-b", "all", "-S"});

    /* kernels must set CONFIG_PSTORE_PMSG, slice up pstore with device tree */
    RunCommand("LAST LOGCAT",
                {"logcat", "-L", "-b", "all", "-v", "threadtime", "-v", "printable", "-v", "uid",
                        "-d", "*:v"});
}

2.3.4 AddAnrTraceFiles()

static void AddAnrTraceFiles() {
    bool add_to_zip = ds.IsZipping() && ds.version_ == VERSION_SPLIT_ANR;
    std::string dump_traces_dir;

    /* show the traces we collected in main(), if that was done */
    if (dump_traces_path != nullptr) {
        if (add_to_zip) {
            dump_traces_dir = dirname(dump_traces_path);
            MYLOGD("Adding ANR traces (directory %s) to the zip file\n", dump_traces_dir.c_str());
            ds.AddDir(dump_traces_dir, true);
        } else {
            MYLOGD("Dumping current ANR traces (%s) to the main bugreport entry\n",
                   dump_traces_path);
            ds.DumpFile("VM TRACES JUST NOW", dump_traces_path);
        }
    }

    std::string anr_traces_path = android::base::GetProperty("dalvik.vm.stack-trace-file", "");
    std::string anr_traces_dir = dirname(anr_traces_path.c_str());

    // Make sure directory is not added twice.
    // TODO: this is an overzealous check because it's relying on dump_traces_path - which is
    // generated by dump_traces() -  and anr_traces_path - which is retrieved from a system
    // property - but in reality they're the same path (although the former could be nullptr).
    // Anyways, once dump_traces() is refactored as a private Dumpstate function, this logic should
    // be revisited.
    bool already_dumped = anr_traces_dir == dump_traces_dir;

    MYLOGD("AddAnrTraceFiles(): dump_traces_dir=%s, anr_traces_dir=%s, already_dumped=%d\n",
           dump_traces_dir.c_str(), anr_traces_dir.c_str(), already_dumped);

    if (anr_traces_path.empty()) {
        printf("*** NO VM TRACES FILE DEFINED (dalvik.vm.stack-trace-file)\n\n");
    } else {
        int fd = TEMP_FAILURE_RETRY(
            open(anr_traces_path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK));
        if (fd < 0) {
            printf("*** NO ANR VM TRACES FILE (%s): %s\n\n", anr_traces_path.c_str(),
                   strerror(errno));
        } else {
            if (add_to_zip) {
                if (!already_dumped) {
                    MYLOGD("Adding dalvik ANR traces (directory %s) to the zip file\n",
                           anr_traces_dir.c_str());
                    ds.AddDir(anr_traces_dir, true);
                    already_dumped = true;
                }
            } else {
                MYLOGD("Dumping last ANR traces (%s) to the main bugreport entry\n",
                       anr_traces_path.c_str());
                dump_file_from_fd("VM TRACES AT LAST ANR", anr_traces_path.c_str(), fd);
            }
        }
    }

    if (add_to_zip && already_dumped) {
        MYLOGD("Already dumped directory %s to the zip file\n", anr_traces_dir.c_str());
        return;
    }

    /* 输出慢操作的vm traces,例如/data/anr/slow1.txt*/
    struct stat st;
    if (!anr_traces_path.empty()) {
        int tail = anr_traces_path.size() - 1;
        while (tail > 0 && anr_traces_path.at(tail) != '/') {
            tail--;
        }
        int i = 0;
        while (1) {
            anr_traces_path = anr_traces_path.substr(0, tail + 1) +
                              android::base::StringPrintf("slow%02d.txt", i);
            if (stat(anr_traces_path.c_str(), &st)) {
                // No traces file at this index, done with the files.
                break;
            }
            ds.DumpFile("VM TRACES WHEN SLOW", anr_traces_path.c_str());
            i++;
        }
    }
}

2.3.5 RunCommand()

static int RunCommand(const std::string& title, const std::vector<std::string>& full_command,
                      const CommandOptions& options = CommandOptions::DEFAULT) {
    return ds.RunCommand(title, full_command, options);
}

2.3.6 RunDumpsys()

static void RunDumpsys(const std::string& title, const std::vector<std::string>& dumpsysArgs,
                       const CommandOptions& options = Dumpstate::DEFAULT_DUMPSYS,
                       long dumpsysTimeout = 0) {
    return ds.RunDumpsys(title, dumpsysArgs, options, dumpsysTimeout);
}

2.3.7 dump_files()

```utils.cpp```
/* calls skip to gate calling dump_from_fd recursively
 * in the specified directory. dump_from_fd defaults to
 * dump_file_from_fd above when set to NULL. skip defaults
 * to false when set to NULL. dump_from_fd will always be
 * called with title NULL.
 */
int dump_files(const std::string& title, const char* dir, bool (*skip)(const char* path),
               int (*dump_from_fd)(const char* title, const char* path, int fd)) {
    DurationReporter duration_reporter(title);
    DIR *dirp;
    struct dirent *d;
    char *newpath = NULL;
    const char *slash = "/";
    int fd, retval = 0;

    if (!title.empty()) {
        printf("------ %s (%s) ------\n", title.c_str(), dir);
    }
    if (PropertiesHelper::IsDryRun()) return 0;

    if (dir[strlen(dir) - 1] == '/') {
        ++slash;
    }
    dirp = opendir(dir);
    if (dirp == NULL) {
        retval = -errno;
        MYLOGE("%s: %s\n", dir, strerror(errno));
        return retval;
    }

    if (!dump_from_fd) {
        dump_from_fd = dump_file_from_fd;
    }
    for (; ((d = readdir(dirp))); free(newpath), newpath = NULL) {
        if ((d->d_name[0] == '.')
         && (((d->d_name[1] == '.') && (d->d_name[2] == '\0'))
          || (d->d_name[1] == '\0'))) {
            continue;
        }
        asprintf(&newpath, "%s%s%s%s", dir, slash, d->d_name,
                 (d->d_type == DT_DIR) ? "/" : "");
        if (!newpath) {
            retval = -errno;
            continue;
        }
        if (skip && (*skip)(newpath)) {
            continue;
        }
        if (d->d_type == DT_DIR) {
            int ret = dump_files("", newpath, skip, dump_from_fd);
            if (ret < 0) {
                retval = ret;
            }
            continue;
        }
        fd = TEMP_FAILURE_RETRY(open(newpath, O_RDONLY | O_NONBLOCK | O_CLOEXEC));
        if (fd < 0) {
            retval = fd;
            printf("*** %s: %s\n", newpath, strerror(errno));
            continue;
        }
        (*dump_from_fd)(NULL, newpath, fd);
    }
    closedir(dirp);
    if (!title.empty()) {
        printf("\n");
    }
    return retval;
}

2.3.8 DumpFile()

utils.cpp

int Dumpstate::DumpFile(const std::string& title, const std::string& path) {
    DurationReporter duration_reporter(title);

    int status = DumpFileToFd(STDOUT_FILENO, title, path);

    UpdateProgress(WEIGHT_FILE);

    return status;
}

2.3.9 DumpFileToFd()

DumpstateUtil.cpp

int DumpFileToFd(int out_fd, const std::string& title, const std::string& path) {
    int fd = TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC));
    if (fd < 0) {
        int err = errno;
        if (title.empty()) {
            dprintf(out_fd, "*** Error dumping %s: %s\n", path.c_str(), strerror(err));
        } else {
            dprintf(out_fd, "*** Error dumping %s (%s): %s\n", path.c_str(), title.c_str(),
                    strerror(err));
        }
        fsync(out_fd);
        return -1;
    }
    return DumpFileFromFdToFd(title, path, fd, out_fd, PropertiesHelper::IsDryRun());
}

2.3.10 DumpFileFromFdToFd()

DumpstateInternal.cpp

int DumpFileFromFdToFd(const std::string& title, const std::string& path_string, int fd, int out_fd,
                       bool dry_run) {
    const char* path = path_string.c_str();
    if (!title.empty()) {
        dprintf(out_fd, "------ %s (%s", title.c_str(), path);

        struct stat st;
        // Only show the modification time of non-device files.
        size_t path_len = strlen(path);
        if ((path_len < 6 || memcmp(path, "/proc/", 6)) &&
            (path_len < 5 || memcmp(path, "/sys/", 5)) &&
            (path_len < 3 || memcmp(path, "/d/", 3)) && !fstat(fd, &st)) {
            char stamp[80];
            time_t mtime = st.st_mtime;
            strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", localtime(&mtime));
            dprintf(out_fd, ": %s", stamp);
        }
        dprintf(out_fd, ") ------\n");
        fsync(out_fd);
    }
    if (dry_run) {
        if (out_fd != STDOUT_FILENO) {
            // There is no title, but we should still print a dry-run message
            dprintf(out_fd, "%s: skipped on dry run\n", path);
        } else if (!title.empty()) {
            dprintf(out_fd, "\t(skipped on dry run)\n");
        }
        fsync(out_fd);
        return 0;
    }
    bool newline = false;
    fd_set read_set;
    timeval tm;
    while (true) {
        FD_ZERO(&read_set);
        FD_SET(fd, &read_set);
        /* Timeout if no data is read for 30 seconds. */
        tm.tv_sec = 30;
        tm.tv_usec = 0;
        uint64_t elapsed = Nanotime();
        int ret = TEMP_FAILURE_RETRY(select(fd + 1, &read_set, nullptr, nullptr, &tm));
        if (ret == -1) {
            dprintf(out_fd, "*** %s: select failed: %s\n", path, strerror(errno));
            newline = true;
            break;
        } else if (ret == 0) {
            elapsed = Nanotime() - elapsed;
            dprintf(out_fd, "*** %s: Timed out after %.3fs\n", path, (float)elapsed / NANOS_PER_SEC);
            newline = true;
            break;
        } else {
            char buffer[65536];
            ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
            if (bytes_read > 0) {
                android::base::WriteFully(out_fd, buffer, bytes_read);
                newline = (buffer[bytes_read - 1] == '\n');
            } else {
                if (bytes_read == -1) {
                    dprintf(out_fd, "*** %s: Failed to read from fd: %s", path, strerror(errno));
                    newline = true;
                }
                break;
            }
        }
    }
    close(fd);

    if (!newline) dprintf(out_fd, "\n");
    if (!title.empty()) dprintf(out_fd, "\n");
    return 0;
}

2.4 总结

Bugreport通过socket与dumpstate服务建立通信,在dumpstate.cpp中根据系统属性PROPERTY_EXTRA_OPTIONS来区分bugreportplus,bugreportremote,bugreportwear,bugreporttelephony等这几种类型来获取不同的信息。如果是telephony_only为ture,则会调用DumpIpTables(), do_dmesg(), DoLogcat(),DoKmsg(),DumpstateBoard(), DumpModemLogs(),其他类型则会最终调用到dumpstate()方法完成核心功能,该功能依次输出内容项, 主要分为5大类:

  1. current log: kernel,system, event, radio;
  2. last log: kernel, system, radio;
  3. vm traces: just now, last ANR, tombstones
  4. dumpsys: all, checkin, app
  5. system info:cpu, memory, io等

从bugreport内容的输出顺序的角度,再详细列举其内容:

  1. 系统build以及运行时长等相关信息;
  2. 内存/CPU/进程等信息;
  3. kernel log;
  4. lsof、map及Wait-Channels;
  5. system log;
  6. event log;
  7. radio log;
  8. vm traces:
    -- VM TRACES JUST NOW (/data/anr/traces.txt.bugreport) (抓bugreport时主动触发)
    -- VM TRACES AT LAST ANR (/data/anr/traces.txt) (存在则输出)
    -- TOMBSTONE (/data/tombstones/tombstone_xx) (存在这输出)
  9. network相关信息;
  10. last kernel log;
  11. last system log;
  12. ip相关信息;
  13. 中断向量表
  14. property以及fs等信息
  15. last radio log;
  16. Binder相关信息;
  17. dumpsys all:
  18. dumpsys checkin相关:
    -- dumpsys batterystats电池统计;
    -- dumpsys meminfo内存
    -- dumpsys netstats网络统计;
    -- dumpsys procstats进程统计;
    -- dumpsys usagestats使用情况;
    -- dumpsys package.
  19. dumpsys app相关
    -- dumpsys activity;
    -- dumpsys activity service all;
    -- dumpsys activity provider all.

Bugreport几乎涵盖整个系统信息,内容非常长。跟android6.0相比改动不大,只是针对不同的arm设备做了更精细的分类和处理。在Bugreport中,几乎很多信息子项都以------ xxx ------开头。 例如APP ACTIVITIES的开头便是 ------ APP ACTIVITIES (dumpsys activity all) ------,其中括号内的便是输出该信息指令,即dumpsys activity all。Bugreport对于分析系统问题有很重要的帮助。

参考:
http://gityuan.com/2016/06/10/bugreport/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 2,619评论 0 3
  • 版权声明:本文为作者原创,转载必须注明出处。转载请注明出处:https://www.jianshu.com/p/9...
    凡星轨迹阅读 10,024评论 0 55
  • 我就是想, 什么都不做, 就任东西躺在地板上, 就任耳机歌唱着, 就任风扇旋转着, 就任台灯照耀着, 就任思维兴奋...
    M_152阅读 285评论 0 1
  • 双面人有两张脸,一张高兴,一张悲伤。他在高兴的时候,用高兴脸,在不高兴的时候,用悲伤脸。 双面人对此很满意。他有点...
    菩拉阅读 1,307评论 5 6
  • 今天中午爸爸回来接我。带我去妈妈上班的地方。我们玩了很久。晚上嘛,我认识了三个大姐姐,一个大哥哥。我和大姐们聊了很...
    517cd8555872阅读 67评论 0 0