Skip to content

Add an AArch64 object whose functions take a class by value - #214

Open
zardus wants to merge 1 commit into
masterfrom
feature/aarch64-aggregate-args
Open

Add an AArch64 object whose functions take a class by value#214
zardus wants to merge 1 commit into
masterfrom
feature/aarch64-aggregate-args

Conversation

@zardus

@zardus zardus commented Aug 29, 2026

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Problem

Nothing committed here lets a consumer assert what angr does with an AArch64 function that
takes a class by value. Scanning the 37 AArch64/ARM64 objects tracked in this repository for
an Itanium-mangled symbol whose demangled signature has a by-value class parameter finds
three, all in tests/aarch64/IPwnKit.macho.kext, and cle refuses that object:

CLECompatibilityError: Unsupported Mach-O file type: 11. Please open an issue if you need support for this

Root cause

angr builds an aggregate argument type from the demangled C++ name rather than from debug
information. A C struct passed by value is recovered as unsigned long whether or not the
object carries DWARF, so the shape needs a C++ object with unstripped mangled symbols, and
there is no AArch64 one here. tests_src/test_pass_struct_by_value.c is built for armel only.

Fix

tests/aarch64/struct_by_value_aarch64.so, 6136 bytes, built from
tests_src/arch/aarch64/struct_by_value_aarch64.cpp with the recipe recorded at the top of
that source. Three free functions take a composite by value -- a 16-byte struct that AAPCS64
puts in a register pair, a 40-byte struct it passes indirectly, and a class holding an array
-- and a fourth calls all three, so the callee side and the call site both see one. The scan
above returns no loadable object at the merge base and this one at the head.

Testing

Loading the object with angr at a9ca247a5 recovers eight functions and decompiles two; the
other six produce nothing, because SimCCAArch64.next_arg raises on the aggregate. The
consumer is the angr pull request that links this one.

Validation: #214 (comment)

session: sharpen

@zardus

zardus commented Aug 29, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head 0a309529ea4176d35522c1ffab808dff2950a22e against baseline 003e82a2bfa641530924055695b36cec8af483ab.

  • Artifact: tests/aarch64/struct_by_value_aarch64.so, sha256 8767623d693efb78f9964b11eca62d35664d06645c63e9cf7baeeb8e1077dc8a, 6136 bytes
  • Header, read back from the committed bytes: ELF64, little-endian, e_type 3 (ET_DYN), e_machine 183 (EM_AARCH64), no PT_INTERP, not stripped
  • Provenance: rebuilt from the recipe recorded at the top of tests_src/arch/aarch64/struct_by_value_aarch64.cpp in a clean directory and compared byte for byte with the committed object — identical sha256
  • Local paths: the object carries no /nix/store, no home directory and no user name; -nostdlib is in the recipe because the default link records a DT_RUNPATH of build-machine store paths
  • Shape scan: over the 37 AArch64/ARM64 objects tracked at fbf240c93f2f112829dcc34c13cb8233923f4817, the head this record was keyed to when that scan ran — the baseline it named then held 36, so the figure is the head's — three Itanium-mangled symbols demangle to a signature with a by-value class parameter, all three in tests/aarch64/IPwnKit.macho.kext, which raises CLECompatibilityError: Unsupported Mach-O file type: 11 on load. Master has added AArch64 and ARM64 objects since and the scan was not re-run over them: the same population is 43 at the current baseline 003e82a2bfa641530924055695b36cec8af483ab and 44 at the current head. At this head the scan additionally returns this object, which loads
  • Consumer: loading it with angr at a9ca247a5bcadf0ad8ecccb61be5f76dc4cd5050 recovers 8 functions; 6 have a SimCppClass argument in their recovered prototype and decompile to nothing, arg_locs raising TypeError: <SimCCAArch64> doesn't know how to store aggregate type

Caveats: this repository configures no pre-commit hooks and no CI, so nothing beyond the readback above ran here.

Rebased 2026-09-06; this record is re-keyed to the new head. The branch was replayed from 3de2c41a297606c45be7985aa09b3ae615424d61..ce9f68472e94398f274db31f939d62ae06b0da9c onto binaries master 003e82a2bfa641530924055695b36cec8af483ab, giving 0a309529ea4176d35522c1ffab808dff2950a22e: git range-diff marks every commit =, the two git diff <merge-base>...<head> outputs hash to the same object, and every path the branch touches has the same blob at the old and the new head, so master left those paths alone. The figures that read the branch's own files therefore describe the same bytes on a new base. A figure that counts the whole repository is a different matter: master has added files, so each of those is keyed to the revision named beside it. It moved because a pull request that pins this one is checked out by CI at this branch's tip rather than merged with master, so the consumer was building against a binaries tree missing fixtures that were already on master. angr/binaries runs no pull-request workflow, so there is no run at the new head to cite — check-runs and actions/runs report 0 there, as they do for every pull request in this repository.

session: sharpen

@zardus

zardus commented Aug 29, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Loading tests/aarch64/struct_by_value_aarch64.so and decompiling every recovered function,
with the consumer at angr master and with the consumer's change applied.

Before — six of the eight functions produce nothing, which is the failure this fixture
makes assertable:

angr master a9ca247a5
0x400300  $x                       prototype () -> long long (64 bits)  ->  270 characters
0x400320  _Z10take_point5Point     prototype (class Point) -> void  ->  no decompilation
0x400330  _Z8take_big3Big          prototype (class Big) -> void  ->  no decompilation
0x400340  _Z9take_blob4Blob        prototype (class Blob) -> void  ->  no decompilation
0x400350  _Z10take_point5Point     prototype (class Point) -> void  ->  no decompilation
0x400364  _Z8take_big3Big          prototype (class Big) -> void  ->  no decompilation
0x400380  _Z9take_blob4Blob        prototype (class Blob) -> void  ->  no decompilation
0x40039c  _Z9call_theml            prototype (long (64 bits)) -> void  ->  no decompilation

After — every function decompiles:

angr with SimCCAArch64.next_arg implemented
0x400300  $x                       prototype () -> long long (64 bits)  ->  270 characters
0x400320  _Z10take_point5Point     prototype (class Point) -> void  ->  46 characters
0x400330  _Z8take_big3Big          prototype (class Big) -> void  ->  44 characters
0x400340  _Z9take_blob4Blob        prototype (class Blob) -> void  ->  45 characters
0x400350  _Z10take_point5Point     prototype (class Point) -> void  ->  34 characters
0x400364  _Z8take_big3Big          prototype (class Big) -> void  ->  32 characters
0x400380  _Z9take_blob4Blob        prototype (class Blob) -> void  ->  33 characters
0x40039c  _Z9call_theml            prototype (long (64 bits)) -> void  ->  644 characters

The two sides ran the same script over the same object; only the angr tree differed, and each
side printed which tree it had imported.

session: sharpen

angr recovers an aggregate argument type only from a demangled C++ name, so a
C fixture cannot express this shape. The build recipe is recorded at the top of
the source.
@zardus
zardus force-pushed the feature/aarch64-aggregate-args branch from ce9f684 to 0a30952 Compare September 6, 2026 05:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant