Skip to content

Add an x86-64 object whose structured tree is deeper than a recursive walk - #220

Open
zardus wants to merge 2 commits into
masterfrom
feature/deeply-nested-ifs-fixture
Open

Add an x86-64 object whose structured tree is deeper than a recursive walk#220
zardus wants to merge 2 commits into
masterfrom
feature/deeply-nested-ifs-fixture

Conversation

@zardus

@zardus zardus commented Aug 30, 2026

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Problem

angr has no test input whose structured tree is deeper than a recursive walker can descend,
so a decompiler change that removes that limit cannot be regression tested. Decompiling
28,468 functions of 652 existing fixtures raises no RecursionError at all; the deepest
tree found is 306 levels, in region_slice_target of
tests/aarch64/decompiler/pathological_loop, and that shape costs fewer interpreter frames
per level so it decompiles cleanly.

Root cause

Real functions that reach the depth come from binaries that cannot be redistributed.
This object is generated from source instead.

Fix

tests/x86_64/deeply_nested_ifs is two hundred sequentially guarded statements over a
value the compiler cannot fold away, so the structurer produces a ConditionNode wrapping
a SequenceNode per level and the tree is about four hundred nodes deep. Built with
gcc -O0 -no-pie (GCC 15.3.0) from tests_src/deeply_nested_ifs.c, which is committed
beside it, 15,584 bytes, sha256 ff00d14378eaeedb543c6f30685aa26718a3633a2fa3a533407caabc0b633411.

Testing

angr.Project(...); CFGFast(); Decompiler(main) records a RecursionError and produces no
output on angr master -- 997 traceback frames, 789 of them in sequence_walker.py -- and
produces 809 lines with no errors on the branch that consumes it. The decompilation takes
under five seconds. Validation: #220 (comment)

session: sharpen

@zardus

zardus commented Aug 30, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head 8ee6b81045ef11dba64789cd74202f848f5064b0, which adds tests/x86_64/deeply_nested_ifs.

  • Size 15,584 bytes, sha256 ff00d14378eaeedb543c6f30685aa26718a3633a2fa3a533407caabc0b633411
  • Built with gcc -O0 -no-pie -o tests/x86_64/deeply_nested_ifs tests_src/deeply_nested_ifs.c, GCC 15.3.0. It was built on a Nix system, whose compiler wrapper writes its own store path into the interpreter and adds a RUNPATH that no other object here carries, so that build also needed NIX_DONT_SET_RPATH_x86_64_unknown_linux_gnu=1, an unset NIX_LDFLAGS and -Wl,--dynamic-linker=/lib64/ld-linux-x86-64.so.2. Two builds with that invocation are byte-identical.

Header fields read back with pyelftools:

field value
class / endianness ELFCLASS64 / ELFDATA2LSB
type / machine ET_EXEC / EM_X86_64
entry 0x401020
sections 28, not stripped
.comment GCC: (GNU) 15.3.0
interpreter /lib64/ld-linux-x86-64.so.2, matching every other object here
DT_NEEDED libc.so.6, with no RUNPATH or RPATH
main 0x401106, 3,641 bytes
  • main loads and decompiles under angr.Project(path, auto_load_libs=False); it never calls libc, so nothing needs to be resolved.
  • Structured tree 401 levels deep, 202 basic blocks. Decompiling it takes 2.3 to 3.6 seconds and 809 lines come out.
  • The file carries no build path and no user name: grep -ac nix/store over it returns 0, and the only toolchain string is the .comment above.

session: sharpen

@zardus

zardus commented Aug 30, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

The deepest structured tree the decompiler builds over the fixtures in tests/, before and
after this object is added, and what angr master does with the deepest one in each case.

Before — nothing in the repository reaches a depth that breaks a recursive walk:

652 objects, 28,468 functions
objects attempted   : 652
objects loaded ok   : 638
other statuses      : {'error': 8, 'deadline': 6}
functions decompiled: 28468
funcs discovered    : 46657
RecursionErrors seen: 0

deepest trees
  height=306   region_slice_target@0x4000b0   blocks=1104   tests/aarch64/decompiler/pathological_loop
  height=138   client_config_line_proc@0x407f79  blocks=635   tests/x86_64/decompiler/morton
  height=92    sub_43ab29@0x43ab29            blocks=732    tests/x86_64/decompiler/unzip_gcc17_O0.stripped
  height=69    sub_4015d0@0x4015d0            blocks=232    tests/x86_64/ALLSTAR_9base_dd
  height=67    bridge_parse_opt@0x4000a0      blocks=292    tests/x86_64/decompiler/iplink_bridge.o

the deepest one, on angr master:
  pathological_loop region_slice_target@0x4000b0: 236 characters, 0 errors

After — the new object is deeper than all of them and angr master cannot walk it:

tests/x86_64/deeply_nested_ifs
height=401   main@0x401106   blocks=202   tests/x86_64/deeply_nested_ifs

on angr master:
  deeply_nested_ifs main@0x401106: 0 characters, 2 errors
    RecursionError: maximum recursion depth exceeded
    terminal frame sequence_walker.py:282:_handle_Condition
    997 traceback frames, 789 of them in sequence_walker.py

The heights are the maximum nesting of SequenceWalker._handle, measured with the recursion
limit raised inside the measuring harness so every function completes; the decompilations
beside them are at the stock limit.

… walk

main is two hundred sequentially guarded statements over a value the compiler
cannot fold away, so angr's structurer produces a ConditionNode wrapping a
SequenceNode per level and the tree is 401 nodes deep. A walker that recurses
per level runs out of interpreter frames on it.
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