aboutsummaryrefslogtreecommitdiffstats
path: root/lib/debugger/src/dbg_iserver.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/debugger/src/dbg_iserver.erl')
-rw-r--r--lib/debugger/src/dbg_iserver.erl606
1 files changed, 606 insertions, 0 deletions
diff --git a/lib/debugger/src/dbg_iserver.erl b/lib/debugger/src/dbg_iserver.erl
new file mode 100644
index 0000000000..4c1e9ccb7b
--- /dev/null
+++ b/lib/debugger/src/dbg_iserver.erl
@@ -0,0 +1,606 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-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_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) ->
+ ensure_started(),
+ call(Request).
+
+safe_cast(Request) ->
+ ensure_started(),
+ cast(Request).
+
+ensure_started() ->
+ case whereis(?MODULE) of
+ undefined -> start();
+ _Pid -> ignore
+ 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
+%% --- -----
+%% attributes Attr
+%% exports Exp
+%% defs []
+%% mod_bin Binary
+%% mod_raw Raw Binary
+%% mod_file File
+%% module Mod
+%% {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=all}}.
+
+%% 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 = lists:map(fun(Proc) ->
+ {Proc#proc.pid, Proc#proc.function,
+ Proc#proc.status, Proc#proc.info}
+ end,
+ 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:keysearch(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}};
+ {value, _Break} ->
+ {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 = lists:filter(fun({{M,_L}, _Options}) ->
+ if M==Mod -> true; true -> false end
+ end,
+ State#state.breaks),
+ {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,
+ [{{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_raw, Bin}] = ets:lookup(ModDb, mod_raw),
+ {reply, {ok, Bin}, State};
+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}) ->
+ if Status==exit -> false; true -> true end
+ end,
+ State#state.procs),
+ {noreply, State#state{procs=Procs}};
+
+%% Breakpoint handling
+handle_cast({delete_break, Point}, State) ->
+ case lists:keysearch(Point, 1, State#state.breaks) of
+ {value, _Break} ->
+ 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:keysearch(Point, 1, State#state.breaks) of
+ {value, {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}) ->
+ if M==Mod -> false; true -> true end
+ 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);
+ true -> ignore
+ 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) when is_record(Proc, proc) ->
+ case Proc#proc.attpid of
+ AttPid when is_pid(AttPid) -> ignore;
+ undefined ->
+ auto_attach(Why, Auto, Proc#proc.pid)
+ 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 = lists:map(fun(Proc) -> Proc#proc.meta end, 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}.
+
+get_proc({Type, Pid}, Procs) ->
+ Index = case Type of
+ pid -> #proc.pid;
+ meta -> #proc.meta;
+ attpid -> #proc.attpid
+ end,
+ case lists:keysearch(Pid, Index, Procs) of
+ {value, Proc} -> {true, Proc};
+ false -> false
+ end.