企业中的 IBM 和 Node.js: 针对 IBM SDK for Node.js 的核心转储调试
996433090
9年前
<h2>关于本系列</h2> <p>作为 IBM 的开放技术承诺的一部分,IBM 与 Node.js 社区的合作非常紧密。IBM 是 Node.js Foundation 的铂金级赞助商和董事会成员,而且加入了 Node 技术指导委员会。请跟随本系列文章,了解 IBM 对 Node.js 技术和支持性工具的贡献的最新信息。</p> <p>在 Node.js 的开发中,核心转储分析可以帮助调试程序崩溃和内存泄漏。<a href="/misc/goto?guid=4959673252739921655">IBM SDK for Node.js</a> 通过 <a href="/misc/goto?guid=4959673252823908899">IBM Monitoring and Diagnostic Tools — Interactive Diagnostic Data Explorer</a> (IDDE),为 Node.js 应用程序提供了一种新的核心转储分析和调试方法。IDDE 是一个 Eclipse 插件,可通过免费许可获得,它支持来自 IBM SDK for Node.js 发布到的所有平台的核心转储文件,但 Mac OS X 除外。而且这些转储是可移植的;您可以获取一个计算机的核心转储文件并在另一个计算机上的 IDDE 中打开它,甚至是在运行不同的(受支持)OS 的计算机上也可以打开它。</p> <p>可以从 <a href="/misc/goto?guid=4959673252910327149">Eclipse 市场</a> 或使用 <a href="/misc/goto?guid=4959673252990826285">更新站点</a> 安装 IDDE。请继续阅读,了解在开发 Node.js 应用程序的过程中,如何使用 IDDE 调试程序崩溃和内存泄漏。</p> <h2>生成核心转储文件</h2> <p>生成核心转储文件的方法在不同系统中有所不同。Joyent(Node.js 的企业管理人)推荐使用 <code>--abort-on-uncaught-exception</code> 标记运行所有生产级 Node.js 系统。在 UNIX 系统上,还需要设置 <code>ulimit -c unlimited</code> 来支持生成没有大小限制的核心文件。</p> <h2>扩展 IDDE 支持</h2> <p>IDDE 支持探索和调试 IBM Java 核心转储已有多年。随着 2013 年 IBM SDK for Node.js 的发布,支持在 IDDE 1.2 版中添加 Node.js。IDDE 1.2.2 还添加了对 IBM SDK for Node.js 1.2.x 版(等效于 Node 0.12.x)的支持。</p> <p>如果没有抛出异常,则需要使用系统工具(比如 LInux 上的 <code>gcore</code> 或 AIX 上的 <code>gencore</code>)来生成核心文件,或者使用 <code>kill -11</code> 结束进程。在 Windows 7 和更高版本上,可以使用任务管理器来生成核心转储文件:按下 <strong>Ctrl+Alt+Delete</strong> 并选择 <strong>Start Task Manager</strong>。从 Processes 选项卡中,右键单击 Node.js 进程并选择 <strong>Create Dump File</strong>。还可以使用用于 Windows 的免费的 <a href="/misc/goto?guid=4959673253079882965">ProcDump</a> 实用程序。</p> <p>为了生成程序崩溃,我们将使用 Test.js 脚本。这个简单脚本循环 5 次,然后抛出一个错误。</p> <p>清单 1. Test.js</p> <pre> <code class="language-javascript">function main() { var inputObject = { input: ["one", "two", "three", "fifteen", "one hundred"], counter:0, }; for(; inputObject.counter< inputObject.input.length; inputObject.counter++) { if (inputObject.input[inputObject.counter].length > 8) { throw "Input String Too Big"; } } } main();</code></pre> <p>可在 Linux 上像这样运行 test.js:</p> <pre> <code class="language-javascript">$ cd node-v0.12.4-linux-x64/bin/ $ ulimit -c unlimited $ ./node --abort-on-uncaught-exception ../../test.js Uncaught Input String Too Big FROM main (/home/sian/test.js:11:7) Object.<anonymous> (/home/sian/test.js:16:1) Module._compile (module.js:460:26) Object.Module._extensions..js (module.js:478:10) Module.load (module.js:355:32) Function.Module._load (module.js:310:12) Function.Module.runMain (module.js:501:10) startup (node.js:129:16) node.js:814:3 Illegal instruction (core dumped)</code></pre> <h3>在 IDDE 中打开一个核心转储文件</h3> <p>如果 IDDE 已安装,并在核心转储文件所在的相同计算机上运行,您可以直接从磁盘位置打开核心转储文件。右键单击 PD Navigator View(PD 表示问题判定)并选择 <strong>New PD Artifact</strong>。</p> <p>图 1. 选择 New PD Artifact</p> <p><img alt="该图显示了选择 New PD Artifact 的过程。" src="file:///C:/Users/wqm/AppData/Local/Temp/enhtmlclip/Image(5).jpg"><img alt="" src="https://simg.open-open.com/show/d3515b1f71fcb6d83748ae80abdba076.jpg"></p> <p>浏览到您的核心转储文件的位置并单击 <strong>Finish</strong>。</p> <p>要在另一个计算机上打开该转储文件,可以使用常用工具复制该转储文件,然后采用我们刚才介绍的方法在第二个系统上的 IDDE 中打开它。要在将转储文件复制到不同的位置或不同的计算机时实现更好的原生堆栈轨迹,还可以将 Node 可执行程序复制到同一个目录来启用要解析的符号。</p> <p>您现在需要使用 IDDE 编辑器。从您的新核心文件下选择 <strong>Start Investigation</strong> 来打开该编辑器。</p> <p>图 2. 打开 IDDE 编辑器。</p> <p><img alt="该图显示了打开 IDDE 编辑器的过程" src="file:///C:/Users/wqm/AppData/Local/Temp/enhtmlclip/Image(6).jpg"><img alt="" src="https://simg.open-open.com/show/bef138f6d5da1ddb18e43dfff950acac.jpg"></p> <p>大型转储文件可能需要更多的时间来完成加载。</p> <h3>IDDE 命令</h3> <p>它有助于将 IDDE 编辑器视为编辑器与控制台的一种混合体。您可以像使用控制台一样在 IDDE 编辑器中输入并运行命令,但您的进度保存在编辑器中。</p> <p>要运行命令,必须在命令的前面添加 <code>!</code> 并按下 <strong>Ctrl+Enter</strong>。例如,输入 <code>!help</code> 并按下 <strong>Ctrl+Enter</strong> 会输出帮助消息,其中列出了处理转储的其他可用命令。Node 命令集与 Java 集不同,甚至对于不同的 Node 版本(或 Java)可能也会有所不同,因为新的 IDDE 版本中不断添加新的命令。</p> <p>与其他 Eclipse 编辑器一样,<strong>Ctrl+Space</strong> 会显示完成建议,这在 IDDE 编辑器中就是命令列表。</p> <p>图 3. 使用 Ctrl+Space 打开的命令列表</p> <p><img alt="该图显示了使用 Ctrl+Space 打开的命令列表" src="file:///C:/Users/wqm/AppData/Local/Temp/enhtmlclip/Image(7).jpg"><img alt="" src="https://simg.open-open.com/show/ba77521222af7ee15779a0b83d0b7346.jpg"></p> <p>一个首先使用的不错命令是 <code>nodeoverview</code>,它提供了您运行的 Node 版本、依赖关系等的基本摘要。</p> <pre> <code class="language-javascript">!nodeoverview { Node Property Value ---------------------- ------------------------------------------------------------- Node version 0.12.4 Path to executable /home/sian/node-v0.12.4-linux-x64/bin/node Architecture x64 Platform linux Command Line Arguments /home/sian/node-v0.12.4-linux-x64/bin/node /home/sian/test.js Execution Arguments --abort-on-uncaught-exception Process ID 5643 Dependency Version ----------- ---------- http_parser 2.3 node 0.12.4 v8 3.28.71.19 2.2 [...]</code></pre> <p>从前面的控制台输出中可以看出,该程序将会崩溃并抛出了一个 uncaught 异常,所以在这里要做的第一件事就是查看堆栈轨迹。运行 <code>threads</code> 命令查找线程 ID。</p> <pre> <code class="language-javascript">!threads { Thread ID: 0x74c9 (29897) IP: 0x0000000000eb112d !stack 29897 }</code></pre> <p>只有一个线程在运行,所以它一定是 JavaScript 线程。</p> <p>尝试对该线程运行 <code>stack</code> 命令。<code>threads</code> 命令的输出中包含该命令的快捷键,所以您可以将光标放在包含 <code>!stack 29897</code> 的行的末尾处并按下 <strong>Ctrl+Space</strong>。这是输出(为了提高可读性,我们删除了参数列并截断了一些行):</p> <p><a href="/misc/goto?guid=4959673253159148986">点击查看代码清单</a></p> <p>可以看到,崩溃发生在 <code>main</code> 方法中。要返回到代码来查看可能已发生的情况,不需要离开 IDDE;如果运行前面的输出中以粗体显示的命令,IDDE 会显示源代码。</p> <pre> <code class="language-javascript">!jsobject 0x00001519ED09B451 { Object has fast properties Number of descriptors : 5 Name Value More Information --------- ------------------ ------------------------------- length 0x0000079578A0FE19 <EXECUTABLE_ACCESSOR_INFO_TYPE> name 0x0000079578A0FE51 <EXECUTABLE_ACCESSOR_INFO_TYPE> arguments 0x0000079578A0FE89 <EXECUTABLE_ACCESSOR_INFO_TYPE> caller 0x0000079578A0FEC1 <EXECUTABLE_ACCESSOR_INFO_TYPE> prototype 0x0000079578A0FEF9 <EXECUTABLE_ACCESSOR_INFO_TYPE> Object is a function Name: main Source: () { var inputObject = { input: ["one", "two", "three", "fifteen", "one hundred"], counter:0, }; for(; inputObject.counter< inputObject.input.length; inputObject.counter++) { if (inputObject.input[inputObject.counter].length > 8) { throw "Input String Too Big"; } } } }</code></pre> <p>备注:此特性有助于确认您认为在运行的代码确实是实际运行的代码。</p> <p>从代码中可以明显看到,<code>mainObject.counter</code> 肯定已经达到 4,它指向字符串 <code>"one hundred"</code>,该字符串的长度大于 8。可以使用另外两个 IDDE 命令确认事实就是这样:<code>jsfindbyproperty</code> 和 <code>jsobject</code>。<code>jsfindbyproperty</code> 搜索堆中所有具有所提供的名称的属性的对象 — 在本例中为 <code>counter</code>。<code>jsobject</code> 显示了该对象的属性。</p> <pre> <code class="language-javascript">!jsfindbyproperty counter { !jsobject 0x00001519ED09B539 !jsobject 0x00001519ED09B689 } !jsobject 0x00001519ED09B689 { Object has fast properties Number of descriptors : 2 Name Value More Information ------- ------------------ --------------------------------------------- input 0x00001519ED09B6C1 <JS Array[5]> :- !jsobject 0x00001519ed09b6c1 counter 0x0000000400000000 SMI = 4 }</code></pre> <p><code>jsfindbyproperty</code> 生成的十六进制数(例如 <code>0x00003CF51F09B441</code>)显示了该命令找到的对象的内存地址。可以对其中一个十六进制地址运行 <code>jsobject</code> 命令,如果某个 JavaScript 对象位于该地址中,该命令会打印出该对象的属性信息。在本例中,我们可以看到 <code>counter</code> 在此对象上达到了 4。</p> <p>现在使用来自前面的输出的快捷键命令,查看 <code>input</code> 数组。</p> <pre> <code class="language-javascript">!jsobject 0x00001519ed09b6c1 { Array at !hexdump 0x00001519ED09B4E1 Array len = 5 0 : 0x00000CEAAC1A3679, one 1 : 0x00000CEAAC1A3699, two 2 : 0x00000CEAAC1A36B9, three 3 : 0x00000CEAAC1A36D9, fifteen 4 : 0x00000CEAAC1A36F9, one hundred }</code></pre> <p>可以看到 test.js 代码中未通过测试并导致抛出了一个异常的数组元素。</p> <h2>查找内存泄漏</h2> <p>内存泄漏可能是所有程序中的常见问题。IDDE 拥有多个命令来帮助跟踪哪些对象正在占用内存。在本例中,我们首先从我们认为存在内存泄漏的 Node.js 应用程序中获取一个核心转储文件。</p> <p>跟踪泄漏的一种方式是获取两个或更多具有较大时间差的转储文件,比较在两个转储文件之间执行一些命令的输出。</p> <p><code>jsmeminfo</code> 命令有助于直观显示是否有非常大的对象正在使用大量空间,像本例中一样。</p> <pre> <code class="language-javascript">!jsmeminfo { Memory allocator, used: 1423 MB, available: 0 MB Total Heap Objects: 29497 Largest 5 heap objects Type Size (bytes) More information ---------------------- ----------------- ------------ -------------------------- 0x0000000088a6d319 JS_OBJECT_TYPE 1311125 !jsobject 0x0000000088a6d319 0x0000000088aac6d9 FIXED_ARRAY_TYPE 98360 !array 0x000003ff88aac6d9 0x000003ff8abe31b9 ASCII_STRING_TYPE 48176 !string 0x000003ff8abe31b9 0x000003ff8ab34f09 ASCII_STRING_TYPE 48104 !string 0x000003ff8ab34f09 0x000003ff8ab04101 ASCII_STRING_TYPE 40936 !string 0x000003ff8ab04101</code></pre> <p>与 <a href="/misc/goto?guid=4959673253250029786">调试崩溃</a> 小节中一样,对此对象运行 <code>jsobject</code> 命令可以将它与您程序中的一个对象相关联并修复问题。</p> <p>对于程序创建了许多对象但不处理它们的内存问题,<code>jsgroupobjects</code> 可将相同类型的对象分组到一起,并显示程序中有多少对象。<code>jsgroupobjects</code> 还有助于识别 Node.js 缓冲区在何处被用来显示对象的构造函数。(Node 中的缓冲区是分配超出堆范围的内存的一种方式。)在本例中,<code>Buffer</code> 是最常出现的对象:</p> <p><a href="/misc/goto?guid=4959673253326446207">点击查看代码清单</a></p> <p>您可以通过识别缓冲区所分配的外部数组的位置(在以下输出中用粗体显示),在 IDDE 中打印缓冲区的内容。</p> <pre> <code class="language-javascript">print 0x000003ffec004101 { Object at 0x000003FFEC004101 is JSObject Class hierarchy :- |-JSObject | |- kElementsOffset 0x10 (EXTERNAL_UINT8_ARRAY_TYPE, !print 0x000003FFEC004159) | |- kPropertiesOffset 0x8 (FIXED_ARRAY_TYPE, !print <strong>0x000003FF92A04111</strong>) | |-JSReceiver | | |-HeapObject | | | |- kMapOffset 0x0 (MAP_TYPE, !print 0x000003FF8BE1F6E9) | | | |-Object ...</code></pre> <p>获取 <code>kElementsOffset</code> 的地址并提供给 <code>array</code> 数组。</p> <pre> <code class="language-javascript">!array 0x000003FFEC004159 { Array type : EXTERNAL_UINT8_ARRAY_TYPE Len : 10 0 0x48 H 1 0x65 e 2 0x6c l 3 0x6c l 4 0x6f o 5 0x20 6 0x6e n 7 0x6f o 8 0x64 d 9 0x65 e ..</code></pre> <p><code>objtypes</code> 命令对解决内存问题也很有用。它显示了 V8 堆对象类型的计数和内存大小。</p> <h2>完整的命令参考列表</h2> <p>以下是 IDDE 的完整的命令参考列表。这里用斜体显示的命令可用于任何核心转储文件;其他所有命令特定于 Node.js 转储文件:</p> <p><strong><code>array</code></strong></p> <p>显示指定地址上一个固定数组的元素</p> <p><strong><code><em>find</em></code>、<code><em>findall</em></code>、<code><em>findnext</em></code></strong></p> <p>在内存中查找一个字符串</p> <p><strong><code>frame</code></strong></p> <p>显示单个 JavaScript 堆栈帧的详细信息</p> <p><strong><code><em>help</em></code></strong></p> <p>显示命令列表</p> <p><strong><code><em>hexdump</em></code></strong></p> <p>以十六进制和 ASCII 格式输出一个内存区段</p> <p><strong><code>jsfindbyproperty</code></strong></p> <p>找到具有指定属性的 JavaScript 对象</p> <p><strong><code>jsgroupobjects</code></strong></p> <p>列出共享同一个 <code>Map</code> 的 JavaScript 对象组</p> <p><strong><code>jslistobjects</code></strong></p> <p>列出具有指定的 V8 对象类型的堆对象</p> <p><strong><code>jsmeminfo</code></strong></p> <p>显示 JavaScript 内存使用信息,包括 5 个最大的对象</p> <p><strong><code>jsobject</code></strong></p> <p>显示 JavaScript 对象细节</p> <p><strong><code>jsobjectsmatching</code></strong></p> <p>打印与所提供的对象共享同一个 <code>Map</code> 的 JavaScript 对象</p> <p><strong><code>jsstringsearch</code></strong></p> <p>在堆上搜索某个指定的字符串</p> <p><strong><code><em>locate</em></code></strong></p> <p>在内存中搜索某个指定的字符串</p> <p><strong><code>nodeoverview</code></strong></p> <p>显示 Node 概述信息,包括版本</p> <p><strong><code>objtype</code></strong></p> <p>列出所有 V8 对象类型</p> <p><strong><code>objtypes</code></strong></p> <p>显示 V8 实例类型的堆栈使用分类信息</p> <p><strong><code>print</code></strong></p> <p>显示指定的堆栈对象的 C++ 分层结构</p> <p><strong><code><em>ranges</em></code></strong></p> <p>打印可用的内存范围列表</p> <p><strong><code>stack</code></strong></p> <p>显示某个给定线程的 JavaScript 堆栈轨迹</p> <p><strong><code>string</code></strong></p> <p>显示指定地址上的字符串</p> <p><strong><code>threads</code></strong></p> <p>列出所有线程</p> <p> </p> <p>在本教程中,您学习了如何生成 Node.js 核心转储文件,在 IDDE 中打开该转储文件,在 IDDE 编辑器中输入并运行命令,获取可用于您的转储文件的所有命令的列表。您还了解了哪些 IDDE 命令能够以最佳方式帮助您跟踪程序崩溃和内存泄漏的根源。</p> <p>via:http://www.ibm.com/developerworks/cn/web/wa-ibm-node-enterprise-dump-debug-sdk-nodejs-trs/</p>