Loading cmds/dumpstate/bugreport-format.md 0 → 100644 +87 −0 Original line number Diff line number Diff line # Bugreport file format This document specifies the format of the bugreport files generated by the bugreport services (like `bugreport` and `bugreportplus`) and delivered to the end user (i.e., it doesn’t include other tools like `adb bugreport`). A _bugreport_ is initially generated by dumpstate, then processed by **Shell**, which in turn delivers it to the end user through a `ACTION_SEND_MULTIPLE` intent; the end user then select which app (like an email client) handles such intent. ## Text file (Pre-M) Prior to _Android M (Marshmallow)_, `dumpstate` generates a flat .txt file named _bugreport-DATE.txt_ (where _DATE_ is date the bugreport was generated, in the format _YYYY-MM-DD-HH-MM-SS_), and Shell simply propagates it as an attachment in the `ACTION_SEND_MULTIPLE` intent. ## Version v0 (Android M) On _Android M (Marshmallow)_, dumpstate still generates a flat _bugreport-DATE.txt_ file, but then **Shell** creates a zip file called _bugreport-DATE.zip_ containing a _bugreport-DATE.txt_ entry and sends that file as the `ACTION_SEND_MULTIPLE` attachment. ## Version v1 (Android N) On _Android N (TBD)_, `dumpstate` generates a zip file directly (unless there is a failure, in which case it reverts to the flat file that is zipped by **Shell** and hence the end result is the _v0_ format). The zip file is by default called _bugreport-DATE.zip_ and it contains a _bugreport-DATE.txt_ entry, although the end user can change the name (through **Shell**), in which case they would be called _bugreport-NEW_NAME.zip_ and _bugreport-NEW_NAME.txt_ respectively. The zip file also contains 2 metadata entries generated by `dumpstate`: - `version.txt`: whose value is **v1**. - `main-entry.txt`: whose value is the name of the flat text entry (i.e., _bugreport-DATE.txt_ or _bugreport-NEW_NAME.txt_). `dumpstate` can also copy files from the device’s filesystem into the zip file under the `FS` folder. For example, a `/dirA/dirB/fileC` file in the device would generate a `FS/dirA/dirB/fileC` entry in the zip file. The flat file also has some minor changes: - Tombstone files were removed and added to the zip file. - The duration of each section is printed in the report. - Some dumpsys sections (memory and cpuinfo) are reported earlier in the file. Besides the files generated by `dumpstate`, **Shell** can also add 2 other files upon the end user’s request: - `title.txt`: whose value is a single-line summary of the problem. - `description.txt`: whose value is a multi-line, detailed description of the problem. ## Intermediate versions During development, the versions will be suffixed with _-devX_ or _-devX-EXPERIMENTAL_FEATURE_, where _X_ is a number that increases as the changes become stable. For example, the initial version during _Android N_ development was **v1-dev1**. When `dumpsys` was split in 2 sections but not all tools were ready to parse that format, the version was named **v1-dev1-dumpsys-split**, which had to be passed do `dumpsys` explicitly (i.e., trhough a `-V v1-dev1-dumpsys-split` argument). Once that format became stable and tools knew how to parse it, the default version became **v1-dev2**. Similarly, if changes in the file format are made after the initial release of Android defining that format, then a new _sub-version_ will be used. For example, if after _Android N_ launches changes are made for the next _N_ release, the version will be called **v1.1** or something like that. Determining version and main entry ----------------------------------------------- Tools parsing the zipped bugreport file can use the following algorithm to determine the bugreport format version and its main entry: ``` If [entries contain "version.txt"] version = read("version.txt") main_entry = read("main_entry.txt") else version = v0 main_entry = entries[0] fi ``` cmds/dumpstate/dumpstate.cpp +62 −10 Original line number Diff line number Diff line Loading @@ -83,6 +83,16 @@ static tombstone_data_t tombstone_data[NUM_TOMBSTONES]; // Root dir for all files copied as-is into the bugreport const std::string& ZIP_ROOT_DIR = "FS"; /* * List of supported zip format versions. * * See bugreport-format.txt for more info. */ // TODO: change to "v1" before final N build static std::string VERSION_DEFAULT = "v1-dev1"; // TODO: remove before final N build static std::string VERSION_DUMPSYS_SPLIT = "v1-dev1-dumpsys-split"; /* gets the tombstone data, according to the bugreport type: if zipped gets all tombstones, * otherwise gets just those modified in the last half an hour. */ static void get_tombstone_fds(tombstone_data_t data[NUM_TOMBSTONES]) { Loading Loading @@ -134,7 +144,7 @@ void add_mountinfo() { mount_points.clear(); DurationReporter duration_reporter(title, NULL); for_each_pid(do_mountinfo, NULL); ALOGD("%s: %d entries added to zip file\n", title, mount_points.size()); ALOGD("%s: %lu entries added to zip file\n", title, mount_points.size()); } static void dump_dev_files(const char *title, const char *driverpath, const char *filename) Loading Loading @@ -325,7 +335,7 @@ static unsigned long logcat_timeout(const char *name) { /* End copy from system/core/logd/LogBuffer.cpp */ /* dumps the current system state to stdout */ static void print_header() { static void print_header(std::string version) { char build[PROPERTY_VALUE_MAX], fingerprint[PROPERTY_VALUE_MAX]; char radio[PROPERTY_VALUE_MAX], bootloader[PROPERTY_VALUE_MAX]; char network[PROPERTY_VALUE_MAX], date[80]; Loading @@ -352,6 +362,7 @@ static void print_header() { printf("Kernel: "); dump_file(NULL, "/proc/version"); printf("Command line: %s\n", strtok(cmdline_buf, "\n")); printf("Bugreport format version: %s\n", version.c_str()); printf("\n"); } Loading @@ -361,7 +372,8 @@ static bool add_zip_entry_from_fd(const std::string& entry_name, int fd) { int32_t err = zip_writer->StartEntryWithTime(entry_name.c_str(), ZipWriter::kCompress, get_mtime(fd, now)); if (err) { ALOGE("zip_writer->StartEntryWithTime(%s): %s\n", entry_name.c_str(), ZipWriter::ErrorCodeString(err)); ALOGE("zip_writer->StartEntryWithTime(%s): %s\n", entry_name.c_str(), ZipWriter::ErrorCodeString(err)); return false; } Loading Loading @@ -413,6 +425,32 @@ void add_dir(const char *dir, bool recursive) { dump_files(NULL, dir, recursive ? skip_none : is_dir, _add_file_from_fd); } /* adds a text entry entry to the existing zip file. */ static bool add_text_zip_entry(const std::string& entry_name, const std::string& content) { ALOGD("Adding zip text entry %s (%s)", entry_name.c_str(), content.c_str()); int32_t err = zip_writer->StartEntryWithTime(entry_name.c_str(), ZipWriter::kCompress, now); if (err) { ALOGE("zip_writer->StartEntryWithTime(%s): %s\n", entry_name.c_str(), ZipWriter::ErrorCodeString(err)); return false; } err = zip_writer->WriteBytes(content.c_str(), content.length()); if (err) { ALOGE("zip_writer->WriteBytes(%s): %s\n", entry_name.c_str(), ZipWriter::ErrorCodeString(err)); return false; } err = zip_writer->FinishEntry(); if (err) { ALOGE("zip_writer->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err)); return false; } return true; } static void dumpstate(const std::string& screenshot_path) { DurationReporter duration_reporter("DUMPSTATE"); unsigned long timeout; Loading Loading @@ -724,19 +762,20 @@ static void dumpstate(const std::string& screenshot_path) { } static void usage() { fprintf(stderr, "usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-z]] [-s] [-q]\n" fprintf(stderr, "usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-z]] [-s] [-q] [-B] [-P] [-R] [-V version]\n" " -b: play sound file instead of vibrate, at beginning of job\n" " -e: play sound file instead of vibrate, at end of job\n" " -o: write to file (instead of stdout)\n" " -d: append date to filename (requires -o)\n" " -z: generates zipped file (requires -o)\n" " -p: capture screenshot to filename.png (requires -o)\n" " -z: generates zipped file (requires -o)\n" " -s: write output to control socket (for init)\n" " -b: play sound file instead of vibrate, at beginning of job\n" " -e: play sound file instead of vibrate, at end of job\n" " -q: disable vibrate\n" " -B: send broadcast when finished (requires -o)\n" " -P: send broadacast when started and update system properties on progress (requires -o and -B)\n" " -R: take bugreport in remote mode (requires -o, -z, -d and -B, shouldn't be used with -P)\n" ); " -V: sets the bugreport format version (%s or %s)\n", VERSION_DEFAULT.c_str(), VERSION_DUMPSYS_SPLIT.c_str()); } static void sigpipe_handler(int n) { Loading @@ -753,6 +792,9 @@ static bool finish_zip_file(const std::string& bugreport_name, const std::string ALOGE("Failed to add text entry to .zip file\n"); return false; } if (!add_text_zip_entry("main_entry.txt", bugreport_name)) { ALOGE("Failed to add main_entry.txt to .zip file\n"); } int32_t err = zip_writer->Finish(); if (err) { Loading Loading @@ -855,6 +897,7 @@ int main(int argc, char *argv[]) { int do_broadcast = 0; int do_early_screenshot = 0; int is_remote_mode = 0; std::string version = VERSION_DEFAULT; now = time(NULL); Loading Loading @@ -884,7 +927,7 @@ int main(int argc, char *argv[]) { /* parse arguments */ int c; while ((c = getopt(argc, argv, "dho:svqzpPBR")) != -1) { while ((c = getopt(argc, argv, "dho:svqzpPBRV:")) != -1) { switch (c) { case 'd': do_add_date = 1; break; case 'z': do_zip_file = 1; break; Loading @@ -896,6 +939,7 @@ int main(int argc, char *argv[]) { case 'P': do_update_progress = 1; break; case 'R': is_remote_mode = 1; break; case 'B': do_broadcast = 1; break; case 'V': version = optarg; break; case '?': printf("\n"); case 'h': usage(); Loading @@ -918,6 +962,13 @@ int main(int argc, char *argv[]) { exit(1); } if (version != VERSION_DEFAULT && version != VERSION_DUMPSYS_SPLIT) { usage(); exit(1); } ALOGI("bugreport format version: %s\n", version.c_str()); do_early_screenshot = do_update_progress; // If we are going to use a socket, do it as early as possible Loading Loading @@ -983,6 +1034,7 @@ int main(int argc, char *argv[]) { } else { zip_writer.reset(new ZipWriter(zip_file.get())); } add_text_zip_entry("version.txt", version); } if (do_update_progress) { Loading Loading @@ -1054,7 +1106,7 @@ int main(int argc, char *argv[]) { // NOTE: there should be no stdout output until now, otherwise it would break the header. // In particular, DurationReport objects should be created passing 'title, NULL', so their // duration is logged into ALOG instead. print_header(); print_header(version); dumpstate(do_early_screenshot ? "": screenshot_path); Loading Loading
cmds/dumpstate/bugreport-format.md 0 → 100644 +87 −0 Original line number Diff line number Diff line # Bugreport file format This document specifies the format of the bugreport files generated by the bugreport services (like `bugreport` and `bugreportplus`) and delivered to the end user (i.e., it doesn’t include other tools like `adb bugreport`). A _bugreport_ is initially generated by dumpstate, then processed by **Shell**, which in turn delivers it to the end user through a `ACTION_SEND_MULTIPLE` intent; the end user then select which app (like an email client) handles such intent. ## Text file (Pre-M) Prior to _Android M (Marshmallow)_, `dumpstate` generates a flat .txt file named _bugreport-DATE.txt_ (where _DATE_ is date the bugreport was generated, in the format _YYYY-MM-DD-HH-MM-SS_), and Shell simply propagates it as an attachment in the `ACTION_SEND_MULTIPLE` intent. ## Version v0 (Android M) On _Android M (Marshmallow)_, dumpstate still generates a flat _bugreport-DATE.txt_ file, but then **Shell** creates a zip file called _bugreport-DATE.zip_ containing a _bugreport-DATE.txt_ entry and sends that file as the `ACTION_SEND_MULTIPLE` attachment. ## Version v1 (Android N) On _Android N (TBD)_, `dumpstate` generates a zip file directly (unless there is a failure, in which case it reverts to the flat file that is zipped by **Shell** and hence the end result is the _v0_ format). The zip file is by default called _bugreport-DATE.zip_ and it contains a _bugreport-DATE.txt_ entry, although the end user can change the name (through **Shell**), in which case they would be called _bugreport-NEW_NAME.zip_ and _bugreport-NEW_NAME.txt_ respectively. The zip file also contains 2 metadata entries generated by `dumpstate`: - `version.txt`: whose value is **v1**. - `main-entry.txt`: whose value is the name of the flat text entry (i.e., _bugreport-DATE.txt_ or _bugreport-NEW_NAME.txt_). `dumpstate` can also copy files from the device’s filesystem into the zip file under the `FS` folder. For example, a `/dirA/dirB/fileC` file in the device would generate a `FS/dirA/dirB/fileC` entry in the zip file. The flat file also has some minor changes: - Tombstone files were removed and added to the zip file. - The duration of each section is printed in the report. - Some dumpsys sections (memory and cpuinfo) are reported earlier in the file. Besides the files generated by `dumpstate`, **Shell** can also add 2 other files upon the end user’s request: - `title.txt`: whose value is a single-line summary of the problem. - `description.txt`: whose value is a multi-line, detailed description of the problem. ## Intermediate versions During development, the versions will be suffixed with _-devX_ or _-devX-EXPERIMENTAL_FEATURE_, where _X_ is a number that increases as the changes become stable. For example, the initial version during _Android N_ development was **v1-dev1**. When `dumpsys` was split in 2 sections but not all tools were ready to parse that format, the version was named **v1-dev1-dumpsys-split**, which had to be passed do `dumpsys` explicitly (i.e., trhough a `-V v1-dev1-dumpsys-split` argument). Once that format became stable and tools knew how to parse it, the default version became **v1-dev2**. Similarly, if changes in the file format are made after the initial release of Android defining that format, then a new _sub-version_ will be used. For example, if after _Android N_ launches changes are made for the next _N_ release, the version will be called **v1.1** or something like that. Determining version and main entry ----------------------------------------------- Tools parsing the zipped bugreport file can use the following algorithm to determine the bugreport format version and its main entry: ``` If [entries contain "version.txt"] version = read("version.txt") main_entry = read("main_entry.txt") else version = v0 main_entry = entries[0] fi ```
cmds/dumpstate/dumpstate.cpp +62 −10 Original line number Diff line number Diff line Loading @@ -83,6 +83,16 @@ static tombstone_data_t tombstone_data[NUM_TOMBSTONES]; // Root dir for all files copied as-is into the bugreport const std::string& ZIP_ROOT_DIR = "FS"; /* * List of supported zip format versions. * * See bugreport-format.txt for more info. */ // TODO: change to "v1" before final N build static std::string VERSION_DEFAULT = "v1-dev1"; // TODO: remove before final N build static std::string VERSION_DUMPSYS_SPLIT = "v1-dev1-dumpsys-split"; /* gets the tombstone data, according to the bugreport type: if zipped gets all tombstones, * otherwise gets just those modified in the last half an hour. */ static void get_tombstone_fds(tombstone_data_t data[NUM_TOMBSTONES]) { Loading Loading @@ -134,7 +144,7 @@ void add_mountinfo() { mount_points.clear(); DurationReporter duration_reporter(title, NULL); for_each_pid(do_mountinfo, NULL); ALOGD("%s: %d entries added to zip file\n", title, mount_points.size()); ALOGD("%s: %lu entries added to zip file\n", title, mount_points.size()); } static void dump_dev_files(const char *title, const char *driverpath, const char *filename) Loading Loading @@ -325,7 +335,7 @@ static unsigned long logcat_timeout(const char *name) { /* End copy from system/core/logd/LogBuffer.cpp */ /* dumps the current system state to stdout */ static void print_header() { static void print_header(std::string version) { char build[PROPERTY_VALUE_MAX], fingerprint[PROPERTY_VALUE_MAX]; char radio[PROPERTY_VALUE_MAX], bootloader[PROPERTY_VALUE_MAX]; char network[PROPERTY_VALUE_MAX], date[80]; Loading @@ -352,6 +362,7 @@ static void print_header() { printf("Kernel: "); dump_file(NULL, "/proc/version"); printf("Command line: %s\n", strtok(cmdline_buf, "\n")); printf("Bugreport format version: %s\n", version.c_str()); printf("\n"); } Loading @@ -361,7 +372,8 @@ static bool add_zip_entry_from_fd(const std::string& entry_name, int fd) { int32_t err = zip_writer->StartEntryWithTime(entry_name.c_str(), ZipWriter::kCompress, get_mtime(fd, now)); if (err) { ALOGE("zip_writer->StartEntryWithTime(%s): %s\n", entry_name.c_str(), ZipWriter::ErrorCodeString(err)); ALOGE("zip_writer->StartEntryWithTime(%s): %s\n", entry_name.c_str(), ZipWriter::ErrorCodeString(err)); return false; } Loading Loading @@ -413,6 +425,32 @@ void add_dir(const char *dir, bool recursive) { dump_files(NULL, dir, recursive ? skip_none : is_dir, _add_file_from_fd); } /* adds a text entry entry to the existing zip file. */ static bool add_text_zip_entry(const std::string& entry_name, const std::string& content) { ALOGD("Adding zip text entry %s (%s)", entry_name.c_str(), content.c_str()); int32_t err = zip_writer->StartEntryWithTime(entry_name.c_str(), ZipWriter::kCompress, now); if (err) { ALOGE("zip_writer->StartEntryWithTime(%s): %s\n", entry_name.c_str(), ZipWriter::ErrorCodeString(err)); return false; } err = zip_writer->WriteBytes(content.c_str(), content.length()); if (err) { ALOGE("zip_writer->WriteBytes(%s): %s\n", entry_name.c_str(), ZipWriter::ErrorCodeString(err)); return false; } err = zip_writer->FinishEntry(); if (err) { ALOGE("zip_writer->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err)); return false; } return true; } static void dumpstate(const std::string& screenshot_path) { DurationReporter duration_reporter("DUMPSTATE"); unsigned long timeout; Loading Loading @@ -724,19 +762,20 @@ static void dumpstate(const std::string& screenshot_path) { } static void usage() { fprintf(stderr, "usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-z]] [-s] [-q]\n" fprintf(stderr, "usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-z]] [-s] [-q] [-B] [-P] [-R] [-V version]\n" " -b: play sound file instead of vibrate, at beginning of job\n" " -e: play sound file instead of vibrate, at end of job\n" " -o: write to file (instead of stdout)\n" " -d: append date to filename (requires -o)\n" " -z: generates zipped file (requires -o)\n" " -p: capture screenshot to filename.png (requires -o)\n" " -z: generates zipped file (requires -o)\n" " -s: write output to control socket (for init)\n" " -b: play sound file instead of vibrate, at beginning of job\n" " -e: play sound file instead of vibrate, at end of job\n" " -q: disable vibrate\n" " -B: send broadcast when finished (requires -o)\n" " -P: send broadacast when started and update system properties on progress (requires -o and -B)\n" " -R: take bugreport in remote mode (requires -o, -z, -d and -B, shouldn't be used with -P)\n" ); " -V: sets the bugreport format version (%s or %s)\n", VERSION_DEFAULT.c_str(), VERSION_DUMPSYS_SPLIT.c_str()); } static void sigpipe_handler(int n) { Loading @@ -753,6 +792,9 @@ static bool finish_zip_file(const std::string& bugreport_name, const std::string ALOGE("Failed to add text entry to .zip file\n"); return false; } if (!add_text_zip_entry("main_entry.txt", bugreport_name)) { ALOGE("Failed to add main_entry.txt to .zip file\n"); } int32_t err = zip_writer->Finish(); if (err) { Loading Loading @@ -855,6 +897,7 @@ int main(int argc, char *argv[]) { int do_broadcast = 0; int do_early_screenshot = 0; int is_remote_mode = 0; std::string version = VERSION_DEFAULT; now = time(NULL); Loading Loading @@ -884,7 +927,7 @@ int main(int argc, char *argv[]) { /* parse arguments */ int c; while ((c = getopt(argc, argv, "dho:svqzpPBR")) != -1) { while ((c = getopt(argc, argv, "dho:svqzpPBRV:")) != -1) { switch (c) { case 'd': do_add_date = 1; break; case 'z': do_zip_file = 1; break; Loading @@ -896,6 +939,7 @@ int main(int argc, char *argv[]) { case 'P': do_update_progress = 1; break; case 'R': is_remote_mode = 1; break; case 'B': do_broadcast = 1; break; case 'V': version = optarg; break; case '?': printf("\n"); case 'h': usage(); Loading @@ -918,6 +962,13 @@ int main(int argc, char *argv[]) { exit(1); } if (version != VERSION_DEFAULT && version != VERSION_DUMPSYS_SPLIT) { usage(); exit(1); } ALOGI("bugreport format version: %s\n", version.c_str()); do_early_screenshot = do_update_progress; // If we are going to use a socket, do it as early as possible Loading Loading @@ -983,6 +1034,7 @@ int main(int argc, char *argv[]) { } else { zip_writer.reset(new ZipWriter(zip_file.get())); } add_text_zip_entry("version.txt", version); } if (do_update_progress) { Loading Loading @@ -1054,7 +1106,7 @@ int main(int argc, char *argv[]) { // NOTE: there should be no stdout output until now, otherwise it would break the header. // In particular, DurationReport objects should be created passing 'title, NULL', so their // duration is logged into ALOG instead. print_header(); print_header(version); dumpstate(do_early_screenshot ? "": screenshot_path); Loading