版权声明:本文为作者原创,转载必须注明出处。
转载请注明出处: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"], }
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服务的工作。
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源码改动并不是很大。
整个工作流程如下:
提高执行dumpsate所在进程的优先级,防止被OOM Killer杀死,这里针对内核版本做了一些兼容;
参数解析,可通过命令adb shell dumpstate -h查看dumpstate命令所支持的参数;
根据PROPERTY_EXTRA_OPTIONS系统属性来做区分,主要类型有bugreportplus,bugreportremote,bugreportwear,bugreporttelephony
并获取ams出设置的通知的标题,描述,
打开vibrator,用于在执行bugreport时,手机会先震动一下用于提醒开始抓取系统信息;
通过dump_traces()来完成收集虚拟机和native进程的stack traces;
通过get_tombstone_fds来获取tombstone文件描述符;
根据ds.extra_options来dump不同的信息 telephony_only为判断标识,否则执行dumpstate(),这里是真正干活的地方;
再次通过震动以提醒dump操作执行完成;
发送广播,告知ActivityManager已完成bugreport操作。
接下来就重点说说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"); }
按照源码中的调用顺序,依次列出相关的函数如下:
打印了系统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"); }
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; }
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"}); }
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++; } } }
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); }
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); }
```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; }
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; }
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()); }
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; }
Bugreport通过socket与dumpstate服务建立通信,在dumpstate.cpp中根据系统属性PROPERTY_EXTRA_OPTIONS来区分bugreportplus,bugreportremote,bugreportwear,bugreporttelephony等这几种类型来获取不同的信息。如果是telephony_only为ture,则会调用DumpIpTables(), do_dmesg(), DoLogcat(),DoKmsg(),DumpstateBoard(), DumpModemLogs(),其他类型则会最终调用到dumpstate()方法完成核心功能,该功能依次输出内容项, 主要分为5大类:
current log: kernel,system, event, radio;
last log: kernel, system, radio;
vm traces: just now, last ANR, tombstones
dumpsys: all, checkin, app
system info:cpu, memory, io等
从bugreport内容的输出顺序的角度,再详细列举其内容:
系统build以及运行时长等相关信息;
内存/CPU/进程等信息;
kernel log;
lsof、map及Wait-Channels;
system log;
event log;
radio log;
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) (存在这输出)
network相关信息;
last kernel log;
last system log;
ip相关信息;
中断向量表
property以及fs等信息
last radio log;
Binder相关信息;
dumpsys all:
dumpsys checkin相关:
-- dumpsys batterystats电池统计;
-- dumpsys meminfo内存
-- dumpsys netstats网络统计;
-- dumpsys procstats进程统计;
-- dumpsys usagestats使用情况;
-- dumpsys package.
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对于分析系统问题有很重要的帮助。
联系客服