mirror of
https://github.com/morluto/rea.git
synced 2026-11-04 22:06:10 +00:00
fix(ghidra): report unbacked file-offset addresses without schema repair (#8308)
* fix(ghidra): report unbacked file-offset addresses without schema repair address_to_file_offset projected a well-formed address in uninitialized or unmapped Ghidra memory as generic invalid input: the headline said the arguments were invalid and recovery pointed at the input schema, while the real reason survived only in a nested issue. The bridge now gives each outcome its own code (address_unmapped, address_uninitialized, file_mapping_absent, file_mapping_ambiguous, file_offset_unrepresentable) instead of folding them into invalid_request. The Ghidra projection keeps the bridge reason as the headline, points to inspect_native_load_image, records the caller's exact address and the file_mapping outcome, and scopes diagnostics to the request. AnalysisInputError gains conformsToInputContract so a conforming request omits recovery.kind=inspect_input_contract. Malformed addresses and other bridge refusals keep their existing projection. Fixes #8244 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(ghidra): match fixture block ends to bridge conventions The fake bridge treated .text's exclusive list_segments end (0x1100) as an inclusive bound, so address_to_file_offset mapped an address outside the block. Give the fixture's blocks one owner (start and size) and derive each reply from the production bridge convention: exclusive list_segments ends, inclusive byte-search coverage, half-open containment for file offsets. Add an MCP boundary test that reads the block edges from list_segments and search_bytes, maps the last file-backed byte and refuses the first address past each block with its outcome code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7c9ed94259
commit
c287a73276
15 files changed
+688
-22
No files matched your search
@@ -50,14 +50,23 @@ public final class ReaFileOffsetSourceProbe extends HeadlessScript {
|
||||
currentProgram.getFunctionManager().createFunction("foreign_only", entry,
|
||||
new AddressSet(entry), SourceType.USER_DEFINED);
|
||||
rejected(resolve, bridge, "no loaded source mapping", "file-offset:0x910", new JsonObject());
|
||||
rejected(reverse, bridge, "0 authoritative target-file mappings", reverseInput("0x140100910"));
|
||||
rejectedCode(reverse, bridge, "file_mapping_absent",
|
||||
"has no authoritative mapping to the admitted target file", reverseInput("0x140100910"));
|
||||
|
||||
// Well-formed addresses without source bytes keep distinct outcomes.
|
||||
memory.createUninitializedBlock("rea_uninitialized", toAddr(0x140400000L), 0x100, false);
|
||||
rejectedCode(reverse, bridge, "address_uninitialized",
|
||||
"is in uninitialized Ghidra memory block rea_uninitialized", reverseInput("0x140400010"));
|
||||
rejectedCode(reverse, bridge, "address_unmapped",
|
||||
"is outside every Ghidra memory block", reverseInput("0x140500000"));
|
||||
|
||||
// A source object's declared original-file start does not make its local
|
||||
// offsets a complete, digest-matched target representation.
|
||||
var partial = memory.createFileBytes("partial.bin", 0x200, 0x200,
|
||||
new ByteArrayInputStream(bytes, 0x200, 0x200), monitor);
|
||||
memory.createInitializedBlock("partial", toAddr(0x140300000L), partial, 0, 0x200, false);
|
||||
rejected(reverse, bridge, "0 authoritative target-file mappings", reverseInput("0x140300010"));
|
||||
rejectedCode(reverse, bridge, "file_mapping_absent",
|
||||
"has no authoritative mapping to the admitted target file", reverseInput("0x140300010"));
|
||||
var selected = (Function)resolve.invoke(bridge, "file-offset:0x210", new JsonObject());
|
||||
require(selected.getEntryPoint().equals(toAddr(0x140001010L)), "original target selected despite other sources");
|
||||
var mapped = (JsonObject)reverse.invoke(bridge, reverseInput("0x140001010"));
|
||||
@@ -92,7 +101,7 @@ public final class ReaFileOffsetSourceProbe extends HeadlessScript {
|
||||
new ByteArrayInputStream(bytes), monitor);
|
||||
memory.createInitializedBlock("duplicate", toAddr(0x140201000L), duplicate, 0x200, 0x200, false);
|
||||
rejected(resolve, bridge, "multiple loaded mappings", "file-offset:0x210", new JsonObject());
|
||||
println("REA_FILE_OFFSET_SOURCE_OK foreign/partial rejected; original round trip; duplicate ambiguity; injected backend failure context retained");
|
||||
println("REA_FILE_OFFSET_SOURCE_OK foreign/partial/uninitialized/unmapped rejected; original round trip; duplicate ambiguity; injected backend failure context retained");
|
||||
}
|
||||
|
||||
private JsonObject reverseInput(String address) {
|
||||
@@ -112,6 +121,13 @@ public final class ReaFileOffsetSourceProbe extends HeadlessScript {
|
||||
throw new AssertionError("Expected mapping failure: " + reason);
|
||||
}
|
||||
|
||||
private void rejectedCode(Method method, Object bridge, String code, String reason, Object... arguments) throws Exception {
|
||||
Throwable failure = rejected(method, bridge, reason, arguments);
|
||||
var field = failure.getClass().getDeclaredField("code");
|
||||
field.setAccessible(true);
|
||||
require(field.get(failure).equals(code), "expected mapping failure code: " + code);
|
||||
}
|
||||
|
||||
private void require(boolean condition, String message) {
|
||||
if (!condition) throw new AssertionError(message);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user