Skip to content

xrefs: drop ARM PLT partial sums and predicated-instruction jumps - #1499

Open
alvistar wants to merge 2 commits into
Noelo-Lab:mainfrom
alvistar:alvistar/kuna-arm-xref-fps
Open

alvistar wants to merge 2 commits into
Noelo-Lab:mainfrom
alvistar:alvistar/kuna-arm-xref-fps

Conversation

@alvistar

@alvistar alvistar commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

On ARM, kuna xrefs files two kinds of rows that are not references: a PLT stub files the partial sums it builds its GOT slot's address from, and every predicated instruction (ldrne, moveq, popeq {..,pc}, anything in a Thumb it block) files a jump to the next instruction.

$ kuna xrefs decompiler/crates/kuna-analysis/tests/fixtures/xreffp_arm_le32 --from printf
# 3 references from printf @ 0x10310
0x10318	data	-	@0x10310	adr r12,0x10318
0x12000	data	-	@0x10314	add r12,r12,#0x12000
0x2300c	read	-	@0x10318	ldr pc,[r12,#0xcf4]!
$ kuna xrefs decompiler/crates/kuna-analysis/tests/fixtures/xreffp_arm_le32 --to 0x10568
# 1 reference to 0x10568
0x10564	jump	pick+0x4	ldrne r0,[r0,#0x4]

printf@plt is add ip,pc,#0,12; add ip,ip,#0x12000; ldr pc,[ip,#0xcf4]!; only the slot 0x2300c is a reference. pick is cmp r1,#0; ldrne r0,[r0,#4]; ldreq r0,[r0]; bx lr. The fixture comes with this PR. With it, the first command gives only the read row and the second none.

  • armpltslot (listing/kuna_pltstub.rs): inside .plt and .iplt only, the walk follows a register set to a constant, one or two adds of a constant, then a load through it that a BRANCHIND takes. The chain files no constant data row; the slot's read stays. Any other shape keeps the constant scan's rows.
  • armcondskip: a CBRANCH to the instruction's own fall-through ahead of its other ops (Classified::condition_skip) stays a flow for every walk but files no row, as in Ghidra. bne to the next line keeps its jump, and so does a skip from an instruction with no fall-through (bxne lr, ldrne pc, addls pc) into another function's entry, since it is the one way in.
  • Both default on; --option armpltslot off --option armcondskip off gives main's rows. Other architectures do not change.

Against IDA on three stripped ARM executables, rows kuna files that IDA does not go 1203 → 35, 308 → 49 and 737 → 12; no row IDA and kuna shared is lost. decompile-all output is byte-identical to main on 107 ARM ELF files (musl libc.so, libstdc++.so, libgomp.so and the repo's ARM fixtures among them), except one function that hits the 120 s budget on both sides.

Tests: verify_xreffp.rs (xreffp_arm_le32, plus xreffp_strip_arm_le32 and xrefiplt_arm_le32 built from assembly) checks one slot row and no partial rows for three PLT stubs and an .iplt stub, no jump from nine predicated instructions, the kept jumps above, and both options off. kuna_pltstub unit tests cover the shapes that break a chain. tests/stages/kuna-armpltslot.xml and kuna-armcondskip.xml pin that each option parses and leaves the console's C unchanged.

Speed (CPU, median of 5, main → this PR): kuna strings 1.02 → 1.03 s on x86-64 musl libstdc++.so, 1.06 → 1.06 s and 0.53 → 0.52 s on ARM musl libstdc++.so and libc.so; kuna xrefs --to memcpy within ±0.01 s on all three.

🤖 Generated with Claude Code

alvistar and others added 2 commits November 3, 2026 16:15
Two ARM reference-walk rules, each behind a default-on option:

- armpltslot: a GNU ld A32 PLT stub (adr ip; add ip,ip,#N; ldr pc,[ip,#M]!)
  files its GOT slot as a Read and no longer files the PC value or the
  mapped N<<12 immediate as Data rows.
- armcondskip: the CBRANCH to the next instruction that SLEIGH puts ahead of
  a predicated instruction files no Jump, unless it is the one way into the
  next function's entry.

Rebased onto main as one commit (the earlier 30-commit history sat on the
pre-squash branch-tables layer). With armpltslot off the slot's Read now
comes from regcarry, so the off test expects it. docs/options.md and the
stages baseline are regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now names synthesized structures as a serial run does, so the
callee-first order these rows change no longer renumbers struct_N. On ARM
musl libc.so, its stripped copy and libstdc++.so the output is
byte-identical with both options on and off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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