18 KiB
source/networking/telnet_console.obj — reconstruction
Status: CLOSED — all 17 of 17 symbols byte-exact under the strict comparator
(5/5 functions, 1344/1344 .text bytes, all 11 string literals, .bss).
Was 3/5 functions, 624/1344 bytes when §5 below was written.
Lane: claude/libcmt-stream · Object status: Matching
Toolchain: XDK 3911 CL.Exe 13.00.9254.1 · Flags: the game set, /O2 /Oy- /DDEBUG /Dxbox
Start state: the source was a pure skeleton — symbol-map header and empty
section comments, no code. Every symbol was missing.
| symbol | target | ours | exact |
|---|---|---|---|
_telnet_console_dispose |
80 B / 7 rel | 80 B / 7 rel | yes |
_telnet_console_print |
192 B / 18 rel | 192 B / 18 rel | yes |
_telnet_console_process |
352 B / 33 rel | 352 B / 33 rel | yes |
_bss_00456d00 |
140 B / 0 rel | 140 B / 0 rel | yes |
| all 11 string literals | — | — | yes |
_telnet_console_initialize |
224 B / 18 rel | 224 B / 18 rel | yes (§6) |
_code_001203c0 |
496 B / 30 rel | 496 B / 30 rel | yes (§6) |
1. The globals block
Byte-exact, which confirms the layout. Recovered by reading the relocation addends — every reference into the block resolves to one of four offsets (0x0, 0x4, 0x8, 0x88):
struct telnet_client
{
struct transport_endpoint *endpoint; /* +0x04 */
char buffer[128]; /* +0x08 */
};
struct telnet_console_globals
{
struct transport_endpoint *listening_endpoint; /* +0x00 */
struct telnet_client clients[1]; /* +0x04 */
boolean initialized; /* +0x88 */
};
4 + 132 + 4 = 140. MAXIMUM_TELNET_CLIENTS is 1, proven by the scan loop in
telnet_console_process: it strides add eax,0x84 (132 = one client) from
globals+4 with the bound globals+0x88.
Defined = {0} so the symbol lands in .bss owned by this TU rather than
becoming COMMON — the thread_win32 precedent.
2. Facts recovered from the disassembly
- Port 23 (
mov word ptr [ebp-6],0x17),address_length4. create_transport_endpoint(0x12)— the endpoint type constant._code_001203c0takes its client pointer in ESI. VC7 gives statics with fully-known call sites a custom calling convention; the call site doespush count; push buffer; mov esi, OFFSET globals+4; call. Written as an ordinary third parameter, the compiler reproduces this on its own.- The character switch uses a byte index table at
+0x1e0and a 4-entry jump table at+0x1d0. Decoding both gives the cases exactly:c=4disconnect,c=8backspace,c=10/c=13execute viahs_compile_and_evaluate, other values in 4..13 skip. Printable (isalnum/ispunct/space) appends. read_endpointinto a 32-byte stack buffer, with the returned countpushed before the branch so it serves as the argument to eithercode_001203c0ortransport_error_to_string.
3. Source-shape findings
Five real corrections, each verified by the gate:
printtests its write result once, at a shared join. January writes"\r\n", then the string, then optionally the pending input buffer, and tests once at a join reached both by fall-through and by the empty-buffer branch. A short-circuita>0 && b>0 && (!buf[0] || c>0)adds a branch and shifts every later offset. Using aresultlocal and testing at the join made it exact.processindexes the globals directly, it does not cache aclientpointer. The three missing relocations were all references tobss_00456d00+0x4/+0x8/+0x88. Rewriting the second half to usebss_00456d00.clients[0]throughout recovered them.process's greeting branch is written failure-first. January's layout isjg <assign>falling through into the delete; writingif (write>0) {assign} else {delete}produces the opposite layout. Inverting toif (write<=0) {delete} else {assign}made the function exact.initializeusesif (endpoint) {...} else {error}, not an early return. The positive form places all three error blocks out of line at the end, in reverse order, with the bind and listen paths tail-merging into a shareddelete+ NULL + return (jmp 0x97). The early-return form emits them inline.- The switch case order is
10/13,8,4. MSVC lays case bodies out in source order, and January's are execute → backspace → goodbye. Writing them in ascending value order reverses the layout: this alone took_code_001203c0from 219 to 120 differing instructions.
Also: the transport_address is zeroed inline in an inner scope.
csmemset(&address, …) emits a real call (+2 relocations, 208 B); declaring
= {0} at function scope emits the zeroing before the globals csmemset. An
inner block opened after the endpoint check puts it where January has it.
4. transport.h — union member order
struct transport_address_data's union originally listed byte bytes[16] first.
With that order, struct transport_address address = {0} makes MSVC initialise
the union's first member — so it emits a byte store for bytes[0] followed by a
misaligned blob fill ([ebp-0x17], [ebp-0x13], …). January's fill is six clean
4-aligned dword stores.
Reordering the union to put unsigned long long_words[4] first restores the
aligned fill and matches January. The union order is not observable except
through codegen. The admission lane forced clean rebuilds of both current exact
consumers: all 5 transport_address.obj functions and all 100
ui_widget_event_handler_functions.obj functions remained strict-exact.
5. The residual as it was diagnosed — SUPERSEDED BY §6
This section's class verdict was wrong. Both functions were adjudicated
here as register-allocation ties; both closed from source (§6). The measured
negatives below remain valid negatives and are kept. Two specific claims are
corrected: (a) the two functions were not "identical modulo register naming"
— _code_001203c0 had two push 0 where January has push eax (a 2-byte
code-size difference hidden by section alignment padding, which also displaced
the jump table), and (b) the register assignment was not a tie — it was
downstream of a live range the source failed to create, exactly the house-rules
§6 pattern "read the live ranges, never chase the registers".
_code_001203c0
January puts index in EDI and size in EBX; we do the reverse. A mechanical
EBX↔EDI rename of our disassembly aligns 132 of 172 instructions, and the
residue shows it is not a clean swap: at the execute case both builds use EDI
for the buffer pointer (lea edi,[esi+4]). January therefore reuses EDI for
both index and the buffer pointer, spilling index and reloading size from
[ebp+0xc] each iteration; we keep the two in separate registers. Same
instruction sequence, different live-range packing.
Verified inert against it:
| attempt | ndiff |
|---|---|
| baseline | 120 |
index declared before result |
120 |
length at function scope |
120 |
character and length both at function scope |
120 |
named client_buffer local in the append path |
119 |
cached limit local for the bound |
119 |
buffer[index] directly, no pointer variable |
119 |
size>index |
119 |
index<size && result (result tested second) |
275 |
while form |
198 |
The one-instruction wins are metric noise, not progress: size>index emits
cmp ebx,edi; jle where January has cmp edi,ebx; jge, so index<size is the
faithful form and is what the source keeps.
_telnet_console_initialize
January materialises 0 into two registers — ECX for the three address
dwords, EDX for [ebp-8] and [ebp-4] — because ECX is consumed by
lea ecx,[ebp-0x18] for the &address push. We keep one zero register and use
EDX for the lea, so the three error-path reloads then rotate EDX/EAX/ECX
against our EAX/ECX/EDX. The extra xor edx,edx is exactly the two bytes our
version spends on trailing padding — both functions are 224 bytes.
Verified inert or worse: field assignment order (port first), explicit
address_type = 0 (133), address_type = 0 before the other fields (133),
declaring address at function scope (115).
Class (as adjudicated then — retracted in §6)
Same as the woutput residual — a register-allocation / live-range packing
decision with no source or flag lever found. Not credited; the object stays
NonMatching.
6. Close-out — both "register ties" fell to source levers (2026-08-10)
Diagnosed by a relocation-masked lockstep walk of both residuals against the target (register- and slot-wildcarded tiers), which separated the genuine divergences from the permutation noise the earlier ndiff metric mixed together.
_code_001203c0: January passes the write result to the error report
At both write-failure sites January has push eax where we had push 0:
the original passes the failed write_endpoint result to
transport_error_to_string, not a literal (short)0 — the same idiom
telnet_console_process already proved with its (short)count. Spelled
length = write_endpoint(client->endpoint, character, 1);
if (length<=0)
{
error(2, "failed to write to telnet client ('%s')",
transport_error_to_string((short)length));
...
}
(and likewise at the overflow-message site, reusing the length local), the
function became byte-exact in one step. The named result's live range now
crosses into the error arm, and that alone flipped the entire EBX/EDI
assignment and the prologue push schedule into January's shape — the "120
differing instructions" were all downstream of these two calls. The tell in
the disassembly was 1-byte push eax (0x50) vs 2-byte push 0 (0x6A 00)
with every later branch target off by one, and section size still 496 because
alignment padding absorbed the difference. Size-exactness can hide a real
code-size delta; diff the padding tail too.
(The earlier §5 claim that January "reuses EDI for both index and the buffer
pointer" was an artifact of the mechanical rename: January converts index
to the character pointer in place (add edi, eax) exactly as we do, and the
append-path buffer pointer lands in EBX. The execute path's
lea edi,[esi+4] matched byte-for-byte on both sides while meaning different
things — jan's EDI held the dead character pointer, ours the dead size copy.)
_telnet_console_initialize: {{0}}, not {0}
January's zero-fill of the transport_address local splits exactly at the
member boundary: [-0x18] immediate + [-0x14..-0xc] from ECX cover the
16-byte struct transport_address_data address member, then
lea ecx,[ebp-0x18] consumes ECX, then [-8]/[-4] (the trailing
address_length/port/address_type scalars) come from a freshly
materialised EDX. Two zero sources for one initializer means two fill
operations in the IR. VC7 groups a = {…} zero-fill by brace level:
= {0}→ the0initialises the deepest first scalar (long_words[0], the immediate store) and the rest of the whole struct is one fill — one zero register (ours, wrong);= {{0}}→ member 0's fill and the implicit zeroing of the remaining members are separate fills — the allocator can hand the addressleathe first fill's dead register and materialise a second zero for the tail (January, exact).
One character of source. The three error-path register rotations §5 describes were pure cascade from this and closed with it.
Verification
Whole unit rebuilt via ninja (CL path patched per the known checkout issue)
and every one of the 17 target sections verified with the strict comparator
(section_infos_equal): 17/17 EXACT, 5/5 functions, 1344/1344 .text
bytes. configure.py progress validation (parked/semantic/ownership gates)
passes. Status flipped NonMatching → Matching.
The §5 compiler A/B result (all three local VC7 suites byte-identical) stands — correctly, as it turns out: the residual was never compiler variance.
House-rule audit
Every parameter remains one-per-line, both no-argument void parameter lists
use the project form, and every void function ends in an explicit return;.
The source contains no assembly, volatile codegen forcing, alignment trick,
compiler-flag override, or raw typed tag_get/object_get cast. The early
returns in telnet_console_print, telnet_console_process, and
code_001203c0 are measured control-flow requirements of the exact January
sections; rewriting them into one exit changes the emitted branches and is the
documented exception to the preferred single-return rule.
Note:
ninjacannot launchxbox/bin/vc7/CL.Exein this checkout (CreateProcess failed); pre-existing, reproduces incampaign-integration. Verification used CL directly with the exact cflags ninja generates.
7. Current-campaign independent admission audit (2026-08-13)
The completed reconstruction was transplanted onto campaign tip 738deff8
and rebuilt from source with XDK 3911 CL.Exe 13.00.9254.1 and the unchanged
/O2 /Oy- /DDEBUG /Dxbox flags. This audit did not trust the earlier report:
it enumerated every external, target-owned COFF section and compared each one
again with tools/coff_compare.py::section_infos_equal.
All 17/17 target-owned sections are strict-equal: the five functions
(1344 padded code bytes, 106 relocations), all eleven string COMDATs (473
bytes, no relocations), and _bss_00456d00 (140 bytes, no relocations). The
function measurements from this rebuild are:
| function | padded bytes | relocations | normalized SHA-256 |
|---|---|---|---|
_telnet_console_initialize |
224 | 18 | 2b614d5f0126ba4234219a23adcb5e98ec7e39b2feab42b900006feab1ed1748 |
_telnet_console_dispose |
80 | 7 | bb37c5b76e11f4f7badf0fbdf0de442d5af90930871d0645d3412b483e279a2a |
_telnet_console_print |
192 | 18 | 5509b44b5d0c4ffbcda8b091d14df2ed82d3dd1748b0e7a6757edd28b4e93657 |
_code_001203c0 |
496 | 30 | 320e28baa4ff4a0f80d2a37ce9c2107d66c72e2bf5c321c8f205d813d25fc398 |
_telnet_console_process |
352 | 33 | 6697a49e8c92a112af1c2dce74fa24f03bcbf0c7c6c014844f69c4ae25eb6715 |
The ordinary objdiff reporter still scores _code_001203c0 at 89.14286%
because its switch-table/local-label ownership is encoded differently. The
hardened comparator proves all 496 padded bytes and all 30 resolved relocation
destinations equal, so the current fail-closed semantic ledger records that
specific false negative. tools.audit_semantic_matches independently accepts
it from semantic-coff evidence; after that entry, ninja progress credits
the complete object instead of revoking it.
The only shared-header change reorders the three equivalent views in
transport_address_data so the dword view is the aggregate's first member.
The complete current-tree include census found exactly one other C consumer,
transport_address.c. It was force-rebuilt, and all 42/42 of its owned
sections remained strict-equal (five functions, all string COMDATs, and its
256-byte BSS). This closes the change's measured regression radius.
Plainly: the code, its strings, its zero-initialized globals, and every call or data reference all describe the same January object. The one progress-report disagreement is a label-spelling artifact, not a byte or destination mismatch.
Current house/Berth audit
- every parameter is on its own line, and every no-argument
(void)is on its own line; - every void function has an explicit final
return;; - the few early returns are the measured exceptions required to reproduce the original branch layout and are documented above;
- no inline assembly,
volatile,__forceinline, undefined behavior, byte-forcing, alignment pragma, flag override, raw object/tag cast, or magic field-offset access was introduced; - recovered numeric values are named protocol/domain constants, and recovered storage is represented by readable structures rather than offset arithmetic.
Final current-campaign gates
ninja halobetacache_build: passed after rebuilding all 465 base objects.ninja progress: creditedsource/networking/telnet_consoleas the 257th complete Halo unit; halobetacache is 20.26% object-matched (257/468), with 358614/1770166 code bytes and 3190/7574 functions credited.tools.regression_gate snapshot/check --unit source/networking/telnet_console: passed with fivestill_exact, no changed nonexact functions, no failures, and no warnings.python -m unittest discover -s tools -p 'test_*.py' -v: 173/173 passed.
Compiler A/B — all three local VC7 suites produce identical bytes
Prompted by the units.obj compiler-provenance work
(units-campaign-current-20260802/docs/object_matching_logs/units_obj_compiler_provenance.md
and units_obj_compiler_qfe_hunt.md), the local worktrees were re-censused for
distinct compiler binaries. Three distinct CL.Exe images exist, not one:
| suite | copies | CL.Exe SHA-256 (16) |
note |
|---|---|---|---|
| 13.00.9254.1 | 830 | 483e00c47bb08d69 |
project standard |
| 13.00.9210 | 4 | 38955691c987c64d |
genuinely different C1/C2 back end |
| vsnet-beta2-rc4-9254 | 1 | cd6b42c2e4f14140 |
reports 9254 but is a distinct image |
This object was rebuilt with all three complete suites (each with its matching
C1.dll / C1xx.dll / C2.dll), same flags, same source. All three produce
byte-identical output, with the identical residual.
That is a stronger park than "only one compiler is available": the residual is now shown not to be compiler-version variance at all. It also corrects an earlier claim in this log that only a single CL binary existed locally — that was wrong, and the conclusion it supported (a QFE-level codegen difference) is not supported by this test.