Open Source / ALEAPP #1795

Reading Android bug reports in a single pass

ALEAPP staged the files of an Android bug report zip but did not read its main text, so a report with a package list, build properties and hundreds of service sections produced no rows. This contribution adds a one-pass reader for that text and four tables built on it.

  • Daniel Addai, SectraTEK Lab
  • Merged
  • CC BY 4.0

Summary

  • Before this change, a bug report zip given to ALEAPP was staged, but nothing read the main text inside it. Only the existing tombstone and ANR artifacts matched anything in the zip.
  • The change adds a reader for that text, scripts/dumpstate_reader.py, and four artifacts in the category Android Bug Report: Device Facts, Sections, Installed Packages and Package Times.
  • The reader reads the text once, line by line, and never changes the staged file. A zip member is streamed, not extracted.
  • The Sections table lists every section the text opens, with its line range, the duration the platform printed, whether the platform cut the dump at its time limit, and which artifact presents it. A section no artifact reads says "not read".
  • Package Times keeps every time as printed and adds a Time Note column that marks unset times and values that the phone image shares across many packages.
  • The tables present data only: no severity, no finding, no interpretation. The pull request was merged on 26 October 2026.
  • 4artifacts added
  • 946lines in the new reader module
  • 70files in the pull request, mostly synthetic fixtures and baselines
  • 35properties read from getprop

Methodology note

Drafted with AI assistance and reviewed by the author before publication.

The issue

An examiner receives an Android bug report: the zip a phone writes when it is asked for a diagnostic snapshot. Its main member is one long text, the dumpstate file, with the build properties, the package manager's list of installed apps and a section for every system service. In the five research reports behind this work the text had between 343 and 656 sections, and in the pull request's performance check one text was 214 MB.

Before this change an examiner who ran ALEAPP on such a zip got no rows about any of it. The zip was staged member by member, but only the tombstone and ANR artifacts matched anything in it. An empty result looked like a report with nothing in it (Figure 1).

Two further problems appear once the text is read. The first is time. The package manager prints install and update times as wall-clock text, and for an app that sits on the read-only system partitions the value is read from the phone image, not from anybody installing anything. One value can therefore be printed for hundreds of unrelated packages. In the five research reports, 62 to 91 percent of the package time rows carried such a placeholder or image-wide value (Figure 3). A timeline built from them would put hundreds of events at one moment that belongs to the firmware.

The second is silent partial reading. The platform stops a slow service dump at a time limit, so a list can be short because the dump was cut, not because the phone held few apps. In one of the five reports, the Samsung A-series phone, the package dump ends with the platform's 10 second timeout marker, and 572 packages were read from it. A table that does not say so would read as complete.

Before and after: one bug report zip, with and without a reader for the main textTwo columns. Before: the zip is staged, the tombstone and ANR artifacts match only their own members, and the main text is read by nothing, so no rows about build, packages or times appear. After: dumpstate_reader.py finds the text, confirms the banner, reads it once and feeds four tables: Device Facts, Sections, Installed Packages and Package Times.BeforeAfterBug report zip, staged with -t zipsynthetic example: main text and other membersBug report zip, staged with -t zipsynthetic example: main text and other membersExisting artifactstombstone and ANR artifacts matchonly their own members in the zipMain text (dumpstate)No artifact reads itNo rows about build, packages or timesLooks like a report with nothing in itdumpstate_reader.pyfinds the text, confirms the banner '== dumpstate:',reads it once, line by lineDevice Factsbuild, security, time zoneSectionsevery section, read or notInstalled Packagesone row per package blockPackage Timesas printed, with Time Note
Figure 1The same synthetic bug report zip before and after the change. Before, the main text is staged and read by nothing; after, one reader feeds four tables. Source: ALEAPP #1795 pull request text and scripts/dumpstate_reader.py; diagram of the flow (synthetic example).

The essence

The change adds one reader and four tables. The reader finds the bug report text, confirms it by its == dumpstate: banner, passes over it once and hands each section's lines to the parser registered for that section. The tables present the result as the platform printed it, with the line number of every row, and the Sections table says which parts of the text no table reads.

The four artifacts added by ALEAPP #1795
ArtifactWhat a row isKey columnsWhat it leaves out
Bug Report - Device FactsA header line, one of 35 listed getprop properties, or a derived row (report time in UTC, time zone used)Fact, Value, Origin, Source Line, Source FileSerial numbers, IMEIs and the kernel command line (added later, in their own artifact; see the post on ALEAPP #1809)
Bug Report - SectionsA section the text opens: header, command, dumpsys service or dumpsys groupSection, Kind, Start Line, Last Line, Lines, Printed Duration (s), Timeout Marker (ms), Read ByThe content of the sections; "not read" marks sections no artifact presents
Bug Report - Installed PackagesOne Package block, from the Packages part or the Hidden system packages partPackage, Block, Version Code, Version Name, Code Path, Flags, Installer, Users Installed, User StatePermissions, signatures, components and dexopt state
Bug Report - Package TimesOne firstInstallTime, lastUpdateTime or timeStamp valueTime (UTC), Time As Printed, Package, Event, User, Block, Time NoteA UTC value for the platform's "no time" values

Two rules keep the tables honest. A time is converted to UTC only with the zone that the report itself records in persist.sys.timezone; where that property is missing, the UTC column is empty and the printed text is kept. And 1970-01-01 00:00:00 and 1969-12-31 23:59:59, the platform's "no time" values, get no UTC value at all.

Our methodology

The format was learned from the structure of real bug reports and checked against the platform sources cited in LAB-TR-0001. The text opens with a banner and header lines, then repeats two kinds of block: a command section that starts with a line like ------ TITLE (command) ------ and a service section that starts with DUMP OF SERVICE name:. A section ends at its duration line when the platform prints one, and otherwise at the next header. Inside a service, the platform can print a DUMP TIMEOUT marker when it stopped the dump at its limit.

The reader therefore does not trust a file name. It confirms the banner in the first 8,192 bytes before it reads anything, and a file that matches the name patterns but not the banner is left out and logged. It counts lines on the line-feed byte only, so a lone carriage return or form feed inside a line cannot move the numbers away from what a text editor shows. Figure 2 follows the path from file to report columns.

Pipeline: from bug report file to the report columns of four artifactsFive stages in a row: find the text, stream it once, build the section index, run the readers, write four tables. Under the last stage, the four tables with their columns: Device Facts, Sections, Installed Packages and Package Times.1 Find the textdumpstate.txtbugreport-*.txtmain text of a zipbanner in the first8,192 bytes2 Stream onceline by line, spliton LF onlyline cut at 1 MiBfirst byte - D = *tested as a boundary3 Section indexcommand and servicesections: name, linerange, duration,DUMP TIMEOUT ms4 Readersheader factsgetprop: 35 listedpropertiespackage blocks5 Four tablesDevice FactsSectionsInstalled PackagesPackage TimesDevice FactsFact, Value, Origin,Source Line, Source FileSectionsSection, Kind, Start Line,Last Line, Lines, Read By,Printed Duration (s),Timeout Marker (ms)Installed PackagesPackage, Block, App ID,Code Path, Flags,Installer, User State,(18 columns in all)Package TimesTime (UTC),Time As Printed, Package,Event, User, Installer,Block, Time Note
Figure 2From file to columns. The section index is built in the same pass that feeds the readers, so no second reading of the text is needed. Source: scripts/dumpstate_reader.py (constants, iter_lines, scan_lines) and scripts/artifacts/bugreportDevice.py (headers) in ALEAPP #1795; diagram.

The fixtures are synthetic. bugreport_fixture.py builds them from the structure of real reports with invented values, with package names under com.example.*. Eight constructed layouts run through each of the four artifacts give 32 new test cases, and 47 new unit tests were added. With them the repository's case run went from 171 to 203 passes.

The real reports were used to check the reader, never as fixtures. Each row count was compared with a count made from the raw text with plain regular expressions, and with the rows of an independent parser written for another tool, field by field. For Package Times the comparison included the factory-stamp marks, and the same rows were marked in both implementations (Figure 3). The repository's own output checker found only uniform or empty columns, such as Package Source on some builds, and the artifact notes now name each of them.

Damaged and unusual input was tried on variants of one real text: empty, banner only, cut at half and inside the package list (105 complete packages kept), CRLF line endings, stray carriage returns, form feeds and NUL bytes, Latin-1, a byte order mark, UTF-16 text (not read, by design), a 150 MB single line, and the text repeated twelve times (twelve times the package rows, 241 MB peak). None raised an error. The Lab's validation of the same reader (LAB-TR-0001) found, with a test of this kind, that one damaged line inside the package dump had cut 284 packages to 15 without any error; a section heading must now end with a colon.

Package Times rows per research report, split into marked and not markedHorizontal stacked bars on one scale from 0 to 2,500 rows. For each of five research bug reports the bar shows how many Package Times rows carry a Time Note for a placeholder or image-wide value. Marked rows: 1,199 of 1,716; 1,331 of 2,160; 876 of 966; 1,692 of 2,668; 734 of 852.Marked: placeholder or image-wide valueNot marked05001,0001,5002,0002,500Package Times rows in one reportSamsung A-seriesAndroid 161,199 of 1,716 (70%)Samsung S-seriesAndroid 161,331 of 2,160 (62%)HuaweiAndroid 10876 of 966 (91%)MotorolaAndroid 151,692 of 2,668 (63%)Transsion-brandAndroid 10734 of 852 (86%)
Show this figure as a table
Package Times rows per research report and how many carry a factory-stamp Time Note
ReportAndroidRowsMarkedNot markedShare marked
Samsung A-seriesAndroid 161,7161,19951769.9%
Samsung S-seriesAndroid 162,1601,33182961.6%
HuaweiAndroid 109668769090.7%
MotorolaAndroid 152,6681,69297663.4%
Transsion-brandAndroid 1085273411886.2%
Figure 3Most package time rows in these five reports carry a value that the phone image shares or a fixed placeholder. A reader that presents them as install times would mislead. Source: ALEAPP #1795 pull request text (rows and marked rows per report); not-marked counts are total minus marked. Real aggregate counts from five research bug reports, not synthetic.

Speed and memory were measured on the whole run. The four artifacts took 12 seconds on a text of 163 MB and 214 MB, including the load of the tool's 1,360 modules, with a 230 to 241 MB peak working set; the text itself is read in 1 to 3 seconds. A privacy scan of every added file (zips and nested zips opened), every commit message and the branch name against the identifiers of the five reports found no hit. That scan was later shown to be incomplete: three numbers copied into fixtures were found afterwards and replaced (see the post on ALEAPP #1809).

The value

For an examiner who holds a bug report and little else, this turns a text of hundreds of megabytes into tables that can be sorted, filtered and exported, with each row tied to a line number and a file. Device Facts answers which build, patch level and time zone the report records. Installed Packages answers which packages the package manager listed, with version, installer fields and per-user state as printed. Sections answers a question that is easy to forget to ask: what did the tool not look at?

Package Times is where the most care went. A row with no note shows the value the dump prints for that package. A row with a note keeps its time, so a timeline can still be filtered on the column, and the note says why the time should not be read as an install time (Figure 4).

What Package Times rows look like, with the Time Note columnA table with columns Time (UTC), Time As Printed, Package, Event and Time Note. Two rows without a note, two rows whose time is shared across system packages and carries the placeholder note, and one row whose time is unset and has no UTC value. All values are invented.Time (UTC)Time As PrintedPackageEventTime Note2026-09-14 08:21:072026-09-14 08:21:07com.example.notesfirstInstallTimeno note2026-09-30 17:02:442026-09-30 17:02:44com.example.noteslastUpdateTimeno note2008-12-31 15:00:002008-12-31 15:00:00com.example.system.afirstInstallTimeplaceholder or image-wide value,not read as an install time2008-12-31 15:00:002008-12-31 15:00:00com.example.system.blastUpdateTimeplaceholder or image-wide value,not read as an install time(no UTC)1970-01-01 00:00:00com.example.system.clastUpdateTimetime not set on the device(shown as printed)A marked row keeps its time and UTC value, so a timeline can be filtered on the Time Note column.
Show this figure as a table
The five synthetic Package Times rows of Figure 4
Time (UTC)Time As PrintedPackageEventTime Note
2026-09-14 08:21:072026-09-14 08:21:07com.example.notesfirstInstallTimenone
2026-09-30 17:02:442026-09-30 17:02:44com.example.noteslastUpdateTimenone
2008-12-31 15:00:002008-12-31 15:00:00com.example.system.afirstInstallTimeplaceholder or image-wide value, not read as an install time
2008-12-31 15:00:002008-12-31 15:00:00com.example.system.blastUpdateTimeplaceholder or image-wide value, not read as an install time
(no UTC)1970-01-01 00:00:00com.example.system.clastUpdateTimetime not set on the device (shown as printed)
Figure 4A Package Times view with invented values. The first two rows are plain printed times. The next two share one time across system packages. The last has a time the device never set. Source: column names from scripts/artifacts/bugreportDevice.py; note texts from scripts/dumpstate_reader.py; values are a synthetic example, invented values.

The tables are the package manager's state at the moment of capture. An app removed before then has no row, and an app removed for one user can still show a row with installed=false for that user. A time or an installer field does not show how an app was installed, or that it was ever used.

The factory-stamp thresholds (20 packages, 2 seconds and 30 seconds) were chosen on these five reports. They are a rule of this reader, not a statement of the platform, and a factory value shared by fewer than 20 packages is not marked. Android 11 to 14, other vendors and a bug report zip found inside a full file system extraction were not examined on real data. Every real report was in a UTC+0 zone, so offsets and daylight-saving edges are tested on constructed text only.

Where this sits in the Lab's research

This change is the foundation of Lab Technical Report LAB-TR-0001, "Making Silent Failures Visible: A Single-Pass Section Reader for Android Bug Reports". The report describes the reader's design (the section index, timeout detection, bounded memory), its validation on seven research bug reports, and the seven reader defects that validation found. It counts 158 artifacts in 22 groups on the same reader; this pull request is the first of the seven that released them. LAB-TR-0001 also reports that, on the five reports compared, the stamp sets of the reader and of a second parser were equal.

Cite: Addai, D. (2026). Making Silent Failures Visible: A Single-Pass Section Reader for Android Bug Reports. Lab Technical Report LAB-TR-0001, version 1.1. Zenodo. doi:10.5281/zenodo.23719508. Read it on this site at LAB-TR-0001.

The public article Android bug reports, category by category (LAB-0002, early research) describes what the sections of a bug report hold and which questions they can answer.

Sources

Every number and claim in this post, and where it comes from. Numbers that could not be sourced were left out.

Sources for the numbers and claims in this post
Claim or numberSource
Nothing in ALEAPP read the bug report text; only the tombstone and ANR artifacts matched anything in a zipPull request text, "What this changes"
Reader finds dumpstate.txt, bugreport-*.txt and the main text in bugreport-*.zip; banner check; reads once, line by line; a 214 MB text is not held in memory; lines cut at 1 MiB; zip member streamed; staged file not modifiedPull request text; scripts/dumpstate_reader.py (module docstring and constants)
Banner checked in the first 8,192 bytes; first byte - D = * tested as a section boundary; lines counted on LF onlyscripts/dumpstate_reader.py (HEAD_BYTES, scan_lines, module docstring)
Four artifacts, category Android Bug Report; 35 listed getprop properties; serial numbers, IMEIs and kernel command line not read in this changePull request text; scripts/artifacts/bugreportDevice.py
946 lines in the new reader module; 70 files in the pull requestGitHub file list of the pull request (scripts/dumpstate_reader.py, 946 added lines)
Sections per report: 609, 656, 410, 539, 343 (range 343 to 656)Pull request text, rows per report (Sections)
Installed Packages rows: 572, 720, 322, 667, 284; Package Times rows: 1,716, 2,160, 966, 2,668, 852Pull request text, rows per report
Marked Package Times rows: 1,199, 1,331, 876, 1,692, 734, the same rows in both implementations; 62 to 91 percent markedPull request text; Package Times notes in bugreportDevice.py
Not-marked rows (517, 829, 90, 976, 118) and percentages in Figure 3Derived: total rows minus marked rows
Marking tests: fixed placeholder time; 20 or more packages on the image; runs within 2 s and 30 sscripts/dumpstate_reader.py (comments and constants above find_shared_stamps)
Time Note texts and the unset-time rule (1970-01-01 00:00:00, 1969-12-31 23:59:59)scripts/dumpstate_reader.py (NOTE_FACTORY_STAMP, NOTE_UNSET, UNSET_WALL_TIMES)
Samsung A-series package dump ends with a 10 s timeout marker; 572 packages readPull request text, "Anything reviewers should know"
203 passing cases on the branch against 171 on the base; 32 new cases (8 layouts x 4 artifacts); 47 new unit testsPull request text, test record
Adversarial variants, including 105 complete packages kept when cut inside the package list, and twelve repeats giving 480 MB, 15 s, 241 MBPull request text, test record
12 s for the four artifacts on a 163 MB / 214 MB text; 1,360 modules; 230 to 241 MB peak; text read in 1 to 3 sPull request text, performance row
Privacy scan: no hit; three fixture numbers found laterPull request text, privacy scan; ALEAPP #1809 pull request text
Damaged line at column 0 cut 284 packages to 15; heading must end with a colonLAB-TR-0001, introduction and Table 7 (defect 3)
All five real reports are UTC+0; other zones tested on constructed textPull request text, "Anything reviewers should know"
Merged 26 October 2026GitHub merge record of the pull request (mergedAt)
LAB-TR-0001 version 1.1 DOI 10.5281/zenodo.23719508; seven pull requests; 158 artifacts in 22 groups; stamp sets equal (Table 5)Lab DOI list (DOIS.md); LAB-TR-0001, Artifact Availability and Table 5
Figure 4 valuesInvented by the author for illustration (synthetic example)