%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2004-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% %%% Purpose: %%% This module implements support for using the Erlang trace in a simple way for ssh %%% debugging. %%% %%% Begin the session with ssh_dbg:start(). This will do a dbg:start() if needed and %%% then dbg:p/2 to set some flags. %%% %%% Next select trace points to activate: for example plain text printouts of messages %%% sent or received. This is switched on and off with ssh_dbg:on(TracePoint(s)) and %%% ssh_dbg:off(TracePoint(s)). For example: %%% %%% ssh_dbg:on(messages) -- switch on printing plain text messages %%% ssh_dbg:on([alg,terminate]) -- switch on printing info about algorithm negotiation %%% ssh_dbg:on() -- switch on all ssh debugging %%% %%% To switch, use the off/0 or off/1 function in the same way, for example: %%% %%% ssh_dbg:off(alg) -- switch off algorithm negotiation tracing, but keep all other %%% ssh_dbg:off() -- switch off all ssh debugging %%% %%% Present the trace result with some other method than the default io:format/2: %%% ssh_dbg:start(fun(Format,Args) -> %%% my_special( io_lib:format(Format,Args) ) %%% end) %%% -module(ssh_dbg). -export([start/0, start/1, stop/0, start_server/0, start_tracer/0, start_tracer/1, on/1, on/0, off/1, off/0, go_on/0 ]). -export([shrink_bin/1, reduce_state/1, wr_record/3]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2]). -include("ssh.hrl"). -include("ssh_transport.hrl"). -include("ssh_connect.hrl"). -include("ssh_auth.hrl"). -behaviour(gen_server). -define(SERVER, ?MODULE). %%%================================================================ -define(ALL_DBG_TYPES, get_all_dbg_types()). start() -> start(fun io:format/2). start(IoFmtFun) when is_function(IoFmtFun,2) ; is_function(IoFmtFun,3) -> start_server(), catch dbg:start(), start_tracer(IoFmtFun), dbg:p(all, get_all_trace_flags()), ?ALL_DBG_TYPES. stop() -> try dbg:stop_clear(), gen_server:stop(?SERVER) catch _:_ -> ok end. start_server() -> gen_server:start({local,?SERVER}, ?MODULE, [], []). start_tracer() -> start_tracer(fun io:format/2). start_tracer(WriteFun) when is_function(WriteFun,2) -> start_tracer(fun(F,A,S) -> WriteFun(F,A), S end); start_tracer(WriteFun) when is_function(WriteFun,3) -> start_tracer(WriteFun, undefined). start_tracer(WriteFun, InitAcc) when is_function(WriteFun, 3) -> Handler = fun(Arg, Acc0) -> try_all_types_in_all_modules(gen_server:call(?SERVER, get_on), Arg, WriteFun, Acc0) end, dbg:tracer(process, {Handler,InitAcc}). %%%---------------------------------------------------------------- on() -> on(?ALL_DBG_TYPES). on(Type) -> switch(on, Type). off() -> off(?ALL_DBG_TYPES). % A bit overkill... off(Type) -> switch(off, Type). go_on() -> IsOn = gen_server:call(?SERVER, get_on), on(IsOn). %%%---------------------------------------------------------------- shrink_bin(B) when is_binary(B), size(B)>256 -> {'*** SHRINKED BIN', size(B), element(1,split_binary(B,64)), '...', element(2,split_binary(B,size(B)-64)) }; shrink_bin(L) when is_list(L) -> lists:map(fun shrink_bin/1, L); shrink_bin(T) when is_tuple(T) -> list_to_tuple(shrink_bin(tuple_to_list(T))); shrink_bin(X) -> X. %%%---------------------------------------------------------------- %% Replace last element (the state) with "#{}" reduce_state(T) -> try erlang:setelement(size(T), T, lists:concat(['#',element(1,element(size(T),T)),'{}']) ) catch _:_ -> T end. %%%================================================================ -record(data, { types_on = [] }). %%%---------------------------------------------------------------- init(_) -> {ok, #data{}}. %%%---------------------------------------------------------------- handle_call({switch,on,Types}, _From, D) -> NowOn = lists:usort(Types ++ D#data.types_on), call_modules(on, Types, NowOn), {reply, {ok,NowOn}, D#data{types_on = NowOn}}; handle_call({switch,off,Types}, _From, D) -> StillOn = D#data.types_on -- Types, call_modules(off, Types, StillOn), call_modules(on, StillOn, StillOn), {reply, {ok,StillOn}, D#data{types_on = StillOn}}; handle_call(get_on, _From, D) -> {reply, D#data.types_on, D}; handle_call(C, _From, D) -> io:format('*** Unknown call: ~p~n',[C]), {reply, {error,{unknown_call,C}}, D}. handle_cast(C, D) -> io:format('*** Unknown cast: ~p~n',[C]), {noreply, D}. handle_info(C, D) -> io:format('*** Unknown info: ~p~n',[C]), {noreply, D}. %%%================================================================ %%%---------------------------------------------------------------- ssh_modules_with_trace() -> {ok,AllSshModules} = application:get_key(ssh, modules), [M || M <- AllSshModules, lists:member({dbg_trace,3}, M:module_info(exports))]. %%%---------------------------------------------------------------- get_all_trace_flags() -> get_all_trace_flags(ssh_modules_with_trace()). get_all_trace_flags(Modules) -> lists:usort( lists:flatten( lists:foldl( fun(Type, Acc) -> call_modules(flags, Type, undefined, Acc, Modules) end, [timestamp], ?ALL_DBG_TYPES))). %%%---------------------------------------------------------------- get_all_dbg_types() -> lists:usort( lists:flatten( call_modules(points, undefined) )). %%%---------------------------------------------------------------- call_modules(Cmnd, Type) -> call_modules(Cmnd, Type, undefined). call_modules(Cmnd, Type, Arg) -> call_modules(Cmnd, Type, Arg, []). call_modules(Cmnd, Type, Arg, Acc0) -> call_modules(Cmnd, Type, Arg, Acc0, ssh_modules_with_trace()). call_modules(Cmnd, Types, Arg, Acc0, Modules) when is_list(Types) -> lists:foldl( fun(Type, Acc) -> call_modules(Cmnd, Type, Arg, Acc, Modules) end, Acc0, Types); call_modules(Cmnd, Type, Arg, Acc0, Modules) -> lists:foldl( fun(Mod, Acc) -> try Mod:dbg_trace(Cmnd, Type, Arg) of Result -> [Result|Acc] catch _:_ -> Acc end end, Acc0, Modules). %%%---------------------------------------------------------------- switch(X, Type) when is_atom(Type) -> switch(X, [Type]); switch(X, Types) when is_list(Types) -> case whereis(?SERVER) of undefined -> start(); _ -> ok end, case lists:usort(Types) -- ?ALL_DBG_TYPES of [] -> gen_server:call(?SERVER, {switch,X,Types}); L -> {error, {unknown, L}} end. %%%---------------------------------------------------------------- %%% Format of trace messages are described in reference manual for erlang:trace/4 %%% {call,MFA} %%% {return_from,{M,F,N},Result} %%% {send,Msg,To} %%% {'receive',Msg} trace_pid({trace,Pid,_}) -> Pid; trace_pid({trace,Pid,_,_}) -> Pid; trace_pid({trace,Pid,_,_,_}) -> Pid; trace_pid({trace,Pid,_,_,_,_}) -> Pid; trace_pid({trace,Pid,_,_,_,_,_}) -> Pid; trace_pid({trace_ts,Pid,_,_TS}) -> Pid; trace_pid({trace_ts,Pid,_,_,_TS}) -> Pid; trace_pid({trace_ts,Pid,_,_,_,_TS}) -> Pid; trace_pid({trace_ts,Pid,_,_,_,_,_TS}) -> Pid; trace_pid({trace_ts,Pid,_,_,_,_,_,_TS}) -> Pid. trace_ts({trace_ts,_Pid,_,TS}) -> ts(TS); trace_ts({trace_ts,_Pid,_,_,TS}) -> ts(TS); trace_ts({trace_ts,_Pid,_,_,_,TS}) -> ts(TS); trace_ts({trace_ts,_Pid,_,_,_,_,TS}) -> ts(TS); trace_ts({trace_ts,_Pid,_,_,_,_,_,TS}) -> ts(TS); trace_ts(_) -> "-". trace_info({trace,_Pid,A}) -> A; trace_info({trace,_Pid,A,B}) -> {A,B}; trace_info({trace,_Pid,A,B,C}) -> {A,B,C}; trace_info({trace,_Pid,A,B,C,D}) -> {A,B,C,D}; trace_info({trace,_Pid,A,B,C,D,E}) -> {A,B,C,D,E}; trace_info({trace_ts,_Pid,A,_TS}) -> A; trace_info({trace_ts,_Pid,A,B,_TS}) -> {A,B}; trace_info({trace_ts,_Pid,A,B,C,_TS}) -> {A,B,C}; trace_info({trace_ts,_Pid,A,B,C,D,_TS}) -> {A,B,C,D}; trace_info({trace_ts,_Pid,A,B,C,D,E,_TS}) -> {A,B,C,D,E}. try_all_types_in_all_modules(TypesOn, Arg, WriteFun, Acc0) -> SshModules = ssh_modules_with_trace(), TS = trace_ts(Arg), PID = trace_pid(Arg), INFO = trace_info(Arg), lists:foldl( fun(Type, Acc1) -> lists:foldl( fun(SshMod,Acc) -> try WriteFun("~n~s ~p ~s~n", [lists:flatten(TS), PID, lists:flatten(SshMod:dbg_trace(format,Type,INFO))], Acc) catch _:_ -> Acc end end, Acc1, SshModules) end, Acc0, TypesOn). %%%---------------------------------------------------------------- wr_record(T, Fs, BL) when is_tuple(T) -> wr_record(tuple_to_list(T), Fs, BL); wr_record([_Name|Values], Fields, BlackL) -> W = case Fields of [] -> 0; _ -> lists:max([length(atom_to_list(F)) || F<-Fields]) end, [io_lib:format(" ~*p: ~p~n",[W,Tag,Value]) || {Tag,Value} <- lists:zip(Fields,Values), not lists:member(Tag,BlackL) ]. %%%---------------------------------------------------------------- ts({_,_,Usec}=Now) when is_integer(Usec) -> {_Date,{HH,MM,SS}} = calendar:now_to_local_time(Now), io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.6.0w",[HH,MM,SS,Usec]); ts(_) -> "-".