问题:
我有来自服务器的日志文件,其中包含抛出错误的调用堆栈,触发了此日志文件的创建。服务器应用程序使用nodejs用typescript编写,但是会将其转换为javascript,并且javascript代码会被谷歌闭包编译器混淆。现在我的callstack很难解释,我试图通过对js代码进行反混淆来改变,使用闭包编译器创建的源映射,然后再次使用源映射," untranspile" js callstack到打字稿callstack。
我的限制
我可以访问源映射,源代码(ts和js)以及混淆代码,但我无法更改代码本身,因此我坚持使用当前的callstacks。我还可以访问所有选项以及混淆代码的代码/工具,因此我可能会将一些所需信息存储在一个文件中(源图中未显示的信息),如其他映射。
想法和尝试
第一次尝试是简单地解释源映射,并使用该信息反向混淆callstack(反混淆是困难的部分),但是在我试图理解cc创建源映射的方式后,我遇到了一些问题: cc并不只是将一个名称映射到另一个名称,因为他多次重复使用某些名称(例如a,f或这些"名称")。因此,可能存在具有一些匿名函数或嵌套函数的函数,其中名称f被多次使用,但由于范围的原因,在每个上下文中具有不同的含义。
下一个想法只是信任callstack。要理解我的意思,你必须理解(如果我理解正确的话)cc如何创建和管理映射:
return method.call(thisObj, args[0], args[1]);
这一行对此进行了混淆(我留下了空白以更好地理解索引):
return f.call(d, a[0], a[1]);
现在为这一行创建了几个映射,单个映射如下所示:
export interface MappingItem {
source: string;
generatedLine: number;
generatedColumn: number;
originalLine: number;
originalColumn: number;
name: string | null;
}
此映射实例中唯一重要的信息是列和名称。某些映射包含其他名称不包含的名称。不包含名称的那些用于围绕具有名称的那些构建某种范围,以便找出名称/替换名称的开始和结束位置(索引)。
使用上述两个陈述的逻辑示例:
Generated │ Original │ Name │ Scope
0 │ 16 │ null │ ━━━┓
15 │ 23 │ method │ x │
16 │ 23 │ call │ x │
21 │ 23 │ null │ ━┓ │
22 │ 35 │ thisObject │ x│ │
23 │ 23 │ null │ ━┛ │
25 │ 44 │ args │ x │
26 │ 44 │ null │ ━┓ │
27 │ 49 │ null │ ?│ │
28 │ 44 │ null │ ━┛ │
29 │ 23 │ null │ ━━┓│
31 │ 53 │ args │ x ││
32 │ 53 │ null │ ━┓││
33 │ 58 │ null │ ?│││
34 │ 53 │ null │ ━┛││
35 │ 23 │ null │ ━━┛│
36 │ 16 │ null │ ━━━┛
使用此callstack,我想解决来自applications.js的所有问题。所有已编译和混淆的js代码都在那里。休息是无关紧要的:
at do2 (c:\Users\me\test\js\test.js:14:11)
at do1 (c:\Users\me\test\js\test.js:11:5)
at Server.<anonymous> (c:\Users\me\test\js\test.js:6:5)
at f (c:\Users\me\build\transpiled\obfuscated\application.js:235:18)
at Object.a.safeInvoke (c:\Users\me\build\transpiled\obfuscated\application.js:285:27)
at Server.g.getWrappedListener (c:\Users\me\build\transpiled\obfuscated\application.js:3313:17)
at emitTwo (events.js:106:13)
at Server.emit (events.js:191:7)
at HTTPParser.parserOnIncoming [as onIncoming] (_http_server.js:546:12)
at HTTPParser.parserOnHeadersComplete (_http_common.js:99:23)
现在使用源图中的信息很容易获得原始行和列,但名称不是。我试图首先尝试没有代码中的信息,准备好前一个位置(行和列)以推断下一行的名称。
所以,如果我想解决f,我会看看它被调用的地方(285:18),然后在源地图中查找,我会找到它的名字。但是对于这个过程,我始终需要知道它的调用位置。现在就是问题所在。因为如果函数已经存储在变量中,或者是匿名的或类似的东西,我就有问题。
f.call(d, a[0], a[1]);
我也注意到,在这种情况下调用某些方法并不会在callstack中列出,这是另一个问题。所以现在我可以至少解析名称,如果我能确定我是否知道他们被叫到哪里以及他们是否在callstack中。但我不是这样的半解决方案。
我的第二次尝试是使用我发现的有前途的javascript模块:stacktrace-js
这个模块是为浏览器js而制作的,并且打字稿文件/打字很差,虽然它是用打字稿写的。这也导致在本地不支持读取文件,因为它们总是被xmlhttprequests调用。该部分有一些变通方法,但模块非常复杂(可能是由于被转换的代码),还有其他部分也不支持我,使用本地文件。重写/改变它以适当地使用nodejs就太多了....
您是否知道使用该模块更简洁的方法?我还想过使用源代码解析器获得更多上下文来支持源映射(如果是那些恶意的.call方法)。也许我可以编写自己的源代码解析器,如果在解析代码和解释代码时我必须注意的所有异常的文档...... 也许还有另一种方式,我目前正在监督......
答案 0 :(得分:2)
首先,确保您拥有完全合成的源地图。您提到了两个生成源映射的工具,typescript编译器和闭包编译器。闭包编译器提供了输入源映射吗?如果是这样,它将输出提取原始文件的源地图。如果没有,你将有两倍的工作量。事后可以使用source-map package组成源地图。
从原始问题中可以清楚地看出,您并未完全了解源地图。例如,没有name
的条目通常是语言语义。例如:
document.createElement('div')
源地图可以包含document
和createElement
的映射,也可以包含.
和(
字符的映射。这里没有涉及范围。
有多种可视化工具可以在这里提供帮助。我最喜欢的一些是:
这里的想法是您在工具中加载源地图和源,然后单击以查看事物的映射方式。它需要一点点,但你应该能够在原始源中找到与堆栈跟踪中的行/列信息匹配的行和列。
像https://sentry.io/这样的工具存在是有原因的。它会自动为您调用一个调用堆栈。