原文

如何解释 Erlang Crash Dumps

本节介绍 Erlang 运行时系统在异常退出时生成的 erl_crash.dump 文件

注意: 在 Erlang/OTP R9C 中, Erlang crash dump 有大幅的改变。在本节中的信息不能直接应用到旧的 dumps 文件中。但是,如果你使用了 crashdump_viewer(3) 查看旧的 dumps 文件,则 crash dumps 文件会被转换为与此相类似的格式。

系统将 crash dump 文件写入模拟器(译注:即 Erlang虚拟机,类似JVM)的当前目录(译注:即你在哪个目录中启动 Erlang 虚拟机)或由环境变量 ERL_CRASH_DUMP 指定的文件中(无论是哪种系统都如此)。对于要写入的 crash dump 文件,必须是在一个已经挂载的可写入的文件系统。

主要由于两种原因之一会导致写 crash dump 文件:内置函数 erlang:halt/1 从正在执行的 Erlang 代码中被显式地带有一个 string 参数来调用,或者运行时系统检测到无法处理的错误。最常见的系统无法处理错误的原因是由外部的限制导致的,例如内存不足。由于内部错误引起的 crash dump 可能会由于系统在模拟器自身达到了极限(例如系统中的原子数,或者太多同步ETS表)引起的。通常重新配置模拟器或操作系统可以避免崩溃,因此正确地解释 crash dump 是很重要的。

在支持OS信号的系统上,它也可 以通过发送 SIGUSR1 信号来停止运行时系统和生成 crash dump。

Erlang的 crash dump 是一个可读文本文件,但可能比较难以阅读它。使用在 Observer 应用中的 Crashdump Viewer 工具可以简化该任务。这是一个用于浏览 Erlang crash dumps 的基于 wx-widget 的工具。

一般信息(General Information)

Crash dump 的第一部分显示了以下信息:

  • dump 的创建时间
  • 一个 Slogan ,它指明产生 dump 的原因
  • dump 的源节点的系统版本
  • 源节点正执行的模拟器编译时间
  • 原子表中的原子数
  • 导致 crash dump 的运行时系统线程

Crash Dump 的原因(Slogan)

Dump 的原因显示在文件的开头:

Slogan: <reason>

如果系统是由 BIF erlang:halt/1 停止的话,则 Slogan 是传递给 BIF 的 string 参数,否则它是模拟器或(Erlang)内核生成的描述。通常,该消息足以知道问题所在,但有时只是一些描述一些消息。请注意,认为的 crash 的原因,仅仅只是认为 。具体的错误原因会因本地应用以及所在的操作系统而异。

: Cannot allocate bytes of memory (of type “”)

系统内存不足。 是失败分配内存的分配器。 试图分配的 字节数 是内存需要分配的内存块类型。最常见的情况是进程存储大量的数据。在这种情况下,最常见的导致crash的 heapold heap , heap_fraqbinary 。更多关于分配器的信息,请看 erts_alloc(3)

: Cannot reallocate bytes of memory (of type “”)

与上述相同,除了内存是重新分配,而不是在系统内存不足时被分配外。

Unexpected op code

编译代码错误, beam 文件损坏或编译器出错。

Module undefined | Function undefined | No function :/1 | No function :start/2

内核/STDLIB 应用程序已经损坏或启动脚本已经损坏

Driver_select called with too large file descriptor N

套接字的文件描述符数超过 1024 (仅适用于 Unix 系统)。某些 Unix 文件描述符的限制可以设置为超过 1024 ,但 Erlang 只能同时使用 1024 个 套接字/管道(因为 Unix 中 select 调用的限制)。打开常规文件的数量不受此影响。

Received SIGUSR1

发送 SIGUSR1 信号给 Erlang 虚拟机(仅适用于 Unix 系统)可以强制生成 crash dump 。这个 Slogan 指明 Erlang 虚拟机的 crash dump 是由于收到该信号导致的

Kernel pid terminated () ()

内核监控程序检测到故障,通常 application_controller 已经关闭( Who = application_controller, Why = shutdown )。Application Controller 可能由于许多原因而关闭,最常见的原因是分布式 Erlang 节点的节点名已经被使用了。一棵完整的监控树 crash (即,最顶层的监控者已经退出)也会给出同样的结果。该消息来自 Erlang 代码,而不是虚拟机自身。这总是由于应用程序里的一些故障,无论它是在 OTP 还是用户写的应用都如此。可能第一步适当采取的应该是查看你的应用程序日志。

Init terminating in do_boot ()

主要的 Erlang 启动顺序已经被终止,最可能是因为启动脚本有错误或无法读取。这通常是配置错误;系统可能使用了错误的 -boot 参数、或从错误的OTP版本中的启动脚本来启动。

Could not start kernel pid () ()

内核进程之一无法启动。这可能是因为参数有问题(比如 -config 参数中有错)或配置文件错误。检查所有文件是否在正确的位置,并且检查配置文件(如果有的话)是否有损坏。通常消息也会写入控制终端和/或错误日志中以解释哪里错了。

其他错误

除上面外的其他错误也可能会发生,因为 erlang:halt/1 BIF 可以产生任何消息。如果消息不是由 BIF 产生的,并且不在上面的列表之一中,那可能是由于模拟器出现错误了。然而,可能会出现不寻常的消息,即这里没提及到,但仍然是与应用程序故障有关的。有更多可用的信息,所以完整阅读 crash dump 可以披露 crash 的原因。进程的大小, ETS表的数量, 以及每个Erlang进程栈的数据对找出所在问题都是有帮助的。

原子数

crash 时系统中的原子数显示为 Atoms: ,好大几W个原子数是完全正常的,但更多地可以表明 BIF erlang:list_to_atom/1 用来动态生成不同的原子,它绝不是一个好方式。

调度器信息(Scheduler Information)

在标签 =scheduler 下面,显示了有关运行时系统中调度顺的当前统计的信息。在操作系统中允许暂停其他线程,在本节中的数据反映了当发生 crash 时运行时系统的状态。

进程可以存在以下字段:

=scheduler:id

标题。说明调度器的ID

Scheduler Sleep Info Flags

如果为空,表示调度器正在工作中。

如果非空,则调度器正处于某种状态中: sleepsuspended

该条仅出现在基于 SMP 模拟器中。

Scheduler Sleep Info Aux Work

如果不为空,则调度器内部的辅助工作已经调度完成。

Current Port

当前由调度器执行的 port 的 port 标识符。(译注:Port 在Erlang中与普通理解的 Port 并不太一样。它是一种 Erlang 与外部程序通信的方式之一)

Current Process

由当前调度器执行的进程的进程标识符。如果有这样的一个进程,这个条目下面就跟着有:StateInternal StateProgram Counter 以及 CP 。这些项是描述了 进程信息部分

注意,这些是当 crash dump 开始生成时的一个快照。因此,它们很可能与在 = proc 部分找到的同样进程的条目不太相同(并说明更多)。如果当前没有运行的进程,则仅显示 Current Process

Current Process Limited Stack Trace

本条目仅在有 Current Process 时显示。类似 =proc_stack ,除了仅显示函数帧(也就是说,省略了栈变量)。此外,仅显示栈的顶部和底部。如果栈足够小的话( < 512 个 slots ),则会完整显示。否则显示条目 skipping ## slots## 是被忽略的 slots 数量。

Run Queue

显示在此调度器上有多少个进程和不同调度优先级的 port 的统计信息

** crashed **

该条目通常是不会显示的。这意味着获取有关此调度器的其余信息由于某些原因而导致失败。

内存信息(Memory Information)

在标签 =memory 下面显示的信息类似于在一个存活的节点通过 erlang:memory() 获取的信息。

内部表信息(Internal Table Information)

在标签 =hash_table:=index_table: 下面显示了内部表的信息。这是运行时系统开发者最感兴趣的。

分配区域(Allocated Areas)

在标签 =allocated_areas 下面显示了在一个存活节点中通过 erlang:system_info(allocated_areas). 获取的信息类似。

分配器(Allocator)

在标签 =allocator: 下显示了各种关于分配器 的信息。这些信息类似于在一个存活的节点上通过 erlang:system_info({allocator, }). 获取的信息。更多信息,请参阅 erts_alloc(3)

进程信息(Process Information)

Erlang crashdump 包含了每个存活在 Erlang 系统的进程列表。以下字段可以存在于一个进程中:

=proc:

标题。指示进程的 ID

State

进程的状态。可以是以下中的一个:

Scheduled

进程被调度去执行,但它当前并不在运行(“在 Run Queue 中”)

Waiting

进程正等待某些东西(在接收)

Running

进程正在运行。如果 BIF erlang:halt/1 被调用,就是该进程调用它的。

Exiting

进程正在退出

Garbing

这是不幸运的,当写 crash dump 时该进程正在被垃圾收集。该进程的其余信息就有限了。

Suspended

进程在 suspended 中,可能是由 BIF erlang:suspend_process/1 调用或它尝试向一个繁忙的 port 写数据导致的。

Registered name

该进程的注册名,如果有的话。

Spawned as

进程的入口点,即启动进程的 spawnspawn_link 函数的引用。

Last scheduled in for | Current call

当前进程的函数。该字段并不是总会存在的。

Spawned by

进程的父进程,即执行 spawnspawn_link 的进程。

Started

进程启动的日期和时间

Message queue length

进程消息队列的长度

Number of heap fragments

已分配的堆帧数

Heap fragment data

堆帧数据大小。该数据是由发送到该进程的消息或者由 Erlang BIFs 创建的。这个数量取决于该字段完全不感兴趣的事情数。

与此进程相关联的进程ID列表。也可包含 ports 。如果进程用作监视的话,该字段还会告知有效的直接监控。也就是说,一个连到进程的连接,它告诉你 当前 进程正监控其他进程(译注:即 P1 -> P2,这表示P2正监控P1)。一个从进程中的连接,它告诉你其他的进程正监控着当前的进程。(译注:P1 <- P2 ,表示P1正在监控P2)

Reductions

进程消耗的减少量。

译注:该字段不是很理解。。

Stack+heap

栈和堆的大小(它们共享内存段)

OldHeap

old heap 的大小。Erlang虚拟机使用2代的分代垃圾收集器。有一个用于新数据项的堆,另一个用于两个垃圾收集后还存活的数据。这个假设(这几乎总是正确的)是,两个垃圾收集还存活的数据可以 升级 到一个更少垃圾收集的堆中,因为它们将存活比较长的一段时间。这是虚拟机中常见的技术。堆和栈一起的总和构成了进程的绝大部分分配的内存。

Heap unused, OldHeap unused

每个堆上未使用的内存量。它通常是无用的。

Memory

该进程总使用内存量。它包括了调用栈、堆和内部结构。与 erlang:process_info(Pid,memory) 相同。

Program counter

当前指令指针。仅运行时系统开发者感兴趣。它指向的函数是当前进程的函数。

CP

Continuation Pointer,即当前调用的返回地址。对于非运行时系统开发者来说是无用的。可以跟踪 CP 指向的函数,即调用当前函数的函数。

Arity

实数参数的寄存器数量。如果有实参的话则紧接着参数寄存器。它可包含函数参数,如果它们还没有迁移到栈的话。

Internal State

关于该进程的更多内部状态

其他

参看 process data

Port 信息

这部分列出了打开的 ports ,它们的拥有者,任何连接的进程,它们 driver 或外部进程的名字。

ETS 表

这部分包含了系统中的所有关于 ETS 表的信息。以下是每一张表的字段

=ets:

标题。指明表的拥有者(一个进程ID)

Table

表的ID。如果它是一个 named_table ,则是它的名字。

Name

表名,不管它是否是一个 named_table

Hash table, Buckets

是否是一张 hash 表,即,它并不是一张 ordered_set

Hash table, Chain Length

表是否是一张 hash 表。包含了关于表的统计信息,比如:最大、最小和平均链长度。具有比平均值大得多和远大于预期标准偏差的标准偏差,这表示由于某些原因,terms 的 hash 表现不好。

Ordered set (AVL tree), Elements

表是否是 ordered_set 。(元素的数量与表中的对象数相同)

Fixed

表是否使用 ets:safe_fixtable/2 或一些内部机制修复了。

Objects

表中的对象数

Words

表中数据被分配的字数(通常4字节为一字)

Type

表的类型, set , bag , dublicate_bagordered_set

Compressed

表是否压缩

Protection

表的 protection

Write Concurrency

表是否开启 write_concurrency

Read Concurrency

表是否开启 read_concurrency

计时器(Timers)

这部分包含了所在关于以 erlang:start_timer/3erlang:send_after/3 启动的计时器信息。以下是每个计时器的字段:

=timer:

标题。指明 timer 的拥有者(一个进程ID),也就是说,进程接收消息时 timers 过期了。

Message

被发送的消息

Time left

直到消息将被发送的剩余毫秒数(milliseconds left)

分布式信息(Distribution Information)

如果 Erlang 节点存活,即设置了与其他节点进行通信,这部分列出了活动的连接。可以存在以下字段:

=node:

节点名

no_distribution

节点是否并不是分布式的

=visible_node:

可视节点的标题,即,一个存活节点连接到一个 crash 的节点。指明了节点的 channel 序号。

=hidden_node:

隐藏节点标题。隐藏节点与可视节点相同,除了它是以 -hidden 标识开头。指明了节点的 channel 序号。

=not_connected:

早先连接到 crash 节点的节点标题。crash 时存在的未连接节点的引用(即,进程或 port 的标识符)。指明了节点的 channel 序号。

Name

远程节点的名字

Controller

控制与远程节点通信的 port

Creation

与节点名标识符一起的一个整数(1-3)来标识特定的节点实例。

Remote monitoring:

在 crash 时,正监控远程进程的本地进程

Remotely monitored by:

在 crash 时,正在监控本地进程的远程进程

在 crash 时,存在于本地进程和远程节点的链接。

已加载模块的信息(Loaded Module Information)

这部分包含了关于所有已加载模块的信息

首先是已加载代码的占用内存总述:

  • Current code : 模块的当前最新版本的代码
  • Old code : 在系统中存在更新版本的代码,但旧版本还没有被清除的代码

内存占用是以字节为单位的。然后会列出所有已加载的模块。会出现以下字段:

=mod:

标题。指明模块名

Current size

已加载代码的内存占用,单位字节

Old size

旧代码的内存占用,单位字节

Current attributes

当前代码的模块属性。该字段会在使用 Crashdump Viewer 工具时被解码。

Old attributes

旧代码的模块属性,如果有的话。该字段会在使用 Crashdump Viewer 工具时被解码。

Current compilation info

当前代码的编译信息(选项)。该字段会在使用 Crashdump Viewer 工具时被解码。

Old compilation info

旧代码的编译信息(选项),如果有的话。该字段会在使用 Crashdump Viewer 工具时被解码。

Fun 信息

该部分列出所有 funs 。以下字段可出现在每一个 fun 中。

=fun

标题

Module

fun 定义所在的模块名

Uniq, Index

标识符

Address

fun 代码的地址

Native_address

当开启 HiPE 时, fun 代码的本地代码地址

Refc

引用 fun 的引用数

进程数据(Process Data)

对于每一个进程,至少有一个 =proc_stack 和一个 =proc_heap 标签,后接着的是进程的栈和堆的原始内存信息

对于每一个进程,如果进程的消息队列不为空的话,还会有一个 =proc_messages 标签,如果进程的 dictionary (即 put/2get/1 所做的事)不为空的话,还有一个 proc_dictionary 标签。

原始内存信息可以在使用 Crashdump Viewer 工具时被解码。然后你可以看到栈的dump,消息队列(如果有话),以及 dictionary (如果有话)

栈 dump 是Erlang进程的dump 。大多存活的数据(即,当前使用的变量)会放到栈上;因此可能比较感兴趣。人们可以猜测它是什么,但作为信息来说是符号来的,完整阅读这些信息可能是有用的。例如,我们可以在下面的例子的第5,和第6行中找出 Erlang 基本加载器的变量状态:

(1)  3cac44   Return addr 0x13BF58 (<terminate process normally>)
(2)  y(0)     ["/view/siri_r10_dev/clearcase/otp/erts/lib/kernel/ebin",
(3)            "/view/siri_r10_dev/clearcase/otp/erts/lib/stdlib/ebin"]
(4)  y(1)     <0.1.0>
(5)  y(2)     {state,[],none,#Fun<erl_prim_loader.6.7085890>,undefined,#Fun<erl_prim_loader.7.9000327>,
(6)            #Fun<erl_prim_loader.8.116480692>,#Port<0.2>,infinity,#Fun<erl_prim_loader.9.10708760>}
(7)  y(3)     infinity    

当为进程解释这些数据时,有助于知道匿名函数对象(funs) 是给出以下:

  • 创建它们的函数的名字的名称构造
  • 一个数字(从0开始),表示该函数中的 fun 个数。

原子(Atoms)

本节介绍系统中所有的原子。这仅是那些怀疑动态生成原子会是个问题的人感兴趣的部分,否则,这一节可以忽略。

请注意,最后创建的原子反而是最先显示的。

免责声明(Disclaimer)

crash dump 文件格式会在 OTP 版本之间演变。此处描述的某些信息可能并不适用于你的版本。这样的描述永不会完整;这意味着,对于 crash dump 的解释是一般的,以及对尝试查找应用程序错误是有帮助的,而不是完整的规范。