查看源代码 调试器

入门

要使用调试器,基本步骤如下:

步骤 1. 通过调用 debugger:start() 启动调试器。

监视窗口将显示有关所有被调试进程、解释模块和所选选项的信息。通常,最初没有被调试的进程。首先,必须指定要调试(也称为解释)的模块。按如下步骤操作:

步骤 2. 在监视窗口中选择模块 > 解释...

将显示解释模块窗口

步骤 3. 从“解释对话框”窗口中选择适当的模块。

注意

只有使用选项 debug_info 设置编译的模块才能被解释。不可解释的模块在“解释模块”窗口中显示在括号内。

步骤 4. 在监视窗口中,选择模块 > 要解释的模块 > 查看

源代码文件的内容将显示在查看模块窗口中。

步骤 5. 设置断点(如果有)。

步骤 6. 启动要调试的程序。这通常从 Erlang shell 中完成。

所有在解释模块中执行代码的进程都会显示在监视窗口中。

步骤 7.附加到这些进程之一,请双击它,或选择该进程,然后选择进程 > 附加。附加到进程会为此进程打开一个附加进程窗口

步骤 8. 从“附加进程”窗口中,您可以控制进程执行、检查变量值、设置断点等等。

断点和断点对话框窗口

一旦解释了适当的模块,就可以在源代码中的相关位置设置断点。断点是按行指定的。当进程到达断点时,它会停止并等待用户发出的命令(单步执行跳过继续 ...)。

注意

当进程到达断点时,只会停止该进程。其他进程不受影响。

断点是使用监视窗口、“查看模块”窗口或“附加进程”窗口的断点菜单创建和删除的。

可执行行

要生效,必须在可执行行上设置断点,该行是包含可执行表达式(例如匹配或函数调用)的代码行。空行或包含注释、函数头或 case 语句或 receive 语句中的模式的行不是可执行的。

在以下示例中,第 2、4、6、8 和 11 行是可执行行

1: is_loaded(Module,Compiled) ->
2:   case get_file(Module,Compiled) of
3:     {ok,File} ->
4:       case code:which(Module) of
5:         ?TAG ->
6:           {loaded,File};
7:         _ ->
8:           unloaded
9:       end;
10:    false ->
11:      false
12:  end.

状态和触发操作

断点可以是活动的非活动的。非活动的断点将被忽略。

每个断点都有一个触发操作,用于指定当进程到达它(并停止)时会发生什么

  • 启用 - 断点保持活动状态(默认)。
  • 禁用 - 断点被设为非活动状态。
  • 删除 - 断点被删除。

行断点

行断点是在模块中的特定行创建的。

Line Break Dialog Window

右键单击模块条目以打开弹出菜单,从中可以选择适当的模块。

当模块在“查看模块”窗口或“附加进程”窗口中显示时,也可以通过双击该行来创建(和删除)行断点。

条件断点

条件断点是在模块中的特定行创建的,但只有当指定的条件为真时,到达该断点的进程才会停止。

条件由用户指定为模块名称 CModule 和函数名称 CFunction。当进程到达断点时,将评估 CModule:CFunction(Bindings)。当且仅当此函数调用返回 true 时,进程才会停止。如果函数调用返回 false,则会静默忽略断点。

Bindings 是一个变量绑定列表。要检索 Variable(作为原子给出)的值,请使用函数 int:get_binding(Variable, Bindings)。该函数返回 unbound{value,Value}

Conditional Break Dialog Window

右键单击模块条目以打开弹出菜单,从中可以选择适当的模块。

示例

在模块 fact 的第 6 行添加一个调用 c_test:c_break/1 的条件断点。每次到达断点时,都会调用该函数。当 N 等于 3 时,该函数返回 true,进程停止。

fact.erl 中提取

5. fac(0) -> 1;
6. fac(N) when N > 0, is_integer(N) -> N * fac(N-1).

c_test:c_break/1 的定义

-module(c_test).
-export([c_break/1]).

c_break(Bindings) ->
    case int:get_binding('N', Bindings) of
        {value, 3} ->
            true;
        _ ->
            false
    end.

函数断点

函数断点是一组行断点,每个行断点位于指定函数中每个子句的第一行。

Function Break Dialog Window

要打开弹出菜单,从中可以选择适当的模块,请右键单击模块条目。

要在列表框中显示模块的所有函数,请在指定模块名称后单击确定按钮(或按 ReturnTab 键)。

堆栈跟踪

Erlang 模拟器会跟踪堆栈跟踪,即有关最近函数调用的信息。如果发生错误,则会使用此信息,例如

1> catch a+1.
{'EXIT',{badarith,[{erlang,'+',[a,1],[]},
                   {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,573}]},
                   {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,357}]},
                   {shell,exprs,7,[{file,"shell.erl"},{line,674}]},
                   {shell,eval_exprs,7,[{file,"shell.erl"},{line,629}]},
                   {shell,eval_loop,3,[{file,"shell.erl"},{line,614}]}]}}

有关堆栈跟踪的详细信息,请参阅 Erlang 参考手册中的错误和错误处理部分。

调试器通过跟踪最近调用的解释函数来模拟堆栈跟踪。(不能使用真实的堆栈跟踪,因为它显示的是调用了调试器的哪些函数,而不是调用了哪些解释函数。)

可以使用“附加进程”窗口中的向上向下按钮来遍历函数调用链。

默认情况下,调试器仅保存有关递归函数调用的信息,即尚未返回值的函数调用(选项启用堆栈,无尾部)。

但是,有时保存所有调用(即使是尾递归调用)也很有用。这是通过选项启用堆栈,尾部完成的。请注意,当有许多尾递归调用时,此选项会消耗更多内存并减慢解释函数的执行速度。

要关闭调试器堆栈跟踪功能,请选择选项关闭堆栈

注意

如果发生错误,在这种情况下,堆栈跟踪将变为空。

有关如何更改堆栈跟踪选项的信息,请参阅监视窗口部分。

监视窗口

监视窗口是调试器的主要窗口,并显示以下内容:

  • 一个列表框,其中包含所有解释模块的名称

    双击模块会调出“查看模块”窗口。

  • 选择了哪些选项

  • 有关所有被调试进程的信息,即所有在解释模块中执行过或正在执行代码的进程

Monitor Window

自动附加框、堆栈跟踪标签、回溯大小标签和字符串框显示一些设置的选项。有关这些选项的详细信息,请参阅选项菜单部分。

进程网格

  • Pid - 进程标识符。

  • 初始调用 - 此进程对解释函数的第一次调用。(Module:Function/Arity

  • 名称 - 已注册的名称(如果有)。如果未显示注册的名称,则可能是调试器在注册名称之前收到了有关该进程的信息。尝试选择编辑 > 刷新

  • 状态 - 当前状态,为以下之一:

    • idle - 解释的函数调用已返回值,并且该进程不再执行解释的代码。

    • running - 进程正在运行。

    • waiting - 进程正在 receive 语句中等待。

    • break - 进程在断点处停止。

    • exit - 进程已终止。

    • no_conn - 与进程所在的节点没有连接。

  • 信息 - 更多信息(如果有)。如果进程在断点处停止,则该字段包含有关位置 {Module,Line} 的信息。如果进程已终止,则该字段包含退出原因。

文件菜单

  • 加载设置... - 尝试从先前使用保存设置...(见下文)保存的文件中加载并还原调试器设置。任何错误都将被静默忽略。

    请注意,Erlang/OTP R16B01 或更高版本保存的设置不能被 Erlang/OTP R16B 或更早版本读取。

  • 保存设置... - 将调试器设置保存到文件。设置包括解释的文件集、断点和所选选项。可以使用加载设置...(见上文)在以后的调试器会话中还原设置。任何错误都将被静默忽略。

  • 退出 - 停止调试器。

编辑菜单

  • 刷新 - 更新有关被调试进程的信息。有关所有已终止进程的信息将从窗口中删除。所有已终止进程的“附加进程”窗口都将关闭。

  • 全部终止 - 使用 exit(Pid, kill) 终止窗口中列出的所有进程。

模块菜单

  • 解释... - 打开解释模块窗口,可以在其中指定要解释的新模块。

  • 全部删除 - 停止解释所有模块。在解释模块中执行的进程将终止。

对于每个解释的模块,都会向模块菜单添加一个相应的条目,其中包含以下子菜单:

  • 删除 - 停止解释所选模块。在此模块中执行的进程将终止。

  • 查看 - 打开一个查看模块窗口,显示所选模块的内容。

进程菜单

以下菜单项适用于当前选定的进程,前提是该进程已在断点处停止(详情请参阅附加进程窗口部分)

  • 单步执行

  • 下一步

  • 继续

  • 完成

以下菜单项适用于当前选定的进程

断点菜单

此菜单中的项用于创建和删除断点。详情请参阅断点部分。

  • 行断点... - 设置行断点。

  • 条件断点... - 设置条件断点。

  • 函数断点... - 设置函数断点。

  • 启用所有 - 启用所有断点。

  • 禁用所有 - 禁用所有断点。

  • 删除所有 - 删除所有断点。

对于每个断点,都会在“断点”菜单中添加一个对应的条目,从中可以禁用、启用或删除该断点,并更改其触发操作。

选项菜单

  • 跟踪窗口 - 设置在附加进程窗口中可见的区域。不影响现有的附加进程窗口。

  • 自动附加 - 设置要自动附加调试进程的事件。影响现有的调试进程。

    • 首次调用 - 进程首次调用已解释模块中的函数。
    • 退出时 - 在进程终止时。
    • 在断点处 - 当进程到达断点时。
  • 堆栈跟踪 - 设置堆栈跟踪选项,请参阅堆栈跟踪部分。不影响现有的调试进程。

    • 堆栈开启,尾部 - 保存有关所有当前调用的信息。
    • 堆栈开启,无尾部 - 保存有关当前调用的信息,在进行尾递归调用时丢弃先前的信息。
    • 堆栈关闭 - 不保存有关当前调用的任何信息。
  • 字符串 - 设置要打印为字符串的整数列表。不影响现有的调试进程。

    • 使用 +pc 标志的范围 - 使用erl(1) 标志 +pc 设置的可打印字符范围。
  • 回溯大小... - 设置从附加进程窗口检查调用堆栈时要获取的调用帧数量。不影响现有的附加进程窗口。

窗口菜单

包含每个打开的调试器窗口的菜单项。选择其中一项会提升相应的窗口。

帮助菜单

  • 帮助 - 显示调试器文档。此功能需要 Web 浏览器。

解释模块窗口

解释模块窗口用于选择要解释的模块。最初,该窗口显示当前工作目录的模块(erl 文件)和子目录。

可解释的模块是指,与源代码位于同一目录,或在源代码旁边的 ebin 目录中,存在使用 debug_info 选项编译的 .beam 文件的模块。

不满足这些要求的模块不可解释,因此显示在括号内。

debug_info 选项会导致将调试信息抽象代码添加到 .beam 文件中。这会增加文件大小,并使得重新构造源代码成为可能。因此,建议不要在面向目标系统的代码中包含调试信息。

以下示例说明如何使用 erlc 编译包含调试信息的代码

% erlc +debug_info module.erl

以下示例说明如何从 Erlang shell 编译包含调试信息的代码

4> c(module, debug_info).

Interpret Modules Window

要浏览文件层次结构并解释相应的模块,请选择一个模块名称并单击“选择”(或按回车键),或双击模块名称。解释后的模块的类型为 erl src

要解释所选目录中显示的所有模块,请单击“全部”。

要关闭窗口,请单击“完成”。

注意

当调试器以全局模式启动时(这是默认设置,请参阅 debugger:start/0),为解释添加(或删除)的模块将在所有已知的 Erlang 节点上添加(或删除)。

附加进程窗口

从附加进程窗口中,您可以与调试的进程进行交互。为每个已附加的进程打开一个窗口。请注意,附加到进程时,其执行会自动停止。

Attach Process Window

该窗口分为以下五个部分

  • 代码区域,显示正在执行的代码。代码会缩进,并且每行都以行号为前缀。如果进程执行已停止,则当前行将标记为 -->。某行上已存在的断点会标记一个停止符号。在图中显示的示例中,执行在第 6 行处停止,在执行 fac/1 之前。

    活动的断点以红色显示,非活动的断点以蓝色显示。

  • 按钮区域,带有快速访问“进程”菜单中常用功能的按钮。

  • 求值器区域,您可以在其中评估调试进程上下文中的函数,如果该进程的执行已停止。

  • 绑定区域,显示所有变量绑定。如果单击变量名称,则该值将显示在求值器区域中。双击变量名称可打开一个窗口,可在其中编辑变量值。但是请注意,除非可以在运行的系统中表示,否则 pid、端口、引用或 fun 值无法编辑。

  • 跟踪区域,显示进程的跟踪输出。

    • ++ (N) <L> - 函数调用,其中 N 是调用级别,L 是行号。

    • -- (N) - 函数返回值

      .

    • ==> Pid : Msg - 消息 Msg 被发送到进程 Pid

    • <== Msg - 接收到消息 Msg

    • ++ (N) receive - 等待 receive

    • ++ (N) receive with timeout - 在 receive...after 中等待。

    跟踪区域还显示回溯,即堆栈上当前函数调用的摘要。

使用“选项”菜单,您可以设置要显示的区域。默认情况下,将显示除跟踪区域以外的所有区域。

文件菜单

  • 关闭 - 关闭此窗口并从进程分离。

编辑菜单

  • 转到行... - 转到指定的行号。

  • 搜索... - 搜索指定的字符串。

进程菜单

  • 单步执行 - 执行当前代码行,步入任何(已解释的)函数调用。

  • 下一步 - 执行当前代码行,并在下一行停止。

  • 继续 - 继续执行。

  • 完成 - 继续执行,直到当前函数返回。

  • 跳过 - 跳过当前代码行,并在下一行停止。如果在函数体中的最后一行使用,则函数返回 skipped

  • 超时 - 在执行 receive...after 语句时模拟超时。

  • 停止 - 停止正在运行的进程的执行,即让进程在断点处停止。该命令将在进程下次收到消息时生效(可见)。

  • 位置 - 验证执行的当前位置在代码区域中可见。

  • 终止 - 使用 exit(Pid, kill) 终止进程。

  • 消息 - 检查进程的消息队列。该队列显示在求值器区域中。

  • 回溯 - 在跟踪区域中显示进程的回溯,即堆栈上当前函数调用的摘要。要求跟踪区域可见,且堆栈跟踪选项为“堆栈开启,尾部”或“堆栈开启,无尾部”。

  • 向上 - 检查堆栈上之前的函数调用,显示位置和变量绑定。

  • 向下 - 检查堆栈上的下一个函数调用,显示位置和变量绑定。

选项菜单

  • 跟踪窗口 - 设置要可见的区域。不影响其他附加进程窗口。

  • 堆栈跟踪 - 与监视器窗口中的相同,但仅影响窗口附加的调试进程。

  • 字符串 - 与监视器窗口中的相同,但仅影响窗口附加的调试进程。

  • 回溯大小... - 设置在检查调用堆栈时要获取的调用帧的数量。不影响其他“附加进程”窗口。

中断、窗口和帮助菜单

中断窗口帮助菜单与监视窗口中的相同,但中断菜单仅适用于本地断点。

查看模块窗口

“查看模块”窗口显示解释模块的内容,并允许设置断点。

View Module Window

源代码会缩进,并且每行都以其行号为前缀。

单击某行会高亮显示该行,并将其选为中断菜单中可用断点功能的目标。 要在该行上设置行断点,请双击它。 要删除断点,请双击带有现有断点的行。

断点用停止符号标记。

文件和编辑菜单

文件编辑菜单与附加进程窗口中的相同。

中断、窗口和帮助菜单

中断窗口帮助菜单与监视窗口中的相同,但中断菜单仅适用于本地断点。

性能

解释代码的执行速度自然比常规编译模块慢。使用调试器也会增加系统中的进程数量,因为对于每个调试的进程,都会创建一个额外的进程(元进程)。

还值得注意的是,带有计时器的程序在调试时可能会表现不同。当停止进程的执行时(例如,在断点处)尤其如此。超时可能会在其他正常继续执行的进程中发生。

代码加载机制

代码加载的工作方式几乎与往常一样,只是解释的模块也存储在数据库中,并且调试的进程仅使用此存储的代码。重新解释一个解释的模块也会导致新版本被存储,但不会影响执行旧版本代码的现有进程。这意味着 Erlang 的代码替换机制不适用于调试的进程。

调试远程节点

通过使用debugger:start/1,您可以指定调试器是在本地模式还是全局模式下启动

debugger:start(local | global)

如果调用debugger:start/0,则调试器将在全局模式下启动。

在本地模式下,代码仅在当前节点上解释。在全局模式下,代码在所有已知节点上解释。其他节点上执行解释代码的进程会自动显示在“监视”窗口中,并且可以像任何其他调试的进程一样附加。

注意

不建议在网络中的多个节点上以全局模式启动调试器,因为节点会相互干扰,导致行为不一致。