Skip to content

Link DWARF types only when prototypes match ##analysis - #26455

Open
trufae wants to merge 4 commits into
masterfrom
link-match
Open

Link DWARF types only when prototypes match ##analysis#26455
trufae wants to merge 4 commits into
masterfrom
link-match

Conversation

@trufae

@trufae trufae commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

PR 26455: Link DWARF types only when prototypes match

What it does (one line)

Only install the fcnlink.<addr> address link when the type stored in
sdb_types actually has the same shape (return type, arg count, arg types,
variadic) as the prototype DWARF describes. Before, the link was installed as
soon as the type existed, even if r_anal_import_c_decls had normalized the
declaration into something else.

Why

The C importer can rewrite declarations while importing (known case: main
gets conventional parameters appended; typedef resolution can also change the
spelling). With the old code, afs would then authoritatively show the
importer's shape as if DWARF said so. This PR makes the link fail-closed:
no exact match, no link.

Behavior change

  • Functions whose imported type round-trips exactly: unchanged, link installed.
  • Functions where the importer changed the shape: no address link anymore.
    They fall back to the old name-based signature lookup, i.e. exactly the
    pre-fcnlink behavior. Nothing is deleted, no metadata is lost.
  • Non-C languages (Rust, Go...) use the fallback writer which stores types
    verbatim, so they always match and always keep their links.

Pros

  • A wrong authoritative signature is worse than no signature; this removes the
    only known path that could install a wrong one.
  • Reuses the existing dwarf_function_type_matches helper; +26/-11 lines, no
    new APIs, no format changes, no extra sdb walk when the match was already
    verified during name selection.
  • Test pins both sides: main (collides with libc main from the types db,
    gets suffixed to main_1169, links with exact int main_1169 ();) and the
    C++ ctor (Bird * const this survives the C parser round-trip and stays
    linked).

Cons / what you might not want

  • The match is a strict per-arg strcmp: a type the importer spells
    differently but equivalently (qualifier order, resolved typedef) also loses
    its link. Conservative by design; if this turns out common, the fix is
    canonicalizing both sides through one serializer, not loosening the compare.
  • The skip is silent: no log when a link is withheld, so "why does afs not
    show my dwarf signature" needs debugging. A one-line R_LOG_DEBUG would help.
  • Cosmetic (preexisting, now visible in the test): colliding names get an
    address suffix, so afs shows main_1169 instead of main.

How to test

r2 -q -c 'e asm.dwarf=false; aaa; afs @ 0x1169; afs @ 0x130e; k anal/types/fcnlink.00001169' bins/elf/dwarf3_cpp.elf

Expect int main_1169 ();, void Bird_Bird_ (Bird * const this); and the
fcnlink key present. Then check any binary with DWARF where the C importer
rewrites a prototype: the address must have NO fcnlink key and afs must show
the legacy signature, never the importer's rewritten one attributed to DWARF.

Verdict

Safe to merge: strictly reduces the cases where a link is installed, never
produces new output, and every skipped case degrades to pre-26451 behavior.

@trufae
trufae requested a review from 0verflowme August 13, 2026 23:25
@trufae

trufae commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

@0verflowme thats a followup from your previous pr. @phix33 feedback is welcome

@phix33

phix33 commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

I think it needs a few tweaks:

When a DWARF signature is rejected because its parsed shape differs, the imported type remains in the global type database. Even though fcnlink.<addr> is not created, afs can still find that type through the function-name fallback, so the normalized signature may still be shown (the behavior this PR is intended to prevent).

Also, a mismatching prototype can receive a new suffixed type name on every aaa run, such as foo_<addr>, foo_<addr>_2, and so on. The existing typed_name should be reused independently of whether the type matches, while the match result should only determine whether fcnlink is installed.

The new tests cover successful matches and user overrides, but not the mismatch path. Suggestion is to add a negative regression test confirming that:

  • no fcnlink.<addr> is created for a mismatch;
  • afs does not resolve to the rejected imported signature;
  • typed_name remains stable after another aaa; and
  • repeated analysis does not create additional suffixed types.

@0verflowme 0verflowme left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with direction, it matches with what we’ve been building.

I’d add only these correctness pieces also to close this PR’s invariant

  • Reuse fcn.<id>.typed_name when the DWARF function identity is unchanged, regardless of whether its current type matches. Matching should control only fcnlink publication. This prevents _2, _3, etc. after repeated aaa
  • If a newly imported signature is normalized into a mismatch, do not leave it available to function-name fallback:
    either delete that newly created signature; or
    delete it and recreate the exact DWARF shape through the existing SDB fallback.
  • Revoke a stale fcnlink.<addr> when reanalysis proves that its referenced type no longer matches. Merely refusing to write a new link can leave an old, now-invalid link active.
  • Perform the final prototype comparison after all importing/fallback work. Only this final stored representation may receive fcnlink

Comment thread libr/anal/dwarf_process.c
@@ -1751,7 +1759,9 @@ static void import_dwarf_function_type(Context *ctx, const char *sname, const ch
(void)import_dwarf_function_fallback (anal, typed_name, ret_type, variables, has_unspecified_parameters);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Withholding fcnlink does not prevent the rejected imported type from remaining in sdb_types. The name-based afs fallback can still display it

Comment thread libr/anal/dwarf_process.c Outdated
const char *previous_name = sdb_const_getf (ctx->sdb, NULL, "fcn.%s.name", sname);
const char *previous = sdb_const_getf (ctx->sdb, NULL, "fcn.%s.typed_name", sname);
if (previous_name && !strcmp (previous_name, dwarf_fcn->name)
&& previous && dwarf_function_type_matches (types, previous,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the previous typed_name is reused only when it matches. A persistent mismatch therefore creates foo_<addr>, foo_<addr>_2, etc. after repeated aaa

Comment thread test/db/formats/dwarf
CMDS=<<EOF
e asm.dwarf=false
aaa
k anal/types/fcnlink.00001169

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no negative mismatch regression proving link absence, safe afs fallback, stable naming, and no type accumulation

@trufae

trufae commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

updated @phix33 @0verflowme

@0verflowme 0verflowme left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One edge case still seems open: if fcnlink.<addr> points to an older differently named DWARF type and the new typed_name matches, we keep the stale link; since fcnlink.* is DWARF-owned, should we replace it with the current matching type and add a small regression test?

@phix33

phix33 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator
  • [P1] Registered types can be overwritten before they are loaded.
    type_existed is checked while the type database may still be empty, then the importer loads and replaces an existing registered type.
    Fix: call r_anal_types_ensure_loaded() at the start of r_anal_dwarf_process_info(). Add a fresh-RAnal test proving registered types survive and collision naming is stable.

  • [P1] The main mismatch-repair path is still untested.
    The current test pre-creates the function type, so it bypasses the importer → mismatch detection → removal → exact DWARF fallback path.
    Fix: add a test starting with no function record, force parser normalization, and verify exact DWARF types are restored, referenced types remain, linking follows prototype completeness, and repeated analysis creates no extra suffixes.

There's also:

  • [P2] Duplicate DIEs at the same name/address can preserve an inferior prototype.
    The reuse shortcut cannot distinguish a repeated analysis pass from a second DIE in the same pass, so a later complete prototype may fail to replace an earlier incomplete one. Link selection is also order-dependent.
    Fix: track DIE identity or processing-pass state. Could collect candidates per address and commit the best complete, exactly matching prototype after processing all DIEs.

@trufae

trufae commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Ok to merge now?

radare added 2 commits August 15, 2026 16:13
Always overwrite fcnlink with the current typed name once type_matches
is true, instead of only reusing it when it already equals the old
value. A persistent mismatch previously kept re-minting suffixed names
(foo_<addr>, foo_<addr>_2, ...) on every aaa; now the link tracks the
latest matching type.
@0verflowme

Copy link
Copy Markdown
Member

tests

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.

4 participants