1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1998-2016. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(dbg_iserver).
21-behaviour(gen_server).
22
23%% External exports
24-export([start/0, stop/0, find/0,
25	 call/1, call/2, cast/1, cast/2, safe_call/1, safe_cast/1]).
26
27%% gen_server callbacks
28-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
29	 terminate/2, code_change/3]).
30
31-record(proc,  {pid,           % pid() Debugged process
32		meta,          % pid() Meta process
33		attpid,        % pid() | undefined  Attached process
34		status,        % running | exit | idle | waiting
35		info     = {}, % {} | term()
36		exit_info= {}, % {} | {{Mod,Line}, Bs, Stack}
37		function       % {Mod,Func,Args} Initial function call
38	       }).
39
40-record(state, {db,            % ETS table
41		procs    = [], % [#proc{}]
42		breaks   = [], % [{{M,L},Options} Breakpoints
43		auto,          % Auto attach settings
44		stack,         % Stack trace settings
45		subs     = []  % [pid()]   Subscribers (Debugger)
46	       }).
47
48
49%%====================================================================
50%% External exports
51%%====================================================================
52
53start() ->
54    gen_server:start({local, ?MODULE}, ?MODULE, [], []).
55
56stop() ->
57    gen_server:cast(?MODULE, stop).
58
59find() ->
60    global:whereis_name(?MODULE).
61
62call(Request) ->
63    gen_server:call(?MODULE, Request, infinity).
64
65call(Int, Request) ->
66    gen_server:call(Int, Request, infinity).
67
68cast(Request) ->
69    gen_server:cast(?MODULE, Request).
70
71cast(Int, Request) ->
72    gen_server:cast(Int, Request).
73
74safe_call(Request) ->
75    {ok, _} = ensure_started(),
76    call(Request).
77
78safe_cast(Request) ->
79    {ok, _} = ensure_started(),
80    cast(Request).
81
82ensure_started() ->
83    case whereis(?MODULE) of
84	undefined -> start();
85	Pid -> {ok, Pid}
86    end.
87
88%%--Module database---------------------------------------------------
89%% This server creates an ETS table, where the following information
90%% is saved:
91%%
92%% Key                        Value
93%% ---                        -----
94%% {Mod, refs}                [ModDb]
95%% ModDb                      [pid()]
96%%
97%% In each ModDb, the following information is saved by dbg_iload:
98%%
99%% Key                        Value
100%% ---                        -----
101%% defs                       []
102%% mod_bin                    Binary
103%% mod_raw                    Raw Binary
104%% mod_file                   File
105%% {Mod,Name,Arity,Exported}  Cs
106%% {'fun',Mod,Index,Uniq}     {Name,Arity,Cs}
107%% Line                       {Pos,PosNL}
108
109
110%%====================================================================
111%% gen_server callbacks
112%%====================================================================
113
114init([]) ->
115    process_flag(trap_exit, true),
116    global:register_name(?MODULE, self()),
117    Db = ets:new(?MODULE, [ordered_set, protected]),
118    {ok, #state{db=Db, auto=false, stack=no_tail}}.
119
120%% Attaching to a process
121handle_call({attached, AttPid, Pid}, _From, State) ->
122    {true, Proc} = get_proc({pid, Pid}, State#state.procs),
123    case Proc#proc.attpid of
124	undefined ->
125	    link(AttPid),
126	    case Proc#proc.status of
127		exit ->
128		    Args = [self(),
129			    AttPid,Pid,Proc#proc.info,
130			    Proc#proc.exit_info],
131		    Meta = spawn_link(dbg_ieval, exit_info, Args),
132		    Proc2 = Proc#proc{meta=Meta, attpid=AttPid},
133		    Procs = lists:keyreplace(Pid, #proc.pid,
134					     State#state.procs, Proc2),
135		    {reply, {ok,Meta}, State#state{procs=Procs}};
136		_Status ->
137		    Meta = Proc#proc.meta,
138		    send(Meta, {attached, AttPid}),
139		    Procs = lists:keyreplace(Pid, #proc.pid,
140					     State#state.procs,
141					     Proc#proc{attpid=AttPid}),
142		    {reply, {ok, Meta}, State#state{procs=Procs}}
143	    end;
144	_AttPid -> % there is already an attached process
145	    {reply, error, State}
146    end;
147
148%% Getting and setting options
149handle_call(get_auto_attach, _From, State) ->
150    {reply, State#state.auto, State};
151handle_call(get_stack_trace, _From, State) ->
152    {reply, State#state.stack, State};
153
154%% Retrieving information
155handle_call(snapshot, _From, State) ->
156    Reply = [{Proc#proc.pid, Proc#proc.function,
157	      Proc#proc.status, Proc#proc.info} || Proc <- State#state.procs],
158    {reply, Reply, State};
159handle_call({get_meta, Pid}, _From, State) ->
160    Reply = case get_proc({pid, Pid}, State#state.procs) of
161		{true, Proc} ->
162		    {ok, Proc#proc.meta};
163		false ->
164		    {error, not_interpreted}
165	    end,
166    {reply, Reply, State};
167handle_call({get_attpid, Pid}, _From, State) ->
168    Reply = case get_proc({pid, Pid}, State#state.procs) of
169		{true, Proc} ->
170		    {ok, Proc#proc.attpid};
171		false ->
172		    {error, not_interpreted}
173	    end,
174    {reply, Reply, State};
175
176
177%% Breakpoint handling
178handle_call({new_break, Point, Options}, _From, State) ->
179    case lists:keymember(Point, 1, State#state.breaks) of
180	false ->
181	    Break = {Point, Options},
182	    send_all([subscriber, meta, attached],
183		     {new_break, Break}, State),
184	    Breaks = keyinsert(Break, 1, State#state.breaks),
185	    {reply, ok, State#state{breaks=Breaks}};
186	true ->
187	    {reply, {error, break_exists}, State}
188    end;
189handle_call(all_breaks, _From, State) ->
190    {reply, State#state.breaks, State};
191handle_call({all_breaks, Mod}, _From, State) ->
192    Reply = [Break || Break = {{M, _},_} <- State#state.breaks, M =:= Mod],
193    {reply, Reply, State};
194
195%% From Meta process
196handle_call({new_process, Pid, Meta, Function}, _From, State) ->
197    link(Meta),
198
199    %% A new, debugged process has been started. Return its status,
200    %% ie running (running as usual) or break (stop)
201    %% The status depends on if the process is automatically attached to
202    %% or not.
203    Reply = case auto_attach(init, State#state.auto, Pid) of
204		AttPid when is_pid(AttPid) -> break;
205		ignore -> running
206	    end,
207
208    %% Do not add AttPid, it should call attached/2 when started instead
209    Proc = #proc{pid=Pid, meta=Meta, status=running, function=Function},
210    send_all(subscriber,
211	     {new_process, {Pid,Function,running,{}}}, State),
212
213    {reply, Reply, State#state{procs=State#state.procs++[Proc]}};
214
215%% Code loading
216handle_call({load, Mod, Src, Bin}, _From, State) ->
217    %% Create an ETS table for storing information about the module
218    Db = State#state.db,
219    ModDb = ets:new(Mod, [ordered_set, public]),
220    ModDbs = case ets:lookup(Db, {Mod, refs}) of
221		 [] -> [];
222		 [{{Mod, refs}, ModDbs1}] -> ModDbs1
223	     end,
224    ets:insert(Db, {{Mod, refs}, [ModDb|ModDbs]}),
225    ets:insert(Db, {ModDb, []}),
226
227    %% Load the code
228    {ok, Mod} = dbg_iload:load_mod(Mod, Src, Bin, ModDb),
229
230    %% Inform all subscribers and attached processes
231    send_all([subscriber, attached], {interpret, Mod}, State),
232
233    {reply, {module, Mod}, State};
234
235%% Module database
236handle_call({get_module_db, Mod, Pid}, _From, State) ->
237    Db = State#state.db,
238    Reply = case ets:lookup(Db, {Mod, refs}) of
239		[] -> not_found;
240		[{{Mod, refs}, [ModDb|_ModDbs]}] ->
241		    [{ModDb, Pids}] = ets:lookup(Db, ModDb),
242		    ets:insert(Db, {ModDb, [Pid|Pids]}),
243		    ModDb
244	    end,
245    {reply, Reply, State};
246handle_call({lookup, Mod, Key}, _From, State) ->
247    Db = State#state.db,
248    Reply = case ets:lookup(Db, {Mod, refs}) of
249		[] -> not_found;
250		[{{Mod, refs}, [ModDb|_ModDbs]}] ->
251		    case ets:lookup(ModDb, Key) of
252			[] -> not_found;
253			[{Key, Value}] -> {ok, Value}
254		    end
255	    end,
256    {reply, Reply, State};
257handle_call({functions, Mod}, _From, State) ->
258    Db = State#state.db,
259    Reply = case ets:lookup(Db, {Mod, refs}) of
260		[] -> [];
261		[{{Mod, refs}, [ModDb|_ModDbs]}] ->
262		    Pattern = {{Mod,'$1','$2','_'}, '_'},
263		    ets:match(ModDb, Pattern)
264	    end,
265    {reply, Reply, State};
266handle_call({contents, Mod, Pid}, _From, State) ->
267    Db = State#state.db,
268    [{{Mod, refs}, ModDbs}] = ets:lookup(Db, {Mod, refs}),
269    ModDb = if
270		Pid =:= any -> hd(ModDbs);
271		true ->
272		    lists:foldl(fun(T, not_found) ->
273					[{T, Pids}] = ets:lookup(Db, T),
274					case lists:member(Pid, Pids) of
275					    true -> T;
276					    false -> not_found
277					end;
278				   (_T, T) -> T
279				end,
280				not_found,
281				ModDbs)
282	    end,
283    [{mod_bin, Bin}] = ets:lookup(ModDb, mod_bin),
284    {reply, {ok, Bin}, State};
285handle_call({raw_contents, Mod, Pid}, _From, State) ->
286    Db = State#state.db,
287    case ets:lookup(Db, {Mod, refs}) of
288	[{{Mod, refs}, ModDbs}] ->
289	    ModDb =
290		if
291		    Pid =:= any -> hd(ModDbs);
292		    true ->
293			lists:foldl(fun(T, not_found) ->
294					    [{T, Pids}] = ets:lookup(Db, T),
295					    case lists:member(Pid, Pids) of
296						true -> T;
297						false -> not_found
298					    end;
299				       (_T, T) -> T
300				    end,
301				    not_found,
302				    ModDbs)
303		end,
304	    [{mod_raw, Bin}] = ets:lookup(ModDb, mod_raw),
305	    {reply, {ok, Bin}, State};
306	[] ->					% code not interpreted
307	    {reply, not_found, State}
308    end;
309handle_call({is_interpreted, Mod, Name, Arity}, _From, State) ->
310    Db = State#state.db,
311    Reply = case ets:lookup(Db, {Mod, refs}) of
312		[] -> false;
313		[{{Mod, refs}, [ModDb|_ModDbs]}] ->
314		    Pattern = {{Mod,Name,Arity,'_'}, '_'},
315		    case ets:match_object(ModDb, Pattern) of
316			[{_Key, Clauses}] -> {true, Clauses};
317			[] -> false
318		    end
319	    end,
320    {reply, Reply, State};
321handle_call(all_interpreted, _From, State) ->
322    Db = State#state.db,
323    Mods = ets:select(Db, [{{{'$1',refs},'_'},[],['$1']}]),
324    {reply, Mods, State};
325handle_call({file, Mod}, From, State) ->
326    {reply, Res, _} = handle_call({lookup, Mod, mod_file}, From, State),
327    Reply = case Res of
328		{ok, File} -> File;
329		not_found -> {error, not_loaded}
330	    end,
331    {reply, Reply, State}.
332
333
334handle_cast(stop, State) ->
335    {stop, shutdown, State};
336handle_cast({subscribe, Sub}, State) ->
337    {noreply, State#state{subs=[Sub|State#state.subs]}};
338
339%% Attaching to a process
340handle_cast({attach, Pid, {Mod, Func, Args}}, State) ->
341    %% Simply spawn process, which should call int:attached(Pid)
342    spawn(Mod, Func, [Pid | Args]),
343    {noreply, State};
344
345%% Getting and setting options
346handle_cast({set_auto_attach, false}, State) ->
347    send_all(subscriber, {auto_attach, false}, State),
348    {noreply, State#state{auto=false}};
349handle_cast({set_auto_attach, Flags, Function}, State) ->
350    send_all(subscriber, {auto_attach, {Flags, Function}}, State),
351    {noreply, State#state{auto={Flags, Function}}};
352handle_cast({set_stack_trace, Flag}, State) ->
353    send_all(subscriber, {stack_trace, Flag}, State),
354    {noreply, State#state{stack=Flag}};
355
356%% Retrieving information
357handle_cast(clear, State) ->
358    Procs = lists:filter(fun(#proc{status=Status}) ->
359				 Status =/= exit
360			 end,
361			 State#state.procs),
362    {noreply, State#state{procs=Procs}};
363
364%% Breakpoint handling
365handle_cast({delete_break, Point}, State) ->
366    case lists:keymember(Point, 1, State#state.breaks) of
367	true ->
368	    send_all([subscriber, meta, attached],
369		     {delete_break, Point}, State),
370	    Breaks = lists:keydelete(Point, 1, State#state.breaks),
371	    {noreply, State#state{breaks=Breaks}};
372	false ->
373	    {noreply, State}
374    end;
375handle_cast({break_option, Point, Option, Value}, State) ->
376    case lists:keyfind(Point, 1, State#state.breaks) of
377	{Point, Options} ->
378	    N = case Option of
379		    status -> 1;
380		    action -> 2;
381		    condition -> 4
382		end,
383	    Options2 = list_setelement(N, Options, Value),
384	    send_all([subscriber, meta, attached],
385		     {break_options, {Point, Options2}}, State),
386	    Breaks = lists:keyreplace(Point, 1, State#state.breaks,
387				      {Point, Options2}),
388	    {noreply, State#state{breaks=Breaks}};
389	false ->
390	    {noreply, State}
391    end;
392handle_cast(no_break, State) ->
393    send_all([subscriber, meta, attached], no_break, State),
394    {noreply, State#state{breaks=[]}};
395handle_cast({no_break, Mod}, State) ->
396    send_all([subscriber, meta, attached], {no_break, Mod}, State),
397    Breaks = lists:filter(fun({{M, _L}, _O}) ->
398				  M =/= Mod
399			  end,
400			  State#state.breaks),
401    {noreply, State#state{breaks=Breaks}};
402
403%% From Meta process
404handle_cast({set_status, Meta, Status, Info}, State) ->
405    {true, Proc} = get_proc({meta, Meta}, State#state.procs),
406    send_all(subscriber, {new_status, Proc#proc.pid, Status, Info}, State),
407    if
408	Status =:= break ->
409	    _ = auto_attach(break, State#state.auto, Proc),
410	    ok;
411	true ->
412	    ok
413    end,
414    Proc2 = Proc#proc{status=Status, info=Info},
415    {noreply, State#state{procs=lists:keyreplace(Meta, #proc.meta,
416						 State#state.procs, Proc2)}};
417handle_cast({set_exit_info, Meta, ExitInfo}, State) ->
418    {true, Proc} = get_proc({meta, Meta}, State#state.procs),
419    Procs = lists:keyreplace(Meta, #proc.meta, State#state.procs,
420			     Proc#proc{exit_info=ExitInfo}),
421    {noreply,State#state{procs=Procs}};
422
423%% Code loading
424handle_cast({delete, Mod}, State) ->
425
426    %% Remove the ETS table with information about the module
427    Db = State#state.db,
428    case ets:lookup(Db, {Mod, refs}) of
429	[] -> % Mod is not interpreted
430	    {noreply, State};
431	[{{Mod, refs}, ModDbs}] ->
432	    ets:delete(Db, {Mod, refs}),
433	    AllPids = lists:foldl(
434			fun(ModDb, PidsAcc) ->
435				[{ModDb, Pids}] = ets:lookup(Db, ModDb),
436				ets:delete(Db, ModDb),
437				ets:delete(ModDb),
438				PidsAcc++Pids
439			end,
440			[],
441			ModDbs),
442	    lists:foreach(fun(Pid) ->
443				  case get_proc({pid, Pid},
444						State#state.procs) of
445				      {true, Proc} ->
446					  send(Proc#proc.meta,
447					       {old_code, Mod});
448				      false -> ignore % pid may have exited
449				  end
450			  end,
451			  AllPids),
452
453	    send_all([subscriber,attached], {no_interpret, Mod}, State),
454
455	    %% Remove all breakpoints for Mod
456	    handle_cast({no_break, Mod}, State)
457    end.
458
459%% Process exits
460handle_info({'EXIT',Who,Why}, State) ->
461    case get_proc({meta, Who}, State#state.procs) of
462
463	%% Exited process is a meta process for exit_info
464	{true,#proc{status=exit}} ->
465	    {noreply,State};
466
467	%% Exited process is a meta process
468	{true,Proc} ->
469	    Pid = Proc#proc.pid,
470	    ExitInfo = Proc#proc.exit_info,
471	    %% Check if someone is attached to the debugged process,
472	    %% if so a new meta process should be started
473	    Meta = case Proc#proc.attpid of
474		       AttPid when is_pid(AttPid) ->
475			   spawn_link(dbg_ieval, exit_info,
476				      [self(),AttPid,Pid,Why,ExitInfo]);
477		       undefined ->
478			   %% Otherwise, auto attach if necessary
479			   _ = auto_attach(exit, State#state.auto, Pid),
480			   Who
481		   end,
482	    send_all(subscriber, {new_status,Pid,exit,Why}, State),
483	    Procs = lists:keyreplace(Who, #proc.meta, State#state.procs,
484				     Proc#proc{meta=Meta,
485					       status=exit,
486					       info=Why}),
487	    {noreply,State#state{procs=Procs}};
488
489	false ->
490	    case get_proc({attpid, Who}, State#state.procs) of
491
492		%% Exited process is an attached process
493		{true, Proc} ->
494		    %% If status==exit, then the meta process is a
495		    %% simple meta for a terminated process and can be
496		    %% terminated as well (it is only needed by
497		    %% the attached process)
498		    case Proc#proc.status of
499			exit -> send(Proc#proc.meta, stop);
500			_Status -> send(Proc#proc.meta, detached)
501		    end,
502		    Procs = lists:keyreplace(Proc#proc.pid, #proc.pid,
503					     State#state.procs,
504					     Proc#proc{attpid=undefined}),
505		    {noreply, State#state{procs=Procs}};
506
507		%% Otherwise exited process must be a subscriber
508		false ->
509		    Subs = lists:delete(Who, State#state.subs),
510		    {noreply, State#state{subs=Subs}}
511	    end
512    end.
513
514terminate(_Reason, _State) ->
515    EbinDir = filename:join(code:lib_dir(debugger), "ebin"),
516    code:unstick_dir(EbinDir),
517    ok.
518
519code_change(_OldVsn, State, _Extra) ->
520    {ok, State}.
521
522
523%%====================================================================
524%% Internal functions
525%%====================================================================
526
527auto_attach(Why, Auto, #proc{attpid = Attpid, pid = Pid}) ->
528    case Attpid of
529	undefined -> auto_attach(Why, Auto, Pid);
530	_ when is_pid(Attpid) -> ignore
531    end;
532auto_attach(Why, Auto, Pid) when is_pid(Pid) ->
533    case Auto of
534	false -> ignore;
535	{Flags, {Mod, Func, Args}} ->
536	    case lists:member(Why, Flags) of
537		true ->
538		    spawn(Mod, Func, [Pid | Args]);
539		false -> ignore
540	    end
541    end.
542
543keyinsert(Tuple1, N, [Tuple2|Tuples]) ->
544    if
545	element(N, Tuple1) < element(N, Tuple2) ->
546	    [Tuple1, Tuple2|Tuples];
547	true ->
548	    [Tuple2 | keyinsert(Tuple1, N, Tuples)]
549    end;
550keyinsert(Tuple, _N, []) ->
551    [Tuple].
552
553list_setelement(N, L, E) -> list_setelement(1, N, L, E).
554
555list_setelement(I, I, [_|T], E) ->
556    [E|T];
557list_setelement(I, N, [H|T], E) ->
558    [H|list_setelement(I+1, N, T, E)].
559
560mapfilter(Fun, [H|T]) ->
561    case Fun(H) of
562	ignore -> mapfilter(Fun, T);
563	H2 -> [H2|mapfilter(Fun, T)]
564    end;
565mapfilter(_Fun, []) ->
566    [].
567
568send_all([Type|Types], Msg, State) ->
569    send_all(Type, Msg, State),
570    send_all(Types, Msg, State);
571send_all([], _Msg, _State) -> ok;
572
573send_all(subscriber, Msg, State) ->
574    send_all(State#state.subs, Msg);
575send_all(meta, Msg, State) ->
576    Metas = [Proc#proc.meta || Proc <- State#state.procs],
577    send_all(Metas, Msg);
578send_all(attached, Msg, State) ->
579    AttPids= mapfilter(fun(Proc) ->
580			       case Proc#proc.attpid of
581				   Pid when is_pid(Pid) -> Pid;
582				   undefined -> ignore
583			       end
584		       end,
585		       State#state.procs),
586    send_all(AttPids, Msg).
587
588send_all(Pids, Msg) ->
589    lists:foreach(fun(Pid) -> send(Pid, Msg) end, Pids).
590
591send(Pid, Msg) ->
592    Pid ! {int, Msg},
593    ok.
594
595get_proc({Type, Pid}, Procs) ->
596    Index = case Type of
597		pid -> #proc.pid;
598		meta -> #proc.meta;
599		attpid -> #proc.attpid
600	    end,
601    case lists:keyfind(Pid, Index, Procs) of
602	false -> false;
603	Proc -> {true, Proc}
604    end.
605