| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
rename bug
Two passes were spent guessing at this and both guesses were wrong -- a defect in the rename path,
then a collision with an existing symbol. Tracing `assigned` through the fix block and the emit for
the build that actually failed settled it in one run.
The seed reference list is built from fw/libdumps, which still carried the PLACEHOLDER names, while
the processed reference build contributed the SEMANTIC names for the same body hash. build_index
then saw one hash mapping to two distinct names, marked those bodies ambiguous and refused to name
them in any build. The tell was in the per-build line all along: ambiguity in 22.0.0.53 read amb=36
against amb=8 at HEAD, and I had been reading the named count without looking at the column next to
it.
Applying LIB_RENAME to the seed names as well makes the pool consistent. 22.0.0.53 returns to amb=8
and 96 named, and the six held-back names can now be applied: isdigit, vsnprintf_getc,
vsnprintf_emit_char, vsnprintf_emit_char_bounded, vsnprintf_narrow_signed and
vsnprintf_narrow_unsigned.
Net across the corpus, both metrics move the right way at once, which is what the earlier attempts
could not achieve: named 19,304 -> 19,352 (66.7% -> 66.9%), placeholder names 397 -> 235, field
share 67.4% -> 67.5%. GCP alone gains 20 names and drops from 143 placeholders to 77.
The temporary tracing is removed and LIB_RENAME_PENDING is gone, replaced by a note recording that
the cause was an inconsistent seed and that it was found by instrumenting rather than reasoning.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
trade-off entirely
Last pass rejected the placeholder-name upgrade because it cost about 1175 typed field accesses:
the shared-runtime typing matched NAME fragments, so renaming lib_e53fbaaa_32 to vsnprintf_setup
moved the function out from under the rule that typed its argument. The fix identified there is
implemented here.
TEE_FMT_HASHES holds the 71 body hashes of that cluster, derived from every function the fragment
rule used to match across all 99 builds so the switch cannot narrow coverage. Content identity is
what established the cluster in the first place, and it is immune to any renaming.
With that in place the trade disappears and both metrics improve together, which is the point:
gcp share 82.5% -> 83.6% placeholders 389 -> 143
wspe share 91.3% -> 92.6% placeholders 252 -> 133
powerplay share 65.3% -> 70.7% placeholders 186 -> 60
xgmi share 69.1% -> 71.0% placeholders 100 -> 55
LIB_RENAME is therefore enabled, with the measured before/after recorded at the gate rather than
a bare "fixed".
Also corrected from last pass: I reported that the rename "does not work" and ruled out ordering,
mapping contents and key matching. The mechanism was fine. Standalone it returned True on the
very functions I claimed it failed on, and the actual cause of the zero-diff was my own shell --
"| grep -m1" closed the pipe, SIGPIPE killed the run before it wrote, and I read that as the code
having no effect. Two passes were spent on a bug that was in my measurement, not the pipeline.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
DISPATCH names
An earlier pass reported that all 99 artifacts regenerate byte-identically. That was true of that
one run and does not hold in general. A full clean regeneration of all sixteen modules changes 220
files, and the cause is IDA's auto-analysis not being identical across fresh databases: AUC's
function count comes back 39 instead of 40, and decompiled text varies cosmetically
("(int)nullsub_3" against "nullsub_3"). The claim is corrected here rather than left standing.
Semantically the drift is small -- 4 names of 19,353 -- but all four were tier DISPATCH, which
exposed a wrong decision. fw/ta_snapshot_names.py excluded DISPATCH on the grounds that
fw/ta_re.py re-derives it every run. It does, but not deterministically: dispatcher recovery
depends on which function Hex-Rays decompiles cleanly on a given analysis, so xgmi_cmd00,
xgmi_cmd04, xgmi_cmd0B and fp_cmd04 simply did not come back. DISPATCH is now persisted, and the
exclusion reasoning in that file is rewritten to say why.
Persisting it still cannot mask a regression: the dispatcher step runs regardless, so where
recovery works the DISPATCH tier wins and where it fails the restored name appears as tier IDA.
The tier difference is itself the signal that recovery failed on that build.
7817 names were recovered from the committed artifacts into fw/cgnames so nothing derived is left
depending on a database that may not reproduce. XGMI came back whole.
The one name that did NOT come back turned out to be a defect being fixed rather than lost. HEAD
had TWO functions called fp_cmd04 -- 0x401334 from the dispatcher and 0x402b4c from byte porting
-- and the current dispatcher map routes command 4 to 0x402b4c, agreeing with the ported name. So
HEAD carried a duplicate, this run does not, and the allow_overwrite=False guard on the re-apply
is what refused to restore the bad one. Worth keeping: a name count going DOWN was the correct
outcome, and reading it as a regression would have reintroduced the duplicate.
Verified state after the clean regeneration: 16/16 gates PASS, 99 builds, 19,349 of 28,945 named
(66.8%), field share 66.4%, 21 decompile failures.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Seven more request structures derived the same way as the earlier four, which takes the count
from 8 to 15 of 16. The sixteenth is TMZ, and it needs nothing: its invoke entry is literally
'return 0', which confirms from the entry itself the "TMZ is a stub" note in AUDIT-NEW-TAS.md.
Four of the seven close against a size the firmware itself enforces, which is the standard the
earlier xgmi/oc/wspe/hdcp structures set:
csu floor 0x24 cmd at +0, ack at +4, status at +8, body to 0x24
dtm floor 0x50 args at +0x10, 0x10 + 0x40 = 0x50 exactly
merit floor 0x2024 handlers receive the whole buffer
opm floor 0x3D54 independently reproduces the "params_size >= 0x3D54" already recorded
in AUDIT-NEW-TAS-RE.md, from the entry rather than from that note
Three do not enforce a size and say so in their declarations, as asd_req and powerplay_req
already do: auc (ctx bounded exactly at +0x10..+0x298, params extent only observed), fp (the
_DWORD * interior pointers scale, so v4 + 3 is +0x0C and v4 + 68 is +0x110), and rap -- which
is nonetheless the best bounded of the three because its entry zero-fills exactly 24 bytes at
+0x18 and then stores at +0x30, closing the layout at 0x34 without guesswork.
RAP also REFINES the negative recorded against powerplay_req. That commit said POWERPLAY is the
only entry writing a 'command | 0x80000000' ack word; the test looked for it at +4. RAP writes
the same ack at +0x08 and CSU at +0x04. So the ack convention is NOT unique to POWERPLAY -- it
simply sits at a component-specific offset, which remains a reason not to hoist one shared
header, but not the reason first given.
Two more cases of the inherited-prototype defect, both recorded rather than silently applied:
CSU's handlers are called as csu_cmdNN(ctx: *a4, params: v5) where v5 is a4[1], the declared
SIZE and an integer, so the 'struct ta_params *' prototype from fw/pseudo misdescribes it --
the same defect already noted for OC.
Metric correction worth stating: the field-access measure now also counts struct-by-value
accesses, because XGMI applies its struct to a stack buffer and reads v12.cmd_id rather than
v12->cmd_id, which the arrow-only count had been scoring as zero. On the comparable named-only
basis: typed field accesses 0 at session start -> 3742, against 8254 raw pointer-arithmetic
dereferences, a 31.2% field share. Struct-typed signatures 309/10070 (3.1%) -> 487/10088 (4.8%).
Only MERIT and the TMZ stub still show no typed field accesses, MERIT because IDA keeps an
explicit cast at its one dispatch read even though the pointer is typed.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Matching prototypes to functions by NAME reached only 8 of GCP's 24, and the reason turned out
to be a correctness problem rather than a matching one: this tree carries two naming
generations that disagree about which command a handler serves. fw/pseudo calls the handler at
0x402f90 gcp_cmdFE0C_CreateCryptoSession; fw/libdumps calls the same function
gcp_cmdFE0C_CreateAuthenticatedSession. The hardware findings settle it --
FINDING-GCP-ACQUIRE-GATE-BYPASS records 0xFE0C as CreateCryptoSession and
FINDING-GCP-FE0D-REFCOUNT-DESYNC agrees -- so the libdump name is wrong and had propagated
into every artifact generated so far.
Keying on the command id and resolving the address through the dispatcher avoids the
disagreement entirely, because the dispatcher is the only authority on which function serves
which command. GCP goes from 5-9 applied prototypes per build to 14-17 and from 28 struct-typed
signatures to 172, and the handler at 0x402e94 is relabelled from gcp_cmdFE03_* to
gcp_cmdFE07_* on every build -- the single conflict the cross-check had been reporting since it
was built.
Two more request structures are derived and verified the same way as xgmi_req. OC and WSPE both
dispatch with if/else chains rather than a switch, so the switch-based dispatcher recovery finds
nothing for them and their invoke entry has to be used directly; both work on the host buffer in
place rather than staging a copy, so their offsets are host offsets by construction. oc_req
closes at 0x1C (handler blocks at +0x0C and +0x16, the latter running 6 bytes to the end of the
validated size) and wspe_req at 0x14 (handler block at +0x10). Both sizes are the ones the
dispatchers' own size floors enforce. Applied, OC's entry reads 'v5->cmd_id == 2' and the type
propagates outward to 'struct oc_req **a4'.
Recorded against this work rather than hidden: applying a pseudo-file prototype by name can
introduce a WRONG signature. OC's dispatcher calls oc_cmd01(ctx: v5 + 3, params:
(struct ta_params *)7) -- the second argument is the literal 7, so the inherited
'struct ta_params *' misdescribes it, and the cast in the output is the tell. Prototypes should
be checked against what their call sites actually pass before being applied.
Measured from the artifacts rather than from effort: struct-typed signatures 309/10070 (3.1%)
-> 456/10070 (4.5%), modules with a derived request struct 0 -> 3, modules with zero struct-typed
signatures 6 -> 4. The rate stays low for a structural reason worth stating plainly: ASD is 6763
of the 10070 functions and has no recorded type information at all, so work on the small
components cannot move the global figure. ASD is where the remaining value is.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
|
|
twelve types
Byte and mnemonic similarity cannot name a handler whose code diverged, and that failure
landed exactly where it hurts: gcp_cmdFE0C, the function carrying the acquire-gate
refcount bypass, was unnamed on six of eleven GCP builds. The dispatch table does not care
about similarity -- whatever sits in the 0xFE0C arm is the 0xFE0C handler by construction.
So the handler names now come from the image itself: the header's trampoline gives
TA_InvokeCommandEntryPoint, Hex-Rays recovers the switch, and each case arm is attributed
to the function it calls. Parsing the decompiler's switch output beats hand-rolling Thumb
jump-table decoding, and callee tokens resolve through the live function list rather than
by assuming they look like sub_<addr>, so it works whether or not names were applied first.
Validated against the one authoritative map that already existed: all 14 commands this
recovers for GCP 22.0.4.50 match fw/cmdmaps/gcp_ea.json address for address, with none
spurious. The three it misses (0x3, 0xFE08, 0xFE0E) are precisely the arms with no callee
-- inlined into the dispatcher, and recorded in the old map as loc_ labels that were never
function starts. FE0C is now named on all eleven builds.
Two bugs the first attempt had, both caught by its own conflict reporting. Walking three
levels deep from the invoke entry swept up nested sub-command switches inside individual
handlers and invented a GCP command 0x0F that does not exist; the walk is now limited to
the invoke entry and its direct callees, with a depth-2 fallback only when depth 1 finds no
switch at all. And one handler commonly serves several commands -- GCP routes 0xFE03-0xFE07
to one function -- so commands are grouped by handler address before naming or judging
agreement.
The self-port gate was measuring the wrong quantity. Dividing EXACT matches by every
recorded symbol conflated a broken matcher with IDA simply not defining a function at a
recorded address, and failed five sound types on that basis -- TMZ scored 17%. The
denominator is now the reference symbols that are function starts in the reference build;
all twelve types then gate at 100%.
That fix made the gate blind to a different error, so it gained a second check. MERIT ships
two distinct 24.0.4.42 builds, the reference was being chosen by version alone, and the
libdump had been recorded from 444eceee while bc1706f8 was loaded: 24 of 31 symbols landed
on nothing and every MERIT build sat near 30% named. The reference is now pinned by hash
from fw/syms/<ta>.meta.json, and an orphan-symbol rate above 10% is reported as a suspected
build/symbol mismatch. MERIT went to 58-83% named.
62 builds across twelve types, zero decompile failures. The remaining conflicts are real
and worth reading rather than suppressing: GCP's 0xFE07 handler carries the inherited name
gcp_cmdFE03_CreateAuthenticatedSession, because the older name-keyed cmdmap collapsed
0xFE03-0xFE07 onto one label while the dispatcher shows only 0xFE07 reaching that function.
|