%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1998-2016. 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% %% -module(dbg_iserver). -behaviour(gen_server). %% External exports -export([start/0, stop/0, find/0, call/1, call/2, cast/1, cast/2, safe_call/1, safe_cast/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(proc, {pid, % pid() Debugged process meta, % pid() Meta process attpid, % pid() | undefined Attached process status, % running | exit | idle | waiting info = {}, % {} | term() exit_info= {}, % {} | {{Mod,Line}, Bs, Stack} function % {Mod,Func,Args} Initial function call }). -record(state, {db, % ETS table procs = [], % [#proc{}] breaks = [], % [{{M,L},Options} Breakpoints auto, % Auto attach settings stack, % Stack trace settings subs = [] % [pid()] Subscribers (Debugger) }). %%==================================================================== %% External exports %%==================================================================== start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []). stop() -> gen_server:cast(?MODULE, stop). find() -> global:whereis_name(?MODULE). call(Request) -> gen_server:call(?MODULE, Request, infinity). call(Int, Request) -> gen_server:call(Int, Request, infinity). cast(Request) -> gen_server:cast(?MODULE, Request). cast(Int, Request) -> gen_server:cast(Int, Request). safe_call(Request) -> {ok, _} = ensure_started(), call(Request). safe_cast(Request) -> {ok, _} = ensure_started(), cast(Request). ensure_started() -> case whereis(?MODULE) of undefined -> start(); Pid -> {ok, Pid} end. %%--Module database--------------------------------------------------- %% This server creates an ETS table, where the following information %% is saved: %% %% Key Value %% --- ----- %% {Mod, refs} [ModDb] %% ModDb [pid()] %% %% In each ModDb, the following information is saved by dbg_iload: %% %% Key Value %% --- ----- %% defs [] %% mod_bin Binary %% mod_raw Raw Binary %% mod_file File %% {Mod,Name,Arity,Exported} Cs %% {'fun',Mod,Index,Uniq} {Name,Arity,Cs} %% Line {Pos,PosNL} %%==================================================================== %% gen_server callbacks %%==================================================================== init([]) -> process_flag(trap_exit, true), global:register_name(?MODULE, self()), Db = ets:new(?MODULE, [ordered_set, protected]), {ok, #state{db=Db, auto=false, stack=no_tail}}. %% Attaching to a process handle_call({attached, AttPid, Pid}, _From, State) -> {true, Proc} = get_proc({pid, Pid}, State#state.procs), case Proc#proc.attpid of undefined -> link(AttPid), case Proc#proc.status of exit -> Args = [self(), AttPid,Pid,Proc#proc.info, Proc#proc.exit_info], Meta = spawn_link(dbg_ieval, exit_info, Args), Proc2 = Proc#proc{meta=Meta, attpid=AttPid}, Procs = lists:keyreplace(Pid, #proc.pid, State#state.procs, Proc2), {reply, {ok,Meta}, State#state{procs=Procs}}; _Status -> Meta = Proc#proc.meta, send(Meta, {attached, AttPid}), Procs = lists:keyreplace(Pid, #proc.pid, State#state.procs, Proc#proc{attpid=AttPid}), {reply, {ok, Meta}, State#state{procs=Procs}} end; _AttPid -> % there is already an attached process {reply, error, State} end; %% Getting and setting options handle_call(get_auto_attach, _From, State) -> {reply, State#state.auto, State}; handle_call(get_stack_trace, _From, State) -> {reply, State#state.stack, State}; %% Retrieving information handle_call(snapshot, _From, State) -> Reply = [{Proc#proc.pid, Proc#proc.function, Proc#proc.status, Proc#proc.info} || Proc <- State#state.procs], {reply, Reply, State}; handle_call({get_meta, Pid}, _From, State) -> Reply = case get_proc({pid, Pid}, State#state.procs) of {true, Proc} -> {ok, Proc#proc.meta}; false -> {error, not_interpreted} end, {reply, Reply, State}; handle_call({get_attpid, Pid}, _From, State) -> Reply = case get_proc({pid, Pid}, State#state.procs) of {true, Proc} -> {ok, Proc#proc.attpid}; false -> {error, not_interpreted} end, {reply, Reply, State}; %% Breakpoint handling handle_call({new_break, Point, Options}, _From, State) -> case lists:keymember(Point, 1, State#state.breaks) of false -> Break = {Point, Options}, send_all([subscriber, meta, attached], {new_break, Break}, State), Breaks = keyinsert(Break, 1, State#state.breaks), {reply, ok, State#state{breaks=Breaks}}; true -> {reply, {error, break_exists}, State} end; handle_call(all_breaks, _From, State) -> {reply, State#state.breaks, State}; handle_call({all_breaks, Mod}, _From, State) -> Reply = [Break || Break = {{M, _},_} <- State#state.breaks, M =:= Mod], {reply, Reply, State}; %% From Meta process handle_call({new_process, Pid, Meta, Function}, _From, State) -> link(Meta), %% A new, debugged process has been started. Return its status, %% ie running (running as usual) or break (stop) %% The status depends on if the process is automatically attached to %% or not. Reply = case auto_attach(init, State#state.auto, Pid) of AttPid when is_pid(AttPid) -> break; ignore -> running end, %% Do not add AttPid, it should call attached/2 when started instead Proc = #proc{pid=Pid, meta=Meta, status=running, function=Function}, send_all(subscriber, {new_process, {Pid,Function,running,{}}}, State), {reply, Reply, State#state{procs=State#state.procs++[Proc]}}; %% Code loading handle_call({load, Mod, Src, Bin}, _From, State) -> %% Create an ETS table for storing information about the module Db = State#state.db, ModDb = ets:new(Mod, [ordered_set, public]), ModDbs = case ets:lookup(Db, {Mod, refs}) of [] -> []; [{{Mod, refs}, ModDbs1}] -> ModDbs1 end, ets:insert(Db, {{Mod, refs}, [ModDb|ModDbs]}), ets:insert(Db, {ModDb, []}), %% Load the code {ok, Mod} = dbg_iload:load_mod(Mod, Src, Bin, ModDb), %% Inform all subscribers and attached processes send_all([subscriber, attached], {interpret, Mod}, State), {reply, {module, Mod}, State}; %% Module database handle_call({get_module_db, Mod, Pid}, _From, State) -> Db = State#state.db, Reply = case ets:lookup(Db, {Mod, refs}) of [] -> not_found; [{{Mod, refs}, [ModDb|_ModDbs]}] -> [{ModDb, Pids}] = ets:lookup(Db, ModDb), ets:insert(Db, {ModDb, [Pid|Pids]}), ModDb end, {reply, Reply, State}; handle_call({lookup, Mod, Key}, _From, State) -> Db = State#state.db, Reply = case ets:lookup(Db, {Mod, refs}) of [] -> not_found; [{{Mod, refs}, [ModDb|_ModDbs]}] -> case ets:lookup(ModDb, Key) of [] -> not_found; [{Key, Value}] -> {ok, Value} end end, {reply, Reply, State}; handle_call({functions, Mod}, _From, State) -> Db = State#state.db, Reply = case ets:lookup(Db, {Mod, refs}) of [] -> []; [{{Mod, refs}, [ModDb|_ModDbs]}] -> Pattern = {{Mod,'$1','$2','_'}, '_'}, ets:match(ModDb, Pattern) end, {reply, Reply, State}; handle_call({contents, Mod, Pid}, _From, State) -> Db = State#state.db, [{{Mod, refs}, ModDbs}] = ets:lookup(Db, {Mod, refs}), ModDb = if Pid =:= any -> hd(ModDbs); true -> lists:foldl(fun(T, not_found) -> [{T, Pids}] = ets:lookup(Db, T), case lists:member(Pid, Pids) of true -> T; false -> not_found end; (_T, T) -> T end, not_found, ModDbs) end, [{mod_bin, Bin}] = ets:lookup(ModDb, mod_bin), {reply, {ok, Bin}, State}; handle_call({raw_contents, Mod, Pid}, _From, State) -> Db = State#state.db, case ets:lookup(Db, {Mod, refs}) of [{{Mod, refs}, ModDbs}] -> ModDb = if Pid =:= any -> hd(ModDbs); true -> lists:foldl(fun(T, not_found) -> [{T, Pids}] = ets:lookup(Db, T), case lists:member(Pid, Pids) of true -> T; false -> not_found end; (_T, T) -> T end, not_found, ModDbs) end, [{mod_raw, Bin}] = ets:lookup(ModDb, mod_raw), {reply, {ok, Bin}, State}; [] -> % code not interpreted {reply, not_found, State} end; handle_call({is_interpreted, Mod, Name, Arity}, _From, State) -> Db = State#state.db, Reply = case ets:lookup(Db, {Mod, refs}) of [] -> false; [{{Mod, refs}, [ModDb|_ModDbs]}] -> Pattern = {{Mod,Name,Arity,'_'}, '_'}, case ets:match_object(ModDb, Pattern) of [{_Key, Clauses}] -> {true, Clauses}; [] -> false end end, {reply, Reply, State}; handle_call(all_interpreted, _From, State) -> Db = State#state.db, Mods = ets:select(Db, [{{{'$1',refs},'_'},[],['$1']}]), {reply, Mods, State}; handle_call({file, Mod}, From, State) -> {reply, Res, _} = handle_call({lookup, Mod, mod_file}, From, State), Reply = case Res of {ok, File} -> File; not_found -> {error, not_loaded} end, {reply, Reply, State}. handle_cast(stop, State) -> {stop, shutdown, State}; handle_cast({subscribe, Sub}, State) -> {noreply, State#state{subs=[Sub|State#state.subs]}}; %% Attaching to a process handle_cast({attach, Pid, {Mod, Func, Args}}, State) -> %% Simply spawn process, which should call int:attached(Pid) spawn(Mod, Func, [Pid | Args]), {noreply, State}; %% Getting and setting options handle_cast({set_auto_attach, false}, State) -> send_all(subscriber, {auto_attach, false}, State), {noreply, State#state{auto=false}}; handle_cast({set_auto_attach, Flags, Function}, State) -> send_all(subscriber, {auto_attach, {Flags, Function}}, State), {noreply, State#state{auto={Flags, Function}}}; handle_cast({set_stack_trace, Flag}, State) -> send_all(subscriber, {stack_trace, Flag}, State), {noreply, State#state{stack=Flag}}; %% Retrieving information handle_cast(clear, State) -> Procs = lists:filter(fun(#proc{status=Status}) -> Status =/= exit end, State#state.procs), {noreply, State#state{procs=Procs}}; %% Breakpoint handling handle_cast({delete_break, Point}, State) -> case lists:keymember(Point, 1, State#state.breaks) of true -> send_all([subscriber, meta, attached], {delete_break, Point}, State), Breaks = lists:keydelete(Point, 1, State#state.breaks), {noreply, State#state{breaks=Breaks}}; false -> {noreply, State} end; handle_cast({break_option, Point, Option, Value}, State) -> case lists:keyfind(Point, 1, State#state.breaks) of {Point, Options} -> N = case Option of status -> 1; action -> 2; condition -> 4 end, Options2 = list_setelement(N, Options, Value), send_all([subscriber, meta, attached], {break_options, {Point, Options2}}, State), Breaks = lists:keyreplace(Point, 1, State#state.breaks, {Point, Options2}), {noreply, State#state{breaks=Breaks}}; false -> {noreply, State} end; handle_cast(no_break, State) -> send_all([subscriber, meta, attached], no_break, State), {noreply, State#state{breaks=[]}}; handle_cast({no_break, Mod}, State) -> send_all([subscriber, meta, attached], {no_break, Mod}, State), Breaks = lists:filter(fun({{M, _L}, _O}) -> M =/= Mod end, State#state.breaks), {noreply, State#state{breaks=Breaks}}; %% From Meta process handle_cast({set_status, Meta, Status, Info}, State) -> {true, Proc} = get_proc({meta, Meta}, State#state.procs), send_all(subscriber, {new_status, Proc#proc.pid, Status, Info}, State), if Status =:= break -> _ = auto_attach(break, State#state.auto, Proc), ok; true -> ok end, Proc2 = Proc#proc{status=Status, info=Info}, {noreply, State#state{procs=lists:keyreplace(Meta, #proc.meta, State#state.procs, Proc2)}}; handle_cast({set_exit_info, Meta, ExitInfo}, State) -> {true, Proc} = get_proc({meta, Meta}, State#state.procs), Procs = lists:keyreplace(Meta, #proc.meta, State#state.procs, Proc#proc{exit_info=ExitInfo}), {noreply,State#state{procs=Procs}}; %% Code loading handle_cast({delete, Mod}, State) -> %% Remove the ETS table with information about the module Db = State#state.db, case ets:lookup(Db, {Mod, refs}) of [] -> % Mod is not interpreted {noreply, State}; [{{Mod, refs}, ModDbs}] -> ets:delete(Db, {Mod, refs}), AllPids = lists:foldl( fun(ModDb, PidsAcc) -> [{ModDb, Pids}] = ets:lookup(Db, ModDb), ets:delete(Db, ModDb), ets:delete(ModDb), PidsAcc++Pids end, [], ModDbs), lists:foreach(fun(Pid) -> case get_proc({pid, Pid}, State#state.procs) of {true, Proc} -> send(Proc#proc.meta, {old_code, Mod}); false -> ignore % pid may have exited end end, AllPids), send_all([subscriber,attached], {no_interpret, Mod}, State), %% Remove all breakpoints for Mod handle_cast({no_break, Mod}, State) end. %% Process exits handle_info({'EXIT',Who,Why}, State) -> case get_proc({meta, Who}, State#state.procs) of %% Exited process is a meta process for exit_info {true,#proc{status=exit}} -> {noreply,State}; %% Exited process is a meta process {true,Proc} -> Pid = Proc#proc.pid, ExitInfo = Proc#proc.exit_info, %% Check if someone is attached to the debugged process, %% if so a new meta process should be started Meta = case Proc#proc.attpid of AttPid when is_pid(AttPid) -> spawn_link(dbg_ieval, exit_info, [self(),AttPid,Pid,Why,ExitInfo]); undefined -> %% Otherwise, auto attach if necessary _ = auto_attach(exit, State#state.auto, Pid), Who end, send_all(subscriber, {new_status,Pid,exit,Why}, State), Procs = lists:keyreplace(Who, #proc.meta, State#state.procs, Proc#proc{meta=Meta, status=exit, info=Why}), {noreply,State#state{procs=Procs}}; false -> case get_proc({attpid, Who}, State#state.procs) of %% Exited process is an attached process {true, Proc} -> %% If status==exit, then the meta process is a %% simple meta for a terminated process and can be %% terminated as well (it is only needed by %% the attached process) case Proc#proc.status of exit -> send(Proc#proc.meta, stop); _Status -> send(Proc#proc.meta, detached) end, Procs = lists:keyreplace(Proc#proc.pid, #proc.pid, State#state.procs, Proc#proc{attpid=undefined}), {noreply, State#state{procs=Procs}}; %% Otherwise exited process must be a subscriber false -> Subs = lists:delete(Who, State#state.subs), {noreply, State#state{subs=Subs}} end end. terminate(_Reason, _State) -> EbinDir = filename:join(code:lib_dir(debugger), "ebin"), code:unstick_dir(EbinDir), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%==================================================================== %% Internal functions %%==================================================================== auto_attach(Why, Auto, #proc{attpid = Attpid, pid = Pid}) -> case Attpid of undefined -> auto_attach(Why, Auto, Pid); _ when is_pid(Attpid) -> ignore end; auto_attach(Why, Auto, Pid) when is_pid(Pid) -> case Auto of false -> ignore; {Flags, {Mod, Func, Args}} -> case lists:member(Why, Flags) of true -> spawn(Mod, Func, [Pid | Args]); false -> ignore end end. keyinsert(Tuple1, N, [Tuple2|Tuples]) -> if element(N, Tuple1) < element(N, Tuple2) -> [Tuple1, Tuple2|Tuples]; true -> [Tuple2 | keyinsert(Tuple1, N, Tuples)] end; keyinsert(Tuple, _N, []) -> [Tuple]. list_setelement(N, L, E) -> list_setelement(1, N, L, E). list_setelement(I, I, [_|T], E) -> [E|T]; list_setelement(I, N, [H|T], E) -> [H|list_setelement(I+1, N, T, E)]. mapfilter(Fun, [H|T]) -> case Fun(H) of ignore -> mapfilter(Fun, T); H2 -> [H2|mapfilter(Fun, T)] end; mapfilter(_Fun, []) -> []. send_all([Type|Types], Msg, State) -> send_all(Type, Msg, State), send_all(Types, Msg, State); send_all([], _Msg, _State) -> ok; send_all(subscriber, Msg, State) -> send_all(State#state.subs, Msg); send_all(meta, Msg, State) -> Metas = [Proc#proc.meta || Proc <- State#state.procs], send_all(Metas, Msg); send_all(attached, Msg, State) -> AttPids= mapfilter(fun(Proc) -> case Proc#proc.attpid of Pid when is_pid(Pid) -> Pid; undefined -> ignore end end, State#state.procs), send_all(AttPids, Msg). send_all(Pids, Msg) -> lists:foreach(fun(Pid) -> send(Pid, Msg) end, Pids). send(Pid, Msg) -> Pid ! {int, Msg}, ok. get_proc({Type, Pid}, Procs) -> Index = case Type of pid -> #proc.pid; meta -> #proc.meta; attpid -> #proc.attpid end, case lists:keyfind(Pid, Index, Procs) of false -> false; Proc -> {true, Proc} end.