aboutsummaryrefslogtreecommitdiffstats
path: root/lib/runtime_tools/src/dbg.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/runtime_tools/src/dbg.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/runtime_tools/src/dbg.erl')
-rw-r--r--lib/runtime_tools/src/dbg.erl1749
1 files changed, 1749 insertions, 0 deletions
diff --git a/lib/runtime_tools/src/dbg.erl b/lib/runtime_tools/src/dbg.erl
new file mode 100644
index 0000000000..66ac0422eb
--- /dev/null
+++ b/lib/runtime_tools/src/dbg.erl
@@ -0,0 +1,1749 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(dbg).
+-export([p/1,p/2,c/3,c/4,i/0,start/0,stop/0,stop_clear/0,tracer/0,
+ tracer/2, tracer/3, get_tracer/0, get_tracer/1, tp/2, tp/3, tp/4,
+ ctp/0, ctp/1, ctp/2, ctp/3, tpl/2, tpl/3, tpl/4, ctpl/0, ctpl/1,
+ ctpl/2, ctpl/3, ctpg/0, ctpg/1, ctpg/2, ctpg/3, ltp/0, wtp/1, rtp/1,
+ dtp/0, dtp/1, n/1, cn/1, ln/0, h/0, h/1]).
+
+-export([trace_port/2, flush_trace_port/0, flush_trace_port/1,
+ trace_port_control/1, trace_port_control/2, trace_client/2,
+ trace_client/3, stop_trace_client/1]).
+
+-export([transform_flags/1,dhandler/2]).
+
+-export([fun2ms/1]).
+
+%% Local exports
+-export([erlang_trace/3,get_info/0]).
+
+%% Debug exports
+-export([wrap_presort/2, wrap_sort/2, wrap_postsort/1, wrap_sortfix/2,
+ match_front/2, match_rear/2,
+ match_0_9/1]).
+
+
+%%% Shell callable utility
+fun2ms(ShellFun) when is_function(ShellFun) ->
+ % Check that this is really a shell fun...
+ case erl_eval:fun_data(ShellFun) of
+ {fun_data,ImportList,Clauses} ->
+ case ms_transform:transform_from_shell(
+ ?MODULE,Clauses,ImportList) of
+ {error,[{_,[{_,_,Code}|_]}|_],_} ->
+ io:format("Error: ~s~n",
+ [ms_transform:format_error(Code)]),
+ {error,transform_error};
+ Else ->
+ Else
+ end;
+ false ->
+ exit({badarg,{?MODULE,fun2ms,
+ [function,called,with,real,'fun',
+ should,be,transformed,with,
+ parse_transform,'or',called,with,
+ a,'fun',generated,in,the,
+ shell]}})
+ end.
+
+
+%%% Client functions.
+
+%%
+%% n(Node) -> {ok, Node} | {error, Reason}
+%% Adds Node to the list of traced nodes.
+%%
+n(Node) when Node =:= node() ->
+ {error, cant_add_local_node};
+n(Node) ->
+ case (catch net_adm:ping(Node)) of
+ {'EXIT',_} ->
+ {error, {bad_node, Node}};
+ pang ->
+ {error, {nodedown, Node}};
+ pong ->
+ req({add_node, Node});
+ Other ->
+ {error, Other}
+ end.
+
+%%
+%% cn(Node) -> ok
+%% Remove Node from the list of traced nodes.
+%%
+cn(Node) ->
+ req({remove_node, Node}).
+
+%%
+%% ln() -> ok
+%% List traced nodes
+%%
+ln() ->
+ lists:foreach(fun(X) ->
+ io:format("~p~n",[X])
+ end,
+ req(get_nodes)),
+ ok.
+
+%%
+%% tp/tpl(Module, Pattern) | tp/tpl(Module,Function,Pattern) |
+%% tp/tpl(Module,Function,Arity,Pattern) | tp/tpl({M,F,A},Pattern)
+%% -> {ok, [{matched,Node,N}]} |
+%% {ok, [{matched,Node,N}, {saved,M}]} |
+%% {ok, [{saved,M}]} |
+%% {error, Reason}
+%% Set trace pattern for function or group of functions.
+%%
+tp(Module, Function, Pattern) ->
+ do_tp({Module, Function, '_'}, Pattern, []).
+tp(Module, Function, Arity, Pattern) ->
+ do_tp({Module, Function, Arity}, Pattern, []).
+tp(Module, Pattern) when is_atom(Module) ->
+ do_tp({Module, '_', '_'}, Pattern, []);
+tp({_Module, _Function, _Arity} = X, Pattern) ->
+ do_tp(X,Pattern,[]).
+tpl(Module, Function, Pattern) ->
+ do_tp({Module, Function, '_'}, Pattern, [local]).
+tpl(Module, Function, Arity, Pattern) ->
+ do_tp({Module, Function, Arity}, Pattern, [local]).
+tpl(Module, Pattern) when is_atom(Module) ->
+ do_tp({Module, '_', '_'}, Pattern, [local]);
+tpl({_Module, _Function, _Arity} = X, Pattern) ->
+ do_tp(X,Pattern,[local]).
+do_tp({_Module, _Function, _Arity} = X, Pattern, Flags)
+ when is_integer(Pattern);
+ is_atom(Pattern) ->
+ case ets:lookup(get_pattern_table(), Pattern) of
+ [{_,NPattern}] ->
+ do_tp(X, binary_to_term(NPattern), Flags);
+ _ ->
+ {error, unknown_pattern}
+ end;
+do_tp({Module, _Function, _Arity} = X, Pattern, Flags) when is_list(Pattern) ->
+ Nodes = req(get_nodes),
+ case Module of
+ '_' ->
+ ok;
+ M when is_atom(M) ->
+ %% Try to load M on all nodes
+ lists:foreach(fun(Node) ->
+ rpc:call(Node, M, module_info, [])
+ end,
+ Nodes)
+ end,
+ case lint_tp(Pattern) of
+ {ok,_} ->
+ SaveInfo = case save_pattern(Pattern) of
+ N when is_integer(N), N > 0; is_atom(N) ->
+ [{saved, N}];
+ _ ->
+ []
+ end,
+ {ok, do_tp_on_nodes(Nodes, X, Pattern, Flags) ++ SaveInfo};
+ Other ->
+ Other
+ end.
+
+%% All nodes are handled the same way - also the local node if it is traced
+do_tp_on_nodes(Nodes, MFA, P, Flags) ->
+ lists:map(fun(Node) ->
+ case rpc:call(Node,erlang,trace_pattern,[MFA,P, Flags]) of
+ N when is_integer(N) ->
+ {matched, Node, N};
+ Else ->
+ {matched, Node, 0, Else}
+ end
+ end,
+ Nodes).
+
+%%
+%% ctp/ctpl(Module) | ctp/ctpl(Module,Function) |
+%% ctp/ctpl(Module,Function,Arity) | ctp/ctpl({M,F,A}) ->
+%% {ok, [{matched, N}]} | {error, Reason}
+%% Clears trace pattern for function or group of functions.
+%%
+ctp() ->
+ do_ctp({'_','_','_'},[]).
+ctp(Module, Function) ->
+ do_ctp({Module, Function, '_'}, []).
+ctp(Module, Function, Arity) ->
+ do_ctp({Module, Function, Arity}, []).
+ctp(Module) when is_atom(Module) ->
+ do_ctp({Module, '_', '_'}, []);
+ctp({_Module, _Function, _Arity} = X) ->
+ do_ctp(X,[]).
+ctpl() ->
+ do_ctp({'_', '_', '_'}, [local]).
+ctpl(Module, Function) ->
+ do_ctp({Module, Function, '_'}, [local]).
+ctpl(Module, Function, Arity) ->
+ do_ctp({Module, Function, Arity}, [local]).
+ctpl(Module) when is_atom(Module) ->
+ do_ctp({Module, '_', '_'}, [local]);
+ctpl({_Module, _Function, _Arity} = X) ->
+ do_ctp(X,[local]).
+ctpg() ->
+ do_ctp({'_', '_', '_'}, [global]).
+ctpg(Module, Function) ->
+ do_ctp({Module, Function, '_'}, [global]).
+ctpg(Module, Function, Arity) ->
+ do_ctp({Module, Function, Arity}, [global]).
+ctpg(Module) when is_atom(Module) ->
+ do_ctp({Module, '_', '_'}, [global]);
+ctpg({_Module, _Function, _Arity} = X) ->
+ do_ctp(X,[global]).
+do_ctp({Module, Function, Arity},[]) ->
+ do_ctp({Module, Function, Arity},[global]),
+ do_ctp({Module, Function, Arity},[local]);
+do_ctp({_Module, _Function, _Arity}=MFA,Flags) ->
+ Nodes = req(get_nodes),
+ {ok,do_tp_on_nodes(Nodes,MFA,false,Flags)}.
+
+%%
+%% ltp() -> ok
+%% List saved and built-in trace patterns.
+%%
+ltp() ->
+ pt_doforall(fun({X, El},_Ignore) ->
+ io:format("~p: ~p~n", [X,El])
+ end,[]).
+
+%%
+%% dtp() | dtp(N) -> ok
+%% Delete saved pattern with number N or all saved patterns
+%%
+%% Do not delete built-in trace patterns.
+dtp() ->
+ pt_doforall(fun ({Key, _}, _) when is_integer(Key) ->
+ dtp(Key);
+ ({_, _}, _) ->
+ ok
+ end,
+ []).
+dtp(N) when is_integer(N) ->
+ ets:delete(get_pattern_table(), N),
+ ok;
+dtp(_) ->
+ ok.
+
+%%
+%% wtp(FileName) -> ok | {error, Reason}
+%% Writes all current saved trace patterns to a file.
+%%
+%% Actually write the built-in trace patterns too.
+wtp(FileName) ->
+ case file:open(FileName,[write]) of
+ {error, Reason} ->
+ {error, Reason};
+ {ok, File} ->
+ pt_doforall(fun ({_, Val}, _) when is_list(Val) ->
+ io:format(File, "~p.~n", [Val]);
+ ({_, _}, _) ->
+ ok
+ end,
+ []),
+ file:close(File),
+ ok
+ end.
+
+%%
+%% rtp(FileName) -> ok | {error, Reason}
+%% Reads in previously saved pattern file and merges the contents
+%% with what's there now.
+%%
+%% So the saved built-in trace patterns will merge with
+%% the already existing, which should be the same.
+rtp(FileName) ->
+ T = get_pattern_table(),
+ case file:consult(FileName) of
+ {error, Reason1} ->
+ {error, {read_error, Reason1}};
+ {ok, Data} ->
+ case check_list(Data) of
+ ok ->
+ lists:foreach(fun(X) ->
+ save_pattern(X,T)
+ end, Data),
+ ok;
+ {error, Reason2} ->
+ {error, {file_format_error, Reason2}}
+ end
+ end.
+
+tracer() ->
+ tracer(process, {fun dhandler/2,user}).
+
+tracer(port, Fun) when is_function(Fun) ->
+ start(Fun);
+
+tracer(port, Port) when is_port(Port) ->
+ start(fun() -> Port end);
+
+tracer(process, {Handler,HandlerData}) ->
+ start(fun() -> start_tracer_process(Handler, HandlerData) end).
+
+
+remote_tracer(port, Fun) when is_function(Fun) ->
+ remote_start(Fun);
+
+remote_tracer(port, Port) when is_port(Port) ->
+ remote_start(fun() -> Port end);
+
+remote_tracer(process, {Handler,HandlerData}) ->
+ remote_start(fun() -> start_tracer_process(Handler, HandlerData) end).
+
+remote_start(StartTracer) ->
+ case (catch StartTracer()) of
+ {'EXIT', Reason} ->
+ {error, Reason};
+ Tracer ->
+ {ok,Tracer}
+ end.
+
+%%
+%% tracer(Node,Type,Data) -> {ok, Node} | {error, Reason}
+%% Add Node to the list of traced nodes and a trace port defined by
+%% Type and Data is started on Node.
+%%
+tracer(Node,Type,Data) when Node =:= node() ->
+ case tracer(Type,Data) of
+ {ok,_Dbg} -> {ok,Node};
+ Error -> Error
+ end;
+tracer(Node,Type,Data) ->
+ case (catch net_adm:ping(Node)) of
+ {'EXIT',_} ->
+ {error, {bad_node, Node}};
+ pang ->
+ {error, {nodedown, Node}};
+ pong ->
+ req({add_node, Node, Type, Data});
+ Other ->
+ {error, Other}
+ end.
+
+flush_trace_port() ->
+ trace_port_control(flush).
+flush_trace_port(Node) ->
+ trace_port_control(Node, flush).
+
+trace_port_control(Operation) ->
+ trace_port_control(node(), Operation).
+
+trace_port_control(Node, flush) ->
+ Ref = erlang:trace_delivered(all),
+ receive
+ {trace_delivered,all,Ref} -> ok
+ end,
+ case trace_port_control(Node, $f, "") of
+ {ok, [0]} ->
+ ok;
+ {ok, _} ->
+ {error, not_supported_by_trace_driver};
+ Other ->
+ Other
+ end;
+trace_port_control(Node,get_listen_port) ->
+ case trace_port_control(Node,$p, "") of
+ {ok, <<0, IpPort:16>>} ->
+ {ok, IpPort};
+ {ok, _Other} ->
+ {error, not_supported_by_trace_driver};
+ Other ->
+ Other
+ end.
+
+trace_port_control(Node, Command, Arg) ->
+ case get_tracer(Node) of
+ {ok, Port} when is_port(Port) ->
+ {ok, catch rpc:call(Node,erlang,port_control,[Port, Command, Arg])};
+ _ ->
+ {error, no_trace_driver}
+ end.
+
+
+
+
+trace_port(file, {Filename, wrap, Tail}) ->
+ trace_port(file, {Filename, wrap, Tail, 128*1024});
+trace_port(file, {Filename, wrap, Tail, WrapSize}) ->
+ trace_port(file, {Filename, wrap, Tail, WrapSize, 8});
+trace_port(file, {Filename, wrap, Tail, WrapSize, WrapCnt})
+ when is_list(Tail),
+ is_integer(WrapSize), WrapSize >= 0, WrapSize < (1 bsl 32),
+ is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) ->
+ trace_port1(file, Filename, {wrap, Tail, WrapSize, WrapCnt, 0});
+trace_port(file, {Filename, wrap, Tail, {time, WrapTime}, WrapCnt})
+ when is_list(Tail),
+ is_integer(WrapTime), WrapTime >= 1, WrapTime < (1 bsl 32),
+ is_integer(WrapCnt), WrapCnt >= 1, WrapCnt < (1 bsl 32) ->
+ trace_port1(file, Filename, {wrap, Tail, 0, WrapCnt, WrapTime});
+trace_port(file, Filename) ->
+ trace_port1(file, Filename, nowrap);
+
+trace_port(ip, Portno) when is_integer(Portno) ->
+ trace_port(ip,{Portno,50});
+
+trace_port(ip, {Portno, Qsiz}) when is_integer(Portno), is_integer(Qsiz) ->
+ fun() ->
+ Driver = "trace_ip_drv",
+ Dir1 = filename:join(code:priv_dir(runtime_tools), "lib"),
+ case catch erl_ddll:load_driver(Dir1, Driver) of
+ ok ->
+ ok;
+ _ ->
+ Dir2 = filename:join(
+ Dir1,
+ erlang:system_info(system_architecture)),
+ catch erl_ddll:load_driver(Dir2, Driver)
+ end,
+ L = lists:flatten(
+ io_lib:format("~s ~p ~p 2",
+ [Driver, Portno, Qsiz])),
+ open_port({spawn, L}, [eof])
+ end.
+
+trace_port1(file, Filename, Options) ->
+ Driver = "trace_file_drv",
+ fun() ->
+ Name = filename:absname(Filename),
+ %% Absname is needed since the driver uses
+ %% the supplied name without further investigations,
+ %% and if the name is relative the resulting path
+ %% might be too long which can cause a bus error
+ %% on vxworks instead of a nice error code return.
+ %% Also, the absname must be found inside the fun,
+ %% in case the actual node where the port shall be
+ %% started is on another node (or even another host)
+ {Wrap, Tail} =
+ case Options of
+ {wrap, T, WrapSize, WrapCnt, WrapTime} ->
+ {lists:flatten(
+ io_lib:format("w ~p ~p ~p ~p ",
+ [WrapSize, WrapCnt, WrapTime,
+ length(Name)])),
+ T};
+ nowrap ->
+ {"", ""}
+ end,
+ Command = Driver ++ " " ++ Wrap ++ "n " ++ Name ++ Tail,
+ Dir1 = filename:join(code:priv_dir(runtime_tools), "lib"),
+ case catch erl_ddll:load_driver(Dir1, Driver) of
+ ok ->
+ ok;
+ _ ->
+ Dir2 = filename:join(
+ Dir1,
+ erlang:system_info(system_architecture)),
+ catch erl_ddll:load_driver(Dir2, Driver)
+ end,
+ if element(1, Options) == wrap ->
+ %% Delete all files from any previous wrap log
+ Files = wrap_postsort(wrap_presort(Name, Tail)),
+ lists:foreach(
+ fun(N) -> file:delete(N) end,
+ Files);
+ true -> ok
+ end,
+ open_port({spawn, Command}, [eof])
+ end.
+
+
+trace_client(file, Filename) ->
+ trace_client(file, Filename, {fun dhandler/2,user});
+trace_client(follow_file, Filename) ->
+ trace_client(follow_file, Filename, {fun dhandler/2,user});
+trace_client(ip, Portno) when is_integer(Portno) ->
+ trace_client1(ip, {"localhost", Portno}, {fun dhandler/2,user});
+trace_client(ip, {Host, Portno}) when is_integer(Portno) ->
+ trace_client1(ip, {Host, Portno}, {fun dhandler/2,user}).
+
+trace_client(file, {Filename, wrap, Tail}, FD) ->
+ trace_client(file, {Filename, wrap, Tail, 128*1024}, FD);
+trace_client(file, {Filename, wrap, Tail, WrapSize}, FD) ->
+ trace_client(file, {Filename, wrap, Tail, WrapSize, 8}, FD);
+trace_client(file,
+ {_Filename, wrap, Tail, _WrapSize, WrapCnt} = WrapSpec,
+ {Fun, _Data} = FD)
+ when is_list(Tail), is_function(Fun), is_integer(WrapCnt), WrapCnt >= 1 ->
+ trace_client1(file, WrapSpec, FD);
+trace_client(file, Filename, {Fun, Data} ) when is_function(Fun) ->
+ trace_client1(file, Filename, {Fun, Data});
+trace_client(follow_file, Filename, {Fun, Data} ) when is_function(Fun) ->
+ trace_client1(follow_file, Filename, {Fun, Data});
+trace_client(ip, Portno, {Fun, Data}) when is_integer(Portno), is_function(Fun) ->
+ trace_client1(ip, {"localhost", Portno}, {Fun, Data});
+trace_client(ip, {Host, Portno}, {Fun, Data}) when is_integer(Portno),
+ is_function(Fun) ->
+ trace_client1(ip, {Host, Portno}, {Fun, Data}).
+
+trace_client1(Type, OpenData, {Handler,HData}) ->
+ case req({link_to,
+ spawn(
+ fun() ->
+ tc_loop(gen_reader(Type, OpenData), Handler, HData)
+ end)}) of
+ {ok, Pid} ->
+ Pid;
+ Other ->
+ Other
+ end.
+
+stop_trace_client(Pid) when is_pid(Pid) ->
+ process_flag(trap_exit,true),
+ link(Pid),
+ exit(to_pidspec(Pid),abnormal),
+ Res = receive
+ {'EXIT', Pid, _} ->
+ ok
+ after 5000 ->
+ {error, timeout}
+ end,
+ process_flag(trap_exit,false),
+ Res.
+
+p(Pid) ->
+ p(Pid, [m]).
+
+p(Pid, Flags) when is_atom(Flags) ->
+ p(Pid, [Flags]);
+p(Pid, Flags) ->
+ req({p,Pid,Flags}).
+
+i() -> req(i).
+
+c(M, F, A) ->
+ c(M, F, A, all).
+c(M, F, A, Flags) when is_atom(Flags) ->
+ c(M, F, A, [Flags]);
+c(M, F, A, Flags) ->
+ case transform_flags(Flags) of
+ {error,Reason} -> {error,Reason};
+ Flags1 ->
+ tracer(),
+ {ok, Tracer} = get_tracer(),
+ S = self(),
+ Pid = spawn(fun() -> c(S, M, F, A, [{tracer, Tracer} | Flags1]) end),
+ Mref = erlang:monitor(process, Pid),
+ receive
+ {'DOWN', Mref, _, _, Reason} ->
+ stop_clear(),
+ {error, Reason};
+ {Pid, Res} ->
+ erlang:demonitor(Mref),
+ receive {'DOWN', Mref, _, _, _} -> ok after 0 -> ok end,
+ %% 'sleep' prevents the tracer (recv_all_traces) from
+ %% receiving garbage {'EXIT',...} when dbg i stopped.
+ timer:sleep(1),
+ stop_clear(),
+ Res
+ end
+ end.
+
+c(Parent, M, F, A, Flags) ->
+ %% The trace BIF is used directly here instead of the existing function
+ %% p/2. The reason is that p/2 (when stopping trace) sends messages which
+ %% we don't want to show up in this simple tracing from the shell.
+ erlang:trace(self(), true, Flags),
+ Res = apply(M, F, A),
+ erlang:trace(self(), false, [all]),
+ Parent ! {self(), Res}.
+
+stop() ->
+ Mref = erlang:monitor(process, dbg),
+ catch dbg ! {self(),stop},
+ receive
+ {'DOWN',Mref,_,_,_} ->
+ ok
+ end.
+
+stop_clear() ->
+ ctp(),
+ stop().
+
+%%% Calling the server.
+
+req(R) ->
+ P = ensure(), % The pid or perhaps the name of the server
+ Mref = erlang:monitor(process, P),
+ catch P ! {self(), R}, % May crash if P = atom() and server died
+ receive
+ {'DOWN', Mref, _, _, _} -> % If server died
+ exit(dbg_server_crash);
+ {dbg, Reply} ->
+ erlang:demonitor(Mref),
+ receive {'DOWN', Mref, _, _, _} -> ok after 0 -> ok end,
+ Reply
+ end.
+
+%% Returns the pid of the dbg server, or in worst case the name.
+%% Starts a new server if necessary.
+ensure() ->
+ case whereis(dbg) of
+ undefined ->
+ case start() of
+ {ok, P} ->
+ P;
+ {error, already_started} ->
+ dbg
+ end;
+ Pid ->
+ Pid
+ end.
+
+
+%%% Server implementation.
+start() ->
+ start(no_tracer).
+
+start(TracerFun) ->
+ S = self(),
+ case whereis(dbg) of
+ undefined ->
+ Dbg = spawn(fun() -> init(S) end),
+ receive {Dbg,started} -> ok end,
+ case TracerFun of
+ no_tracer ->
+ {ok, Dbg};
+ Fun when is_function(Fun) ->
+ req({tracer,TracerFun})
+ end;
+ Pid when is_pid(Pid), is_function(TracerFun) ->
+ req({tracer,TracerFun})
+ end.
+
+init(Parent) ->
+ process_flag(trap_exit, true),
+ register(dbg, self()),
+ Parent ! {self(),started},
+ loop({[],[]},[]).
+
+%
+% SurviveLinks = Processes we should take with us while falling,
+% but not get killed by if they die (i. e. trace clients
+% and relay processes on other nodes)
+% SurviveLinks = {TraceClients,Relays}
+%
+loop({C,T}=SurviveLinks, Table) ->
+ receive
+ {From,i} ->
+ reply(From, display_info(lists:map(fun({N,_}) -> N end,get()))),
+ loop(SurviveLinks, Table);
+ {From,{p,Pid,Flags}} ->
+ reply(From, trace_process(Pid, Flags)),
+ loop(SurviveLinks, Table);
+ {From,{tracer,TracerFun}} when is_function(TracerFun) ->
+ case get(node()) of
+ undefined ->
+ case (catch TracerFun()) of
+ {'EXIT', Reason} ->
+ reply(From, {error, Reason});
+ Tracer when is_pid(Tracer); is_port(Tracer) ->
+ put(node(),{self(),Tracer}),
+ reply(From, {ok,self()})
+ end;
+ {_Relay,_Tracer} ->
+ reply(From, {error, already_started})
+ end,
+ loop(SurviveLinks,Table);
+ {From,{get_tracer,Node}} ->
+ case get(Node) of
+ undefined -> reply(From,{error, {no_tracer_on_node,Node}});
+ {_Relay,Tracer} -> reply(From, {ok,Tracer})
+ end,
+ loop(SurviveLinks, Table);
+ {From, get_table} ->
+ Tab = case Table of
+ [] ->
+ new_pattern_table();
+ _exists ->
+ Table
+ end,
+ reply(From, {ok, Tab}),
+ loop(SurviveLinks, Tab);
+ {_From,stop} ->
+ %% We want to make sure that all trace messages have been delivered
+ %% on all nodes that might be traced. Since dbg:cn/1 does not turn off
+ %% tracing on the node it removes from the list of active trace nodes,
+ %% we will call erlang:trace_delivered/1 on ALL nodes that we have
+ %% connections to.
+ Delivered = fun() ->
+ Ref = erlang:trace_delivered(all),
+ receive
+ {trace_delivered,all,Ref} -> ok
+ end
+ end,
+ catch rpc:multicall(nodes(), erlang, apply, [Delivered,[]]),
+ Ref = erlang:trace_delivered(all),
+ receive
+ {trace_delivered,all,Ref} ->
+ exit(done)
+ end;
+ {From, {link_to, Pid}} ->
+ case (catch link(Pid)) of
+ {'EXIT', Reason} ->
+ reply(From, {error, Reason}),
+ loop(SurviveLinks, Table);
+ _ ->
+ reply(From, {ok, Pid}),
+ loop({[Pid|C],T}, Table)
+ end;
+ {From, {add_node, Node}} ->
+ case get(node()) of
+ undefined ->
+ reply(From, {error, no_local_tracer}),
+ loop(SurviveLinks, Table);
+ {_LocalRelay,Tracer} when is_port(Tracer) ->
+ reply(From, {error, cant_trace_remote_pid_to_local_port}),
+ loop(SurviveLinks, Table);
+ {_LocalRelay,Tracer} when is_pid(Tracer) ->
+ case (catch relay(Node, Tracer)) of
+ {ok,Relay} ->
+ reply(From, {ok, Node}),
+ loop({C,[Relay|T]}, Table);
+ {'EXIT', Something} ->
+ reply(From, {error, Something}),
+ loop(SurviveLinks, Table);
+ Error ->
+ reply(From, Error),
+ loop(SurviveLinks, Table)
+ end
+ end;
+ {From, {add_node, Node, Type, Data}} ->
+ case (catch relay(Node, {Type,Data})) of
+ {ok,Relay} ->
+ reply(From, {ok, Node}),
+ loop({C,[Relay|T]}, Table);
+ {'EXIT', Something} ->
+ reply(From, {error, Something}),
+ loop(SurviveLinks, Table);
+ Error ->
+ reply(From, Error),
+ loop(SurviveLinks, Table)
+ end;
+ {From, {remove_node, Node}} ->
+ erase(Node),
+ reply(From, ok),
+ loop(SurviveLinks, Table);
+ {From, get_nodes} ->
+ reply(From, lists:map(fun({N,_}) -> N end, get())),
+ loop(SurviveLinks, Table);
+ {'EXIT', Pid, Reason} ->
+ case lists:delete(Pid, C) of
+ C ->
+ case lists:delete(Pid,T) of
+ T ->
+ io:format(user,"** dbg got EXIT - terminating: ~p~n",
+ [Reason]),
+ exit(done);
+ NewT ->
+ erase(node(Pid)),
+ loop({C,NewT}, Table)
+ end;
+ NewC ->
+ loop({NewC,T}, Table)
+ end;
+ Other ->
+ io:format(user,"** dbg got garbage: ~p~n",
+ [{Other,SurviveLinks,Table}]),
+ loop(SurviveLinks, Table)
+ end.
+
+reply(Pid, Reply) ->
+ Pid ! {dbg,Reply}.
+
+
+%%% A process-based tracer.
+
+start_tracer_process(Handler, HandlerData) ->
+ spawn_opt(fun() -> tracer_init(Handler, HandlerData) end,
+ [link,{priority,max}]).
+
+
+tracer_init(Handler, HandlerData) ->
+ process_flag(trap_exit, true),
+ tracer_loop(Handler, HandlerData).
+
+tracer_loop(Handler, Hdata) ->
+ receive
+ Msg ->
+ %% Don't match in receive to avoid giving EXIT message higher
+ %% priority than the trace messages.
+ case Msg of
+ {'EXIT',_Pid,_Reason} ->
+ ok;
+ Trace ->
+ NewData = recv_all_traces(Trace, Handler, Hdata),
+ tracer_loop(Handler, NewData)
+ end
+ end.
+
+recv_all_traces(Trace, Handler, Hdata) ->
+ Suspended = suspend(Trace, []),
+ recv_all_traces(Suspended, Handler, Hdata, [Trace]).
+
+recv_all_traces(Suspended0, Handler, Hdata, Traces) ->
+ receive
+ Trace when is_tuple(Trace), element(1, Trace) == trace ->
+ Suspended = suspend(Trace, Suspended0),
+ recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]);
+ Trace when is_tuple(Trace), element(1, Trace) == trace_ts ->
+ Suspended = suspend(Trace, Suspended0),
+ recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]);
+ Trace when is_tuple(Trace), element(1, Trace) == seq_trace ->
+ Suspended = suspend(Trace, Suspended0),
+ recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]);
+ Trace when is_tuple(Trace), element(1, Trace) == drop ->
+ Suspended = suspend(Trace, Suspended0),
+ recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]);
+ Other ->
+ %%% Is this really a good idea?
+ io:format(user,"** tracer received garbage: ~p~n", [Other]),
+ recv_all_traces(Suspended0, Handler, Hdata, Traces)
+ after 0 ->
+ case catch invoke_handler(Traces, Handler, Hdata) of
+ {'EXIT',Reason} ->
+ resume(Suspended0),
+ exit({trace_handler_crashed,Reason});
+ NewHdata ->
+ resume(Suspended0),
+ NewHdata
+ end
+ end.
+
+invoke_handler([Tr|Traces], Handler, Hdata0) ->
+ Hdata = invoke_handler(Traces, Handler, Hdata0),
+ Handler(Tr, Hdata);
+invoke_handler([], _Handler, Hdata) ->
+ Hdata.
+
+suspend({trace,From,call,_Func}, Suspended) when node(From) == node() ->
+ case (catch erlang:suspend_process(From, [unless_suspending,
+ asynchronous])) of
+ true ->
+ [From | Suspended];
+ _ ->
+ Suspended
+ end;
+suspend(_Other, Suspended) -> Suspended.
+
+resume([Pid|Pids]) when node(Pid) == node() ->
+ (catch erlang:resume_process(Pid)),
+ resume(Pids);
+resume([]) -> ok.
+
+
+
+%%% Utilities.
+
+trac(Proc, How, Flags) when is_atom(Proc) ->
+ %% Proc = all | new | existing | RegisteredName
+ %% Must go to all nodes
+ case get() of
+ [] ->
+ {error,no_tracers};
+ Nodes ->
+ Matched = [trac(Node, NodeInfo, Proc, How, Flags)
+ || {Node, NodeInfo} <- Nodes],
+ {ok,Matched}
+ end;
+trac(Proc, How, Flags) ->
+ %% Proc = Pid | Integer | {X,Y,Z} | "<X.Y.Z>"
+ %% One node only
+ Pid = to_pid(Proc),
+ case Pid of
+ {badpid,_} ->
+ {error,Pid};
+ _ ->
+ Node = if is_pid(Pid) -> node(Pid); true -> node() end,
+ case get(Node) of
+ undefined ->
+ {error,{no_tracer_on_node,Node}};
+ NodeInfo ->
+ Match = trac(Node, NodeInfo, Pid, How, Flags),
+ {ok,[Match]}
+ end
+ end.
+
+trac(Node, {_Relay, Tracer}, AtomPid, How, Flags) ->
+ case rpc:call(Node, ?MODULE, erlang_trace,
+ [AtomPid, How, [{tracer, Tracer} | Flags]]) of
+ N when is_integer(N) ->
+ {matched, Node, N};
+ {badrpc,Reason} ->
+ {matched, Node, 0, Reason};
+ Else ->
+ {matched, Node, 0, Else}
+ end.
+
+erlang_trace(AtomPid, How, Flags) ->
+ case to_pidspec(AtomPid) of
+ {badpid,_} ->
+ {no_proc,AtomPid};
+ P ->
+ erlang:trace(P, How, Flags)
+ end.
+
+%% Since we are not allowed to do erlang:trace/3 on a remote
+%% process, we create a relay process at the remote node.
+
+relay(Node,To) when Node /= node() ->
+ case get(Node) of
+ undefined ->
+ S = self(),
+ Pid = spawn_link(Node, fun() -> do_relay(S,To) end),
+ receive {started,Remote} -> put(Node, {Pid,Remote}) end,
+ {ok,Pid};
+ {_Relay,PortOrPid} ->
+ {error, {already_started, PortOrPid}}
+ end.
+
+do_relay(Parent,RelP) ->
+ process_flag(trap_exit, true),
+ case RelP of
+ {Type,Data} ->
+ {ok,Tracer} = remote_tracer(Type,Data),
+ Parent ! {started,Tracer};
+ Pid when is_pid(Pid) ->
+ Parent ! {started,self()}
+ end,
+ do_relay_1(RelP).
+
+do_relay_1(RelP) ->
+ %% In the case of a port tracer, this process exists only so that
+ %% dbg know that the node is alive... should maybe use monitor instead?
+ receive
+ {'EXIT', _P, _} ->
+ exit(normal);
+ TraceInfo when is_pid(RelP) -> % Here is the normal case for trace i/o
+ RelP ! TraceInfo,
+ do_relay_1(RelP);
+ Other ->
+ io:format(user,"** relay got garbage: ~p~n", [Other]),
+ do_relay_1(RelP)
+ end.
+
+dhandler(end_of_trace, Out) ->
+ Out;
+dhandler(Trace, Out) when element(1, Trace) == trace, tuple_size(Trace) >= 3 ->
+ dhandler1(Trace, tuple_size(Trace), Out);
+dhandler(Trace, Out) when element(1, Trace) == trace_ts, tuple_size(Trace) >= 4 ->
+ dhandler1(Trace, tuple_size(Trace)-1, Out);
+dhandler(Trace, Out) when element(1, Trace) == drop, tuple_size(Trace) =:= 2 ->
+ io:format(Out, "*** Dropped ~p messages.~n", [element(2,Trace)]),
+ Out;
+dhandler(Trace, Out) when element(1, Trace) == seq_trace, tuple_size(Trace) >= 3 ->
+ SeqTraceInfo = case Trace of
+ {seq_trace, Lbl, STI, TS} ->
+ io:format(Out, "SeqTrace ~p [~p]: ",
+ [TS, Lbl]),
+ STI;
+ {seq_trace, Lbl, STI} ->
+ io:format(Out, "SeqTrace [~p]: ",
+ [Lbl]),
+ STI
+ end,
+ case SeqTraceInfo of
+ {send, Ser, Fr, To, Mes} ->
+ io:format(Out, "(~p) ~p ! ~p [Serial: ~p]~n",
+ [Fr, To, Mes, Ser]);
+ {'receive', Ser, Fr, To, Mes} ->
+ io:format(Out, "(~p) << ~p [Serial: ~p, From: ~p]~n",
+ [To, Mes, Ser, Fr]);
+ {print, Ser, Fr, _, Info} ->
+ io:format(Out, "-> ~p [Serial: ~p, From: ~p]~n",
+ [Info, Ser, Fr]);
+ Else ->
+ io:format(Out, "~p~n", [Else])
+ end,
+ Out;
+dhandler(_Trace, Out) ->
+ Out.
+
+dhandler1(Trace, Size, Out) ->
+%%%! Self = self(),
+ From = element(2, Trace),
+ case element(3, Trace) of
+ 'receive' ->
+ case element(4, Trace) of
+ {dbg,ok} -> ok;
+ Message -> io:format(Out, "(~p) << ~p~n", [From,Message])
+ end;
+ 'send' ->
+ Message = element(4, Trace),
+ case element(5, Trace) of
+%%%! This causes messages to disappear when used by ttb (observer). Tests
+%%%! so far show that there is no difference in results with dbg even if I
+%%%! comment it out, so I hope this is only some old code which isn't
+%%%! needed anymore... /siri
+%%%! Self -> ok;
+ To -> io:format(Out, "(~p) ~p ! ~p~n", [From,To,Message])
+ end;
+ call ->
+ case element(4, Trace) of
+ MFA when Size == 5 ->
+ Message = element(5, Trace),
+ io:format(Out, "(~p) call ~s (~p)~n", [From,ffunc(MFA),Message]);
+ MFA ->
+ io:format(Out, "(~p) call ~s~n", [From,ffunc(MFA)])
+ end;
+ return -> %% To be deleted...
+ case element(4, Trace) of
+ MFA when Size == 5 ->
+ Ret = element(5, Trace),
+ io:format(Out, "(~p) old_ret ~s -> ~p~n", [From,ffunc(MFA),Ret]);
+ MFA ->
+ io:format(Out, "(~p) old_ret ~s~n", [From,ffunc(MFA)])
+ end;
+ return_from ->
+ MFA = element(4, Trace),
+ Ret = element(5, Trace),
+ io:format(Out, "(~p) returned from ~s -> ~p~n", [From,ffunc(MFA),Ret]);
+ return_to ->
+ MFA = element(4, Trace),
+ io:format(Out, "(~p) returning to ~s~n", [From,ffunc(MFA)]);
+ spawn when Size == 5 ->
+ Pid = element(4, Trace),
+ MFA = element(5, Trace),
+ io:format(Out, "(~p) spawn ~p as ~s~n", [From,Pid,ffunc(MFA)]);
+ Op ->
+ io:format(Out, "(~p) ~p ~s~n", [From,Op,ftup(Trace,4,Size)])
+ end,
+ Out.
+
+
+
+%%% These f* functions returns non-flat strings
+
+%% {M,F,[A1, A2, ..., AN]} -> "M:F(A1, A2, ..., AN)"
+%% {M,F,A} -> "M:F/A"
+ffunc({M,F,Argl}) when is_list(Argl) ->
+ io_lib:format("~p:~p(~s)", [M, F, fargs(Argl)]);
+ffunc({M,F,Arity}) ->
+ io_lib:format("~p:~p/~p", [M,F,Arity]);
+ffunc(X) -> io_lib:format("~p", [X]).
+
+%% Integer -> "Integer"
+%% [A1, A2, ..., AN] -> "A1, A2, ..., AN"
+fargs(Arity) when is_integer(Arity) -> integer_to_list(Arity);
+fargs([]) -> [];
+fargs([A]) -> io_lib:format("~p", [A]); %% last arg
+fargs([A|Args]) -> [io_lib:format("~p,", [A]) | fargs(Args)];
+fargs(A) -> io_lib:format("~p", [A]). % last or only arg
+
+%% {A_1, A_2, ..., A_N} -> "A_Index A_Index+1 ... A_Size"
+ftup(Trace, Index, Index) ->
+ io_lib:format("~p", [element(Index, Trace)]);
+ftup(Trace, Index, Size) ->
+ [io_lib:format("~p ", [element(Index, Trace)])
+ | ftup(Trace, Index+1, Size)].
+
+
+
+trace_process(Pid, [clear]) ->
+ trac(Pid, false, all());
+trace_process(Pid, Flags0) ->
+ case transform_flags(Flags0) of
+ {error,Reason} -> {error,Reason};
+ Flags -> trac(Pid, true, Flags)
+ end.
+
+transform_flags(Flags0) ->
+ transform_flags(Flags0,[]).
+transform_flags([],Acc) -> Acc;
+transform_flags([m|Tail],Acc) -> transform_flags(Tail,[send,'receive'|Acc]);
+transform_flags([s|Tail],Acc) -> transform_flags(Tail,[send|Acc]);
+transform_flags([r|Tail],Acc) -> transform_flags(Tail,['receive'|Acc]);
+transform_flags([c|Tail],Acc) -> transform_flags(Tail,[call|Acc]);
+transform_flags([call|Tail],Acc) -> transform_flags(Tail,[call|Acc]);
+transform_flags([p|Tail],Acc) -> transform_flags(Tail,[procs|Acc]);
+transform_flags([sos|Tail],Acc) -> transform_flags(Tail,[set_on_spawn|Acc]);
+transform_flags([sol|Tail],Acc) -> transform_flags(Tail,[set_on_link|Acc]);
+transform_flags([sofs|Tail],Acc) -> transform_flags(Tail,[set_on_first_spawn|Acc]);
+transform_flags([sofl|Tail],Acc) -> transform_flags(Tail,[set_on_first_link|Acc]);
+transform_flags([all|_],_Acc) -> all();
+transform_flags([F|Tail]=List,Acc) when is_atom(F) ->
+ case lists:member(F, all()) of
+ true -> transform_flags(Tail,[F|Acc]);
+ false -> {error,{bad_flags,List}}
+ end;
+transform_flags(Bad,_Acc) -> {error,{bad_flags,Bad}}.
+
+all() ->
+ [send,'receive',call,procs,garbage_collection,running,
+ set_on_spawn,set_on_first_spawn,set_on_link,set_on_first_link,
+ timestamp,arity,return_to].
+
+display_info([Node|Nodes]) ->
+ io:format("~nNode ~w:~n",[Node]),
+ io:format("~-12s ~-21s Trace ~n", ["Pid", "Initial call"]),
+ List = rpc:call(Node,?MODULE,get_info,[]),
+ display_info1(List),
+ display_info(Nodes);
+display_info([]) ->
+ ok.
+
+display_info1([{Pid,Call,Flags}|T]) ->
+ io:format("~-12s ~-21s ~s~n",
+ [io_lib:format("~w",[Pid]),
+ io_lib:format("~p", [Call]),
+ format_trace(Flags)]),
+ display_info1(T);
+display_info1([]) ->
+ ok.
+
+get_info() ->
+ get_info(processes(),[]).
+
+get_info([Pid|T],Acc) ->
+ case pinfo(Pid, initial_call) of
+ undefined ->
+ get_info(T,Acc);
+ {initial_call, Call} ->
+ case tinfo(Pid, flags) of
+ undefined ->
+ get_info(T,Acc);
+ {flags,[]} ->
+ get_info(T,Acc);
+ {flags,Flags} ->
+ get_info(T,[{Pid,Call,Flags}|Acc])
+ end
+ end;
+get_info([],Acc) -> Acc.
+
+format_trace([]) -> [];
+format_trace([Item]) -> [ts(Item)];
+format_trace([Item|T]) -> [ts(Item) ," | ", format_trace(T)].
+
+ts(send) -> "s";
+ts('receive') -> "r";
+ts(call) -> "c";
+ts(procs) -> "p";
+ts(set_on_spawn) -> "sos";
+ts(set_on_first_spawn) -> "sofs";
+ts(set_on_link) -> "sol";
+ts(set_on_first_link) -> "sofl";
+ts(Other) -> atom_to_list(Other).
+
+%%
+%% Turn (pid or) atom into a PidSpec for erlang:trace,
+%% return {badpid,X} on failure
+%%
+
+to_pidspec(X) when is_pid(X) ->
+ case erlang:is_process_alive(X) of
+ true -> X;
+ false -> {badpid,X}
+ end;
+to_pidspec(new) -> new;
+to_pidspec(all) -> all;
+to_pidspec(existing) -> existing;
+to_pidspec(X) when is_atom(X) ->
+ case whereis(X) of
+ undefined -> {badpid,X};
+ Pid -> Pid
+ end;
+to_pidspec(X) -> {badpid,X}.
+
+%%
+%% Turn (pid or) integer or tuple or list into pid
+%%
+
+to_pid(X) when is_pid(X) -> X;
+to_pid(X) when is_integer(X) -> to_pid({0,X,0});
+to_pid({X,Y,Z}) ->
+ to_pid(lists:concat(["<",integer_to_list(X),".",
+ integer_to_list(Y),".",
+ integer_to_list(Z),">"]));
+to_pid(X) when is_list(X) ->
+ try list_to_pid(X) of
+ Pid -> Pid
+ catch
+ error:badarg -> {badpid,X}
+ end;
+to_pid(X) -> {badpid,X}.
+
+
+pinfo(P, X) when node(P) == node() -> erlang:process_info(P, X);
+pinfo(P, X) -> check(rpc:call(node(P), erlang, process_info, [P, X])).
+
+tinfo(P, X) when node(P) == node() -> erlang:trace_info(P, X);
+tinfo(P, X) -> check(rpc:call(node(P), erlang, trace_info, [P, X])).
+
+check({badrpc, _}) -> undefined;
+check(X) -> X.
+
+%% Process loop that processes a trace. Reads the trace with
+%% the reader Reader, and feeds the trace terms
+%% to handler Handler, keeping a state variable for the
+%% handler.
+%%
+%% Exits 'normal' at end of trace, other exits due to errors.
+%%
+%% Reader is a lazy list, i.e either a list or a fun/0.
+%% If it is a fun, it is evaluated for rest of the lazy list.
+%% A list head is considered to be a trace term. End of list
+%% is interpreted as end of trace.
+
+tc_loop([Term|Tail], Handler, HData0) ->
+ HData = Handler(Term, HData0),
+ tc_loop(Tail, Handler, HData);
+tc_loop([], Handler, HData) ->
+ Handler(end_of_trace, HData),
+ exit(normal);
+tc_loop(Reader, Handler, HData) when is_function(Reader) ->
+ tc_loop(Reader(), Handler, HData);
+tc_loop(Other, _Handler, _HData) ->
+ io:format("~p:tc_loop ~p~n", [?MODULE, Other]),
+ exit({unknown_term_from_reader, Other}).
+
+
+
+%% Returns a reader (lazy list of trace terms) for tc_loop/2.
+gen_reader(ip, {Host, Portno}) ->
+ case gen_tcp:connect(Host, Portno, [{active, false}, binary]) of
+ {ok, Sock} ->
+ mk_reader(fun ip_read/2, Sock);
+ Error ->
+ exit(Error)
+ end;
+gen_reader(file, {Filename, wrap, Tail, _, WrapCnt}) ->
+ mk_reader_wrap(wrap_sort(wrap_presort(Filename, Tail), WrapCnt));
+gen_reader(file, Filename) ->
+ gen_reader_file(fun file_read/2, Filename);
+gen_reader(follow_file, Filename) ->
+ gen_reader_file(fun follow_read/2, Filename).
+
+%% Opens a file and returns a reader (lazy list).
+gen_reader_file(ReadFun, Filename) ->
+ case file:open(Filename, [read, raw, binary]) of
+ {ok, File} ->
+ mk_reader(ReadFun, File);
+ Error ->
+ exit({client_cannot_open, Error})
+ end.
+
+%% Creates and returns a reader (lazy list).
+mk_reader(ReadFun, Source) ->
+ fun() ->
+ case read_term(ReadFun, Source) of
+ {ok, Term} ->
+ [Term | mk_reader(ReadFun, Source)];
+ eof ->
+ [] % end_of_trace
+ end
+ end.
+
+%% Creates and returns a reader (lazy list) for a wrap log.
+%% The argument is a sorted list of sort converted
+%% wrap log file names, see wrap_presort/2.
+
+mk_reader_wrap([]) ->
+ [];
+mk_reader_wrap([Hd | _] = WrapFiles) ->
+ case file:open(wrap_name(Hd), [read, raw, binary]) of
+ {ok, File} ->
+ mk_reader_wrap(WrapFiles, File);
+ Error ->
+ exit({client_cannot_open, Error})
+ end.
+
+mk_reader_wrap([_Hd | Tail] = WrapFiles, File) ->
+ fun() ->
+ case read_term(fun file_read/2, File) of
+ {ok, Term} ->
+ [Term | mk_reader_wrap(WrapFiles, File)];
+ eof ->
+ file:close(File),
+ case Tail of
+ [_|_] ->
+ mk_reader_wrap(Tail);
+ [] ->
+ [] % end_of_trace
+ end
+ end
+ end.
+
+
+
+%% Generic read term function.
+%% Returns {ok, Term} | 'eof'. Exits on errors.
+
+read_term(ReadFun, Source) ->
+ case ReadFun(Source, 5) of
+ Bin when is_binary(Bin) ->
+ read_term(ReadFun, Source, Bin);
+ List when is_list(List) ->
+ read_term(ReadFun, Source, list_to_binary(List));
+ eof ->
+ eof
+ end.
+
+read_term(ReadFun, Source, <<Op, Size:32>> = Tag) ->
+ case Op of
+ 0 ->
+ case ReadFun(Source, Size) of
+ eof ->
+ exit({'trace term missing',
+ binary_to_list(Tag)});
+ Bin when is_binary(Bin) ->
+ {ok, binary_to_term(Bin)};
+ List when is_list(List) ->
+ {ok, binary_to_term(list_to_binary(List))}
+ end;
+ 1 ->
+ {ok, {drop, Size}};
+ Junk ->
+ exit({'bad trace tag', Junk})
+ end.
+
+
+
+%% Read functions for different source types, for read_term/2.
+%%
+%% Returns a binary of length N, an I/O-list of
+%% effective length N or 'eof'. Exits on errors.
+
+file_read(File, N) ->
+ case file:read(File, N) of
+ {ok, Bin} when byte_size(Bin) =:= N ->
+ Bin;
+ {ok, Bin} when is_binary(Bin) ->
+ exit({'truncated file', binary_to_list(Bin)});
+ eof ->
+ eof;
+ {error, Reason} ->
+ exit({'file read error', Reason})
+ end.
+
+follow_read(File, N) ->
+ follow_read(File, N, cur).
+
+follow_read(File, N, Pos) ->
+ case file:position(File, Pos) of
+ {ok, Offset} ->
+ case file:read(File, N) of
+ {ok, Bin} when byte_size(Bin) =:= N ->
+ Bin;
+ {ok, Bin} when is_binary(Bin) ->
+ follow_read(File, N, Offset);
+ eof ->
+ follow_read(File, N, Offset);
+ {error, Reason} ->
+ exit({'file read error', Reason})
+ end;
+ {error, Reason} ->
+ exit({'file position error', Reason})
+ end.
+
+ip_read(Socket, N) ->
+ case gen_tcp:recv(Socket, N) of
+ {ok, Bin} when byte_size(Bin) < N ->
+ [Bin | ip_read(Socket, N-byte_size(Bin))];
+ {ok, Bin} when byte_size(Bin) == N ->
+ [Bin];
+ {ok, Bin} when is_binary(Bin) ->
+ exit({'socket read too much data', Bin});
+ {error, closed} ->
+ eof;
+ {error, _Reason} = Error ->
+ exit({'socket read error', Error})
+ end.
+
+get_tracer() ->
+ req({get_tracer,node()}).
+get_tracer(Node) ->
+ req({get_tracer,Node}).
+
+save_pattern([]) ->
+ 0;
+save_pattern(P) ->
+ (catch save_pattern(P, get_pattern_table())).
+
+save_pattern(Pattern, PT) ->
+ Last = last_pattern(ets:last(PT), PT),
+ BPattern = term_to_binary(Pattern),
+ case ets:match_object(PT, {'_', BPattern}) of
+ [] ->
+ ets:insert(PT, {Last + 1, BPattern}),
+ Last + 1;
+ [{N, BPattern}] ->
+ N
+ end.
+
+last_pattern('$end_of_table', _PT) ->
+ 0;
+last_pattern(I, PT) when is_atom(I) ->
+ last_pattern(ets:prev(PT, I), PT);
+last_pattern(I, _PT) when is_integer(I) ->
+ I;
+last_pattern(_, _) ->
+ throw({error, badtable}).
+
+
+get_pattern_table() ->
+ {ok, Ret} = req(get_table),
+ Ret.
+
+new_pattern_table() ->
+ PT = ets:new(dbg_tab, [ordered_set, public]),
+ ets:insert(PT,
+ {x,
+ term_to_binary([{'_',[],[{exception_trace}]}])}),
+ ets:insert(PT,
+ {exception_trace,
+ term_to_binary(x)}),
+ PT.
+
+
+pt_doforall(Fun, Ld) ->
+ T = get_pattern_table(),
+ pt_doforall(T, Fun, ets:first(T), Ld).
+
+pt_doforall(_, _, '$end_of_table', _Ld) ->
+ ok;
+pt_doforall(T, Fun, Key, Ld) ->
+ [{A,B}] = ets:lookup(T,Key),
+ NLd = Fun({A,binary_to_term(B)},Ld),
+ pt_doforall(T,Fun,ets:next(T,Key),NLd).
+
+lint_tp([]) ->
+ {ok,[]};
+lint_tp(Pattern) ->
+ case erlang:match_spec_test([],Pattern,trace) of
+ {ok,_Res,Warnings,_Flags} ->
+ {ok, Warnings};
+ {error, Reasons} ->
+ {error, Reasons}
+ end.
+
+check_list(T) ->
+ case (catch lists:foldl(
+ fun(Val,_) ->
+ {ok,_,_,_} =
+ erlang:match_spec_test([],Val,trace),
+ ok
+ end,
+ ok, T)) of
+ {'EXIT',_} ->
+ {error, bad_match_spec};
+ ok ->
+ ok;
+ _Else ->
+ {error, badfile}
+ end.
+
+
+
+%% Find all possible wrap log files.
+%% Returns: a list of sort converted filenames.
+%%
+%% The sort conversion is done by extracting the wrap sequence counter
+%% from the filename, and calling wrap_encode/2.
+wrap_presort(Filename, Tail) ->
+ Name = filename:basename(Filename),
+ Dirname = filename:dirname(Filename),
+ case file:list_dir(Dirname) of
+ {ok, Files} ->
+ lists:zf(
+ fun(N) ->
+ case match_front(N, Name) of
+ false ->
+ false;
+ X ->
+ case match_rear(X, Tail) of
+ false ->
+ false;
+ C -> % Counter
+ case match_0_9(C) of
+ true ->
+ {true,
+ wrap_encode(
+ filename:join(Dirname, N),
+ C)};
+ false ->
+ false
+ end
+ end
+ end
+ end,
+ Files);
+ _ ->
+ []
+ end.
+
+
+
+%% Sorts a list of sort converted files
+wrap_sort(Files, N) ->
+ wrap_sortfix(lists:sort(Files), N).
+
+%% Finish the sorting, since the lists:sort order is not the correct order.
+%% Cut the list of files at the gap (at least one file is supposed
+%% to be 'missing') and create a new list by cons'ing the two parts
+%% in the right order.
+wrap_sortfix([], N) when N >= 1 ->
+ [];
+wrap_sortfix([], _N) ->
+ exit(inconsistent_wrap_file_trace_set);
+%% file 0, gap 1..N
+wrap_sortfix([{0, _}] = Files, N) when N >= 1 ->
+ Files;
+wrap_sortfix([{0, _}], _N) ->
+ exit(inconsistent_wrap_file_trace_set);
+%% files 0, ...
+wrap_sortfix([{0, _} | _] = Files, N) when N >= 1->
+ wrap_sortfix_1(Files, N, [], Files);
+%% gap 0, files 1, ...
+wrap_sortfix([{1, _} | _] = Files, N) when N >= 1 ->
+ wrap_sortfix_2(Files, N, [], Files);
+wrap_sortfix([{_C, _} | _], _N) ->
+ exit(inconsistent_wrap_file_trace_set).
+
+%% files 0..C, gap C+1..N
+wrap_sortfix_1([{C, _}], N, _R, Files)
+ when C < N ->
+ Files;
+%% files 0..C1, C1+1==C2, ...
+wrap_sortfix_1([{C1, _} = F1 | [{C2, _} | _] = Tail], N, R, Files)
+ when C1+1 == C2, C2 < N ->
+ wrap_sortfix_1(Tail, N, [F1 | R], Files);
+%% files 0..C1, gap C1+1, files C1+2==C2, ...
+wrap_sortfix_1([{C1, _} = F1 | [{C2, _} | _] = Tail], N, R, _Files)
+ when C1+2 == C2, C2 =< N ->
+ wrap_sortfix_2(Tail, N, lists:reverse([F1 | R]), Tail);
+wrap_sortfix_1([_F1 | [_F2 | _]], _N, _R, _Files) ->
+ exit(inconsistent_wrap_file_trace_set).
+
+%% M == length(R); files 0..M-1, gap M, files M+1..N
+wrap_sortfix_2([{N, _}], N, R, Files) ->
+ Files ++ R;
+wrap_sortfix_2([{_C, _}], _N, _R, _Files) ->
+ exit(inconsistent_wrap_file_trace_set);
+%% M == length(R); files 0..M-1, gap M, files M+1..C1, C1+1==C2, ...
+wrap_sortfix_2([{C1, _} | [{C2, _} | _] = Tail], N, R, Files)
+ when C1+1 == C2, C2 =< N ->
+ wrap_sortfix_2(Tail, N, R, Files);
+wrap_sortfix_2([{_C1, _} | [{_C2, _} | _]], _N, _R, _Files) ->
+ exit(inconsistent_wrap_file_trace_set).
+
+
+
+%% Extract the filenames from a list of sort converted ones.
+wrap_postsort(Files) ->
+ lists:map(fun wrap_name/1, Files).
+
+wrap_encode(N, C) ->
+ {list_to_integer(C), N}.
+
+wrap_name({_C, N}) ->
+ N.
+
+%% Returns what is left of ListA when removing all matching
+%% elements from ListB, or false if some element did not match,
+%% or if ListA runs out of elements before ListB.
+match_front(ListA, []) when is_list(ListA) ->
+ ListA;
+match_front([], ListB) when is_list(ListB) ->
+ false;
+match_front([Hd|TlA], [Hd|TlB]) ->
+ match_front(TlA,TlB);
+match_front([_HdA|_], [_HdB|_]) ->
+ false.
+
+%% Reversed version of match_front/2
+match_rear(ListA, ListB) when is_list(ListA), is_list(ListB) ->
+ case match_front(lists:reverse(ListA), lists:reverse(ListB)) of
+ false ->
+ false;
+ List ->
+ lists:reverse(List)
+ end.
+
+%% Returns true if the non-empty list arguments contains all
+%% characters $0 .. $9.
+match_0_9([]) ->
+ false;
+match_0_9([H]) when is_integer(H), $0 =< H, H =< $9 ->
+ true;
+match_0_9([H|T]) when is_integer(H), $0 =< H, H =< $9 ->
+ match_0_9(T);
+match_0_9(L) when is_list(L) ->
+ false.
+
+%%%%%%%%%%%%%%%%%%
+%% Help...
+%%%%%%%%%%%%%%%%%%
+
+help_display([]) ->
+ io:format("~n",[]),
+ ok;
+help_display([H|T]) ->
+ io:format("~s~n",[H]),
+ help_display(T).
+
+h() ->
+ help_display(
+ [
+ "The following help items are available:",
+ " p, c",
+ " - Set trace flags for processes",
+ " tp, tpl, ctp, ctpl, ctpg, ltp, dtp, wtp, rtp",
+ " - Manipulate trace patterns for functions",
+ " n, cn, ln",
+ " - Add/remove traced nodes.",
+ " tracer, trace_port, trace_client, get_tracer, stop, stop_clear",
+ " - Manipulate tracer process/port",
+ " i",
+ " - Info",
+ "",
+ "call dbg:h(Item) for brief help a brief description",
+ "of one of the items above."]).
+h(p) ->
+ help_display(["p(Item) -> {ok, MatchDesc} | {error, term()}",
+ " - Traces messages to and from Item.",
+ "p(Item, Flags) -> {ok, MatchDesc} | {error, term()}",
+ " - Traces Item according to Flags.",
+ " Flags can be one of s,r,m,c,p,sos,sol,sofs,",
+ " sofl,all,clear or any flag accepted by erlang:trace/3"]);
+h(c) ->
+ help_display(["c(Mod, Fun, Args)",
+ " - Evaluates apply(M,F,Args) with all trace flags set.",
+ "c(Mod, Fun, Args, Flags)",
+ " - Evaluates apply(M,F,Args) with Flags trace flags set."]);
+h(i) ->
+ help_display(["i() -> ok",
+ " - Displays information about all traced processes."]);
+h(tp) ->
+ help_display(
+ ["tp(Module,MatchSpec)",
+ " - Same as tp({Module, '_', '_'}, MatchSpec)",
+ "tp(Module,Function,MatchSpec)",
+ " - Same as tp({Module, Function, '_'}, MatchSpec)",
+ "tp(Module, Function, Arity, MatchSpec)",
+ " - Same as tp({Module, Function, Arity}, MatchSpec)",
+ "tp({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} "
+ "| {error, term()}",
+ " - Set pattern for traced global function calls."]);
+h(tpl) ->
+ help_display(
+ ["tpl(Module,MatchSpec)",
+ " - Same as tpl({Module, '_', '_'}, MatchSpec)",
+ "tpl(Module,Function,MatchSpec)",
+ " - Same as tpl({Module, Function, '_'}, MatchSpec)",
+ "tpl(Module, Function, Arity, MatchSpec)",
+ " - Same as tpl({Module, Function, Arity}, MatchSpec)",
+ "tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} "
+ "| {error, term()}",
+ " - Set pattern for traced local (as well as global) function calls."]);
+h(ctp) ->
+ help_display(
+ ["ctp()",
+ " - Same as ctp({'_', '_', '_'})",
+ "ctp(Module)",
+ " - Same as ctp({Module, '_', '_'})",
+ "ctp(Module, Function)",
+ " - Same as ctp({Module, Function, '_'})",
+ "ctp(Module, Function, Arity)",
+ " - Same as ctp({Module, Function, Arity})",
+ "ctp({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}",
+ " - Clear call trace pattern for the specified functions"]);
+h(ctpl) ->
+ help_display(
+ ["ctpl()",
+ " - Same as ctpl({'_', '_', '_'})",
+ "ctpl(Module)",
+ " - Same as ctpl({Module, '_', '_'})",
+ "ctpl(Module, Function)",
+ " - Same as ctpl({Module, Function, '_'})",
+ "ctpl(Module, Function, Arity)",
+ " - Same as ctpl({Module, Function, Arity})",
+ "ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}",
+ " - Clear local call trace pattern for the specified functions"]);
+h(ctpg) ->
+ help_display(
+ ["ctpg()",
+ " - Same as ctpg({'_', '_', '_'})",
+ "ctpg(Module)",
+ " - Same as ctpg({Module, '_', '_'})",
+ "ctpg(Module, Function)",
+ " - Same as ctpg({Module, Function, '_'})",
+ "ctpg(Module, Function, Arity)",
+ " - Same as ctpg({Module, Function, Arity})",
+ "ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}",
+ " - Clear global call trace pattern for the specified functions"]);
+h(ltp) ->
+ help_display(["ltp() -> ok",
+ " - Lists saved and built-in match_spec's on the console."]);
+h(dtp) ->
+ help_display(["dtp() -> ok",
+ " - Deletes all saved match_spec's.",
+ "dtp(N) -> ok",
+ " - Deletes a specific saved match_spec."]);
+h(wtp) ->
+ help_display(["wtp(Name) -> ok | {error, IOError}",
+ " - Writes all saved match_spec's to a file"]);
+h(rtp) ->
+ help_display(["rtp(Name) -> ok | {error, Error}",
+ " - Read saved match specifications from file."]);
+h(n) ->
+ help_display(
+ ["n(Nodename) -> {ok, Nodename} | {error, Reason}",
+ " - Starts a tracer server on the given node.",
+ "n(Nodename,Type,Data) -> {ok, Nodename} | {error, Reason}",
+ " - Starts a tracer server with additional args on the given node."]);
+h(cn) ->
+ help_display(["cn(Nodename) -> ok",
+ " - Clears a node from the list of traced nodes."]);
+h(ln) ->
+ help_display(["ln() -> ok",
+ " - Shows the list of traced nodes on the console."]);
+h(tracer) ->
+ help_display(["tracer() -> {ok, pid()} | {error, already_started}",
+ " - Starts a tracer server that handles trace messages.",
+ "tracer(Type, Data) -> {ok, pid()} | {error, Error}",
+ " - Starts a tracer server with additional parameters"]);
+h(trace_port) ->
+ help_display(["trace_port(Type, Parameters) -> fun()",
+ " - Creates and returns a trace port generating fun"]);
+h(trace_client) ->
+ help_display(["trace_client(Type, Parameters) -> pid()",
+ " - Starts a trace client that reads messages created by "
+ "a trace port driver",
+ "trace_client(Type, Parameters, HandlerSpec) -> pid()",
+ " - Starts a trace client that reads messages created by a",
+ " trace port driver, with a user defined handler"]);
+h(get_tracer) ->
+ help_display(
+ ["get_tracer() -> {ok, Tracer}",
+ " - Returns the process or port to which all trace messages are sent.",
+ "get_tracer(Node) -> {ok, Tracer}",
+ " - Returns the process or port to which all trace messages are sent."]);
+h(stop) ->
+ help_display(
+ ["stop() -> stopped",
+ " - Stops the dbg server and the tracing of all processes.",
+ " Does not clear any trace patterns."]);
+h(stop_clear) ->
+ help_display(
+ ["stop_clear() -> stopped",
+ " - Stops the dbg server and the tracing of all processes,",
+ " and clears all trace patterns."]).
+