Load partial-memory minidumps - #715
Conversation
A minidump only has to capture the memory ranges its writer selected, and cle assumed the opposite in three places, so a dump smaller than a full-memory one either failed to load or broke a consumer. Modules had to have a captured range starting exactly at their base address, or loading raised CLEInvalidBinaryError. A module now maps whatever part of its image the dump holds: a writer emits one range per run of pages sharing a protection and stores adjacent ranges adjacently, so a fully captured module is still a single file range, while a module with nothing captured gets a section with no file bytes so an analysis can still attribute an address to it. Reading a thread's fs or gs segment base means reading its TEB, which a small dump usually leaves out. thread_registers() now omits that register instead of raising KeyError, and the TLS thread manager skips a thread whose TEB is missing with a warning instead of propagating the KeyError out of cle.Loader(). Module sections were also plain Section instances, whose permission properties raise NotImplementedError, so anything that inspected them crashed on every minidump; angr's CFGFast does. They are now MinidumpSection instances that report read, write and execute, matching what the backend reports for its memory segments, and that carry the captured size separately from the image size.
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Validation record for head
The measurements below are from
End to end with angr 9.3.3.dev0,
Section file ranges, checked against the raw dump bytes:
Caveats:
Corpus measurement of the open queue, 2026-08-15 — this record understates the change by about twenty-foldCorrecting the record upward, which is as much of a correction as the other direction. The open pull-request queue was scored against 733 objects drawn from a sweep's own failing units (35 error classes, 49 architectures, 16 containers), with each repository's current master as the baseline rather than the revisions the sweep pinned. Each object is loaded with This PR was filed against the failure it is named for — The larger effect is in a class this PR was never associated with. The Weighting each (class, architecture, container) cell by the corpus units it holds:
About 2,200 corpus units, from this PR alone — roughly twenty times what the class in its title accounts for, and the largest upward correction in the queue measurement. It is not the largest absolute figure in the queue: angr#6794 clears about 14,946 units on its own, cle#728 about 3,441 and angr#6805 about 3,804. The point is only that this record, as written, described about a twentieth of what the change does. One detail worth recording for anyone re-running this: the permission class was drawn on The corpus is private, so its objects are named by architecture, container and digest rather than by path. |
|
Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_715 |
The assertion that a fully captured module maps its image base compared the loaded bytes against a b"MZ" literal. Use pefile's own IMAGE_DOS_SIGNATURE, which says what the check means and keeps a header magic literal out of a test file, where it otherwise reads as a hand-assembled container. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A minidump's stream directory lists only the streams its writer chose to emit; the header and the directory are the only mandatory parts of the file. The backend read four of them and handled a missing one three different ways: the memory list was required, the module and thread lists were dereferenced unguarded and produced an AttributeError from the middle of a loop, and only the system information stream raised a considered error. A dump with no memory list now loads with no memory, and one with no module or thread list loads with no sections or with no threads. The module list is read on the architecture path as well, where a 64-bit dump without one failed before any of that. MinidumpMissingStreamError, which the missing system information stream still raises when no architecture was passed either, now derives from CLEError and hands its explanation to the base class. It was invisible to except CLEError, and str() on it was empty because the explanation only ever reached an attribute. A module whose image the dump did not capture is still rejected twenty lines further down; #715 covers that half.
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS
A minidump only has to capture the memory ranges its writer chose, and cle
assumed otherwise. A module with no captured range at its base address raised
CLEInvalidBinaryError, reading a thread's segment base assumed its TEB was in
the file, and module sections were plain Section objects, whose permission
properties raise NotImplementedError, so CFGFast crashed on every minidump.
Modules now map whatever part of their image the dump holds, thread registers
survive a missing TEB, and a section carries its captured size separately from
the image size.
The regression test loads a Breakpad crash dump that captures only the faulting
instruction and the two thread stacks, a fixture added by angr/binaries#175, and
the Windows and macOS jobs check out binaries master, so the checks here stay
red until that merges.
Validation: #715 (comment)
sync: angr/binaries#175