%%%-------------------------------------------------------------------
%%% @author Torbjorn Tornkvist <kruskakli@gmail.com>
%%% @copyright (C) 2017, Torbjorn Tornkvist
%%% @doc edbg - Erlang/Elixir trace and debug tool
%%%
%%% `edbg' is a tty based interface to the Erlang debugger/tracer
%%% and the supervisor trees.
%%%
%%% This module is the main interface to `edbg'.
%%%
%%% Note that most of the functions here relates to the debugger
%%% functionality.
%%%
%%% For tracing you only need the functions: {@link fstart/2} ,
%%% {@link fstop/0}, {@link file/0}, {@link xfile/0}
%%% (and possibly some variants of the argument number).
%%%
%%% For the Supervisor Tree Browser, you only need:
%%% {@link suptrees/0}.
%%%
%%% @end
%%% Created : 4 Sep 2017 by Torbjorn Tornkvist <kruskakli@gmail.com>
%%%-------------------------------------------------------------------
-module(edbg).
-on_load(init_edbg/0).
-export([a/0,
a/1,
a/3,
attach/1,
attach/3,
b/2,
br/0,
br/1,
break/2,
breaks/0,
breaks/1,
break_in/3,
c/1,
c/3,
continue/1,
continue/3,
delete_breaks/0,
delete_break/2,
bdel/0,
bdel/2,
disable_breaks/0,
disable_break/2,
boff/0,
boff/2,
enable_breaks/0,
enable_break/2,
file/0,
file/1,
fhelp/0,
fpid/1,
fpid/2,
fstart/0,
fstart/1,
fstart/2,
fstop/0,
bon/0,
bon/2,
f/1,
f/3,
finish/1,
finish/3,
i/1,
i/2,
id/0,
it/3,
lab/0,
load_all_breakpoints/0,
ml/1,
ml/2,
ml/3,
mlist/1,
mlist/2,
mlist/3,
pl/0,
plist/0,
n/1,
n/3,
next/1,
next/3,
sab/0,
save_all_breakpoints/0,
s/1,
s/3,
step/1,
step/3,
suptrees/0,
t/2,
t/3,
lts/0,
xfile/0,
xfile/1
]).
%% Internal export
-export([aloop/1,
ploop/3,
it_loop/1,
itest_at_break/1,
find_source/1
]).
-ifndef(ELIXIR).
-ifdef(USE_COLORS).
-define(info_msg(Fmt,Args), edbg_color_srv:info_msg(Fmt,Args)).
-define(att_msg(Fmt,Args), edbg_color_srv:att_msg(Fmt,Args)).
-define(warn_msg(Fmt,Args), edbg_color_srv:warn_msg(Fmt,Args)).
-define(err_msg(Fmt,Args), edbg_color_srv:err_msg(Fmt,Args)).
-define(cur_line_msg(Fmt,Args), edbg_color_srv:cur_line_msg(Fmt,Args)).
-define(c_hi(Str), edbg_color_srv:c_hi(Str)).
-define(c_warn(Str), edbg_color_srv:c_warn(Str)).
-define(c_err(Str), edbg_color_srv:c_err(Str)).
-define(help_hi(Str), edbg_color_srv:help_hi(Str)).
-define(edbg_color_srv_init(), edbg_color_srv:init()).
-else.
-define(info_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)).
-define(att_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)).
-define(warn_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)).
-define(err_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)).
-define(cur_line_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)).
-define(c_hi(Str), Str).
-define(c_warn(Str), Str).
-define(c_err(Str), Str).
-define(help_hi(Str), Str).
-define(edbg_color_srv_init(), ok).
-endif.
-else.
-define(info_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)).
-define(att_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)).
-define(warn_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)).
-define(err_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)).
-define(cur_line_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)).
-define(c_hi(Str), Str).
-define(c_warn(Str), Str).
-define(c_err(Str), Str).
-define(help_hi(Str), Str).
-define(edbg_color_srv_init(), ok).
-endif.
%% @private
init_edbg() ->
ok = ?edbg_color_srv_init().
%% @private
lts() ->
edbg_tracer:lts().
%% @doc As 'file/1' but uses the default trace output filename.
file() ->
edbg_tracer:file().
%% @doc Load the trace output from the file 'Fname'.
%%
%% When the file is loaded, enter the trace list mode.
%% @end
file(Fname) ->
edbg_tracer:file(Fname).
%% @doc As 'file/0' but hint that Elixir code is traced.
xfile() ->
edbg_tracer:xfile().
%% @doc As 'file/1' but hint that Elixir code is traced.
xfile(Fname) ->
edbg_tracer:xfile(Fname).
%% @doc Start tracing making use of a previously stored configuration.
%%
%% A previous call to 'fstart/2' will store the configuration
%% on disk so that it can be resued when calling this function.
%%
%% @end
fstart() ->
edbg_tracer:fstart().
%% @doc Start tracing making use of a previously stored configuration.
%%
%% A previous call to 'fstart/2' will store the configuration
%% on disk so that it can be resued when calling this function.
%%
%% @end
fstart(ModFunList) ->
edbg_tracer:fstart(ModFunList).
%% @doc Start tracing to file.
%%
%% 'ModFunList' is a list of module names (atoms)
%% or tuples {ModuleName, FunctionName}. This makes it possible to trace
%% on all functions within a Module, or just a few functions within a Module.
%%
%% 'Opts' is a list of option tuples:
%%
%% <ul>
%% <li>{log_file, FileName} : file where to store trace output; default: 'edbg.trace_result'</li>
%% <li>{cfg_file, FileName} : file where to store the config; default: 'edbg_trace.config'</li>
%% <li>{max_msgs, MaxNumOfMsgs} : max number of trace messages; default = 1000</li>
%% <li>{trace_time, Seconds} : max time to trace; default = 10 seconds</li>
%% <li>{trace_spec, Spec} : see the erlang:trace/3 docs; default = all</li>
%% <li>dump_output_eager : trace output goes to file often</li>
%% <li>dump_output_lazy : trace output goes to file not so often (default)</li>
%% <li>monotonic_ts : show the elapsed monotonic nano seconds</li>
%% <li>send_receive : trace send/receive messages from 'known' pids</li>
%% <li>memory : track the memory usage of the 'known' pids</li>
%% <li>set_on_spawn : any process created by a traced process inherit its trace flags</li>
%% <li>set_on_first_spawn : the first process created by a traced process inherit its trace flags</li>
%% <li>set_on_link : any process linked by a traced process inherit its trace flags</li>
%% <li>set_on_first_link : the first process linked by a traced process inherit its trace flags</li>
%% </ul>
%%
%% Tracing in an Erlang node is setup by the 'erlang:trace/3' and
%% 'erlang:trace_pattern/3' BIF's. The generated trace output in
%% a production system can quickly produce a staggering amount of
%% data, which easily can swamp the whole system, so that it becomes
%% unusable.
%%
%% It is therefore important to restrict what to trace, the amount of
%% generated trace messages and the maximum time we allow tracing to go on.
%% 'edbg' helps you with this but you can still brake
%% your system if you are careless setting the trace parameters.
%%
%% With the `log_file` you can override the default name of the file
%% where the trace output should be stored. This can be necessary if
%% you want to specify a certain location (e.g a r/w directory/file of
%% an Elixir/Nerves device). For the same reason you can specify what
%% file the 'cfg_file' should point to, or simply turn off the config
%% file completely by setting it to `false'.
%%
%% With the `max_msgs' and `trace_time' parameters you can
%% restrict the amount of generated trace messages and running time
%% before stopping the tracing.
%%
%% The `trace_spec' is also a way of restricting what to trace on.
%% Default is `all', but for later OTP versions (> 18.3): `processes'
%% is available and would be more specific.
%% For more info about this, see the 'erlang:trace/3' documentation.
%%
%% With the `dump_output_lazy' switch set, trace output goes to file not
%% until the tracer is stopped (e.g by calling the 'file/1' function), or
%% that a limiting filter such as `max_msg' or `trace_time' is reached.
%% This is the default.
%%
%% With the `dump_output_eager' switch set, trace output goes to file often
%% which may be necessary if you run edbg tracing and the system unexpectedly
%% shuts down.
%%
%% With the `monotonic_ts' switch set, each trace message will have a
%% monotonic timestamp, in nanoseconds, attached to it. This will be displayed
%% in the call graph as the elapsed time counted from the first received
%% trace message.
%%
%% With the `send_receive' switch set, we will also trace messages sent and
%% received by 'known' pids. By 'known' pids we mean processes that we have
%% seen earlier in a traced call. The reason for this is to avoid beig swamped
%% by all the messages that the trace BIF is sending us. Note that we still
%% may get a lots of messages that will cause the resulting trace file to be
%% large and make the handling of it slower. The display of sent and received
%% messages can be toggled on/off from the trace command prompt, see also the
%% trace examples.
%%
%% With the `memory' switch set, we will also track the memory usage of
%% the processes that we get trace messages for. The memory size shown
%% is the size in bytes of the process. This includes call stack, heap,
%% and internal structures, as what we get from the process_info(Pid, memory)
%% BIF.
%%
%% NOTE: we run the process_info/2 BIF when we receive the
%% trace message from the BEAM engine so the memory size we present does
%% not exactly represent the state of the process at the creation of the
%% trace message.
%%
%% The `set_on_XXX' options probably works best together with the {@link fpid/2}
%% function.
%%
%%
%%```
%% % Example, trace calls to the foo module, no more than 1000 trace msgs
%% edbg:fstart([foo], [{max_msgs, 1000}]).
%%'''
%%
%%
%%```
%% % Example, trace all modules in a particular process,
%% % dump the traced output to file often,
%% % no more than 1000 trace msgs.
%% edbg:fstart([], [{trace_spec,Pid}, dump_output_eager, {max_msgs, 1000}]).
%% '''
%%
%% @end
fstart(ModFunList, Options) ->
edbg_tracer:fstart(ModFunList, Options).
%% @doc Stop tracing and dump the trace output to file.
fstop() ->
edbg_tracer:fstop().
%% @doc Start tracing the process: 'Pid'.
%%
%% A qick way of start tracing a process.
%%
%% The Options are set to be:
%%
%% ```
%% [dump_output_eager,
%% send_receive,
%% {max_msgs, 1000000}]
%% '''
%%
%% @end
fpid(Pid) when is_pid(Pid) ->
fpid(Pid, [dump_output_eager,
send_receive,
{max_msgs, 1000000}]).
%% @doc Start tracing the process: 'Pid'.
%%
%% Start tracing the process. The Options are the same as
%% for the 'fstart/2' function.
%%
%% NOTE: The `set_on_XXXX' options can be very useful here.
%% See also {@link fstart/2}
%%
%% @end
fpid(Pid, Options) when is_pid(Pid) andalso is_list(Options) ->
edbg:fstart([], [{trace_spec,Pid} | Options]),
io:format("~n~s ~s ~p~n",[?c_warn("<INFO>"),"Tracing on Pid:",Pid]).
%% @doc Display a short help text.
fhelp() ->
edbg_tracer:fhelp().
%% @doc Enter the Supervisor Tree Browser.
%% @see edbg_sup_trees
suptrees() ->
edbg_sup_trees:start().
%% @doc Show all interpreted processes.
pl() ->
plist().
%% @doc As 'mlist/1'.
ml(X) -> mlist(X).
%% @doc As 'mlist/2'.
ml(X,Y) -> mlist(X,Y).
%% @doc As 'mlist/3'.
ml(X,Y,Z) -> mlist(X,Y,Z).
%% @doc Display all break points.
br() ->
breaks().
%% @doc Display all break points.
breaks() ->
print_all_breakpoints(int:all_breaks()).
%% @doc Display all break points for module 'Mod'.
br(Mod) ->
breaks(Mod).
%% @doc Display all break points for module 'Mod'.
breaks(Mod) ->
print_all_breakpoints(int:all_breaks(Mod)).
%% @doc Set a break point in 'Mod' at line 'Line'.
b(Mod, Line) ->
break(Mod,Line).
%% @doc Set a break point in 'Mod' at line 'Line'.
break(Mod, Line) ->
ok = int:break(Mod, Line),
save_all_breakpoints(),
ok.
%% @doc Delete all break points.
bdel() ->
delete_breaks().
%% @doc Delete all break points.
delete_breaks() ->
[delete_break(Mod, Line) || {{Mod, Line},_} <- int:all_breaks()].
%% @doc Delete the break point in 'Mod' on line 'Line'.
bdel(Mod, Line) ->
delete_break(Mod, Line).
%% @doc Delete the break point in 'Mod' on line 'Line'.
delete_break(Mod, Line) ->
ok = int:delete_break(Mod, Line),
save_all_breakpoints(),
ok.
%% @doc Disable all break points.
boff() ->
disable_breaks().
%% @doc Disable all break points.
disable_breaks() ->
[disable_break(Mod, Line) || {{Mod, Line},_} <- int:all_breaks()].
%% @doc Disable the break point in 'Mod' on line 'Line'.
boff(Mod, Line) ->
disable_break(Mod, Line).
%% @doc Disable the break point in 'Mod' on line 'Line'.
disable_break(Mod, Line) ->
ok = int:disable_break(Mod, Line),
save_all_breakpoints(),
ok.
%% @doc Disable all break points.
bon() ->
enable_breaks().
%% @doc Disable all break points.
enable_breaks() ->
[enable_break(Mod, Line) || {{Mod, Line},_} <- int:all_breaks()].
%% @doc Enable the break point in 'Mod' on line 'Line'.
bon(Mod, Line) ->
enable_break(Mod, Line).
%% @doc Enable the break point in 'Mod' on line 'Line'.
enable_break(Mod, Line) ->
ok = int:enable_break(Mod, Line),
save_all_breakpoints(),
ok.
%% @doc Continue the execution of the given process 'Pid'.
c(Pid) ->
continue(Pid).
%% @doc Continue the execution of the given process <P0.P1.P2>.
c(P0, P1, P2) ->
continue(c:pid(P0, P1, P2)).
%% @doc Continue the execution of the given process <P0.P1.P2>.
continue(P0, P1, P2) ->
continue(c:pid(P0, P1, P2)).
%% @doc Continue the execution of the given process 'Pid'.
continue(Pid) when is_pid(Pid) ->
int:continue(Pid).
%% FIXME doesn't work ?
%% @private
break_in(Mod, Line, Arity) ->
int:break_in(Mod, Line, Arity).
%% @doc Start interpret the module 'Mod'.
%%
%% With only one argument, you don't set an explicit break point,
%% but you will be able to step into the module while debugging.
%% @end
i(Mod) ->
Fname = find_source(Mod),
int:i(Fname).
%% @doc Show all interpreted modules.
id() ->
int:interpreted().
%% @doc Start interpret module 'Mod' and set a break point at line 'Line'.
i(Mod,Line) ->
Fname = find_source(Mod),
int:i(Fname),
int:delete_break(Mod, Line),
break(Mod, Line).
%% @doc Start interpret module 'Mod' and set a conditional break point.
%%
%% Start interpret module 'Mod' and set a conditional break point
%% in 'Mod' at line 'Line'.
%% The 'Fun/1' as an anonymous function of arity 1 that gets executed
%% each time the break point is passed. When the 'Fun/1' returns
%% 'true' the break point will trigger and the execution will stop,
%% else the execution will continue.
%%
%% The 'Fun/1' takes an argument which will be a list of current Variable
%% bindings; typically it makes use of the function
%% 'int:get_binding(Var, Bindings)' (where 'Var' is an atom denoting a
%% particular variable) to decide if the break point should trigger
%% or not. See the example further below for how to use it.
%%
%% Note that only one interactive trigger function can be used at a time.
%% @end
it(Mod,Line,Fun) when is_function(Fun) ->
Fname = find_source(Mod),
int:i(Fname),
t(Mod,Line,Fun).
%% @doc As 't/3' but will reuse an existing trigger function.
t(Mod,Line) ->
int:delete_break(Mod, Line),
ok = int:break(Mod, Line),
ok = int:test_at_break(Mod, Line, {edbg,itest_at_break}),
save_all_breakpoints(),
ok.
%% @doc As 'it/3' but assumes 'Mod' already is interpreted.
t(Mod,Line,Fun) when is_function(Fun) ->
int:delete_break(Mod, Line),
ok = itest_at_break(Fun),
ok = int:break(Mod, Line),
ok = int:test_at_break(Mod, Line, {edbg,itest_at_break}),
save_all_breakpoints(),
ok.
-define(itest_at_break, itest_at_break).
%% @private
itest_at_break(Fun) when is_function(Fun) ->
case whereis(?itest_at_break) of
Pid when is_pid(Pid) ->
Pid ! {self(), cond_fun, Fun},
receive
{Pid, ok} -> ok
after 3000 ->
{error, timeout}
end;
_ ->
Pid = spawn(fun() -> ?MODULE:it_loop(Fun) end),
register(?itest_at_break, Pid),
ok
end;
%%
itest_at_break(Bindings) ->
try
case whereis(?itest_at_break) of
Pid when is_pid(Pid) ->
Pid ! {self(), eval, Bindings},
receive
{Pid, result, Bool} ->
Bool
after 3000 ->
false
end
end
catch
_:_ ->
false
end.
%% @private
it_loop(Fun) ->
receive
{From, cond_fun, NewFun} ->
From ! {self(), ok},
?MODULE:it_loop(NewFun);
{From, eval, Bindings} ->
try
true = Fun(Bindings),
From ! {self(), result, true}
catch
_:_ ->
From ! {self(), result, false}
end,
?MODULE:it_loop(Fun);
%% Used when saving breakpoints...
{From, get_fun} ->
From ! {self(), ok, Fun},
?MODULE:it_loop(Fun);
_ ->
?MODULE:it_loop(Fun)
end.
get_it_break_fun() ->
try
case whereis(?itest_at_break) of
Pid when is_pid(Pid) ->
Pid ! {self(), get_fun},
receive
{Pid, ok, Fun} ->
Fun
after 3000 ->
null
end
end
catch
_:_ ->
null
end.
%% @doc Same as 'step/1'.
s(Pid) ->
step(Pid).
%% @doc Same as 'step/3'.
s(P0, P1, P2) -> step(c:pid(P0, P1, P2)).
%% @doc Do a 'Step' debug operation of a stopped process: >P0.P1.P2<.
step(P0, P1, P2) -> step(c:pid(P0, P1, P2)).
%% @doc Do a 'Step' debug operation of a stopped process: 'Pid'.
step(Pid) when is_pid(Pid) ->
int:step(Pid),
code_list(Pid).
%% @doc Same as 'next/1'.
n(Pid) ->
next(Pid).
%% @doc Same as 'next/3'.
n(P0, P1, P2) ->
next(c:pid(P0, P1, P2)).
%% @doc Do a 'Next' debug operation of a stopped process: >P0.P1.P2<.
next(P0, P1, P2) ->
next(c:pid(P0, P1, P2)).
%% @doc Do a 'Next' debug operation of a stopped process: 'Pid'.
next(Pid) when is_pid(Pid) ->
int:next(Pid),
code_list(Pid).
%% @doc Finish execution of a debugged function in process: 'Pid'.
f(Pid) ->
finish(Pid).
%% @doc Finish execution of a debugged function in process: <P0.P1.P2>.
f(P0, P1, P2) ->
finish(c:pid(P0, P1, P2)).
%% @doc Finish execution of a debugged function in process: 'Pid'.
finish(Pid) when is_pid(Pid) ->
int:finish(Pid),
code_list(Pid).
%% @doc Finish execution of a debugged function in process: <P0.P1.P2>.
finish(P0, P1, P2) ->
finish(c:pid(P0, P1, P2)).
code_list(Pid) ->
case get_break_point(Pid) of
{Pid, {_Mod,_Name,_Args}, break, {Mod,Line}} ->
mlist(Mod,Line),
ok;
Else ->
Else
end.
-define(DEFAULT_CONTEXT_SIZE, 5).
-record(s, {
pid,
meta,
prompt,
break_at,
break_points = [],
context = ?DEFAULT_CONTEXT_SIZE,
stack,
trace = false, % toggle
mytracer
}).
toggle(true) -> false;
toggle(false) -> true.
%% @doc Attach to the first process found stopped on a break point.
%%
%% Attach to an interpreted process in order to manually control
%% the further execution, inspect variables, etc. When called, you
%% will enter a sort of mini-shell where you can issue a number of
%% commands.
%% @end
a() ->
Self = self(),
case [Pid || {Pid, _Func, Status, _Info} <- int:snapshot(),
Status == break,
Pid =/= Self] of
[X|_] ->
attach(X);
_ ->
"No process found to be at a break point!"
end.
%% @doc Attach to the given Pid.
a(Pid) ->
attach(Pid).
%% @doc Attach to the given process: <P0.P1.P2> .
a(P0, P1, P2) ->
attach(c:pid(P0, P1, P2)).
%% @doc Attach to the given process: <P0.P1.P2> .
attach(P0, P1, P2) ->
attach(c:pid(P0, P1, P2)).
%% @doc Attach to the given Pid.
attach(Pid) when is_pid(Pid) ->
Self = self(),
case int:attached(Pid) of
{ok, Meta} ->
Prompt = spawn_link(fun() -> prompt(Pid,Self) end),
print_help(),
aloop(#s{pid=Pid,
meta=Meta,
break_points = int:all_breaks(),
prompt=Prompt});
Else ->
?err_msg("Failed to attach to process: ~p~n",[Pid]),
Else
end.
%% @private
aloop(#s{meta = Meta,
prompt = Prompt} = S) ->
receive
{'EXIT', Meta, Reason} ->
{meta_exit, Reason};
{'EXIT', Prompt, Reason} ->
{prompt_exit, Reason};
%% FROM THE INTERPRETER (?)
{int,{new_break,{{Mod,Line},_L} = Bp}} ->
Bps = S#s.break_points,
?info_msg([?c_hi("Break point set at"), " ~p:~p~n"], [Mod,Line]),
?MODULE:aloop(S#s{break_points = [Bp|Bps]});
{int,{interpret,_Mod}} ->
?MODULE:aloop(S);
{int,{break_options,{{_Mod,_Line},_Opts}}} ->
?MODULE:aloop(S);
{int,{delete_break,{_Mod,_Line}}} ->
?MODULE:aloop(S);
%% FROM THE META PROCESS
{Meta, {trace,true}} ->
?MODULE:aloop(S);
{Meta, running} ->
?MODULE:aloop(S);
{Meta, {attached, _Mod, _Line, _Bool}} ->
?MODULE:aloop(S);
{Meta, {break_at, Mod, Line, Cur}} ->
Bs = int:meta(Meta, bindings, nostack),
mlist(Mod,Line,S#s.context),
?MODULE:aloop(S#s{break_at = {Mod,Line},
stack = {Cur,Cur,Bs,{Mod,Line}}});
{Meta, {func_at, Mod, Line, Cur}} ->
Bs = int:meta(Meta, bindings, nostack),
mlist(Mod,Line,S#s.context),
?MODULE:aloop(S#s{break_at = {Mod,Line},
stack = {Cur,Cur,Bs,{Mod,Line}}});
{Meta,{exit_at, {Mod, Line}, Reason, Cur}} ->
Bs = int:meta(Meta, bindings, nostack),
mlist(Mod,Line,S#s.context),
?err_msg("ERROR REASON: ~p~n",[Reason]),
?MODULE:aloop(S#s{break_at = {Mod,Line},
stack = {Cur+1,Cur+1,Bs,{Mod,Line}}});
{Meta, {trace_output, StrFun}} ->
?info_msg("~s~n",[StrFun("~tp")]),
?MODULE:aloop(S);
%% FROM THE PROMPTER
{Prompt, eval_expr, Str} ->
Bs = int:meta(Meta, bindings, nostack),
case evaluate(Str, Bs) of
{ok, Value} ->
?info_msg([?c_hi("EVALUATED VALUE"), ":~n~p~n"], [Value]);
{error, ErrStr} ->
?err_msg("~s~n",[ErrStr])
end,
?MODULE:aloop(S);
{Prompt, up} ->
{Cur, Max, _, _} = S#s.stack,
case int:meta(Meta, stack_frame, {up, Cur}) of
{New, {undefined,-1}, _Bs} -> % call from non-interpreted code
Stack = {New, Max, undefined, undefined};
{New, {Mod,Line}, Bs} ->
Stack = {New, Max, Bs, {Mod,Line}},
mlist(Mod,Line,S#s.context);
top ->
Stack = S#s.stack,
?att_msg("Top of stack frames!~n",[])
end,
?MODULE:aloop(S#s{stack = Stack});
{Prompt, down} ->
{Cur, Max, _, _} = S#s.stack,
case int:meta(Meta, stack_frame, {down, Cur}) of
{New, {undefined,-1}, _Bs} -> % call from non-interpreted code
Stack = {New, Max, undefined, undefined};
{New, {Mod,Line}, Bs} ->
Stack = {New, Max, Bs, {Mod,Line}},
mlist(Mod,Line,S#s.context);
bottom ->
Bs = int:meta(Meta, bindings, nostack),
{Mod,Line} = ModLine = S#s.break_at,
Stack = {Max,Max,Bs,ModLine},
mlist(Mod,Line,S#s.context)
end,
?MODULE:aloop(S#s{stack = Stack});
{Prompt, at} ->
case S#s.stack of
{_,_,_,{Mod,Line}} ->
mlist(Mod,Line,S#s.context);
_ ->
?att_msg("No info available...~n",[])
end,
?MODULE:aloop(S);
{Prompt, at, Ctx} ->
case S#s.stack of
{_,_,_,{Mod,Line}} ->
mlist(Mod,Line,Ctx);
_ ->
?att_msg("No info available...~n",[])
end,
?MODULE:aloop(S);
{Prompt, list_module, {Mod,Line,Ctx}} ->
mlist(Mod,Line,Ctx),
?MODULE:aloop(S);
{Prompt, list_module, {Mod,Line}} ->
mlist(Mod,Line),
?MODULE:aloop(S);
{Prompt, step = X} ->
int:meta(Meta, X),
?MODULE:aloop(S);
{Prompt, next = X} ->
int:meta(Meta, X),
?MODULE:aloop(S);
{Prompt, finish = X} ->
int:meta(Meta, X),
?MODULE:aloop(S);
{Prompt, processes} ->
plist(),
?MODULE:aloop(S);
{Prompt, messages = X} ->
Ms = int:meta(Meta, X),
?info_msg([?c_hi("MSGS"), ": ~p~n"] ,[Ms]),
?MODULE:aloop(S);
{Prompt, continue = X} ->
int:meta(Meta, X),
?MODULE:aloop(S);
{Prompt, skip = X} ->
int:meta(Meta, X),
?MODULE:aloop(S);
{Prompt, context, Ctx} ->
?MODULE:aloop(S#s{context = Ctx});
{Prompt, breakpoints} ->
breaks(),
?MODULE:aloop(S);
{Prompt, backtrace = X} ->
Bt = int:meta(Meta, X, 1),
?info_msg("BT ~p~n",[Bt]),
?MODULE:aloop(S);
{Prompt, interpret, Mod} ->
int:i(Mod),
?info_msg([?c_hi("Interpreted modules"), ": ~p~n"],
[int:interpreted()]),
?MODULE:aloop(S);
{Prompt, var, Var} ->
{_Cur,_Max,Bs,_} = S#s.stack,
case find_var(Var, Bs) of
[{VarName, Val}] ->
?info_msg([?c_hi("~p"), "= ~p~n"], [VarName, Val]);
[{_Var1, _Val1}|_] = Candidates ->
?info_msg([?c_hi("~p"), ?c_warn(" ambigious prefix"),
": ~p~n"], [Var, [N || {N,_} <- Candidates]]);
[] -> ?info_msg([?c_hi("~p"), ?c_err(" not found"), "~n"],
[Var])
end,
?MODULE:aloop(S);
{Prompt, pr_var, Var} ->
{_Cur,_Max,Bs,_} = S#s.stack,
try
case find_var(Var, Bs) of
[{VarName, Val}] ->
case S#s.stack of
{_,_,_,{Mod,_Line}} ->
Fname = edbg:find_source(Mod),
{ok, Defs} = pp_record:read(Fname),
?info_msg("~p =~n~s~n",
[VarName,
pp_record:print(Val, Defs)]);
_ ->
?err_msg("No module info available...~n",[])
end;
[{_Var1, _Val1}|_] = Candidates ->
?err_msg("~p ambigious prefix: ~p~n",
[Var, [N || {N,_} <- Candidates]]);
[] ->
?att_msg("~p not found~n",[Var])
end
catch
_:_ -> ?err_msg("Operation failed...~n",[])
end,
?MODULE:aloop(S);
{Prompt, set_break, Line} when is_integer(Line) ->
case S#s.break_at of
{Mod,_} ->
int:break(Mod, Line);
_ ->
?err_msg("Unknown Module; no break point set~n",[])
end,
save_all_breakpoints(),
?MODULE:aloop(S);
{Prompt, test_break, Line} when is_integer(Line) ->
case S#s.break_at of
{Mod,_} ->
t(Mod, Line);
_ ->
?err_msg("Unknown Module; no break point set~n",[])
end,
save_all_breakpoints(),
?MODULE:aloop(S);
{Prompt, delete_break, Line} when is_integer(Line) ->
case S#s.break_at of
{Mod,_} ->
int:delete_break(Mod, Line);
_ ->
?err_msg("Unknown Module; no break point deleted~n",[])
end,
save_all_breakpoints(),
?MODULE:aloop(S);
{Prompt, disable_break, Line} when is_integer(Line) ->
case S#s.break_at of
{Mod,_} ->
int:disable_break(Mod, Line);
_ ->
?err_msg("Unknown Module; no break point disabled~n", [])
end,
save_all_breakpoints(),
?MODULE:aloop(S);
{Prompt, enable_break, Line} when is_integer(Line) ->
case S#s.break_at of
{Mod,_} ->
int:enable_break(Mod, Line);
_ ->
?err_msg("Unknown Module; no break point enabled~n", [])
end,
save_all_breakpoints(),
?MODULE:aloop(S);
{Prompt, set_break, {Mod,Line}} when is_integer(Line) ->
int:break(Mod, Line),
save_all_breakpoints(),
?MODULE:aloop(S);
{Prompt, test_break, {Mod,Line}} when is_integer(Line) ->
t(Mod,Line),
save_all_breakpoints(),
?MODULE:aloop(S);
{Prompt, delete_break, {Mod,Line}} when is_integer(Line) ->
int:delete_break(Mod, Line),
save_all_breakpoints(),
?MODULE:aloop(S);
{Prompt, disable_break, {Mod,Line}} when is_integer(Line) ->
int:disable_break(Mod, Line),
save_all_breakpoints(),
?MODULE:aloop(S);
{Prompt, enable_break, {Mod,Line}} when is_integer(Line) ->
int:enable_break(Mod, Line),
save_all_breakpoints(),
?MODULE:aloop(S);
{Prompt, trace = X} ->
Trace = S#s.trace,
NewTrace = toggle(Trace),
int:meta(Meta, X, NewTrace),
?MODULE:aloop(S#s{trace = NewTrace});
{Prompt, quit} ->
exit(normal);
_X ->
?info_msg("aloop got: ~p~n",[_X]),
?MODULE:aloop(S)
end.
%%
%% {ok, Tokens, _} = erl_scan:string("lists:map(fun(X) -> X+1 end, L).").
%% {ok, Exprs} = erl_parse:parse_exprs(Tokens).
%% erl_eval:add_bindings('L',[1,2,3],erl_eval:new_bindings()).
%% erl_eval:exprs(Exprs,Bs).
%% {value,[2,3,4],[{'L',[1,2,3]}]}
%%
evaluate(ExprStr, Bindings) ->
try
{ok, Tokens, _} = erl_scan:string(ExprStr),
{ok, Exprs} = erl_parse:parse_exprs(Tokens),
{value, Value, _Bs} = erl_eval:exprs(Exprs, Bindings),
{ok, Value}
catch
_:_ ->
{error,"Failed to parse/evaluate expression"}
end.
print_all_breakpoints(L) ->
?att_msg("~nBREAKPOINTS~n",[]),
F = fun({{Mod,Line},[Status,Trigger,_,Cond]}) ->
?info_msg(" ~p:~p Status=~p Trigger=~p Cond=~p~n",
[Mod,Line,Status,Trigger,Cond])
end,
[F(X) || X <- L].
%% @doc Save all current break points.
%%
%% Identical to 'save_all_breakpoints/0'.
%%
%% @end
sab() ->
save_all_breakpoints().
%% @doc Save all current break points.
%%
%% Whenever a break point is set or modified, information is
%% stored on disk in the file 'breakpoints.edbg' .
%%
%% @end
save_all_breakpoints() ->
L = int:all_breaks(),
{ok,Fd} = file:open("breakpoints.edbg",[write]),
try
F = fun({{Mod,Line},[Status,Trigger,X,Cond]}) ->
case Cond of
{edbg,itest_at_break} ->
Cfun = get_it_break_fun(),
io:format(Fd,"{{~p,~p},{~p,~p,~p,~p}}.~n",
[Mod,Line,Status,Trigger,X,
term_to_binary(Cfun)]);
_ ->
io:format(Fd,"{{~p,~p},{~p,~p,~p,~p}}.~n",
[Mod,Line,Status,Trigger,X,Cond])
end
end,
[F(Z) || Z <- L]
after
file:close(Fd)
end.
%% @doc Load (previously) stored break points.
%%
%% Identical to 'load_all_breakpoints/0'.
%%
%% @end
lab() ->
load_all_breakpoints().
%% @doc Load (previously) stored break points.
%%
%% Whenever a break point is set or modified, information is
%% stored on disk in the file 'breakpoints.edbg' . This function
%% will load and set those breakpoints found in this file.
%%
%% @end
load_all_breakpoints() ->
{ok,L} = file:consult("breakpoints.edbg"),
F = fun({{Mod,Line},{Status,Trigger,_X,Cond}})->
case Cond of
Cbin when is_binary(Cbin) ->
Cfun = binary_to_term(Cbin),
it(Mod,Line,Cfun);
{Cmod,_} when Cmod =/= ?MODULE ->
i(Mod,Line),
ok = int:test_at_break(Mod,Line,Cond);
_ ->
i(Mod,Line)
end,
case {Status,Trigger} of
{inactive,_} -> int:disable_break(Mod,Line);
{_,disable} -> int:disable_break(Mod,Line); % disable?
_ -> false
end
end,
[F(Z) || Z <- L].
%% First try for exact match, then for a prefix match
find_var(Var, Bindings) ->
case int:get_binding(Var, Bindings) of
{value, Val} ->
[{Var,Val}];
_ ->
VarStr = erlang:atom_to_list(Var),
PrefixFun = fun({K,_V}) ->
lists:prefix(VarStr, erlang:atom_to_list(K))
end,
lists:filtermap(PrefixFun, Bindings)
end.
prompt(Pid, Apid) when is_pid(Pid), is_pid(Apid) ->
Prompt = "("++pid_to_list(Pid)++")> ",
ploop(Apid, Prompt, _PrevCmd = []).
%% @private
ploop(Apid, Prompt, PrevCmd) ->
%% Empty prompt repeats previous command
Cmd = case string:tokens(io:get_line(Prompt), "\n") of
[] -> PrevCmd;
Cmd0 -> Cmd0
end,
case Cmd of
["a"++X] -> at(Apid, X);
["l"++X] -> send_list_module(Apid, X);
["n"++_] -> Apid ! {self(), next};
["s"++_] -> Apid ! {self(), step};
["f"++_] -> Apid ! {self(), finish};
["c"++_] -> Apid ! {self(), continue};
["m"++_] -> Apid ! {self(), messages};
["pr"++X] -> Apid ! {self(), pr_var, list_to_atom(string:strip(X))};
["p"++_] -> Apid ! {self(), processes};
["k"++_] -> Apid ! {self(), skip};
["u"++_] -> Apid ! {self(), up};
["q"++_] -> Apid ! {self(), quit}, exit(normal);
["i"++X] -> Apid ! {self(), interpret, list_to_atom(string:strip(X))};
["v"++X] -> Apid ! {self(), var, list_to_atom(string:strip(X))};
["x"++X] -> set_context(Apid, X);
["h"++_] -> print_help();
["ba"++_] -> Apid ! {self(), backtrace};
["br"++_] -> Apid ! {self(), breakpoints};
["b"++X] -> set_break(Apid, X);
["te"++X] -> test_break(Apid, X);
["t"++_] -> Apid ! {self(), trace};
["en"++X] -> ena_break(Apid, X);
["e"++X] -> Apid ! {self(), eval_expr, string:strip(X)};
["di"++X] -> dis_break(Apid, X);
["de"++X] -> del_break(Apid, X);
["d"++_] -> Apid ! {self(), down};
_X ->
?info_msg("prompt got: ~p~n",[_X])
end,
?MODULE:ploop(Apid, Prompt, Cmd).
at(Apid, X) ->
try
[Ctx] = string:tokens(string:strip(X), " "),
Apid ! {self(), at, list_to_integer(Ctx)}
catch
_:_ -> Apid ! {self(), at}
end.
print_help() ->
S1 = " (h)elp (a)t <Ctx> (n)ext (s)tep (f)inish (c)ontinue s(k)ip",
S2 = " (m)essages (t)oggle trace (q)uit (br)eakpoints (p)rocesses",
S3 = " (de)lete/(di)sable/(en)able/(te)st/(b)reak <Line | Mod Line>",
S4 = " (v)ar <variable> (e)val <expr> (i)nterpret <Mod>",
S5 = " (pr)etty print record <variable> conte(x)t <Ctx>",
S6 = " (u)p (d)own (l)ist module <Mod Line <Ctx>>",
S = io_lib:format("~n~s~n~s~n~s~n~s~n~s~n~s~n", [S1,S2,S3,S4,S5,S6]),
?info_msg(?help_hi(S), []).
set_context(Apid, X) ->
case string:strip(X) of
[] ->
%% Empty argument resets to default context size of five rows
Apid ! {self(), context, ?DEFAULT_CONTEXT_SIZE};
Arg ->
%% Try setting context size, notify user if bad input
try
Apid ! {self(), context, list_to_integer(Arg)}
catch
error:badarg ->
?info_msg("context only takes numbers~n", [])
end
end.
set_break(Apid, X) ->
send_mod_line(Apid, X, set_break).
test_break(Apid, X) ->
send_mod_line(Apid, X, test_break).
del_break(Apid, X) ->
send_mod_line(Apid, X, delete_break).
dis_break(Apid, X) ->
send_mod_line(Apid, X, disable_break).
ena_break(Apid, X) ->
send_mod_line(Apid, X, enable_break).
send_mod_line(Apid, X, Cmd) ->
try
case string:tokens(string:strip(X), " ") of
[Mod,Line] ->
Apid ! {self(), Cmd, {list_to_atom(Mod),list_to_integer(Line)}};
[Line] ->
Apid ! {self(), Cmd, list_to_integer(Line)}
end,
true
catch
_:_ -> false
end.
send_list_module(Apid, X) ->
try
case string:tokens(string:strip(X), " ") of
[Mod,Line,Ctx] ->
Apid ! {self(), list_module, {list_to_atom(Mod),
list_to_integer(Line),
list_to_integer(Ctx)}};
[Mod,Line] ->
Apid ! {self(), list_module, {list_to_atom(Mod),
list_to_integer(Line)}}
end,
true
catch
_:_ -> false
end.
%% @doc Show all interpreted processes.
%%
%% Show all interpreted processes and what state there are in.
%% In particular this is useful to see if a process has stopped
%% at a break point. The process identifier ('Pid') displayed in the
%% leftmost column can be used with the 'attach/1' function.
%%
%% @end
plist() ->
Self = self(),
L = [X || X = {Pid, _Func, _Status, _Info} <- int:snapshot(),
Pid =/= Self],
%%[{<0.33.0>,{sloop,start,[]},idle,{}},
%% {<0.42.0>,{sloop,init,[]},break,{sloop,19}}]
lists:map(fun({Pid, {Mod,Name,Args}, break = Status, {Mod2,Line}}) ->
?info_msg("~10.p ~-25.s ~-10.s ~p:~p~n",
[Pid,q(Mod,Name,length(Args)),
q(Status),Mod2,Line]);
({Pid, {Mod,Name,Args}, exit = Status, Reason}) ->
if (Reason =/= normal) ->
?info_msg("~10.p ~-25.s ~-10.s Reason: ~p~n",
[Pid,
q(Mod,Name,length(Args)),
q(Status),Reason]);
true ->
false
end,
ok;
({Pid, {Mod,Name,Args}, running = Status, _}) ->
?info_msg("~10.p ~-25.s ~-10.s~n",
[Pid,q(Mod,Name,length(Args)),q(Status)]);
({Pid, {Mod,Name,Args}, idle = Status, _}) ->
?info_msg("~10.p ~-25.s ~-10.s~n",
[Pid,q(Mod,Name,length(Args)),q(Status)]);
({Pid, {Mod,Name,Args}, waiting = Status, _}) ->
?info_msg("~10.p ~-25.s ~-10.s~n",
[Pid,q(Mod,Name,length(Args)),q(Status)])
end, L),
ok.
q(M,F,A) ->
q(M)++":"++q(F)++"/"++q(A).
q(A) when is_atom(A) -> atom_to_list(A);
q(I) when is_integer(I) -> integer_to_list(I).
%% @doc List the source code, centered around a break point.
%%
%% The 'mlist/1' can also take a Module as an argument to
%% list the source ode starting from Row 1.
%% @end
mlist(Pid) when is_pid(Pid) ->
case get_break_point(Pid) of
{_Pid, {_Mod,_Name,_Args}, break, {Mod,Line}} ->
mlist(Mod, Line);
Else ->
Else
end;
mlist(Mod) when is_atom(Mod) ->
mod_list(Mod, {1,-1,-1}).
%% @doc List the source code of a 'Module' centered around 'Row'.
mlist(Mod, Row) when is_atom(Mod) ->
mod_list(Mod, {1,Row,5}).
%% @doc List the source code of a Module.
%%
%% List the source code of a 'Module', either centered around a triggered
%% break point, or around a given 'Line'. The amount of lines being display
%% around the line is controlled by the 'Contexft' value, which per default
%% is set to '5' (i.e display 5 lines above and below the line).
%%
%% Note that the listing will display the line numbers at the left border
%% of the output where breakpoints are high lighted by a '*' character
%% and the given line as '>'. However, if no line is given, the '>'
%% character will be used to denote where we currently are stopped.
%%
%% The 'mlist/3' can also take a sequence of integers for supplying a 'Pid'
%% when we should center around a break point.
%% @end
mlist(Mod, Row, Ctx) when is_atom(Mod) ->
mod_list(Mod, {1,Row,Ctx});
mlist(P0, P1, P2) when P0 >= 0, P1 >= 0, P2 >= 0 ->
mlist(c:pid(P0, P1, P2)).
mod_list(Mod, Rinfo)->
%% The interpreter requires both the source code and the object code.
%% The object code must include debug information
Fname = find_source(Mod),
{ok, SrcBin, Fname} = erl_prim_loader:get_file(Fname),
put(mod_bps, int:all_breaks(Mod)),
?info_msg([?c_hi("MODULE"), ": ~p.erl~n"], [Mod]),
lists:foldl(fun(Line,{Cur,Row,Ctx}) when Cur == Row ->
Fs = field_size(Row,Ctx),
?cur_line_msg("~"++Fs++".s> ~s~n",[i2l(Cur),b2l(Line)]),
{Cur+1,Row,Ctx};
(Line,{Cur,Row,Ctx}) when Row == -1 orelse
(Cur >= (Row-Ctx) andalso
Cur =< (Row+Ctx)) ->
Fs = field_size(Row,Ctx),
?info_msg("~"++Fs++".s~s ~s~n",[i2l(Cur),sep(Cur),
b2l(Line)]),
{Cur+1,Row,Ctx};
(_Line,{Cur,Row,Ctx}) ->
{Cur+1,Row,Ctx}
end,
Rinfo,
re:split(SrcBin,"\n")),
ok.
field_size(Row,Ctx) ->
integer_to_list(length(integer_to_list(Row+Ctx))).
sep(Row) ->
case get(mod_bps) of
[] ->
":";
Bs ->
case [X || {{_,L},_} = X <- Bs,
L == Row] of
[] ->
":";
_ ->
?c_err("*")
end
end.
%% @private
find_source(Mod) ->
{ok, Fname} = filelib:find_source(code:which(Mod)),
%% [Fname] = [Z || {source,Z} <-
%% hd([X || {compile,X} <-
%% apply(Mod,module_info,[])])],
Fname.
i2l(I) when is_integer(I) -> integer_to_list(I).
b2l(B) when is_binary(B) -> binary_to_list(B).
get_break_point(MyPid) ->
case [X || X = {Pid, _Func, _Status, _Info} <- int:snapshot(),
Pid == MyPid] of
[] ->
pid_not_found;
[{_Pid, {Mod,_Name,_Args}, break, {Mod,_Line}} = Bx] ->
Bx;
_ ->
no_break_found
end.