%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2011-2013. 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_istk).
-export([init/0,delayed_to_external/0,from_external/1,
push/3,pop/0,pop/1,stack_level/0,
delayed_stacktrace/0,delayed_stacktrace/2,
bindings/1,stack_frame/2,backtrace/2,
in_use_p/2]).
-include("dbg_ieval.hrl").
-define(STACK, ?MODULE).
-record(e,
{level, %Level
mfa, %{Mod,Func,Args|Arity}|{Fun,Args}
line, %Line called from
bindings,
lc %Last call (true|false)
}).
init() ->
init([]).
delayed_to_external() ->
Stack = get(?STACK),
fun() -> {stack,term_to_binary(Stack)} end.
from_external({stack,Stk}) ->
put(?STACK, binary_to_term(Stk)).
init(Stack) ->
put(?STACK, Stack).
%% We keep track of a call stack that is used for
%% 1) saving stack frames that can be inspected from an Attached
%% Process GUI (using dbg_icmd:get(Meta, stack_frame, {Dir, SP})
%% 2) generate an approximation of regular stacktrace -- sent to
%% Debugged when it should raise an exception or evaluate a
%% function (since it might possible raise an exception)
%%
%% How to push depends on the "Stack Trace" option (value saved in
%% process dictionary item 'trace_stack').
%% all - everything is pushed
%% no_tail - tail recursive push
%% false - nothing is pushed
%% Whenever a function returns, the corresponding call frame is popped.
push(Bs, #ieval{level=Le,module=Mod,function=Name,
arguments=As,line=Li}=Ieval, Lc) ->
Entry = #e{level=Le,mfa={Mod,Name,As},line=Li,bindings=Bs,lc=Lc},
case get(trace_stack) of
false ->
Ieval#ieval{level=Le+1};
no_tail when Lc ->
Ieval;
_ -> % all | no_tail when Lc =:= false
put(?STACK, [Entry|get(?STACK)]),
Ieval#ieval{level=Le+1}
end.
pop() ->
case get(trace_stack) of
false -> ignore;
_ -> % all | no_tail
case get(?STACK) of
[_Entry|Entries] ->
put(?STACK, Entries);
[] ->
ignore
end
end.
pop(Le) ->
case get(trace_stack) of
false -> ignore;
_ -> % all | no_tail
put(?STACK, pop(Le, get(?STACK)))
end.
pop(Level, [#e{level=Le}|Stack]) when Level =< Le ->
pop(Level, Stack);
pop(_Level, Stack) ->
Stack.
%% stack_level() -> Le
%% stack_level(Stack) -> Le
%% Top call level
stack_level() ->
stack_level(get(?STACK)).
stack_level([]) -> 1;
stack_level([#e{level=Le}|_]) -> Le.
%% delayed_stacktrace() -> CreateStacktraceFun
%% delayed_stacktrace(ArgFlag, #ieval{}) -> CreateStacktraceFun
%% ArgFlag = no_args | include_args
%% CreateStacktraceFun = fun(NumberOfEntries)
%%
%% Return a fun that can convert the internal stack format to
%% an imitation of the regular stacktrace.
delayed_stacktrace() ->
Stack0 = get(?STACK),
fun(NumEntries) ->
Stack = stacktrace(NumEntries, Stack0, []),
[finalize(ArityOnly) || {ArityOnly,_} <- Stack]
end.
delayed_stacktrace(include_args, Ieval) ->
#ieval{module=Mod,function=Name,arguments=As,line=Li} = Ieval,
Stack0 = [#e{mfa={Mod,Name,As},line=Li}|get(?STACK)],
fun(NumEntries) ->
case stacktrace(NumEntries, Stack0, []) of
[] ->
[];
[{_,WithArgs}|Stack] ->
[finalize(WithArgs) |
[finalize(ArityOnly) || {ArityOnly,_} <- Stack]]
end
end;
delayed_stacktrace(no_args, Ieval) ->
#ieval{module=Mod,function=Name,arguments=As,line=Li} = Ieval,
Stack0 = [#e{mfa={Mod,Name,As},line=Li}|get(?STACK)],
fun(NumEntries) ->
Stack = stacktrace(NumEntries, Stack0, []),
[finalize(ArityOnly) || {ArityOnly,_} <- Stack]
end.
stacktrace(N, [#e{lc=true}|T], Acc) ->
stacktrace(N, T, Acc);
stacktrace(N, [E|T], []) ->
stacktrace(N-1, T, [normalize(E)]);
stacktrace(N, [E|T], [{P,_}|_]=Acc) when N > 0 ->
case normalize(E) of
{P,_} ->
stacktrace(N, T, Acc);
New ->
stacktrace(N-1, T, [New|Acc])
end;
stacktrace(_, _, Acc) ->
lists:reverse(Acc).
normalize(#e{mfa={M,Fun,As},line=Li}) when is_function(Fun) ->
Loc = {M,Li},
{{Fun,length(As),Loc},{Fun,As,Loc}};
normalize(#e{mfa={M,F,As},line=Li}) ->
Loc = {M,Li},
{{M,F,length(As),Loc},{M,F,As,Loc}}.
finalize({M,F,A,Loc}) -> {M,F,A,line(Loc)};
finalize({Fun,A,Loc}) -> {Fun,A,line(Loc)}.
line({Mod,Line}) when Line > 0 ->
[{file,atom_to_list(Mod)++".erl"},{line,Line}];
line(_) -> [].
%% bindings(SP) -> Bs
%% SP = Le % stack pointer
%% Return the bindings for the specified call level
bindings(SP) ->
bindings(SP, get(?STACK)).
bindings(SP, [#e{level=SP,bindings=Bs}|_]) ->
Bs;
bindings(SP, [_Entry|Entries]) ->
bindings(SP, Entries);
bindings(_SP, []) ->
erl_eval:new_bindings().
%% stack_frame(Dir, SP) -> {Le, Where, Bs} | top | bottom
%% Dir = up | down
%% Where = {Cm, Li}
%% Cm = Module | undefined % module
%% Li = int() | -1 % line number
%% Bs = bindings()
%% Return stack frame info one step up/down from given stack pointer
%% up = to lower call levels
%% down = to higher call levels
stack_frame(up, SP) ->
stack_frame(SP, up, get(?STACK));
stack_frame(down, SP) ->
stack_frame(SP, down, lists:reverse(get(?STACK))).
stack_frame(SP, up, [#e{level=Le,mfa={Cm,_,_},line=Li,bindings=Bs}|_])
when Le < SP ->
{Le,{Cm,Li},Bs};
stack_frame(SP, down, [#e{level=Le,mfa={Cm,_,_},line=Li,bindings=Bs}|_])
when Le > SP ->
{Le,{Cm,Li},Bs};
stack_frame(SP, Dir, [#e{level=SP}|Stack]) ->
case Stack of
[#e{level=Le,mfa={Cm,_,_},line=Li,bindings=Bs}|_] ->
{Le,{Cm,Li},Bs};
[] when Dir =:= up ->
top;
[] when Dir =:= down ->
bottom
end;
stack_frame(SP, Dir, [_Entry|Stack]) ->
stack_frame(SP, Dir, Stack).
%% backtrace(HowMany) -> Backtrace
%% HowMany = all | int()
%% Backtrace = {Le, MFA}
%% Return all/the last N called functions, in reversed call order
backtrace(HowMany, Ieval) ->
#ieval{level=Level,module=Mod,function=Name,arguments=As} = Ieval,
Stack0 = [#e{level=Level,mfa={Mod,Name,As}}|get(?STACK)],
Stack = case HowMany of
all -> Stack0;
N -> lists:sublist(Stack0, N)
end,
[{Le,MFA} || #e{level=Le,mfa=MFA} <- Stack].
%%--------------------------------------------------------------------
%% in_use_p(Mod, Cm) -> boolean()
%% Mod = Cm = atom()
%% Returns true if Mod is found on the stack, otherwise false.
%%--------------------------------------------------------------------
in_use_p(Mod, Mod) -> true;
in_use_p(Mod, _Cm) ->
case get(trace_stack) of
false -> true;
_ -> % all | no_tail
lists:any(fun(#e{mfa={M,_,_}}) when M =:= Mod -> true;
(_) -> false
end, get(?STACK))
end.