feat(protocol): 位痕迹按 codec 槽位指纹定位字段
This commit is contained in:
1 parent
97917478a4
commit
e0542b2681
2 files changed
+69
-77
No files matched your search
@@ -1,18 +1,20 @@
|
|||||||
# analyze_nullmap_trace.py - 把客户端采到的「可选 + 列表」位痕迹映射回 模块::类.方法
|
# analyze_nullmap_trace.py - 把客户端采到的「可选 + 列表」位痕迹定位到具体字段
|
||||||
#
|
#
|
||||||
# 数据来源:镜像站 module_24635.js 的 Ht.t7h 补丁(可选读器)。它只对「内层读器是列表」的
|
# 数据来源:镜像站 module_24635.js 的 Ht.t7h 补丁(可选读器)。它只对「内层读器是列表」的可选字段
|
||||||
# 可选字段记录一条 {bit, inner, stack},节流 POST 到 serve.js 的 /__dump__/nullmap_trace.json。
|
# 记录 {hid, bit, inner, elem, elemKeys, stack},节流 POST 到 serve.js 的 /__dump__/nullmap_trace.json。
|
||||||
# 目的:确认 opt<list<…>> 在真实数据里到底占不占 nullmap 位(例如 ParticleEffectItem.colors)。
|
# 目的:确认 opt<list<…>> 在真实数据里到底占不占 nullmap 位(例如 ParticleEffectItem.colors)。
|
||||||
#
|
#
|
||||||
# 映射方法:Node 的 stack 里带 `module_XXXX.js:行:列`;用 src 里 `(<类>).<方法> = function`
|
# 定位方法(不依赖 stack,因为镜像站用 eval 加载模块,栈里没有模块名):
|
||||||
# 的起始行建「行号 → 类.方法」索引,取不晚于该行的最近一条。
|
# 1. 每个可选包装器实例在客户端被分配稳定 id(hid),同一字段的所有读取共享同一个 hid;
|
||||||
|
# 2. elemKeys = 内层列表元素 codec 的槽位名集合。槽位名全局唯一,
|
||||||
|
# 用它去 names/codec_fields.json 的 Slots 里反查,能唯一确定「元素是哪个 codec」;
|
||||||
|
# 3. 再找哪些字段的类型是 opt<list<该模型>>,若唯一即定位成功。
|
||||||
#
|
#
|
||||||
# 用法: python tools/analyze_nullmap_trace.py [--names names] [--src src]
|
# 用法: python tools/analyze_nullmap_trace.py [--names names]
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
from collections import Counter
|
from collections import Counter, defaultdict
|
||||||
|
|
||||||
Root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
Root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
@@ -26,42 +28,33 @@ def Arg(Name, Def):
|
|||||||
|
|
||||||
|
|
||||||
NamesDir = os.path.abspath(Arg('names', os.path.join(Root, 'names')))
|
NamesDir = os.path.abspath(Arg('names', os.path.join(Root, 'names')))
|
||||||
SrcDir = os.path.abspath(Arg('src', os.path.join(Root, 'src')))
|
|
||||||
TracePath = os.path.join(NamesDir, 'nullmap_trace.json')
|
TracePath = os.path.join(NamesDir, 'nullmap_trace.json')
|
||||||
|
FieldsPath = os.path.join(NamesDir, 'codec_fields.json')
|
||||||
FrameRe = re.compile(r'module_(\d+)\.js:(\d+):(\d+)')
|
|
||||||
AssignRe = re.compile(r'\(([\w$]+)\)\.(t7h|s7h|u7h)\s*=\s*function')
|
|
||||||
|
|
||||||
|
|
||||||
def LoadLineMap():
|
def LoadSlots():
|
||||||
"""module 号 → [(起始行, '类.方法')],按行号升序。"""
|
"""槽位集合 → [(codec 键, 模型名)],用于反查元素 codec。"""
|
||||||
Out = {}
|
with open(FieldsPath, 'r', encoding='utf-8') as F:
|
||||||
for Name in os.listdir(SrcDir):
|
Codecs = json.load(F).get('Codecs') or {}
|
||||||
M = re.match(r'module_(\d+)\.js$', Name)
|
Out = defaultdict(list)
|
||||||
if not M:
|
for Key, C in Codecs.items():
|
||||||
continue
|
Slots = tuple(sorted(S.get('Slot') for S in (C.get('Slots') or [])))
|
||||||
Mod = str(int(M.group(1)))
|
if Slots:
|
||||||
Path = os.path.join(SrcDir, Name)
|
Out[Slots].append((Key, C.get('ModelName') or C.get('ModelClassLocal') or '?'))
|
||||||
Rows = []
|
return Out, Codecs
|
||||||
with open(Path, 'r', encoding='utf-8', errors='replace') as F:
|
|
||||||
for LineNo, Line in enumerate(F, 1):
|
|
||||||
for A in AssignRe.finditer(Line):
|
def FindOwners(Codecs, ModelName):
|
||||||
Rows.append((LineNo, A.group(1) + '.' + A.group(2)))
|
"""类型为 opt<list<ModelName>> 的 (codec 键, 字段名)。"""
|
||||||
Rows.sort()
|
Out = []
|
||||||
Out[Mod] = Rows
|
Needle = 'opt<list<' + ModelName + '>>'
|
||||||
|
for Key, C in Codecs.items():
|
||||||
|
for F in C.get('Fields') or []:
|
||||||
|
if F.get('Type') == Needle:
|
||||||
|
Out.append((Key, F.get('Name')))
|
||||||
return Out
|
return Out
|
||||||
|
|
||||||
|
|
||||||
def Locate(LineMap, Mod, Line):
|
|
||||||
Rows = LineMap.get(Mod) or []
|
|
||||||
Best = None
|
|
||||||
for Start, Name in Rows:
|
|
||||||
if Start > Line:
|
|
||||||
break
|
|
||||||
Best = Name
|
|
||||||
return Best or '?'
|
|
||||||
|
|
||||||
|
|
||||||
def Main():
|
def Main():
|
||||||
if not os.path.exists(TracePath):
|
if not os.path.exists(TracePath):
|
||||||
print('缺少 ' + TracePath)
|
print('缺少 ' + TracePath)
|
||||||
@@ -72,46 +65,29 @@ def Main():
|
|||||||
Rows = Data.get('trace') or []
|
Rows = Data.get('trace') or []
|
||||||
print('位痕迹:%d 条(采集于 %s)' % (len(Rows), Data.get('at', '?')))
|
print('位痕迹:%d 条(采集于 %s)' % (len(Rows), Data.get('at', '?')))
|
||||||
|
|
||||||
LineMap = LoadLineMap()
|
SlotIndex, Codecs = LoadSlots()
|
||||||
ByCaller = Counter()
|
ByHid = defaultdict(list)
|
||||||
ByBit = Counter()
|
|
||||||
Samples = {}
|
|
||||||
Ordered = []
|
|
||||||
for R in Rows:
|
for R in Rows:
|
||||||
Bit = R.get('bit')
|
ByHid[R.get('hid')].append(R)
|
||||||
Stack = R.get('stack') or ''
|
|
||||||
Caller = '?'
|
|
||||||
CallerMod = '?'
|
|
||||||
# 取第一个能定位的栈帧(跳过 Ht.t7h 自己)
|
|
||||||
for M in FrameRe.finditer(Stack):
|
|
||||||
Mod, Line = M.group(1), int(M.group(2))
|
|
||||||
Name = Locate(LineMap, str(int(Mod)), Line)
|
|
||||||
if Name.startswith('Ht.'):
|
|
||||||
continue
|
|
||||||
Caller = Name
|
|
||||||
CallerMod = 'module_' + str(int(Mod))
|
|
||||||
break
|
|
||||||
Key = CallerMod + '::' + Caller
|
|
||||||
ByCaller[(Key, R.get('inner'))] += 1
|
|
||||||
ByBit[(Key, R.get('inner'), Bit)] += 1
|
|
||||||
if Key not in Samples:
|
|
||||||
Samples[Key] = Stack
|
|
||||||
Ordered.append((Key, R.get('inner'), Bit))
|
|
||||||
|
|
||||||
print(' 调用者分布(按次数):')
|
print(' 可选列表字段 %d 个(按 hid 分组):' % len(ByHid))
|
||||||
for (Key, Inner), N in ByCaller.most_common(20):
|
for Hid in sorted(ByHid, key=lambda H: (H is None, H)):
|
||||||
Ones = ByBit.get((Key, Inner, 1), 0)
|
Group = ByHid[Hid]
|
||||||
Zeros = ByBit.get((Key, Inner, 0), 0)
|
ElemKeys = tuple(sorted(Group[0].get('elemKeys') or []))
|
||||||
print(' %6d %-40s inner=%-6s bit=1:%d bit=0:%d' % (N, Key, Inner, Ones, Zeros))
|
Elems = SlotIndex.get(ElemKeys) or []
|
||||||
|
ElemName = Elems[0][1] if len(Elems) == 1 else Group[0].get('elem')
|
||||||
# 关注 ParticleEffectItem(85183::Ll):把它的位序列按出现顺序打出来
|
Owners = FindOwners(Codecs, ElemName) if ElemName else []
|
||||||
Focus = [(K, B) for K, I, B in Ordered if K == 'module_85183::Ll.t7h']
|
Bits = Counter(R.get('bit') for R in Group)
|
||||||
if Focus:
|
Seq = ''.join(str(R.get('bit')) for R in Group[:40])
|
||||||
print(' ParticleEffectItem(module_85183::Ll)位序列,前 60 条:')
|
Where = (Owners[0][0] + ' . ' + str(Owners[0][1])) if len(Owners) == 1 else '?'
|
||||||
print(' ' + ''.join(str(B) for _, B in Focus[:60]))
|
print(' hid=%-4s 次数=%-4d bit0=%-4d bit1=%-4d 元素=%-22s 字段=%s'
|
||||||
print(' 样例栈:')
|
% (Hid, len(Group), Bits.get(0, 0), Bits.get(1, 0), ElemName or '?', Where))
|
||||||
for Key, S in list(Samples.items())[:3]:
|
if len(Seq) >= 4:
|
||||||
print(' [' + Key + '] ' + S[:160])
|
print(' 位序列:' + Seq)
|
||||||
|
if len(Elems) > 1:
|
||||||
|
print(' 元素多解:' + ', '.join(K + '(' + N + ')' for K, N in Elems[:5]))
|
||||||
|
if len(Owners) > 1:
|
||||||
|
print(' 字段多解:' + ', '.join(K + '.' + str(N) for K, N in Owners[:5]))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,23 @@
|
|||||||
try {
|
try {
|
||||||
St = String(new Error().stack || "").split("\n").slice(1, 4).join(" | ");
|
St = String(new Error().stack || "").split("\n").slice(1, 4).join(" | ");
|
||||||
} catch (E3) {}
|
} catch (E3) {}
|
||||||
T.push({ bit: B0, inner: Inner.constructor && Inner.constructor.name, stack: St });
|
// hid:给每个可选包装器实例分配稳定 id(同一字段的多次读取共享 id);
|
||||||
|
// elem/elemKeys:内层列表的元素 codec 类名与槽位名——槽位名全局唯一,可离线唯一定位字段。
|
||||||
|
var Elem = Inner.z7h_1;
|
||||||
|
if (!Inner.__hornetHid) {
|
||||||
|
Inner.__hornetHid = T.nextId || 1;
|
||||||
|
T.nextId = Inner.__hornetHid + 1;
|
||||||
|
}
|
||||||
|
var ElemName = '';
|
||||||
|
var ElemKeys = [];
|
||||||
|
try {
|
||||||
|
ElemName = (Elem && Elem.constructor && Elem.constructor.name) || '';
|
||||||
|
ElemKeys = Elem ? Object.keys(Elem) : [];
|
||||||
|
} catch (E5) {}
|
||||||
|
T.push({
|
||||||
|
hid: Inner.__hornetHid, bit: B0, inner: Inner.constructor && Inner.constructor.name,
|
||||||
|
elem: ElemName, elemKeys: ElemKeys, stack: St,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (E4) {}
|
} catch (E4) {}
|
||||||
return B0 ? null : Inner.t7h(t);
|
return B0 ? null : Inner.t7h(t);
|
||||||
|
|||||||
Reference in new issue
Block a user