├── ebin └── .gitkeep ├── .vscode └── settings.json ├── CONTRIBUTORS ├── rebar.config.script ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── src ├── edbg.app.src ├── edbg_shell_history.erl ├── edbg_color.erl ├── edbg_trace.hrl ├── edbg_trace_filter.erl ├── edbg_color_srv.erl ├── edbg_pp_tree.erl ├── edbg_file_tracer.erl ├── edbg_sup_trees.erl ├── edbg.erl └── edbg_tracer.erl ├── rebar.config ├── LICENSE ├── Makefile └── README.md /ebin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "erlang.inlayHintsEnabled": true 3 | } -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Mattias Ljunggren 2 | Per Andersson 3 | Tomas Bjurman 4 | 5 | -------------------------------------------------------------------------------- /rebar.config.script: -------------------------------------------------------------------------------- 1 | case os:getenv("MIX_ENV") of 2 | false -> CONFIG; 3 | _ -> 4 | {erl_opts, Opts} = lists:keyfind(erl_opts, 1, CONFIG), 5 | NewOpts = [{d,'ELIXIR'}|Opts], 6 | lists:keyreplace(erl_opts, 1, CONFIG, {erl_opts, NewOpts}) 7 | end. 8 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/base:bookworm 2 | 3 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 4 | && apt-get -y install --no-install-recommends \ 5 | python3-pip \ 6 | python3-venv \ 7 | erlang \ 8 | && apt-get clean && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /src/edbg.app.src: -------------------------------------------------------------------------------- 1 | {application, edbg, 2 | [{description, "TTY interface to the Erlang debugger and tracer"}, 3 | {vsn, "0.9.6"}, 4 | {registered, []}, 5 | {applications, 6 | [kernel, 7 | stdlib 8 | ]}, 9 | {env,[]}, 10 | {modules, []}, 11 | {maintainers, []}, 12 | {licenses, ["Apache"]}, 13 | {links, [{"Github", "https://github.com/etnt/edbg"}]} 14 | ]}. 15 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | 3 | {src_dirs, ["src"]}. 4 | 5 | {deps, [ 6 | {pp_record, "0.1.3"} % hex package 7 | %{pp_record, {git, "git://github.com/etnt/pp_record.git", {tag, "0.1.1"}}} % alternatively, source 8 | ] 9 | }. 10 | 11 | {ex_doc, [ 12 | {source_url, <<"https://github.com/etnt/edbg">>}, 13 | {extras, [<<"README.md">>, <<"LICENSE">>]}, 14 | {main, <<"README.md">>} 15 | ]}. 16 | 17 | {hex, [{doc, ex_doc}]}. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Torbjorn Tornkvist 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/debian 3 | { 4 | "name": "Debian", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "build": { 7 | "dockerfile": "Dockerfile", 8 | // Update 'VARIANT' to pick a Debian variant: buster, bullseye, or sid 9 | "args": { "VARIANT": "testing" } 10 | } 11 | 12 | // Features to add to the dev container. More info: https://containers.dev/features. 13 | // "features": {}, 14 | 15 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 16 | // "forwardPorts": [], 17 | 18 | // Configure tool-specific properties. 19 | // "customizations": {}, 20 | 21 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 22 | // "remoteUser": "root" 23 | } 24 | -------------------------------------------------------------------------------- /src/edbg_shell_history.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Torbjorn Tornkvist 3 | %%% @copyright (C) 20224, Torbjorn Tornkvist 4 | %%% @doc The `edbg' shell (hisotry) prompt handling 5 | %%% 6 | %%% Since OTP-26 the Erlang shell was changed so that edbg's prompt-loop 7 | %%% is not added to the shell history. In OTP-28 a io:setopts will be 8 | %%% added as well but until then, the following code could be used. 9 | %%% 10 | %%% See the demo module at: 11 | %%% https://www.erlang.org/doc/apps/stdlib/io_protocol.html#input-requests 12 | %%% 13 | %%%------------------------------------------------------------------- 14 | -module(edbg_shell_history). 15 | 16 | -export([until_newline/3, 17 | get_line/1 18 | ]). 19 | 20 | until_newline(_ThisFar,eof,_MyStopCharacter) -> 21 | {done,eof,[]}; 22 | until_newline(ThisFar,CharList,MyStopCharacter) -> 23 | case 24 | lists:splitwith(fun(X) -> X =/= MyStopCharacter end, CharList) 25 | of 26 | {L,[]} -> 27 | {more,ThisFar++L}; 28 | {L2,[MyStopCharacter|Rest]} -> 29 | {done,ThisFar++L2++[MyStopCharacter],Rest} 30 | end. 31 | 32 | get_line(Prompt) when is_atom(Prompt) -> 33 | get_line(Prompt, group_leader()). 34 | 35 | get_line(Prompt, IoServer) when is_atom(Prompt) -> 36 | IoServer ! {io_request, 37 | self(), 38 | IoServer, 39 | {get_until, unicode, Prompt, ?MODULE, until_newline, [$\n]}}, 40 | receive 41 | {io_reply, IoServer, Data} -> 42 | Data 43 | end. 44 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | ERLC_USE_SERVER ?= true 4 | export ERLC_USE_SERVER 5 | 6 | ERLC=erlc 7 | USE_COLORS ?= USE_COLORS 8 | ERLC_FLAGS= +debug_info +compressed -D$(USE_COLORS) 9 | 10 | BEAM=$(patsubst %.erl,%.beam,$(wildcard src/*.erl)) 11 | TBEAM=$(patsubst %.erl,%.beam,$(wildcard test/*.erl)) 12 | 13 | # To disable color: env USE_COLORS=false make 14 | # Ugly as hell, but wtf... 15 | ifeq "$(USE_COLORS)" "USE_COLORS" 16 | BEAM_OBJS=$(BEAM) 17 | else 18 | BEAM_OBJS=src/edbg.beam src/edbg_tracer.beam src/edbg_trace_filter.beam 19 | #BEAM_OBJS=$(filter-out *color*, $(BEAM)) 20 | endif 21 | 22 | REBAR3_URL=https://s3.amazonaws.com/rebar3/rebar3 23 | WGET=$(shell which wget) 24 | CURL=$(shell which curl) 25 | 26 | .PHONY: all old test clean 27 | all: rebar3 compile 28 | old: old-get-deps $(BEAM_OBJS) 29 | test: $(TBEAM) unit 30 | 31 | unit: 32 | erl -noshell -s edbg_tracer test -s init stop 33 | 34 | %.beam: %.erl 35 | $(ERLC) $(ERLC_FLAGS) -o ebin $< 36 | 37 | clean: 38 | rm -f ebin/*.beam 39 | rm -rf _build 40 | 41 | # ----------------------- 42 | # D E P E N D E N C I E S 43 | # ----------------------- 44 | .PHONY: compile get-deps old_deps pp_record-dep 45 | compile: 46 | ./rebar3 compile 47 | 48 | get-deps: rebar3 old_deps pp_record-dep 49 | 50 | old-get-deps: old_deps pp_record-dep 51 | 52 | ifeq ($(WGET),) 53 | rebar3: 54 | $(CURL) -O $(REBAR3_URL) && chmod +x rebar3 55 | else 56 | rebar3: 57 | $(WGET) $(REBAR3_URL) && chmod +x rebar3 58 | endif 59 | 60 | old_deps: 61 | if [ ! -d deps ]; then \ 62 | mkdir deps; \ 63 | fi 64 | 65 | pp_record-dep: 66 | if [ ! -d deps/pp_record ]; then \ 67 | cd deps; \ 68 | git clone https://github.com/etnt/pp_record.git; \ 69 | make -C pp_record old; \ 70 | fi 71 | 72 | rm-deps: 73 | rm -rf deps rebar3 74 | 75 | -PHONY: docs 76 | docs: 77 | rebar3 ex_doc 78 | -------------------------------------------------------------------------------- /src/edbg_color.erl: -------------------------------------------------------------------------------- 1 | %%% @private 2 | %%% @hidden 3 | %%% @end 4 | 5 | %% 6 | %% Code ripped and stripped from https://github.com/julianduque/erlang-color 7 | %% 8 | -module(edbg_color). 9 | 10 | -export([black/1, blackb/1, 11 | red/1, redb/1, 12 | green/1, greenb/1, 13 | yellow/1, yellowb/1, 14 | blue/1, blueb/1, 15 | magenta/1, magentab/1, 16 | cyan/1, cyanb/1, 17 | white/1, whiteb/1 18 | ]). 19 | 20 | -define(ESC, <<"\e[">>). 21 | -define(RST, <<"0">>). 22 | -define(BOLD, <<"1">>). 23 | -define(SEP, <<";">>). 24 | -define(END, <<"m">>). 25 | 26 | %% Colors 27 | -define(BLACK, <<"30">>). 28 | -define(RED, <<"31">>). 29 | -define(GREEN, <<"32">>). 30 | -define(YELLOW, <<"33">>). 31 | -define(BLUE, <<"34">>). 32 | -define(MAGENTA, <<"35">>). 33 | -define(CYAN, <<"36">>). 34 | -define(WHITE, <<"37">>). 35 | -define(DEFAULT, <<"39">>). 36 | 37 | %% public API 38 | 39 | %% @private 40 | black(Text) -> [color(?BLACK), Text, reset()]. 41 | %% @private 42 | blackb(Text) -> [colorb(?BLACK), Text, reset()]. 43 | %% @private 44 | red(Text) -> [color(?RED), Text, reset()]. 45 | %% @private 46 | redb(Text) -> [colorb(?RED), Text, reset()]. 47 | %% @private 48 | green(Text) -> [color(?GREEN), Text, reset()]. 49 | %% @private 50 | greenb(Text) -> [colorb(?GREEN), Text, reset()]. 51 | %% @private 52 | yellow(Text) -> [color(?YELLOW), Text, reset()]. 53 | %% @private 54 | yellowb(Text) -> [colorb(?YELLOW), Text, reset()]. 55 | %% @private 56 | blue(Text) -> [color(?BLUE), Text, reset()]. 57 | %% @private 58 | blueb(Text) -> [colorb(?BLUE), Text, reset()]. 59 | %% @private 60 | magenta(Text) -> [color(?MAGENTA), Text, reset()]. 61 | %% @private 62 | magentab(Text) -> [colorb(?MAGENTA), Text, reset()]. 63 | %% @private 64 | cyan(Text) -> [color(?CYAN), Text, reset()]. 65 | %% @private 66 | cyanb(Text) -> [colorb(?CYAN), Text, reset()]. 67 | %% @private 68 | white(Text) -> [color(?WHITE), Text, reset()]. 69 | %% @private 70 | whiteb(Text) -> [colorb(?WHITE), Text, reset()]. 71 | 72 | 73 | %% internal 74 | 75 | %% colored text 76 | color(Color) -> 77 | <>. 78 | 79 | %% bold colored text 80 | colorb(Color) -> 81 | <>. 82 | 83 | reset() -> 84 | <>. 85 | -------------------------------------------------------------------------------- /src/edbg_trace.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(_EDBG_TRACE_HRL_). 2 | -define(_EDBG_TRACE_HRL_, true). 3 | 4 | -ifndef(ELIXIR). 5 | -ifdef(USE_COLORS). 6 | -define(info_msg(Fmt,Args), edbg_color_srv:info_msg(Fmt,Args)). 7 | -define(att_msg(Fmt,Args), edbg_color_srv:att_msg(Fmt,Args)). 8 | -define(warn_msg(Fmt,Args), edbg_color_srv:warn_msg(Fmt,Args)). 9 | -define(err_msg(Fmt,Args), edbg_color_srv:err_msg(Fmt,Args)). 10 | -define(cur_line_msg(Fmt,Args), edbg_color_srv:cur_line_msg(Fmt,Args)). 11 | -define(c_hi(Str), edbg_color_srv:c_hi(Str)). 12 | -define(c_warn(Str), edbg_color_srv:c_warn(Str)). 13 | -define(c_err(Str), edbg_color_srv:c_err(Str)). 14 | -define(help_hi(Str), edbg_color_srv:help_hi(Str)). 15 | -define(edbg_color_srv_init(), edbg_color_srv:init()). 16 | -else. 17 | -define(info_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 18 | -define(att_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 19 | -define(warn_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 20 | -define(err_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 21 | -define(cur_line_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 22 | -define(c_hi(Str), Str). 23 | -define(c_warn(Str), Str). 24 | -define(c_err(Str), Str). 25 | -define(help_hi(Str), Str). 26 | -define(edbg_color_srv_init(), ok). 27 | -endif. 28 | -else. 29 | -define(info_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 30 | -define(att_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 31 | -define(warn_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 32 | -define(err_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 33 | -define(cur_line_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 34 | -define(c_hi(Str), Str). 35 | -define(c_warn(Str), Str). 36 | -define(c_err(Str), Str). 37 | -define(help_hi(Str), Str). 38 | -define(edbg_color_srv_init(), ok). 39 | -endif. 40 | 41 | 42 | %% Access to trace message items 43 | -define(is_trace_msg(Trace), (element(1,Trace) == trace)). 44 | -define(is_trace_ts_msg(Trace), (element(1,Trace) == trace_ts)). 45 | -define(trace_pid(Trace), element(2,Trace)). 46 | -define(trace_type(Trace), element(3,Trace)). 47 | -define(is_trace_call(Trace), (?trace_type(Trace) == 'call')). 48 | -define(is_trace_return_from(Trace), (?trace_type(Trace) == 'return_from')). 49 | -define(is_trace_send(Trace), (?trace_type(Trace) == 'send')). 50 | -define(is_trace_receive(Trace), (?trace_type(Trace) == 'receive')). 51 | 52 | %% TRACE MESSAGES 53 | -define(CALL(Pid,MFA,As), {trace, Pid, call, MFA, As}). 54 | -define(CALL_TS(Pid,MFA,TS,As), {trace_ts, Pid, call, MFA, TS, As}). 55 | 56 | -define(RETURN_FROM(Pid,MFA,Value,As), 57 | {trace, Pid, return_from, MFA, Value, As}). 58 | -define(RETURN_FROM_TS(Pid,MFA,Value,TS,As), 59 | {trace_ts, Pid, return_from, MFA, Value, TS, As}). 60 | 61 | -define(SEND(FromPid,Msg,ToPid,As), 62 | {trace, FromPid, send, Msg, ToPid, As}). 63 | -define(SEND_TS(FromPid,Msg,ToPid,TS,As), 64 | {trace_ts, FromPid, send, Msg, ToPid, TS, As}). 65 | 66 | -define(RECEIVE(FromPid,Msg,As), 67 | {trace, FromPid, 'receive', Msg, As}). 68 | -define(RECEIVE_TS(FromPid,Msg,TS,As), 69 | {trace_ts, FromPid, 'receive', Msg, TS, As}). 70 | 71 | -endif. 72 | -------------------------------------------------------------------------------- /src/edbg_trace_filter.erl: -------------------------------------------------------------------------------- 1 | %%% @private 2 | %%% -------------------------------------------------------------------- 3 | %%% Created : 25 Aug 2017 by Torbjorn Tornkvist 4 | %%% 5 | %%% This module is responsible for setting up what we are tracing on 6 | %%% and if/how we should do any kind of filtering of the trace messages. 7 | %%% 8 | %%% Any approved trace message should be sent to the edbg_tracer process 9 | %%% for storage and display. 10 | %%% -------------------------------------------------------------------- 11 | -module(edbg_trace_filter). 12 | 13 | -export([start_tracer/1]). 14 | 15 | -record(ts, { 16 | n = 0, 17 | max = 1000, 18 | mod = false, 19 | mods = [], 20 | traced_pids = [], 21 | opts = [], 22 | which_pid = all % all | first 23 | }). 24 | 25 | new_tstate(Mod, Mods, Opts) when is_atom(Mod) andalso 26 | is_list(Mods) andalso 27 | is_list(Opts) -> 28 | 29 | WhichPid = case lists:member(first, Opts) of 30 | true -> first; 31 | false -> all 32 | end, 33 | 34 | #ts{mod = Mod, 35 | mods = Mods, 36 | opts = Opts, 37 | which_pid = WhichPid}. 38 | 39 | 40 | %% Executed in the context of the edbg_tracer process. 41 | %% @private 42 | start_tracer({Mod, Mods, Opts}) -> 43 | F = tracer_fun(self()), 44 | case dbg:tracer(process, {F, new_tstate(Mod, Mods, Opts)}) of 45 | {ok, _NewTracer} = Res -> 46 | dbg:p(all, [c,p]), 47 | [{ok,_} = dbg:tpl(M,'_',[{'_',[],[{return_trace}]}]) 48 | || M <- [Mod|Mods]], 49 | Res; 50 | 51 | {error, _} = Error -> 52 | Error 53 | end. 54 | 55 | 56 | %% More specific filtering of trace messages 57 | tracer_fun(Pid) when is_pid(Pid) -> 58 | fun({trace, OldPid, spawn, NewPid, {_M, _F, _Args}}, 59 | #ts{traced_pids = Tpids} = X) -> 60 | 61 | case lists:member(OldPid, Tpids) of 62 | true -> 63 | %%ok = edbg_tracer:send(Pid, {N, Trace}), 64 | X#ts{traced_pids = [NewPid|Tpids]}; 65 | false -> 66 | X 67 | end; 68 | 69 | (Trace, #ts{n = N, 70 | traced_pids = Tpids, 71 | mod = Tmod, 72 | which_pid = WhichPid} = X) 73 | when element(3, Trace) == call orelse 74 | element(3, Trace) == return_from -> 75 | 76 | Tpid = element(2, Trace), 77 | Tmfa = element(4, Trace), 78 | 79 | case {lists:member(Tpid, Tpids),Tmfa} of 80 | 81 | %% Trace from 'approved' process to be traced! 82 | {true,_} -> 83 | ok = edbg_tracer:send(Pid, {N, Trace}), 84 | X#ts{n = N+1}; 85 | 86 | %% All processes where we see traces of 'Tmod' should be traced! 87 | {false, {Tmod,_,_}} when WhichPid == all -> 88 | ok = edbg_tracer:send(Pid, {N, Trace}), 89 | X#ts{n = N+1, traced_pids = [Tpid|Tpids]}; 90 | 91 | %% Only the first process, where we see any trace of 'Tmod' 92 | %% should be traced! 93 | {false, {Tmod,_,_}} when (WhichPid == first) andalso 94 | (Tpids == []) -> 95 | ok = edbg_tracer:send(Pid, {N, Trace}), 96 | X#ts{n = N+1, traced_pids = [Tpid|Tpids]}; 97 | 98 | %% Anything else should not be traced! 99 | _ -> 100 | X 101 | end; 102 | 103 | %% Anything else should not be traced! 104 | (_Trace, X) -> 105 | X 106 | end. 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # E D B G 2 | 3 | [![hex.pm version](https://img.shields.io/hexpm/v/edbg.svg)](https://hex.pm/packages/edbg) 4 | [![Hex.pm Downloads](https://img.shields.io/hexpm/dt/edbg.svg?style=flat-square)](https://hex.pm/packages/edbg) 5 | [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) 6 | 7 | > A tty based interface to the Erlang debugger/tracer and a supervisor tree browser. 8 | 9 | The purpose of `edbg` is to provide a simple and intuitive 10 | interface to the Erlang debugger and the builtin tracing functionality. 11 | 12 | Especially when tracing in the BEAM machine, it is easy to be swamped 13 | by all the trace messages that are generated. `edbg` will avoid that 14 | by presenting a clean function call graph, indented according to the call 15 | depth. `edbg` provide means to search for a particular function call, 16 | or an arbitrary string among the function arguments or return values; 17 | you may then inspect the content of only those arguments you are 18 | interested in. 19 | 20 | `edbg` now also support [Elixir](https://github.com/etnt/edbg/wiki/Tracing#elixir-support)! 21 | 22 | Example: The call graph 23 | 24 | ```erlang 25 | 10: <0.258.0> yaws_server:gserv_loop/4 26 | 11: <0.281.0> yaws_server:acceptor0/2 27 | 12: <0.281.0> yaws_server:do_accept/1 28 | 13: <0.259.0> yaws_server:aloop/4 29 | 30 | ``` 31 | 32 | Example: Display content of a function call argument 33 | 34 | ```erlang 35 | tlist> pr 177 2 36 | Call: yaws_server:handle_request/3 , argument 2: 37 | ----------------------------------- 38 | #arg{clisock = #Port<0.6886>, 39 | client_ip_port = {{127,0,0,1},35871}, 40 | headers = #headers{connection = undefined,accept = "*/*", 41 | host = "localhost:8008",if_modified_since = undefined, 42 | if_match = undefined,if_none_match = undefined, 43 | ...snip... 44 | 45 | ``` 46 | `edbg` consists of three main parts: 47 | 48 | * [Tracing](#tracing) 49 | * [Supervision Tree Browser](#supervisor-tree) 50 | * [Debugger](#debugger) 51 | 52 | If you don't like GUI's, `edbg` may be your cup of tea. 53 | 54 | Or, for example, you work from home but still want to debug your code 55 | at your work desktop without the hassle of forwarding a GUI, 56 | then `edbg` is perfect. 57 | 58 | You can see examples of how to use `edbg` in 59 | the [wiki](https://github.com/etnt/edbg/wiki) pages, or 60 | [here](https://etnt.gitbook.io/edbg). 61 | 62 | 63 | ## INSTALL 64 | ``` 65 | Run: make 66 | Add: code:add_path("YOUR-PATH-HERE/edbg/_build/default/lib/edbg/ebin"). 67 | Add: code:add_path("YOUR-PATH-HERE/edbg/_build/default/lib/pp_record/ebin"). 68 | to your ~/.erlang file. 69 | ``` 70 | 71 | If you don't fancy rebar and favour plain Make + Erlc then: 72 | ``` 73 | Run: make old 74 | Add: code:add_path("YOUR-PATH-HERE/edbg/ebin"). 75 | Add: code:add_path("YOUR-PATH-HERE/edbg/deps/pp_record/ebin"). 76 | to your ~/.erlang file. 77 | ``` 78 | 79 | A Hex package exist so to use as an Elixir Mix dependency, 80 | add this to your list of deps: 81 | 82 | ``` erlang 83 | {:edbg, "~> 0.9.6"} 84 | ``` 85 | 86 | NOTE: The coloring code makes use of 'maps', so in case of an 87 | older Erlang system where 'maps' isn't supported, you must compile 88 | the code as: 89 | ``` 90 | env USE_COLORS=false make 91 | ``` 92 | 93 | 94 | 95 | 96 | ## Tracing 97 | 98 | The built in tracing functionality of the BEAM is great 99 | and there are a couple of tools that builds upon it. 100 | 101 | What differentiates `edbg` is two things; first it runs in 102 | the terminal with all the pros (and cons) that brings; 103 | second it try to avoid drowning you in trace output. 104 | 105 | Trace output can be massive and in fact sink your whole 106 | BEAM node. `edbg` protects you from that by letting you 107 | restrict the amount of trace messages generated and/or 108 | the amount of time the tracing will run for. The output 109 | will then be presented in structured way to let you 110 | focus on what you are looking for, without having to 111 | wade through mountains of non relevant data. 112 | 113 | 114 | ## Supervisor Tree Browser 115 | By invoking the `edbg:suptrees()` function from the Erlang shell, 116 | you will enter the supervision tree browser; a way to quickly 117 | get an overview of your system by listing the running supervisors. 118 | 119 | The browser makes it possible to browse through the tree of supervisors 120 | as well as any linked processes. At any point, a process can have its 121 | (default) process-info data printed as well as its backtrace. 122 | 123 | If the worker process is of type gen_server, gen_event or gen_statem, 124 | it is possible to pretty-print the State of the callback module that 125 | the worker process is maintaining. 126 | 127 | It is also possible to setup a process monitor on any process in order 128 | to get a notification printed if the process should terminate. 129 | 130 | Note that the (edbg) tracing is built-in; i.e you can start tracing 131 | on any process you can access from the supervision browser. 132 | This will trace all modules that are executing within the process 133 | as well as any messages sent/received from/to the process. 134 | 135 | 136 | 137 | ## Debugger 138 | 139 | With the `edbg` debugger interface you can make use of the Erlang 140 | debugger in a similar way as with the GUI version. 141 | 142 | You can set break points and then attach to a process that has 143 | stopped at a break point. From there you can single step and do the 144 | usual debugger operations. 145 | 146 | What differentiates `edbg` is the fact that you are running in a 147 | terminal. 148 | 149 | -------------------------------------------------------------------------------- /src/edbg_color_srv.erl: -------------------------------------------------------------------------------- 1 | %%% @private 2 | %% 3 | %% Custom colors may be set using the environment variable EDBG_COLOR. 4 | %% 5 | %% It should contain a SPACE separated string with ITEM=COLOR entries. 6 | %% 7 | %% Valid ITEMs are: 8 | %% 9 | %% att - attention color (default: whiteb) 10 | %% warn - warning color (default: yellow) 11 | %% err - error color (default: red) 12 | %% cur - current line color (default: green) 13 | %% 14 | %% Valid COLORs are: 15 | %% 16 | %% black, blackb 17 | %% red, redb 18 | %% green, greenb 19 | %% yellow, yellowb 20 | %% blue, blueb 21 | %% magenta, magentab 22 | %% cyan, cyanb 23 | %% white, whiteb 24 | %% 25 | %% Colors ending in 'b' are the bright variants. 26 | %% 27 | %% Example: 28 | %% 29 | %% export EDBG_COLOR="att=yellowb warn=magenta" 30 | %% 31 | %% Colors not specified in EDBG_COLOR will keep their defaults. 32 | %% 33 | -module(edbg_color_srv). 34 | 35 | -export([init/0, 36 | info_msg/2, 37 | att_msg/2, 38 | warn_msg/2, 39 | err_msg/2, 40 | cur_line_msg/2, 41 | info_str/2, 42 | att_str/2, 43 | warn_str/2, 44 | err_str/2, 45 | cur_line_str/2, 46 | c_hi/1, 47 | c_warn/1, 48 | c_err/1, 49 | help_hi/1]). 50 | 51 | -define(SERVER, ?MODULE). 52 | 53 | 54 | 55 | %% 56 | %% PUBLIC API 57 | %% 58 | 59 | %% @private 60 | info_msg(Fmt, Args) -> 61 | io:format(info_str(Fmt, Args)). 62 | 63 | %% @private 64 | att_msg(Fmt, Args) -> 65 | io:format(att_str(Fmt, Args)). 66 | 67 | %% @private 68 | warn_msg(Fmt, Args) -> 69 | io:format(warn_str(Fmt, Args)). 70 | 71 | %% @private 72 | err_msg(Fmt, Args) -> 73 | io:format(err_str(Fmt, Args)). 74 | 75 | %% @private 76 | cur_line_msg(Fmt, Args) -> 77 | io:format(cur_line_str(Fmt, Args)). 78 | 79 | %% @private 80 | info_str(Fmt, Args) -> 81 | call({info_str, Fmt, Args}). 82 | 83 | %% @private 84 | att_str(Fmt, Args) -> 85 | call({att_str, Fmt, Args}). 86 | 87 | %% @private 88 | warn_str(Fmt, Args) -> 89 | call({warn_str, Fmt, Args}). 90 | 91 | %% @private 92 | err_str(Fmt, Args) -> 93 | call({err_str, Fmt, Args}). 94 | 95 | %% @private 96 | cur_line_str(Fmt, Args) -> 97 | call({cur_line_str, Fmt, Args}). 98 | 99 | %% @private 100 | c_hi(Str) -> 101 | call({c_hi, Str}). 102 | 103 | %% @private 104 | c_warn(Str) -> 105 | call({c_warn, Str}). 106 | 107 | %% @private 108 | c_err(Str) -> 109 | call({c_err, Str}). 110 | 111 | %% @private 112 | help_hi(Str) -> 113 | %% highligt character(s) inside parentheses 114 | F = fun($), {normal, _Collect, Acc}) -> 115 | {in_parentheses, [], Acc}; 116 | ($(, {in_parentheses, Collect, Acc}) -> 117 | Hi = [$(, c_hi(Collect), $)], 118 | {normal, [], [Hi | Acc]}; 119 | (C, {M = in_parentheses, Collect, Acc}) -> 120 | {M, [C | Collect], Acc}; 121 | (C, {M = normal, [], Acc}) -> 122 | {M, [], [C | Acc]} 123 | end, 124 | {_, _, Res} = lists:foldr(F, {normal, [], []}, lists:flatten(Str)), 125 | Res. 126 | 127 | %% @private 128 | init() -> 129 | case whereis(?SERVER) of 130 | undefined -> 131 | ok; 132 | Pid0 when is_pid(Pid0) -> 133 | unregister(?SERVER), 134 | Pid0 ! quit 135 | end, 136 | Pid = spawn(fun() -> color_srv() end), 137 | register(?SERVER, Pid), 138 | ok. 139 | 140 | %% 141 | %% INTERNAL 142 | %% 143 | 144 | color_srv() -> 145 | CMap0 = #{att => whiteb, 146 | err => red, 147 | warn => yellow, 148 | cur => green}, 149 | 150 | CMap = case os:getenv("EDBG_COLOR") of 151 | false -> 152 | CMap0; 153 | ColorStr -> 154 | parse_env(ColorStr, CMap0) 155 | end, 156 | color_server_loop(CMap). 157 | 158 | parse_env(ColorStr, CMap) -> 159 | parse_env_c(string:tokens(ColorStr, " "), CMap). 160 | 161 | parse_env_c(["att="++Color|T], CMap) -> 162 | parse_env_c(T, set_color(att, Color, CMap)); 163 | parse_env_c(["warn="++Color|T], CMap) -> 164 | parse_env_c(T, set_color(warn, Color, CMap)); 165 | parse_env_c(["err="++Color|T], CMap) -> 166 | parse_env_c(T, set_color(err, Color, CMap)); 167 | parse_env_c(["cur="++Color|T], CMap) -> 168 | parse_env_c(T, set_color(cur, Color, CMap)); 169 | parse_env_c([Item|T], CMap) -> 170 | io:format( 171 | "warning: unknown option ~p in EDBG_COLOR - ignoring~n" 172 | " valid color items are ~p~n", [Item, [att, warn, err, cur]]), 173 | parse_env_c(T, CMap); 174 | parse_env_c([], CMap) -> 175 | CMap. 176 | 177 | set_color(Item, Color, CMap) -> 178 | ValidColors = [black, blackb, red, redb, green, greenb, yellow, 179 | yellowb, blue, blueb, magenta, magentab, cyan, 180 | cyanb, white, whiteb], 181 | 182 | AColor = list_to_atom(Color), 183 | case lists:member(AColor, ValidColors) of 184 | true -> 185 | CMap#{Item => AColor}; 186 | false -> 187 | io:format("warning: invalid color \"" ++ Color ++ "\"" 188 | " in EDBG_COLOR - ignoring~n" 189 | " valid colors are ~p", [ValidColors]), 190 | CMap 191 | end. 192 | 193 | color_server_loop(CMap) -> 194 | receive 195 | quit -> 196 | exit(normal); 197 | {{info_str, Fmt, Args}, From, Ref} -> 198 | R = io_lib:format(lists:flatten(Fmt), Args), 199 | reply(From, Ref, R); 200 | {{att_str, Fmt, Args}, From, Ref} -> 201 | R = color_str(maps:get(att, CMap), Fmt, Args), 202 | reply(From, Ref, R); 203 | {{warn_str, Fmt, Args}, From, Ref} -> 204 | R = color_str(maps:get(warn, CMap), Fmt, Args), 205 | reply(From, Ref, R); 206 | {{err_str, Fmt, Args}, From, Ref} -> 207 | R = color_str(maps:get(err, CMap), Fmt, Args), 208 | reply(From, Ref, R); 209 | {{cur_line_str, Fmt, Args}, From, Ref} -> 210 | R = color_str(maps:get(cur, CMap), Fmt, Args), 211 | reply(From, Ref, R); 212 | {{c_hi, Str}, From, Ref} -> 213 | C = maps:get(att, CMap), 214 | reply(From, Ref, edbg_color:C(Str)); 215 | {{c_warn, Str}, From, Ref} -> 216 | C = maps:get(warn, CMap), 217 | reply(From, Ref, edbg_color:C(Str)); 218 | {{c_err, Str}, From, Ref} -> 219 | C = maps:get(err, CMap), 220 | reply(From, Ref, edbg_color:C(Str)); 221 | _ -> 222 | undefined 223 | end, 224 | color_server_loop(CMap). 225 | 226 | call(Msg) -> 227 | call(?SERVER, Msg). 228 | 229 | call(Pid, Msg) -> 230 | Ref = make_ref(), 231 | Pid ! {Msg, self(), Ref}, 232 | receive 233 | {Ref, Response} -> 234 | Response 235 | end. 236 | 237 | reply(Pid, Ref, Term) -> 238 | Pid ! {Ref, Term}. 239 | 240 | color_str(Color, Fmt, Args) when is_atom(Color) -> 241 | io_lib:format(lists:flatten(edbg_color:Color(Fmt)), Args). 242 | -------------------------------------------------------------------------------- /src/edbg_pp_tree.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Torbjorn Tornkvist 3 | %%% @copyright (C) 2024, Torbjorn Tornkvist 4 | %%% @doc 5 | %%% This module provides functionality to pretty print a tree of linked 6 | %%% Erlang processes and ports. It visualizes the process hierarchy using 7 | %%% ASCII characters, making it easier to understand the relationships 8 | %%% between processes in an Erlang system. 9 | %%% 10 | %%% The module offers three main functions: 11 | %%% - print/1: Prints the process tree starting from a given node. 12 | %%% - print/2: Prints the process tree with additional information. 13 | %%% - print/3: Prints the process tree with a maximum depth and additional information. 14 | %%% 15 | %%% @end 16 | %%%------------------------------------------------------------------- 17 | -module(edbg_pp_tree). 18 | 19 | -export([ 20 | print/1, 21 | print/2, 22 | print/3 23 | ]). 24 | 25 | -define(init_maxdepth(MaxDepth), {0,MaxDepth}). 26 | %%-define(below_maxdepth(D), element(1, D) < element(2, D)). 27 | -define(above_maxdepth(D), element(1, D) > element(2, D)). 28 | -define(inc_depth(D), {element(1, D) + 1, element(2, D)}). 29 | 30 | %%-------------------------------------------------------------------- 31 | %% @doc Prints the process tree starting from the given node. 32 | %% @param Node The starting node (pid or port) for the tree. 33 | %% @end 34 | %%-------------------------------------------------------------------- 35 | print(Node) -> 36 | print(Node, []). 37 | 38 | %%-------------------------------------------------------------------- 39 | %% @doc Prints the process tree with additional information. 40 | %% @param Node The starting node (pid or port) for the tree. 41 | %% @param Xinfo List of additional process information to display. 42 | %% @end 43 | %%-------------------------------------------------------------------- 44 | print(Node, Xinfo) when is_list(Xinfo) -> 45 | print(Node, nodepth, Xinfo). 46 | 47 | %%-------------------------------------------------------------------- 48 | %% @doc Prints the process tree with a maximum depth and additional information. 49 | %% @param Node The starting node (pid or port) for the tree. 50 | %% @param MaxDepth The maximum depth to traverse in the tree. 51 | %% @param Xinfo List of additional process information to display. 52 | %% @return {ok, TotalNodes} where TotalNodes is the number of nodes printed. 53 | %% @end 54 | %%-------------------------------------------------------------------- 55 | print(Node, MaxDepth, Xinfo) when is_list(Xinfo) -> 56 | io:format("~p ~s~n", [Node, mk_xinfo(Node, Xinfo)]), 57 | init_visited(), 58 | push_visited(Node), 59 | Children = get_children(Node), 60 | {ok, 1 + print_children(Children, ?inc_depth(?init_maxdepth(MaxDepth)), [" |"], Xinfo)}. 61 | 62 | %%-------------------------------------------------------------------- 63 | %% @doc Recursively prints children of a node. 64 | %% @private 65 | %%-------------------------------------------------------------------- 66 | print_children(_,Depth, _, _) when ?above_maxdepth(Depth) -> 67 | 0; 68 | print_children([],_, _, _) -> 69 | 0; 70 | print_children([H], Depth, Indent, Xinfo) -> 71 | pretty_print(H, Indent, Depth, Xinfo, _IsLast = true); 72 | print_children([H | T], Depth, Indent, Xinfo) -> 73 | N = pretty_print(H, Indent, Depth, Xinfo, _IsLast = false), 74 | N + print_children(T, Depth, Indent, Xinfo). 75 | 76 | %%-------------------------------------------------------------------- 77 | %% @doc Pretty prints a single node in the tree. 78 | %% @private 79 | %%-------------------------------------------------------------------- 80 | pretty_print(Node, RIndent, Depth, Xinfo, IsLast) -> 81 | Indent = 82 | case IsLast of 83 | false -> lists:reverse(RIndent); 84 | true -> lists:reverse([" +" | tl(RIndent)]) 85 | end, 86 | io:format("~s--~p ~s~n", [Indent, Node, mk_xinfo(Node, Xinfo)]), 87 | Children = get_children(Node), 88 | R = 89 | case IsLast of 90 | true -> [" |", " " | tl(RIndent)]; 91 | false -> [" |" | RIndent] 92 | end, 93 | push_visited(Node), 94 | 1 + print_children(Children, ?inc_depth(Depth), R, Xinfo). 95 | 96 | %%-------------------------------------------------------------------- 97 | %% @doc Retrieves unvisited children of a node. 98 | %% @private 99 | %%-------------------------------------------------------------------- 100 | get_children(Node) -> 101 | [ 102 | C 103 | || C <- children(Node), 104 | false == is_visited(C) 105 | ]. 106 | 107 | %%-------------------------------------------------------------------- 108 | %% @doc Retrieves all linked processes or ports for a given node. 109 | %% @private 110 | %%-------------------------------------------------------------------- 111 | children(Node) when is_pid(Node) orelse is_port(Node) -> 112 | try erlang:process_info(Node, links) of 113 | {links, Links} -> Links 114 | catch 115 | _:_ -> [] 116 | end. 117 | 118 | %%-------------------------------------------------------------------- 119 | %% @doc Formats additional information for a node. 120 | %% @private 121 | %%-------------------------------------------------------------------- 122 | mk_xinfo(_Node, []) -> 123 | ""; 124 | mk_xinfo(Node, _) when is_port(Node) -> 125 | ""; 126 | mk_xinfo(Node, L) -> 127 | Pinfo = process_info(Node, L), 128 | lists:foldr( 129 | fun 130 | ({current_function, {M, F, A}}, Acc) -> 131 | [io_lib:format(", current=~p:~p/~p", [M, F, A]) | Acc]; 132 | ({initial_call, {M, F, A}}, Acc) -> 133 | [io_lib:format(", init=~p:~p/~p", [M, F, A]) | Acc]; 134 | ({registered_name, Name}, Acc) -> 135 | case Name of 136 | [] -> Acc; 137 | _ -> [io_lib:format(", regname=~p",[Name]) | Acc] 138 | end; 139 | ({trap_exit, Bool}, Acc) -> 140 | case Bool of 141 | true -> [io_lib:format(", trap_exit", []) | Acc]; 142 | _ -> Acc 143 | end; 144 | ({Key, Val}, Acc) -> 145 | [io_lib:format(", ~p=~p", [Key, Val]) | Acc] 146 | end, 147 | [], 148 | Pinfo 149 | ). 150 | 151 | %%-------------------------------------------------------------------- 152 | %% @doc Initializes the visited nodes set. 153 | %% @private 154 | %%-------------------------------------------------------------------- 155 | init_visited() -> 156 | erase(visited), 157 | put(visited, sets:new()). 158 | 159 | %%-------------------------------------------------------------------- 160 | %% @doc Marks a node as visited. 161 | %% @private 162 | %%-------------------------------------------------------------------- 163 | push_visited(Node) -> 164 | Visited = get(visited), 165 | put(visited, sets:add_element(Node, Visited)). 166 | 167 | %%-------------------------------------------------------------------- 168 | %% @doc Checks if a node has been visited. 169 | %% @private 170 | %%-------------------------------------------------------------------- 171 | is_visited(Node) -> 172 | Visited = get(visited), 173 | sets:is_element(Node, Visited). 174 | -------------------------------------------------------------------------------- /src/edbg_file_tracer.erl: -------------------------------------------------------------------------------- 1 | %%% @private 2 | %%%------------------------------------------------------------------- 3 | %%% Created : 24 May 2018 by kruskakli@gmail.com 4 | %%% 5 | %%% This is a new version of the edbg tracer that logs trace 6 | %%% messages to file. It is no longer using the dbg.erl module; 7 | %%% instead it is using the trace BIFs directly. 8 | %%% 9 | %%% The way to set the config goes like this: 10 | %%% 11 | %%% MF = new_mf(), 12 | %%% set_config([log_file_f("./my.log"), 13 | %%% max_msgs_f(500), 14 | %%% add_mf_f(fname(mname(MF, lists), reverse), 15 | %%% add_mf_f(mname(MF, mymod)) 16 | %%% ], get_config()). 17 | %%% 18 | %%% Then start stop the tracing as: 19 | %%% 20 | %%% start_trace() 21 | %%% stop_trace() 22 | %%% 23 | %%%------------------------------------------------------------------- 24 | -module(edbg_file_tracer). 25 | 26 | -behaviour(gen_server). 27 | 28 | %% API 29 | -export([add_mf_f/1 30 | , cfg_file_f/1 31 | , dump_output_eager_f/0 32 | , dump_output_lazy_f/0 33 | , fname/2 34 | , get_config/0 35 | , get_trace_spec/0 36 | , load_config/0 37 | , log_file_f/1 38 | , max_msgs_f/1 39 | , memory_f/0 40 | , mname/2 41 | , monotonic_ts_f/0 42 | , new_mf/0 43 | , send_receive_f/0 44 | , set_config/2 45 | , set_on_f/1 46 | , start/0 47 | , start_link/0 48 | , start_trace/0 49 | , stop/0 50 | , stop_trace/0 51 | , trace_spec_f/1 52 | , trace_time_f/1 53 | ]). 54 | 55 | %% gen_server callbacks 56 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 57 | terminate/2, code_change/3]). 58 | 59 | %% debug export 60 | -export([log/2]). 61 | 62 | 63 | -include("edbg_trace.hrl"). 64 | 65 | 66 | -define(SERVER, ?MODULE). 67 | 68 | -define(DOT_EDBG, ".edbg"). 69 | 70 | %%-define(log(Fmt,Args), log("~p: "++Fmt,[?MODULE|Args])). 71 | -define(log(Fmt,Args), ok). 72 | 73 | -define(DEFAULT_MAX_MSGS, 1000). 74 | -define(DEFAULT_TRACE_TIME, 10). % seconds 75 | 76 | 77 | -record(m, { 78 | mname = '_', 79 | fname = '_' 80 | }). 81 | 82 | -record(state, { 83 | log_file = "./edbg.trace_result", 84 | cfg_file = "./edbg_trace.config", 85 | max_msgs = ?DEFAULT_MAX_MSGS, 86 | trace_time = ?DEFAULT_TRACE_TIME, 87 | trace_spec = all, 88 | modules = [] :: [#m{}], 89 | which_pid = all, % all | first 90 | 91 | dump_output = false :: boolean(), 92 | 93 | tracer :: pid(), 94 | srv_pid :: pid(), 95 | 96 | %% use the 'monotonoc_timestamp' trace option 97 | monotonic_ts = false, 98 | 99 | %% list of 'known' pids, i.e a list containing the 100 | %% Pids that has been seen in trace-call messages. 101 | known_pids = ordsets:new() :: [pid()], 102 | 103 | %% trace on send/receive msgs from 'known' pids 104 | %% (to avoid drowning in send/receive trace calls, 105 | %% we only save those trace messages that are sent or 106 | %% received from a Pid in a previously seen trace-call message) 107 | send_receive = false, 108 | 109 | %% trace memory via the process_info/2 BIF. 110 | memory = false, 111 | 112 | %% Any of: [set_on_spawn, set_on_first_spawn, set_on_link, set_on_first_link], 113 | set_on = [] 114 | 115 | }). 116 | 117 | %%% -------------------------------------------------------------------- 118 | %%% A P I 119 | %%% 120 | %%% -------------------------------------------------------------------- 121 | 122 | %% @private 123 | start() -> 124 | gen_server:start({local, ?SERVER}, ?MODULE, [], []). 125 | 126 | %% @private 127 | start_link() -> 128 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 129 | 130 | %% @private 131 | stop() -> 132 | gen_server:stop(?SERVER). 133 | 134 | %% @private 135 | start_trace() -> 136 | call(start_trace). 137 | 138 | %% @private 139 | stop_trace() -> 140 | call(stop_trace). 141 | 142 | %% @private 143 | get_config() -> 144 | call(get_config). 145 | 146 | %% @private 147 | load_config() -> 148 | call(load_config). 149 | 150 | %% @private 151 | set_config(Funs, State) 152 | when is_list(Funs) andalso 153 | is_record(State, state) -> 154 | NewState = lists:foldl(fun(F,S) -> F(S) end, State, Funs), 155 | call({set_config, NewState}). 156 | 157 | %% @private 158 | get_trace_spec() -> 159 | call(get_trace_spec). 160 | 161 | %% @private 162 | log_file_f(LogFile) 163 | when is_list(LogFile) -> 164 | fun(State) -> State#state{log_file = LogFile} end. 165 | 166 | %% @private 167 | cfg_file_f(CfgFile) 168 | when is_list(CfgFile) orelse (CfgFile == false) -> 169 | fun(State) -> State#state{cfg_file = CfgFile} end. 170 | 171 | %% @private 172 | dump_output_lazy_f() -> 173 | fun(State) -> State#state{dump_output = false} end. 174 | 175 | %% @private 176 | dump_output_eager_f() -> 177 | fun(State) -> State#state{dump_output = true} end. 178 | 179 | %% @private 180 | monotonic_ts_f() -> 181 | fun(State) -> State#state{monotonic_ts = true} end. 182 | 183 | %% @private 184 | send_receive_f() -> 185 | fun(State) -> State#state{send_receive = true} end. 186 | 187 | %% @private 188 | memory_f() -> 189 | fun(State) -> State#state{memory = true} end. 190 | 191 | %% @private 192 | set_on_f(Key) -> 193 | fun(State) -> 194 | L = State#state.set_on, 195 | State#state{set_on = [Key|L]} 196 | end. 197 | 198 | %% @private 199 | max_msgs_f(Max) 200 | when is_integer(Max) andalso Max >= 0 -> 201 | fun(State) -> State#state{max_msgs = Max} end. 202 | 203 | %% @private 204 | trace_time_f(Time) 205 | when is_integer(Time) andalso Time >= 0 -> 206 | fun(State) -> State#state{trace_time = Time} end. 207 | 208 | %% @private 209 | trace_spec_f(Spec) 210 | when is_atom(Spec) orelse is_pid(Spec) -> 211 | fun(State) -> State#state{trace_spec = Spec} end. 212 | 213 | %% @private 214 | add_mf_f(M) 215 | when is_record(M, m) -> 216 | fun(#state{modules = Ms} = State) -> State#state{modules = [M|Ms]} end. 217 | 218 | %% @private 219 | new_mf() -> #m{}. 220 | 221 | %% @private 222 | mname(M, Mname) when is_record(M, m) -> 223 | M#m{mname = l2a(Mname)}. 224 | 225 | %% @private 226 | fname(M, Fname) 227 | when is_record(M, m) -> 228 | M#m{fname = l2a(Fname)}. 229 | 230 | call(Msg) -> 231 | gen_server:call(?SERVER, Msg, infinity). 232 | 233 | %%%=================================================================== 234 | %%% gen_server callbacks 235 | %%%=================================================================== 236 | 237 | %%-------------------------------------------------------------------- 238 | %% @private 239 | init([]) -> 240 | process_flag(trap_exit, true), 241 | {ok, setup_init_state()}. 242 | 243 | -define(is_bool(B), ((B == true) orelse (B == false))). 244 | 245 | setup_init_state() -> 246 | try 247 | Home = os:getenv("HOME"), 248 | Fname = filename:join([Home, ?DOT_EDBG]), 249 | L = consult_file(Fname), 250 | F = fun({log_file, LogFile}, S) when is_list(LogFile) -> 251 | S#state{log_file = LogFile}; 252 | ({cfg_file, CfgFile}, S) when is_list(CfgFile) -> 253 | S#state{cfg_file = CfgFile}; 254 | ({max_msgs, MaxMsgs}, S) when is_integer(MaxMsgs) -> 255 | S#state{max_msgs = MaxMsgs}; 256 | ({trace_time, TraceTime}, S) when is_integer(TraceTime) -> 257 | S#state{trace_time = TraceTime}; 258 | ({monotonic_ts, MonoTS}, S) when ?is_bool(MonoTS) -> 259 | S#state{monotonic_ts = MonoTS}; 260 | ({send_receive, SendRec}, S) when ?is_bool(SendRec) -> 261 | S#state{send_receive = SendRec}; 262 | ({memory, Memory}, S) when ?is_bool(Memory) -> 263 | S#state{memory = Memory}; 264 | ({set_on, SetOn}, S) when is_list(SetOn) -> 265 | S#state{set_on = SetOn}; 266 | (Opt, S) -> 267 | ?err_msg("~nIgnoring unknown default option: ~p~n",[Opt]), 268 | S 269 | end, 270 | lists:foldl(F, #state{}, L) 271 | catch 272 | undefined -> 273 | #state{}; 274 | _:Err -> 275 | ?err_msg("~nError reading: ~p, ignoring it: ~p~n",[?DOT_EDBG,Err]), 276 | #state{} 277 | end. 278 | 279 | consult_file(Fname) -> 280 | case file:consult(Fname) of 281 | {ok,L} -> L; 282 | _ -> throw(undefined) 283 | end. 284 | 285 | %%-------------------------------------------------------------------- 286 | %% handle_call(ping, _From, State) -> 287 | %% Reply = pong, 288 | %% {reply, Reply, State}; 289 | %% @private 290 | handle_call(get_config, _From, State) -> 291 | Reply = State, 292 | {reply, Reply, State}; 293 | 294 | handle_call({set_config, State}, _From, _State) -> 295 | save_config(State, State#state.cfg_file), 296 | Reply = ok, 297 | {reply, Reply, State}; 298 | 299 | handle_call(load_config, _From, OldState) -> 300 | State = get_file_config(OldState#state.cfg_file), 301 | Reply = ok, 302 | {reply, Reply, State}; 303 | 304 | handle_call(get_trace_spec, _From, State) -> 305 | Reply = State#state.trace_spec, 306 | {reply, Reply, State}; 307 | 308 | handle_call(start_trace, _From, #state{tracer = Pid, trace_time = Time} = State) 309 | when not(is_pid(Pid)) -> 310 | Tracer = start_tracer(State), 311 | ?log("Starting Tracer(~p)...~n",[Tracer]), 312 | timer:apply_after(timer:seconds(Time),gen_server,cast,[?SERVER,stop_dbg]), 313 | ?log("Tracer started: ~p", [Tracer]), 314 | {reply, ok, State#state{tracer = Tracer}}; 315 | 316 | handle_call(stop_trace, _From, #state{tracer = Tracer} = State) 317 | when is_pid(Tracer) -> 318 | ?log("Stopping Tracer(~p)...~n",[Tracer]), 319 | Ref = erlang:trace_delivered(all), 320 | receive 321 | {trace_delivered,all,Ref} -> ok 322 | end, 323 | Tracer ! {self(),stop}, 324 | receive 325 | {Tracer, stopped} -> ok; 326 | {'EXIT', Tracer, _} -> ok 327 | end, 328 | ?log("Server(~p): stopping Tracer(~p)...DONE~n",[self(), Tracer]), 329 | {reply, ok, State#state{tracer = undefined}}; 330 | 331 | handle_call(_Req, _From, State) -> 332 | Reply = error, 333 | {reply, Reply, State}. 334 | 335 | 336 | %%-------------------------------------------------------------------- 337 | 338 | %% @private 339 | handle_cast(_Msg, State) -> 340 | ?log("Got unexpected cast: ~p", [_Msg]), 341 | {noreply, State}. 342 | 343 | %%-------------------------------------------------------------------- 344 | %% @private 345 | handle_info({'EXIT', Tracer, _Reason}, #state{tracer = Tracer} = State) -> 346 | ?log("Tracer exited, reason: ~p", [_Reason]), 347 | {noreply, State#state{tracer = undefined}}; 348 | 349 | handle_info(_Info, State) -> 350 | ?log("Got unexpected info: ~p", [_Info]), 351 | {noreply, State}. 352 | 353 | %%-------------------------------------------------------------------- 354 | %% @private 355 | terminate(_Reason, _State) -> 356 | ?log("Server stopped - ~p", [_Reason]), 357 | ok. 358 | 359 | %%-------------------------------------------------------------------- 360 | %% @private 361 | code_change(_OldVsn, State, _Extra) -> 362 | {ok, State}. 363 | 364 | 365 | 366 | %%%=================================================================== 367 | %%% Internal functions 368 | %%%=================================================================== 369 | 370 | save_config(_X, false) -> 371 | ok; 372 | save_config(X, CfgFile) -> 373 | {ok,Fd} = file:open(CfgFile, [write]), 374 | try 375 | io:format(Fd, "~p.~n", [X]) 376 | after 377 | file:close(Fd) 378 | end. 379 | 380 | get_file_config(false) -> 381 | #state{cfg_file = false}; 382 | get_file_config(CfgFile) -> 383 | try 384 | {ok,[X]} = file:consult(CfgFile), 385 | X 386 | catch 387 | _:_ -> 388 | ?info_msg("~nNo config file loaded!~n",[]), 389 | #state{cfg_file = false} 390 | end. 391 | 392 | 393 | start_tracer(State0) -> 394 | State = State0#state{srv_pid = self()}, 395 | spawn_opt(fun() -> run_tracer(State) end, [link,{priority,max}]). 396 | 397 | 398 | run_tracer(#state{modules = Modules, trace_spec = TraceSpec} = State) -> 399 | 400 | if length(Modules) > 0 -> 401 | %% Setup which Modules we want to do call-trace on. 402 | [code:ensure_loaded(M#m.mname) || M <- Modules], 403 | [erlang:trace_pattern({M,F,'_'},[{'_',[],[{return_trace}]}],[local]) 404 | || #m{mname=M, fname=F} <- Modules]; 405 | true -> 406 | erlang:trace_pattern({'_','_','_'}, 407 | [{'_',[],[{return_trace}]}], 408 | [local]) 409 | end, 410 | 411 | %% Start tracing! 412 | erlang:trace(TraceSpec,true, 413 | [call,procs,{tracer,self()}] ++ 414 | monotonic_ts(State) ++ 415 | set_on(State) ++ 416 | send_receive(State)), 417 | tloop(State, 0, []). 418 | 419 | l2a(L) when is_list(L) -> 420 | erlang:list_to_atom(L); 421 | l2a(A) when is_atom(A) -> 422 | A. 423 | 424 | 425 | 426 | 427 | tloop(#state{srv_pid = SrvPid, 428 | trace_spec = TraceSpec, 429 | known_pids = KnownPids0} = State, 430 | N, 431 | Tmsgs) -> 432 | {Suspended, Traces, MaybeStop, KnownPids} = 433 | recv_all_traces(State, SrvPid, KnownPids0), 434 | {NewN, NewTmsgs} = tmsgs(State, Traces, N, Tmsgs), 435 | resume(Suspended), 436 | 437 | case {NewN >= State#state.max_msgs, MaybeStop} of 438 | 439 | {true, _} -> 440 | ?log("Tracer(~p): max reached, stopping N=~p ...~n", [self(),NewN]), 441 | %% Max amount of trace msgs; stop tracing! 442 | erlang:trace(TraceSpec,false, 443 | [call,procs,{tracer,self()}] ++ 444 | monotonic_ts(State) ++ 445 | set_on(State) ++ 446 | send_receive(State)), 447 | dump_tmsgs(State#state{dump_output = true, 448 | known_pids = KnownPids}, NewTmsgs), 449 | exit(max_msgs); 450 | 451 | {_, {From, stop}} -> 452 | ?log("Tracer(~p): stopping N=~p ...~n", [self(),NewN]), 453 | erlang:trace(TraceSpec,false, 454 | [call,procs,{tracer,self()}] ++ 455 | monotonic_ts(State) ++ 456 | set_on(State) ++ 457 | send_receive(State)), 458 | dump_tmsgs(State#state{dump_output = true, 459 | known_pids = KnownPids}, Tmsgs), 460 | From ! {self(), stopped}, 461 | exit(normal); 462 | 463 | _ -> 464 | dump_tmsgs(State, NewTmsgs), 465 | tloop(State#state{known_pids = KnownPids}, NewN, NewTmsgs) 466 | end. 467 | 468 | monotonic_ts(#state{monotonic_ts = true}) -> [monotonic_timestamp]; 469 | monotonic_ts(_State) -> []. 470 | 471 | send_receive(#state{send_receive = true}) -> [send,'receive']; 472 | send_receive(_State) -> []. 473 | 474 | 475 | set_on(#state{set_on = SetOn}) -> 476 | Allowed = [set_on_spawn, set_on_first_spawn, 477 | set_on_link, set_on_first_link], 478 | lists:foldl( 479 | fun(X,Acc) -> 480 | case lists:member(X,Allowed) of 481 | true -> [X|Acc]; 482 | false -> Acc 483 | end 484 | end, [], SetOn). 485 | 486 | 487 | dump_tmsgs(#state{dump_output = false}, _Tmsgs) -> 488 | ok; 489 | dump_tmsgs(#state{log_file = Fname}, Tmsgs) -> 490 | %% We just overwrite any existing file in order to ensure 491 | %% we have the latest on disk in case we should crash. 492 | %% FIXME: Appending data would save memory that now is 493 | %% held in this process by all the trace messages. 494 | ok = file:write_file(Fname,term_to_binary(Tmsgs)). 495 | 496 | 497 | tmsgs(#state{max_msgs = Max, send_receive = SendReceive} = State, 498 | [Trace|Traces], 499 | N, % Max allowed number of collected trace messages! 500 | Tmsgs) 501 | when N < Max andalso 502 | (?is_trace_call(Trace) orelse 503 | ?is_trace_return_from(Trace) orelse 504 | (SendReceive == true 505 | andalso 506 | (?is_trace_send(Trace) orelse 507 | ?is_trace_receive(Trace))) 508 | ) -> 509 | tmsgs(State, Traces, N+1, [{N,Trace}|Tmsgs]); 510 | %% 511 | %% Anything else should not be collected! 512 | tmsgs(#state{max_msgs = Max} = State, [_|Traces], N, Tmsgs) when N < Max -> 513 | tmsgs(State, Traces, N, Tmsgs); 514 | %% 515 | %% Max amount of trace messages reached! 516 | tmsgs(#state{max_msgs = Max}, _Traces, N, Tmsgs) when (N >= Max) -> 517 | {N, Tmsgs}; 518 | %% 519 | tmsgs(_State, [], N, Tmsgs) -> 520 | {N, Tmsgs}. 521 | 522 | 523 | %% @private 524 | log(Format, Args) -> 525 | io:format(Format, Args). 526 | 527 | 528 | %% --------------------------------------------------------------------- 529 | %% CODE TAKEN FROM THE OTP dbg.erl MODULE! 530 | %% (slightly modified...) 531 | %% 532 | %% So why are they doing it like this, I mean the suspend/resume thing...? 533 | %% Probably some sort of throttling mechanism? 534 | %% 535 | recv_all_traces(State, SrvPid, KnownPids) -> 536 | recv_all_traces(State, SrvPid, KnownPids, [], [], infinity). 537 | 538 | recv_all_traces(State, SrvPid, KnownPids, Suspended0, Traces, Timeout) -> 539 | receive 540 | Trace when is_tuple(Trace) andalso 541 | ((?is_trace_msg(Trace) orelse 542 | ?is_trace_ts_msg(Trace)) andalso 543 | (?trace_pid(Trace) =/= SrvPid)) -> 544 | Suspended = suspend(Trace, Suspended0), 545 | case save_trace_p(Trace, KnownPids) of 546 | true -> 547 | NewKnownPids = maybe_add_to_known_pids(Trace, KnownPids), 548 | recv_all_traces(State, 549 | SrvPid, 550 | NewKnownPids, 551 | Suspended, 552 | [x(State,Trace)|Traces], 0); 553 | false -> 554 | recv_all_traces(State, 555 | SrvPid, 556 | KnownPids, 557 | Suspended, 558 | Traces, 0) 559 | end; 560 | 561 | {_From, stop} = Msg -> 562 | {Suspended0, lists:reverse(Traces), Msg, KnownPids}; 563 | 564 | _Other -> 565 | recv_all_traces(State, SrvPid, KnownPids, Suspended0, Traces, 0) 566 | 567 | after Timeout -> 568 | {Suspended0, lists:reverse(Traces), false, KnownPids} 569 | end. 570 | 571 | %% Attach an attribute list to every trace message. 572 | %% So, for example, change: 573 | %% {trace, Pid, call, MFA} 574 | %% to: 575 | %% {trace, Pid, call, MFA, [{memory,Memory}]} 576 | x(#state{memory = true}, Trace) when is_pid(element(2,Trace)) -> 577 | Memory = pinfo(?trace_pid(Trace), memory), 578 | list_to_tuple(tuple_to_list(Trace)++[[{memory,Memory}]]); 579 | x(_, Trace) -> 580 | list_to_tuple(tuple_to_list(Trace)++[[]]). 581 | 582 | 583 | maybe_add_to_known_pids(Trace, KnownPids) when element(3, Trace) == call -> 584 | ordsets:add_element(?trace_pid(Trace), KnownPids); 585 | maybe_add_to_known_pids(_, KnownPids) -> 586 | KnownPids. 587 | 588 | save_trace_p(Trace, KnownPids) 589 | when ?is_trace_send(Trace) orelse 590 | ?is_trace_receive(Trace) -> 591 | ordsets:is_element(?trace_pid(Trace), KnownPids); 592 | save_trace_p(_, _) -> 593 | true. 594 | 595 | 596 | suspend({trace,From,call,_Func}, Suspended) when node(From) == node() -> 597 | case (catch erlang:suspend_process(From, [unless_suspending, 598 | asynchronous])) of 599 | true -> 600 | [From | Suspended]; 601 | _ -> 602 | Suspended 603 | end; 604 | suspend(_Other, Suspended) -> Suspended. 605 | 606 | resume([Pid|Pids]) when node(Pid) == node() -> 607 | (catch erlang:resume_process(Pid)), 608 | resume(Pids); 609 | resume([]) -> ok. 610 | 611 | pinfo(Pid, Item) -> 612 | case process_info(Pid, Item) of 613 | {_Item, X} -> 614 | X; 615 | undefined -> 616 | '-' 617 | end. 618 | -------------------------------------------------------------------------------- /src/edbg_sup_trees.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Torbjorn Tornkvist 3 | %%% @copyright (C) 2017, Torbjorn Tornkvist 4 | %%% @doc Explore all supervision trees 5 | %%% 6 | %%% Display all supervisors we have found in our system. 7 | %%% 8 | %%% Each line has a number that can be referenced, 9 | %%% where the `S' mean that the process is a supervisor. 10 | %%% 11 | %%% ``` 12 | %%% (abc@ozzy)1> edbg:suptrees(). 13 | %%% 1:S kernel_safe_sup <0.74.0> [] 14 | %%% 2:S kernel_sup <0.49.0> [erl_distribution] 15 | %%% 23:S logger_sup <0.75.0> [] 16 | %%% 27:S net_sup <0.57.0> [] 17 | %%% 18 | %%% (h)elp e(x)pand [] (s)hrink [] 19 | %%% (p)rocess info [ []] (b)acktrace [ []] 20 | %%% (r)efresh (q)uit 21 | %%% ''' 22 | %%% 23 | %%% Note the short help text; we will now go through what those commands does. 24 | %%% 25 | %%% First let us expand supervisor 23 to see its children. Note the indentation 26 | %%% and the `G' which mean that the process is a `gen_server' process, 27 | %%% where `W' mean some other worker process. 28 | %%% 29 | %%% ``` 30 | %%% suptrees> x 23 31 | %%% 1:S kernel_safe_sup <0.74.0> [] 32 | %%% 2:S kernel_sup <0.49.0> [erl_distribution] 33 | %%% 23:S logger_sup <0.75.0> [] 34 | %%% 24:G default <0.79.0> [logger_h_common] 35 | %%% 25:G logger_proxy <0.77.0> [logger_proxy] 36 | %%% 26:G logger_handler_watcher <0.76.0> [logger_handler_watcher] 37 | %%% 27:S net_sup <0.57.0> [] 38 | %%% ''' 39 | %%% 40 | %%% Print the process-info of worker 24 Note the list of linked processes. 41 | %%% 42 | %%% ``` 43 | %%% suptrees> p 24 44 | %%% 45 | %%% === Process Info: <0.79.0> 46 | %%% [{registered_name,logger_std_h_default}, 47 | %%% {current_function,{gen_server,loop,7}}, 48 | %%% {initial_call,{proc_lib,init_p,5}}, 49 | %%% {status,waiting}, 50 | %%% {message_queue_len,0}, 51 | %%% {links,[<0.75.0>,<0.80.0>]}, 52 | %%% {dictionary, 53 | %%% [{'$ancest...snip... 54 | %%% ''' 55 | %%% 56 | %%% We can also print the process info of any linked processes. 57 | %%% Let us print the process-info of the second process in the links list. 58 | %%% 59 | %%% ``` 60 | %%% suptrees> p 24 2 61 | %%% 62 | %%% === Process Info: <0.80.0> 63 | %%% [{current_function,{logger_std_h,file_ctrl_loop,1}}, 64 | %%% {initial_call,{erlang,apply,2}}, 65 | %%% {status,waiting}, 66 | %%% {message_queue_len,0}, 67 | %%% {links,[<0.79.0>]}, 68 | %%% {diction.....snip... 69 | %%% ''' 70 | %%% 71 | %%% We can continue like this... 72 | %%% 73 | %%% ``` 74 | %%% suptrees> p 24 2 1 75 | %%% 76 | %%% === Process Info: <0.79.0> 77 | %%% [{registered_name,logger_std_h_default}, 78 | %%% {current_function,{gen_server,loop,7}}, 79 | %%% {initial_call,{proc_lib,init_p,5}}, 80 | %%% {status,waiting}, 81 | %%% {message_queue_len,0}, 82 | %%% {links,[<0.75.0>,<0.80.0>]}, 83 | %%% {dictionary,....snip... 84 | %%% ''' 85 | %%% 86 | %%% We can also print the process backtrace in the same way: 87 | %%% 88 | %%% ``` 89 | %%% suptrees> b 24 2 90 | %%% ...snip... 91 | %%% ''' 92 | %%% 93 | %%% We can also setup a monitor for a process: 94 | %%% 95 | %%% ``` 96 | %%% suptrees> m 161 2 97 | %%% 98 | %%% Monitoring: <0.343.0> 99 | %%% 100 | %%% ...do stuff, crunch... 101 | %%% 102 | %%% Monitor got DOWN from: <0.343.0> , Reason: shutdown 103 | %%% ''' 104 | %%% 105 | %%% We can print the state of a gen_server. 106 | %%% Let say we have the following: 107 | %%% 108 | %%% ``` 109 | %%% 1:S kernel_safe_sup <0.74.0> [] 110 | %%% 2:S kernel_sup <0.49.0> [erl_distribution] 111 | %%% 3:S logger_sup <0.75.0> [] 112 | %%% 4:G default <0.79.0> [logger_h_common] 113 | %%% ...snip... 114 | %%% ''' 115 | %%% 116 | %%% Now print the state of the <0.79.0> gen_server process. 117 | %%% 118 | %%% ``` 119 | %%% suptrees> g 4 120 | %%% 121 | %%% Process State: <0.79.0> 122 | %%% #{burst_limit_enable => true,burst_limit_max_count => 500, 123 | %%% burst_limit_window_time => 1000,burst_msg_count => 0, 124 | %%% ...snip.. 125 | %%% ''' 126 | %%% 127 | %%% You can start tracing on a process like this: 128 | %%% 129 | %%% ``` 130 | %%% suptrees> ts 4 131 | %%% ''' 132 | %%% 133 | %%% You stop the tracing like this: 134 | %%% 135 | %%% ``` 136 | %%% suptrees> te 137 | %%% ''' 138 | %%% 139 | %%% You show the trace output (like with {@link edbg:file/0} ) like this: 140 | %%% 141 | %%% ``` 142 | %%% suptrees> tf 143 | %%% ''' 144 | %%% 145 | %%% @end 146 | %%% Created : 14 Dec 2022 by Torbjorn Tornkvist 147 | %%%------------------------------------------------------------------- 148 | -module(edbg_sup_trees). 149 | 150 | -export([all_sup_trees/0 151 | , loop/3 152 | , start/0 153 | , supervisors/0 154 | , supervisors/1 155 | ]). 156 | 157 | 158 | -define(SERVER, ?MODULE). 159 | 160 | -ifdef(USE_COLORS). 161 | -define(info_msg(Fmt,Args), edbg_color_srv:info_msg(Fmt,Args)). 162 | -define(att_msg(Fmt,Args), edbg_color_srv:att_msg(Fmt,Args)). 163 | -define(warn_msg(Fmt,Args), edbg_color_srv:warn_msg(Fmt,Args)). 164 | -define(err_msg(Fmt,Args), edbg_color_srv:err_msg(Fmt,Args)). 165 | -define(cur_line_msg(Fmt,Args), edbg_color_srv:cur_line_msg(Fmt,Args)). 166 | -define(c_hi(Str), edbg_color_srv:c_hi(Str)). 167 | -define(c_warn(Str), edbg_color_srv:c_warn(Str)). 168 | -define(c_err(Str), edbg_color_srv:c_err(Str)). 169 | -define(help_hi(Str), edbg_color_srv:help_hi(Str)). 170 | -define(edbg_color_srv_init(), edbg_color_srv:init()). 171 | -else. 172 | -define(info_msg(Fmt,Args), io:format(Fmt,Args)). 173 | -define(att_msg(Fmt,Args), io:format(Fmt,Args)). 174 | -define(warn_msg(Fmt,Args), io:format(Fmt,Args)). 175 | -define(err_msg(Fmt,Args), io:format(Fmt,Args)). 176 | -define(cur_line_msg(Fmt,Args), io:format(Fmt,Args)). 177 | -define(c_hi(Str), Str). 178 | -define(c_warn(Str), Str). 179 | -define(c_err(Str), Str). 180 | -define(help_hi(Str), Str). 181 | -define(edbg_color_srv_init(), ok). 182 | -endif. 183 | 184 | -record(state, 185 | { 186 | sup_trees = [], 187 | trace_started = false, 188 | traced_pid 189 | }). 190 | 191 | %% Regarding the 'modules' entry: 192 | %% It is used by the release handler during code replacement to determine 193 | %% which processes are using a certain module. As a rule of thumb, 194 | %% if the child process is a supervisor, gen_server or, gen_statem, 195 | %% this is to be a list with one element [Module], where Module is 196 | %% the callback module. If the child process is an event manager 197 | %% (gen_event) with a dynamic set of callback modules, 198 | %% value dynamic must be used. 199 | -record(sup_tree, 200 | {n = 0, % enumeration number 201 | id, 202 | pid, 203 | modules = [], 204 | children = [], 205 | expand = false 206 | }). 207 | 208 | -record(worker, 209 | {n = 0, % enumeration number 210 | id, 211 | pid, 212 | modules = [], 213 | is_gen_server = false, 214 | gen_module 215 | }). 216 | 217 | %% @doc Enter the Supervisor Tree Browser. 218 | start() -> 219 | Self = self(), 220 | Prompt = spawn_link(fun() -> prompt(Self) end), 221 | Prompt ! show, 222 | ploop(Prompt). 223 | 224 | ploop(Prompt) -> 225 | receive 226 | {'EXIT', Prompt, _} -> 227 | true; 228 | 229 | quit -> 230 | true; 231 | 232 | _ -> 233 | ?MODULE:ploop(Prompt) 234 | end. 235 | 236 | prompt(Pid) when is_pid(Pid) -> 237 | process_flag(trap_exit, true), 238 | State0 = refresh(), 239 | State = help(show(State0)), 240 | loop(Pid, prompt(), State). 241 | 242 | 243 | %% @private 244 | loop(Pid, Prompt, State0) -> 245 | io:format("~n",[]), 246 | State = 247 | case string:tokens(b2l(io:get_line(Prompt)), "\n") of 248 | ["h"++X] -> help(X, State0); 249 | ["x"++X] -> help(expand(X, State0)); 250 | ["s"++X] -> shrink_or_show(X, State0); 251 | ["p"++X] -> pinfo(X, State0); 252 | ["g"++X] -> gen_state(X, State0); 253 | ["b"++X] -> backtrace(X, State0); 254 | ["m"++X] -> setup_monitor(X, State0); 255 | ["r"++_] -> help(show(refresh())); 256 | ["ts"++X] -> start_trace(X, State0); 257 | ["te"++_] -> stop_trace(State0); 258 | ["tf"++_] -> show(show_trace(State0)); 259 | ["q"++_] -> Pid ! quit, exit(normal); 260 | 261 | _X -> 262 | ?info_msg("prompt got: ~p~n",[_X]), 263 | State0 264 | end, 265 | ?MODULE:loop(Pid, Prompt, State). 266 | 267 | 268 | b2l(B) when is_binary(B) -> 269 | erlang:binary_to_list(B); 270 | b2l(L) when is_list(L) -> 271 | L. 272 | 273 | 274 | refresh() -> 275 | #state{sup_trees = enumerate(all_sup_trees())}. 276 | 277 | prompt() -> 278 | "suptrees> ". 279 | 280 | 281 | show(State) -> 282 | show("", State). 283 | 284 | show(Chars, State) -> 285 | display(Chars, State#state.sup_trees, 0), 286 | State. 287 | 288 | display(Chars, 289 | [#sup_tree{n = N, 290 | id = Id, 291 | pid = Pid, 292 | modules = Modules, 293 | expand = false}|Tail], 294 | Indent) -> 295 | Pad = indent(Indent), 296 | io:format("~p:~s ~s~p ~p ~p~n", [N, ?c_err("S"), Pad, Id, Pid, Modules]), 297 | display(Chars, Tail, Indent); 298 | display(Chars, 299 | [#sup_tree{n = N, 300 | id = Id, 301 | pid = Pid, 302 | modules = Modules, 303 | children = Children, 304 | expand = true}|Tail], 305 | Indent) -> 306 | Pad = indent(Indent), 307 | io:format("~p:~s ~s~p ~p ~p~n", [N, ?c_err("S"), Pad, Id, Pid, Modules]), 308 | display(Chars, Children, Indent+2), 309 | display(Chars, Tail, Indent); 310 | display(Chars, [#worker{n = N, 311 | id = Id, 312 | pid = Pid, 313 | is_gen_server = IsGenSrv, 314 | modules = Modules}|Tail], Indent) -> 315 | Pad = indent(Indent), 316 | io:format("~p:~s ~s~p ~p ~p~n", [N, ?c_warn(wg(IsGenSrv)), Pad, Id, Pid, Modules]), 317 | display(Chars, Tail, Indent); 318 | display(_, [], _) -> 319 | ok. 320 | 321 | wg(true = _IsGenSrv) -> "G"; 322 | wg(_) -> "W". 323 | 324 | 325 | indent(N) -> 326 | string:copies(" ", N). 327 | 328 | help(State) -> 329 | print_help(), 330 | State. 331 | 332 | help(_, State) -> 333 | print_help(), 334 | State. 335 | 336 | 337 | get_pid(#sup_tree{pid = Pid}) -> Pid; 338 | get_pid(#worker{pid = Pid}) -> Pid; 339 | get_pid(Pid) when is_pid(Pid) -> Pid. 340 | 341 | 342 | gen_state(Chars, #state{sup_trees = SupTrees} = State) -> 343 | try 344 | case parse_ints(Chars) of 345 | [I] -> 346 | do(SupTrees, I, fun(X) -> do_pinfo(X, fun p_gen_state/1) end), 347 | State; 348 | [I | Ints] -> 349 | do(SupTrees, I, fun(X) -> do_linfo(Ints, X, fun p_gen_state/1) end), 350 | State 351 | end 352 | catch 353 | _:_ -> 354 | show(State) 355 | end. 356 | 357 | %% Print the State reord of the callback module that 358 | %% the gen_server is holding. Try to pretty-print it 359 | %% if possible. 360 | p_gen_state(Pid) when is_pid(Pid) -> 361 | try 362 | State = sys:get_state(Pid, 100), 363 | UsePpRecord = is_pp_record_available(), 364 | print_gen_state(Pid, State, UsePpRecord) 365 | catch 366 | _:_ -> 367 | ok 368 | end; 369 | p_gen_state(_) -> 370 | ok. 371 | 372 | print_gen_state(Pid, State, false = _UsePpRecord) -> 373 | io:format("~n~s ~p~n~p~n", [?c_warn("Process State:"),Pid, State]); 374 | print_gen_state(Pid, State, true = _UsePpRecord) -> 375 | try 376 | {ok, {Mod,_,_}} = get_initial_call(Pid), 377 | Fname = edbg:find_source(Mod), 378 | {ok, Defs} = pp_record:read(Fname), 379 | io:format("~n~s~n", [pp_record:print(State, Defs)]) 380 | catch 381 | _:_ -> 382 | print_gen_state(Pid, State, false) 383 | end. 384 | 385 | is_pp_record_available() -> 386 | case code:ensure_loaded(pp_record) of 387 | {module, pp_record} -> 388 | true; 389 | _ -> 390 | false 391 | end. 392 | 393 | 394 | start_trace(Chars, #state{trace_started = false, sup_trees = SupTrees} = State) -> 395 | try 396 | case parse_ints(Chars) of 397 | [I] -> 398 | do(SupTrees, I, fun(X) -> do_pinfo(X, fun do_trace/1) end), 399 | store_traced_pid(State); 400 | [I | Ints] -> 401 | do(SupTrees, I, fun(X) -> do_linfo(Ints, X, fun do_trace/1) end), 402 | store_traced_pid(State) 403 | end 404 | catch 405 | _:_ -> 406 | State 407 | end; 408 | start_trace(_Chars, #state{trace_started = true, traced_pid = Pid} = State) -> 409 | io:format("~n~s ~s ~p~n",[?c_err(""),"Already tracing on Pid:",Pid]), 410 | State. 411 | 412 | store_traced_pid(State) -> 413 | case get(traced_pid) of 414 | Pid when is_pid(Pid) -> 415 | State#state{trace_started = true, traced_pid = Pid}; 416 | _ -> 417 | State 418 | end. 419 | 420 | do_trace(Pid) when is_pid(Pid) -> 421 | edbg:fstart([], [{trace_spec,Pid}, 422 | dump_output_eager, 423 | send_receive, 424 | {max_msgs, 1000000}]), 425 | put(traced_pid, Pid), 426 | io:format("~n~s ~s ~p~n",[?c_warn(""),"Tracing on Pid:",Pid]). 427 | 428 | 429 | stop_trace(#state{trace_started = true, traced_pid = Pid} = State) -> 430 | edbg:fstop(), 431 | io:format("~n~s ~s ~p~n",[?c_warn(""),"Stopped tracing on Pid:",Pid]), 432 | State#state{trace_started = false, traced_pid = undefined}; 433 | stop_trace(State) -> 434 | %% In case suptree was stopped/started and 435 | %% perhaps even the shells proc.dict was erased; 436 | Pid = case erase(traced_pid) of 437 | Pid0 when is_pid(Pid0) -> Pid0; 438 | _ -> 439 | edbg_tracer:get_traced_pid() 440 | end, 441 | edbg:fstop(), 442 | io:format("~n~s ~s ~p~n",[?c_warn(""),"Stopped tracing on Pid:",Pid]), 443 | State#state{trace_started = false, traced_pid = undefined}. 444 | 445 | show_trace(State) -> 446 | edbg:file(), 447 | State. 448 | 449 | 450 | setup_monitor(Chars, #state{sup_trees = SupTrees} = State) -> 451 | try 452 | case parse_ints(Chars) of 453 | [I] -> 454 | do(SupTrees, I, fun(X) -> do_pinfo(X, fun create_monitor/1) end), 455 | State; 456 | [I | Ints] -> 457 | do(SupTrees, I, fun(X) -> do_linfo(Ints, X, fun create_monitor/1) end), 458 | State 459 | end 460 | catch 461 | _:_ -> 462 | show(State) 463 | end. 464 | 465 | create_monitor(Pid) when is_pid(Pid) -> 466 | F = fun() -> 467 | ReqId = erlang:monitor(process, Pid), 468 | io:format("~n~s ~s ~p~n",[?c_warn(""),"Monitoring:",Pid]), 469 | receive 470 | {'DOWN', ReqId, process, Pid, ExitReason} -> 471 | io:format("~n~s ~s ~p , Reason: ~p~n", 472 | [?c_warn(""),"Monitor got DOWN from:", 473 | Pid, ExitReason]) 474 | end 475 | end, 476 | erlang:spawn(F); 477 | create_monitor(_) -> 478 | ok. 479 | 480 | 481 | 482 | pinfo(Chars, #state{sup_trees = SupTrees} = State) -> 483 | try 484 | case parse_ints(Chars) of 485 | [I] -> 486 | do(SupTrees, I, fun(X) -> do_pinfo(X, fun out_pinfo/1) end), 487 | State; 488 | [I | Ints] -> 489 | do(SupTrees, I, fun(X) -> do_linfo(Ints, X, fun out_pinfo/1) end), 490 | State 491 | end 492 | catch 493 | _:_ -> 494 | show(State) 495 | end. 496 | 497 | backtrace(Chars, #state{sup_trees = SupTrees} = State) -> 498 | try 499 | case parse_ints(Chars) of 500 | [I] -> 501 | do(SupTrees, I, fun(X) -> do_pinfo(X, fun out_btrace/1) end), 502 | State; 503 | [I | Ints] -> 504 | do(SupTrees, I, fun(X) -> do_linfo(Ints, X, fun out_btrace/1) end), 505 | State 506 | end 507 | catch 508 | _:_ -> 509 | show(State) 510 | end. 511 | 512 | 513 | 514 | 515 | do_pinfo(X, OutFun) -> 516 | Pid = get_pid(X), 517 | OutFun(Pid), 518 | X. 519 | 520 | do_linfo([I], X, OutFun) -> 521 | try 522 | Pid = get_pid(X), 523 | {links, Pids} = erlang:process_info(Pid, links), 524 | LPid = lists:nth(I, Pids), 525 | OutFun(LPid), 526 | X 527 | catch 528 | _:_ -> X 529 | end; 530 | do_linfo([H|T], X, OutFun) -> 531 | try 532 | Pid = get_pid(X), 533 | {links, Pids} = erlang:process_info(Pid, links), 534 | LPid = lists:nth(H, Pids), 535 | do_linfo(T, LPid, OutFun) 536 | catch 537 | _:_ -> X 538 | end; 539 | do_linfo([], X, _OutFun) -> 540 | X. 541 | 542 | out_pinfo(Pid) when is_pid(Pid) -> 543 | io:format("~n=== Process Info: ~p~n~p~n", [Pid,erlang:process_info(Pid)]). 544 | 545 | out_btrace(Pid) when is_pid(Pid) -> 546 | io:format("~n=== Process Backtrace: ~p~n", [Pid]), 547 | c:bt(Pid). 548 | 549 | expand(Chars, #state{sup_trees = SupTrees0} = State) -> 550 | try 551 | I = parse_int(Chars), 552 | SupTrees = set_expand(SupTrees0, I, true), 553 | show(State#state{sup_trees = SupTrees}) 554 | catch 555 | _:_ -> 556 | help(show(State)) 557 | end. 558 | 559 | shrink_or_show(Chars, #state{sup_trees = SupTrees0} = State) -> 560 | try 561 | I = parse_int(Chars), 562 | SupTrees = set_expand(SupTrees0, I, false), 563 | help(show(State#state{sup_trees = SupTrees})) 564 | catch 565 | _:_ -> 566 | help(show(State)) 567 | end. 568 | 569 | 570 | enumerate(SupTrees0) -> 571 | {SupTrees, _MaxN} = enumerate(SupTrees0, 1), 572 | SupTrees. 573 | 574 | enumerate([#sup_tree{children = Children0} = H | Tail0], N) -> 575 | {Children, N1} = enumerate(Children0, N+1), 576 | {Tail, N2} = enumerate(Tail0, N1), 577 | {[H#sup_tree{n = N, children = Children} | Tail], N2}; 578 | enumerate([#worker{} = H | Tail0], N) -> 579 | {Tail, N1} = enumerate(Tail0, N+1), 580 | {[H#worker{n = N} | Tail], N1}; 581 | enumerate([], N) -> 582 | {[],N}. 583 | 584 | 585 | 586 | 587 | 588 | set_expand([#sup_tree{n = N} = H | T], N, Bool) -> 589 | [H#sup_tree{expand = Bool} | T]; 590 | set_expand([#sup_tree{n = N1, children = Children} = H1, 591 | #sup_tree{n = N2} = H2 | T], I, Bool) 592 | when N1I-> 593 | [H1#sup_tree{children = set_expand(Children, I, Bool)},H2|T]; 594 | set_expand([#sup_tree{n = N, children = Children} = H], I, Bool) when N 595 | [H#sup_tree{children = set_expand(Children, I, Bool)}]; 596 | set_expand([H|T], N, Bool) -> 597 | [H | set_expand(T, N, Bool)]; 598 | set_expand([], _, _) -> 599 | []. 600 | 601 | 602 | do([#sup_tree{n = N} = H | T], N, Fun) -> 603 | [Fun(H) | T]; 604 | do([#sup_tree{n = N1, children = Children} = H1, 605 | #sup_tree{n = N2} = H2 | T], I, Fun) 606 | when N1I-> 607 | [H1#sup_tree{children = do(Children, I, Fun)},H2|T]; 608 | do([#sup_tree{n = N, children = Children} = H], I, Fun) when N 609 | [H#sup_tree{children = do(Children, I, Fun)}]; 610 | do([#worker{n = N} = H | T], N, Fun) -> 611 | [Fun(H) | T]; 612 | do([H|T], N, Fun) -> 613 | [H | do(T, N, Fun)]; 614 | do([], _, _) -> 615 | []. 616 | 617 | 618 | parse_int(Chars) -> 619 | try string:tokens(string:strip(Chars), " ") of 620 | [X] -> list_to_integer(X); 621 | _ -> throw(parse_int) 622 | catch 623 | _:_ -> 624 | throw(parse_int) 625 | end. 626 | 627 | parse_ints(Chars) -> 628 | try 629 | [list_to_integer(X) || 630 | X <- string:tokens(string:strip(Chars), " ")] 631 | catch 632 | _:_ -> 633 | throw(parse_int) 634 | end. 635 | 636 | 637 | 638 | print_help() -> 639 | S1 = " (h)elp e(x)pand [] (s)hrink [] (s)how", 640 | S2 = " (p)rocess info [ ]+ (b)acktrace [ ]+", 641 | S3 = " (m)onitor [ ]+ (g)en-state [ ]+", 642 | S4 = " (ts)start-trace [ ]+ (te)end-trace (tf)show-trace", 643 | S5 = " (r)efresh (q)uit", 644 | S = io_lib:format("~n~s~n~s~n~s~n~s~n~s~n",[S1,S2,S3,S4,S5]), 645 | ?info_msg(?help_hi(S), []). 646 | 647 | 648 | %% @private 649 | supervisors() -> 650 | supervisors(kernel_sup). 651 | 652 | %% @private 653 | supervisors(Supervisor) when is_atom(Supervisor) -> 654 | supervisors(Supervisor, new_sup_tree(Supervisor)). 655 | 656 | %% supervisors(user = Supervisor, SupTree) -> % eternal loop? 657 | %% SupTree; 658 | %% supervisors(standard_error = Supervisor, SupTree) -> % eternal loop? 659 | %% SupTree; 660 | supervisors(_Supervisor, SupTree) -> 661 | case supervisor:which_children(SupTree#sup_tree.pid) of 662 | Children when is_list(Children) -> 663 | lists:foldl(fun({Id, Pid, Type, Modules}, AccTree) 664 | when (Type == supervisor) andalso is_pid(Pid) -> 665 | Cs = AccTree#sup_tree.children, 666 | AccTree#sup_tree{modules = Modules, 667 | children = 668 | Cs ++ 669 | [supervisors(Id, new_sup_tree(Id, Pid))]}; 670 | ({Id, Pid, Type, Modules}, AccTree) when Type == worker -> 671 | {IsGenSrv, Gmod} = is_gen_server(Pid), 672 | Entry = #worker{id = Id, pid = Pid, 673 | is_gen_server = IsGenSrv, 674 | gen_module = Gmod, 675 | modules = Modules}, 676 | Cs = AccTree#sup_tree.children, 677 | AccTree#sup_tree{children = Cs ++ [Entry]}; 678 | (Entry, AccTree) -> 679 | ?warn_msg("Ignoring: ~p~n",[Entry]), 680 | AccTree 681 | end, SupTree, Children); 682 | _ -> 683 | SupTree 684 | end. 685 | 686 | %% Is the given process of type: gen_server, gen_statem or gen_event? 687 | %% We will use this info to enable extration of the State using 688 | %% the sys:get_state/2 function 689 | is_gen_server(Pid) when is_pid(Pid) -> 690 | case erlang:process_info(Pid, current_function) of 691 | {current_function, {M , _Fun, _NoOfArgs}} 692 | when M == gen_server orelse 693 | M == gen_statem orelse 694 | M == gen_event -> 695 | {true, M}; 696 | _ -> 697 | {false, undefined} 698 | end; 699 | is_gen_server(_) -> 700 | {false, undefined}. 701 | 702 | 703 | %% Try to find all Supervisors in the system. 704 | %% Idea: Go through all registered processes and 705 | %% try to figure out if they are a supervisor. 706 | %% Is there a better way? 707 | %% Can a supervisor not be registered and hence not be included here? 708 | %% @private 709 | all_sup_trees() -> 710 | Sups = lists:foldr( 711 | fun(Id,Acc) -> 712 | case get_initial_call(whereis(Id)) of 713 | {ok, {supervisor,_,_}} -> 714 | [Id|Acc]; 715 | _ -> 716 | Acc 717 | end 718 | end, [], lists:sort(erlang:registered())), 719 | [supervisors(Id) || Id <- Sups]. 720 | 721 | 722 | get_initial_call(Pid) when is_pid(Pid) -> 723 | case process_info(Pid, dictionary) of 724 | {dictionary,L} -> 725 | case lists:keyfind('$initial_call', 1, L) of 726 | {'$initial_call', Icall} -> 727 | {ok, Icall}; 728 | _ -> 729 | false 730 | end; 731 | _ -> 732 | false 733 | end; 734 | get_initial_call(_) -> 735 | false. 736 | 737 | 738 | 739 | new_sup_tree(SupervisorId) -> 740 | new_sup_tree(SupervisorId, erlang:whereis(SupervisorId)). 741 | 742 | new_sup_tree(SupervisorId, Pid) -> 743 | #sup_tree{id = SupervisorId, 744 | pid = Pid}. 745 | -------------------------------------------------------------------------------- /src/edbg.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Torbjorn Tornkvist 3 | %%% @copyright (C) 2017, Torbjorn Tornkvist 4 | %%% @doc edbg - Erlang/Elixir trace and debug tool 5 | %%% 6 | %%% `edbg' is a tty based interface to the Erlang debugger/tracer 7 | %%% and the supervisor trees. 8 | %%% 9 | %%% This module is the main interface to `edbg'. 10 | %%% 11 | %%% Note that most of the functions here relates to the debugger 12 | %%% functionality. 13 | %%% 14 | %%% For tracing you only need the functions: {@link fstart/2} , 15 | %%% {@link fstop/0}, {@link file/0}, {@link xfile/0} 16 | %%% (and possibly some variants of the argument number). 17 | %%% 18 | %%% For the Supervisor Tree Browser, you only need: 19 | %%% {@link suptrees/0}. 20 | %%% 21 | %%% @end 22 | %%% Created : 4 Sep 2017 by Torbjorn Tornkvist 23 | %%%------------------------------------------------------------------- 24 | -module(edbg). 25 | 26 | -on_load(init_edbg/0). 27 | 28 | -export([a/0, 29 | a/1, 30 | a/3, 31 | attach/1, 32 | attach/3, 33 | b/2, 34 | br/0, 35 | br/1, 36 | break/2, 37 | breaks/0, 38 | breaks/1, 39 | break_in/3, 40 | c/1, 41 | c/3, 42 | continue/1, 43 | continue/3, 44 | delete_breaks/0, 45 | delete_break/2, 46 | bdel/0, 47 | bdel/2, 48 | disable_breaks/0, 49 | disable_break/2, 50 | boff/0, 51 | boff/2, 52 | enable_breaks/0, 53 | enable_break/2, 54 | file/0, 55 | file/1, 56 | fhelp/0, 57 | fpid/1, 58 | fpid/2, 59 | fstart/0, 60 | fstart/1, 61 | fstart/2, 62 | fstop/0, 63 | bon/0, 64 | bon/2, 65 | f/1, 66 | f/3, 67 | finish/1, 68 | finish/3, 69 | i/1, 70 | i/2, 71 | id/0, 72 | it/3, 73 | lab/0, 74 | load_all_breakpoints/0, 75 | ml/1, 76 | ml/2, 77 | ml/3, 78 | mlist/1, 79 | mlist/2, 80 | mlist/3, 81 | pl/0, 82 | plist/0, 83 | pp_tree/1, 84 | pp_tree/2, 85 | pp_tree/3, 86 | n/1, 87 | n/3, 88 | next/1, 89 | next/3, 90 | rmi/0, 91 | rmi/1, 92 | sab/0, 93 | save_all_breakpoints/0, 94 | s/1, 95 | s/3, 96 | step/1, 97 | step/3, 98 | suptrees/0, 99 | t/2, 100 | t/3, 101 | lts/0, 102 | xfile/0, 103 | xfile/1 104 | ]). 105 | 106 | %% Internal export 107 | -export([aloop/1, 108 | ploop/3, 109 | it_loop/1, 110 | itest_at_break/1, 111 | find_source/1 112 | ]). 113 | 114 | -ifndef(ELIXIR). 115 | -ifdef(USE_COLORS). 116 | -define(info_msg(Fmt,Args), edbg_color_srv:info_msg(Fmt,Args)). 117 | -define(att_msg(Fmt,Args), edbg_color_srv:att_msg(Fmt,Args)). 118 | -define(warn_msg(Fmt,Args), edbg_color_srv:warn_msg(Fmt,Args)). 119 | -define(err_msg(Fmt,Args), edbg_color_srv:err_msg(Fmt,Args)). 120 | -define(cur_line_msg(Fmt,Args), edbg_color_srv:cur_line_msg(Fmt,Args)). 121 | -define(c_hi(Str), edbg_color_srv:c_hi(Str)). 122 | -define(c_warn(Str), edbg_color_srv:c_warn(Str)). 123 | -define(c_err(Str), edbg_color_srv:c_err(Str)). 124 | -define(help_hi(Str), edbg_color_srv:help_hi(Str)). 125 | -define(edbg_color_srv_init(), edbg_color_srv:init()). 126 | -else. 127 | -define(info_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 128 | -define(att_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 129 | -define(warn_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 130 | -define(err_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 131 | -define(cur_line_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 132 | -define(c_hi(Str), Str). 133 | -define(c_warn(Str), Str). 134 | -define(c_err(Str), Str). 135 | -define(help_hi(Str), Str). 136 | -define(edbg_color_srv_init(), ok). 137 | -endif. 138 | -else. 139 | -define(info_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 140 | -define(att_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 141 | -define(warn_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 142 | -define(err_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 143 | -define(cur_line_msg(Fmt,Args), io:format(lists:flatten(Fmt),Args)). 144 | -define(c_hi(Str), Str). 145 | -define(c_warn(Str), Str). 146 | -define(c_err(Str), Str). 147 | -define(help_hi(Str), Str). 148 | -define(edbg_color_srv_init(), ok). 149 | -endif. 150 | 151 | 152 | %% @doc Prints the process tree starting from the given node. 153 | %% @param Node The starting node (pid or port) for the tree. 154 | %% @end 155 | pp_tree(Pid) -> 156 | edbg_pp_tree:print(Pid). 157 | 158 | %% @doc Prints the process tree with additional information. 159 | %% @param Node The starting node (pid or port) for the tree. 160 | %% @param Xinfo List of additional process information to display. 161 | %% @end 162 | pp_tree(Pid, Options) -> 163 | edbg_pp_tree:print(Pid, Options). 164 | 165 | %% @doc Prints the process tree with a maximum depth and additional information. 166 | %% @param Node The starting node (pid or port) for the tree. 167 | %% @param MaxDepth The maximum depth to traverse in the tree. 168 | %% @param Xinfo List of additional process information to display. 169 | %% @return {ok, TotalNodes} where TotalNodes is the number of nodes printed. 170 | %% @end 171 | pp_tree(Pid, MaxDepth, Options) -> 172 | edbg_pp_tree:print(Pid, MaxDepth, Options). 173 | 174 | %% @private 175 | init_edbg() -> 176 | ok = ?edbg_color_srv_init(). 177 | 178 | %% @private 179 | lts() -> 180 | edbg_tracer:lts(). 181 | 182 | %% @doc As 'file/1' but uses the default trace output filename. 183 | file() -> 184 | edbg_tracer:file(). 185 | 186 | %% @doc Load the trace output from the file 'Fname'. 187 | %% 188 | %% When the file is loaded, enter the trace list mode. 189 | %% @end 190 | file(Fname) -> 191 | edbg_tracer:file(Fname). 192 | 193 | %% @doc As 'file/0' but hint that Elixir code is traced. 194 | xfile() -> 195 | edbg_tracer:xfile(). 196 | 197 | %% @doc As 'file/1' but hint that Elixir code is traced. 198 | xfile(Fname) -> 199 | edbg_tracer:xfile(Fname). 200 | 201 | %% @doc Start tracing making use of a previously stored configuration. 202 | %% 203 | %% A previous call to 'fstart/2' will store the configuration 204 | %% on disk so that it can be resued when calling this function. 205 | %% 206 | %% @end 207 | fstart() -> 208 | edbg_tracer:fstart(). 209 | 210 | %% @doc Start tracing making use of a previously stored configuration. 211 | %% 212 | %% A previous call to 'fstart/2' will store the configuration 213 | %% on disk so that it can be resued when calling this function. 214 | %% 215 | %% @end 216 | fstart(ModFunList) -> 217 | edbg_tracer:fstart(ModFunList). 218 | 219 | %% @doc Start tracing to file. 220 | %% 221 | %% 'ModFunList' is a list of module names (atoms) 222 | %% or tuples {ModuleName, FunctionName}. This makes it possible to trace 223 | %% on all functions within a Module, or just a few functions within a Module. 224 | %% 225 | %% 'Opts' is a list of option tuples: 226 | %% 227 | %%
    228 | %%
  • {log_file, FileName} : file where to store trace output; default: 'edbg.trace_result'
  • 229 | %%
  • {cfg_file, FileName} : file where to store the config; default: 'edbg_trace.config'
  • 230 | %%
  • {max_msgs, MaxNumOfMsgs} : max number of trace messages; default = 1000
  • 231 | %%
  • {trace_time, Seconds} : max time to trace; default = 10 seconds
  • 232 | %%
  • {trace_spec, Spec} : see the erlang:trace/3 docs; default = all
  • 233 | %%
  • dump_output_eager : trace output goes to file often
  • 234 | %%
  • dump_output_lazy : trace output goes to file not so often (default)
  • 235 | %%
  • monotonic_ts : show the elapsed monotonic nano seconds
  • 236 | %%
  • send_receive : trace send/receive messages from 'known' pids
  • 237 | %%
  • memory : track the memory usage of the 'known' pids
  • 238 | %%
  • set_on_spawn : any process created by a traced process inherit its trace flags
  • 239 | %%
  • set_on_first_spawn : the first process created by a traced process inherit its trace flags
  • 240 | %%
  • set_on_link : any process linked by a traced process inherit its trace flags
  • 241 | %%
  • set_on_first_link : the first process linked by a traced process inherit its trace flags
  • 242 | %%
243 | %% 244 | %% Tracing in an Erlang node is setup by the 'erlang:trace/3' and 245 | %% 'erlang:trace_pattern/3' BIF's. The generated trace output in 246 | %% a production system can quickly produce a staggering amount of 247 | %% data, which easily can swamp the whole system, so that it becomes 248 | %% unusable. 249 | %% 250 | %% It is therefore important to restrict what to trace, the amount of 251 | %% generated trace messages and the maximum time we allow tracing to go on. 252 | %% 'edbg' helps you with this but you can still brake 253 | %% your system if you are careless setting the trace parameters. 254 | %% 255 | %% With the `log_file` you can override the default name of the file 256 | %% where the trace output should be stored. This can be necessary if 257 | %% you want to specify a certain location (e.g a r/w directory/file of 258 | %% an Elixir/Nerves device). For the same reason you can specify what 259 | %% file the 'cfg_file' should point to, or simply turn off the config 260 | %% file completely by setting it to `false'. 261 | %% 262 | %% With the `max_msgs' and `trace_time' parameters you can 263 | %% restrict the amount of generated trace messages and running time 264 | %% before stopping the tracing. 265 | %% 266 | %% The `trace_spec' is also a way of restricting what to trace on. 267 | %% Default is `all', but for later OTP versions (> 18.3): `processes' 268 | %% is available and would be more specific. 269 | %% For more info about this, see the 'erlang:trace/3' documentation. 270 | %% 271 | %% With the `dump_output_lazy' switch set, trace output goes to file not 272 | %% until the tracer is stopped (e.g by calling the 'file/1' function), or 273 | %% that a limiting filter such as `max_msg' or `trace_time' is reached. 274 | %% This is the default. 275 | %% 276 | %% With the `dump_output_eager' switch set, trace output goes to file often 277 | %% which may be necessary if you run edbg tracing and the system unexpectedly 278 | %% shuts down. 279 | %% 280 | %% With the `monotonic_ts' switch set, each trace message will have a 281 | %% monotonic timestamp, in nanoseconds, attached to it. This will be displayed 282 | %% in the call graph as the elapsed time counted from the first received 283 | %% trace message. 284 | %% 285 | %% With the `send_receive' switch set, we will also trace messages sent and 286 | %% received by 'known' pids. By 'known' pids we mean processes that we have 287 | %% seen earlier in a traced call. The reason for this is to avoid beig swamped 288 | %% by all the messages that the trace BIF is sending us. Note that we still 289 | %% may get a lots of messages that will cause the resulting trace file to be 290 | %% large and make the handling of it slower. The display of sent and received 291 | %% messages can be toggled on/off from the trace command prompt, see also the 292 | %% trace examples. 293 | %% 294 | %% With the `memory' switch set, we will also track the memory usage of 295 | %% the processes that we get trace messages for. The memory size shown 296 | %% is the size in bytes of the process. This includes call stack, heap, 297 | %% and internal structures, as what we get from the process_info(Pid, memory) 298 | %% BIF. 299 | %% 300 | %% NOTE: we run the process_info/2 BIF when we receive the 301 | %% trace message from the BEAM engine so the memory size we present does 302 | %% not exactly represent the state of the process at the creation of the 303 | %% trace message. 304 | %% 305 | %% The `set_on_XXX' options probably works best together with the {@link fpid/2} 306 | %% function. 307 | %% 308 | %% 309 | %%``` 310 | %% % Example, trace calls to the foo module, no more than 1000 trace msgs 311 | %% edbg:fstart([foo], [{max_msgs, 1000}]). 312 | %%''' 313 | %% 314 | %% 315 | %%``` 316 | %% % Example, trace all modules in a particular process, 317 | %% % dump the traced output to file often, 318 | %% % no more than 1000 trace msgs. 319 | %% edbg:fstart([], [{trace_spec,Pid}, dump_output_eager, {max_msgs, 1000}]). 320 | %% ''' 321 | %% 322 | %% @end 323 | fstart(ModFunList, Options) -> 324 | edbg_tracer:fstart(ModFunList, Options). 325 | 326 | %% @doc Stop tracing and dump the trace output to file. 327 | fstop() -> 328 | edbg_tracer:fstop(). 329 | 330 | %% @doc Start tracing the process: 'Pid'. 331 | %% 332 | %% A qick way of start tracing a process. 333 | %% 334 | %% The Options are set to be: 335 | %% 336 | %% ``` 337 | %% [dump_output_eager, 338 | %% send_receive, 339 | %% {max_msgs, 1000000}] 340 | %% ''' 341 | %% 342 | %% @end 343 | fpid(Pid) when is_pid(Pid) -> 344 | fpid(Pid, [dump_output_eager, 345 | send_receive, 346 | {max_msgs, 1000000}]). 347 | 348 | %% @doc Start tracing the process: 'Pid'. 349 | %% 350 | %% Start tracing the process. The Options are the same as 351 | %% for the 'fstart/2' function. 352 | %% 353 | %% NOTE: The `set_on_XXXX' options can be very useful here. 354 | %% See also {@link fstart/2} 355 | %% 356 | %% @end 357 | fpid(Pid, Options) when is_pid(Pid) andalso is_list(Options) -> 358 | edbg:fstart([], [{trace_spec,Pid} | Options]), 359 | io:format("~n~s ~s ~p~n",[?c_warn(""),"Tracing on Pid:",Pid]). 360 | 361 | %% @doc Display a short help text. 362 | fhelp() -> 363 | edbg_tracer:fhelp(). 364 | 365 | %% @doc Enter the Supervisor Tree Browser. 366 | %% @see edbg_sup_trees 367 | suptrees() -> 368 | edbg_sup_trees:start(). 369 | 370 | 371 | %% @doc Show all interpreted processes. 372 | pl() -> 373 | plist(). 374 | 375 | %% @doc As 'mlist/1'. 376 | ml(X) -> mlist(X). 377 | %% @doc As 'mlist/2'. 378 | ml(X,Y) -> mlist(X,Y). 379 | %% @doc As 'mlist/3'. 380 | ml(X,Y,Z) -> mlist(X,Y,Z). 381 | 382 | %% @doc Display all break points. 383 | br() -> 384 | breaks(). 385 | %% @doc Display all break points. 386 | breaks() -> 387 | print_all_breakpoints(int:all_breaks()). 388 | 389 | %% @doc Display all break points for module 'Mod'. 390 | br(Mod) -> 391 | breaks(Mod). 392 | %% @doc Display all break points for module 'Mod'. 393 | breaks(Mod) -> 394 | print_all_breakpoints(int:all_breaks(Mod)). 395 | 396 | %% @doc Set a break point in 'Mod' at line 'Line'. 397 | b(Mod, Line) -> 398 | break(Mod,Line). 399 | 400 | %% @doc Set a break point in 'Mod' at line 'Line'. 401 | break(Mod, Line) -> 402 | ok = int:break(Mod, Line), 403 | save_all_breakpoints(), 404 | ok. 405 | 406 | %% @doc Delete all break points. 407 | bdel() -> 408 | delete_breaks(). 409 | 410 | %% @doc Delete all break points. 411 | delete_breaks() -> 412 | [delete_break(Mod, Line) || {{Mod, Line},_} <- int:all_breaks()]. 413 | 414 | %% @doc Delete the break point in 'Mod' on line 'Line'. 415 | bdel(Mod, Line) -> 416 | delete_break(Mod, Line). 417 | %% @doc Delete the break point in 'Mod' on line 'Line'. 418 | delete_break(Mod, Line) -> 419 | ok = int:delete_break(Mod, Line), 420 | save_all_breakpoints(), 421 | ok. 422 | 423 | %% @doc Disable all break points. 424 | boff() -> 425 | disable_breaks(). 426 | %% @doc Disable all break points. 427 | disable_breaks() -> 428 | [disable_break(Mod, Line) || {{Mod, Line},_} <- int:all_breaks()]. 429 | 430 | %% @doc Disable the break point in 'Mod' on line 'Line'. 431 | boff(Mod, Line) -> 432 | disable_break(Mod, Line). 433 | %% @doc Disable the break point in 'Mod' on line 'Line'. 434 | disable_break(Mod, Line) -> 435 | ok = int:disable_break(Mod, Line), 436 | save_all_breakpoints(), 437 | ok. 438 | 439 | 440 | %% @doc Disable all break points. 441 | bon() -> 442 | enable_breaks(). 443 | %% @doc Disable all break points. 444 | enable_breaks() -> 445 | [enable_break(Mod, Line) || {{Mod, Line},_} <- int:all_breaks()]. 446 | 447 | %% @doc Enable the break point in 'Mod' on line 'Line'. 448 | bon(Mod, Line) -> 449 | enable_break(Mod, Line). 450 | %% @doc Enable the break point in 'Mod' on line 'Line'. 451 | enable_break(Mod, Line) -> 452 | ok = int:enable_break(Mod, Line), 453 | save_all_breakpoints(), 454 | ok. 455 | 456 | 457 | %% @doc Continue the execution of the given process 'Pid'. 458 | c(Pid) -> 459 | continue(Pid). 460 | 461 | %% @doc Continue the execution of the given process <P0.P1.P2>. 462 | c(P0, P1, P2) -> 463 | continue(c:pid(P0, P1, P2)). 464 | 465 | %% @doc Continue the execution of the given process <P0.P1.P2>. 466 | continue(P0, P1, P2) -> 467 | continue(c:pid(P0, P1, P2)). 468 | 469 | %% @doc Continue the execution of the given process 'Pid'. 470 | continue(Pid) when is_pid(Pid) -> 471 | int:continue(Pid). 472 | 473 | %% FIXME doesn't work ? 474 | %% @private 475 | break_in(Mod, Line, Arity) -> 476 | int:break_in(Mod, Line, Arity). 477 | 478 | %% @doc Start interpret the module 'Mod'. 479 | %% 480 | %% With only one argument, you don't set an explicit break point, 481 | %% but you will be able to step into the module while debugging. 482 | %% @end 483 | i(Mod) -> 484 | Fname = find_source(Mod), 485 | int:i(Fname). 486 | 487 | %% @doc Show all interpreted modules. 488 | id() -> 489 | int:interpreted(). 490 | 491 | %% @doc Start interpret module 'Mod' and set a break point at line 'Line'. 492 | i(Mod,Line) -> 493 | Fname = find_source(Mod), 494 | int:i(Fname), 495 | int:delete_break(Mod, Line), 496 | break(Mod, Line). 497 | 498 | %% @doc Start interpret module 'Mod' and set a conditional break point. 499 | %% 500 | %% Start interpret module 'Mod' and set a conditional break point 501 | %% in 'Mod' at line 'Line'. 502 | %% The 'Fun/1' as an anonymous function of arity 1 that gets executed 503 | %% each time the break point is passed. When the 'Fun/1' returns 504 | %% 'true' the break point will trigger and the execution will stop, 505 | %% else the execution will continue. 506 | %% 507 | %% The 'Fun/1' takes an argument which will be a list of current Variable 508 | %% bindings; typically it makes use of the function 509 | %% 'int:get_binding(Var, Bindings)' (where 'Var' is an atom denoting a 510 | %% particular variable) to decide if the break point should trigger 511 | %% or not. See the example further below for how to use it. 512 | %% 513 | %% Note that only one interactive trigger function can be used at a time. 514 | %% @end 515 | it(Mod,Line,Fun) when is_function(Fun) -> 516 | Fname = find_source(Mod), 517 | int:i(Fname), 518 | t(Mod,Line,Fun). 519 | 520 | %% @doc As 't/3' but will reuse an existing trigger function. 521 | t(Mod,Line) -> 522 | int:delete_break(Mod, Line), 523 | ok = int:break(Mod, Line), 524 | ok = int:test_at_break(Mod, Line, {edbg,itest_at_break}), 525 | save_all_breakpoints(), 526 | ok. 527 | 528 | %% @doc As 'it/3' but assumes 'Mod' already is interpreted. 529 | t(Mod,Line,Fun) when is_function(Fun) -> 530 | int:delete_break(Mod, Line), 531 | ok = itest_at_break(Fun), 532 | ok = int:break(Mod, Line), 533 | ok = int:test_at_break(Mod, Line, {edbg,itest_at_break}), 534 | save_all_breakpoints(), 535 | ok. 536 | 537 | 538 | 539 | -define(itest_at_break, itest_at_break). 540 | 541 | %% @private 542 | itest_at_break(Fun) when is_function(Fun) -> 543 | case whereis(?itest_at_break) of 544 | Pid when is_pid(Pid) -> 545 | Pid ! {self(), cond_fun, Fun}, 546 | receive 547 | {Pid, ok} -> ok 548 | after 3000 -> 549 | {error, timeout} 550 | end; 551 | _ -> 552 | Pid = spawn(fun() -> ?MODULE:it_loop(Fun) end), 553 | register(?itest_at_break, Pid), 554 | ok 555 | end; 556 | %% 557 | itest_at_break(Bindings) -> 558 | try 559 | case whereis(?itest_at_break) of 560 | Pid when is_pid(Pid) -> 561 | Pid ! {self(), eval, Bindings}, 562 | receive 563 | {Pid, result, Bool} -> 564 | Bool 565 | after 3000 -> 566 | false 567 | end 568 | end 569 | catch 570 | _:_ -> 571 | false 572 | end. 573 | 574 | %% @private 575 | it_loop(Fun) -> 576 | receive 577 | {From, cond_fun, NewFun} -> 578 | From ! {self(), ok}, 579 | ?MODULE:it_loop(NewFun); 580 | 581 | {From, eval, Bindings} -> 582 | try 583 | true = Fun(Bindings), 584 | From ! {self(), result, true} 585 | catch 586 | _:_ -> 587 | From ! {self(), result, false} 588 | end, 589 | ?MODULE:it_loop(Fun); 590 | 591 | %% Used when saving breakpoints... 592 | {From, get_fun} -> 593 | From ! {self(), ok, Fun}, 594 | ?MODULE:it_loop(Fun); 595 | 596 | _ -> 597 | ?MODULE:it_loop(Fun) 598 | end. 599 | 600 | get_it_break_fun() -> 601 | try 602 | case whereis(?itest_at_break) of 603 | Pid when is_pid(Pid) -> 604 | Pid ! {self(), get_fun}, 605 | receive 606 | {Pid, ok, Fun} -> 607 | Fun 608 | after 3000 -> 609 | null 610 | end 611 | end 612 | catch 613 | _:_ -> 614 | null 615 | end. 616 | 617 | 618 | %% @doc Same as 'step/1'. 619 | s(Pid) -> 620 | step(Pid). 621 | 622 | %% @doc Same as 'step/3'. 623 | s(P0, P1, P2) -> step(c:pid(P0, P1, P2)). 624 | 625 | %% @doc Do a 'Step' debug operation of a stopped process: >P0.P1.P2<. 626 | step(P0, P1, P2) -> step(c:pid(P0, P1, P2)). 627 | 628 | %% @doc Do a 'Step' debug operation of a stopped process: 'Pid'. 629 | step(Pid) when is_pid(Pid) -> 630 | int:step(Pid), 631 | code_list(Pid). 632 | 633 | 634 | %% @doc Same as 'next/1'. 635 | n(Pid) -> 636 | next(Pid). 637 | 638 | %% @doc Same as 'next/3'. 639 | n(P0, P1, P2) -> 640 | next(c:pid(P0, P1, P2)). 641 | 642 | %% @doc Do a 'Next' debug operation of a stopped process: >P0.P1.P2<. 643 | next(P0, P1, P2) -> 644 | next(c:pid(P0, P1, P2)). 645 | 646 | %% @doc Do a 'Next' debug operation of a stopped process: 'Pid'. 647 | next(Pid) when is_pid(Pid) -> 648 | int:next(Pid), 649 | code_list(Pid). 650 | 651 | %% @doc Remove all interpreted modules. 652 | rmi() -> 653 | lists:foreach(fun remove_interpreted_module/1, id()). 654 | 655 | %% @doc Remove a specific interpreted modules. 656 | rmi(Mod) -> 657 | remove_interpreted_module(Mod). 658 | 659 | %% @doc Doesn't support distributed mode. See int:del_mod/2 for more. 660 | remove_interpreted_module(Mod) -> 661 | dbg_iserver:safe_cast({delete, Mod}), 662 | erts_debug:breakpoint({Mod,'_','_'}, false), 663 | erlang:yield(). 664 | 665 | %% @doc Finish execution of a debugged function in process: 'Pid'. 666 | f(Pid) -> 667 | finish(Pid). 668 | 669 | %% @doc Finish execution of a debugged function in process: <P0.P1.P2>. 670 | f(P0, P1, P2) -> 671 | finish(c:pid(P0, P1, P2)). 672 | 673 | %% @doc Finish execution of a debugged function in process: 'Pid'. 674 | finish(Pid) when is_pid(Pid) -> 675 | int:finish(Pid), 676 | code_list(Pid). 677 | 678 | %% @doc Finish execution of a debugged function in process: <P0.P1.P2>. 679 | finish(P0, P1, P2) -> 680 | finish(c:pid(P0, P1, P2)). 681 | 682 | 683 | code_list(Pid) -> 684 | case get_break_point(Pid) of 685 | {Pid, {_Mod,_Name,_Args}, break, {Mod,Line}} -> 686 | mlist(Mod,Line), 687 | ok; 688 | Else -> 689 | Else 690 | end. 691 | 692 | 693 | -define(DEFAULT_CONTEXT_SIZE, 5). 694 | 695 | -record(s, { 696 | pid, 697 | meta, 698 | prompt, 699 | break_at, 700 | break_points = [], 701 | context = ?DEFAULT_CONTEXT_SIZE, 702 | stack, 703 | trace = false, % toggle 704 | mytracer 705 | }). 706 | 707 | 708 | toggle(true) -> false; 709 | toggle(false) -> true. 710 | 711 | %% @doc Attach to the first process found stopped on a break point. 712 | %% 713 | %% Attach to an interpreted process in order to manually control 714 | %% the further execution, inspect variables, etc. When called, you 715 | %% will enter a sort of mini-shell where you can issue a number of 716 | %% commands. 717 | %% @end 718 | a() -> 719 | Self = self(), 720 | case [Pid || {Pid, _Func, Status, _Info} <- int:snapshot(), 721 | Status == break, 722 | Pid =/= Self] of 723 | [X|_] -> 724 | attach(X); 725 | _ -> 726 | "No process found to be at a break point!" 727 | end. 728 | 729 | %% @doc Attach to the given Pid. 730 | a(Pid) -> 731 | attach(Pid). 732 | 733 | %% @doc Attach to the given process: <P0.P1.P2> . 734 | a(P0, P1, P2) -> 735 | attach(c:pid(P0, P1, P2)). 736 | 737 | %% @doc Attach to the given process: <P0.P1.P2> . 738 | attach(P0, P1, P2) -> 739 | attach(c:pid(P0, P1, P2)). 740 | 741 | %% @doc Attach to the given Pid. 742 | attach(Pid) when is_pid(Pid) -> 743 | Self = self(), 744 | case int:attached(Pid) of 745 | {ok, Meta} -> 746 | Prompt = spawn_link(fun() -> prompt(Pid,Self) end), 747 | print_help(), 748 | aloop(#s{pid=Pid, 749 | meta=Meta, 750 | break_points = int:all_breaks(), 751 | prompt=Prompt}); 752 | Else -> 753 | ?err_msg("Failed to attach to process: ~p~n",[Pid]), 754 | Else 755 | end. 756 | 757 | 758 | %% @private 759 | aloop(#s{meta = Meta, 760 | prompt = Prompt} = S) -> 761 | receive 762 | 763 | {'EXIT', Meta, Reason} -> 764 | {meta_exit, Reason}; 765 | 766 | {'EXIT', Prompt, Reason} -> 767 | {prompt_exit, Reason}; 768 | 769 | %% FROM THE INTERPRETER (?) 770 | {int,{new_break,{{Mod,Line},_L} = Bp}} -> 771 | Bps = S#s.break_points, 772 | ?info_msg([?c_hi("Break point set at"), " ~p:~p~n"], [Mod,Line]), 773 | ?MODULE:aloop(S#s{break_points = [Bp|Bps]}); 774 | 775 | {int,{interpret,_Mod}} -> 776 | ?MODULE:aloop(S); 777 | 778 | {int,{break_options,{{_Mod,_Line},_Opts}}} -> 779 | ?MODULE:aloop(S); 780 | 781 | {int,{delete_break,{_Mod,_Line}}} -> 782 | ?MODULE:aloop(S); 783 | 784 | %% FROM THE META PROCESS 785 | {Meta, {trace,true}} -> 786 | ?MODULE:aloop(S); 787 | 788 | {Meta, running} -> 789 | ?MODULE:aloop(S); 790 | 791 | {Meta, {attached, _Mod, _Line, _Bool}} -> 792 | ?MODULE:aloop(S); 793 | 794 | {Meta, {break_at, Mod, Line, Cur}} -> 795 | Bs = int:meta(Meta, bindings, nostack), 796 | mlist(Mod,Line,S#s.context), 797 | ?MODULE:aloop(S#s{break_at = {Mod,Line}, 798 | stack = {Cur,Cur,Bs,{Mod,Line}}}); 799 | 800 | {Meta, {func_at, Mod, Line, Cur}} -> 801 | Bs = int:meta(Meta, bindings, nostack), 802 | mlist(Mod,Line,S#s.context), 803 | ?MODULE:aloop(S#s{break_at = {Mod,Line}, 804 | stack = {Cur,Cur,Bs,{Mod,Line}}}); 805 | 806 | {Meta,{exit_at, {Mod, Line}, Reason, Cur}} -> 807 | Bs = int:meta(Meta, bindings, nostack), 808 | mlist(Mod,Line,S#s.context), 809 | ?err_msg("ERROR REASON: ~p~n",[Reason]), 810 | ?MODULE:aloop(S#s{break_at = {Mod,Line}, 811 | stack = {Cur+1,Cur+1,Bs,{Mod,Line}}}); 812 | 813 | {Meta, {trace_output, StrFun}} -> 814 | ?info_msg("~s~n",[StrFun("~tp")]), 815 | ?MODULE:aloop(S); 816 | 817 | %% FROM THE PROMPTER 818 | {Prompt, eval_expr, Str} -> 819 | Bs = int:meta(Meta, bindings, nostack), 820 | case evaluate(Str, Bs) of 821 | {ok, Value} -> 822 | ?info_msg([?c_hi("EVALUATED VALUE"), ":~n~p~n"], [Value]); 823 | {error, ErrStr} -> 824 | ?err_msg("~s~n",[ErrStr]) 825 | end, 826 | ?MODULE:aloop(S); 827 | 828 | {Prompt, up} -> 829 | {Cur, Max, _, _} = S#s.stack, 830 | case int:meta(Meta, stack_frame, {up, Cur}) of 831 | {New, {undefined,-1}, _Bs} -> % call from non-interpreted code 832 | Stack = {New, Max, undefined, undefined}; 833 | 834 | {New, {Mod,Line}, Bs} -> 835 | Stack = {New, Max, Bs, {Mod,Line}}, 836 | mlist(Mod,Line,S#s.context); 837 | 838 | top -> 839 | Stack = S#s.stack, 840 | ?att_msg("Top of stack frames!~n",[]) 841 | end, 842 | ?MODULE:aloop(S#s{stack = Stack}); 843 | 844 | {Prompt, down} -> 845 | {Cur, Max, _, _} = S#s.stack, 846 | case int:meta(Meta, stack_frame, {down, Cur}) of 847 | {New, {undefined,-1}, _Bs} -> % call from non-interpreted code 848 | Stack = {New, Max, undefined, undefined}; 849 | 850 | {New, {Mod,Line}, Bs} -> 851 | Stack = {New, Max, Bs, {Mod,Line}}, 852 | mlist(Mod,Line,S#s.context); 853 | 854 | bottom -> 855 | Bs = int:meta(Meta, bindings, nostack), 856 | {Mod,Line} = ModLine = S#s.break_at, 857 | Stack = {Max,Max,Bs,ModLine}, 858 | mlist(Mod,Line,S#s.context) 859 | end, 860 | ?MODULE:aloop(S#s{stack = Stack}); 861 | 862 | {Prompt, at} -> 863 | case S#s.stack of 864 | {_,_,_,{Mod,Line}} -> 865 | mlist(Mod,Line,S#s.context); 866 | _ -> 867 | ?att_msg("No info available...~n",[]) 868 | end, 869 | ?MODULE:aloop(S); 870 | 871 | {Prompt, at, Ctx} -> 872 | case S#s.stack of 873 | {_,_,_,{Mod,Line}} -> 874 | mlist(Mod,Line,Ctx); 875 | _ -> 876 | ?att_msg("No info available...~n",[]) 877 | end, 878 | ?MODULE:aloop(S); 879 | 880 | {Prompt, list_module, {Mod,Line,Ctx}} -> 881 | mlist(Mod,Line,Ctx), 882 | ?MODULE:aloop(S); 883 | 884 | {Prompt, list_module, {Mod,Line}} -> 885 | mlist(Mod,Line), 886 | ?MODULE:aloop(S); 887 | 888 | {Prompt, step = X} -> 889 | int:meta(Meta, X), 890 | ?MODULE:aloop(S); 891 | 892 | {Prompt, next = X} -> 893 | int:meta(Meta, X), 894 | ?MODULE:aloop(S); 895 | 896 | {Prompt, finish = X} -> 897 | int:meta(Meta, X), 898 | ?MODULE:aloop(S); 899 | 900 | {Prompt, processes} -> 901 | plist(), 902 | ?MODULE:aloop(S); 903 | 904 | {Prompt, messages = X} -> 905 | Ms = int:meta(Meta, X), 906 | ?info_msg([?c_hi("MSGS"), ": ~p~n"] ,[Ms]), 907 | ?MODULE:aloop(S); 908 | 909 | {Prompt, continue = X} -> 910 | int:meta(Meta, X), 911 | ?MODULE:aloop(S); 912 | 913 | {Prompt, skip = X} -> 914 | int:meta(Meta, X), 915 | ?MODULE:aloop(S); 916 | 917 | {Prompt, context, Ctx} -> 918 | ?MODULE:aloop(S#s{context = Ctx}); 919 | 920 | {Prompt, breakpoints} -> 921 | breaks(), 922 | ?MODULE:aloop(S); 923 | 924 | {Prompt, backtrace = X} -> 925 | Bt = int:meta(Meta, X, 1), 926 | ?info_msg("BT ~p~n",[Bt]), 927 | ?MODULE:aloop(S); 928 | 929 | {Prompt, interpret, Mod} -> 930 | int:i(Mod), 931 | ?info_msg([?c_hi("Interpreted modules"), ": ~p~n"], 932 | [int:interpreted()]), 933 | ?MODULE:aloop(S); 934 | 935 | {Prompt, var, Var} -> 936 | {_Cur,_Max,Bs,_} = S#s.stack, 937 | case find_var(Var, Bs) of 938 | [{VarName, Val}] -> 939 | ?info_msg([?c_hi("~p"), "= ~p~n"], [VarName, Val]); 940 | [{_Var1, _Val1}|_] = Candidates -> 941 | ?info_msg([?c_hi("~p"), ?c_warn(" ambigious prefix"), 942 | ": ~p~n"], [Var, [N || {N,_} <- Candidates]]); 943 | [] -> ?info_msg([?c_hi("~p"), ?c_err(" not found"), "~n"], 944 | [Var]) 945 | end, 946 | ?MODULE:aloop(S); 947 | 948 | {Prompt, pr_var, Var} -> 949 | {_Cur,_Max,Bs,_} = S#s.stack, 950 | try 951 | case find_var(Var, Bs) of 952 | [{VarName, Val}] -> 953 | case S#s.stack of 954 | {_,_,_,{Mod,_Line}} -> 955 | Fname = edbg:find_source(Mod), 956 | {ok, Defs} = pp_record:read(Fname), 957 | ?info_msg("~p =~n~s~n", 958 | [VarName, 959 | pp_record:print(Val, Defs)]); 960 | _ -> 961 | ?err_msg("No module info available...~n",[]) 962 | end; 963 | [{_Var1, _Val1}|_] = Candidates -> 964 | ?err_msg("~p ambigious prefix: ~p~n", 965 | [Var, [N || {N,_} <- Candidates]]); 966 | [] -> 967 | ?att_msg("~p not found~n",[Var]) 968 | end 969 | catch 970 | _:_ -> ?err_msg("Operation failed...~n",[]) 971 | end, 972 | ?MODULE:aloop(S); 973 | 974 | {Prompt, set_break, Line} when is_integer(Line) -> 975 | case S#s.break_at of 976 | {Mod,_} -> 977 | int:break(Mod, Line); 978 | _ -> 979 | ?err_msg("Unknown Module; no break point set~n",[]) 980 | end, 981 | save_all_breakpoints(), 982 | ?MODULE:aloop(S); 983 | 984 | {Prompt, test_break, Line} when is_integer(Line) -> 985 | case S#s.break_at of 986 | {Mod,_} -> 987 | t(Mod, Line); 988 | _ -> 989 | ?err_msg("Unknown Module; no break point set~n",[]) 990 | end, 991 | save_all_breakpoints(), 992 | ?MODULE:aloop(S); 993 | 994 | {Prompt, delete_break, Line} when is_integer(Line) -> 995 | case S#s.break_at of 996 | {Mod,_} -> 997 | int:delete_break(Mod, Line); 998 | _ -> 999 | ?err_msg("Unknown Module; no break point deleted~n",[]) 1000 | end, 1001 | save_all_breakpoints(), 1002 | ?MODULE:aloop(S); 1003 | 1004 | {Prompt, disable_break, Line} when is_integer(Line) -> 1005 | case S#s.break_at of 1006 | {Mod,_} -> 1007 | int:disable_break(Mod, Line); 1008 | _ -> 1009 | ?err_msg("Unknown Module; no break point disabled~n", []) 1010 | end, 1011 | save_all_breakpoints(), 1012 | ?MODULE:aloop(S); 1013 | 1014 | {Prompt, enable_break, Line} when is_integer(Line) -> 1015 | case S#s.break_at of 1016 | {Mod,_} -> 1017 | int:enable_break(Mod, Line); 1018 | _ -> 1019 | ?err_msg("Unknown Module; no break point enabled~n", []) 1020 | end, 1021 | save_all_breakpoints(), 1022 | ?MODULE:aloop(S); 1023 | 1024 | {Prompt, set_break, {Mod,Line}} when is_integer(Line) -> 1025 | int:break(Mod, Line), 1026 | save_all_breakpoints(), 1027 | ?MODULE:aloop(S); 1028 | 1029 | {Prompt, test_break, {Mod,Line}} when is_integer(Line) -> 1030 | t(Mod,Line), 1031 | save_all_breakpoints(), 1032 | ?MODULE:aloop(S); 1033 | 1034 | {Prompt, delete_break, {Mod,Line}} when is_integer(Line) -> 1035 | int:delete_break(Mod, Line), 1036 | save_all_breakpoints(), 1037 | ?MODULE:aloop(S); 1038 | 1039 | {Prompt, disable_break, {Mod,Line}} when is_integer(Line) -> 1040 | int:disable_break(Mod, Line), 1041 | save_all_breakpoints(), 1042 | ?MODULE:aloop(S); 1043 | 1044 | {Prompt, enable_break, {Mod,Line}} when is_integer(Line) -> 1045 | int:enable_break(Mod, Line), 1046 | save_all_breakpoints(), 1047 | ?MODULE:aloop(S); 1048 | 1049 | {Prompt, trace = X} -> 1050 | Trace = S#s.trace, 1051 | NewTrace = toggle(Trace), 1052 | int:meta(Meta, X, NewTrace), 1053 | ?MODULE:aloop(S#s{trace = NewTrace}); 1054 | 1055 | {Prompt, quit} -> 1056 | exit(normal); 1057 | 1058 | _X -> 1059 | ?info_msg("aloop got: ~p~n",[_X]), 1060 | ?MODULE:aloop(S) 1061 | 1062 | end. 1063 | 1064 | %% 1065 | %% {ok, Tokens, _} = erl_scan:string("lists:map(fun(X) -> X+1 end, L)."). 1066 | %% {ok, Exprs} = erl_parse:parse_exprs(Tokens). 1067 | %% erl_eval:add_bindings('L',[1,2,3],erl_eval:new_bindings()). 1068 | %% erl_eval:exprs(Exprs,Bs). 1069 | %% {value,[2,3,4],[{'L',[1,2,3]}]} 1070 | %% 1071 | evaluate(ExprStr, Bindings) -> 1072 | try 1073 | {ok, Tokens, _} = erl_scan:string(ExprStr), 1074 | {ok, Exprs} = erl_parse:parse_exprs(Tokens), 1075 | {value, Value, _Bs} = erl_eval:exprs(Exprs, Bindings), 1076 | {ok, Value} 1077 | catch 1078 | _:_ -> 1079 | {error,"Failed to parse/evaluate expression"} 1080 | end. 1081 | 1082 | 1083 | 1084 | print_all_breakpoints(L) -> 1085 | ?att_msg("~nBREAKPOINTS~n",[]), 1086 | F = fun({{Mod,Line},[Status,Trigger,_,Cond]}) -> 1087 | ?info_msg(" ~p:~p Status=~p Trigger=~p Cond=~p~n", 1088 | [Mod,Line,Status,Trigger,Cond]) 1089 | end, 1090 | [F(X) || X <- L]. 1091 | 1092 | %% @doc Save all current break points. 1093 | %% 1094 | %% Identical to 'save_all_breakpoints/0'. 1095 | %% 1096 | %% @end 1097 | sab() -> 1098 | save_all_breakpoints(). 1099 | 1100 | %% @doc Save all current break points. 1101 | %% 1102 | %% Whenever a break point is set or modified, information is 1103 | %% stored on disk in the file 'breakpoints.edbg' . 1104 | %% 1105 | %% @end 1106 | save_all_breakpoints() -> 1107 | L = int:all_breaks(), 1108 | {ok,Fd} = file:open("breakpoints.edbg",[write]), 1109 | try 1110 | F = fun({{Mod,Line},[Status,Trigger,X,Cond]}) -> 1111 | case Cond of 1112 | {edbg,itest_at_break} -> 1113 | Cfun = get_it_break_fun(), 1114 | io:format(Fd,"{{~p,~p},{~p,~p,~p,~p}}.~n", 1115 | [Mod,Line,Status,Trigger,X, 1116 | term_to_binary(Cfun)]); 1117 | _ -> 1118 | io:format(Fd,"{{~p,~p},{~p,~p,~p,~p}}.~n", 1119 | [Mod,Line,Status,Trigger,X,Cond]) 1120 | end 1121 | end, 1122 | [F(Z) || Z <- L] 1123 | after 1124 | file:close(Fd) 1125 | end. 1126 | 1127 | %% @doc Load (previously) stored break points. 1128 | %% 1129 | %% Identical to 'load_all_breakpoints/0'. 1130 | %% 1131 | %% @end 1132 | lab() -> 1133 | load_all_breakpoints(). 1134 | 1135 | %% @doc Load (previously) stored break points. 1136 | %% 1137 | %% Whenever a break point is set or modified, information is 1138 | %% stored on disk in the file 'breakpoints.edbg' . This function 1139 | %% will load and set those breakpoints found in this file. 1140 | %% 1141 | %% @end 1142 | load_all_breakpoints() -> 1143 | {ok,L} = file:consult("breakpoints.edbg"), 1144 | F = fun({{Mod,Line},{Status,Trigger,_X,Cond}})-> 1145 | case Cond of 1146 | Cbin when is_binary(Cbin) -> 1147 | Cfun = binary_to_term(Cbin), 1148 | it(Mod,Line,Cfun); 1149 | 1150 | {Cmod,_} when Cmod =/= ?MODULE -> 1151 | i(Mod,Line), 1152 | ok = int:test_at_break(Mod,Line,Cond); 1153 | 1154 | _ -> 1155 | i(Mod,Line) 1156 | end, 1157 | case {Status,Trigger} of 1158 | {inactive,_} -> int:disable_break(Mod,Line); 1159 | {_,disable} -> int:disable_break(Mod,Line); % disable? 1160 | _ -> false 1161 | end 1162 | end, 1163 | [F(Z) || Z <- L]. 1164 | 1165 | %% First try for exact match, then for a prefix match 1166 | find_var(Var, Bindings) -> 1167 | case int:get_binding(Var, Bindings) of 1168 | {value, Val} -> 1169 | [{Var,Val}]; 1170 | _ -> 1171 | VarStr = erlang:atom_to_list(Var), 1172 | PrefixFun = fun({K,_V}) -> 1173 | lists:prefix(VarStr, erlang:atom_to_list(K)) 1174 | end, 1175 | lists:filtermap(PrefixFun, Bindings) 1176 | end. 1177 | 1178 | prompt(Pid, Apid) when is_pid(Pid), is_pid(Apid) -> 1179 | Prompt = "("++pid_to_list(Pid)++")> ", 1180 | ploop(Apid, Prompt, _PrevCmd = []). 1181 | 1182 | %% @private 1183 | ploop(Apid, Prompt, PrevCmd) -> 1184 | %% Empty prompt repeats previous command 1185 | Cmd = case string:tokens(io:get_line(Prompt), "\n") of 1186 | [] -> PrevCmd; 1187 | Cmd0 -> Cmd0 1188 | end, 1189 | 1190 | case Cmd of 1191 | ["a"++X] -> at(Apid, X); 1192 | ["l"++X] -> send_list_module(Apid, X); 1193 | ["n"++_] -> Apid ! {self(), next}; 1194 | ["s"++_] -> Apid ! {self(), step}; 1195 | ["f"++_] -> Apid ! {self(), finish}; 1196 | ["c"++_] -> Apid ! {self(), continue}; 1197 | ["m"++_] -> Apid ! {self(), messages}; 1198 | ["pr"++X] -> Apid ! {self(), pr_var, list_to_atom(string:strip(X))}; 1199 | ["p"++_] -> Apid ! {self(), processes}; 1200 | ["k"++_] -> Apid ! {self(), skip}; 1201 | ["u"++_] -> Apid ! {self(), up}; 1202 | ["q"++_] -> Apid ! {self(), quit}, exit(normal); 1203 | ["i"++X] -> Apid ! {self(), interpret, list_to_atom(string:strip(X))}; 1204 | ["v"++X] -> Apid ! {self(), var, list_to_atom(string:strip(X))}; 1205 | ["x"++X] -> set_context(Apid, X); 1206 | ["h"++_] -> print_help(); 1207 | 1208 | ["ba"++_] -> Apid ! {self(), backtrace}; 1209 | ["br"++_] -> Apid ! {self(), breakpoints}; 1210 | ["b"++X] -> set_break(Apid, X); 1211 | 1212 | ["te"++X] -> test_break(Apid, X); 1213 | ["t"++_] -> Apid ! {self(), trace}; 1214 | 1215 | ["en"++X] -> ena_break(Apid, X); 1216 | ["e"++X] -> Apid ! {self(), eval_expr, string:strip(X)}; 1217 | 1218 | ["di"++X] -> dis_break(Apid, X); 1219 | ["de"++X] -> del_break(Apid, X); 1220 | ["d"++_] -> Apid ! {self(), down}; 1221 | 1222 | _X -> 1223 | ?info_msg("prompt got: ~p~n",[_X]) 1224 | end, 1225 | ?MODULE:ploop(Apid, Prompt, Cmd). 1226 | 1227 | at(Apid, X) -> 1228 | try 1229 | [Ctx] = string:tokens(string:strip(X), " "), 1230 | Apid ! {self(), at, list_to_integer(Ctx)} 1231 | catch 1232 | _:_ -> Apid ! {self(), at} 1233 | end. 1234 | 1235 | print_help() -> 1236 | S1 = " (h)elp (a)t (n)ext (s)tep (f)inish (c)ontinue s(k)ip", 1237 | S2 = " (m)essages (t)oggle trace (q)uit (br)eakpoints (p)rocesses", 1238 | S3 = " (de)lete/(di)sable/(en)able/(te)st/(b)reak ", 1239 | S4 = " (v)ar (e)val (i)nterpret ", 1240 | S5 = " (pr)etty print record conte(x)t ", 1241 | S6 = " (u)p (d)own (l)ist module >", 1242 | S = io_lib:format("~n~s~n~s~n~s~n~s~n~s~n~s~n", [S1,S2,S3,S4,S5,S6]), 1243 | ?info_msg(?help_hi(S), []). 1244 | 1245 | set_context(Apid, X) -> 1246 | case string:strip(X) of 1247 | [] -> 1248 | %% Empty argument resets to default context size of five rows 1249 | Apid ! {self(), context, ?DEFAULT_CONTEXT_SIZE}; 1250 | 1251 | Arg -> 1252 | %% Try setting context size, notify user if bad input 1253 | try 1254 | Apid ! {self(), context, list_to_integer(Arg)} 1255 | catch 1256 | error:badarg -> 1257 | ?info_msg("context only takes numbers~n", []) 1258 | end 1259 | end. 1260 | 1261 | set_break(Apid, X) -> 1262 | send_mod_line(Apid, X, set_break). 1263 | 1264 | test_break(Apid, X) -> 1265 | send_mod_line(Apid, X, test_break). 1266 | 1267 | del_break(Apid, X) -> 1268 | send_mod_line(Apid, X, delete_break). 1269 | 1270 | dis_break(Apid, X) -> 1271 | send_mod_line(Apid, X, disable_break). 1272 | 1273 | ena_break(Apid, X) -> 1274 | send_mod_line(Apid, X, enable_break). 1275 | 1276 | send_mod_line(Apid, X, Cmd) -> 1277 | try 1278 | case string:tokens(string:strip(X), " ") of 1279 | [Mod,Line] -> 1280 | Apid ! {self(), Cmd, {list_to_atom(Mod),list_to_integer(Line)}}; 1281 | [Line] -> 1282 | Apid ! {self(), Cmd, list_to_integer(Line)} 1283 | end, 1284 | true 1285 | catch 1286 | _:_ -> false 1287 | end. 1288 | 1289 | 1290 | send_list_module(Apid, X) -> 1291 | try 1292 | case string:tokens(string:strip(X), " ") of 1293 | [Mod,Line,Ctx] -> 1294 | Apid ! {self(), list_module, {list_to_atom(Mod), 1295 | list_to_integer(Line), 1296 | list_to_integer(Ctx)}}; 1297 | [Mod,Line] -> 1298 | Apid ! {self(), list_module, {list_to_atom(Mod), 1299 | list_to_integer(Line)}} 1300 | end, 1301 | true 1302 | catch 1303 | _:_ -> false 1304 | end. 1305 | 1306 | 1307 | 1308 | 1309 | %% @doc Show all interpreted processes. 1310 | %% 1311 | %% Show all interpreted processes and what state there are in. 1312 | %% In particular this is useful to see if a process has stopped 1313 | %% at a break point. The process identifier ('Pid') displayed in the 1314 | %% leftmost column can be used with the 'attach/1' function. 1315 | %% 1316 | %% @end 1317 | plist() -> 1318 | Self = self(), 1319 | L = [X || X = {Pid, _Func, _Status, _Info} <- int:snapshot(), 1320 | Pid =/= Self], 1321 | %%[{<0.33.0>,{sloop,start,[]},idle,{}}, 1322 | %% {<0.42.0>,{sloop,init,[]},break,{sloop,19}}] 1323 | 1324 | lists:map(fun({Pid, {Mod,Name,Args}, break = Status, {Mod2,Line}}) -> 1325 | ?info_msg("~10.p ~-25.s ~-10.s ~p:~p~n", 1326 | [Pid,q(Mod,Name,length(Args)), 1327 | q(Status),Mod2,Line]); 1328 | 1329 | ({Pid, {Mod,Name,Args}, exit = Status, Reason}) -> 1330 | if (Reason =/= normal) -> 1331 | ?info_msg("~10.p ~-25.s ~-10.s Reason: ~p~n", 1332 | [Pid, 1333 | q(Mod,Name,length(Args)), 1334 | q(Status),Reason]); 1335 | true -> 1336 | false 1337 | end, 1338 | ok; 1339 | 1340 | ({Pid, {Mod,Name,Args}, running = Status, _}) -> 1341 | ?info_msg("~10.p ~-25.s ~-10.s~n", 1342 | [Pid,q(Mod,Name,length(Args)),q(Status)]); 1343 | 1344 | ({Pid, {Mod,Name,Args}, idle = Status, _}) -> 1345 | ?info_msg("~10.p ~-25.s ~-10.s~n", 1346 | [Pid,q(Mod,Name,length(Args)),q(Status)]); 1347 | 1348 | ({Pid, {Mod,Name,Args}, waiting = Status, _}) -> 1349 | ?info_msg("~10.p ~-25.s ~-10.s~n", 1350 | [Pid,q(Mod,Name,length(Args)),q(Status)]) 1351 | 1352 | end, L), 1353 | ok. 1354 | 1355 | q(M,F,A) -> 1356 | q(M)++":"++q(F)++"/"++q(A). 1357 | 1358 | q(A) when is_atom(A) -> atom_to_list(A); 1359 | q(I) when is_integer(I) -> integer_to_list(I). 1360 | 1361 | %% @doc List the source code, centered around a break point. 1362 | %% 1363 | %% The 'mlist/1' can also take a Module as an argument to 1364 | %% list the source ode starting from Row 1. 1365 | %% @end 1366 | mlist(Pid) when is_pid(Pid) -> 1367 | case get_break_point(Pid) of 1368 | {_Pid, {_Mod,_Name,_Args}, break, {Mod,Line}} -> 1369 | mlist(Mod, Line); 1370 | Else -> 1371 | Else 1372 | end; 1373 | mlist(Mod) when is_atom(Mod) -> 1374 | mod_list(Mod, {1,-1,-1}). 1375 | 1376 | 1377 | 1378 | %% @doc List the source code of a 'Module' centered around 'Row'. 1379 | mlist(Mod, Row) when is_atom(Mod) -> 1380 | mod_list(Mod, {1,Row,5}). 1381 | 1382 | %% @doc List the source code of a Module. 1383 | %% 1384 | %% List the source code of a 'Module', either centered around a triggered 1385 | %% break point, or around a given 'Line'. The amount of lines being display 1386 | %% around the line is controlled by the 'Contexft' value, which per default 1387 | %% is set to '5' (i.e display 5 lines above and below the line). 1388 | %% 1389 | %% Note that the listing will display the line numbers at the left border 1390 | %% of the output where breakpoints are high lighted by a '*' character 1391 | %% and the given line as '>'. However, if no line is given, the '>' 1392 | %% character will be used to denote where we currently are stopped. 1393 | %% 1394 | %% The 'mlist/3' can also take a sequence of integers for supplying a 'Pid' 1395 | %% when we should center around a break point. 1396 | %% @end 1397 | mlist(Mod, Row, Ctx) when is_atom(Mod) -> 1398 | mod_list(Mod, {1,Row,Ctx}); 1399 | mlist(P0, P1, P2) when P0 >= 0, P1 >= 0, P2 >= 0 -> 1400 | mlist(c:pid(P0, P1, P2)). 1401 | 1402 | mod_list(Mod, Rinfo)-> 1403 | 1404 | %% The interpreter requires both the source code and the object code. 1405 | %% The object code must include debug information 1406 | 1407 | Fname = find_source(Mod), 1408 | {ok, SrcBin, Fname} = erl_prim_loader:get_file(Fname), 1409 | 1410 | put(mod_bps, int:all_breaks(Mod)), 1411 | ?info_msg([?c_hi("MODULE"), ": ~p.erl~n"], [Mod]), 1412 | lists:foldl(fun(Line,{Cur,Row,Ctx}) when Cur == Row -> 1413 | Fs = field_size(Row,Ctx), 1414 | ?cur_line_msg("~"++Fs++".s> ~s~n",[i2l(Cur),b2l(Line)]), 1415 | {Cur+1,Row,Ctx}; 1416 | 1417 | (Line,{Cur,Row,Ctx}) when Row == -1 orelse 1418 | (Cur >= (Row-Ctx) andalso 1419 | Cur =< (Row+Ctx)) -> 1420 | Fs = field_size(Row,Ctx), 1421 | ?info_msg("~"++Fs++".s~s ~s~n",[i2l(Cur),sep(Cur), 1422 | b2l(Line)]), 1423 | {Cur+1,Row,Ctx}; 1424 | 1425 | (_Line,{Cur,Row,Ctx}) -> 1426 | {Cur+1,Row,Ctx} 1427 | end, 1428 | Rinfo, 1429 | re:split(SrcBin,"\n")), 1430 | ok. 1431 | 1432 | field_size(Row,Ctx) -> 1433 | integer_to_list(length(integer_to_list(Row+Ctx))). 1434 | 1435 | sep(Row) -> 1436 | case get(mod_bps) of 1437 | [] -> 1438 | ":"; 1439 | Bs -> 1440 | case [X || {{_,L},_} = X <- Bs, 1441 | L == Row] of 1442 | [] -> 1443 | ":"; 1444 | _ -> 1445 | ?c_err("*") 1446 | end 1447 | end. 1448 | 1449 | %% @private 1450 | find_source(Mod) -> 1451 | case filelib:find_source(code:which(Mod)) of 1452 | {ok, Fname} -> 1453 | Fname; 1454 | {error, _} -> 1455 | find_source_from_modinfo(Mod) 1456 | end. 1457 | %% [Fname] = [Z || {source,Z} <- 1458 | %% hd([X || {compile,X} <- 1459 | %% apply(Mod,module_info,[])])], 1460 | 1461 | %% @private 1462 | find_source_from_modinfo(Mod) -> 1463 | Cs = Mod:module_info(compile), 1464 | {source, Fname} = lists:keyfind(source, 1, Cs), 1465 | Fname. 1466 | 1467 | i2l(I) when is_integer(I) -> integer_to_list(I). 1468 | b2l(B) when is_binary(B) -> binary_to_list(B). 1469 | 1470 | get_break_point(MyPid) -> 1471 | case [X || X = {Pid, _Func, _Status, _Info} <- int:snapshot(), 1472 | Pid == MyPid] of 1473 | [] -> 1474 | pid_not_found; 1475 | 1476 | 1477 | [{_Pid, {Mod,_Name,_Args}, break, {Mod,_Line}} = Bx] -> 1478 | Bx; 1479 | 1480 | _ -> 1481 | no_break_found 1482 | end. 1483 | -------------------------------------------------------------------------------- /src/edbg_tracer.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Torbjorn Tornkvist 3 | %%% @copyright (C) 2017, Torbjorn Tornkvist 4 | %%% @doc The `edbg' tracer. 5 | %%% 6 | %%% The function {@link edbg:fstart/2} takes one argument containing 7 | %%% the list of modules we want to trace, and a second argument 8 | %%% containing various options. The trace output will be stored on file. 9 | %%% 10 | %%% So in the example below we want to trace on three modules: 11 | %%% yaws_server, yaws, yaws_config, from the Yaws webserver. 12 | %%% With the `max_msgs' option we restrict the allowed number 13 | %%% of trace messages to 10000. 14 | %%% 15 | %%% ``` 16 | %%% 1> edbg:fstart([yaws_server,yaws,yaws_config],[{max_msgs,10000}]). 17 | %%% ok 18 | %%% ''' 19 | %%% 20 | %%% Now run some traffic toward yaws and when done, stop the tracing: 21 | %%% 22 | %%% ``` 23 | %%% 2> edbg:fstop(). 24 | %%% ok 25 | %%% ''' 26 | %%% 27 | %%% Here we rely on using the default filename for storing the trace output, 28 | %%% hence we don't have to specify it here when loading the trace info to 29 | %%% be displayed. 30 | %%% 31 | %%% ``` 32 | %%% 3> edbg:file(). 33 | %%% 34 | %%% (h)elp (a)t [] (d)own (u)p (t)op (b)ottom 35 | %%% (s)how [] (r)etval ra(w) 36 | %%% (pr)etty print record 37 | %%% (f)ind [ ] (fr) 38 | %%% (on)/(off) send_receive | memory 39 | %%% (p)agesize (q)uit 40 | %%% (set) [] (let) 41 | %%% (eval) (xall/xnall) 42 | %%% ''' 43 | %%% 44 | %%% As can be seen, we first get a compact help text showing what commands 45 | %%% we can use. Then follows the beginning of the trace output. 46 | %%% Each line is prefixed with a number that we use for reference. 47 | %%% The indentation shows the depth of the call chain. 48 | %%% 49 | %%% ``` 50 | %%% 0: <0.255.0> yaws_server:gserv_loop/4 51 | %%% 1: <0.258.0> yaws_server:gserv_loop/4 52 | %%% 2: <0.233.0> yaws:month/1 53 | %%% 4: <0.259.0> yaws_server:peername/2 54 | %%% 6: <0.258.0> yaws_server:close_accepted_if_max/2 55 | %%% 8: <0.258.0> yaws_server:acceptor/1 56 | %%% 10: <0.258.0> yaws_server:gserv_loop/4 57 | %%% 11: <0.281.0> yaws_server:acceptor0/2 58 | %%% 12: <0.281.0> yaws_server:do_accept/1 59 | %%% ...snip... 60 | %%% ''' 61 | %%% 62 | %%% As you can see, we get a pretty output where we can follow 63 | %%% the chain of execution without drowning in output which would 64 | %%% be the case if we should have displayed the contents of all 65 | %%% the arguments to the functions. 66 | %%% 67 | %%% Instead, we can now inspect a particular call of interest, 68 | %%% let's say line 4; we use the `(s)how' command to display 69 | %%% the function clause heads in order to help us decide which 70 | %%% argument to inspect. 71 | %%% 72 | %%% ``` 73 | %%% tlist> s 4 74 | %%% 75 | %%% Call: yaws_server:peername/2 76 | %%% ----------------------------------- 77 | %%% 78 | %%% peername(CliSock, ssl) -> 79 | %%% 80 | %%% peername(CliSock, nossl) -> 81 | %%% 82 | %%% ----------------------------------- 83 | %%% ''' 84 | %%% 85 | %%% To show what the second argument contained, 86 | %%% we add 2 to the show command: 87 | %%% 88 | %%% ``` 89 | %%% tlist> s 4 2 90 | %%% 91 | %%% Call: yaws_server:peername/2 , argument 2: 92 | %%% ----------------------------------- 93 | %%% nossl 94 | %%% ''' 95 | %%% 96 | %%% We can also see what the function returned: 97 | %%% 98 | %%% ``` 99 | %%% tlist> r 4 100 | %%% 101 | %%% Call: yaws_server:peername/2 , return value: 102 | %%% ----------------------------------- 103 | %%% {{127,0,0,1},35871} 104 | %%% ''' 105 | %%% 106 | %%% To display (again) the function call chain, 107 | %%% you use the `a(t)' command. With no arguments it will just 108 | %%% re-display the trace output. If you want to go to a particular 109 | %%% line you just give that as an argument. 110 | %%% Example, go to line 10 in the example above: 111 | %%% 112 | %%% ``` 113 | %%% tlist> a 10 114 | %%% 10: <0.258.0> yaws_server:gserv_loop/4 115 | %%% 11: <0.281.0> yaws_server:acceptor0/2 116 | %%% 12: <0.281.0> yaws_server:do_accept/1 117 | %%% 13: <0.259.0> yaws_server:aloop/4 118 | %%% ...snip... 119 | %%% ''' 120 | %%% 121 | %%% To change the number of lines shown of the trace output. 122 | %%% Set it with the `p(age)' command. 123 | %%% Example, display (roughly) 50 lines: 124 | %%% 125 | %%% ``` 126 | %%% tlist> p 50 127 | %%% ''' 128 | %%% 129 | %%% The amount of trace output can be huge so we can search 130 | %%% for a particular function call that we are interested in. 131 | %%% Note that you can specify a RegExp for searching among 132 | %%% the Mod:Fun calls. 133 | %%% 134 | %%% ``` 135 | %%% tlist> f yaws:decode_b 136 | %%% 32: <0.537.0> yaws:decode_base64/1 137 | %%% 33: <0.537.0> yaws:decode_base64/2 138 | %%% 34: <0.537.0> yaws:d/1 139 | %%% ...snip... 140 | %%% ''' 141 | %%% 142 | %%% We can also search in a particular argument of a particular 143 | %%% function call. Here the second argument of yaws:setopts should 144 | %%% contain the string: 'packet_size': 145 | %%% 146 | %%% ``` 147 | %%% tlist> f yaws:setopts 2 packet_size 148 | %%% 22: <0.537.0> yaws:setopts/3 149 | %%% 24: <0.537.0> yaws:do_recv/3 150 | %%% 26: <0.537.0> yaws:http_collect_headers/5 151 | %%% ...snip... 152 | %%% ''' 153 | %%% 154 | %%% We can now verify that it found it: 155 | %%% 156 | %%% ``` 157 | %%% tlist> s 22 2 158 | %%% 159 | %%% Call: yaws:setopts/3 , argument 2: 160 | %%% ----------------------------------- 161 | %%% [{packet,httph},{packet_size,16384}] 162 | %%% ''' 163 | %%% 164 | %%% To search among the return values we use the 'fr' command: 165 | %%% 166 | %%% ``` 167 | %%% tlist> fr GET 168 | %%% 184: <0.537.0> yaws:make_allow_header/1 169 | %%% 187: <0.537.0> yaws_server:deliver_accumulated/1 170 | %%% 188: <0.537.0> yaws:outh_get_content_encoding/0 171 | %%% 190: <0.537.0> yaws:outh_set_content_encoding/1 172 | %%% ...snip... 173 | %%% ''' 174 | %%% 175 | %%% We can now verify that it found it: 176 | %%% 177 | %%% ``` 178 | %%% tlist> r 184 179 | %%% 180 | %%% Call: yaws:make_allow_header/1 , return value: 181 | %%% ----------------------------------- 182 | %%% ["Allow: GET, POST, OPTIONS, HEAD\r\n"] 183 | %%% ''' 184 | %%% 185 | %%% To see more examples visit the `edbg' wiki at: 186 | %%% [https://github.com/etnt/edbg/wiki/Tracing] 187 | %%% 188 | %%% @end 189 | %%% Created : 4 Sep 2017 by Torbjorn Tornkvist 190 | %%%------------------------------------------------------------------- 191 | -module(edbg_tracer). 192 | 193 | -export([file/0 194 | , file/1 195 | , fhelp/0 196 | , fstart/0 197 | , fstart/1 198 | , fstart/2 199 | , fstop/0 200 | , get_traced_pid/0 201 | , lts/0 202 | , send/2 203 | , start_my_tracer/0 204 | , tlist/0 205 | , xfile/0 206 | , xfile/1 207 | ]). 208 | 209 | -import(edbg_file_tracer, 210 | [add_mf_f/1 211 | , cfg_file_f/1 212 | , dump_output_eager_f/0 213 | , dump_output_lazy_f/0 214 | , fname/2 215 | , get_config/0 216 | , get_trace_spec/0 217 | , log_file_f/1 218 | , max_msgs_f/1 219 | , memory_f/0 220 | , mname/2 221 | , monotonic_ts_f/0 222 | , new_mf/0 223 | , send_receive_f/0 224 | , set_config/2 225 | , set_on_f/1 226 | , start_trace/0 227 | , stop_trace/0 228 | , trace_spec_f/1 229 | , trace_time_f/1 230 | ]). 231 | 232 | %% Internal export 233 | -export([tloop/3, 234 | ploop/1, 235 | rloop/2 236 | ]). 237 | 238 | -include("edbg_trace.hrl"). 239 | 240 | %%-define(TEST, true). 241 | -ifdef(TEST). 242 | -include_lib("eunit/include/eunit.hrl"). 243 | -endif. 244 | 245 | 246 | 247 | -define(mytracer, mytracer). 248 | 249 | -define(inside(At,Cur,Page), ((Cur >=At) andalso (Cur =< (At+Page)))). 250 | -define(inside_vl(At,Cur,VL,Page), ((Cur >=At) andalso (VL =< Page))). 251 | 252 | -record(tlist, 253 | { 254 | level = maps:new(), % Key= , Val= 255 | at = 1, 256 | current = 1, 257 | page = 20, 258 | send_receive = true, % do (not) show send/receive msgs 259 | memory = true, % do (not) show memory info 260 | bs = erl_eval:new_bindings(), 261 | 262 | %% -- num of visible lines -- 263 | %% If display of send/receive msgs is turned off 264 | %% then this counter keeps track of how many lines 265 | %% we are currently displaying, making it possible 266 | %% to fill the 'page'. 267 | vlines = 0, 268 | 269 | follow_process = false % false | 270 | }). 271 | 272 | -record(t, { 273 | trace_max = 10000, 274 | tracer, 275 | elixir = false 276 | }). 277 | 278 | %% @private 279 | start_my_tracer() -> 280 | case whereis(?mytracer) of 281 | Pid when is_pid(Pid) -> 282 | Pid; 283 | _ -> 284 | Pid = spawn(fun() -> tinit(#t{}) end), 285 | register(?mytracer, Pid), 286 | Pid 287 | end. 288 | 289 | %% @private 290 | fhelp() -> 291 | S = "\n" 292 | "edbg:fstart(ModFunList, Opts)\n" 293 | "edbg:fstop()\n" 294 | "edbg:file()\n" 295 | "edbg:file(FileName)\n" 296 | "edbg:fpid(Pid)\n" 297 | "edbg:fpid(Pid, Opts)\n" 298 | "\n" 299 | "ModFunList is a list of module names (atoms) or tuples {ModName, FunName}.\n" 300 | "\n" 301 | "Opts is a list of option tuples:\n" 302 | "\n" 303 | "{log_file, FileName} : file where to store trace output\n" 304 | " default is: edbg.trace_result\n" 305 | "{cfg_file, FileName} : file where to store config\n" 306 | " default is: edbg_trace.config\n" 307 | " use 'false' to turn it off\n" 308 | "{max_msgs, MaxNumOfMsgs} : max number of trace messages\n" 309 | " default = 1000\n" 310 | "{trace_time, Seconds} : max time to trace\n" 311 | " default = 10 seconds\n" 312 | "{trace_spec, Spec} : see the erlang:trace/3 docs\n" 313 | " default = all\n" 314 | "dump_output_eager : trace output goes to file often\n" 315 | "dump_output_lazy : trace output goes to file when done (default)\n" 316 | "monotonic_ts : show the elapsed monotonic nano seconds\n" 317 | "send_receive : trace send/receive messages from 'known' pids\n" 318 | "memory : track the memory usage of the 'known' pids\n", 319 | io:format("~s~n",[S]). 320 | 321 | 322 | 323 | %% dbg:tracer(process,{fun(Trace,N) -> 324 | %% io:format("TRACE (#~p): ~p~n",[N,Trace]), 325 | %% N+1 326 | %% end, 0}). 327 | %%dbg:p(all,clear). 328 | %%dbg:p(all,[c]). 329 | 330 | %% @private 331 | file() -> 332 | file("./edbg.trace_result"). 333 | 334 | %% @private 335 | file(Fname) -> 336 | file(Fname, false). 337 | 338 | 339 | %% @private 340 | xfile() -> 341 | xfile("./edbg.trace_result"). 342 | 343 | %% @private 344 | xfile(Fname) -> 345 | file(Fname, true). 346 | 347 | 348 | file(Fname, IsElixir) -> 349 | catch stop_trace(), 350 | catch edbg_file_tracer:stop(), 351 | try file:read_file(Fname) of 352 | {ok, Tdata} when byte_size(Tdata) > 0 -> 353 | call(start_my_tracer(), {elixir, IsElixir}), 354 | %% We expect Tdata to be a list of trace tuples as 355 | %% a binary in the external term form. 356 | call(start_my_tracer(), {load_trace_data, 357 | binary_to_term(Tdata)}), 358 | tlist(); 359 | {ok, _Tdata} -> 360 | {error, empty_trace_file}; 361 | Error -> 362 | Error 363 | catch 364 | _:Err -> 365 | {error, Err} 366 | end. 367 | 368 | %% @private 369 | fstart() -> 370 | edbg_file_tracer:start(), 371 | edbg_file_tracer:load_config(), 372 | start_trace(). 373 | 374 | %% @private 375 | fstart(ModFunList) -> 376 | fstart(ModFunList, []). 377 | 378 | %% @private 379 | fstart(ModFunList, Options) 380 | when is_list(ModFunList) andalso 381 | is_list(Options) -> 382 | edbg_file_tracer:start(), 383 | MF = new_mf(), 384 | MFs = lists:foldr(fun({Mname,Fname}, Acc) -> 385 | [add_mf_f(fname(mname(MF, Mname), Fname))|Acc]; 386 | (Mname, Acc) when is_atom(Mname) -> 387 | [add_mf_f(mname(MF, Mname))|Acc]; 388 | (X, Acc) -> 389 | io:format("Ignoring ModFun: ~p~n",[X]), 390 | Acc 391 | end, [], ModFunList), 392 | 393 | Opts = lists:foldr(fun({log_file, Lname}, Acc) -> 394 | [log_file_f(Lname)|Acc]; 395 | ({cfg_file, Lname}, Acc) -> 396 | [cfg_file_f(Lname)|Acc]; 397 | ({max_msgs, Max}, Acc) -> 398 | [max_msgs_f(Max)|Acc]; 399 | ({trace_time, Time}, Acc) -> 400 | [trace_time_f(Time)|Acc]; 401 | ({trace_spec, Spec}, Acc) -> 402 | [trace_spec_f(Spec)|Acc]; 403 | (dump_output_lazy, Acc) -> 404 | [dump_output_lazy_f()|Acc]; 405 | (dump_output_eager, Acc) -> 406 | [dump_output_eager_f()|Acc]; 407 | (monotonic_ts, Acc) -> 408 | [monotonic_ts_f()|Acc]; 409 | (send_receive, Acc) -> 410 | [send_receive_f()|Acc]; 411 | (memory, Acc) -> 412 | [memory_f()|Acc]; 413 | (set_on_spawn = K, Acc) -> 414 | [set_on_f(K)|Acc]; 415 | (set_on_first_spawn = K, Acc) -> 416 | [set_on_f(K)|Acc]; 417 | (set_on_link = K, Acc) -> 418 | [set_on_f(K)|Acc]; 419 | (set_on_first_link = K, Acc) -> 420 | [set_on_f(K)|Acc]; 421 | (X, Acc) -> 422 | io:format("Ignoring Option: ~p~n",[X]), 423 | Acc 424 | end, [], Options), 425 | set_config(MFs++Opts, get_config()), 426 | start_trace(). 427 | 428 | %% @private 429 | fstop() -> 430 | edbg_file_tracer:stop_trace(), 431 | edbg_file_tracer:stop(). 432 | 433 | 434 | %% @private 435 | get_traced_pid() -> 436 | case get_trace_spec() of 437 | Pid when is_pid(Pid) -> Pid; 438 | _ -> undefined 439 | end. 440 | 441 | 442 | 443 | 444 | call(MyTracer, Msg) -> 445 | MyTracer ! {self(), Msg}, 446 | receive 447 | {MyTracer, Result} -> 448 | Result 449 | end. 450 | 451 | %% @private 452 | lts() -> 453 | {ok,[X]} = file:consult("trace.edbg"), 454 | call(start_my_tracer(), X). 455 | 456 | 457 | %% @private 458 | tlist() -> 459 | Self = self(), 460 | Prompt = spawn_link(fun() -> prompt(Self) end), 461 | print_help(), 462 | ?mytracer ! at, 463 | ploop(Prompt). 464 | 465 | %% @private 466 | ploop(Prompt) -> 467 | receive 468 | {'EXIT', Prompt, _} -> 469 | true; 470 | 471 | quit -> 472 | true; 473 | 474 | _ -> 475 | ?MODULE:ploop(Prompt) 476 | end. 477 | 478 | 479 | prompt(Pid) when is_pid(Pid) -> 480 | Prompt = "tlist> ", 481 | rloop(Pid, Prompt). 482 | 483 | %%% Since OTP-26 the Erlang shell was changed so that edbg's 484 | %%% old prompt-loop (using io:get_line/1) will not have the 485 | %%% input added to the shell history. 486 | %%% This code try to solve this. 487 | get_line(Prompt) when is_list(Prompt) -> 488 | try list_to_integer(erlang:system_info(otp_release)) of 489 | Vsn when Vsn >= 26 -> 490 | edbg_shell_history:get_line(list_to_atom(Prompt)); 491 | _ -> 492 | io:get_line(Prompt) 493 | catch 494 | _:_ -> 495 | io:get_line(Prompt) 496 | end. 497 | 498 | 499 | %% @private 500 | rloop(Pid, Prompt) -> 501 | case string:tokens(b2l(get_line(Prompt)), "\n") of 502 | ["eval"++X] -> xeval(?mytracer, X); 503 | ["xnall"++X] -> xnall(?mytracer, X); 504 | ["xall"++X] -> xall(?mytracer, X); 505 | ["let"++X] -> xlet(?mytracer, X); 506 | ["set"++X] -> xset(?mytracer, X); 507 | ["off"++X] -> off(?mytracer, X); 508 | ["on"++X] -> on(?mytracer, X); 509 | ["pr"++X] -> show_record(?mytracer, X); 510 | ["fr"++X] -> find_retv(?mytracer, X); 511 | ["fp"++_] -> ?mytracer ! follow_process; 512 | ["up"++_] -> ?mytracer ! unfollow_process; 513 | ["f"++X] -> find(?mytracer, X); 514 | ["d"++_] -> ?mytracer ! down; 515 | ["u"++_] -> ?mytracer ! up; 516 | ["t"++_] -> ?mytracer ! top; 517 | ["b"++_] -> ?mytracer ! bottom; 518 | ["a"++X] -> at(?mytracer, X); 519 | ["s"++X] -> show(?mytracer, X); 520 | ["r"++X] -> show_return(?mytracer, X); 521 | ["w"++X] -> show_raw(?mytracer, X); 522 | ["p"++X] -> set_page(?mytracer, X); 523 | ["h"++_] -> print_help(); 524 | ["q"++_] -> 525 | ?mytracer ! quit, 526 | Pid ! quit, 527 | exit(normal); 528 | 529 | _X -> 530 | ?info_msg("prompt got: ~p~n",[_X]) 531 | end, 532 | ?MODULE:rloop(Pid, Prompt). 533 | 534 | b2l(B) when is_binary(B) -> 535 | erlang:binary_to_list(B); 536 | b2l(L) when is_list(L) -> 537 | L. 538 | 539 | 540 | %% Find a match among the return values 541 | find_retv(Pid, X) -> 542 | Xstr = string:strip(X), 543 | Pid ! {find, {ret, Xstr}}. 544 | 545 | %% Search by Regexp 546 | find(Pid, X) -> 547 | try 548 | Xstr = string:strip(X), 549 | {Str,Args} = find_args(Xstr), 550 | Pid ! {find, {rexp, Str, Args}} 551 | catch 552 | _:_ -> false 553 | end. 554 | 555 | 556 | find_args(AStr) -> 557 | find_args(AStr, []). 558 | 559 | find_args([$\\,$\\|T], Acc) -> 560 | find_args(T, [$\\|Acc]); 561 | find_args([$\\,$\s|T], Acc) -> 562 | find_args(T, [$\s|Acc]); 563 | find_args([$\s|T], Acc) -> 564 | Str = lists:reverse(Acc), 565 | case string:tokens(T, " ") of 566 | [An,Av] -> 567 | {Str, {list_to_integer(An),Av}}; 568 | _ -> 569 | {Str, undefined} 570 | end; 571 | find_args([H|T], Acc) -> 572 | find_args(T, [H|Acc]); 573 | find_args([], Acc) -> 574 | Str = lists:reverse(Acc), 575 | {Str, undefined}. 576 | 577 | 578 | -ifdef(EUNIT). 579 | 580 | find_args_test_() -> 581 | [?_assertMatch({"lists:rev", undefined}, 582 | find_args("lists:rev")), 583 | ?_assertMatch({"lists:rev", {1,"rune"}}, 584 | find_args("lists:rev 1 rune")), 585 | ?_assertMatch({"abc def", undefined}, 586 | find_args("abc\\ def")), 587 | ?_assertMatch({"abc def", {2,"rune"}}, 588 | find_args("abc\\ def 2 rune")) 589 | ]. 590 | 591 | -endif. 592 | 593 | 594 | find_pid_before(Pid, {BeforeList, AfterList} = _SplitBuffer) -> 595 | {RevNewBefore, NewAfter} = find_pid_before_helper(Pid, lists:reverse(BeforeList), AfterList), 596 | {lists:reverse(RevNewBefore), NewAfter}. 597 | 598 | find_pid_before_helper(_Pid, [], AfterList) -> 599 | {[], AfterList}; 600 | find_pid_before_helper(Pid, [{_N, ?CALL(Pid, _MFA, _As)} = H | T], AfterList) -> 601 | {T, [H|AfterList]}; 602 | find_pid_before_helper(Pid, [{_N, ?CALL_TS(Pid, _MFA, _Ts, _As)} = H | T], AfterList) -> 603 | {T, [H|AfterList]}; 604 | find_pid_before_helper(Pid, [H | T], AfterList) -> 605 | find_pid_before_helper(Pid, T, [H | AfterList]). 606 | 607 | -ifdef(EUNIT). 608 | 609 | find_pid_before_test_() -> 610 | Pid1 = 1, Pid2 = 2, Pid3 = 3, Pid4 = 4, 611 | 612 | Buffer = [{1, ?CALL(Pid1, mfa, as)}], 613 | 614 | Buffer2 = [{1, ?CALL(Pid1, mfa, as)}, 615 | {2, ?CALL(Pid2, mfa, as)}], 616 | 617 | Buffer3 = [{1, ?CALL(Pid1, mfa, as)}, 618 | {2, ?CALL(Pid2, mfa, as)}, 619 | {3, ?CALL(Pid3, mfa, as)}], 620 | 621 | Buffer4 = [{1, ?CALL(Pid1, mfa, as)}, 622 | {2, ?CALL(Pid2, mfa, as)}, 623 | {3, ?CALL(Pid3, mfa, as)}, 624 | {4, ?CALL(Pid4, mfa, as)}], 625 | 626 | [?_assertMatch({[], [{1, ?CALL(Pid1, mfa, as)}]}, 627 | find_pid_before(Pid1, mk_split_buffer(1, Buffer))), 628 | 629 | ?_assertMatch({[], 630 | [{1, ?CALL(Pid1, mfa, as)}, 631 | {2, ?CALL(Pid2, mfa, as)}]}, 632 | find_pid_before(Pid2, mk_split_buffer(2, Buffer2))), 633 | 634 | ?_assertMatch({[], 635 | [{1, ?CALL(Pid1, mfa, as)}, 636 | {2, ?CALL(Pid2, mfa, as)}]}, 637 | find_pid_before(Pid1, mk_split_buffer(2, Buffer2))), 638 | 639 | ?_assertMatch({[{1, ?CALL(Pid1, mfa, as)}], 640 | [{2, ?CALL(Pid2, mfa, as)}, 641 | {3, ?CALL(Pid3, mfa, as)}]}, 642 | find_pid_before(Pid2, mk_split_buffer(3, Buffer3))), 643 | 644 | ?_assertMatch({[{1, ?CALL(Pid1, mfa, as)}], 645 | [{2, ?CALL(Pid2, mfa, as)}, 646 | {3, ?CALL(Pid3, mfa, as)}, 647 | {4, ?CALL(Pid4, mfa, as)}]}, 648 | find_pid_before(Pid2, mk_split_buffer(4, Buffer4))) 649 | ]. 650 | 651 | -endif. 652 | 653 | 654 | 655 | mk_split_buffer(At, Buf) -> 656 | lists:splitwith(fun({A, _}) -> A < At end, Buf). 657 | 658 | -ifdef(EUNIT). 659 | 660 | mk_split_buffer_test_() -> 661 | Pid1 = 1, Pid2 = 2, Pid3 = 3, 662 | 663 | _Buffer = [{1, {call, Pid1, mfa, as}}], 664 | 665 | _Buffer2 = [{1, {call, Pid1, mfa, as}}, 666 | {2, {call, Pid2, mfa, as}}], 667 | 668 | Buffer3 = [{1, {call, Pid1, mfa, as}}, 669 | {2, {call, Pid2, mfa, as}}, 670 | {3, {call, Pid3, mfa, as}}], 671 | 672 | [?_assertMatch({[{1,a}],[{2,b},{3,c}]}, 673 | mk_split_buffer(2, [{1,a},{2,b},{3,c}])), 674 | ?_assertMatch({[{1,a},{2,b}],[{3,c},{4,d}]}, 675 | mk_split_buffer(3, [{1,a},{2,b},{3,c},{4,d}])), 676 | ?_assertMatch({[], [{1,a},{2,b}]}, 677 | mk_split_buffer(1, [{1,a},{2,b}])), 678 | ?_assertMatch({[{1,a},{2,b}],[]}, 679 | mk_split_buffer(3, [{1,a},{2,b}])), 680 | ?_assertMatch({[{1, {call, Pid1, mfa, as}}], 681 | [{2, {call, Pid2, mfa, as}}, 682 | {3, {call, Pid3, mfa, as}}]}, 683 | mk_split_buffer(2, Buffer3)) 684 | ]. 685 | 686 | -endif. 687 | 688 | 689 | 690 | on(Pid, X) -> 691 | case string:strip(X) of 692 | "send_receive" -> 693 | Pid ! {on, send_receive}; 694 | "memory" -> 695 | Pid ! {on, memory}; 696 | _ -> 697 | false 698 | end. 699 | 700 | off(Pid, X) -> 701 | case string:strip(X) of 702 | "send_receive" -> 703 | Pid ! {off, send_receive}; 704 | "memory" -> 705 | Pid ! {off, memory}; 706 | _ -> 707 | false 708 | end. 709 | 710 | %% Unload/Remove the current a module and re-load it from the code path! 711 | xnall(_Pid, X) -> 712 | try 713 | {ModStr, _} = get_first_token(string:strip(X, left)), 714 | Module = list_to_atom(ModStr), 715 | reload_module(Module), 716 | c:m(Module), 717 | true 718 | catch 719 | _:_ -> false 720 | end. 721 | 722 | reload_module(Module) -> 723 | case code:delete(Module) of 724 | false -> 725 | code:purge(Module), 726 | code:delete(Module); 727 | true -> 728 | true 729 | end, 730 | {module, Module} = code:load_file(Module). 731 | 732 | 733 | 734 | 735 | %% Re-Compile and load a module with the export-all compiler option! 736 | xall(_Pid, X) -> 737 | try 738 | {ModStr, _} = get_first_token(string:strip(X, left)), 739 | Module = list_to_atom(ModStr), 740 | recompile_as_export_all(Module), 741 | c:m(Module), 742 | true 743 | catch 744 | _:_ -> false 745 | end. 746 | 747 | recompile_as_export_all(Module) -> 748 | %%Cs = Module:module_info(compile), 749 | %%{source, SrcFname} = lists:keyfind(source, 1, Cs), 750 | SrcFname = edbg:find_source(Module), 751 | File = filename:rootname(SrcFname, ".erl"), 752 | {ok, Module, Code} = compile:file(File, [export_all,binary]), 753 | code:load_binary(Module, File, Code). 754 | 755 | xeval(Pid, X) -> 756 | try 757 | Pid ! {xeval, X} 758 | catch 759 | _:_ -> false 760 | end. 761 | 762 | xlet(Pid, X) -> 763 | try 764 | {Var, ExprStr} = get_first_token(string:strip(X, left)), 765 | Pid ! {xlet, Var, ExprStr} 766 | catch 767 | _:_ -> false 768 | end. 769 | 770 | get_first_token(Str) -> 771 | string:take(Str, " ", true, leading). 772 | 773 | xset(Pid, X) -> 774 | try 775 | case string:tokens(string:strip(X), " ") of 776 | [Var,A] -> 777 | %% Bind Var to Return-Value 778 | Pid ! {xset, Var, list_to_integer(A)}; 779 | 780 | [Var,A,B] -> 781 | %% Bind Var to Argument-Value 782 | Pid ! {xset, Var, list_to_integer(A), list_to_integer(B)}; 783 | 784 | [Var,A,B,R] -> 785 | %% Bind Var to a record field in the Argument-Value 786 | %% Example: string:tokens("#arg.client_ip_port","#."). 787 | case string:tokens(string:strip(R), "#.") of 788 | [Record,Field] -> 789 | Pid ! {xset, Var, 790 | list_to_integer(A),list_to_integer(B), 791 | Record, Field}; 792 | _ -> 793 | ?info_msg("~ncould not parse record field: ~p~n",[R]), 794 | Pid ! {xset, Var, 795 | list_to_integer(A), list_to_integer(B)} 796 | end 797 | end 798 | catch 799 | _:_ -> false 800 | end. 801 | 802 | show(Pid, X) -> 803 | parse_integers(Pid, X, show). 804 | 805 | show_record(Pid, X) -> 806 | parse_integers(Pid, X, show_record). 807 | 808 | set_page(Pid, X) -> 809 | parse_integers(Pid, X, set_page). 810 | 811 | at(Pid, X) -> 812 | parse_integers(Pid, X, at). 813 | 814 | show_return(Pid, X) -> 815 | parse_integers(Pid, X, show_return). 816 | 817 | show_raw(Pid, X) -> 818 | parse_integers(Pid, X, show_raw). 819 | 820 | parse_integers(Pid, X, Msg) -> 821 | try 822 | case string:tokens(string:strip(b2l(X)), b2l(" ")) of 823 | [] -> 824 | Pid ! Msg; 825 | [A] -> 826 | Pid ! {Msg, list_to_integer(A)}; 827 | [A,B] -> 828 | Pid ! {Msg, list_to_integer(A), list_to_integer(B)} 829 | end 830 | catch 831 | _:_ -> false 832 | end. 833 | 834 | 835 | print_help() -> 836 | S1 = " (h)elp (a)t [] (d)own (u)p (t)op (b)ottom", 837 | S2 = " (s)how [] (r)etval ra(w) ", 838 | S3 = " (pr)etty print record ", 839 | S4 = " (f)ind [ ] (fr) ", 840 | S5 = " (fp) follow process | (up) unfollow process", 841 | S6 = " (on)/(off) send_receive | memory", 842 | S7 = " (p)agesize (q)uit", 843 | S8 = " (set) [] (let) ", 844 | S9 = " (eval) (xall/xnall) ", 845 | S = io_lib:format("~n~s~n~s~n~s~n~s~n~s~n~s~n~s~n~s~n~s~n", 846 | [S1,S2,S3,S4,S5,S6,S7,S8,S9]), 847 | ?info_msg(?help_hi(S), []). 848 | 849 | 850 | 851 | 852 | tinit(X) -> 853 | process_flag(trap_exit, true), 854 | ?MODULE:tloop(X, #tlist{}, []). 855 | 856 | 857 | %% @private 858 | tloop(#t{trace_max = MaxTrace} = X, Tlist, Buf) -> 859 | receive 860 | 861 | %% FROM THE TRACE FILTER 862 | 863 | %% Trace everything until Max is reached. 864 | {trace, From, {N,_Trace} = Msg} when N =< MaxTrace -> 865 | reply(From, ok), 866 | ?MODULE:tloop(X, Tlist ,[Msg|Buf]); 867 | 868 | %% Max is reached; stop tracing! 869 | {trace, From , {N,_Trace} = _Msg} when N > MaxTrace -> 870 | reply(From, stop), 871 | dbg:stop(), 872 | ?MODULE:tloop(X#t{tracer = undefined}, Tlist ,Buf); 873 | 874 | 875 | %% FROM EDBG 876 | 877 | {From, {elixir, Bool}} -> 878 | From ! {self(), ok}, 879 | ?MODULE:tloop(X#t{elixir = Bool}, Tlist ,Buf); 880 | 881 | {max, N} -> 882 | ?MODULE:tloop(X#t{trace_max = N}, Tlist ,Buf); 883 | 884 | {set_page, Page} -> 885 | ?MODULE:tloop(X, Tlist#tlist{page = Page} ,Buf); 886 | 887 | {show_raw, N} -> 888 | dbg:stop(), 889 | case lists:keyfind(N, 1, Buf) of 890 | {_, Msg} -> 891 | ?info_msg("~n~p~n", [Msg]); 892 | _ -> 893 | ?err_msg("not found~n",[]) 894 | end, 895 | ?MODULE:tloop(X, Tlist ,Buf); 896 | 897 | {show_return, N} -> 898 | dbg:stop(), 899 | case get_return_value(N, lists:reverse(Buf)) of 900 | {ok, {M,F,Alen}, RetVal} -> 901 | Sep = pad(35, $-), 902 | ?info_msg("~nCall: ~p:~p/~p , return value:~n~s~n~p~n", 903 | [M,F,Alen,Sep,RetVal]); 904 | not_found -> 905 | ?info_msg("~nNo return value found!~n",[]) 906 | end, 907 | ?MODULE:tloop(X, Tlist ,Buf); 908 | 909 | {show, N} -> 910 | dbg:stop(), 911 | mlist(N, Buf), 912 | ?MODULE:tloop(X, Tlist ,Buf); 913 | 914 | {show, N, ArgN} -> 915 | dbg:stop(), 916 | try 917 | case lists:keyfind(N, 1, Buf) of 918 | {_, ?CALL(_Pid, MFA, _As)} -> 919 | show_arg(ArgN, MFA); 920 | 921 | {_, ?CALL_TS(_Pid, MFA, _TS, _As)} -> 922 | show_arg(ArgN, MFA); 923 | 924 | _ -> 925 | ?err_msg("not found~n",[]) 926 | end 927 | catch 928 | _:_ -> ?err_msg("not found~n",[]) 929 | end, 930 | ?MODULE:tloop(X, Tlist ,Buf); 931 | 932 | {show_record, N, ArgN} -> 933 | dbg:stop(), 934 | try 935 | case lists:keyfind(N, 1, Buf) of 936 | {_, ?CALL(_Pid, MFA, _As)} -> 937 | show_rec(ArgN, MFA); 938 | 939 | {_, ?CALL_TS(_Pid, MFA, _TS, _As)} -> 940 | show_rec(ArgN, MFA); 941 | 942 | _ -> 943 | ?err_msg("not found~n",[]) 944 | end 945 | catch 946 | _:_ -> 947 | ?err_msg("not found~n",[]) 948 | end, 949 | ?MODULE:tloop(X, Tlist ,Buf); 950 | 951 | %% Set Variable to the specified Return-Value 952 | {xset, Var, N} -> 953 | dbg:stop(), 954 | NewTlist = 955 | try 956 | case get_return_value(N, lists:reverse(Buf)) of 957 | {ok, {_M,_F,_Alen}, RetVal} -> 958 | add_binding(Tlist, Var, RetVal); 959 | 960 | not_found -> 961 | ?info_msg("~nNo return value found!~n",[]), 962 | Tlist 963 | end 964 | catch 965 | _:_ -> 966 | ?err_msg("unexpected error~n",[]), 967 | Tlist 968 | end, 969 | ?MODULE:tloop(X, NewTlist ,Buf); 970 | 971 | %% Set Variable to the specified Argument-Value 972 | {xset, Var, N, ArgN} -> 973 | dbg:stop(), 974 | NewTlist = 975 | try 976 | case lists:keyfind(N, 1, Buf) of 977 | {_, ?CALL(_Pid, MFA, _As)} -> 978 | Val = get_arg(ArgN, MFA), 979 | add_binding(Tlist, Var, Val); 980 | 981 | {_, ?CALL_TS(_Pid, MFA, _TS, _As)} -> 982 | Val = get_arg(ArgN, MFA), 983 | add_binding(Tlist, Var, Val); 984 | 985 | _ -> 986 | ?err_msg("not found~n",[]), 987 | Tlist 988 | end 989 | catch 990 | _:_ -> 991 | ?err_msg("unexpected error~n",[]), 992 | Tlist 993 | end, 994 | ?MODULE:tloop(X, NewTlist ,Buf); 995 | 996 | %% Set Variable to the specified record field of Argument-Value 997 | {xset, Var, N, ArgN, RecordStr, FieldStr} -> 998 | dbg:stop(), 999 | Record = list_to_atom(RecordStr), 1000 | Field = list_to_atom(FieldStr), 1001 | NewTlist = 1002 | try 1003 | case lists:keyfind(N, 1, Buf) of 1004 | {_, ?CALL(_Pid, MFA, _As)} -> 1005 | Val = get_arg(ArgN, MFA), 1006 | FieldIndex = record_field_index(MFA, Record, Field), 1007 | add_binding(Tlist, Var, element(FieldIndex,Val)); 1008 | 1009 | {_, ?CALL_TS(_Pid, MFA, _TS, _As)} -> 1010 | Val = get_arg(ArgN, MFA), 1011 | FieldIndex = record_field_index(MFA, Record, Field), 1012 | add_binding(Tlist, Var, element(FieldIndex,Val)); 1013 | 1014 | _ -> 1015 | ?err_msg("not found~n",[]), 1016 | Tlist 1017 | end 1018 | catch 1019 | _:_ -> 1020 | ?err_msg("unexpected error~n",[]), 1021 | Tlist 1022 | end, 1023 | ?MODULE:tloop(X, NewTlist ,Buf); 1024 | 1025 | {xlet, Var, ExprStr} -> 1026 | dbg:stop(), 1027 | NewTlist = 1028 | try 1029 | {ok, Tokens, _} = erl_scan:string(ExprStr), 1030 | {ok, Exprs} = erl_parse:parse_exprs(Tokens), 1031 | {value, Result, _} = erl_eval:exprs(Exprs, Tlist#tlist.bs), 1032 | ?info_msg("~p~n",[Result]), 1033 | add_binding(Tlist, Var, Result) 1034 | catch 1035 | _:_ -> 1036 | ?err_msg("unexpected error~n",[]), 1037 | Tlist 1038 | end, 1039 | ?MODULE:tloop(X, NewTlist ,Buf); 1040 | 1041 | {xeval, ExprStr} -> 1042 | dbg:stop(), 1043 | try 1044 | {ok, Tokens, _} = erl_scan:string(ExprStr), 1045 | {ok, Exprs} = erl_parse:parse_exprs(Tokens), 1046 | case catch erl_eval:exprs(Exprs, Tlist#tlist.bs) of 1047 | {value, Result, _} -> 1048 | ?info_msg("~p~n",[Result]); 1049 | Else -> 1050 | ?err_msg("~n~p~n",[Else]) 1051 | end 1052 | catch 1053 | _:Err -> 1054 | ?err_msg("parse/eval error: ~n~p~n",[Err]) 1055 | end, 1056 | ?MODULE:tloop(X, Tlist ,Buf); 1057 | 1058 | 1059 | {find, What} -> 1060 | NewTlist = do_find(Tlist, Buf, What), 1061 | ?MODULE:tloop(X, NewTlist ,Buf); 1062 | 1063 | top -> 1064 | NewTlist = list_trace(Tlist#tlist{at = 0}, Buf), 1065 | ?MODULE:tloop(X, NewTlist, Buf); 1066 | 1067 | bottom -> 1068 | {N,_} = hd(Buf), 1069 | NewTlist = list_trace(Tlist#tlist{at = N}, Buf), 1070 | ?MODULE:tloop(X, NewTlist, Buf); 1071 | 1072 | {on, send_receive} -> 1073 | ?info_msg("turning on display of send/receive messages~n",[]), 1074 | ?MODULE:tloop(X, Tlist#tlist{send_receive = true}, Buf); 1075 | 1076 | {on, memory} -> 1077 | ?info_msg("turning on display of memory usage~n",[]), 1078 | ?MODULE:tloop(X, Tlist#tlist{memory = true}, Buf); 1079 | 1080 | {off, send_receive} -> 1081 | ?info_msg("turning off display of send/receive messages~n",[]), 1082 | ?MODULE:tloop(X, Tlist#tlist{send_receive = false}, Buf); 1083 | 1084 | {off, memory} -> 1085 | ?info_msg("turning off display of memory usage~n",[]), 1086 | ?MODULE:tloop(X, Tlist#tlist{memory = false}, Buf); 1087 | 1088 | follow_process -> 1089 | At = erlang:max(0, Tlist#tlist.at - Tlist#tlist.page - 1), 1090 | CurrentPid = get_current_pid(At, Buf), 1091 | ?info_msg("following process: ~p~n",[CurrentPid]), 1092 | ?MODULE:tloop(X, Tlist#tlist{follow_process = CurrentPid}, Buf); 1093 | 1094 | unfollow_process -> 1095 | ?info_msg("unfollowing process~n",[]), 1096 | ?MODULE:tloop(X, Tlist#tlist{follow_process = false}, Buf); 1097 | 1098 | at -> 1099 | NewAt = erlang:max(0, Tlist#tlist.at - Tlist#tlist.page - 1), 1100 | NewTlist = list_trace(Tlist#tlist{at = NewAt}, Buf), 1101 | ?MODULE:tloop(X, NewTlist, Buf); 1102 | 1103 | {at, At} -> 1104 | NewTlist = list_trace(Tlist#tlist{at = At}, Buf), 1105 | ?MODULE:tloop(X, NewTlist, Buf); 1106 | 1107 | up -> 1108 | NewAt0 = erlang:max(0, Tlist#tlist.at - (2*Tlist#tlist.page)), 1109 | NewAt = case Tlist#tlist.follow_process of 1110 | FPid when is_pid(FPid) -> 1111 | %% When following process! 1112 | try 1113 | {_Before,[{N,_} | _After]} = 1114 | find_pid_before(FPid, 1115 | mk_split_buffer(NewAt0, 1116 | lists:reverse(Buf))), 1117 | N 1118 | catch 1119 | _:_ -> 1120 | NewAt0 1121 | end; 1122 | _ -> 1123 | NewAt0 1124 | end, 1125 | NewTlist = list_trace(Tlist#tlist{at = NewAt}, Buf), 1126 | ?MODULE:tloop(X, NewTlist, Buf); 1127 | 1128 | down -> 1129 | dbg:stop(), 1130 | NewTlist = list_trace(Tlist, Buf), 1131 | ?MODULE:tloop(X, NewTlist, Buf); 1132 | 1133 | {raw, N} -> 1134 | case lists:keyfind(N, 1, Buf) of 1135 | {_,V} -> ?info_msg("~p~n",[V]); 1136 | _ -> ?info_msg("nothing found!~n",[]) 1137 | end, 1138 | ?MODULE:tloop(X, Tlist ,Buf); 1139 | 1140 | stop -> 1141 | dbg:stop(), 1142 | ?MODULE:tloop(X#t{tracer = undefined}, Tlist ,Buf); 1143 | 1144 | quit -> 1145 | dbg:stop(), 1146 | exit(quit); 1147 | 1148 | {From, {load_trace_data, TraceData}} -> 1149 | From ! {self(), ok}, 1150 | ?MODULE:tloop(X, Tlist ,TraceData); 1151 | 1152 | {From, {start, Start, Opts}} -> 1153 | TraceSetupMod = get_trace_setup_mod(Opts), 1154 | TraceMax = get_trace_max(Opts), 1155 | case TraceSetupMod:start_tracer(Start) of 1156 | {ok, NewTracer} -> 1157 | From ! {self(), started}, 1158 | save_start_trace({start, Start, Opts}), 1159 | ?MODULE:tloop(X#t{trace_max = TraceMax, 1160 | tracer = NewTracer}, Tlist ,[]); 1161 | {error, _} = Error -> 1162 | From ! {self(), Error}, 1163 | ?MODULE:tloop(X, Tlist ,Buf) 1164 | end; 1165 | 1166 | _X -> 1167 | %%?info_msg("mytracer got: ~p~n",[_X]), 1168 | ?MODULE:tloop(X, Tlist ,Buf) 1169 | end. 1170 | 1171 | %% Find a match among the return values 1172 | do_find(Tlist, Buf, {ret, Str}) -> 1173 | case find_retval(Tlist#tlist.at, Buf, Str) of 1174 | not_found -> 1175 | ?info_msg("not found~n",[]), 1176 | Tlist; 1177 | NewAt -> 1178 | list_trace(Tlist#tlist{at = NewAt}, Buf) 1179 | end; 1180 | do_find(Tlist, Buf, {rexp, Str, undefined}) -> 1181 | case xfind_mf(Tlist#tlist.at, Buf, Str) of 1182 | not_found -> 1183 | ?info_msg("not found~n",[]), 1184 | Tlist; 1185 | NewAt -> 1186 | list_trace(Tlist#tlist{at = NewAt}, Buf) 1187 | end; 1188 | do_find(Tlist, Buf, {rexp, Str, {An,Av}}) -> 1189 | case xfind_mf_av(Tlist#tlist.at, Buf, Str, An, Av) of 1190 | not_found -> 1191 | ?info_msg("not found~n",[]), 1192 | Tlist; 1193 | NewAt -> 1194 | list_trace(Tlist#tlist{at = NewAt}, Buf) 1195 | end. 1196 | 1197 | 1198 | get_current_pid(At, Buf) -> 1199 | case lists:keyfind(At, 1, Buf) of 1200 | {_,{trace, Pid, _, _, _}} -> 1201 | Pid; 1202 | {_,{trace_ts, Pid, _, _, _, _}} -> 1203 | Pid; 1204 | _ -> 1205 | false 1206 | end. 1207 | 1208 | 1209 | add_binding(#tlist{bs = Bs} = T, Var, Val) -> 1210 | T#tlist{bs = erl_eval:add_binding(list_to_atom(Var), Val, Bs)}. 1211 | 1212 | get_arg(ArgN, {_M,_F,A}) -> 1213 | lists:nth(ArgN, A). 1214 | 1215 | show_arg(ArgN, {M,F,A}) -> 1216 | Sep = pad(35, $-), 1217 | ArgStr = "argument "++integer_to_list(ArgN)++":", 1218 | ?info_msg("~nCall: ~p:~p/~p , ~s~n~s~n~p~n", 1219 | [M,F,length(A),ArgStr,Sep,lists:nth(ArgN,A)]). 1220 | 1221 | show_rec(ArgN, {M,F,A}) -> 1222 | Sep = pad(35, $-), 1223 | Fname = edbg:find_source(M), 1224 | {ok, Defs} = pp_record:read(Fname), 1225 | ArgStr = "argument "++integer_to_list(ArgN)++":", 1226 | ?info_msg("~nCall: ~p:~p/~p , ~s~n~s~n~s~n", 1227 | [M,F,length(A),ArgStr,Sep, 1228 | pp_record:print(lists:nth(ArgN,A), Defs)]). 1229 | 1230 | 1231 | %% Do a RegExp search in "Mod:Fun" 1232 | xfind_mf(At, Buf, Str) -> 1233 | %% First get the set of trace messages to investigate 1234 | L = lists:takewhile( 1235 | fun({N,_}) when N>=At -> true; 1236 | (_) -> false 1237 | end, Buf), 1238 | %% Discard non-matching calls 1239 | R = lists:dropwhile( 1240 | fun({_N, ?CALL(_Pid, {M,F,_}, _As)}) -> 1241 | re_match(atom_to_list(M)++":"++atom_to_list(F), Str); 1242 | ({_N, ?CALL_TS(_Pid, {M,F,_}, _TS, _As)}) -> 1243 | re_match(atom_to_list(M)++":"++atom_to_list(F), Str); 1244 | (_) -> 1245 | true 1246 | end, lists:reverse(L)), 1247 | case R of 1248 | [{N,_}|_] -> N; 1249 | _ -> not_found 1250 | end. 1251 | 1252 | 1253 | xfind_mf_av(At, Buf, Str, An, Av) -> 1254 | %% First get the set of trace messages to investigate 1255 | L = lists:takewhile( 1256 | fun({N,_}) when N>=At -> true; 1257 | (_) -> false 1258 | end, Buf), 1259 | %% Discard non-matching calls 1260 | R = lists:dropwhile( 1261 | fun({_N, ?CALL(_Pid, {M,F,A}, _As)}) when length(A) >= An -> 1262 | Arg = lists:nth(An, A), 1263 | re_match(atom_to_list(M)++":"++atom_to_list(F), Str) 1264 | orelse 1265 | re_match(lists:flatten(io_lib:format("~p",[Arg])), Av); 1266 | ({_N, ?CALL_TS(_Pid, {M,F,A}, _TS, _As)}) when length(A) >= An -> 1267 | Arg = lists:nth(An, A), 1268 | re_match(atom_to_list(M)++":"++atom_to_list(F), Str) 1269 | orelse 1270 | re_match(lists:flatten(io_lib:format("~p",[Arg])), Av); 1271 | (_) -> 1272 | true 1273 | end, lists:reverse(L)), 1274 | case R of 1275 | [{N,_}|_] -> N; 1276 | _ -> not_found 1277 | end. 1278 | 1279 | 1280 | 1281 | re_match(Str, Rexp) -> 1282 | try re:run(Str, Rexp) of 1283 | nomatch -> true; 1284 | _ -> false 1285 | catch 1286 | _:_ -> true 1287 | end. 1288 | 1289 | 1290 | get_buf_at(At, Buf) -> 1291 | lists:takewhile( 1292 | fun({N,_}) when N>=At -> true; 1293 | (_) -> false 1294 | end, Buf). 1295 | 1296 | get_buf_before_at(At, Buf) -> 1297 | lists:dropwhile( 1298 | fun({N,_}) when N>=At -> true; 1299 | (_) -> false 1300 | end, Buf). 1301 | 1302 | 1303 | find_retval(At, Buf, Str) -> 1304 | %% First get the set of trace messages to investigate 1305 | L = get_buf_at(At, Buf), 1306 | %% Discard non-matching return values 1307 | try 1308 | lists:foldl( 1309 | fun({N, ?RETURN_FROM(_Pid, MFA, Value, _As)}=X,_Acc) -> 1310 | do_find_retval(N, Str, Value, X, MFA, Buf); 1311 | ({N, ?RETURN_FROM_TS(_Pid, MFA, Value, _TS, _As)}=X,_Acc) -> 1312 | do_find_retval(N, Str, Value, X, MFA, Buf); 1313 | (X, Acc) -> 1314 | [X|Acc] 1315 | end, [], lists:reverse(L)), 1316 | not_found 1317 | catch 1318 | throw:{matching_call,{N,_}} -> N; 1319 | _:_ -> not_found 1320 | end. 1321 | 1322 | do_find_retval(At, Str, Value, X, MFA, Buf) -> 1323 | L = get_buf_before_at(At, Buf), 1324 | ValStr = lists:flatten(io_lib:format("~p",[Value])), 1325 | try re:run(ValStr, Str) of 1326 | nomatch -> [X|Buf]; 1327 | _ -> find_matching_call(MFA, L, 0) 1328 | catch 1329 | _:_ -> [X|Buf] 1330 | end. 1331 | 1332 | %% X = {Trace(_ts), Pid, CallOrReturnFrom, MFA, ...} 1333 | -define(m(X), element(1,element(4,X))). 1334 | -define(f(X), element(2,element(4,X))). 1335 | -define(a(X), element(3,element(4,X))). 1336 | -define(l(X), length(element(3,element(4,X)))). 1337 | 1338 | %% Will throw exception at success; crash if nothing is found! 1339 | find_matching_call({M,F,A}, [{_N,Trace}=X|_], 0) 1340 | when ?is_trace_call(Trace) andalso 1341 | ?m(Trace) == M andalso 1342 | ?f(Trace) == F andalso 1343 | ?l(Trace) == A -> 1344 | throw({matching_call,X}); 1345 | find_matching_call({M,F,A}=MFA, [{_N,Trace}|L], N) 1346 | when ?is_trace_call(Trace) andalso 1347 | ?m(Trace) == M andalso 1348 | ?f(Trace) == F andalso 1349 | ?l(Trace) == A -> 1350 | find_matching_call(MFA, L, N-1); 1351 | find_matching_call({M,F,A}=MFA, [{_N,Trace}|L], N) 1352 | when ?is_trace_return_from(Trace) andalso 1353 | ?m(Trace) == M andalso 1354 | ?f(Trace) == F andalso 1355 | ?a(Trace) == A -> 1356 | find_matching_call(MFA, L, N+1); 1357 | find_matching_call(MFA, [{_N,_Trace}|L], N) -> 1358 | find_matching_call(MFA, L, N). 1359 | 1360 | 1361 | get_trace_setup_mod(Opts) -> 1362 | get_opts(Opts, setup_mod, edbg_trace_filter). 1363 | 1364 | get_trace_max(Opts) -> 1365 | get_opts(Opts, trace_max, 10000). 1366 | 1367 | get_opts(Opts, Key, Default) -> 1368 | case lists:keyfind(Key, 1, Opts) of 1369 | {Key, Mod} -> Mod; 1370 | _ -> Default 1371 | end. 1372 | 1373 | 1374 | save_start_trace(X) -> 1375 | {ok,Fd} = file:open("trace.edbg",[write]), 1376 | try 1377 | io:format(Fd, "~p.~n", [X]) 1378 | after 1379 | file:close(Fd) 1380 | end. 1381 | 1382 | field_size([{N,_}|_]) -> 1383 | integer_to_list(length(integer_to_list(N))); 1384 | field_size(_) -> 1385 | "1". % shouldn't happen... 1386 | 1387 | -define(is_followed(Pid, FPid), (FPid == false orelse FPid == Pid)). 1388 | 1389 | list_trace(Tlist, Buf) -> 1390 | maybe_put_first_timestamp(Buf), 1391 | Fs = field_size(Buf), 1392 | FPid = Tlist#tlist.follow_process, 1393 | Zlist = 1394 | lists:foldr( 1395 | 1396 | %% C A L L 1397 | fun({N, ?CALL(Pid, {M,F,A}, As)}, 1398 | #tlist{level = LevelMap, 1399 | memory = MemoryP, 1400 | at = At, 1401 | vlines = VL, 1402 | page = Page} = Z) 1403 | when ?inside_vl(At,N,VL,Page) andalso ?is_followed(Pid, FPid) -> 1404 | Level = maps:get(Pid, LevelMap, 0), 1405 | MPid = mpid(MemoryP, Pid, As), 1406 | ?info_msg("~"++Fs++".s:~s ~s ~p:~p/~p~n", 1407 | [integer_to_list(N),pad(Level),MPid,M,F,length(A)]), 1408 | Z#tlist{vlines = VL + 1, 1409 | level = maps:put(Pid, Level+1, LevelMap)}; 1410 | 1411 | ({N, ?CALL_TS(Pid, {M,F,A}, TS, As)}, 1412 | #tlist{level = LevelMap, 1413 | memory = MemoryP, 1414 | at = At, 1415 | vlines = VL, 1416 | page = Page} = Z) 1417 | when ?inside_vl(At,N,VL,Page) andalso ?is_followed(Pid, FPid) -> 1418 | Level = maps:get(Pid, LevelMap, 0), 1419 | MPid = mpid(MemoryP, Pid, As), 1420 | ?info_msg("~"++Fs++".s:~s ~s ~p:~p/~p - ~p~n", 1421 | [integer_to_list(N),pad(Level),MPid,M,F,length(A), 1422 | xts(TS)]), 1423 | Z#tlist{vlines = VL + 1, 1424 | level = maps:put(Pid, Level+1, LevelMap)}; 1425 | 1426 | ({_N, ?CALL(Pid, {_M,_F,_A}, _As}), 1427 | #tlist{level = LevelMap} = Z) -> 1428 | Level = maps:get(Pid, LevelMap, 0), 1429 | Z#tlist{level = maps:put(Pid, Level+1, LevelMap)}; 1430 | 1431 | ({_N, ?CALL_TS(Pid, {_M,_F,_A}, _TS, _As)}, 1432 | #tlist{level = LevelMap} = Z) -> 1433 | Level = maps:get(Pid, LevelMap, 0), 1434 | Z#tlist{level = maps:put(Pid, Level+1, LevelMap)}; 1435 | 1436 | %% R E T U R N _ F R O M 1437 | ({_N, ?RETURN_FROM(Pid, _MFA, _Value, _As)}, 1438 | #tlist{level = LevelMap} = Z) -> 1439 | Level = maps:get(Pid, LevelMap, 0), 1440 | Z#tlist{level = maps:put(Pid,erlang:max(Level-1,0),LevelMap)}; 1441 | 1442 | ({_N, ?RETURN_FROM_TS(Pid, _MFA, _Value, _TS, _As)}, 1443 | #tlist{level = LevelMap} = Z) -> 1444 | Level = maps:get(Pid, LevelMap, 0), 1445 | Z#tlist{level = maps:put(Pid,erlang:max(Level-1,0),LevelMap)}; 1446 | 1447 | %% S E N D 1448 | ({N, ?SEND(FromPid, Msg, ToPid, _As)}, 1449 | #tlist{send_receive = true, 1450 | level = LevelMap, 1451 | at = At, 1452 | vlines = VL, 1453 | page = Page} = Z) 1454 | when ?inside_vl(At,N,VL,Page) andalso ?is_followed(FromPid, FPid) -> 1455 | Level = maps:get(FromPid, LevelMap, 0), 1456 | ?info_msg("~"++Fs++".s:~s >>> Send(~p) -> To(~p) ~s~n", 1457 | [integer_to_list(N),pad(Level), 1458 | FromPid,ToPid,truncate(Msg)]), 1459 | Z#tlist{vlines = VL + 1}; 1460 | ({_N, ?SEND(_FromPid, _Msg, _ToPid, _As)}, Z) -> 1461 | Z; 1462 | 1463 | ({N, ?SEND_TS(FromPid, Msg, ToPid, TS, _As)}, 1464 | #tlist{send_receive = true, 1465 | level = LevelMap, 1466 | at = At, 1467 | vlines = VL, 1468 | page = Page} = Z) 1469 | when ?inside_vl(At,N,VL,Page) -> 1470 | Level = maps:get(FromPid, LevelMap, 0), 1471 | ?info_msg("~"++Fs++".s:~s >>> Send(~p) -> To(~p) ~s - ~p~n", 1472 | [integer_to_list(N),pad(Level), 1473 | FromPid,ToPid,truncate(Msg),xts(TS)]), 1474 | Z#tlist{vlines = VL + 1}; 1475 | ({_N, ?SEND_TS(_FromPid, _Msg, _ToPid, _TS, _As)}, Z) -> 1476 | Z; 1477 | 1478 | %% R E C E I V E 1479 | ({N, ?RECEIVE(ToPid, Msg, _As)}, 1480 | #tlist{send_receive = true, 1481 | level = LevelMap, 1482 | at = At, 1483 | vlines = VL, 1484 | page = Page} = Z) 1485 | when ?inside_vl(At,N,VL,Page) andalso ?is_followed(ToPid, FPid) -> 1486 | Level = maps:get(ToPid, LevelMap, 0), 1487 | ?info_msg("~"++Fs++".s:~s <<< Receive(~p) ~s~n", 1488 | [integer_to_list(N),pad(Level), 1489 | ToPid,truncate(Msg)]), 1490 | Z#tlist{vlines = VL + 1}; 1491 | ({_N, ?RECEIVE(_ToPid, _Msg, _As)}, Z) -> 1492 | Z; 1493 | 1494 | ({N, ?RECEIVE_TS(ToPid, Msg, TS, _As)}, 1495 | #tlist{send_receive = true, 1496 | level = LevelMap, 1497 | at = At, 1498 | vlines = VL, 1499 | page = Page} = Z) 1500 | when ?inside_vl(At,N,VL,Page) -> 1501 | Level = maps:get(ToPid, LevelMap, 0), 1502 | ?info_msg("~"++Fs++".s:~s <<< Receive(~p) ~s - ~p~n", 1503 | [integer_to_list(N),pad(Level), 1504 | ToPid,truncate(Msg),xts(TS)]), 1505 | Z#tlist{vlines = VL + 1}; 1506 | ({_N, ?RECEIVE_TS(_ToPid, _Msg, _TS, _As)}, Z) -> 1507 | Z 1508 | 1509 | end, Tlist#tlist{vlines = 0, 1510 | level = maps:new()}, Buf), 1511 | 1512 | NewAt = Tlist#tlist.at + Tlist#tlist.page + 1, 1513 | Zlist#tlist{at = NewAt}. 1514 | 1515 | mpid(true = _MemoryP, Pid, As) -> 1516 | case lists:keyfind(memory, 1, As) of 1517 | {_, Mem} when is_integer(Mem) -> 1518 | pid_to_list(Pid)++"("++integer_to_list(Mem)++")"; 1519 | _ -> 1520 | pid_to_list(Pid) 1521 | end; 1522 | mpid(_MemoryP , Pid, _As) -> 1523 | pid_to_list(Pid). 1524 | 1525 | 1526 | truncate(Term) -> 1527 | truncate(Term, 20). 1528 | 1529 | truncate(Term, Length) -> 1530 | string:slice(io_lib:format("~p",[Term]), 0, Length)++"...". 1531 | 1532 | %% Elapsed monotonic time since first trace message 1533 | xts(TS) -> 1534 | case get(first_monotonic_timestamp) of 1535 | undefined -> 1536 | 0; 1537 | XTS -> 1538 | TS - XTS 1539 | end. 1540 | 1541 | 1542 | maybe_put_first_timestamp(Buf) -> 1543 | case get(first_monotonic_timestamp) of 1544 | undefined -> 1545 | case Buf of 1546 | [{_N, ?CALL_TS(_Pid, _MFA, _TS, _S)}|_] -> 1547 | put(first_monotonic_timestamp, 1548 | get_first_monotonic_timestamp(Buf)); 1549 | [{_N, ?RETURN_FROM_TS(_Pid, _MFA, _Value, _TS, _As)}|_] -> 1550 | put(first_monotonic_timestamp, 1551 | get_first_monotonic_timestamp(Buf)); 1552 | [{_N, ?SEND_TS(_FromPid, _Msg, _ToPid, _TS, _As)}|_] -> 1553 | put(first_monotonic_timestamp, 1554 | get_first_monotonic_timestamp(Buf)); 1555 | [{_N, ?RECEIVE_TS(_FromPid, _Msg, _TS, _As)}|_] -> 1556 | put(first_monotonic_timestamp, 1557 | get_first_monotonic_timestamp(Buf)); 1558 | _ -> 1559 | undefined 1560 | end; 1561 | TS -> 1562 | TS 1563 | end. 1564 | 1565 | get_first_monotonic_timestamp(Buf) -> 1566 | case lists:reverse(Buf) of 1567 | [{_N, ?CALL_TS(_Pid, _MFA, TS, _S)}|_] -> 1568 | TS; 1569 | [{_N, ?RETURN_FROM_TS(_Pid, _MFA, _Value, TS, _As)}|_] -> 1570 | TS; 1571 | [{_N, ?SEND_TS(_FromPid, _Msg, _ToPid, TS, _As)}|_] -> 1572 | TS; 1573 | [{_N, ?RECEIVE_TS(_FromPid, _Msg, TS, _As)}|_] -> 1574 | TS 1575 | end. 1576 | 1577 | 1578 | get_return_value(N, [{I,_}|T]) when I < N -> 1579 | get_return_value(N, T); 1580 | get_return_value(N, [{N, ?CALL(_Pid, {M,F,A}, _As)}|T]) -> 1581 | find_return_value({M,F,length(A)}, T); 1582 | get_return_value(N, [{N, ?CALL_TS(_Pid, {M,F,A}, _TS, _As)}|T]) -> 1583 | find_return_value({M,F,length(A)}, T); 1584 | get_return_value(N, [{I,_}|_]) when I > N -> 1585 | not_found; 1586 | get_return_value(_, []) -> 1587 | not_found. 1588 | 1589 | find_return_value(MFA, T) -> 1590 | find_return_value(MFA, T, 0). 1591 | 1592 | find_return_value(MFA,[{_, ?RETURN_FROM(_Pid, MFA, Val, _As)}|_],0 = _Depth)-> 1593 | {ok, MFA, Val}; 1594 | find_return_value(MFA, [{_, ?RETURN_FROM_TS(_Pid, MFA, Val, _TS, _As)}|_], 1595 | 0 = _Depth) -> 1596 | {ok, MFA, Val}; 1597 | find_return_value(MFA, [{_, ?RETURN_FROM(_Pid, MFA, _Val, _As)}|T], Depth) 1598 | when Depth > 0 -> 1599 | find_return_value(MFA, T, Depth-1); 1600 | find_return_value(MFA,[{_, ?RETURN_FROM_TS(_Pid, MFA, _MFA, _TS, _As)}|T],Depth) 1601 | when Depth > 0 -> 1602 | find_return_value(MFA, T, Depth-1); 1603 | find_return_value(MFA, [{_, ?CALL(_Pid, MFA, _As)}|T], Depth) -> 1604 | find_return_value(MFA, T, Depth+1); 1605 | find_return_value(MFA, [{_, ?CALL_TS(_Pid, MFA, _TS, _As)}|T], Depth) -> 1606 | find_return_value(MFA, T, Depth+1); 1607 | find_return_value(MFA, [_|T], Depth) -> 1608 | find_return_value(MFA, T, Depth); 1609 | find_return_value(_MFA, [], _Depth) -> 1610 | not_found. 1611 | 1612 | 1613 | mlist(N, Buf) -> 1614 | try 1615 | case lists:keyfind(N, 1, Buf) of 1616 | {_, ?CALL(_Pid, MFA, _As)} -> 1617 | do_mlist(MFA); 1618 | 1619 | {_, ?CALL_TS(_Pid, MFA, _TS, _As)} -> 1620 | do_mlist(MFA); 1621 | 1622 | {_, ?SEND(SendPid, Msg, ToPid, _As)} -> 1623 | show_send_msg(SendPid, ToPid, Msg); 1624 | 1625 | {_, ?SEND_TS(SendPid, Msg, ToPid, _TS, _As)} -> 1626 | show_send_msg(SendPid, ToPid, Msg); 1627 | 1628 | {_, ?RECEIVE(RecvPid, Msg, _As)} -> 1629 | show_recv_msg(RecvPid, Msg); 1630 | 1631 | {_, ?RECEIVE_TS(RecvPid, Msg, _TS, _As)} -> 1632 | show_recv_msg(RecvPid, Msg); 1633 | 1634 | _ -> 1635 | ?info_msg("not found~n",[]) 1636 | end 1637 | catch 1638 | _:Err:STrace -> 1639 | ?info_msg(?c_err("CRASH: ~p") ++ " ~p~n", [Err,STrace]) 1640 | end. 1641 | 1642 | show_send_msg(SendPid, ToPid, Msg) -> 1643 | ?info_msg("~nMessage sent by: ~p to: ~p~n~p~n", 1644 | [SendPid,ToPid,Msg]). 1645 | 1646 | show_recv_msg(RecvPid, Msg) -> 1647 | ?info_msg("~nMessage received by: ~p~n~p~n", 1648 | [RecvPid,Msg]). 1649 | 1650 | 1651 | do_mlist({M,F,A}) -> 1652 | Fname = edbg:find_source(M), 1653 | {ok, SrcBin, Fname} = erl_prim_loader:get_file(Fname), 1654 | file:write_file("/tmp/"++filename:basename(Fname), SrcBin), 1655 | LF = atom_to_list(F), 1656 | Src = binary_to_list(SrcBin), 1657 | %% '.*?' ::= ungreedy match! 1658 | RegExp = b2l("\\n"++LF++"\\(.*?->"), 1659 | ExRegExp = b2l("def[p]?[ ]+"++LF++"[ (].*do"), % Elixir! 1660 | %% 'dotall' ::= allow multiline function headers 1661 | case re:run(Src, RegExp, [global,dotall,report_errors]) of 1662 | {match, MatchList} -> 1663 | {FmtStr, Args} = mk_print_match(SrcBin, MatchList), 1664 | Sep = pad(35, $-), 1665 | ?info_msg("~nCall: ~p:~p/~p~n~s~n"++FmtStr++"~n~s~n", 1666 | [M,F,length(A),Sep|Args]++[Sep]); 1667 | Else -> 1668 | %% Could it be Elixir? 1669 | case re:run(Src, ExRegExp, [global,report_errors]) of 1670 | {match, ExMatchList} -> 1671 | {FmtStr, Args} = mk_print_match(SrcBin, ExMatchList), 1672 | Sep = pad(35, $-), 1673 | ?info_msg("~nCall: ~p:~p/~p~n~s~n"++FmtStr++"~n~s~n", 1674 | [M,F,length(A),Sep|Args]++[Sep]); 1675 | _ -> 1676 | ?info_msg("nomatch: ~p~n",[Else]) 1677 | end 1678 | end. 1679 | 1680 | 1681 | mk_print_match(SrcBin, MatchList) -> 1682 | F = fun([{Start,Length}], {FmtStrAcc, ArgsAcc}) -> 1683 | <<_:Start/binary,Match:Length/binary,_/binary>> = SrcBin, 1684 | Str = binary_to_list(Match), 1685 | {"~s~n"++FmtStrAcc, [Str|ArgsAcc]} 1686 | end, 1687 | lists:foldr(F, {"",[]}, MatchList). 1688 | 1689 | 1690 | 1691 | pad(0) -> []; 1692 | pad(N) -> 1693 | pad(N, $\s). 1694 | 1695 | pad(N,C) -> 1696 | lists:duplicate(N,C). 1697 | 1698 | %% @private 1699 | send(Pid, Msg) -> 1700 | Pid ! {trace, self(), Msg}, 1701 | receive 1702 | {Pid, ok} -> ok; 1703 | {Pid, stop} -> exit(stop) 1704 | end. 1705 | 1706 | reply(Pid, Msg) -> 1707 | Pid ! {self(), Msg}. 1708 | 1709 | 1710 | 1711 | record_field_index({M,_,_}, Record, Field) -> 1712 | %% we cache the Record Field info for a Module 1713 | case get({M,Record}) of 1714 | undefined -> 1715 | %% [{Field,Index}] 1716 | Fields = get_record_fields(M, Record), 1717 | put({M,Record}, Fields), 1718 | {Field,Index} = lists:keyfind(Field, 1, Fields), 1719 | Index; 1720 | Fields -> 1721 | {Field,Index} = lists:keyfind(Field, 1, Fields), 1722 | Index 1723 | end. 1724 | 1725 | 1726 | 1727 | get_record_fields(Mod, Record) -> 1728 | %% Get the Record definitions for the Module 1729 | Fname = edbg:find_source(Mod), 1730 | {ok, Defs} = pp_record:read(Fname), 1731 | 1732 | %% Get the Record Fields 1733 | {Record,{attribute,_,record,{Record,Fs}}} = lists:keyfind(Record, 1, Defs), 1734 | 1735 | %% Extract the Record Field names; keep the order 1736 | F = fun({typed_record_field,{record_field,_,{atom,_,Name},_},_},Acc) -> 1737 | [Name|Acc]; 1738 | ({typed_record_field,{record_field,_,{atom,_,Name}},_},Acc) -> 1739 | [Name|Acc]; 1740 | ({record_field,_,{atom,_,Name}},Acc) -> 1741 | [Name|Acc]; 1742 | ({record_field,_,{atom,_,Name},_},Acc) -> 1743 | [Name|Acc] 1744 | end, 1745 | Fields = lists:foldr(F, [], Fs), 1746 | 1747 | %% Create a Key-Value list of the fields and their index into the Record 1748 | lists:zip(Fields, lists:seq(2, erlang:length(Fields)+1)). 1749 | --------------------------------------------------------------------------------