aboutsummaryrefslogtreecommitdiffstats
path: root/fw/decomp/hdcp.summary.json (follow)
Commit message (Collapse)AuthorAge
* regenerate all 16 modules on one decoder; align the RPC naming so diffs mean ↵user2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | something Finishes the rebuild that was interrupted earlier and leaves the corpus on a single decoder version for the first time since the SVC-opcode work started. All 16 modules rebuilt clean, 16/16 self-port gates PASS, no duplicate names in any of the 99 builds, no address-derived names. The naming fix from the previous commit is now actually in the artifacts: 1,038 SVC stubs use the libdump's sysdrv_rpc_<N> convention instead of my own rpc_<N>. Six names persisted in fw/cgnames from when I wrote them by hand are converted too, otherwise they would have quietly reintroduced the inconsistency on the next rebuild. The 85 remaining bare rpc_ names are of the form gcp_asd_rpc_202 -- ASD's libdump names ported into other modules through shared code, so that is the libdump's own convention and correct to leave. Effect on diffs, which is why this mattered: the 22.0.4.49 -> 22.0.4.50 pair reported 8 functions added and 8 removed with no firmware change behind it, and now reports 4. The remaining 4 are a different and smaller problem, now documented in ta_diff.py: a function can carry a richer name in one build than another, gcp_cmdFE0C against gcp_cmdFE0C_CreateCryptoSession, because the semantic half comes from a pass that only reaches some builds. Corpus-wide the diffs are now usable. Adjacent patch versions average 0.5 changed functions in ras, 2.5 in wspe, 3.3 in merit, 4.6 in opm, up to 26 in xgmi where the builds are genuinely far apart. Added/removed should be read as "look at this" rather than ground truth; changed-function counts are sound. Metrics unchanged by the rebuild at 19,487 meaningful names (67.3%) and 75.8% field share, which is the point -- it confirms the mixed-decoder tree was not inflating anything.
* replace the SVC-opcode heuristic with a structural rule: 940/940 against ↵user2026-10-01
| | | | | | | | | | | | | | | | | | | | | known names The previous pass left 102 stubs unnamed because they load more than one in-range constant. The tempting fix was to take the first immediate, which matches 4 of 4 multi-immediate stubs that carry an established name -- and that number is worthless, because all four are the same XGMI function in four builds. It is n=1 dressed up as n=4, and applying it to 102 functions would have been a guess with a validation figure attached. The structural rule earns the names instead: track MOV/MOVW/MOVS immediates per register, then return whichever register is stored to [SP,#0], which is the first word of the frame R1 is pointed at before the supervisor call. Against the 940 stubs the libdump already names *_rpc_N it matches 940 and disagrees on 0, with no abstentions, and it resolves all 102 stubs the count heuristic could not. So the weaker rule is superseded rather than patched, and the audit records both along with why the 4/4 figure was rejected -- that reasoning is the part worth keeping. Regenerated gcp, hdcp, merit, opm and asd. Corpus 19,405 -> 19,433 meaningful names (67.1%), no duplicate names in any of the 99 builds, address-derived still 0.
* generalise SVC-opcode naming corpus-wide after validating it: 501 stubs, ↵user2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | corpus to 67.0% The opcode decode that closed dtm and csu last commit is not module-specific, so I measured its reach and validated it before applying it anywhere. Validation came free: the libdump already names 299 single-immediate SVC stubs *_rpc_N, which is independent ground truth for exactly this decode. The decoded immediate matches the established number in 296 of 299. The three exceptions are one XGMI function across three builds whose libdump name uses a different numbering scheme, not a bad decode. Applied as a pipeline pass, keyed on bytes rather than addresses so it survives database rebuilds, and only where exactly one in-range immediate survives -- 102 ambiguous stubs stay unnamed rather than guessed. 501 stubs named: hdcp 87 -> 96, fp 92 -> 95, gcp 87 -> 91, opm 62 -> 75, merit 76 -> 79, xgmi 75 -> 76, ras 35 -> 38, asd 64 -> 65, powerplay and wspe smaller. Corpus 19,134 -> 19,405 meaningful names, 66.1% -> 67.0%. A second corroboration turned up after the fact: HDCP's newly decoded hdcp_rpc_161 carries the same service number as ASD's independently libdump-named asd_rpc_161, so two modules calling the same SYS_DRV entry agree without either having been told about the other. Names are uniquified per build; no build contains a duplicate name, checked across all 99. Worth noting what made this possible: the claim being overturned was my own, that these stubs "carry no semantic information whatsoever". It was right about the shared thunks and wrong about the per-module ones, and I had written it down as settled.
* name all five GP entry-point slots from the image header: TMZ reaches 100%user2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | ta_entry_points read the trampoline's +0xA0 (Create) and +0xB0 (Invoke) slots and threw away the three between them. Those are TA_DestroyEntryPoint, TA_OpenSessionEntryPoint and TA_CloseSessionEntryPoint. That is the whole reason TMZ sat at 50% named: it has six functions and three of the unnamed ones were entry stubs the header identifies exactly. The slot order is read off GCP rather than assumed from the GP spec. GCP has all five named independently and its table reads Create, nullsub(destroy), OpenSession, CloseSession, Invoke, with WSPE and HDCP agreeing and the types agreeing too -- slots 0, 2, 4 return a status, slots 1 and 3 are void. Running the new derivation against GCP reproduces GCP's own names exactly, which is the check that makes this evidence rather than convention. Applied to every module, only where porting left the address unnamed. Per-module: tmz 50 -> 100, rap 76 -> 98, csu 70 -> 92, oc 90 -> 97, dtm 75 -> 89. Corpus 19,038 -> 19,126 meaningful names (65.8% -> 66.1%), field share 75.5% -> 75.8%. RAS disagrees and is deliberately left alone. Its libdump names TA_CreateEntryPoint and TA_DestroyEntryPoint at addresses the header assigns to OpenSession and CloseSession, a systematic two-slot shift across all 18 builds. My first run wrote the header names anyway, which left RAS asserting two different Create entry points -- a contradiction I introduced and then caught by checking for duplicated roles. A guard now refuses to add a second holder of a role, so the conflict is reported on every build and nothing is written. No build has a duplicated entry-point role. Which source is right for RAS stays open: the obvious tiebreaker, argument count, is not decisive on functions IDA never prototyped.
* type hdcp2_tx_RepeaterAuth_Stream_Manage: 24 raw dereferences per build down ↵user2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | to 1 Two errors of mine found by measuring the typing residue per build instead of summed across builds. First, the per-function table in AUDIT-TA-STRUCTS.md summed each function's dereferences over all builds, which made the residue look concentrated -- asd_link_sub_C930 appeared to hold 158 when it holds 14 in any single build. Measured per (build, function) pair, which is what a reader actually opens, the distribution is flat: median 2, mean 2.7, p90 4, max 33 across 1,360 pairs. The max is GCP's start, the entry stub, where raw pointer arithmetic is correct and typing would be wrong. Second, that measurement surfaced one real target, and it had been excluded for a wrong reason. The recorded note said its dereferences "go through a local (v10), not a1, so typing a1 would not reach them". The body says "v10 = a1". The note was wrong, and because it read as a settled finding it was never rechecked -- the same failure mode as the thunks-only and GCP-only claims. The real disqualifier was the documented padding rule: offsets 0x1-0x3, 0xD, 0xF9, 0x106 and 0x250 landed inside declared pad arrays. Per union-never-intersection those offsets belong in the layout, so pad00, pad08, pad55 and pad24E are split to name them. Struct size is unchanged at 0x26C and all ten observed offsets now resolve to named fields, verified by walking the declaration offset by offset before applying it. In the artifacts, across all 7 HDCP builds: 24 raw dereferences per build to 1, with 22 typed field accesses. Corpus named-only field share 74.5% to 75.6%, raw dereferences 3,661 to 3,506. Named count unchanged, and HDCP's gate still passes.
* type and name the HDCP1 authentication handler, the hardware-confirmed ↵user2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | overflow site hdcp1_first_part_authentication carries the HDCP overflow that AUDIT-TA-MEMSAFETY-HUNT records as confirmed on hardware at 0x402cde, with the copy length taken from a byte struct field. It was still "int a1" with eleven vN locals. hdcp1_auth_req is derived from the accesses across all seven builds: a dword at 0x00, a dword at 0x04, bytes 0x08..0x0E and halfwords at 0x10 and 0x12, closing at 0x14. The block is what the invoke entry passes as v7 + 12, i.e. hdcp_req.argA, which is consistent with the request layout already derived. The first dword is named session_idx on direct evidence rather than by analogy: the handler reads it, bounds it with "<= 7", and then indexes a stride-652 table as "dword_40237C + 652 * session_idx". Eight sessions of 652 bytes. Typing a2 was already in place from the earlier argN work; a1 now joins it, and hdcp1_first_part_authentication's locals session_idx and status are named. Recorded limitation rather than smoothed over: the documented bug site loads its length from [r5, #0x38], and r5 is NOT v12 in this build -- register allocation differs between the build the memsafety audit used and the reference here. The local names are therefore keyed on what each local demonstrably does in this build, and the two that do not correspond are left alone rather than mapped by assumption. Also honest about the measured effect: HDCP's field share moves only 70.0% to 70.2%, because Hex-Rays keeps the explicit "*(_DWORD *)a1" cast at several of these sites even with the pointer typed, so the signature improves while those particular lines do not. The win here is the signature and the session_idx/652-stride relationship being legible, not the metric.
* correct two mislabelled GCP handlers, and stop emitting stale pseudocodeuser2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Two GCP handlers were mislabelled in the libdump symbol set and the mislabels had been propagating into every artifact. The duplicate name was the tell: FE0B and FE0D were both called DestroyAuthenticatedSession. The code settles it without needing the documents. gcp_cmdFE0C iterates ctx->crypto, zero-fills a 0x58 gcp_crypto_session and sets crypto->state and crypto->key, so it creates a CRYPTO session. gcp_cmdFE0D computes "(char *)ctx + 88 * HIBYTE(arg10)" -- stride 88, the crypto array at ctx+0x800 -- while gcp_cmdFE0B uses stride 80, the auth array at ctx+0x300. AUDIT-NEW-TAS-RE.md states exactly that split, and the hardware findings agree: FINDING-GCP-ACQUIRE-GATE-BYPASS records 0xFE0C as CreateCryptoSession. Worth distinguishing from the FE07 case: that one claimed the wrong COMMAND and the dispatcher cross-check caught it automatically. These two claim the right command and the wrong SEMANTICS, so no command-id check can see them -- only reading what the code touches. The corrections are applied by name through a RENAME_FIX table so they hold for every build. While verifying them, the dispatcher kept printing the old callee name in four builds after the rename had demonstrably succeeded. Hex-Rays caches pseudocode per function and remote_ops.rename refreshes only the function it renamed, never its callers, so a stale cache was reading as a failed rename. Every function's cached text is now invalidated before the artifacts are emitted. That turned out to matter far beyond the two names. Applied TYPES were also being hidden behind stale text: GCP's field share goes 59% to 83.4%, its typed field accesses 1716 to 3081 and its raw dereferences 1193 to 612, with no new structure added -- the types had been applied all along and the emitted artifacts were showing pre-typing output. WSPE lands at 91%, HDCP 70%, XGMI 69%, POWERPLAY 67%. Corpus field share 65.9% -> 66.4%, held down by ASD, which dominates the corpus and barely moved. This is the sixth consecutive pass whose main finding was that something already done was not visible in the output. The remaining low scores are now informative rather than suspect: merit at 0% and opm at 19% are low because their structures are mostly opaque body arrays, not because typing failed.
* type the structures that arrive as a2, which the arg1 machinery could never ↵user2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | reach Two of the three largest remaining dereference groups in named functions were not blocked by analysis at all: hdcp2_process_auth_message_v2 with 161 and hdcp1_create_session with 49 both take their structure as the SECOND argument, and every typing path built so far rewrites only the first parameter. retype_arg1 is generalised to retype_arg with a 1-based index, with the old name kept as an alias so the existing call sites are untouched, and a per-module ARGN_BY_TA table drives it. hdcp2_auth_msg is contiguous dwords 0x04..0x24 followed by an 8-byte read at 0x28, which is 8-aligned, so it closes at 0x30. hdcp1_session_req is sparser, and its 8-byte read at 0x04 is NOT 8-aligned. It is declared as two dwords rather than a 64-bit field: declaring the wide read as one member would force alignment padding and shift every later offset, which is the same trap asd_link_cfg's unaligned 0x14 qword read presented. Size verified at 0x20. HDCP field share 62.8% -> 68.9%, corpus 65.2% -> 65.9%, raw dereferences 7192 -> 6982. Arg-based typing in named functions is now effectively exhausted, and the numbers say so rather than my asserting it: the total has gone 2201 -> 1507 -> 1163 across three passes, and every group still standing touches one or two offsets. A structure for a function that reads two offsets is a one-field structure, which buys nothing a raw offset did not already show, so the remaining dereferences are deliberately left alone rather than dressed up.
* wire in the HDCP signature rule that was measured and then forgottenuser2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | Re-running the concentration analysis after last pass's fix left hdcp2_rx_RepeaterAuth_ReceiverID_List at 135 raw dereferences while its signature plainly showed "struct hdcp2_session *a1". Both facts were true: it is typed in 4 of 7 builds and unnamed in the other 3, and the name-keyed rule cannot reach a function it cannot find by name. A signature rule for exactly this case was measured when hdcp2_session was first derived -- 3 unnamed functions, 135 dereferences, zero false positives among the named -- and then never implemented. It is now wired in through a generic SIG_BY_TA table rather than another module-specific branch, since ASD already had its own copy of this idea. The predicate requires every a1 offset to fall inside the structure's declared FIELDS rather than anywhere within its extent. Accesses landing in padding are treated as disqualifying, because padding means the layout does not describe that function -- which is the check that would have caught the hdcp2_tx_RepeaterAuth_Stream_Manage mistype earlier without needing to be reasoned about by hand. Checking that condition also found two offsets, 0x222 and 0x224, that real accesses touch while hdcp2_session had them as padding. Both are now fields; 0x224 is a single byte rather than a halfword because 0x225 begins the next run, and the size is re-verified at 0x26C. Two process notes worth keeping. The insertion failed silently the first time -- the anchor text did not match, the assertion fired inside a heredoc whose output was swallowed by the following command, and the pipeline then ran happily without the new rule. It was caught only by grepping for SIG_BY_TA in the generated file rather than trusting that the edit had landed, which is the same class of silent no-op as the last four passes.
* make derived names durable in the repository instead of only inside the .i64 ↵user2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | files This pass fixes a fragility that was actively destroying work rather than adding coverage. Four IDA databases have now come back corrupted with "worker exited with status 1", and the only remedy is to delete and rebuild them. Every name derived by fw/ta_cgname.py lived solely in those databases, so a rebuild discarded them -- GCP silently fell from 83% to 74% named when I wiped its databases for the dispatcher fix, and I only noticed because I happened to re-measure per module. Worse, those names cannot be re-derived: once a callee has a name it stops being a sub_* candidate, so re-running the tool on an already-processed build reports nothing to do. Three changes: * fw/ta_cgname.py persists every name it applies to fw/cgnames/<ta>.json, keyed by build and address. * fw/ta_re.py re-applies that map at the start of each build, before anything reads names, so a rebuilt database comes back with its derived names intact. * fw/ta_snapshot_names.py recovers the names applied before the map existed. They are read out of fw/decomp/*.index.json, which records name, address and tier per function, and only the IDA tier is snapshotted. EXACT, MNEM, FUZZY and DISPATCH are deliberately excluded because fw/ta_re.py re-derives them every run; persisting those would bake a derived result into the repository and hide a future regression in the porting itself. 1476 names are now durable across 15 modules, including ASD's 847, which would otherwise have needed a 40-minute wipe-and-rederive to recover. Verified by doing the thing that caused the loss: XGMI's databases were deleted and rebuilt. It came back at 238 of 334 named against 239 before, so a full database loss now costs one function instead of nine percentage points. The single remaining loss is not chased down here, but it is recorded rather than rounded away. GCP is restored to 84% and HDCP re-derived cleanly to 86% with a complete map. Corpus naming stands at 67.0%.
* fix phantom commands in the dispatcher map, found by auditing names against ituser2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Rather than chase another percentage, this pass audited whether the names already applied are CORRECT, using the only authority available: the dispatcher. For every build, each handler whose name claims a command id was checked against the command the dispatcher actually routes to it. 257 handlers checked, 246 agreed, and 11 conflicted -- all in GCP, all of the form gcp_cmdFE0A_QueryAccessibilityAttributes__hN being routed from commands 0x8, 0x9, 0xA and 0xF. Those are not GCP commands at all. They are arms of 0xFE0A's own sub-command switch, absorbed as top-level commands because dispatch_map merged EVERY switch-bearing callee at the shallowest depth. The handler names were right; my command map was wrong, which is the opposite of what the conflict report implied. The merge is replaced by taking only the richest switch at the shallowest depth. The union was introduced to allow a TA that splits low ids and 0xFExx ids across sibling switches, but GCP serves both from one function, so nothing is lost: the reference still recovers 14 commands, all 14 still match fw/cmdmaps/gcp_ea.json address for address, and the phantom 0x8/0x9/0xA/0xF are gone. The three still missing (0x3, 0xFE08, 0xFE0E) remain the inlined arms with no callee, as before. Re-running the audit afterwards: 332 handlers checked, 332 agree, zero conflicts. The FE07 case that had been reported since the cross-check was built is also clear, because the pipeline now relabels that handler from gcp_cmdFE03_* to gcp_cmdFE07_* rather than leaving the inherited misnomer in place. Two more corrupted databases were found and rebuilt (hdcp_23.0.8.49, merit_24.0.4.42), with the same "worker exited with status 1" symptom as the gcp one last pass. Rebuilding loses the call-graph names on those builds, so ta_cgname was re-run for both; naming lands at 66.5% rather than 66.9% because of that churn. Also measured and rejected this pass, so they are not retried: cross-module naming by exact body hash yields 77 names, and by mnemonic sequence 170 (against 1496 that match an ambiguous cluster and cannot be resolved). Together under 2% of what remains unnamed, which is not worth the regeneration cost.
* run call-graph naming across every module: 66.9% of functions nameduser2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | The tool built last pass was only ever run on asd and ras. It is generic, so it is now run on the rest, and the mismatch-rate control holds everywhere -- 0 to 4 mismatched anchors against 16 to 83 aligned, which is why the results are applied rather than argued about. Yields are very uneven and that is informative in itself: hdcp 150 names 65% -> 86% named gcp 18 + a partial earlier run 74% -> 83% merit 20 69% -> 74% opm 13 58% -> 60% xgmi 13 67% -> 72% powerplay 5 68% -> 69% wspe 4 82% -> 83% HDCP and GCP pay because their corpus builds are close to their reference; powerplay and wspe were already well covered by byte porting, so there was little left for the call graph to add. Corpus overall: 19,377 of 28,947 named, 66.9%, up from 65.8% and from 35.4% when this work started. Tiers now 7813 THUNK, 7503 EXACT, 2261 MNEM, 1424 IDA, 194 DISPATCH, 182 FUZZY. One operational note worth recording: the gcp run aborted partway with "Failed to open database gcp_22.0.0.71_a4b4fff1.elf.i64". Builds processed before the abort had already been saved, so the retry found them named and reported only the remainder -- the 18 figure above understates what gcp actually gained. Clearing the unreadable database and re-running fw/ta_re.py rebuilt it; nothing was lost, but a partial-save interruption is a state this flow does not detect on its own.
* revise hdcp2_session from the full handler union, and audit every struct for ↵user2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | truncation Two parts, both driven by the same recurring failure: a layout derived from a subset of its users comes out wrong. First, a truncation audit of all 24 declared structures, since asd_link_cfg and then hdcp2_session had both been understated. For each module the maximum offset still reached by a raw argument dereference across every build is compared against the declared size. Nothing is over its bound -- OPM's 0x341C sits inside its 0x3D48, HDCP's 0x264 inside 0x26C -- so the request structures hold. Two apparent anomalies are artefacts of the audit script rather than defects: gcp_ctx and merit_table compute as 0xC and 0x0 because the size walker does not account for nested structure arrays. Second, hdcp2_session needed revising for the third instance of the subset problem. Its first version came from the three heaviest rx handlers. Adding the transmit-side handlers to the union exposed two errors in it: 0x2C is read as a DWORD where the rx-only union had left byte padding, and 0x246..0x24C is a byte run that had fallen inside pad235. Both are now fields, and field18 widens to eight dwords covering 0x18..0x34. The merge also had to resolve a genuine overlap -- 0x2D is read as a byte inside the dword at 0x2C -- by giving dword coverage precedence, since a narrow access into a wider field is ordinary rather than evidence of a separate member. Three transmit-side handlers are now typed by name as well: hdcp2_tx_AKE_Init, hdcp2_tx_SKE_Send_Eks and hdcp2_rx_AKE_Send_H_prime, whose offsets are covered by construction because they contributed to the union. hdcp2_tx_RepeaterAuth_Stream_Manage is deliberately left out despite holding 164 dereferences: they go through a local, v10, not a1, so typing a1 would not reach them and claiming otherwise would be wrong. A signature-based rule for HDCP was also built and measured before being kept small: it selects 3 unnamed functions holding 135 dereferences with zero false positives among the named ones, which is honest but minor next to the by-name typing. Measured across fw/decomp: HDCP went from 53 typed field accesses against 1881 raw dereferences to 1365 against 1039. Overall typed field accesses 11436 -> 11971, raw 8733 -> 8481, field share 56.7% -> 58.5%, struct-typed signatures 6.4%.
* derive HDCP2's session context, correcting an intersection test I got wronguser2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | An earlier pass examined HDCP's three heaviest handlers -- hdcp2_rx_AKE_Send_Pairing_Info, hdcp2_rx_RepeaterAuth_ReceiverID_List and hdcp2_rx_AKE_Send_Cert, 590 dereferences between them -- took their INTERSECTION, found only two offsets in common, and recorded them as three unrelated message layouts not worth a shared structure. That was the wrong test, and it is the same mistake that made asd_link_cfg come out at half its real size. Handlers over a shared context each touch a different subset of fields, so the intersection shrinks as handlers are added while the union is what actually describes the structure. They do share 0x04, 0x10, 0x18 and 0x24, and two of them share the byte run at 0x107..0x10E, which the intersection of all three hid. The union across all builds gives 56 observed offsets from 0x04 to 0x268, with each field's width taken from the accesses themselves rather than assumed: dword runs at 0x18..0x28, 0x30..0x34, 0x3C..0x48 and 0x254..0x268, byte runs at 0x4F..0x54, 0x107..0x10E and 0x225..0x234, halfwords at 0x192..0x198, and single flag bytes at 0x2D, 0x38 and 0x24D. Unobserved spans are declared as padding rather than invented as fields, and the declaration is generated programmatically so the run arithmetic closes at 0x26C by construction instead of by hand. CAVEAT recorded in the declaration: 0x26C is the extent observed, not a bound the firmware checks, and the padding runs are genuinely unknown rather than known-unused -- precisely the situation that understated asd_link_cfg, so a richer build may extend this too. Two pieces of plumbing were ASD-specific and are now generic, since HDCP is the second module to need them: load_type_decls already read <TA>_INTERNAL_DECLS, so the structure is declared under that name, and the name-keyed arg1 map is now looked up per module through ARG1_BY_TA instead of being hardcoded to ASD. The gate for that block silently failed to be rewritten on the first attempt and was left as 'if ta == "asd"', which is why the typing did not fire until it was checked rather than assumed. Applied, the handlers read a1->field225, a1->field18, a1->field107 and so on, 3 of 3 functions on most builds. Measured across fw/decomp: typed field accesses 10789 -> 11436, raw pointer-arithmetic dereferences 9323 -> 8733, field share 53.6% -> 56.7%, struct-typed signatures 6.0% -> 6.2%.
* type the TEE runtime's shared formatting state across every module that names ituser2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Rather than pick the next target by hand, the artifacts were measured for where raw dereferences through function ARGUMENTS concentrate. That surfaced something better than another per-module struct: the same offset set (0x08, 0x18, 0x1C, 0x20) appears through a1 in gcp, powerplay, wspe and xgmi, in functions whose names are content-hash clusters -- lib_e53fbaaa_32, lib_7fca7c43_82, lib_8680f995_112, lib_366a1ceb_178. RAS is what identifies it. There the same cluster carries real names -- vsnprintf_fmt_int, vsnprintf_fmt_ll, vsnprintf_pad, vsnprintf_prefix -- and adds 0x10, 0x14 and the tail 0x24 byte, 0x25 byte, 0x26 word, closing the layout at 0x28. It is the runtime's string-formatting state. Declaring ONE shared type here is sound where the powerplay ack-word guess was not, and the difference is worth stating: those "_<hash8>_" names come from ta_libmatch.py, which accepts a cluster only when the function bytes match EXACTLY across modules. The functions are literally the same code, so they necessarily share the structure. The earlier ack-word generalisation rested on a resemblance and was wrong. Applied via the first-parameter rewrite, so each function keeps the rest of its recorded signature: 24 functions in RAS, 6 each in wspe and xgmi, 4 each in gcp and powerplay. HDCP was examined and deliberately NOT given a shared struct. Its three heaviest functions -- hdcp2_rx_AKE_Send_Pairing_Info, hdcp2_rx_RepeaterAuth_ReceiverID_List and hdcp2_rx_AKE_Send_Cert, 590 dereferences between them -- share only two offsets, 0x18 and 0x24. They are distinct HDCP2 message layouts, not one structure, and forcing a common type would have misdescribed all three. Honest limit on the reach of this change: the cluster is only NAMED in five modules. In hdcp, opm, merit, auc, fp, rap, csu, oc, dtm and asd the same helpers exist but are still sub_*, so the fragment-keyed typing cannot attach to them. Running the libnames clustering across those modules first would let the same single declaration cover them too -- the clearest next step. Measured across fw/decomp: typed field accesses 5246 -> 7101, raw pointer-arithmetic dereferences 12701 -> 11849, field share 29.2% -> 37.5%, struct-typed signatures 4.8% -> 5.7%.
* type HDCP's host request, and make the deref locator survive applied prototypesuser2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | HDCP was the worst remaining module by readability: 1742 raw pointer-arithmetic dereferences, zero typed field accesses, zero struct-typed signatures. Its layout comes from TA_InvokeCommandEntryPoint, which takes the host buffer as v7 = *params, switches on *(_DWORD *)v7 across 19 cases, and hands handlers two interior blocks at v7 + 12 and v7 + 5136. The size floor is the dispatch guard itself -- it proceeds only when params[1] > 0x2817 -- and both blocks close exactly against it: 0x0C + 0x1404 = 0x1410 and 0x1410 + 0x1408 = 0x2818. A recorded comment is wrong about that guard and is now flagged in the declaration, because the comment-application added earlier propagates it into the artifacts: it claims the entry "validates that params[0].size <= 0x2817", while the code dispatches when the size EXCEEDS 0x2817. It is a floor, not a ceiling. Three fixes to the locator, each found by its own failure rather than by inspection: * it was reading the switch-bearing function, but 'deref' describes what the ENTRY does with the params pointer, and HDCP's switch is not in the function that does 'v5 = *a4'; deref mode now always reads the entry; * the candidate argument name was hardcoded, so builds where no prototype has been applied call it a4 rather than params and matched nothing. Widening it to every parameter was worse -- it matched an unrelated assignment and typed the wrong local on the reference build -- so only the declared name and the last parameter are tried; * an applied prototype can introduce a cast, so an optional cast is now allowed before the dereference. Applied, the entry reads v7->cmd_id, v7->argA and v7->argB across 25 accesses. It lands on 2 of 7 HDCP builds; the other five do not expose the assignment in a shape the locator recognises and are left untyped rather than forced. Measured across fw/decomp: struct-typed signatures 309/10070 (3.1%) at session start -> 474/10088 (4.7%); typed field accesses 0 -> 3547 against 8254 remaining raw dereferences, a 30.1% field share; modules with a derived request structure 0 -> 7 of 16.
* apply the prototypes, types and comments the pass had been droppinguser2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The decompiled artifacts were missing almost all type information. Three things were available in the tree and only one was being used: * declare_type was never called, so no type definitions were parsed at all; * set_type ran only for the four handover types, because fw/libdumps records just {ea,hash,name,size} and the twelve Windows TA types therefore had no signatures to port; * the recorded comments were read into a variable and discarded without ever being applied. All three are now applied, in dependency order, and counted per build so the result is measurable instead of something you have to notice by reading the output. Across 99 builds: 7727 prototypes (was 1232, all of them ASD), 6068 comments (was zero), 309 struct-typed signatures (was zero). The reason prototypes were failing was NOT the missing types, which is what I assumed when I reported "202 of 1434 rejected" without diagnosing it. Declaring the types first changed the rejection count by nothing. 205 of the 239 failures were prototypes recorded in the bare shape 'int()', with no __fastcall for the name-insertion to latch onto, so they were emitted as the unparsable 'int();'. The name now goes before the first parenthesis whatever the shape, and prototypes land 100%: 1434/1434 on the ASD reference, 99/99 HDCP, 9/9 DTM. Worth recording so nobody repeats the detour: the 43 'local_types' in each handover symbol set are ARM NEON and SVE vector typedefs plus va_list and BOOL -- compiler boilerplate with no domain structs in it. Declaring them is harmless and buys nothing. The only real PSP structs in this tree are the seven hand-written ones in fw/ta_ida.py (gcp_ctx, ta_params, gcp_drm_session, gcp_auth_session, gcp_crypto_session, merit_slot, merit_table), which were sitting unused and are now applied: GCP command handlers finally read as '(struct gcp_ctx *ctx, struct ta_params *params)' rather than '(int, int)'. So the remaining type gap is a data gap, not a pipeline one. Thirteen of the sixteen components have no struct definitions anywhere in the repo to apply, and ASD's 6800 prototypes are almost entirely primitive-typed, which is why it shows only 30 struct-typed signatures against 1439 named functions. Closing that needs structs to be defined by hand, component by component.
* extend the decompilation to ASD and the three handover-symbol typesuser2026-10-01
The twelve Windows TA types take their symbols from fw/libdumps; ASD, HDCP, RAS and DTM have theirs in psp_out/handover, in a different shape that also carries prototypes and comments. This adds that reference path, so every component the corpus holds is covered by one pipeline rather than twelve of fifteen. ASD needed its own ELFs. It is driver-class, not a TA: base 0xD00100, and no TA entry trampoline, so dispatcher-anchored naming does not apply to it at all -- its RPC dispatcher hangs off trampoline+0x1C instead. The base is not guessed; it matches psp_out/handover/elf/asd2.elf, whose LOAD segment contains 1562/1562 of the recorded ASD symbols. The frame discriminator used for the TAs is inapplicable here for the same reason the trampoline is: there is none, and it reports "neither" for all 17 builds. Prototypes are applied where recorded. Without them Hex-Rays guesses argument counts and types, which is most of the distance between readable C and a wall of _DWORD; 1232 of 1434 land on the ASD reference and the rest are rejected by IDA, reported rather than hidden. One more false-positive class in the dispatcher cross-check, found by running it on HDCP: the agreement test assumed a ported name encodes its command number, which is GCP's convention and not HDCP's, so every one of HDCP's 16 commands was reported as conflicting. A name can only contradict the dispatcher if it claims a command itself, so the check now requires the ported name to match <ta>_cmd<hex> before judging it. HDCP drops to zero conflicts while GCP's single real one -- the 0xFE07 handler carrying the inherited name gcp_cmdFE03_CreateAuthenticatedSession -- is still reported. Coverage is honest rather than flattering. ASD lands 162-425 named of ~1100-1400 functions per build, and the dominant limiter is ambiguity, not similarity: 677-908 functions per build share a byte signature with a differently-named reference function and are skipped instead of being assigned arbitrarily. ASD is full of identical small stubs, so this is expected, but it means the ASD figures are a floor and a smarter tie-break would raise them. RAS is weaker still at 13-20 named per build.