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