Tracing in Erlang with dbg

View Source

The dbg module in Erlang provides a text-based interface for tracing function calls, processes, ports, and messages. It simplifies the use of the underlying trace:process/4, trace:port/4, and trace:function/4 BIFs (Built-In Functions). This guide will walk you through the basics of using dbg for your Erlang applications.

This facility is useful for both quick debugging sessions in the shell and for more structured system testing, especially where other tools might have too much performance impact.

Quick Start

To trace a call to a function with minimal fuss, call dbg:c(Module, Name, Arguments). It starts a temporary trace receiver, enables all trace flags, and calls the designated function from a temporary process. For example, here is how to trace a call to application:which_applications/0:

1> dbg:c(application, which_applications, []).
(<0.92.0>) <0.45.0> ! {'$gen_call',{<0.92.0>,
                                    [alias|
                                     #Ref<0.0.11779.270031856.1478295555.230456>]},
                                   which_applications} (Timestamp: {1710,
                                                                    847802,
                                                                    479222})
(<0.92.0>) out {gen,do_call,4} (Timestamp: {1710,847802,479231})
(<0.92.0>) in {gen,do_call,4} (Timestamp: {1710,847802,479271})
(<0.92.0>) << {[alias|#Ref<0.0.11779.270031856.1478295555.230456>],
               [{stdlib,"ERTS  CXC 138 10","5.2.1"},
                {kernel,"ERTS  CXC 138 10","9.2.2"}]} (Timestamp: {1710,
                                                                   847802,
                                                                   479274})
[{stdlib,"ERTS  CXC 138 10","5.2.1"},
 {kernel,"ERTS  CXC 138 10","9.2.2"}]

In this example, four trace events are generated:

  • A send event (!) for the sending of a request from the current process to the application_controller process.
  • A schedule-out event (out) when the current process schedules out while waiting in a receive for the reply to arrive.
  • A schedule-in event (in) when the current process is scheduled in when reply has arrived.
  • A receive event (<<) when the current process retrieves the reply from the application_controller process.

The dbg:c/4 function has a fourth argument for specifying the trace flags, (see flags).

How-to Trace Systems

For more control, another way of tracing is to explicitly start a tracer and set the trace flags of your choice on the processes you want to trace. This is useful, when there is a complex system of processes, ports or nodes interacting where dbg:c/3 is to blunt.

Starting a Tracer (dbg:tracer/0,2)

First, you need to start a tracer process that will receive and display trace messages.

1> dbg:tracer().  % Start the default trace message receiver
{ok,<0.90.0>}     % <0.90.0> is the PID of the tracer process

This starts a server on the local node that will be the recipient of all trace messages. It uses a default handler that prints formatted trace messages to the Erlang shell.

If you need a custom tracer other than the default, you can create a tracer using dbg:tracer(Type, Data):

  • Type = process: Data is {HandlerFun, InitialState}. HandlerFun is a fun/2 that takes the trace message and the previous state, returning a new state.
  • Type = port: Data is a fun/0 that returns a new trace port (e.g., created by dbg:trace_port/2).
  • Type = module: Data is {TracerModule, TracerState} or a fun/0 returning this, for use with erl_tracer.
  • Type = file: Data is a filename where traces will be printed.

Note that only one tracer using this method can be started at a time. You can use trace sessions to start multiple tracers (see trace sessions).

Tracing Processes and Ports (dbg:p/1,2)

Once the tracer is started, you can tell dbg which processes or ports to trace and what events to trace for them using dbg:p(Item, Flags).

Item can be:

  • pid/0 or port/0 - The corresponding process or port is traced. The process or port can be a remote process or port (on another Erlang node). The node must be in the list of traced nodes (see dbg:n/1 and dbg:tracer/3).

  • all - All processes and ports in the system as well as all processes and ports created hereafter are to be traced.

  • processes - All processes in the system as well as all processes created hereafter are to be traced.

  • ports - All ports in the system as well as all ports created hereafter are to be traced.

  • new - All processes and ports created after the call are to be traced.

  • new_processes - All processes created after the call are to be traced.

  • new_ports - All ports created after the call are to be traced.

  • existing - All existing processes and ports are traced.

  • existing_processes - All existing processes are traced.

  • existing_ports - All existing ports are traced.

  • atom/0 - The process or port with the corresponding registered name is traced. The process or port can on another Erlang node. The node must be in the list of traced nodes (see dbg:n/1 and dbg:tracer/3).

  • integer/0 - The process <0.Item.0> is traced.

  • {X, Y, Z} - The process <X.Y.Z> is traced.

  • string/0 - If the Item is a string "<X.Y.Z>" as returned from pid_to_list/1, the process <X.Y.Z> is traced.

Flags can be a single atom or a list of flags. The available flags are:

  • s (send) - Traces the messages the process or port sends.

  • r (receive) - Traces the messages the process or port receives.

  • m (messages) - Traces the messages the process or port receives and sends.

  • c (call) - Traces global function calls for the process according to the trace patterns set in the system (see dbg:tp/2).

  • p (procs) - Traces process related events to the process.

  • ports - Traces port related events to the port.

  • sos (set on spawn) - Lets all processes created by the traced process inherit the trace flags of the traced process.

  • sol (set on link) - Lets another process, P2, inherit the trace flags of the traced process whenever the traced process links to P2.

  • sofs (set on first spawn) - This is the same as sos, but only for the first process spawned by the traced process.

  • sofl (set on first link) - This is the same as sol, but only for the first call to link/1 by the traced process.

  • all - Sets all flags except silent.

  • clear - Clears all flags.

  • Other flags accepted by trace:process/4 or trace:port/4 (e.g., timestamp, arity, return_to).

dbg:p(Item) is a shorthand for dbg:p(Item, [m]).

This function returns either an error tuple or an {ok, List} tuple. The List consists of specifications of how many processes and ports that matched (in the case of a single pid exactly 1). The specification of matched processes is {matched, Node, N}. If the remote processor call (using rpc) to a remote node fails, the rpc error message is returned as the fourth element in the tuple and the number of matched processes is 0.

Example: Trace messages and process events for a specific process

1> Pid = spawn(fun() -> receive {From,Msg} -> From ! Msg end end).
<0.90.0>
2> dbg:tracer().
{ok,<0.92.0>}
3> dbg:p(Pid, [m,procs]). % Trace messages and process events for Pid
{ok,[{matched,nonode@nohost,1}]}
4> Pid ! {self(),hello}.
(<0.90.0>) << {<0.88.0>,hello} % Received by Pid
{<0.88.0>,hello}
(<0.90.0>) <0.88.0> ! hello    % Sent by Pid
(<0.90.0>) exit normal         % Process event: Pid exited
5> flush().
Shell got hello
ok

Tracing Function Calls (dbg:tp/2,3,4, dbg:tpl/2,3,4)

To trace function calls, you need to:

  1. Enable the c/call flag for the process(es) that will make the calls (using dbg:p/2).
  2. Set a trace pattern for the function(s) you want to trace using dbg:tp/2 or dbg:tpl/2.

tp stands for trace pattern (for exported functions by default). tpl stands for trace pattern local (for local or remote calls to local and exported functions).

The general syntax is dbg:tp(ModuleOrMFA, MatchSpec) or dbg:tpl(ModuleOrMFA, MatchSpec).

ModuleOrMFA can be:

  • Module :: atom(): Equivalent to {Module, '_', '_'} (trace all functions in the module).
  • {Module, Function, Arity}: Trace specific function. '_' can be used as a wildcard.

Note that if the Module is specified as '_', the Function and Arity parts must be specified as '_' as well. The same holds for the Function in relation to Arity.

MatchSpec defines what to trace and how, see match specifications.

  • For simple call tracing, you can insert the empty list [].
  • The most common generic match specifications used can be found as built-in aliases.
    • x or exception_trace: Shows function names, parameters, return values, and exceptions. [{'_',[],[{exception_trace}]}]
    • c or caller_trace: Shows function names, parameters, and caller information. [{'_',[],[{message,{caller_line}}]}]
    • cx or caller_exception_trace: Combines x and c. [{'_',[],[{exception_trace},{message,{caller_line}}]}]

Example using built-in aliases:

1> dbg:tracer().
{ok,<0.90.0>}
2> dbg:p(all, c). % Short for dbg:p(all, call)
{ok,[{matched,nonode@nohost,49}]}
3> dbg:tp(lists, seq, cx). % cx: call and exception tracing with caller info
{ok,[{matched,nonode@nohost,2},{saved,cx}]}
4> lists:seq(1, 3).
(<0.88.0>) call lists:seq(1,3) ({erl_eval,do_apply,7,{"erl_eval.erl",904}})
[1,2,3]
(<0.88.0>) returned from lists:seq/2 -> [1,2,3]

Note that the caller info is the function that called lists:seq with file and line number.

Tracing Message Events (dbg:tpe/2)

By default, if send or receive tracing is enabled for a process, all such events are traced. dbg:tpe(Event, MatchSpec) allows you to filter these events.

  • Event: send or 'receive'.
  • MatchSpec: A match specifications.
    • For send: Matches on [Receiver, Msg].
    • For 'receive': Matches on [Node, Sender, Msg].

Managing Trace Patterns

You can display, remove, save and load trace pattern matchspecifications, if any of these bullets are of interest, click the title to read the documentation.

To stop tracing specific functions, you clear their trace patterns.

  • dbg:ctp(ModuleOrMFA): Clears both global and local trace patterns.
    • ctp(): Clears all trace patterns for all functions.
    • ctp(Module): Clears patterns for all functions in Module.
    • ctp(Module, Function) / ctp(Module, Function, Arity): More specific.
  • dbg:ctpl(ModuleOrMFA): Clears only local trace patterns (set by tpl).
  • dbg:ctpg(ModuleOrMFA): Clears only global trace patterns (set by tp).
  • dbg:ctpe(Event): Clears the match specification for send or 'receive', reverting to tracing all such events if the flag is set.

Match Specifications

Match Specifications are a powerful mini-language used to define conditions for tracing and actions to take. The dbg:tp/2, dbg:tpl/2 and dbg:tpe/2 functions accept them. For a description of the format for the MatchSpec argument, see Match Specifications in Erlang, which explains the general match specification language. For most users, dbg:fun2ms/1 explained below will do.

A match specification is a list of tuples: [{MatchHead, Guard, BodyActions}].

  • MatchHead: Patterns to match function arguments. '_' matches anything. '$1' is a variable.
  • Guard: Conditions that must be true.
  • BodyActions: Actions like {return_trace} (trace return value), {message, term()} (include extra info), {set_seq_token, ...}.

Creating Match Specifications with dbg:fun2ms/1

You can use dbg:fun2ms/1 to translate a literal Erlang fun into a match specification. This often feels more natural than writing the raw match spec. The fun must take a single list argument (matching the function arguments) and its body can use guard expressions and special tracing functions. The parse transform module ms_transform must be enabled. The easiest way to enable it is by adding the following line to the source file: -include_lib("stdlib/include/ms_transform.hrl"). In the shell its already enabled.

The head of the fun must be a single pattern that matches a list. That pattern will be used to match the arguments for the call:

1> dbg:fun2ms(fun([_,_]) -> true end). % Matches a function with two arguments
[{['_','_'],[],[true]}]
2> dbg:fun2ms(fun([A]) when is_atom(A) -> return_trace() end).
[{['$1'],[{is_atom,'$1'}],[{return_trace}]}]

The first match specification matches when a function having two arguments is called. The second matches when a function, taking one atom as an argument, is called.

Trace Sessions

To avoid interference between different tracing activities, you can create isolated dbg sessions.

First you create a session with dbg:session_create(Name) where the name is an atom, a session/0 is returned. Several sessions may have the same name.

When you have the session/0, you use dbg:session(Session, Fun). This function runs dbg commands within Fun using the specified session.

Any dbg function that is called with in the provided fun will use the session/0 provided instead of the default dbg session. This means that the tracing will be isolated from other tracing users on the system.

When you no longer need the session, use dbg:session_destroy(Session).

Example:

1> S = dbg:session_create(my_session).
<0.91.0>
2> dbg:session(S, fun() -> dbg:tracer(), dbg:p(all,c), dbg:tp(lists,seq,x) end).
{ok,[{matched,nonode@nohost,2},{saved,x}]}
3> lists:seq(1, 10).
(<0.89.0>) call lists:seq(1,10)
(<0.89.0>) returned from lists:seq/2 -> [1,2,3,4,5,6,7,8,9,10]
[1,2,3,4,5,6,7,8,9,10]
4> dbg:session_destroy(S).
ok

The state of the session/0 is preserved in between dbg:session/2 calls, so you can call dbg:session/2 multiple times when debugging you application.

Example:

1> S = dbg:session_create(my_session).
<0.91.0>
%% Setup the initial traces
2> dbg:session(S, fun() -> dbg:tracer(), dbg:p(self(),c), dbg:tp(lists,seq,x) end).
{ok,[{matched,nonode@nohost,2},{saved,x}]}
3> lists:seq(1, 3).
(<0.89.0>) call lists:seq(1,3)
(<0.89.0>) returned from lists:seq/2 -> [1,2,3]
[1,2,3]
%% Add an additional trace pattern
4> dbg:session(S, fun() -> dbg:tpl(lists,seq_loop,x) end).
ok
5> lists:seq(1, 3).
(<0.89.0>) call lists:seq(1,3)
(<0.89.0>) call lists:seq_loop(3,3,[])
(<0.89.0>) call lists:seq_loop(1,1,[2,3])
(<0.89.0>) returned from lists:seq_loop/3 -> [1,2,3]
(<0.89.0>) returned from lists:seq_loop/3 -> [1,2,3]
(<0.89.0>) returned from lists:seq/2 -> [1,2,3]
[1,2,3]
6> dbg:session_destroy(S).
ok

Trace on Remote Nodes

The dbg server keeps a list of nodes where tracing should be performed. Whenever a dbg:tp/2 call or a dbg:p/2 call is made, it is executed for all nodes in this list including the local node (except for dbg:p/2 with a specific pid/0 or port/0 as first argument, in which case the command is executed only on the node where the designated process or port resides).

dbg:n(Nodename): When this function is called, it starts a tracer process on the remote node, which will send all trace messages to the tracer process on the local node (via the Erlang distribution). If no tracer process is running on the local node, the error reason no_local_tracer is returned. The tracer process on the local node must be started with the dbg:tracer/0,2 function.

If Nodename is the local node, the error reason cant_add_local_node is returned. The function will also return an error if the node Nodename is not reachable.

If a trace port (see dbg:trace_port/2) is running on the local node, remote nodes cannot be traced with a tracer process. The error reason cant_trace_remote_pid_to_local_port is returned. However, a trace port can be started on the remote node with the dbg:tracer/3 function.

dbg:tracer(Nodename, Type, Data): An independent tracer is started on the node (Nodename) and the node is added to the list of traced nodes.

Note

dbg:tracer(Nodename, Type, Data) is not equivalent to dbg:n/1. While dbg:n/1 starts a process tracer which redirects all trace information to a process tracer on the local node (that is, the trace control node), dbg:tracer/3 starts any type of tracer, independent of the type of tracer on the trace control node.

Managing nodes

dbg can trace processes and functions on other Erlang nodes in a distributed system.

Trace Ports for Lower Overhead

For high-volume tracing, sending messages to an Erlang process can be too slow. A trace port is an Erlang port to a dynamically linked-in driver that handles trace messages directly, without the overhead of sending them as messages to an Erlang process. Using a trace port significantly lowers the overhead imposed by tracing.

Creating Trace Ports (dbg:trace_port/2)

dbg:trace_port(Type, Parameters) returns a fun/0 that, when called, creates and returns a port. This fun is then passed as the second argument to dbg:tracer(port, Fun).

Two trace drivers are available: the file and the ip trace drivers.

  • file: Writes trace messages to binary file(s).
    • Parameters: Filename or a wrap files specification: {Filename, wrap, Suffix} {Filename, wrap, Suffix, WrapSize} {Filename, wrap, Suffix, WrapSize, WrapCnt} {Filename, wrap, Suffix, {time, WrapTime}, WrapCnt} Wrap files limit disk space by rotating through WrapCnt files, each up to WrapSize or open for WrapTime.
  • ip: Opens a TCP/IP listening port. A client connects to receive trace messages.
    • Parameters: PortNumber or {PortNumber, QueSize}.

The file trace driver expects a filename or a wrap files specification as parameter. A file is written with a high degree of buffering, which is why there is no guarantee that all are saved in the file in case of a system crash.

A wrap files specification is used to limit the disk space consumed by the trace. The trace is written to a limited number of files each with a limited size. The actual filenames are Filename ++ SeqCnt ++ Suffix, where SeqCnt counts as a decimal string from 0 to WrapCnt and then around again from 0. When a trace term written to the current file makes it longer than WrapSize, that file is closed, and if the number of files in this wrap trace is as many as WrapCnt the oldest file is deleted, and a new file is opened to become the current. Thus, when a wrap trace has been stopped, there are at most WrapCnt trace files saved with a size of at least WrapSize (but not much larger), except for the last file that might even be empty. The default values are WrapSize = 128*1024 and WrapCnt = 8.

The SeqCnt values in the filenames are all in the range 0 through WrapCnt with a gap in the circular sequence. The gap is needed to find the end of the trace.

If the WrapSize is specified as {time, WrapTime}, the current file is closed when it has been open more than WrapTime milliseconds, regardless of it being empty or not.

The ip trace driver has a queue of QueSize messages waiting to be delivered. If the driver cannot deliver messages as fast as they are produced by the runtime system, a special message is sent, which indicates how many messages that are dropped. That message will arrive at the handler function specified in dbg:trace_client/3 as the tuple {drop, N} where N is the number of consecutive messages dropped. In case of heavy tracing, drops are likely to occur, and they surely occur if no client is reading the trace messages. The default value of QueSize is 200.

Reading Trace Port Data (dbg:trace_client/2,3)

dbg:trace_client(Type, Parameters) Starts a trace client that reads the output created by a trace port driver (see dbg:trace_port/2) and handles it in mostly the same way as a tracer process created by the dbg:tracer/0 function. dbg:trace_client(Type, Parameters, HandlerSpec) This function works exactly as dbg:trace_client/2, but allows you to write your own handler function.

If Type is file, the client reads all trace messages stored in the file named Filename or specified by WrapFilesSpec (must be the same as used when creating the trace) and lets the default handler function format the messages on the console. This is one way to interpret the data stored in a file by the file trace port driver.

If Type is follow_file, the client behaves as in the file case, but keeps trying to read (and process) more data from the file until stopped by dbg:stop_trace_client/1. WrapFilesSpec is not allowed as second argument for this Type.

If Type is ip, the client connects to the TCP/IP port PortNumber on the host Hostname, from where it reads trace messages until the TCP/IP connection is closed. If no Hostname is specified, the local host is assumed.

The handler function works mostly as the one described in dbg:tracer/2, but must also be prepared to handle trace messages of the form {drop, N}, where N is the number of dropped messages. This pseudo trace message will only occur if the ip trace driver is used.

For trace type file, the pseudo trace message end_of_trace will appear at the end of the trace. The return value from the handler function is in this case ignored.

Example: Using an IP trace port and connecting to it from another node As an example, one can let trace messages be sent over the network to another Erlang node (preferably not distributed), where the formatting occurs.

On the node stack there exists an Erlang node ant@stack. In the shell, type the following:

ant@stack> dbg:tracer(port, dbg:trace_port(ip, 4711)).
<0.17.0>
ant@stack> dbg:p(self(), send).
{ok,1}

All trace messages are now sent to the trace port driver, which in turn listens for connections on the TCP/IP port 4711. If we want to see the messages on another node, preferably on another host, we do like this:

1> dbg:trace_client(ip, {"stack", 4711}).
<0.42.0>

If we now send a message from the shell on the node ant@stack, where all sends from the shell are traced:

ant@stack> self() ! hello.
hello

The following will appear at the console on the node that started the trace client:

(<0.23.0>) <0.23.0> ! hello
(<0.23.0>) <0.22.0> ! {shell_rep,<0.23.0>,{value,hello,[],[]}}

The last line is generated due to internal message passing in the Erlang shell. The pids will vary.

Controlling Trace Ports

Sequential Tracing

The dbg module is primarily targeted towards tracing through the trace:process/4 function. It is sometimes desired to trace messages in a more delicate way, which can be done with the help of the seq_trace module.

seq_trace implements sequential tracing (known in the AXE10 world, and sometimes called "forlopp tracing"). dbg can interpret messages generated from seq_trace and the same tracer function for both types of tracing can be used. The seq_trace messages can also be sent to a trace port for further analysis.

As a match specification can turn on sequential tracing, the combination of dbg and seq_trace can be powerful. This brief example shows a session where sequential tracing is used to trace the dbg module and the trace itself:

1> dbg:tracer().
{ok,<0.30.0>}
2> {ok, Tracer} = dbg:get_tracer().
{ok,<0.31.0>}
3> seq_trace:set_system_tracer(Tracer).
false
4> dbg:tp(dbg, get_tracer, 0, [{[],[],[{set_seq_token, send, true}]}]).
{ok,[{matched,nonode@nohost,1},{saved,1}]}
5> dbg:p(all,call).
{ok,[{matched,nonode@nohost,22}]}
6> dbg:get_tracer(), seq_trace:set_token([]).
(<0.25.0>) call dbg:get_tracer()
SeqTrace [0]: (<0.25.0>) <0.30.0> ! {<0.25.0>,get_tracer} [Serial: {2,4}]
SeqTrace [0]: (<0.30.0>) <0.25.0> ! {dbg,{ok,<0.31.0>}} [Serial: {4,5}]
{1,0,5,<0.30.0>,4}

This session sets the system_tracer to the same process as the ordinary tracer process (i. e. <0.31.0>) and sets the trace pattern for the function dbg:get_tracer to one that has the action of setting a sequential token. When the function is called by a traced process (all processes are traced in this case), the process gets "contaminated" by the token and seq_trace messages are sent both for the server request and the response. The seq_trace:set_token([]) after the call clears the seq_trace token, which is why no messages are sent when the answer propagates via the shell to the console port. Otherwise the output would have been more noisy.

Avoiding Overloads

Tracing can generate a significant amount of data, potentially overwhelming your system if not managed carefully. To prevent performance degradation or even crashes, consider these strategies:

Time-Limited Tracing: One effective method is to automatically stop tracing after a set period.

dbg:tracer(), dbg:p(all,[c]), dbg:tpl(lists,map,x), timer:sleep(1000), dbg:stop().

Be Specific:

Processes: Instead of dbg:p(all, Flags)., try to pinpoint specific processes if you know which ones are relevant: dbg:p(Pid, Flags). You can also trace newly spawned processes with dbg:p(new, Flags)..

Modules & Functions: Rather than tracing all calls, narrow down to specific modules and functions with dbg:tp/2 or dbg:tpl/2

Use Match Specifications: For example, to only trace calls to my_module:my_function/1 when the first argument is the atom error:

dbg:tpl(my_module, my_function, dbg:fun2ms(fun([error])->true end)).

Limit Trace Flags: Only enable the flags essential for your debugging task (e.g., m for message passing, c for function calls).

Trace to File for High Volumes: If you anticipate a large volume of trace data, tracing directly to the console can become a bottleneck. Consider tracing to a file instead, see trace ports.

dbg:tracer(port, {file, "trace_output.log"}),
% ... your other dbg commands ...
timer:sleep(5000),
dbg:stop().

Avoiding Deadlocks

When tracing function calls on a group leader process (an I/O process), there is risk of causing a deadlock. This will happen if a group leader process generates a trace message and the tracer process, by calling the trace handler function, sends an I/O request to the same group leader. The problem can only occur if the trace handler prints to the tty using an io function such as format/2. Note that when dbg:p(all, call) is called, IO processes are also traced. Here is an example:

%% Using a default line editing shell
1> dbg:tracer(process, {fun(Msg,_) -> io:format("~p~n", [Msg]), 0 end, 0}).
{ok,<0.37.0>}
2> dbg:p(all, [call]).
{ok,[{matched,nonode@nohost,25}]}
3> dbg:tp(mymod,[{'_',[],[]}]).
{ok,[{matched,nonode@nohost,0},{saved,1}]}
4> mymod: % TAB pressed here
%% -- Deadlock --

Here is another example:

%% Using a shell without line editing (oldshell)
1> dbg:tracer(process).
{ok,<0.31.0>}
2> dbg:p(all, [call]).
{ok,[{matched,nonode@nohost,25}]}
3> dbg:tp(lists,[{'_',[],[]}]).
{ok,[{matched,nonode@nohost,0},{saved,1}]}
% -- Deadlock --

The reason we get a deadlock in the first example is because when TAB is pressed to expand the function name, the group leader (which handles character input) calls mymod:module_info(). This generates a trace message which, in turn, causes the tracer process to send an IO request to the group leader (by calling io:format/2). We end up in a deadlock.

In the second example we use the default trace handler function. This handler prints to the tty by sending IO requests to the user process. When Erlang is started in the oldshell mode, the shell process will have user as its group leader and so will the tracer process in this example. Since user calls functions in lists we end up in a deadlock as soon as the first IO request is sent.

Here are a few suggestions for avoiding deadlock:

  • Do not trace the group leader of the tracer process. If tracing has been switched on for all processes, call dbg:p(TracerGLPid, clear) to stop tracing the group leader (TracerGLPid). process_info(TracerPid, group_leader) tells you which process this is (TracerPid is returned from dbg:get_tracer/0).
  • Do not trace the user process if using the default trace handler function.
  • In your own trace handler function, call erlang:display/1 instead of an io function or, if user is not used as group leader, print to user instead of the default group leader. Example: io:format(user, Str, Args).

Getting Information and Help