4
%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
6
%% The contents of this file are subject to the Erlang Public License,
7
%% Version 1.1, (the "License"); you may not use this file except in
8
%% compliance with the License. You should have received a copy of the
9
%% Erlang Public License along with this software. If not, it can be
10
%% retrieved online at http://www.erlang.org/.
12
%% Software distributed under the License is distributed on an "AS IS"
13
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
14
%% the License for the specific language governing rights and limitations
23
-include_lib("kernel/include/file.hrl").
24
-include_lib("wx/include/wx.hrl").
27
-export([start/2, stop/0]).
29
-define(TRACEWIN, ['Search Area', 'Button Area', 'Evaluator Area', 'Bindings Area']).
30
-define(BACKTRACE, 100).
32
-record(pinfo, {pid, % pid()
33
status % break | exit | idle | running | waiting
36
-record(state, {mode, % local | global
37
starter, % bool() 'true' if int was started by me
39
win, % term() Monitor window data
40
focus, % undefined | #pinfo{} Process in focus
41
coords, % {X,Y} Mouse pointer position
43
intdir, % string() Default dir
44
pinfos, % [#pinfo{}] Debugged processes
46
tracewin, % [Area] Areas shown in trace window
47
backtrace, % integer() Number of call frames to fetch
49
attach, % false | {Flags, Function}
51
sfile, % default | string() Settings file
52
changed % boolean() Settings have been changed
55
%%====================================================================
57
%%====================================================================
59
%%--------------------------------------------------------------------
60
%% start(Mode, SFile) -> {ok, Pid} | {error, Reason}
61
%% Mode = local | global
62
%% SFile = string() | default Settings file
64
%% Reason = {already_started,Pid} | term()
65
%%--------------------------------------------------------------------
67
case whereis(?MODULE) of
70
Pid = spawn(fun () -> init(CallingPid, Mode, SFile) end),
72
{initialization_complete, Pid} ->
79
{error, {already_started,Pid}}
82
%%--------------------------------------------------------------------
84
%%--------------------------------------------------------------------
86
case whereis(?MODULE) of
90
Flag = process_flag(trap_exit, true),
94
{'EXIT', Pid, stop} ->
95
process_flag(trap_exit, Flag),
101
%%====================================================================
103
%%====================================================================
105
init(CallingPid, Mode, SFile) ->
106
register(?MODULE, self()),
109
case catch dbg_wx_mon_win:init() of
111
CallingPid ! {error, Reason};
114
init2(CallingPid, Mode, SFile, GS)
118
io:format("~p: Crashed {~p,~p} in~n ~p",
119
[?MODULE, Error, Reason, erlang:get_stacktrace()])
123
init2(CallingPid, Mode, SFile, GS) ->
124
%% Start Int if necessary and subscribe to information from it
125
Bool = case int:start() of
127
{error, {already_started, _Int}} -> false
131
%% Start other necessary stuff
133
dbg_wx_winman:start(), % Debugger window manager
135
%% Create monitor window
137
Win = dbg_wx_mon_win:create_win(GS, Title, menus()),
138
Window = dbg_wx_mon_win:get_window(Win),
139
dbg_wx_winman:insert(Title, Window),
141
%% Initial process state
142
State1 = #state{mode = Mode,
149
intdir = element(2, file:get_cwd()),
156
State2 = init_options(?TRACEWIN, % Trace Window
157
int:auto_attach(), % Auto Attach
158
int:stack_trace(), % Stack Trace
159
?BACKTRACE, % Back Trace Size
162
State3 = init_contents(int:interpreted(), % Modules
163
int:all_breaks(), % Breakpoints
164
int:snapshot(), % Processes
167
%% Disable/enable functionality according to process in focus (none)
168
gui_enable_functions(State3#state.focus),
170
CallingPid ! {initialization_complete, self()},
176
loop(load_settings(SFile, State3))
179
init_options(TraceWin, AutoAttach, StackTrace, BackTrace, State) ->
180
lists:foreach(fun(Area) ->
181
dbg_wx_mon_win:select(Area, true)
187
{Flags, _Function} ->
188
dbg_wx_mon_win:show_option(State#state.win,
190
lists:foreach(fun(Flag) ->
191
dbg_wx_mon_win:select(map(Flag), true)
196
dbg_wx_mon_win:show_option(State#state.win,
197
stack_trace, StackTrace),
198
dbg_wx_mon_win:select(map(StackTrace), true),
200
dbg_wx_mon_win:show_option(State#state.win, back_trace, BackTrace),
202
State#state{tracewin=TraceWin, backtrace=BackTrace}.
204
init_contents(Mods, Breaks, Processes, State) ->
206
lists:foldl(fun(Mod, Win) ->
207
dbg_wx_mon_win:add_module(Win,'Module',Mod)
213
lists:foldl(fun(Break, Win) ->
214
dbg_wx_mon_win:add_break(Win,'Break',Break)
219
lists:foldl(fun(PidTuple, State0) ->
220
int_cmd({new_process, PidTuple}, State0)
222
State#state{win=Win3},
226
%%====================================================================
227
%% Main loop and message handling
228
%%====================================================================
233
gui_cmd(stopped, State);
237
Cmd = dbg_wx_mon_win:handle_event(GuiEvent,State#state.win),
238
State2 = gui_cmd(Cmd, State),
241
%% From the interpreter process
243
State2 = int_cmd(Cmd, State),
246
%% From the dbg_ui_interpret process
247
{dbg_ui_interpret, Dir} ->
248
loop(State#state{intdir=Dir});
250
%% From the dbg_wx_winman process (Debugger window manager)
251
{dbg_ui_winman, update_windows_menu, Data} ->
252
Window = dbg_wx_mon_win:get_window(State#state.win),
253
dbg_wx_winman:update_windows_menu(Window,Data),
256
%% From the trace window
257
{dbg_wx_trace, From, get_env} ->
258
From ! {env, self(), wx:get_env(), dbg_wx_mon_win:get_window(State#state.win)},
262
%%--Commands from the GUI---------------------------------------------
263
%% Act upon a command from the GUI. In most cases, it is only necessary
264
%% to call a relevant int-function. int will then report when the action
267
gui_cmd(ignore, State) ->
269
gui_cmd(stopped, State) ->
271
State#state.starter==true -> int:stop();
272
true -> int:auto_attach(false)
275
gui_cmd({coords, Coords}, State) ->
276
State#state{coords=Coords};
278
gui_cmd({shortcut, Key}, State) ->
279
case shortcut(Key) of
280
{always, Cmd} -> gui_cmd(Cmd, State);
282
case dbg_wx_mon_win:is_enabled(Cmd) of
283
true -> gui_cmd(Cmd, State);
290
gui_cmd('Load Settings...', State) ->
291
Window = dbg_wx_mon_win:get_window(State#state.win),
292
case dbg_wx_settings:load(Window, State#state.coords,State#state.sfile) of
294
{ok, File} -> load_settings(File,State)
296
gui_cmd('Save Settings...', State) ->
297
Window = dbg_wx_mon_win:get_window(State#state.win),
298
case dbg_wx_settings:save(Window, State#state.coords,State#state.sfile) of
300
{ok, File} -> save_settings(File,State)
302
gui_cmd('Exit', State) ->
303
gui_cmd(stopped, State);
306
gui_cmd('Refresh', State) ->
308
Win = dbg_wx_mon_win:clear_processes(State#state.win),
309
gui_enable_functions(undefined),
310
State2 = State#state{win=Win, focus=undefined, pinfos=[]},
311
lists:foldl(fun(PidTuple, S) ->
312
int_cmd({new_process,PidTuple}, S)
316
gui_cmd('Kill All', State) ->
317
lists:foreach(fun(PInfo) ->
318
case PInfo#pinfo.status of
320
_Status -> exit(PInfo#pinfo.pid, kill)
327
gui_cmd('Interpret...', State) ->
328
Window = dbg_wx_mon_win:get_window(State#state.win),
329
dbg_wx_interpret:start(Window, State#state.coords,
330
State#state.intdir, State#state.mode),
332
gui_cmd('Delete All Modules', State) ->
333
lists:foreach(fun(Mod) -> int:nn(Mod) end, int:interpreted()),
335
gui_cmd({module, Mod, What}, State) ->
337
delete -> int:nn(Mod);
339
Window = dbg_wx_mon_win:get_window(State#state.win),
340
dbg_wx_view:start(Window, Mod)
345
gui_cmd('Step', State) ->
346
int:step((State#state.focus)#pinfo.pid),
348
gui_cmd('Next', State) ->
349
int:next((State#state.focus)#pinfo.pid),
351
gui_cmd('Continue', State) ->
352
int:continue((State#state.focus)#pinfo.pid),
354
gui_cmd('Finish ', State) ->
355
int:finish((State#state.focus)#pinfo.pid),
357
gui_cmd('Attach', State) ->
358
Pid = (State#state.focus)#pinfo.pid,
359
case dbg_wx_winman:is_started(dbg_wx_trace:title(Pid)) of
361
false -> int:attach(Pid, trace_function(State))
364
gui_cmd('Kill', State) ->
365
exit((State#state.focus)#pinfo.pid, kill),
369
gui_cmd('Line Break...', State) ->
370
Window = dbg_wx_mon_win:get_window(State#state.win),
371
dbg_wx_break:start(Window, State#state.coords, line),
373
gui_cmd('Conditional Break...', State) ->
374
Window = dbg_wx_mon_win:get_window(State#state.win),
375
dbg_wx_break:start(Window, State#state.coords, conditional),
377
gui_cmd('Function Break...', State) ->
378
Window = dbg_wx_mon_win:get_window(State#state.win),
379
dbg_wx_break:start(Window, State#state.coords, function),
381
gui_cmd('Enable All', State) ->
382
Breaks = int:all_breaks(),
383
lists:foreach(fun ({{Mod, Line}, _Options}) ->
384
int:enable_break(Mod, Line)
388
gui_cmd('Disable All', State) ->
389
Breaks = int:all_breaks(),
390
lists:foreach(fun ({{Mod, Line}, _Options}) ->
391
int:disable_break(Mod, Line)
395
gui_cmd('Delete All', State) ->
398
gui_cmd({break, {Mod, Line}, What}, State) ->
400
delete -> int:delete_break(Mod, Line);
401
{status, inactive} -> int:disable_break(Mod, Line);
402
{status, active} -> int:enable_break(Mod, Line);
403
{trigger, Action} -> int:action_at_break(Mod, Line, Action)
408
gui_cmd({'Trace Window', TraceWin}, State) ->
409
State2 = State#state{tracewin=TraceWin},
410
case State#state.attach of
412
{Flags, {dbg_ui_trace, start, StartFlags}} ->
413
case trace_function(State2) of
414
{_, _, StartFlags} -> ignore;
415
NewFunction -> % {_, _, NewStartFlags}
416
int:auto_attach(Flags, NewFunction)
418
_AutoAttach -> ignore
421
gui_cmd({'Auto Attach', When}, State) ->
423
When==[] -> int:auto_attach(false);
425
Flags = lists:map(fun(Name) -> map(Name) end, When),
426
int:auto_attach(Flags, trace_function(State))
429
gui_cmd({'Stack Trace', [Name]}, State) ->
430
int:stack_trace(map(Name)),
432
gui_cmd('Back Trace Size...', State) ->
433
Window = dbg_wx_mon_win:get_window(State#state.win),
434
What = {integer, State#state.backtrace},
435
case dbg_wx_win:entry(Window, "Backtrace", 'Backtrace:', What) of
439
dbg_wx_mon_win:show_option(State#state.win,back_trace, BackTrace),
440
State#state{backtrace=BackTrace}
444
gui_cmd('Debugger', State) ->
445
HelpFile = filename:join([code:lib_dir(debugger),
446
"doc", "html", "part_frame.html"]),
447
Window = dbg_wx_mon_win:get_window(State#state.win),
448
dbg_wx_win:open_help(Window, HelpFile),
451
gui_cmd({focus, Pid, Win}, State) ->
453
lists:keysearch(Pid, #pinfo.pid, State#state.pinfos),
454
gui_enable_functions(PInfo),
455
State#state{win=Win, focus=PInfo};
456
gui_cmd(default, State) ->
457
case lists:member('Attach', menus(enabled, State#state.focus)) of
458
true -> gui_cmd('Attach', State);
462
%%--Commands from the interpreter-------------------------------------
464
int_cmd({interpret, Mod}, State) ->
465
Win = dbg_wx_mon_win:add_module(State#state.win, 'Module', Mod),
466
State#state{win=Win};
467
int_cmd({no_interpret, Mod}, State) ->
468
Win = dbg_wx_mon_win:delete_module(State#state.win, Mod),
469
State#state{win=Win};
471
int_cmd({new_process, {Pid, Function, Status, Info}}, State) ->
473
%% Create record with information about the process
474
Name = registered_name(Pid),
475
PInfo = #pinfo{pid=Pid, status=Status},
478
Win = dbg_wx_mon_win:add_process(State#state.win,
479
Pid, Name, Function, Status, Info),
481
%% Store process information
482
PInfos = State#state.pinfos ++ [PInfo],
483
State#state{win=Win, pinfos=PInfos};
484
int_cmd({new_status, Pid, Status, Info}, State) ->
486
%% Find stored information about the process
487
PInfos = State#state.pinfos,
488
{value, PInfo} = lists:keysearch(Pid, #pinfo.pid, PInfos),
490
%% Update process information
491
PInfo2 = PInfo#pinfo{status=Status},
492
PInfos2 = lists:keyreplace(Pid, #pinfo.pid, PInfos, PInfo2),
493
State2 = State#state{pinfos=PInfos2},
496
dbg_wx_mon_win:update_process(State2#state.win, Pid, Status, Info),
497
case State2#state.focus of
499
gui_enable_functions(PInfo2),
500
State2#state{focus=PInfo2};
505
int_cmd({new_break, Break}, State) ->
506
Win = dbg_wx_mon_win:add_break(State#state.win, 'Break', Break),
507
State#state{win=Win};
508
int_cmd({delete_break, Point}, State) ->
509
Win = dbg_wx_mon_win:delete_break(State#state.win, Point),
510
State#state{win=Win};
511
int_cmd({break_options, Break}, State) ->
512
dbg_wx_mon_win:update_break(State#state.win, Break),
514
int_cmd(no_break, State) ->
515
Win = dbg_wx_mon_win:clear_breaks(State#state.win),
516
State#state{win=Win};
517
int_cmd({no_break, Mod}, State) ->
518
Win = dbg_wx_mon_win:clear_breaks(State#state.win, Mod),
519
State#state{win=Win};
521
int_cmd({auto_attach, AutoAttach}, State) ->
522
OnFlags = case AutoAttach of
524
{Flags, _Function} -> Flags
526
OffFlags = [init, exit, break] -- OnFlags,
527
dbg_wx_mon_win:show_option(State#state.win, auto_attach, OnFlags),
528
lists:foreach(fun(Flag) ->
529
dbg_wx_mon_win:select(map(Flag), true)
532
lists:foreach(fun(Flag) ->
533
dbg_wx_mon_win:select(map(Flag), false)
536
State#state{attach=AutoAttach};
537
int_cmd({stack_trace, Flag}, State) ->
538
dbg_wx_mon_win:show_option(State#state.win, stack_trace, Flag),
539
dbg_wx_mon_win:select(map(Flag), true),
543
%%====================================================================
544
%% GUI auxiliary functions
545
%%====================================================================
548
[{'File', [{'Load Settings...', 0},
549
{'Save Settings...', 2},
552
{'Edit', [{'Refresh', no},
554
{'Module', [{'Interpret...', 0},
555
{'Delete All Modules', no},
557
{'Process', [{'Step', 0},
564
{'Break', [{'Line Break...', 5},
565
{'Conditional Break...', no},
566
{'Function Break...', no},
572
{'Options', [{'Trace Window', no, cascade,
573
[{'Search Area', no, check},
574
{'Button Area', no, check},
575
{'Evaluator Area', no, check},
576
{'Bindings Area', no, check},
577
{'Trace Area', no, check}]},
578
{'Auto Attach', no, cascade,
579
[{'First Call', no, check},
580
{'On Break', no, check},
581
{'On Exit', no, check}]},
582
{'Stack Trace', no, cascade,
583
[{'Stack On, Tail', no, radio},
584
{'Stack On, No Tail', no, radio},
585
{'Stack Off', no, radio}]},
586
{'Back Trace Size...', no}]},
588
{'Help', [{'Debugger', no}]}].
590
menus(enabled, undefined) ->
592
menus(disabled, undefined) ->
593
['Step','Next','Continue','Finish ','Attach','Kill'];
594
menus(enabled, #pinfo{status=exit}) ->
596
menus(disabled, #pinfo{status=exit}) ->
597
['Step','Next','Continue','Finish ','Kill'];
598
menus(enabled, #pinfo{status=break}) ->
599
['Step','Next','Continue','Finish ','Attach','Kill'];
600
menus(disabled, #pinfo{status=break}) ->
602
menus(enabled, _PInfo) ->
604
menus(disabled, _PInfo) ->
605
['Step','Next','Continue','Finish '].
607
shortcut(l) -> {always, 'Load Settings...'};
608
shortcut(v) -> {always, 'Save Settings...'};
609
shortcut(e) -> {always, 'Exit'};
611
shortcut(i) -> {always, 'Interpret...'};
613
shortcut(s) -> {if_enabled, 'Step'};
614
shortcut(n) -> {if_enabled, 'Next'};
615
shortcut(c) -> {if_enabled, 'Continue'};
616
shortcut(f) -> {if_enabled, 'Finish '};
617
shortcut(a) -> {if_enabled, 'Attach'};
619
shortcut(b) -> {always, 'Line Break...'};
620
shortcut(d) -> {always, 'Delete All'};
622
shortcut(_) -> false.
624
%% Enable/disable functionality depending on the state of the process
625
%% currently in Focus
626
gui_enable_functions(PInfo) ->
627
Enabled = menus(enabled, PInfo),
628
Disabled = menus(disabled, PInfo),
629
dbg_wx_mon_win:enable(Enabled, true),
630
dbg_wx_mon_win:enable(Disabled, false).
632
%% Map values used by int to/from GUI names
633
map('First Call') -> init; % Auto attach
634
map('On Exit') -> exit;
635
map('On Break') -> break;
636
map(init) -> 'First Call';
637
map(exit) -> 'On Exit';
638
map(break) -> 'On Break';
640
map('Stack On, Tail') -> all; % Stack trace
641
map('Stack On, No Tail') -> no_tail;
642
map('Stack Off') -> false;
643
map(all) -> 'Stack On, Tail';
644
map(true) -> 'Stack On, Tail';
645
map(no_tail) -> 'Stack On, No Tail';
646
map(false) -> 'Stack Off'.
649
%%====================================================================
651
%%====================================================================
653
load_settings(SFile, State) ->
654
case file:read_file(SFile) of
656
case catch binary_to_term(Binary) of
657
{debugger_settings, Settings} ->
658
load_settings2(Settings,
659
State#state{sfile=SFile,
663
{error, _Reason} -> State
666
load_settings2(Settings, State) ->
667
{TraceWin, AutoAttach, StackTrace, BackTrace, Files, Breaks} =
670
TraceWinAll = ['Button Area', 'Evaluator Area', 'Bindings Area',
672
lists:foreach(fun(Area) -> dbg_wx_mon_win:select(Area, true) end,
674
lists:foreach(fun(Area) -> dbg_wx_mon_win:select(Area, false) end,
675
TraceWinAll--TraceWin),
678
false -> int:auto_attach(false);
679
{Flags, Function} -> int:auto_attach(Flags, Function)
682
int:stack_trace(StackTrace),
684
dbg_wx_mon_win:show_option(State#state.win, back_trace, BackTrace),
686
case State#state.mode of
687
local -> lists:foreach(fun(File) -> int:i(File) end, Files);
688
global -> lists:foreach(fun(File) -> int:ni(File) end, Files)
690
lists:foreach(fun(Break) ->
691
{{Mod, Line}, [Status, Action, _, Cond]} =
693
int:break(Mod, Line),
696
int:disable_break(Mod, Line);
701
int:action_at_break(Mod,Line,Action);
705
CFunction when is_tuple(CFunction) ->
706
int:test_at_break(Mod,Line,CFunction);
712
State#state{tracewin=TraceWin, backtrace=BackTrace}.
714
save_settings(SFile, State) ->
715
Settings = {State#state.tracewin,
718
State#state.backtrace,
719
lists:map(fun(Mod) ->
725
Binary = term_to_binary({debugger_settings, Settings}),
726
case file:write_file(SFile, Binary) of
728
State#state{sfile=SFile, changed=false};
734
%%====================================================================
735
%% Other internal functions
736
%%====================================================================
738
registered_name(Pid) ->
740
%% Yield in order to give Pid more time to register its name
746
case erlang:process_info(Pid, registered_name) of
747
{registered_name, Name} -> Name;
751
case rpc:call(Node,erlang,process_info,
752
[Pid,registered_name]) of
753
{registered_name, Name} -> Name;
758
trace_function(State) ->
759
{dbg_wx_trace, start, [State#state.tracewin, State#state.backtrace]}.