View Source edb (edb_core v0.5.0)

The (new!) Erlang debugger

Summary

Functions

Set a breakpoint on the line of a loaded module on the attached node.

Start a debugging session by attaching to the given node.

Returns the node being debugged.

Clear a previously set breakpoint on the attached node.

Clear all previously set breakpoints of a module on the attached node.

Continues the execution on the attached node and returns right away. Returns not_paused if no process was paused, otherwise resumed.

Detach from the currently attached node.

The provided function will be given the requested stack-frame variables of the process, and the result of the evaluation, if successful, will be returned.

Add a single process to the set of processes excluded from debugging. It is equivalent to exclude_processes([{proc, Proc}]).

Extend the set of processes excluded by the debugger.

List the pids that will not be paused by the debugger on the attached node.

Get all currently set breakpoints on the attached node.

Get currently set breakpoints for a given module on the attached node.

Get the list of processes currently paused at a breakpoint on the attached node.

Check if there exists paused processes.

Pause the execution of the attached node.

Get information about a process managed by the debugger on the attached node.

Get the set of processes managed by the debugger on the attached node.

Prepare for attachment by a node that doesn't exist yet.

Request that a sync event is sent to the given subscription.

Set breakpoints on multiple lines of a given module, on the attached node.

Get the local variables for a paused processes at a give frame.

Get the local variables for a paused process at a given frame.

Get the stack frames for a paused process.

Continues the execution on the attached node and returns right away. Execution will stop once Pid enters the current call target, or if an exception raised by any of its arguments is caught.

Continues the execution on the attached node and returns right away. Execution will stop once Pid exits the current function.

Continues the execution on the attached node and returns right away. Execution will stop once Pid finishes executing the next expression.

Subscribe caller process to receive debugging events from the attached node.

Terminates the debugging session.

Removes an exclusion previously added with exclude_processes/1.

Remove a previously added subscription.

Waits until the node gets paused.

Types

add_breakpoint_error()

-type add_breakpoint_error() ::
          unsupported |
          {unsupported, module()} |
          {unsupported, Line :: line()} |
          {badkey, module()} |
          {badkey, Line :: line()}.

A breakpoint may not be added for various reasons:

  • unsupported: The node does not support line-breakpoint instrumentations (likely for not being started with the +D emulator flag).
  • {badkey, Module}: The given module does not exist or can't be loaded.
  • {unsupported, Module}: The module was loaded without suppor for line-breakpoints.
  • {badkey, Line}: The line is not relevant; it could refer to a comment, not exist in the module source, and so on.
  • {unsupported, Line}: It is not possible to set a breakpoint in the given line; for example, if it refers to a function head.

bootstrap_failure()

-type bootstrap_failure() ::
          {no_debugger_support, {missing, erl_debugger} | not_enabled} |
          {module_injection_failed, module(), Reason :: term()}.

breakpoint_info()

-type breakpoint_info() :: #{module := module(), line := line()}.

call_target_error()

-type call_target_error() :: not_found | {module_not_found, module()} | {function_not_found, mfa()}.

catch_handler()

-type catch_handler() :: {'catch', {mfa(), {line, line() | undefined}}}.

eval_error()

-type eval_error() ::
          timeout |
          {exception,
           #{class := error | exit | throw, reason := term(), stacktrace := erlang:stacktrace()}} |
          {killed, Reason :: term()}.

event()

-type event() ::
          {resumed, resumed_event()} |
          {paused, paused_event()} |
          {sync, reference()} |
          {terminated, Reason :: term()} |
          unsubscribed |
          {nodedown, node(), Reason :: term()}.

event_envelope(Event)

-type event_envelope(Event) :: {edb_event, event_subscription(), Event}.

event_subscription()

-type event_subscription() :: edb_events:subscription().

exclusion_reason()

-type exclusion_reason() ::
          debugger_component | excluded_application | excluded_pid | excluded_regname | system_component.

frame_id()

-type frame_id() :: non_neg_integer().

fun_name()

-type fun_name() :: atom().

line()

-type line() :: pos_integer().

paused_event()

-type paused_event() :: {breakpoint, pid(), mfa(), {line, line()}} | pause | {step, pid()}.

process_info()

-type process_info() ::
          #{application => atom(),
            current_bp => {line, line()},
            current_fun => mfa(),
            current_loc => {string(), line()},
            exclusion_reasons => [exclusion_reason()],
            message_queue_len => non_neg_integer(),
            parent => atom() | pid(),
            pid_string => binary(),
            registered_name => atom(),
            status => process_status()}.

process_info_field()

-type process_info_field() ::
          application | current_bp | current_fun | current_loc | exclusion_reasons | message_queue_len |
          parent | pid_string | registered_name | status.

process_status()

-type process_status() :: running | paused | breakpoint.

procs_spec()

-type procs_spec() :: {proc, pid() | atom()} | {application, atom()} | {except, pid()}.

resumed_event()

-type resumed_event() :: {continue, all} | {excluded, #{pid() => []}} | {termination, all}.

set_breakpoints_result()

-type set_breakpoints_result() :: [{line(), Result :: ok | {error, add_breakpoint_error()}}].

stack_frame()

-type stack_frame() ::
          #{id := frame_id(),
            mfa := mfa() | unknown,
            source := file:filename() | undefined,
            line := line() | undefined}.

stack_frame_vars()

-type stack_frame_vars() :: #{vars => #{binary() => value()}, xregs => [value()], yregs => [value()]}.

step_error()

-type step_error() :: not_paused | {cannot_breakpoint, module()}.

step_in_error()

-type step_in_error() :: step_error() | {call_target, call_target_error()}.

value()

-type value() :: {value, term()} | {too_large, Size :: pos_integer(), Max :: non_neg_integer()}.

Functions

add_breakpoint(Module, Line)

-spec add_breakpoint(Module, Line) -> ok | {error, Reason}
                        when Module :: module(), Line :: line(), Reason :: edb:add_breakpoint_error().

Set a breakpoint on the line of a loaded module on the attached node.

attach(AttachOpts0)

-spec attach(#{node := node(), timeout => timeout(), cookie => atom()}) -> ok | {error, Reason}
                when
                    Reason ::
                        attachment_in_progress | nodedown | {bootstrap_failed, bootstrap_failure()}.

Start a debugging session by attaching to the given node.

If edb was already attached to a node, it will get detached first. The attached node may already have a debugging session in progress, in this case, edb joins it.

This call may start distribution and set the node name.

Arguments:

  • node - the node to attach to
  • timeout - how long to wait for the node to be up; defaults to 0,
  • 'cookie' - cookie to use for connecting to the node

attached_node()

-spec attached_node() -> node().

Returns the node being debugged.

Will raise a not_attached error if not attached.

clear_breakpoint(Module, Line)

-spec clear_breakpoint(Module, Line) -> ok | {error, not_found} when Module :: module(), Line :: line().

Clear a previously set breakpoint on the attached node.

clear_breakpoints(Module)

-spec clear_breakpoints(Module) -> ok when Module :: module().

Clear all previously set breakpoints of a module on the attached node.

continue()

-spec continue() -> {ok, resumed | not_paused}.

Continues the execution on the attached node and returns right away. Returns not_paused if no process was paused, otherwise resumed.

detach()

-spec detach() -> ok.

Detach from the currently attached node.

The debugger session running on the node is left undisturbed.

eval(Opts)

-spec eval(Opts) -> not_paused | undefined | {ok, Result} | {eval_error, eval_error()}
              when
                  Opts ::
                      #{context := {pid(), frame_id()},
                        max_term_size := non_neg_integer(),
                        timeout := timeout(),
                        function := fun((Vars :: stack_frame_vars()) -> Result),
                        dependencies => [module()]}.

The provided function will be given the requested stack-frame variables of the process, and the result of the evaluation, if successful, will be returned.

Notice that, if missing, the debuggee node will load the given function's module, and any other modules listed under dependencies, taking the object code from the caller node. It is the caller's responsibility to ensure that any dependency of the function is listed under dependencies.

exclude_process(Proc)

-spec exclude_process(Proc) -> ok when Proc :: pid() | atom().

Add a single process to the set of processes excluded from debugging. It is equivalent to exclude_processes([{proc, Proc}]).

exclude_processes(Specs)

-spec exclude_processes(Specs) -> ok when Specs :: [procs_spec()].

Extend the set of processes excluded by the debugger.

Processes can be specified in the following ways:

  • by pid,
  • by being part of an application,
  • exception list for pids that should not be excluded

E.g. a spec like:

[Pid1, {appication, foo}, {application, bar}, {except, Pid2}, {except, Pid3}]

will exclude Pid1 and all processes in applications foo and bar; however Pid2 and Pid3 are guaranteed not to be excluded, whether they are part of foo, bar, etc. The order of the spec clauses is irrelevant and, in particular, except clauses are global.

If any specified processes are currently paused, they will be automatically resumed.

excluded_processes(RequestedFields)

-spec excluded_processes(RequestedFields) -> #{pid() => []}
                            when RequestedFields :: [process_info_field()].

List the pids that will not be paused by the debugger on the attached node.

get_breakpoints()

-spec get_breakpoints() -> #{module() => [breakpoint_info()]}.

Get all currently set breakpoints on the attached node.

get_breakpoints(Module)

-spec get_breakpoints(Module) -> [breakpoint_info()] when Module :: module().

Get currently set breakpoints for a given module on the attached node.

get_breakpoints_hit()

-spec get_breakpoints_hit() -> #{pid() => breakpoint_info()}.

Get the list of processes currently paused at a breakpoint on the attached node.

is_paused()

-spec is_paused() -> boolean().

Check if there exists paused processes.

pause()

-spec pause() -> ok.

Pause the execution of the attached node.

process_info(Pid, RequestedFields)

-spec process_info(Pid, RequestedFields) -> {ok, process_info()} | undefined
                      when Pid :: pid(), RequestedFields :: [process_info_field()].

Get information about a process managed by the debugger on the attached node.

processes(RequestedFields)

-spec processes(RequestedFields) -> #{pid() => process_info()}
                   when RequestedFields :: [process_info_field()].

Get the set of processes managed by the debugger on the attached node.

reverse_attach(Opts)

-spec reverse_attach(Opts) -> {ok, Info} | {error, Reason}
                        when
                            Opts :: #{name_domain := longnames | shortnames, timeout => timeout()},
                            Info :: #{erl_code_to_inject := binary(), notification_ref := reference()},
                            Reason :: attachment_in_progress.

Prepare for attachment by a node that doesn't exist yet.

The caller is expected to start a new node, and ensure it executes the code returned by this call. The caller can then expect to receive a message tagged with the reference in notification_ref, containing the result of the reverse attachment.

When the node executes the injected code, it will be forced to become attached, and immediately paused.

As long as timeout is not infinity, the caller is guaranteed to eventually receive a message of the form:

  • {NotificationRef, ok}: The reverse attachment succeeded, the node is now paused.
  • {NotificationRef, timeout}: The reverse attachment timed out; it will now never happen.
  • {NotificationRef, {error, {bootstrap_failed, BootstrapFailure}}}: We tried to bootstrap edb on the node but failed

This call may start distribution and set the node name.

Options:

  • name_domain: whether we expect to be attached by a node using longnames or shortnames;
  • timeout: how long to wait for the node to be up; defaults to infinity.

send_sync_event(Subscription)

-spec send_sync_event(Subscription) -> {ok, SyncRef} | undefined
                         when Subscription :: event_subscription(), SyncRef :: reference().

Request that a sync event is sent to the given subscription.

The process holding the subscription will receive a sync event, with the returned reference as value. This can be used to ensure that there are no events the server is planning to send.

Returns {error, unknown_subscription} if the subscription is not known.

set_breakpoints(Module, Lines)

-spec set_breakpoints(Module, [Line]) -> Result
                         when Module :: module(), Line :: line(), Result :: set_breakpoints_result().

Set breakpoints on multiple lines of a given module, on the attached node.

Notice that Module may get loaded as a side-effect of this call.

stack_frame_vars(Pid, FrameId)

-spec stack_frame_vars(Pid, FrameId) -> not_paused | undefined | {ok, Result}
                          when Pid :: pid(), FrameId :: frame_id(), Result :: stack_frame_vars().

Get the local variables for a paused processes at a give frame.

Equivalent to stack_frame_vars(Pid, FrameId, 2048).

stack_frame_vars(Pid, FrameId, MaxTermSize)

-spec stack_frame_vars(Pid, FrameId, MaxTermSize) -> not_paused | undefined | {ok, Result}
                          when
                              Pid :: pid(),
                              FrameId :: frame_id(),
                              MaxTermSize :: non_neg_integer(),
                              Result :: stack_frame_vars().

Get the local variables for a paused process at a given frame.

The value of FrameId must be one of the frame-ids returned by stack_frames/1, or the call will return undefined.

For each variable, the value is returned only if its internal size is at most MaxTermSize, otherwise {too_large, Size, MaxTermSize} is returned. This is to prevent the caller from getting objects that are larger than they are willing to handle.

stack_frames(Pid)

-spec stack_frames(Pid) -> not_paused | {ok, [Frame]} when Pid :: pid(), Frame :: stack_frame().

Get the stack frames for a paused process.

The FrameNo can then be used to retrieve the variables for a particular frame.

step_in(Pid)

-spec step_in(Pid) -> ok | {error, step_in_error()} when Pid :: pid().

Continues the execution on the attached node and returns right away. Execution will stop once Pid enters the current call target, or if an exception raised by any of its arguments is caught.

Returns {error, {call_target, Reason}} in case the call-target cannot be determined.

step_out(Pid)

-spec step_out(Pid) -> ok | {error, step_error()} when Pid :: pid().

Continues the execution on the attached node and returns right away. Execution will stop once Pid exits the current function.

step_over(Pid)

-spec step_over(Pid) -> ok | {error, step_error()} when Pid :: pid().

Continues the execution on the attached node and returns right away. Execution will stop once Pid finishes executing the next expression.

Caveat:

  • If the next expression is a recursive tail-call, execution will stop when the callee starts.
  • This is unlike non-recursive tail-calls, where execution will stop when the callee ends

subscribe()

-spec subscribe() -> {ok, event_subscription()}.

Subscribe caller process to receive debugging events from the attached node.

The caller process can then expect messages of type event_envelope(event()), with the specified subscription in the envelope. A process can hold multiple subscriptions and can unsubscribe from them individually.

terminate()

-spec terminate() -> ok.

Terminates the debugging session.

Detaches from the node, but stopping the debugger running on it. That means that breakpoints will be cleared, and any paused processes will be resumed, etc.

unexclude_processes(Specs)

-spec unexclude_processes(Specs) -> ok when Specs :: [procs_spec()].

Removes an exclusion previously added with exclude_processes/1.

If there are currently paused processes, any specified processes will be paused as well.

unsubscribe(Subscription)

-spec unsubscribe(Subscription) -> ok when Subscription :: event_subscription().

Remove a previously added subscription.

The caller process need not be the one holding the subscription. An unsubscribed event will be sent as final event to the subscription, which marks the end of the event stream.

wait()

-spec wait() -> {ok, paused}.

Waits until the node gets paused.