Register ArchMIPSN32 so its name resolves to it - #385
Conversation
arch_from_id("MIPSN32") returned ArchMIPS32. ArchMIPSN32 was the only class in
archinfo.__all__ that register_arch was never called for, so no rule in
arch_id_map claimed its identifiers and the 32-bit MIPS catch-all took them.
The two architectures share neither a register file nor an instruction width, so
a caller that canonicalises an architecture through its own name got a different
architecture back.
ArchMIPS32's catch-all now declines the n32 spellings and arch_mips64.py claims
them, after the 64-bit rules so all_arches keeps MIPS64 ahead of MIPSN32.
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Validation record for head Workspace gate
angr suites run separately, after adopting angr into the feature
Neither MIPSN32 nor MIPS64 can reach The full angr suite did not run. What ran is the set of suites this change Change-specific measurementsRegression, on the merge base with the source reverted and the tests kept: and on this head,
Wall-clock figures are deliberately absent: two independent runs of the same The three functions that stop are the disclosed Environment |
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Reproducer, on a fixture import angr, archinfo
from angr.procedures.definitions.linux_kernel import lib as klib
print("arch_from_id('MIPSN32') ->", archinfo.arch_from_id("MIPSN32").name)
print("default_ccs ->", {k: v.__name__ for k, v in klib.default_ccs.items() if "MIPS" in k})
p = angr.Project("binaries/tests/mips/busybox", auto_load_libs=False)
cfg = p.analyses.CFGFast(normalize=True)
p.analyses.CompleteCallingConventions(recover_variables=True, analyze_callsites=True, cfg=cfg.model)
print("with a calling convention:", sum(1 for f in p.kb.functions.values() if f.calling_convention is not None), "/", len(p.kb.functions))
print("with a prototype :", sum(1 for f in p.kb.functions.values() if f.prototype is not None), "/", len(p.kb.functions))No wall-clock figures are quoted below: two runs of this script disagreed by up Before -- archinfo
|
|
Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/archinfo_385 |
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS
Problem
arch_from_idanswers the identifierMIPSN32withArchMIPS32:The two are not interchangeable. n32 is a 64-bit instruction stream with 32-bit
pointers; MIPS32 has neither the same register file (
a4-a7exist only on the64-bit one) nor the same instruction width. Anything that canonicalises an
architecture by round-tripping its name therefore gets a different architecture
back, and gets it silently.
angr does exactly that.
SimLibrary.set_default_cccanonicalises its key witharchinfo.arch_from_id(arch_name).name, andprocedures/definitions/linux_kernel.pywalks
SYSCALL_CCcalling it once per architecture.MIPS32is written first andMIPSN32then lands on the same key and overwrites it:Every syscall SimProcedure on a MIPS32 Linux project is then given the n32
convention, whose argument registers are
a0-a7. Onbinaries/tests/mips/busybox(MIPS32 big-endian, 3518 functions),CompleteCallingConventionsreaches themmap/futexstubs and dies:Nothing catches it, so the analysis is lost for the whole binary rather than for
the one function: after the raise 139 of 3518 functions carry a calling
convention.
Root cause
register_archwas never called forArchMIPSN32-- it is the only class inarchinfo.__all__with anameand noarch_id_mapentry. No rule claims itsidentifiers, so
arch_from_idfalls through to ArchMIPS32's catch-all:It is the only 32-bit MIPS rule there is, and
arch_from_idreadsbits=32off the
32inmipsn32, so the two MIPS64 rules are never in the running.Nothing is wrong with
SimCCO32itself: an o32 function's fifth integerargument is a stack slot, exactly as the ABI says, so no o32 convention can
produce an
a4. It comes only from the wrong convention being attached.An identifier answered with a different architecture is worse than
ArchNotFound, because the caller cannot see it happen.Fix
ArchMIPS32's catch-all declines the n32 spellings, andarch_mips64.pyregisters
ArchMIPSN32for them:The n32 rules sit after the 64-bit ones so that
all_arches, whichregister_archappends to as well, keepsMIPS64ahead ofMIPSN32.arch_from_idis insensitive to that placement..*mips64.*|.*mips.*doesmatch the string
mipsn32; it is the bits filter, not the pattern, thatdeclines it. One edge changes with it:
arch_from_id("mips64-linux-gnuabin32", bits=32)now raisesArchNotFoundwhere it used to answerArchMIPS32. Nocaller in cle or angr passes that combination.
With that,
binaries/tests/mips/busyboxcompletes the analysis and 3514 of 3518functions get a calling convention, 3509 of 3518 a prototype, against 139 and
139 on the merge base.
After, every architecture archinfo exports resolves from its own
name. Byclass rather than by name one still does not, on both arms:
ArchARM.nameis"ARMEL", which is the alias relationship those two have.Two things deliberately not done. A
reg_name in arch.registersguard atfact_collector.py:449: it would stop the crash and leave every MIPS32 binaryanalysed with n32 argument facts -- silently wrong instead of loudly wrong --
and angr#6356 is already working that seam. And the GNU triplets
mips64-linux-gnuabin32/mips64el-linux-gnuabin32, which still resolve toArchMIPS64becausearch_from_idreadsbits=64out of the64. That is afamily property, not something left half-done: the
ArchARMHFandArchARMCortexMtriplets resolve toArchARMELthe same way on both arms.One consequence is worth stating, because it is a loss. 12 of the 343 o32
syscall prototypes take a 64-bit argument (
pread64,pwrite64,truncate64,fallocate,readahead,sync_file_range, ...), andSimCCO32LinuxSyscalldoes not override
next_arg, so the base implementation refuses to split oneacross two 32-bit slots:
SimCCO32, the non-syscall o32 convention, lays out all 343 without raising,and the n32 convention handled these 12 only because its argument slot is 8
bytes wide. The same 12 come back on the merge base, so this fix uncovers a gap
that was there all along rather than creating one. In a 60-function sample from
busyboxit costs three functions their output (57 of 60 decompile to textagainst 60 of 60 on the merge base), against 139 recovered conventions rising
to 3514 across the binary. That gap belongs to
SimCCO32LinuxSyscallin angrand is not fixed here.
Testing
tests/test_mips.pygainsTestArchLookupByName, five cases:ArchMIPSN32isin
all_arches;arch_from_id(ArchMIPSN32.name)isArchMIPSN32; every classin
archinfo.__all__that has anameresolves from it; every o32 identifierthat resolved to
ArchMIPS32before still does, in the endness it did before;and the n32 identifiers carry their endness.
tests/test_mips.pyis 12 passedon this head and
4 failed, 8 passedon the merge base with the tests kept; theo32 control is one of the eight that pass on both sides, so it shows the
exclusion regex changed nothing there rather than that the suite is inert.
tests/is 47 passed, 34 subtests passed on this head.register_archmutatesall_arches, which angr walks in two places, andarchinfo's CI cannot see either; both were run against this head.
test_boyscout.pywithtest_stack_alignment.pyis 16 passed, andtest_calling_convention_analysis.pywithtests/procedures/is 98 passed,4 skipped.
Validation: #385 (comment)
session: sharpen