Skip to content

Commit e782e94

Browse files
dspl1236claude
andcommitted
dcl: decode the code-8 KWP service table -- request to expression
Code 8 is the bridge from a diagnostic request into the dataflow graph, and it makes the model testable against a real unit for the first time. u32 key ; u8 keylen ; u16 entry_node ; u16 extra (9 bytes) Named by the firmware's mapper at 08904264, which fails with "cannot map service id 0x%X (size %d byte(s))". keylen is the request length in bytes and key is the request itself: keylen 2 with key 0x2106 is the request 21 06. The prefixes are the expected KWP services -- 0x21 readDataByLocalIdentifier (25 entries), 0x31 routineControl (64), 0x33 requestRoutineResults (22), 0x3B writeDataByLocalIdentifier (20), 0x1A readEcuIdentification. entry_node is in the owning graph's block-local numbering, so it resolves through node_map like anything else; exprs lists the expression attributes reachable from it along code-6 edges. 230 services decoded, 25 of them reaching an expression. Two more recognisable idioms fall out, independent of the byte-rotate, which is further evidence the operand binding is right: (v >> 4) + 0x30 high nibble to an ASCII digit (v & 0xF) + 0x30 low nibble to an ASCII digit BCD-to-ASCII formatting, with the constants exactly where they must be. Caveat recorded in the docstring: reachability shows an expression is downstream of a request, not that its value is what the response carries. A match is evidence; a mismatch is inconclusive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 7db2eb4 commit e782e94

1 file changed

Lines changed: 64 additions & 0 deletions

File tree

pcmexplorer/dcl.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,70 @@ def operand_slots(d, gi, nd, _graphs=None):
495495
return flags
496496

497497

498+
def kwp_services(d):
499+
"""``[{key, keylen, entry, attr, exprs}, ...]`` -- the code-8 service table.
500+
501+
Code 8 is the bridge from a diagnostic request to the dataflow graph. The
502+
firmware's mapper at ``08904264`` fails with ``"cannot map service id 0x%X
503+
(size %d byte(s))"``, which names the first two fields::
504+
505+
u32 key ; u8 keylen ; u16 entry_node ; u16 extra (9 bytes)
506+
507+
``keylen`` is the request's length in bytes, and ``key`` is the request
508+
itself: a ``keylen`` of 2 with key ``0x2106`` is the request ``21 06``.
509+
Observed prefixes are the KWP services you would expect -- ``0x21``
510+
readDataByLocalIdentifier, ``0x31`` routineControl, ``0x33``
511+
requestRoutineResults, ``0x3B`` writeDataByLocalIdentifier, ``0x1A``
512+
readEcuIdentification.
513+
514+
``entry`` is a node number in the owning graph's block-local numbering, so
515+
it resolves through :func:`node_map` like any other. ``exprs`` lists the
516+
expression attributes reachable from it along code-6 edges.
517+
518+
This is what makes the model testable against a unit: a request whose
519+
expression has known constants predicts something about the answer. Treat a
520+
match as evidence and a mismatch as inconclusive -- reachability shows the
521+
expression is downstream of the request, not that its value is what the
522+
response carries.
523+
"""
524+
attrs = list(attributes(d))
525+
gs = graphs(d)
526+
527+
expr_at = {}
528+
for j, (o, path, vals) in enumerate(attrs):
529+
for vo, c, b in vals:
530+
if c == 0x23:
531+
t = _string_value(b).decode("latin1")
532+
if t:
533+
expr_at[j] = t
534+
535+
fwd = defaultdict(lambda: defaultdict(set))
536+
for gi, g in gs.items():
537+
for r in g["rows"]:
538+
if r[0] == 6:
539+
fwd[gi][r[2]].add(r[4])
540+
541+
out = []
542+
for gi, (o, path, vals) in enumerate(attrs):
543+
for vo, c, b in vals:
544+
if c != 8 or len(b) < 9:
545+
continue
546+
key, klen = struct.unpack_from("<IB", b, 0)
547+
entry = struct.unpack_from("<H", b, 5)[0]
548+
S = gs[gi]["S"] if gi in gs else 0
549+
seen, stack = {entry}, [entry]
550+
while stack:
551+
for nxt in fwd[gi].get(stack.pop(), ()):
552+
if nxt not in seen:
553+
seen.add(nxt)
554+
stack.append(nxt)
555+
out.append({"key": key, "keylen": klen, "entry": entry,
556+
"attr": entry + S, "graph": gi,
557+
"exprs": sorted((n + S, expr_at[n + S]) for n in seen
558+
if n + S in expr_at)})
559+
return out
560+
561+
498562
def expression_operands(d):
499563
"""``[(attr_index, offset, text, slots), ...]`` -- every expression, bound.
500564

0 commit comments

Comments
 (0)