[GH-ISSUE #4445] [Bug] NativeAOT metadata recovery finds no MethodTables in a real 26 MB .NET 8 app: the fixed 64M work-unit budget runs out mid-discovery #1764

Open
opened 2026-10-16 21:09:59 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @Rinkia on GitHub (Oct 16, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4445

Before submitting

  • I searched the open and closed issues for a related report (nativeaot budget, work-unit, method-table discovery, NativeAOT large). Related but different: #1679 (PE reads bounded by section size), #4112 (resolved type addresses).
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

inspect-native-load-image --provider ghidra on bflat.exe from bflat 8.0.2 (a .NET 8 NativeAOT win-x64 build of the bflat compiler) returns metadata_recovery with status partial:

{"status":"partial",
 "reason":"RTR 9.1 was found, but method-table discovery reached its resource budget before determining whether a unique System.Object root exists.",
 "header_address":"0x14098cac8","format_major":9,"format_minor":1,
 "method_tables":0,"types":[],"frozen_strings":[],
 "derived_overlay":{"address":"0x14197b000","size_bytes":8920984,"sha256":"2e48b844…"},
 "coverage":{"pointer_candidates_examined":2193664,"pointer_slots_examined":2275261,
   "scan_bytes_examined":18202088,"scan_bytes_omitted":3396389,
   "method_table_validation_items_examined":1990894,
   "work_units_examined":67108860,"work_budget_units":67108864,
   "method_tables_recovered":0,"frozen_string_candidates":0,
   "frozen_scan_bytes_examined":0,"frozen_scan_bytes_omitted":608840,
   "truncation_reason":"work-unit-budget"}}

Everything the parser does report is correct:

  • The RTR header is at RVA 0x98cac8 in .rdata. Its bytes 52545200 0900 0100 are RTR\0, major 9, minor 1. The file contains two other RTR\0 byte matches, and the parser selected the real header.
  • The derived overlay matches the PE hydrated section: VA 0x197b000, virtual size 0x881f98 = 8,920,984.

But the recovery itself produces nothing. The application's own PDB (bflat.pdb from the release's debug-symbols zip) lists 61,954 MethodTable symbols (??_7…@@6B@; 61,948 of them in hydrated) and 6,685 frozen strings (__Str_*).

Cause (source-derived on main @ d72d856f5): src/domain/native/nativeAotPe.ts has one NativeAotParserBudget, with NATIVE_AOT_MAX_WORK_UNITS = 64 * 1024 * 1024. The pointer scan, MethodTable validation and frozen-region scan all charge it through consumeWork. On this image the pointer scan and validation use it up before discovery finishes: 3.4 MB of the scan is unexamined. The frozen-string loop then gets no budget, and it also needs a recovered System.String table. The limit comes only from DEFAULT_NATIVE_AOT_PARSER_LIMITS; I found no environment variable or tool option that raises it.

The partial status and coverage counts are honest, so this isn't silent. In practice, though, the built-in parser returns no MethodTables, types or strings for an ordinary real-world NativeAOT app, and the caller can't do anything about it.

Reproduction

  • bflat-8.0.2-windows-x64.zip from https://github.com/bflattened/bflat/releases/tag/v8.0.2. The release predates GitHub asset digests, so I recorded my own: zip sha256 25b03214c6085607ec2ec5fc86139c93e054edd60266296e708f763455961e7e.
  • bflat.exe from that zip: 26,180,096 bytes, sha256 09c8a41edd6cf22dcffe05ed72fede121632f686843cbbccc77f14e5d93bd3a2.
  • Reference symbols: bflat-8.0.2-windows-x64-debugsymbols.zip → bflat.pdb, read with llvm-pdbutil 18 dump --publics --section-headers.
docker run --rm --memory 5g -e GHIDRA_HEADLESS_MAXMEM=3G -e REA_GHIDRA_STARTUP_TIMEOUT_MS=1800000 \
  -e REA_GHIDRA_DISABLED_ANALYZERS="Decompiler Parameter ID,Decompiler Switch Analysis,Windows x86 PE RTTI Analyzer,Aggressive Instruction Finder,Stack,Non-Returning Functions - Discovered" \
  -v …:/targets:ro rea inspect-native-load-image /targets/bflat.exe --provider ghidra --json

rea-agents@6.5.0, Linux x64 container, Ghidra 12.1.4, no NativeAOT adapter JAR. 589 s in total, almost all of it Ghidra import and analysis. The parser limits are the same on main.

Expected behavior

One or more of:

  • A work budget that scales with the admitted image. The source byte budget already allows 128 MiB, but the work budget runs out on an 18 MB scan.
  • Or a documented setting to raise it, like the other provider limits.
  • Or a separate budget for the frozen-region scan, so string recovery isn't starved by MethodTable discovery.
  • At minimum, the remediation or limitation could say that this image exceeds the parser's fixed budget and that the optional adapter is the route for large apps, if it is.

Impact and scope

  • NativeAOT apps of a realistic size, such as compilers, CLIs and desktop tools. The documented fixture is small, so it doesn't reach the limit.
  • Not tested: the optional headless adapter JAR, a smaller real-world NativeAOT app, and main at run time (source-checked only).
Originally created by @Rinkia on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4445 ### Before submitting - [x] I searched the open and closed issues for a related report (nativeaot budget, work-unit, method-table discovery, NativeAOT large). Related but different: #1679 (PE reads bounded by section size), #4112 (resolved type addresses). - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior `inspect-native-load-image --provider ghidra` on `bflat.exe` from bflat 8.0.2 (a .NET 8 NativeAOT win-x64 build of the bflat compiler) returns `metadata_recovery` with status `partial`: ```json {"status":"partial", "reason":"RTR 9.1 was found, but method-table discovery reached its resource budget before determining whether a unique System.Object root exists.", "header_address":"0x14098cac8","format_major":9,"format_minor":1, "method_tables":0,"types":[],"frozen_strings":[], "derived_overlay":{"address":"0x14197b000","size_bytes":8920984,"sha256":"2e48b844…"}, "coverage":{"pointer_candidates_examined":2193664,"pointer_slots_examined":2275261, "scan_bytes_examined":18202088,"scan_bytes_omitted":3396389, "method_table_validation_items_examined":1990894, "work_units_examined":67108860,"work_budget_units":67108864, "method_tables_recovered":0,"frozen_string_candidates":0, "frozen_scan_bytes_examined":0,"frozen_scan_bytes_omitted":608840, "truncation_reason":"work-unit-budget"}} ``` Everything the parser does report is correct: - The RTR header is at RVA `0x98cac8` in `.rdata`. Its bytes `52545200 0900 0100` are `RTR\0`, major 9, minor 1. The file contains two other `RTR\0` byte matches, and the parser selected the real header. - The derived overlay matches the PE `hydrated` section: VA `0x197b000`, virtual size `0x881f98` = 8,920,984. But the recovery itself produces nothing. The application's own PDB (`bflat.pdb` from the release's debug-symbols zip) lists **61,954** MethodTable symbols (`??_7…@@6B@`; 61,948 of them in `hydrated`) and **6,685** frozen strings (`__Str_*`). **Cause** (source-derived on `main` @ `d72d856f5`): `src/domain/native/nativeAotPe.ts` has one `NativeAotParserBudget`, with `NATIVE_AOT_MAX_WORK_UNITS = 64 * 1024 * 1024`. The pointer scan, MethodTable validation and frozen-region scan all charge it through `consumeWork`. On this image the pointer scan and validation use it up before discovery finishes: 3.4 MB of the scan is unexamined. The frozen-string loop then gets no budget, and it also needs a recovered `System.String` table. The limit comes only from `DEFAULT_NATIVE_AOT_PARSER_LIMITS`; I found no environment variable or tool option that raises it. The `partial` status and coverage counts are honest, so this isn't silent. In practice, though, the built-in parser returns no MethodTables, types or strings for an ordinary real-world NativeAOT app, and the caller can't do anything about it. ### Reproduction - `bflat-8.0.2-windows-x64.zip` from https://github.com/bflattened/bflat/releases/tag/v8.0.2. The release predates GitHub asset digests, so I recorded my own: zip sha256 `25b03214c6085607ec2ec5fc86139c93e054edd60266296e708f763455961e7e`. - `bflat.exe` from that zip: 26,180,096 bytes, sha256 `09c8a41edd6cf22dcffe05ed72fede121632f686843cbbccc77f14e5d93bd3a2`. - Reference symbols: `bflat-8.0.2-windows-x64-debugsymbols.zip` → `bflat.pdb`, read with `llvm-pdbutil 18 dump --publics --section-headers`. ```sh docker run --rm --memory 5g -e GHIDRA_HEADLESS_MAXMEM=3G -e REA_GHIDRA_STARTUP_TIMEOUT_MS=1800000 \ -e REA_GHIDRA_DISABLED_ANALYZERS="Decompiler Parameter ID,Decompiler Switch Analysis,Windows x86 PE RTTI Analyzer,Aggressive Instruction Finder,Stack,Non-Returning Functions - Discovered" \ -v …:/targets:ro rea inspect-native-load-image /targets/bflat.exe --provider ghidra --json ``` `rea-agents@6.5.0`, Linux x64 container, Ghidra 12.1.4, no NativeAOT adapter JAR. 589 s in total, almost all of it Ghidra import and analysis. The parser limits are the same on `main`. ### Expected behavior One or more of: - A work budget that scales with the admitted image. The source byte budget already allows 128 MiB, but the work budget runs out on an 18 MB scan. - Or a documented setting to raise it, like the other provider limits. - Or a separate budget for the frozen-region scan, so string recovery isn't starved by MethodTable discovery. - At minimum, the remediation or limitation could say that this image exceeds the parser's fixed budget and that the optional adapter is the route for large apps, if it is. ### Impact and scope - NativeAOT apps of a realistic size, such as compilers, CLIs and desktop tools. The documented fixture is small, so it doesn't reach the limit. - Not tested: the optional headless adapter JAR, a smaller real-world NativeAOT app, and `main` at run time (source-checked only).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1764
No description provided.