├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── priv └── erl_crash.dot ├── rebar.config ├── src ├── edump.app.src ├── edump.erl ├── edump_allocator.erl ├── edump_analyse.erl ├── edump_dump.erl ├── edump_ets.erl ├── edump_fun.erl ├── edump_heap.erl ├── edump_idx.erl ├── edump_link_list.xrl ├── edump_link_list_parser.yrl ├── edump_mem.erl ├── edump_modules.erl ├── edump_node.erl ├── edump_parse.erl ├── edump_port.erl ├── edump_proc.erl ├── edump_proc_dot.erl ├── edump_proc_graph.erl ├── edump_scheduler.erl ├── edump_script.erl ├── edump_seg.erl ├── edump_seg.hrl ├── edump_stack.erl └── edump_timer.erl ├── templates └── proc_graph.rdtl └── test └── dumpfiles ├── erl_dist_forced_crash.dump ├── erl_dist_forced_crash.dump.md ├── eshell_forced_crash.dump ├── eshell_forced_crash.dump.md ├── eshell_forced_crash.dump.png ├── gh_issue_3.dump └── gh_issue_3.dump.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.dump filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | .rebar 14 | _rel 15 | _deps 16 | _plugins 17 | _tdeps 18 | logs 19 | _build 20 | *.dot 21 | *.eidx 22 | src/edump_link_list_parser.erl 23 | src/edump_link_list.erl 24 | *~ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Geoff Cant . 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | * The names of its contributors may not be used to endorse or promote 16 | products derived from this software without specific prior written 17 | permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # edump 2 | 3 | Efficient Erlang Crashdump Analysis Tools. 4 | 5 | I find `erl_crash.dump` files essential for debugging production 6 | systems after they've crashed, but these files are a pain to deal with 7 | by hand. OTP comes with `crashdump_viewer`, but that tool doesn't work 8 | with really large files, is hard to use programatically, and tricky to 9 | build on. 10 | 11 | > Note: crashdump_viewer had poor large file support in 2015, but 12 | > since then it seems to have had updates to use a similar segment 13 | > indexing strategy. crashdump_viewer probably works reasonably with 14 | > large files now, but my complaints about it being hard to use 15 | > programmatically are still true. 16 | 17 | `edump` is an Erlang library for efficiently parsing and analysing 18 | information in `erl_crash.dump` files. The main trick it uses is to 19 | build an index of segments in a full crashdump file. Indexing the 20 | file, or reading the file without an index requires reading the entire 21 | thing to find what you want, but an `edump *.eidx` file will give you 22 | random access to segments inside a crashdump of any size. 23 | 24 | This library includes code for parsing different kinds of crashdump 25 | segment, can reconstruct process dictionaries, message queues and 26 | stacks from process heaps, and perform a number of basic analysis on 27 | information usually present in crashdumps. 28 | 29 | ## Build 30 | 31 | $ rebar3 compile 32 | 33 | If you'd like the 'edump' escript tool as well, run 34 | 35 | $ rebar3 escriptize 36 | 37 | ## Use 38 | 39 | ### Reading a crashdump 40 | 41 | ``` 42 | $ rebar3 shell 43 | > Handle = edump:open("/path/to/erl_crash.dump"). 44 | ... 45 | ``` 46 | 47 | `edump:open` parses a crashdump file, creates an index of segments in 48 | the dump, (by default) writes the index file to `CrashdumpFile ++ 49 | ".eidx"` and returns a usable handle to the index. Further crashdump 50 | investigation functions use a `Handle`. This is optimized if there's 51 | already an `eidx` file for the dump - we skip the dump file completely 52 | and load the index. 53 | 54 | ### Basic info 55 | 56 | ``` 57 | > edump:ports(Handle). 58 | [{port,<<"#Port<0.1>">>}, 59 | {port,<<"#Port<0.47>">>}, 60 | {port,<<"#Port<0.404>">>}, 61 | {port,<<"#Port<0.413>">>}] 62 | > edump:processes(Handle). 63 | [{proc,<<"<0.0.0>">>}, 64 | {proc,<<"<0.3.0>">>}, 65 | {proc,<<"<0.5.0>">>}, 66 | {proc,<<"<0.6.0>">>}, 67 | {proc,<<"<0.8.0>">>}, 68 | {proc,<<"<0.9.0>">>}, 69 | {proc,<<"<0.10.0>">>}|...] 70 | ``` 71 | 72 | Indexes store data about crashdump file segments. Segments have ids, 73 | for instance `{proc, <<"<0.0.0>">>}` or `{port, 74 | <<"#Port<0.1>">>}`. You can get more information about a segment by 75 | id: 76 | 77 | ``` 78 | > edump:info({port,<<"#Port<0.1>">>}, Handle). 79 | {port,[{slot,1}, 80 | {connected,{proc,<<"<0.3.0>">>}}, 81 | {links,{proc,<<"<0.3.0>">>}}, 82 | {driver,<<"efile">>}], 83 | []} 84 | ``` 85 | 86 | ### Drawing Process Graphs 87 | 88 | There are some analysis modules that will construct a graph of erlang 89 | entities (port, processes, nodes, etc) from the data in a crashdump 90 | and turn these into GraphViz dot files for viewing. 91 | 92 | ``` 93 | > Graph = edump:proc_graph(Handle) 94 | {digraph,700451,704546,708644,true} 95 | > edump_proc_dot:to_dot("priv/erl_crash.dot", Graph, 96 | #{include_edge => fun edump_proc_dot:fewer_edges/3}). 97 | ok 98 | ``` 99 | 100 | This produces a graph like this when rendered with GraphViz (edump 101 | produces only the `.dot` file): 102 | ![image](test/dumpfiles/eshell_forced_crash.dump.png) 103 | 104 | ## Edump escript 105 | 106 | The `edump` escript (found in `_build/default/bin`) provides a 107 | commandline interface to some common functionality: 108 | 109 | * `edump index` parses dump files and produces index files 110 | 111 | ``` 112 | $ edump index -c full test/dumpfiles/eshell_forced_crash.dump 113 | Indexing "test/dumpfiles/eshell_forced_crash.dump" [{checking,full}, 114 | {file,"test/dumpfiles/eshell_forced_crash.dump"}, 115 | {rebuild,false}] 116 | Indexed "test/dumpfiles/eshell_forced_crash.dump" in 0.218382s 117 | ``` 118 | 119 | * `edump graph` produces a GraphViz dot file from a dump file. It will index the dump file if necessary. 120 | 121 | ``` 122 | edump graph test/dumpfiles/eshell_forced_crash.dump -d test/dumpfiles/eshell_forced_crash.full.dot -g "rankdir="TB";size=\"12,8\"" 123 | Graph opts: #{graph_attributes => ["rankdir=TB","size=\"12,8\""], 124 | include_edge => #Fun} 125 | Wrote graph to "test/dumpfiles/eshell_forced_crash.full.dot" 126 | ``` 127 | 128 | * `edump info` prints a description of various information from the dump file. Without options (or `--info basic`), edump presents a basic summary: 129 | 130 | ``` 131 | $ edump info test/dumpfiles/eshell_forced_crash.dump 132 | Crashdump "test/dumpfiles/eshell_forced_crash.dump" 133 | Crashed: <<"Wed Oct 3 23:53:30 2012">> 134 | Slogan: A test crash 135 | Total memory: 9.060 Mb 136 | 78.56% system: 7.117 Mb 137 | 44.69% code: 4.049 Mb 138 | 21.44% processes: 1.943 Mb (100.00% used) 139 | 7.10% ets: 0.644 Mb 140 | 2.14% atom: 0.194 Mb ( 90.07% used) 141 | 1.86% binary: 0.168 Mb 142 | ``` 143 | 144 | * `edump info --info processes` (a really clumsy CLI, I don't know how to fix that yet) 145 | 146 | ``` 147 | $ edump info test/dumpfiles/eshell_forced_crash.dump --info processes 148 | Crashdump "test/dumpfiles/eshell_forced_crash.dump" 149 | Processes (32 of 32): 150 | 1 <0.31.0> (Running, 0 msgq, 4181 mem, 86159 reds) 151 | 2 <0.6.0> application_controll (Waiting, 0 msgq, 28657 mem, 7881 reds) 152 | 3 <0.3.0> erl_prim_loader (Waiting, 0 msgq, 6765 mem, 270453 reds) 153 | 4 <0.10.0> kernel_sup (Waiting, 0 msgq, 4181 mem, 37621 reds) 154 | 5 <0.18.0> code_server (Waiting, 0 msgq, 4181 mem, 155017 reds) 155 | 6 <0.0.0> init (Waiting, 0 msgq, 2584 mem, 3414 reds) 156 | 7 <0.24.0> (Waiting, 0 msgq, 2584 mem, 24580 reds) 157 | 8 <0.25.0> (Waiting, 0 msgq, 2584 mem, 6347 reds) 158 | 9 <0.22.0> user_drv (Waiting, 0 msgq, 987 mem, 8140 reds) 159 | 10 <0.5.0> error_logger (Waiting, 0 msgq, 610 mem, 280 reds) 160 | 11 <0.41.0> (Waiting, 0 msgq, 610 mem, 957 reds) 161 | 12 <0.44.0> disk_log_sup (Waiting, 0 msgq, 610 mem, 236 reds) 162 | 13 <0.8.0> (Waiting, 0 msgq, 377 mem, 44 reds) 163 | 14 <0.17.0> file_server_2 (Waiting, 0 msgq, 377 mem, 514 reds) 164 | 15 <0.27.0> kernel_safe_sup (Waiting, 0 msgq, 377 mem, 195 reds) 165 | 16 <0.45.0> disk_log_server (Waiting, 0 msgq, 377 mem, 228 reds) 166 | 17 <0.9.0> (Waiting, 0 msgq, 233 mem, 69 reds) 167 | 18 <0.11.0> rex (Waiting, 0 msgq, 233 mem, 35 reds) 168 | 19 <0.12.0> global_name_server (Waiting, 0 msgq, 233 mem, 50 reds) 169 | 20 <0.13.0> (Waiting, 0 msgq, 233 mem, 20 reds) 170 | 21 <0.14.0> (Waiting, 0 msgq, 233 mem, 3 reds) 171 | 22 <0.15.0> inet_db (Waiting, 0 msgq, 233 mem, 234 reds) 172 | 23 <0.16.0> global_group (Waiting, 0 msgq, 233 mem, 59 reds) 173 | 24 <0.19.0> standard_error_sup (Waiting, 0 msgq, 233 mem, 41 reds) 174 | 25 <0.20.0> standard_error (Waiting, 0 msgq, 233 mem, 9 reds) 175 | 26 <0.21.0> (Waiting, 0 msgq, 233 mem, 62 reds) 176 | 27 <0.23.0> user (Waiting, 0 msgq, 233 mem, 36 reds) 177 | 28 <0.26.0> (Waiting, 0 msgq, 233 mem, 268 reds) 178 | 29 <0.34.0> (Waiting, 0 msgq, 233 mem, 23 reds) 179 | 30 <0.35.0> (Waiting, 0 msgq, 233 mem, 49 reds) 180 | 31 <0.36.0> edump_sup (Waiting, 0 msgq, 233 mem, 102 reds) 181 | 32 <0.37.0> edump_viewer (Waiting, 0 msgq, 233 mem, 27 reds) 182 | ``` 183 | 184 | ## Todo list 185 | 186 | * edump pstree (ala unix pstree) 187 | * map support (maybe this works already if they're emitted as erl_dist_external?) 188 | * follow binary references when reconstructing proc stacks/dict/messages 189 | * Check [all commits in crashdump_viewer since 2015](https://github.com/erlang/otp/commits/master/lib/observer/src/crashdump_viewer.erl) 190 | -------------------------------------------------------------------------------- /priv/erl_crash.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | size="12,8"; 3 | 4 | 5 | "<0.23.0>" [label="user\n<0.23.0>",shape=oval]; 6 | "<0.27.0>" [label="kernel_safe_sup\n<0.27.0>",shape=oval]; 7 | "<0.33.0>" [label="<0.33.0>",style=dotted,shape=oval]; 8 | "<0.17.0>" [label="file_server_2\n<0.17.0>",shape=oval]; 9 | "<0.25.0>" [label="<0.25.0>",shape=oval]; 10 | "<0.11.0>" [label="rex\n<0.11.0>",shape=oval]; 11 | "<0.10.0>" [label="kernel_sup\n<0.10.0>",shape=oval]; 12 | "<0.0.0>" [label="init\n<0.0.0>",shape=oval]; 13 | "#Port<0.404>" [label="#Port<0.404>",shape=box]; 14 | "<0.41.0>" [label="<0.41.0>",shape=oval]; 15 | "<0.16.0>" [label="global_group\n<0.16.0>",shape=oval]; 16 | "<0.37.0>" [label="edump_viewer\n<0.37.0>",shape=oval]; 17 | "<0.24.0>" [label="<0.24.0>",shape=oval]; 18 | "#Port<0.413>" [label="#Port<0.413>",shape=box]; 19 | "#Port<0.47>" [label="#Port<0.47>",shape=box]; 20 | "<0.13.0>" [label="<0.13.0>",shape=oval]; 21 | "<0.15.0>" [label="inet_db\n<0.15.0>",shape=oval]; 22 | "<0.36.0>" [label="edump_sup\n<0.36.0>",shape=oval]; 23 | "<0.20.0>" [label="standard_error\n<0.20.0>",shape=oval]; 24 | "<0.12.0>" [label="global_name_server\n<0.12.0>",shape=oval]; 25 | "<0.3.0>" [label="erl_prim_loader\n<0.3.0>",shape=oval]; 26 | "<0.44.0>" [label="disk_log_sup\n<0.44.0>",shape=oval]; 27 | "<0.45.0>" [label="disk_log_server\n<0.45.0>",shape=oval]; 28 | "<0.34.0>" [label="<0.34.0>",shape=oval]; 29 | "<0.31.0>" [label="<0.31.0>",color=green,shape=oval]; 30 | "<0.39.0>" [label="<0.39.0>",style=dotted,shape=oval]; 31 | "<0.14.0>" [label="<0.14.0>",shape=oval]; 32 | "<0.8.0>" [label="<0.8.0>",shape=oval]; 33 | "<0.26.0>" [label="<0.26.0>",shape=oval]; 34 | "<0.6.0>" [label="application_controller\n<0.6.0>",shape=oval]; 35 | "<0.18.0>" [label="code_server\n<0.18.0>",shape=oval]; 36 | "#Port<0.1>" [label="#Port<0.1>",shape=box]; 37 | "<0.19.0>" [label="standard_error_sup\n<0.19.0>",shape=oval]; 38 | "<0.22.0>" [label="user_drv\n<0.22.0>",shape=oval]; 39 | "<0.35.0>" [label="<0.35.0>",shape=oval]; 40 | "<0.5.0>" [label="error_logger\n<0.5.0>",shape=oval]; 41 | "erlang" [label="erlang",shape=egg]; 42 | "<0.21.0>" [label="<0.21.0>",shape=oval]; 43 | "<0.9.0>" [label="<0.9.0>",shape=oval]; 44 | "<0.2.0>" [label="<0.2.0>",style=dotted,shape=oval]; 45 | "<0.7.0>" [label="<0.7.0>",style=dotted,shape=oval]; 46 | 47 | 48 | "<0.0.0>" -> "erlang" [label="spawned_by"]; 49 | "<0.12.0>" -> "<0.13.0>" [dir=none]; 50 | "<0.27.0>" -> "<0.44.0>" [dir=none]; 51 | "<0.22.0>" -> "<0.23.0>" [dir=none]; 52 | "<0.12.0>" -> "<0.14.0>" [dir=none]; 53 | "<0.3.0>" -> "#Port<0.1>" [dir=none]; 54 | "<0.10.0>" -> "<0.16.0>" [dir=none]; 55 | "<0.10.0>" -> "<0.18.0>" [dir=none]; 56 | "<0.36.0>" -> "<0.37.0>" [dir=none]; 57 | "<0.23.0>" -> "<0.5.0>" [dir=none]; 58 | "<0.0.0>" -> "<0.5.0>" [dir=none]; 59 | "<0.10.0>" -> "<0.11.0>" [dir=none]; 60 | "<0.10.0>" -> "<0.27.0>" [dir=none]; 61 | "<0.0.0>" -> "<0.6.0>" [dir=none]; 62 | "<0.25.0>" -> "<0.31.0>" [dir=none]; 63 | "<0.10.0>" -> "<0.12.0>" [dir=none]; 64 | "<0.34.0>" -> "<0.35.0>" [dir=none]; 65 | "<0.19.0>" -> "<0.20.0>" [dir=none]; 66 | "<0.10.0>" -> "<0.17.0>" [dir=none]; 67 | "<0.22.0>" -> "<0.24.0>" [dir=none]; 68 | "<0.10.0>" -> "<0.19.0>" [dir=none]; 69 | "<0.21.0>" -> "<0.23.0>" [dir=none]; 70 | "<0.10.0>" -> "<0.26.0>" [dir=none]; 71 | "<0.24.0>" -> "<0.25.0>" [dir=none]; 72 | "<0.10.0>" -> "<0.21.0>" [dir=none]; 73 | "<0.0.0>" -> "<0.3.0>" [dir=none]; 74 | "<0.10.0>" -> "<0.15.0>" [dir=none]; 75 | "<0.35.0>" -> "<0.36.0>" [dir=none]; 76 | "<0.34.0>" -> "<0.6.0>" [dir=none]; 77 | "<0.27.0>" -> "<0.45.0>" [dir=none]; 78 | "<0.20.0>" -> "#Port<0.404>" [dir=none]; 79 | "<0.10.0>" -> "<0.9.0>" [dir=none]; 80 | "<0.8.0>" -> "<0.9.0>" [dir=none]; 81 | "<0.22.0>" -> "#Port<0.413>" [dir=none]; 82 | "<0.17.0>" -> "#Port<0.47>" [dir=none]; 83 | "<0.6.0>" -> "<0.8.0>" [dir=none]; 84 | 85 | } -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang -*- 2 | 3 | {erl_opts, [debug_info]}. 4 | {deps, [{erlydtl, [], 5 | {git, "https://github.com/erlydtl/erlydtl.git", 6 | {branch, "master"}}} 7 | ,{getopt, "", {git, "https://github.com/jcomellas/getopt.git", 8 | {branch, "master"}}} 9 | ]}. 10 | 11 | {provider_hooks, 12 | [{post, [{compile, rdtl}, 13 | {rdtl, escriptize}]}]}. 14 | 15 | {escript_name, "edump"}. 16 | {escript_incl_apps, [getopt]}. 17 | 18 | {plugins, 19 | [{rdtl, "", 20 | {git, "https://github.com/archaelus/rdtl.git", {branch, "master"}}}]}. 21 | -------------------------------------------------------------------------------- /src/edump.app.src: -------------------------------------------------------------------------------- 1 | {application, 'edump', 2 | [{description, "Erlang Crashdump Utilities"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {applications, 6 | [kernel, 7 | stdlib, 8 | erlydtl 9 | ]}, 10 | {env,[]}, 11 | {modules, []}, 12 | 13 | {contributors, ["Geoff Cant"]}, 14 | {maintainers, ["Geoff Cant"]}, 15 | {licenses, ["BSD Simplified"]}, 16 | {links, []} 17 | ]}. 18 | -------------------------------------------------------------------------------- /src/edump.erl: -------------------------------------------------------------------------------- 1 | -module('edump'). 2 | 3 | %% API exports 4 | -export([h/0 5 | ,help/0 6 | ,open/1 7 | ,open/2 8 | ,reopen/1 9 | ,proc_graph/1 10 | ,try_parse/1 11 | ,try_parse/2 12 | ,pread/3 13 | ,processes/1 14 | ,ports/1 15 | ,info/2 16 | ,sort_procs/1 17 | ,sort_procs/2 18 | ]). 19 | 20 | %% Escript export 21 | -export([main/1]). 22 | 23 | %%==================================================================== 24 | %% API functions 25 | %%==================================================================== 26 | 27 | h() -> help(). 28 | 29 | -spec help() -> 'ok'. 30 | help() -> 31 | io:put_chars(<<"Edump: File::filename(), H::edump_idx:handle(), " 32 | "Digraph::digraph:graph(), Id::edump_seg:segment_id()\n" 33 | "open(File) -> H. Open a dump\n" 34 | "open(File, Opts) -> H. Options write_index (true, bool())\n" 35 | " index_checking " 36 | "(by_size, by_size|full|cheap|none)\n" 37 | " force_rebuild " 38 | "(false, bool())\n" 39 | "processes(H) -> [Id]. Find all process segments\n" 40 | "ports(H) -> [Id]. Find all port segments\n" 41 | "info(Id, H). Segment info for Id\n" 42 | "sort_procs(H). \n" 43 | "sort_procs(Opts, H). \n" 44 | "reopen(H) -> H. \n" 45 | "proc_graph(H) -> Digraph. \n" 46 | "try_parse(File). Try to parse all segment types in File. " 47 | "Stop on first error.\n" 48 | "try_parse(SegType, File). " 49 | "Parse only SegType segments in File.\n" 50 | "pread(File, Start, End) -> binary(). " 51 | "Read a block as a binary from File.\n" 52 | >>). 53 | 54 | open(CrashdumpFile) -> 55 | edump_idx:open(CrashdumpFile). 56 | 57 | open(CrashdumpFile, Opts) -> 58 | edump_idx:open(CrashdumpFile, 59 | maps:merge(edump_idx:default_options(), Opts)). 60 | 61 | reopen(Handle) -> 62 | edump_idx:reopen(Handle). 63 | 64 | proc_graph(Handle) -> 65 | edump_proc_graph:from_handle(Handle). 66 | 67 | try_parse(Dump) -> 68 | try_parse(any, Dump). 69 | 70 | try_parse(Type, Dump) when is_list(Dump) -> 71 | try_parse(Type, open(Dump)); 72 | try_parse(Type, Dump) -> 73 | edump_seg:first_parse_failure(Type, Dump). 74 | 75 | pread(File, Start, End) when is_list(File) -> 76 | {ok, FD} = file:open(File, [binary, raw]), 77 | Ret = file:pread(FD, Start, End-Start), 78 | file:close(FD), 79 | Ret. 80 | 81 | 82 | processes(Handle) -> 83 | edump_seg:ids_of_type(proc, Handle). 84 | 85 | ports(Handle) -> 86 | edump_seg:ids_of_type(port, Handle). 87 | 88 | info(Id, Handle) -> 89 | edump_seg:id_info(Id, Handle). 90 | 91 | sort_procs(Handle) -> 92 | sort_procs(#{}, Handle). 93 | 94 | sort_procs(Opts, Handle) -> 95 | edump_analyse:sort_procs(Handle, Opts). 96 | 97 | %%==================================================================== 98 | %% Internal functions 99 | %%==================================================================== 100 | 101 | main(Args) -> 102 | edump_script:main(Args). 103 | -------------------------------------------------------------------------------- /src/edump_allocator.erl: -------------------------------------------------------------------------------- 1 | -module('edump_allocator'). 2 | 3 | %% API exports 4 | -export([parse_areas/1 5 | ,parse/1 6 | ]). 7 | 8 | %%==================================================================== 9 | %% API functions 10 | %%==================================================================== 11 | 12 | %% allocated_areas 13 | parse_areas(Data) -> 14 | edump_parse:kv_section(fun area_line/1, Data). 15 | 16 | %% allocator 17 | parse(Data) -> 18 | [ allocator_line(edump_parse:kv_line(L)) 19 | || L <- edump_parse:lines(Data) ]. 20 | 21 | %%==================================================================== 22 | %% Internal functions 23 | %%==================================================================== 24 | 25 | area_line({K, V}) -> 26 | {binary_to_atom(K, latin1), 27 | case [binary_to_integer(I) 28 | || I <- binary:split(V, <<" ">>, [global])] of 29 | [I] -> I; 30 | L -> L 31 | end}; 32 | area_line(L) -> 33 | edump_parse:atom_int(L). 34 | 35 | allocator_line({K, V}) -> 36 | Key = [binary_to_atom(Part, latin1) 37 | || Part <- binary:split(K, <<" ">>, [global])], 38 | {Key, 39 | values(Key, V)}. 40 | 41 | values([version], V) -> 42 | V; 43 | values([versions], V) -> 44 | binary:split(V, <<" ">>, [global]); 45 | values([fix, type], V) -> 46 | [Thing, A, B] = space_split(V), 47 | [binary_to_atom(Thing, latin1), 48 | binary_to_integer(A), 49 | binary_to_integer(B)]; 50 | values([option, as], V) -> 51 | binary_to_atom(V, latin1); 52 | values([memory, kind], V) -> 53 | V; 54 | values(_, V) -> 55 | [ case I of 56 | <<"true">> -> true; 57 | <<"false">> -> false; 58 | <<"libc">> -> libc; 59 | I -> binary_to_integer(I) 60 | end 61 | || I <- space_split(V) ]. 62 | 63 | space_split(V) -> 64 | binary:split(V, <<" ">>, [global]). 65 | -------------------------------------------------------------------------------- /src/edump_analyse.erl: -------------------------------------------------------------------------------- 1 | -module('edump_analyse'). 2 | 3 | %% API exports 4 | -export([proc_graph/3 5 | ,info/3 6 | ,sort_procs/2 7 | ]). 8 | 9 | %%==================================================================== 10 | %% API functions 11 | %%==================================================================== 12 | 13 | proc_graph(CrashdumpFile, DotFile, Options) -> 14 | Graph = edump_proc_graph:from_handle(edump:open(CrashdumpFile)), 15 | edump_proc_dot:to_dot(DotFile, 16 | Graph, 17 | Options). 18 | 19 | info(basic, Handle, _Opts) -> 20 | DumpId = hd(edump_seg:ids_of_type(erl_crash_dump, Handle)), 21 | basic_info(edump:info(DumpId, Handle)), 22 | mem_info(edump:info(memory, Handle)); 23 | info(processes, Handle, Opts) -> 24 | Pids = edump_seg:ids_of_type(proc, Handle), 25 | Processes = [ {Pid, edump_seg:parse_id(Pid, Handle)} 26 | || Pid <- Pids ], 27 | process_summary(Processes, Opts). 28 | 29 | sort_procs(Handle, Opts) -> 30 | Pids = edump_seg:ids_of_type(proc, Handle), 31 | Processes = [ {Pid, edump_seg:parse_id(Pid, Handle)} 32 | || Pid <- Pids ], 33 | {_Total, Procs} = sort_processes(Processes, Opts), 34 | [ Pid || {{Pid, _}, _Pos} <- Procs]. 35 | 36 | %%==================================================================== 37 | %% Internal functions 38 | %%==================================================================== 39 | 40 | scale(Num, {Suffix, Divisor}) -> 41 | io_lib:format("~6.3f ~s", [Num/Divisor, Suffix]). 42 | 43 | scale(G) when G > 1000000000 -> 44 | {"G", 1000000000}; 45 | scale(M) when M > 1000000 -> 46 | {"M", 1000000}; 47 | scale(K) when K > 1000 -> 48 | {"K", 1000}; 49 | scale(_) -> 50 | {"", 1}. 51 | 52 | basic_info({erl_crash_dump, Basic, _}) -> 53 | io:format("Crashed: ~p~n", [proplists:get_value(date, Basic)]), 54 | io:format("Slogan: ~s~n", [proplists:get_value(slogan, Basic)]), 55 | [ io:format("Tainted by: ~s~n", [T]) 56 | || {taints, T} <- Basic, 57 | byte_size(T) > 0]. 58 | 59 | mem_info({memory, Mem0, _}) -> 60 | Total = proplists:get_value(total, Mem0), 61 | Scale = scale(Total), 62 | Mem = [{atom_to_list(A), V} 63 | || {A, V} <- Mem0, 64 | A =/= total], 65 | {Used, Section} = 66 | lists:partition(fun ({K, _V}) -> 67 | string:str(K, "_used") =/= 0 68 | end, 69 | Mem), 70 | Descending = lists:reverse(lists:keysort(2, Section)), 71 | io:format("Total memory: ~sb~n", [scale(Total, Scale)]), 72 | [ case proplists:get_value(S ++ "_used", Used) of 73 | undefined -> 74 | io:format(" ~6.2f% ~s: ~sb~n", 75 | [100 * Bytes / Total, S, scale(Bytes, Scale)]); 76 | UsedBytes -> 77 | io:format(" ~6.2f% ~s: ~sb (~6.2f% used)~n", 78 | [100 * Bytes / Total, S, 79 | scale(Bytes, Scale), 100 * UsedBytes/Bytes]) 80 | end 81 | || {S, Bytes} <- Descending ]. 82 | 83 | sort_processes(Processes, Opts) -> 84 | Sort = maps:get(sort, Opts, fancy), 85 | SortFn = case Sort of 86 | pid -> fun p_sort_pid/2; 87 | mem -> fun p_sort_mem/2; 88 | msg_q -> fun p_sort_msg_q/2; 89 | reds -> fun p_sort_reds/2; 90 | state -> fun p_sort_state/2; 91 | fancy -> fun p_sort_fancy/2 92 | end, 93 | Sorted = lists:sort(SortFn, Processes), 94 | Total = length(Processes), 95 | Len = case maps:get(max, Opts, 100) of 96 | N when is_integer(N), 97 | N < Total -> N; 98 | N when is_list(N) -> 99 | erlang:min(list_to_integer(N), Total); 100 | _ -> Total 101 | end, 102 | {Total, 103 | lists:zip(lists:sublist(Sorted, Len), 104 | lists:seq(1, Len))}. 105 | 106 | process_summary(Processes, Opts) -> 107 | {Total, Procs} = sort_processes(Processes, Opts), 108 | list_processes(Total, Procs), 109 | ok. 110 | 111 | list_processes(NumProcesses, Procs) -> 112 | io:format("Processes (~p of ~p):~n", [length(Procs), NumProcesses]), 113 | PosWidth = integer_to_list(length(integer_to_list(NumProcesses))), 114 | [ list_process(PosWidth, P) 115 | || P <- Procs ]. 116 | 117 | list_process(PosWidth, {{{proc, Pid}, Info}, Position}) -> 118 | State = proplists:get_value(state, Info), 119 | Reds = proplists:get_value(reductions, Info), 120 | Msgs = proplists:get_value(message_queue_length, Info), 121 | Bytes = proplists:get_value(stack_plus_heap, Info, "unknown"), 122 | PInfo = string:join([[State], 123 | [integer_to_list(Msgs), " msgq"], 124 | [integer_to_list(Bytes), " mem"], 125 | [integer_to_list(Reds), " reds"] 126 | ], ", "), 127 | Name = proplists:get_value(name, Info, ""), 128 | io:format(" ~"++PosWidth++"w ~16s ~20s (~s)~n", 129 | [Position, Pid, Name, PInfo]). 130 | 131 | p_info_sort(Key, {_PidA, InfoA}, {_PidB, InfoB}) -> 132 | proplists:get_value(Key, InfoA) > 133 | proplists:get_value(Key, InfoB). 134 | 135 | p_sort_mem(A, B) -> 136 | p_info_sort(stack_plus_heap, A, B). 137 | 138 | p_sort_msg_q(A, B) -> 139 | p_info_sort(message_queue_length, A, B). 140 | 141 | p_sort_reds(A, B) -> 142 | p_info_sort(reductions, A, B). 143 | 144 | p_sort_state({_, InfoA}, {_, InfoB}) -> 145 | proplists:get_value(state, InfoA) =< 146 | proplists:get_value(state, InfoB). 147 | 148 | p_sort_pid({{proc, A}, _}, {{proc, B}, _}) -> 149 | PidA = list_to_pid(binary_to_list(A)), 150 | PidB = list_to_pid(binary_to_list(B)), 151 | PidA =< PidB. 152 | 153 | p_sort_fancy(A, B) -> 154 | SortA = sortkeys(A), 155 | SortB = sortkeys(B), 156 | sort_by_key(SortA, SortB). 157 | 158 | sort_by_key([{A, '<'}], 159 | [{B, '<'}]) -> 160 | A =< B; 161 | sort_by_key([{A, '>'}], 162 | [{B, '>'}]) -> 163 | A >= B; 164 | sort_by_key([{A, _} | RestA], 165 | [{B, _} | RestB]) when A =:= B -> 166 | sort_by_key(RestA, RestB); 167 | sort_by_key([{A, '<'} | _], 168 | [{B, '<'} | _]) -> 169 | A < B; 170 | sort_by_key([{A, '>'} | _], 171 | [{B, '>'} | _]) -> 172 | A > B. 173 | 174 | sortkeys({{proc, P}, InfoP}) -> 175 | [{proplists:get_value(state, InfoP), '<'}, 176 | {proplists:get_value(message_queue_length, InfoP), '>'}, 177 | {proplists:get_value(stack_plus_heap, InfoP), '>'}, 178 | {list_to_pid(binary_to_list(P)), '<'}]. 179 | -------------------------------------------------------------------------------- /src/edump_dump.erl: -------------------------------------------------------------------------------- 1 | -module('edump_dump'). 2 | 3 | %% API exports 4 | -export([parse/1]). 5 | 6 | %%==================================================================== 7 | %% API functions 8 | %%==================================================================== 9 | 10 | %% Wed Oct 3 23:53:30 2012 11 | %% Slogan: A test crash 12 | %% System version: Erlang R15B01 (erts-5.9.1) [source] [64-bit] [smp:2:2] [async-threads:0] [hipe] [kernel-poll:false] 13 | %% Compiled: Fri Apr 13 08:02:54 2012 14 | %% Taints: 15 | %% Atoms: 7013 16 | 17 | parse(Data) -> 18 | [Date | Lines] = edump_parse:lines(Data), 19 | [{date, Date} | [parse_kv(edump_parse:kv_line(L)) || L <- Lines]]. 20 | 21 | %%==================================================================== 22 | %% Internal functions 23 | %%==================================================================== 24 | 25 | parse_kv({<<"Slogan">>, Slogan}) -> {slogan, Slogan}; 26 | parse_kv({<<"Taints">>, Slogan}) -> {taints, Slogan}; 27 | parse_kv(Other) -> 28 | Other. 29 | -------------------------------------------------------------------------------- /src/edump_ets.erl: -------------------------------------------------------------------------------- 1 | -module(edump_ets). 2 | 3 | %% API exports 4 | -export([parse/1]). 5 | 6 | %%==================================================================== 7 | %% API functions 8 | %%==================================================================== 9 | 10 | parse(Data) -> 11 | FlatInfo = lists:append(edump_parse:kv_section(fun parse_kv/1, Data)), 12 | case proplists:get_value(type, FlatInfo) of 13 | undefined -> 14 | [{type, set} | FlatInfo]; 15 | _ -> 16 | FlatInfo 17 | end. 18 | 19 | %%==================================================================== 20 | %% Internal functions 21 | %%==================================================================== 22 | 23 | parse_kv({<<"Slot">>, V}) -> 24 | [{slot, binary_to_integer(V)}]; 25 | parse_kv({<<"Table">>, Atom}) -> 26 | [{table, Atom}]; 27 | parse_kv({<<"Name">>, Atom}) -> 28 | [{name, Atom}]; 29 | parse_kv({<<"Buckets">>, V}) -> 30 | [{buckets, binary_to_integer(binary:part(V,0,byte_size(V)-1))}]; 31 | parse_kv({<<"Objects">>, V}) -> 32 | [{objects, binary_to_integer(V)}]; 33 | parse_kv({<<"Words">>, V}) -> 34 | [{words, binary_to_integer(V)}]; 35 | parse_kv({<<"Ordered set (AVL tree), Elements">>, V}) -> 36 | [{type, ord_set}, 37 | {elements, binary_to_integer(V)}]; 38 | parse_kv({<<"Chain Length Avg">>, F}) -> 39 | [{chain_length_avg, binary_to_float(F)}]; 40 | parse_kv({<<"Chain Length Max">>, V}) -> 41 | [{chain_length_max, binary_to_integer(V)}]; 42 | parse_kv({<<"Chain Length Min">>, V}) -> 43 | [{chain_length_min, binary_to_integer(V)}]; 44 | parse_kv({<<"Chain Length Std Dev">>, F}) -> 45 | [{chain_length_stdev, binary_to_float(F)}]; 46 | parse_kv({<<"Chain Length Expected Std Dev">>, F}) -> 47 | [{chain_length_expected_stdev, binary_to_float(F)}]; 48 | parse_kv({<<"Fixed">>, A}) -> 49 | [{fixed, binary_to_existing_atom(A, latin1)}]; 50 | parse_kv({<<"Type">>, A}) -> 51 | [{type, binary_to_existing_atom(A, latin1)}]; 52 | parse_kv({<<"Protection">>, A}) -> 53 | [{protection, binary_to_existing_atom(A, latin1)}]; 54 | parse_kv({<<"Compressed">>, A}) -> 55 | [{compressed, binary_to_existing_atom(A, latin1)}]; 56 | parse_kv({<<"Write Concurrency">>, A}) -> 57 | [{write_concurrency, binary_to_existing_atom(A, latin1)}]; 58 | parse_kv({<<"Read Concurrency">>, A}) -> 59 | [{read_concurrency, binary_to_existing_atom(A, latin1)}]; 60 | parse_kv({K, V}) -> 61 | erlang:error({unknown_ets_info, {K, V}}). 62 | 63 | -------------------------------------------------------------------------------- /src/edump_fun.erl: -------------------------------------------------------------------------------- 1 | -module('edump_fun'). 2 | 3 | %% API exports 4 | -export([parse/1 5 | ]). 6 | 7 | %%==================================================================== 8 | %% API functions 9 | %%==================================================================== 10 | 11 | parse(Data) -> 12 | edump_parse:kv_section(fun fun_kv/1, Data). 13 | 14 | 15 | %%==================================================================== 16 | %% Internal functions 17 | %%==================================================================== 18 | 19 | fun_kv({<<"Module">>, M}) -> 20 | {module, binary_to_atom(M, latin1)}; 21 | fun_kv({<<"Uniq">>, U}) -> 22 | {uniq, binary_to_integer(U)}; 23 | fun_kv({<<"Index">>, V}) -> 24 | {index, binary_to_integer(V)}; 25 | fun_kv({<<"Address">>, <<"0x", Addr:16/binary>>}) -> 26 | {address, binary_to_integer(Addr, 16)}; 27 | fun_kv({<<"Native_address">>, <<"0x", Addr:16/binary>>}) -> 28 | {native_address, binary_to_integer(Addr, 16)}; 29 | fun_kv({<<"Refc">>, U}) -> 30 | {refc, binary_to_integer(U)}. 31 | -------------------------------------------------------------------------------- /src/edump_heap.erl: -------------------------------------------------------------------------------- 1 | -module('edump_heap'). 2 | 3 | %% API exports 4 | -export([parse/1 5 | ,parse_dict/1 6 | ,parse_msgs/1 7 | ,reconstruct/2 8 | ,reconstruct_dict/2 9 | ,reconstruct_msgs/2 10 | ,bin_refs/1 11 | ]). 12 | 13 | %%==================================================================== 14 | %% API functions 15 | %%==================================================================== 16 | -type addr() :: binary(). 17 | -type heap() :: [{addr(), edump_mem:value()}]. 18 | -spec parse(Data::binary()) -> heap(). 19 | parse(Data) -> 20 | [begin 21 | [Addr, Rest] = binary:split(Line, <<":">>), 22 | {Addr, edump_mem:parse(Rest)} 23 | end 24 | || Line <- edump_parse:lines(Data)]. 25 | 26 | parse_dict(Data) -> 27 | [ edump_mem:parse(Addr) 28 | || Addr <- edump_parse:lines(Data) ]. 29 | 30 | parse_msgs(Data) -> 31 | [begin 32 | [Addr, Rest] = binary:split(Line, <<":">>), 33 | {edump_mem:parse(Addr), edump_mem:parse(Rest)} 34 | end 35 | || Line <- edump_parse:lines(Data)]. 36 | 37 | reconstruct_msgs(Msgs, Heap) -> 38 | [{r(Ptr, Heap), 39 | r(Data, Heap)} 40 | || {Ptr, Data} <- Msgs]. 41 | 42 | reconstruct_dict(Dict, Heap) -> 43 | [reconstruct(Item, Heap) 44 | || Item <- Dict]. 45 | 46 | reconstruct(Ptr, Heap) -> 47 | r(Ptr, Heap). 48 | 49 | r({cons, Head, Tail}, Heap) -> 50 | [r(Head, Heap) | r(Tail, Heap)]; 51 | r({tuple, List}, Heap) -> 52 | list_to_tuple([ r(Item, Heap) 53 | || Item <- List ]); 54 | r(nil, _Heap) -> 55 | []; 56 | r({heap_ptr, Ptr}, Heap) -> 57 | case proplists:get_value(Ptr, Heap) of 58 | undefined -> 59 | {'$edump_bad_ptr$', Ptr}; 60 | Value -> 61 | r(Value, Heap) 62 | end; 63 | r({atom, Atom} = A, _Heap) -> 64 | try 65 | binary_to_existing_atom(Atom, latin1) 66 | catch 67 | error:badarg -> 68 | A 69 | end; 70 | r({pid, What}, _Heap) -> 71 | {'$pid$', What}; 72 | r({port, What}, _Heap) -> 73 | {'$port$', What}; 74 | r({dist_external, BinTerm}, _Heap) -> 75 | edump_parse:term(BinTerm, unsafe_dist_external); 76 | r(Else, _Heap) -> 77 | Else. 78 | 79 | bin_refs(Heap) -> 80 | Refs = list_bin_refs([ Val || {_Addr, Val} <- Heap], ordsets:new()), 81 | [{binary, Ref} || Ref <- ordsets:to_list(Refs)]. 82 | 83 | %%==================================================================== 84 | %% Internal functions 85 | %%==================================================================== 86 | 87 | list_bin_refs(List, Acc) -> 88 | lists:foldl(fun bin_ref/2, Acc, List). 89 | 90 | bin_ref({sub_bin, {_Ptr, _Offset, _Length}} = Ref, Acc) -> 91 | ordsets:add_element(Ref, Acc); 92 | bin_ref({refc_bin, {_Ptr, _Offset, _Length}} = Ref, Acc) -> 93 | ordsets:add_element(Ref, Acc); 94 | bin_ref({cons, Head, Tail}, Acc) -> 95 | bin_ref(Tail, bin_ref(Head, Acc)); 96 | bin_ref({tuple, Slots}, Acc) -> 97 | list_bin_refs(Slots, Acc); 98 | bin_ref(_, Acc) -> 99 | Acc. 100 | -------------------------------------------------------------------------------- /src/edump_idx.erl: -------------------------------------------------------------------------------- 1 | -module('edump_idx'). 2 | 3 | %% API exports 4 | -export([open/1 5 | ,open/2 6 | ,default_options/0 7 | ,reopen/1 8 | ,close/1 9 | ,to_file/1 10 | ,to_file/2 11 | ,segments/1 12 | ,seg_id/1 13 | ,seg_size/1 14 | ,find_id/2 15 | ,find_ids/2 16 | ,segments_of_type/2 17 | ,read_by_id/2 18 | ,read_by_ids/2 19 | ,read_seg/2 20 | ,read_full_seg/2 21 | ,read_full_seg/3 22 | ,read_full/4 23 | ]). 24 | 25 | -include("edump_seg.hrl"). 26 | 27 | -record(index, {crashdump_file :: string(), 28 | segments = [] :: [#seg{}]}). 29 | 30 | -record(handle, {fd :: file:fd(), % fd to crashdump file 31 | index_file :: file:name(), 32 | crashdump_file :: file:name(), 33 | index :: #index{}}). 34 | 35 | -record(index_file, {vsn, index}). 36 | 37 | -opaque handle() :: #handle{}. 38 | -export_type([handle/0]). 39 | 40 | -define(READ_SIZE, 16384). 41 | 42 | %%==================================================================== 43 | %% API functions 44 | %%==================================================================== 45 | 46 | open(CrashdumpFile) -> 47 | open(CrashdumpFile, default_options()). 48 | 49 | default_options() -> 50 | #{write_index => true, 51 | index_checking => by_size, 52 | force_rebuild => false}. 53 | 54 | open(CrashdumpFile, Opts = #{force_rebuild := true}) -> 55 | case crashdump(CrashdumpFile, Opts) of 56 | {error, _} = E -> E; 57 | {index, Fd, Index} -> 58 | IndexFile = index_filename(CrashdumpFile, Opts), 59 | case maps:get(write_index, Opts, true) of 60 | true -> to_file(IndexFile, Index); 61 | false -> ok 62 | end, 63 | handle_from_index(CrashdumpFile, Fd, IndexFile, Index) 64 | end; 65 | open(CrashdumpFile, Opts) -> 66 | case index_exists(CrashdumpFile, Opts) of 67 | {exists, Index, IndexFile} -> 68 | {ok, Fd} = open_raw(CrashdumpFile), 69 | case check_index(CrashdumpFile, Index, Fd, Opts) of 70 | {index, Fd, Index} -> 71 | handle_from_index(CrashdumpFile, Fd, IndexFile, Index); 72 | {error, _} = Err -> 73 | Err 74 | end; 75 | {no_index, IndexFile} -> 76 | open(CrashdumpFile, Opts#{force_rebuild => true, 77 | index_file => IndexFile}) 78 | end. 79 | 80 | close(#handle{fd = undefined} = H) -> 81 | H; 82 | close(#handle{fd = Fd} = H) -> 83 | file:close(Fd), 84 | H#handle{fd = undefined}. 85 | 86 | reopen(#handle{} = H) -> 87 | handle_from_handle(close(H)). 88 | 89 | segments(#handle{} = H) -> 90 | segments(handle_index(H)); 91 | segments(#index{segments=Segs}) -> 92 | Segs. 93 | 94 | read_seg(false, _) -> 95 | not_present; 96 | read_seg(Seg = #seg{}, 97 | #handle{} = H) -> 98 | raw_read_seg(Seg, handle_fd(H)). 99 | 100 | seg_size(#seg{data_start = undefined}) -> 101 | 0; 102 | seg_size(#seg{data_start = Start, 103 | seg_end = End}) -> 104 | End - Start. 105 | 106 | seg_size_full(#seg{seg_start = Start, 107 | seg_end = End}) -> 108 | End - Start. 109 | 110 | read_full_seg(S, H) -> 111 | read_full_seg(S, 0, H). 112 | 113 | read_full_seg(#seg{seg_start = Start, 114 | seg_end = End}, Slack, H) -> 115 | read_full(Start, End, Slack, H). 116 | 117 | read_full(Start, End, Slack, #handle{} = H) -> 118 | IOStart = Start - Slack, 119 | IOEnd = End + Slack, 120 | file:pread(handle_fd(H), IOStart, IOEnd - IOStart). 121 | 122 | seg_id(#seg{id = ID}) -> ID. 123 | 124 | find_id(Id, Index) -> 125 | lists:keyfind(Id, #seg.id, segments(Index)). 126 | 127 | find_ids(Ids, Index) -> 128 | find(fun (#seg{id=Id}) -> 129 | lists:member(Id, Ids) 130 | end, 131 | Index). 132 | 133 | find(Filter, Index) -> 134 | lists:filter(Filter, segments(Index)). 135 | 136 | segments_of_type(any, Index) -> 137 | segments(Index); 138 | segments_of_type(Type, Index) -> 139 | find(fun (#seg{id={T, _}}) when T =:= Type -> true; 140 | (#seg{id=T}) when T =:= Type -> true; 141 | (_) -> false 142 | end, 143 | Index). 144 | 145 | read_by_ids(Ids, Index) -> 146 | [{seg_id(Seg), read_seg(Seg, Index)} 147 | || Seg <- find_ids(Ids, Index)]. 148 | 149 | read_by_id(Id, Index) -> 150 | read_seg(find_id(Id, Index), Index). 151 | 152 | %%==================================================================== 153 | %% Internal functions 154 | %%==================================================================== 155 | 156 | -spec index_exists(file:name(), boolean()) -> 157 | {exists, #index{}} | 158 | {no_index, file:name()}. 159 | index_exists(CrashdumpFile, Opts) -> 160 | IndexFile = index_filename(CrashdumpFile, Opts), 161 | case file:read_file(IndexFile) of 162 | {ok, B} -> 163 | try fixup(erlang:binary_to_term(B), 164 | maps:get(ignore_vsn_mismatch, Opts, true)) of 165 | rebuild -> 166 | {no_index, IndexFile}; 167 | Index -> 168 | {exists, Index, IndexFile} 169 | catch 170 | error:enoent -> 171 | {no_index, IndexFile} 172 | end; 173 | {error, enoent} -> 174 | {no_index, IndexFile} 175 | end. 176 | 177 | index_filename(_, #{index_file := File}) -> 178 | File; 179 | index_filename(CrashdumpFile, _Opts) -> 180 | CrashdumpFile ++ ".eidx". 181 | 182 | to_file(Idx) -> 183 | to_file(index_file(Idx) ++ ".eidx", Idx). 184 | 185 | to_file(File, Idx) -> 186 | Data = #index_file{vsn = my_vsn(), 187 | index = Idx}, 188 | file:write_file(File, erlang:term_to_binary(Data, [compressed])). 189 | 190 | fixup(#index_file{vsn = FileVSN, index=Idx}, IgnoreMismatch) -> 191 | case my_vsn() of 192 | FileVSN -> 193 | Idx; 194 | Wrong when IgnoreMismatch -> 195 | fixup_idx(Wrong, Idx); 196 | _Wrong -> 197 | rebuild 198 | end; 199 | fixup(_, _) -> 200 | rebuild. 201 | 202 | fixup_idx(_Vsn, Idx = #index{}) -> Idx; 203 | fixup_idx(_, _) -> rebuild. 204 | 205 | handle_from_handle(#handle{fd = undefined, 206 | crashdump_file = CrashdumpFile, 207 | index_file = IndexFile, 208 | index= Index}) -> 209 | handle_from_index(CrashdumpFile, IndexFile, Index). 210 | 211 | handle_from_index(CrashdumpFile, IndexFile, Index) -> 212 | {ok, CdFd} = open_raw(CrashdumpFile), 213 | handle_from_index(CrashdumpFile, CdFd, IndexFile, Index). 214 | 215 | handle_from_index(CrashdumpFile, CdFd, IndexFile, Index) -> 216 | #handle{crashdump_file = CrashdumpFile, 217 | index_file = IndexFile, 218 | index = Index, 219 | fd = CdFd}. 220 | 221 | handle_from_index(Index = #index{crashdump_file=File}, FD) -> 222 | #handle{fd = FD, 223 | crashdump_file = File, 224 | index = Index}. 225 | 226 | index_file(#index{crashdump_file=File}) -> File. 227 | handle_index(#handle{index=Index}) -> Index. 228 | handle_fd(#handle{fd=Fd}) -> Fd. 229 | 230 | -spec segment_id(binary()) -> segment_id(). 231 | segment_id(Bin) -> 232 | case binary:split(Bin, <<":">>) of 233 | [<<"mod">>, Name] -> {mod, binary_to_atom(Name, latin1)}; 234 | [A,B] -> {binary_to_atom(A, latin1), B}; 235 | [A] -> binary_to_atom(A, latin1) 236 | end. 237 | 238 | raw_read_seg(#seg{data_start=undefined}, _) -> 239 | <<>>; % Empty segment 240 | raw_read_seg(#seg{data_start=Start, seg_end=End}, Fd) 241 | when is_integer(End), is_integer(Start), End >= Start -> 242 | {ok, D} = file:pread(Fd, Start, End-Start), 243 | D. 244 | 245 | open_raw(File) -> 246 | file:open(File, [raw, binary, read, 247 | read_ahead]). 248 | 249 | %% Build a list of segments in reverse order by scanning lines in the 250 | %% crashdump file. 251 | build_index(FD, Offset, Acc) -> 252 | case file:read_line(FD) of 253 | {ok, <<"=", HeaderLine/binary>> = Line} -> 254 | NewOffset = byte_size(Line)+Offset, 255 | Header = binary:part(HeaderLine, 0, byte_size(HeaderLine)-1), 256 | Seg = #seg{id=segment_id(Header), 257 | seg_start=Offset, 258 | data_start=unknown, 259 | seg_end=unknown}, 260 | build_index(FD, NewOffset, [Seg | fix_seg_end(Offset, Acc)]); 261 | {ok, Line} when is_binary(Line) -> 262 | NewOffset = byte_size(Line) + Offset, 263 | build_index(FD, NewOffset, fix_data_start(Offset, Acc)); 264 | eof -> 265 | Segs = lists:reverse(fix_seg_end(Offset, Acc)), 266 | {segs, Segs}; 267 | {error, _} = E -> 268 | E 269 | end. 270 | 271 | fix_seg_end(_, []) -> 272 | []; 273 | fix_seg_end(Offset, [S = #seg{data_start=unknown, seg_end=unknown} | Rest]) -> 274 | [S#seg{data_start=undefined, seg_end=Offset-1} | Rest]; 275 | fix_seg_end(Offset, [S = #seg{seg_end=unknown} | Rest]) -> 276 | [S#seg{seg_end=Offset-1} | Rest]. 277 | 278 | fix_data_start(_Offset, [#seg{data_start=I}|_] = Acc) 279 | when is_integer(I) -> 280 | Acc; 281 | fix_data_start(Offset, [S = #seg{data_start=unknown} | Rest]) -> 282 | [S#seg{data_start=Offset} | Rest]. 283 | 284 | my_vsn() -> 285 | proplists:get_value(vsn,?MODULE:module_info(attributes)). 286 | 287 | crashdump(File, Opts) -> 288 | {ok, F} = open_raw(File), 289 | case build_index(F, 0, []) of 290 | {error, _} = E -> E; 291 | {segs, Segs} -> 292 | Index = #index{crashdump_file = File, 293 | segments = Segs}, 294 | check_index(File, Index, F, Opts) 295 | end. 296 | 297 | check_index(CrashdumpFile, Index, Fd, Opts = #{index_checking := by_size}) -> 298 | case filelib:file_size(CrashdumpFile) of 299 | Small when Small < 10000000 -> 300 | check_index(CrashdumpFile, Index, Fd, 301 | Opts#{index_checking => full}); 302 | _TooBig -> 303 | check_index(CrashdumpFile, Index, Fd, 304 | Opts#{index_checking => cheap}) 305 | end; 306 | check_index(_CrashdumpFile, Index, Fd, #{index_checking := none}) -> 307 | {index, Fd, Index}; 308 | check_index(_CrashdumpFile, Index, Fd, #{index_checking := Checkinglevel}) -> 309 | run_checks(Index, Fd, checks(Checkinglevel)). 310 | 311 | checks(cheap) -> 312 | [fun check_gaps/2, 313 | fun check_seg_data/2, 314 | fun check_size/2]; 315 | checks(full) -> 316 | checks(cheap) ++ 317 | [fun check_readability/2, 318 | fun check_parse/2]. 319 | 320 | 321 | 322 | run_checks(Index, Fd, []) -> 323 | {index, Fd, Index}; 324 | run_checks(Index, Fd, [F | Rest]) -> 325 | case F(Index, Fd) of 326 | ok -> 327 | run_checks(Index, Fd, Rest); 328 | Err -> 329 | Err 330 | end. 331 | 332 | check_gaps(Index, _Fd) -> 333 | case gaps(Index) of 334 | [] -> ok; 335 | Gaps -> {error, {gaps, Gaps}} 336 | end. 337 | 338 | gaps(#index{segments=Segs}) -> 339 | gaps(-1, Segs, []). 340 | 341 | gaps(_, [], Acc) -> lists:reverse(Acc); 342 | gaps(Pos, [#seg{seg_start=PosPlus1, seg_end=NewPos} | Segs], Acc) 343 | when Pos + 1 =:= PosPlus1 -> 344 | gaps(NewPos, Segs, Acc); 345 | gaps(Pos, [#seg{seg_start=OtherPos, seg_end=NewPos} | Segs], Acc) -> 346 | gaps(NewPos, Segs, [{Pos, OtherPos} | Acc]). 347 | 348 | check_readability(Index, Fd) -> 349 | Segments = segments(Index), 350 | seg_readable(Fd, Segments). 351 | 352 | seg_readable(_Fd, []) -> 353 | ok; 354 | seg_readable(Fd, [S = #seg{} | Rest]) -> 355 | try raw_read_seg(S, Fd) of 356 | <<"=", _/binary>> = Data-> 357 | {error, 358 | {data_starts_with_seg_marker, S, Data}}; 359 | Data when is_binary(Data) -> 360 | case check_for_marker(Data) of 361 | no_marker -> 362 | seg_readable(Fd, Rest); 363 | _ -> 364 | {error, {seg_contains_marker, Data}} 365 | end; 366 | Else -> 367 | {error, {seg_read_failed, S, 368 | {not_binary, Else}}} 369 | catch 370 | Class:Ex -> 371 | {error, {seg_read_failed, S, 372 | {Class, Ex, erlang:get_stacktrace()}}} 373 | end. 374 | 375 | check_for_marker(Data) -> 376 | case binary:match(Data, <<"\n=">>) of 377 | nomatch -> 378 | no_marker; 379 | _ -> 380 | marker 381 | end. 382 | 383 | check_seg_data(#index{segments=Segs}, _FD) -> 384 | case [ S 385 | || S <- Segs, 386 | bad_seg_data(S) ] of 387 | [] -> 388 | ok; 389 | BadSegs -> 390 | {error, {segs_with_bad_data, BadSegs}} 391 | end. 392 | 393 | bad_seg_data(#seg{id = Id, 394 | seg_start = Start, 395 | data_start = undefined, 396 | seg_end = End}) 397 | when is_integer(Start), 398 | is_integer(End) -> 399 | lists:member(edump_seg:type(Id), 400 | non_zero_length_types()); 401 | bad_seg_data(#seg{seg_start = Start, 402 | data_start = Data, 403 | seg_end = End}) 404 | when is_integer(Start), 405 | is_integer(Data), 406 | is_integer(End) -> 407 | false; 408 | bad_seg_data(_) -> 409 | true. 410 | 411 | non_zero_length_types() -> 412 | [erl_crash_dump 413 | ,memory 414 | ,hash_table 415 | ,index_table 416 | ,allocated_areas 417 | ,allocator 418 | ,port 419 | ,ets 420 | ,timer 421 | ,visible_node 422 | ,not_connected 423 | ,loaded_modules 424 | ,mod 425 | ,'fun' 426 | ,proc 427 | ,proc_dictionary 428 | ,proc_messages 429 | ,atoms 430 | ]. 431 | 432 | check_size(Index, FD) -> 433 | Segments = segments(Index), 434 | Size = lists:foldl(fun (S, Acc) -> 435 | seg_size_full(S) + Acc 436 | end, 437 | 0, 438 | Segments), 439 | NewLines = length(Segments), 440 | FullSize = NewLines + Size, 441 | case file:position(FD, eof) of 442 | {ok, FullSize} -> 443 | ok; 444 | {ok, Other} -> 445 | {error, {wrong_size, 446 | [{segs, FullSize}, 447 | {missing, Other - FullSize}, 448 | {file, Other}]}}; 449 | Else -> 450 | Else 451 | end. 452 | 453 | check_parse(Index, FD) -> 454 | case edump_seg:first_parse_failure(any, handle_from_index(Index, FD)) of 455 | no_failures -> 456 | ok; 457 | F -> 458 | {error, F} 459 | end. 460 | -------------------------------------------------------------------------------- /src/edump_link_list.xrl: -------------------------------------------------------------------------------- 1 | %% Lexer for the Link list: line in a crashdump proc segment. 2 | Definitions. 3 | 4 | P = [0-9.] 5 | D = [0-9] 6 | U = [A-Z] 7 | L = [a-z] 8 | A = ({U}|{L}|{D}|_|@) 9 | 10 | Rules. 11 | 12 | \[ : {token,{list_start,TokenLine}}. 13 | \] : {token,{list_end,TokenLine}}. 14 | ,\s : {token,{list_sep,TokenLine}}. 15 | #Port<{P}+> : {token,{port,TokenLine,iolist_to_binary(TokenChars)}}. 16 | #Ref<{P}+> : {token,{ref,TokenLine,iolist_to_binary(TokenChars)}}. 17 | %% I call pids procs throughout edump, this follows that convention. 18 | <{P}+> : {token,{proc,TokenLine,iolist_to_binary(TokenChars)}}. 19 | 20 | {L}{A}* : {token,{atom,TokenLine,iolist_to_binary(TokenChars)}}. 21 | '(\\\^.|\\.|[^'])*' : Atom = string_gen(strip(TokenChars,TokenLen)), 22 | case is_node(Atom) of 23 | true -> {token, {node,TokenLine,iolist_to_binary(Atom)}}; 24 | false -> {token, {atom,TokenLine,iolist_to_binary(Atom)}} 25 | end. 26 | { : {token,{tuple_start,TokenLine}}. 27 | , : {token,{tuple_sep,TokenLine}}. 28 | } : {token,{tuple_end,TokenLine}}. 29 | 30 | Erlang code. 31 | 32 | strip(TokenChars,TokenLen) -> 33 | lists:sublist(TokenChars, 2, TokenLen - 2). 34 | 35 | is_node(String) -> 36 | case string:find(String, "@") of 37 | nomatch -> false; 38 | _ -> true 39 | end. 40 | 41 | string_gen([$\\|Cs]) -> 42 | string_escape(Cs); 43 | string_gen([C|Cs]) -> 44 | [C|string_gen(Cs)]; 45 | string_gen([]) -> []. 46 | 47 | string_escape([O1,O2,O3|S]) when 48 | O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 -> 49 | [(O1*8 + O2)*8 + O3 - 73*$0|string_gen(S)]; 50 | string_escape([$^,C|Cs]) -> 51 | [C band 31|string_gen(Cs)]; 52 | string_escape([C|Cs]) when C >= $\000, C =< $\s -> 53 | string_gen(Cs); 54 | string_escape([C|Cs]) -> 55 | [escape_char(C)|string_gen(Cs)]. 56 | 57 | escape_char($n) -> $\n; %\n = LF 58 | escape_char($r) -> $\r; %\r = CR 59 | escape_char($t) -> $\t; %\t = TAB 60 | escape_char($v) -> $\v; %\v = VT 61 | escape_char($b) -> $\b; %\b = BS 62 | escape_char($f) -> $\f; %\f = FF 63 | escape_char($e) -> $\e; %\e = ESC 64 | escape_char($s) -> $\s; %\s = SPC 65 | escape_char($d) -> $\d; %\d = DEL 66 | escape_char(C) -> C. 67 | -------------------------------------------------------------------------------- /src/edump_link_list_parser.yrl: -------------------------------------------------------------------------------- 1 | %% Grammar for parsing Link list: line in proc segments. 2 | Nonterminals 3 | list list_elements link pid monitor registered_name. 4 | 5 | Terminals 6 | list_start list_sep list_end port ref proc atom node tuple_start tuple_sep tuple_end. 7 | 8 | Rootsymbol list. 9 | 10 | list -> list_start list_elements list_end : '$2'. 11 | 12 | list_elements -> link : ['$1']. 13 | list_elements -> link list_sep list_elements : ['$1'] ++ '$3'. 14 | 15 | link -> monitor : '$1'. 16 | link -> pid : {link, '$1'}. 17 | 18 | pid -> proc : {proc, unwrap('$1')}. 19 | pid -> port : {port, unwrap('$1')}. 20 | pid -> registered_name : {registered, '$1'}. 21 | 22 | monitor -> tuple_start atom tuple_sep pid tuple_sep ref tuple_end : 23 | {case unwrap('$2') of 24 | <<"to">> -> monitor; 25 | <<"from">> -> monitored_by 26 | end, '$4', unwrap('$6')}. 27 | 28 | registered_name -> atom : unwrap('$1'). 29 | registered_name -> tuple_start atom tuple_sep node tuple_end : 30 | {unwrap('$2'), unwrap('$4')}. 31 | 32 | Erlang code. 33 | 34 | unwrap({_,_,V}) -> V. 35 | -------------------------------------------------------------------------------- /src/edump_mem.erl: -------------------------------------------------------------------------------- 1 | -module('edump_mem'). 2 | 3 | %% API exports 4 | -export([parse/1 5 | ,heap_ptrs/2 6 | ]). 7 | 8 | -type edump_atom() :: {'atom', Name::binary()}. 9 | -type edump_pid() :: {'pid', edump_idx:seg_id()}. 10 | -type edump_port() :: {'port', edump_idx:seg_id()}. 11 | -type edump_heap_ptr() :: {'heap_ptr', Ptr::binary()}. 12 | -type edump_cons() :: {'cons', Head::edump_types(), Tail::edump_types()}. 13 | -type edump_binary() :: {'bin', Bytes::binary()}. 14 | 15 | -type bin_ptr() :: binary(). 16 | -type bin_offset() :: pos_integer(). 17 | 18 | -type bin_length() :: pos_integer(). 19 | 20 | -type bin_ref_info() :: {bin_ptr(), 21 | bin_offset(), bin_length()}. 22 | 23 | -type bin_ref() :: {'sub_bin', bin_ref_info()} | 24 | {'refc_bin', bin_ref_info()}. 25 | 26 | -type edump_types() :: edump_atom() | 27 | edump_pid() | 28 | edump_port() | 29 | edump_heap_ptr() | 30 | edump_tuple() | 31 | edump_cons() | 32 | edump_binary() | 33 | number() | 34 | bin_ref() | 35 | {'dist_external', binary()} | 36 | 'nil'. 37 | -type edump_tuple() :: {'tuple', edump_types()}. 38 | 39 | -type value() :: edump_types(). 40 | -export_type([value/0]). 41 | 42 | %%==================================================================== 43 | %% API functions 44 | %%==================================================================== 45 | 46 | -spec parse(binary()) -> edump_types(). 47 | parse(<<"A", Rest/binary>>) -> 48 | {Len, Atom} = thing_len(Rest), 49 | parse_atom(Len, Atom); 50 | parse(<<"H",HeapAddr/binary>>) -> 51 | {heap_ptr, HeapAddr}; 52 | parse(<<"t", Rest/binary>>) -> 53 | {Len, TupleData} = thing_len(Rest), 54 | parse_tuple(Len, TupleData); 55 | parse(<<"I", IntStr/binary>>) -> 56 | binary_to_integer(IntStr); 57 | parse(<<"B", BignumStr/binary>>) -> 58 | binary_to_integer(BignumStr); 59 | parse(<<"F", Rest/binary>>) -> 60 | {_Len, FloatData} = thing_len(Rest), 61 | binary_to_float(FloatData); 62 | parse(<<"P", Pid/binary>>) -> 63 | {pid, {proc, Pid}}; 64 | parse(<<"p", Port/binary>>) -> 65 | {port, {port, <<"#Port", Port/binary>>}}; 66 | parse(<<"l", Rest/binary>>) -> 67 | [Head, Tail] = binary:split(Rest, <<"|">>), 68 | {cons, parse(Head), parse(Tail)}; 69 | parse(<<"Yh", Rest/binary>>) -> 70 | case parse_bytes(Rest,bin,incomplete_binary) of 71 | {bin, B} -> B; 72 | Else -> Else 73 | end; 74 | parse(<<"Ys", Rest/binary>>) -> 75 | [Val, Offset, Length] = binary:split(Rest, <<":">>, [global]), 76 | {sub_bin, {Val, 77 | binary_to_integer(Offset, 16), 78 | binary_to_integer(Length, 16)}}; 79 | parse(<<"Yc", Rest/binary>>) -> 80 | [Val, Offset, Length] = binary:split(Rest, <<":">>, [global]), 81 | {refc_bin, {Val, 82 | binary_to_integer(Offset, 16), 83 | binary_to_integer(Length, 16)}}; 84 | parse(<<"E", Rest/binary>>) -> 85 | parse_bytes(Rest, dist_external, broken_dist_external); 86 | parse(<<"N">>) -> 87 | nil. 88 | 89 | heap_ptrs({heap,_} = H, Acc) -> 90 | [H | Acc]; 91 | heap_ptrs({tuple, Entries}, Acc) -> 92 | lists:foldl(fun heap_ptrs/2, 93 | Acc, 94 | Entries); 95 | heap_ptrs(_, Acc) -> 96 | Acc. 97 | 98 | %%==================================================================== 99 | %% Internal functions 100 | %%==================================================================== 101 | 102 | parse_tuple(0, <<>>) -> 103 | {tuple, []}; 104 | parse_tuple(Len, Rest) -> 105 | TupleEntries = binary:split(Rest, <<",">>, [global]), 106 | case length(TupleEntries) of 107 | Len -> 108 | {tuple, [parse(Entry) 109 | || Entry <- TupleEntries]}; 110 | BadLen -> 111 | erlang:error({bad, tuple, {Len, vs, BadLen}, 112 | Rest, 113 | TupleEntries}) 114 | end. 115 | 116 | parse_atom(Len, Rest) -> 117 | {atom, binary:part(Rest, 0, Len)}. 118 | 119 | parse_bytes(Data,CompleteTag,_IncompleteTag) -> 120 | {Len, Thing} = thing_len(Data), 121 | Bytes = << <<(binary_to_integer(B, 16)):8>> 122 | || <> <= Thing >>, 123 | case byte_size(Bytes) of 124 | Len -> 125 | {CompleteTag, Bytes}; 126 | _ -> 127 | erlang:error({bad, CompleteTag, Data, Bytes}) 128 | end. 129 | 130 | thing_len(Data) -> 131 | [Size, Rest] = binary:split(Data, <<":">>), 132 | Len = binary_to_integer(Size, 16), 133 | {Len, Rest}. 134 | -------------------------------------------------------------------------------- /src/edump_modules.erl: -------------------------------------------------------------------------------- 1 | -module('edump_modules'). 2 | 3 | %% API exports 4 | -export([parse_loaded/1 5 | ,parse_mod/1 6 | ]). 7 | 8 | %%==================================================================== 9 | %% API functions 10 | %%==================================================================== 11 | 12 | parse_loaded(Data) -> 13 | edump_parse:kv_section(fun loaded/1, Data). 14 | 15 | parse_mod(Data) -> 16 | edump_parse:kv_section(fun mod/1, Data). 17 | 18 | %%==================================================================== 19 | %% Internal functions 20 | %%==================================================================== 21 | 22 | loaded({<<"Current code">>, Code}) -> 23 | {current_code, binary_to_integer(Code)}; 24 | loaded({<<"Old code">>, Old}) -> 25 | {old_code, binary_to_integer(Old)}. 26 | 27 | mod({<<"Current size">>, Sz}) -> 28 | {current_size, binary_to_integer(Sz)}; 29 | mod({<<"Current attributes">>, Attr}) -> 30 | {current_attributes, 31 | edump_parse:term_hexbin(Attr, unsafe_attribute_info)}; 32 | mod({<<"Current compilation info">>, Info}) -> 33 | {current_compilation_info, 34 | edump_parse:term_hexbin(Info, unsafe_compilation_info)}; 35 | mod({<<"Old size">>, Sz}) -> 36 | {old_size, binary_to_integer(Sz)}; 37 | mod({<<"Old attributes">>, Attr}) -> 38 | {old_attributes, 39 | edump_parse:term_hexbin(Attr, unsafe_attribute_info)}; 40 | mod({<<"Old compilation info">>, Info}) -> 41 | {old_compilation_info, 42 | edump_parse:term_hexbin(Info, unsafe_compilation_info)}. 43 | -------------------------------------------------------------------------------- /src/edump_node.erl: -------------------------------------------------------------------------------- 1 | -module(edump_node). 2 | 3 | %% API exports 4 | -export([parse/1]). 5 | 6 | %%==================================================================== 7 | %% API functions 8 | %%==================================================================== 9 | 10 | parse(Data) -> 11 | edump_parse:kv_section(fun kv_line/1, Data). 12 | 13 | %%==================================================================== 14 | %% Internal functions 15 | %%==================================================================== 16 | 17 | kv_line({<<"Name">>, Name}) -> 18 | {name, binary_to_atom(Name, latin1)}; 19 | kv_line({<<"Creation">>, N}) -> 20 | {creation, case edump_parse:int_list(N) of 21 | [C] -> C; 22 | Else -> Else 23 | end}; 24 | kv_line({<<"Controller">>, <<"#Port",_/binary>> = Port}) -> 25 | {controller, {port, Port}}; 26 | kv_line({<<"Controller">>, <<"<",_/binary>> = Pid}) -> 27 | {controller, {proc, Pid}}; 28 | kv_line({<<"Remote link">>, Rest}) -> 29 | [From, To] = binary:split(Rest, <<" ">>), 30 | {remote_link, {proc, From}, {proc, To}}; 31 | kv_line({<<"Remote monitoring">>, Rest}) -> 32 | [From, To] = binary:split(Rest, <<" ">>), 33 | {remote_link, {proc, From}, {node, binary_to_atom(To, latin1)}}. 34 | -------------------------------------------------------------------------------- /src/edump_parse.erl: -------------------------------------------------------------------------------- 1 | -module('edump_parse'). 2 | 3 | %% API exports 4 | -export([lines/1 5 | ,kv_line/1 6 | ,atom_int/1 7 | ,atom_int_block/1 8 | ,int_list/1 9 | ,kv_section/2 10 | ,hexbin/1 11 | ,len_hexbin/1 12 | ,term/2 13 | ,term_hexbin/2 14 | ]). 15 | 16 | %%==================================================================== 17 | %% API functions 18 | %%==================================================================== 19 | 20 | lines(Data) -> 21 | [L || L <- binary:split(Data, <<"\n">>, [global]), 22 | L =/= <<>>]. 23 | 24 | kv_line(Line) -> 25 | [A, B] = binary:split(Line, <<": ">>), 26 | {A, B}. 27 | 28 | atom_int(Line) -> 29 | {A, B} = kv_line(Line), 30 | {binary_to_atom(A, latin1), binary_to_integer(B)}. 31 | 32 | atom_int_block(Data) -> 33 | [ atom_int(L) || L <- lines(Data) ]. 34 | 35 | int_list(L) -> 36 | [ binary_to_integer(I) 37 | || I <- binary:split(L, <<" ">>, [global]), 38 | I =/= <<>> ]. 39 | 40 | kv_section(LineParser, Data) when is_function(LineParser, 1) -> 41 | [ LineParser(kv_line(Line)) 42 | || Line <- lines(Data) ]. 43 | 44 | hexbin(B) -> 45 | << << (binary_to_integer(Byte, 16)) >> 46 | || <> <= B >>. 47 | 48 | len_hexbin(B) -> 49 | [Len,Bin] = binary:split(B, <<":">>), 50 | Length = binary_to_integer(Len, 16), 51 | Binary = hexbin(Bin), 52 | Length = byte_size(Binary), 53 | Binary. 54 | 55 | %% @doc Try to parse a hex encoded external term format. Returns the 56 | %% term if safe, otherwise ```{UnsafeTag, ETFBinary}'''. 57 | term_hexbin(HexBin, UnsafeTag) -> 58 | term(hexbin(HexBin), UnsafeTag). 59 | 60 | %% @doc Try to parse an external term format from a binary 61 | %% string. Return either the term, or {UnsafeTag, 62 | %% OriginalBinary}. This allows callers to safely process binary terms 63 | %% and be able to distinguish good from bad terms. 64 | term(BinTerm, UnsafeTag) -> 65 | try 66 | erlang:binary_to_term(BinTerm, [safe]) 67 | catch 68 | error:badarg -> 69 | {UnsafeTag, BinTerm} 70 | end. 71 | 72 | 73 | %%==================================================================== 74 | %% Internal functions 75 | %%==================================================================== 76 | -------------------------------------------------------------------------------- /src/edump_port.erl: -------------------------------------------------------------------------------- 1 | -module('edump_port'). 2 | 3 | %% API exports 4 | -export([parse/1]). 5 | 6 | %%==================================================================== 7 | %% API functions 8 | %%==================================================================== 9 | 10 | parse(Data) -> 11 | [parse_kv(edump_parse:kv_line(L)) 12 | || L <- edump_parse:lines(Data)]. 13 | 14 | %%==================================================================== 15 | %% Internal functions 16 | %%==================================================================== 17 | 18 | parse_kv({<<"Slot">>, V}) -> 19 | {slot, binary_to_integer(V)}; 20 | parse_kv({<<"Connected">>, Pid}) -> 21 | {connected, {proc, Pid}}; 22 | parse_kv({<<"Links">>, Pid}) -> 23 | {links, {proc, Pid}}; 24 | parse_kv({<<"Port controls linked-in driver">>, Driver}) -> 25 | {driver, Driver}; 26 | parse_kv({<<"Port controls external process">>, Process}) -> 27 | {driver, {spawn, Process}}; 28 | parse_kv({<<"Port is UNIX fd not opened by emulator">>, <>}) -> 29 | {driver, {unix, A, B}}; 30 | parse_kv({<<"Port controls forker process">>, Process}) -> 31 | {driver, {forker, Process}}; 32 | parse_kv({<<"Monitors">>, Data}) -> 33 | case re:run(Data, <<"\\((?[<>.0-9]+),(?#Ref[<>.0-9]+)\\)">>, 34 | [global, {capture, all_names, binary}]) of 35 | {match, MData} -> 36 | {monitors, 37 | [{{proc, Pid}, Ref} 38 | || [{ref, Ref}, {pid, Pid}] <- MData]} 39 | end. 40 | -------------------------------------------------------------------------------- /src/edump_proc.erl: -------------------------------------------------------------------------------- 1 | -module('edump_proc'). 2 | 3 | %% API exports 4 | -export([parse/1 5 | ,related_ids/1 6 | ,related_procs/1 7 | ,procs_in_index/1 8 | ,read/2 9 | ]). 10 | 11 | %%==================================================================== 12 | %% API functions 13 | %%==================================================================== 14 | 15 | parse(Data) when is_binary(Data) -> 16 | [parse_proc_data(L) || L <- edump_parse:lines(Data)]. 17 | 18 | related_ids({proc, Id}) -> 19 | [{proc_stack, Id}, 20 | {proc_heap, Id}, 21 | {proc_dictionary, Id}, 22 | {proc_messages, Id}, 23 | {ets, Id}]. 24 | 25 | related_procs(Info) -> 26 | lists:append([spawned_by(Info), 27 | links(Info), 28 | monitored_by(Info)]). 29 | 30 | spawned_by(Info) -> 31 | case proplists:get_value(spawned_by, Info) of 32 | undefined -> []; 33 | Who -> [{spawned_by, Who}] 34 | end. 35 | 36 | links(Info) -> 37 | [{link, Who} 38 | || {link, Who} <- proplists:get_value(links, Info, [])]. 39 | 40 | monitored_by(Info) -> 41 | [{monitored_by, Who, Ref} 42 | || {monitor, Who, Ref} <- proplists:get_value(links, Info, [])]. 43 | 44 | procs_in_index(Handle) -> 45 | [ edump_idx:seg_id(S) || S <- edump_idx:segs_of_type(proc, Handle) ]. 46 | 47 | read(Id, Handle) -> 48 | parse(edump_idx:read_by_id(Id, Handle)). 49 | 50 | %% State: Waiting 51 | %% Name: application_controller 52 | %% Spawned as: erlang:apply/2 53 | %% Spawned by: <0.2.0> 54 | %% Started: Wed Oct 3 23:47:05 2012 55 | %% Message queue length: 0 56 | %% Number of heap fragments: 0 57 | %% Heap fragment data: 0 58 | %% Link list: [<0.0.0>, <0.34.0>, <0.8.0>] 59 | %% Reductions: 7881 60 | %% Stack+heap: 28657 61 | %% OldHeap: 28657 62 | %% Heap unused: 6305 63 | %% OldHeap unused: 25599 64 | %% Program counter: 0x0000000014673478 (gen_server:loop/6 + 264) 65 | %% CP: 0x0000000000000000 (invalid) 66 | %% arity = 0 67 | parse_proc_data(<<"State: ", State/binary>>) -> 68 | {state, State}; 69 | parse_proc_data(<<"Name: ", Name/binary>>) -> 70 | {name, Name}; 71 | parse_proc_data(<<"Spawned as: ", As/binary>>) -> 72 | {spawned_as, As}; 73 | parse_proc_data(<<"Spawned by: []">>) -> 74 | {spawned_by, erlang}; 75 | parse_proc_data(<<"Spawned by: ", Pid/binary>>) -> 76 | {spawned_by, {proc, Pid}}; 77 | parse_proc_data(<<"Message queue length: ", Len/binary>>) -> 78 | {message_queue_length, binary_to_integer(Len)}; 79 | parse_proc_data(<<"Number of heap fragments: ", Frags/binary>>) -> 80 | {heap_fragments, binary_to_integer(Frags)}; 81 | parse_proc_data(<<"Heap fragment data: ", Data/binary>>) -> 82 | {heap_fragment_data, Data}; 83 | parse_proc_data(<<"Link list: ", Data/binary>>) -> 84 | {ok, Tokens, _} = edump_link_list:string(binary_to_list(Data)), 85 | {ok, Links} = edump_link_list_parser:parse(Tokens), 86 | {links, Links}; 87 | parse_proc_data(<<"Reductions: ", Reds/binary>>) -> 88 | {reductions, binary_to_integer(Reds)}; 89 | parse_proc_data(<<"Stack+heap: ", Mem/binary>>) -> 90 | {stack_plus_heap, binary_to_integer(Mem)}; 91 | parse_proc_data(<<"Program counter: ", PC/binary>>) -> 92 | [ProgramCounter,Rest] = binary:split(PC, <<" (">>), 93 | {cp, {ProgramCounter, binary:part(Rest, 0, byte_size(Rest)-1)}}; 94 | parse_proc_data(<<"CP: ", CP/binary>>) -> 95 | [CodePointer,Rest] = binary:split(CP, <<" (">>), 96 | {cp, {CodePointer, binary:part(Rest, 0, byte_size(Rest)-1)}}; 97 | parse_proc_data(<<"arity = ", Arity/binary>>) -> 98 | {arity, binary_to_integer(Arity)}; 99 | parse_proc_data(Line) -> 100 | case binary:split(Line, <<": ">>) of 101 | [A,B] -> {A, B}; 102 | [A] -> {unknown, A} 103 | end. 104 | 105 | %%==================================================================== 106 | %% Internal functions 107 | %%==================================================================== 108 | -------------------------------------------------------------------------------- /src/edump_proc_dot.erl: -------------------------------------------------------------------------------- 1 | -module('edump_proc_dot'). 2 | 3 | %% API exports 4 | -export([to_dot/2 5 | ,to_dot/3 6 | ,fewer_edges/3 7 | ]). 8 | 9 | %%==================================================================== 10 | %% API functions 11 | %%==================================================================== 12 | 13 | to_dot(File, DG) -> 14 | to_dot(File, DG, default_options()). 15 | 16 | default_options() -> 17 | #{graph_attributes => ["rankdir=lr"]}. 18 | 19 | to_dot(File, DG, Opts0) -> 20 | Opts = maps:merge(default_options(), Opts0), 21 | DotInfo = dot_info(DG, Opts), 22 | try proc_graph:render(DotInfo) of 23 | {ok, Str} -> 24 | file:write_file(File, Str); 25 | Else -> 26 | Else 27 | catch 28 | C:E -> 29 | erlang:error({C, E, DotInfo, erlang:get_stacktrace()}) 30 | end. 31 | 32 | fewer_edges({proc, _} = From, {proc, _} = To, link) -> 33 | From < To; 34 | fewer_edges({port, _}, {proc, _}, link) -> 35 | true; 36 | fewer_edges(_, erlang, spawned_by) -> 37 | true; 38 | fewer_edges(_, _, spawned_by) -> 39 | false; 40 | fewer_edges(_, _, _) -> 41 | true. 42 | 43 | %%==================================================================== 44 | %% Internal functions 45 | %%==================================================================== 46 | 47 | pids(DG) -> 48 | [ digraph:vertex(DG, V) || V <- digraph:vertices(DG) ]. 49 | 50 | relations(DG) -> 51 | [ begin 52 | {_, From, To, Label} = digraph:edge(DG, E), 53 | {From, To, Label} 54 | end 55 | || E <- digraph:edges(DG)]. 56 | 57 | 58 | pids_dot_info(DG, Opts) -> 59 | [ #{id => dot_id(Name), 60 | dot_attrs => dot_node_attributes(Name, Info) 61 | } 62 | || {Name, Info} <- pids(DG), 63 | include_node(Name, Info, Opts) ]. 64 | 65 | dot_info(DG, Opts = #{ graph_attributes := G }) -> 66 | #{ procs => pids_dot_info(DG, Opts), 67 | relations => relations_dot_info(DG, Opts), 68 | graph_attributes => G 69 | }. 70 | 71 | relations_dot_info(DG, Opts) -> 72 | [ #{ from => dot_id(From), 73 | to => dot_id(To), 74 | dot_attrs => dot_edge_attributes(From, To, Label) 75 | } 76 | || {From, To, Label} <- relations(DG), 77 | include_edge(From, To, Label, Opts) ]. 78 | 79 | include_node(Node, Info, #{include_node := F}) 80 | when is_function(F,2) -> 81 | F(Node, Info); 82 | include_node(_, _, _) -> 83 | true. 84 | 85 | include_edge(From, To, Label, #{include_edge := F}) 86 | when is_function(F, 3) -> 87 | F(From, To, Label); 88 | include_edge(_, _, _, _) -> 89 | true. 90 | 91 | 92 | 93 | dot_id(T) -> 94 | pid_dot_label(T). 95 | 96 | 97 | pid_dot_label({proc, ProcStr}) -> 98 | ProcStr; 99 | pid_dot_label({port, PortStr}) -> 100 | PortStr; 101 | pid_dot_label(erlang) -> 102 | "erlang". 103 | 104 | %% DOT shapes: plaintext ellipse oval circle egg triangle box diamond 105 | %% trapezium parallelogram house hexagon octagon note tab box3d 106 | %% component 107 | dot_node_attributes({proc, _} = Id, Info) -> 108 | Name = proc_name(Id, Info), 109 | lists:append([[io_lib:format("label=\"~s\"", [Name])], 110 | case proplists:get_value(in_dump, Info, false) of 111 | true -> []; 112 | false -> ["style=dotted"] 113 | end, 114 | case proplists:get_value(state, Info) of 115 | <<"Garbing">> -> ["color=red", "style=bold"]; 116 | <<"Exiting">> -> ["color=red", "style=dashed"]; 117 | <<"Running">> -> ["color=green"]; 118 | <<"Scheduled">> -> ["style=bold"]; 119 | _ -> [] 120 | end, 121 | ["shape=oval"]]); 122 | dot_node_attributes({port, Str}, _) -> 123 | [io_lib:format("label=\"~s\"", [Str]), 124 | "shape=box"]; 125 | dot_node_attributes(erlang, _) -> 126 | ["label=\"erlang\"", 127 | "shape=egg"]. 128 | 129 | dot_edge_attributes(From, To, Label) -> 130 | lists:append([format_label(Label), 131 | dot_edge_style(From, To, Label)]). 132 | 133 | dot_edge_style(_From, _To, {monitoring, _}) -> 134 | ["style=dashed"]; 135 | dot_edge_style(_From, _To, {monitored_by, _}) -> 136 | ["style=dashed"]; 137 | dot_edge_style(_, _, link) -> 138 | ["dir=none"]; 139 | dot_edge_style(_, _, _) -> 140 | []. 141 | 142 | format_label(link) -> 143 | []; 144 | format_label(L) -> 145 | [iolist_to_binary(["label=", $\", dot_edge_label(L), $\"])]. 146 | 147 | dot_edge_label(spawned_by) -> "spawned_by"; 148 | dot_edge_label(spawned) -> "spawned"; 149 | dot_edge_label({monitored_by, _}) -> "monitored_by"; 150 | dot_edge_label({monitoring, _}) -> "monitoring"; 151 | dot_edge_label(Label) -> 152 | io_lib:format("~p", [Label]). 153 | 154 | proc_name({proc, Pid}, Info) -> 155 | case proplists:get_value(name,Info) of 156 | undefined -> 157 | Pid; 158 | Name -> 159 | [Name, $\n, Pid] 160 | end. 161 | -------------------------------------------------------------------------------- /src/edump_proc_graph.erl: -------------------------------------------------------------------------------- 1 | -module(edump_proc_graph). 2 | 3 | %% API exports 4 | -export([from_handle/1 5 | ,pids/1 6 | ,relations/1 7 | ]). 8 | 9 | %%==================================================================== 10 | %% API functions 11 | %%==================================================================== 12 | 13 | from_handle(Handle) -> 14 | Segs = edump_idx:segments_of_type(proc, Handle), 15 | build_graph(Segs, digraph:new(), Handle). 16 | 17 | pids(DG) -> 18 | [ digraph:vertex(DG, V) || V <- digraph:vertices(DG) ]. 19 | 20 | relations(DG) -> 21 | [ begin 22 | {_, From, To, Label} = digraph:edge(DG, E), 23 | {From, To, Label} 24 | end 25 | || E <- digraph:edges(DG)]. 26 | 27 | %%==================================================================== 28 | %% Internal functions 29 | %%==================================================================== 30 | 31 | build_graph([], DG, _) -> 32 | DG; 33 | build_graph([Seg | Segs], DG, Handle) -> 34 | Id = edump_seg:id(Seg), 35 | Info = edump_seg:parse_seg(Seg, Handle), 36 | add_proc(Id, Info, DG), 37 | add_links(Id, 38 | edump_proc:related_procs(Info), 39 | DG), 40 | build_graph(Segs, DG, Handle). 41 | 42 | add_proc(Id, Info, DG) -> 43 | digraph:add_vertex(DG, Id, [{in_dump, true} | Info]). 44 | 45 | add_links(Id, Links, DG) -> 46 | [ add_link(Id, Link, DG) || Link <- Links ], 47 | ok. 48 | 49 | add_link(Self, {link, Who}, DG) -> 50 | maybe_add_vertex(Who, DG), 51 | maybe_add_edge(Self, Who, link, DG); 52 | %% maybe_add_edge(Who, Self, link, DG); 53 | add_link(Self, {spawned_by, Who}, DG) -> 54 | maybe_add_vertex(Who, DG), 55 | maybe_add_edge(Self, Who, spawned_by, DG); 56 | %% maybe_add_edge(Who, Self, spawned, DG); 57 | add_link(Self, {monitored_by, Who, Ref}, DG) -> 58 | maybe_add_vertex(Who, DG), 59 | maybe_add_edge(Self, Who, {monitored_by, Ref}, DG); 60 | add_link(Self, {monitoring, Who, Ref}, DG) -> 61 | maybe_add_vertex(Who, DG), 62 | maybe_add_edge(Self, Who, {monitoring, Ref}, DG). 63 | 64 | maybe_add_vertex(Who, DG) -> 65 | case digraph:vertex(DG, Who) of 66 | false -> 67 | digraph:add_vertex(DG, Who, []); 68 | _ -> 69 | ok 70 | end. 71 | 72 | maybe_add_edge(From, To, Label, DG) -> 73 | FromEdges = [ digraph:edge(DG, E) 74 | || E <- digraph:out_edges(DG, From) ], 75 | case [E || E = {_,F, T, L} <- FromEdges, 76 | L =:= Label, 77 | F =:= From, 78 | T =:= To] of 79 | [] -> 80 | digraph:add_edge(DG, From, To, Label); 81 | [_] -> 82 | ok 83 | end. 84 | -------------------------------------------------------------------------------- /src/edump_scheduler.erl: -------------------------------------------------------------------------------- 1 | -module(edump_scheduler). 2 | 3 | %% API exports 4 | -export([parse/1]). 5 | 6 | %%==================================================================== 7 | %% API functions 8 | %%==================================================================== 9 | 10 | %% =scheduler:3 11 | %% Scheduler Sleep Info Flags: 12 | %% Scheduler Sleep Info Aux Work: 13 | %% Current Port: 14 | %% Run Queue Max Length: 0 15 | %% Run Queue High Length: 0 16 | %% Run Queue Normal Length: 0 17 | %% Run Queue Low Length: 0 18 | %% Run Queue Port Length: 0 19 | %% Run Queue Flags: OUT_OF_WORK | HALFTIME_OUT_OF_WORK | NONEMPTY | UNKNOWN(134217728) 20 | %% Current Process: <0.39.0> 21 | %% Current Process State: Running 22 | %% Current Process Internal State: ACT_PRIO_NORMAL | USR_PRIO_NORMAL | PRQ_PRIO_NORMAL | ACTIVE | RUNNING 23 | %% Current Process Program counter: 0x00000000194cf850 (unknown function) 24 | %% Current Process CP: 0x000000001a3bc458 (erl_eval:do_apply/6 + 376) 25 | %% Current Process Limited Stack Trace: 26 | %% 0x0000000019efcbe8:SReturn addr 0x1A37DE00 (shell:exprs/7 + 712) 27 | %% 0x0000000019efcc00:SReturn addr 0x1A37D4E8 (shell:eval_exprs/7 + 168) 28 | %% 0x0000000019efcc58:SReturn addr 0x1A37D0B8 (shell:eval_loop/3 + 592) 29 | %% 0x0000000019efcc90:SReturn addr 0x18069BF8 () 30 | 31 | %% This section has a weird and irritating format, 32 | %% so we have do a crazy ish stateful parse. 33 | 34 | parse(Data) -> 35 | lists:reverse(parse_lines(edump_parse:lines(Data), [])). 36 | 37 | %%==================================================================== 38 | %% Internal functions 39 | %%==================================================================== 40 | 41 | parse_lines([], Acc) -> 42 | Acc; 43 | parse_lines([<<"Current Process Limited Stack Trace:">> | Stack], Acc) -> 44 | [{current_process_limited_stack_trace, Stack} | Acc]; 45 | parse_lines([Line | Rest], Acc) -> 46 | Acc1 = [edump_parse:kv_line(Line) | Acc], 47 | parse_lines(Rest, Acc1). 48 | -------------------------------------------------------------------------------- /src/edump_script.erl: -------------------------------------------------------------------------------- 1 | -module('edump_script'). 2 | 3 | %% API exports 4 | -export([main/1]). 5 | 6 | %%==================================================================== 7 | %% API functions 8 | %%==================================================================== 9 | 10 | main(["index" | Args]) -> 11 | case getopt:parse_and_check(index_options(), Args) of 12 | {ok, {IndexOpts, _Args1}} -> 13 | Dump = proplists:get_value(file, IndexOpts), 14 | Rebuild = proplists:get_value(rebuild, IndexOpts), 15 | Checking = proplists:get_value(checking, IndexOpts), 16 | io:format("Indexing ~p ~p~n", [Dump, IndexOpts]), 17 | case timer:tc(edump,open, 18 | [Dump, #{force_rebuild => Rebuild, 19 | index_checking => Checking}]) of 20 | {_Time, {error, What}} -> 21 | io:format("Couldn't index crashdump: ~p~n", [What]), 22 | erlang:halt(1); 23 | {Time, _} -> 24 | io:format("Indexed ~p in ~ps~n", 25 | [Dump, Time/1000000]) 26 | end; 27 | {error, What} -> 28 | getopt:format_error(index_options(), What), 29 | getopt:usage(index_options(), "edump index") 30 | end; 31 | main(["graph" | Args]) -> 32 | case getopt:parse_and_check(graph_options(), Args) of 33 | {ok, {GraphOpts, _Args1}} -> 34 | Dump = proplists:get_value(file, GraphOpts), 35 | DotFile = proplists:get_value(dot_file, GraphOpts), 36 | Attrs = string:tokens(proplists:get_value(graph_attrs, GraphOpts), 37 | ";"), 38 | GOpts = #{graph_attributes => Attrs, 39 | include_edge => 40 | case proplists:get_value(prune, GraphOpts) of 41 | true -> 42 | fun edump_proc_dot:fewer_edges/3; 43 | false -> 44 | undefined 45 | end}, 46 | io:format("Graph opts: ~p~n", [GOpts]), 47 | case edump_analyse:proc_graph(Dump, DotFile, 48 | GOpts) of 49 | ok -> 50 | io:format("Wrote graph to ~p~n", [DotFile]); 51 | {error, What} -> 52 | io:format("Couldn't write graph: ~p~n", [What]), 53 | erlang:halt(1) 54 | end; 55 | {error, What} -> 56 | getopt:format_error(graph_options(), What), 57 | getopt:usage(graph_options(), "edump graph"), 58 | erlang:halt(1) 59 | end; 60 | main(["info" | Args]) -> 61 | case getopt:parse_and_check(info_options(), Args) of 62 | {ok, {InfoOpts, Rest}} -> 63 | Dump = proplists:get_value(file, InfoOpts), 64 | Handle = edump:open(Dump), 65 | Kind = proplists:get_value(info, InfoOpts), 66 | io:format("Crashdump ~p~n", [Dump]), 67 | edump_analyse:info(Kind, Handle, maps:from_list(Rest)), 68 | ok; 69 | {error, What} -> 70 | getopt:format_error(info_options(), What), 71 | getopt:usage(info_options(), "edump info"), 72 | erlang:halt(1) 73 | end; 74 | main(["try" | Args]) -> 75 | case getopt:parse_and_check(try_options(), Args) of 76 | {ok, {TryOpts, _Rest}} -> 77 | Dump = proplists:get_value(file, TryOpts), 78 | Type = proplists:get_value(type, TryOpts), 79 | io:format("Parsing (segment type: ~p) ~s...~n", [Type, Dump]), 80 | io:format("~p~n", [edump:try_parse(Type, edump:open(Dump))]), 81 | ok; 82 | {error, What} -> 83 | getopt:format_error(try_options(), What), 84 | getopt:usage(info_options(), "edump try"), 85 | erlang:halt(1) 86 | end; 87 | main(_Args) -> 88 | getopt:usage(top_options(), "edump"), 89 | erlang:halt(1). 90 | 91 | top_options() -> 92 | [ 93 | %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg} 94 | {help, $h, "help", undefined, "Print this help."}, 95 | {version, $v, "version", undefined, "Show version information."}, 96 | {task, undefined, undefined, atom, "Task to run: index, graph, info, try"} 97 | ]. 98 | 99 | index_options() -> 100 | [ 101 | %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg} 102 | {help, $h, "help", undefined, "Print this help."}, 103 | {version, $v, "version", undefined, "Show version information."}, 104 | {rebuild, $f, "force-rebuild", {boolean, false}, 105 | "Rebuild the index even if it exists."}, 106 | {checking, $c, "checking", {atom, by_size}, 107 | "Index checking level (none, cheap, full, by_size)"}, 108 | {file, undefined, undefined, string, "Crashdump file to index."} 109 | ]. 110 | 111 | graph_options() -> 112 | [ 113 | %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg} 114 | {help, $h, "help", undefined, "Print this help."}, 115 | {version, $v, "version", undefined, "Show version information."}, 116 | {graph_attrs, $g, "graph-attributes", {string, "rankdir=\"TB\";size=\"8,6\";root=\"erlang\";dpi=\"150\""}, 117 | "Graph attributes in DOT format. Specify attributes as one string 118 | separated by ';', with no terminating ';'."}, 119 | {prune, $p, "prune", {boolean, true}, "Prune the output to produce a tidier graph (shows fewer relations between entities in the dump"}, 120 | {file, undefined, undefined, string, "Crashdump file to graph."}, 121 | {dot_file, $d, undefined, string, "Dotfile for graph output."} 122 | ]. 123 | 124 | info_options() -> 125 | [ 126 | %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg} 127 | {help, $h, "help", undefined, "Print this help."}, 128 | {version, $v, "version", undefined, "Show version information."}, 129 | {file, undefined, undefined, string, "Crashdump file."}, 130 | {info, $i, "info", {atom,basic}, "Kind of info to extract from crashdump. (e.g. basic, processes, ports, ...)"} 131 | ]. 132 | 133 | try_options() -> 134 | [ 135 | %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg} 136 | {help, $h, "help", undefined, "Print this help."}, 137 | {version, $v, "version", undefined, "Show version information."}, 138 | {type, $t, "type", {atom,any}, "Segment type to parse (or any)."}, 139 | {file, undefined, undefined, string, "Crashdump file to try parsing."} 140 | ]. 141 | 142 | 143 | %%==================================================================== 144 | %% Internal functions 145 | %%==================================================================== 146 | -------------------------------------------------------------------------------- /src/edump_seg.erl: -------------------------------------------------------------------------------- 1 | -module('edump_seg'). 2 | 3 | %% API exports 4 | -export([format/2 5 | ,fmt/2 6 | ,data/2 7 | ,print_data/2 8 | ,parse_id/2 9 | ,parse_seg/2 10 | ,id/1 11 | ,ids_of_type/2 12 | ,id_info/2 13 | ,id_info/3 14 | ,id_for/2 15 | ,related/2 16 | ,related_ids/2 17 | ,type/1 18 | ,try_parse_all/2 19 | ,first_parse_failure/2 20 | ,segment_types/1 21 | ]). 22 | 23 | -include("edump_seg.hrl"). 24 | -export_type([segment_id/0]). 25 | 26 | %%==================================================================== 27 | %% API functions 28 | %%==================================================================== 29 | 30 | id(#seg{id = ID}) -> ID. 31 | 32 | id_pid({K, Pid}) 33 | when K =:= proc; 34 | K =:= ets; 35 | K =:= proc_dictionary; 36 | K =:= proc_heap; 37 | K =:= proc_messages; 38 | K =:= proc_stack -> 39 | Pid. 40 | 41 | id_info(Id, Handle) -> 42 | id_info(type(Id), Id, Handle). 43 | 44 | id_info(proc, Id, Handle) -> 45 | {proc, parse_id(Id, Handle), 46 | [id_info(RelId, Handle) 47 | || RelId <- related_ids(Id, Handle)]}; 48 | id_info(proc_dictionary, Id, Handle) -> 49 | {proc_dictionary, 50 | edump_heap:reconstruct_dict(parse_id(Id, Handle), 51 | heap_for(Id, Handle))}; 52 | id_info(proc_messages, Id, Handle) -> 53 | {proc_messages, 54 | edump_heap:reconstruct_msgs(parse_id(Id, Handle), 55 | heap_for(Id, Handle))}; 56 | id_info(proc_stack, Id, Handle) -> 57 | {proc_stack, 58 | edump_stack:reconstruct(parse_id(Id, Handle), 59 | heap_for(Id, Handle))}; 60 | id_info(Type, Id, Handle) -> 61 | {Type, parse_id(Id, Handle), 62 | []}. 63 | 64 | heap_for(Id, Handle) -> 65 | parse_id(heap_id_for(Id), Handle). 66 | 67 | heap_id_for(Id) -> 68 | {proc_heap, id_pid(Id)}. 69 | 70 | id_for(Type, {_, Id}) -> 71 | {Type, Id}. 72 | 73 | related({proc_heap, _} = Id, Handle) -> 74 | Heap = heap_for(Id, Handle), 75 | BinRefs = edump_heap:bin_refs(Heap), 76 | edump_idx:find_ids(BinRefs, Handle); 77 | related({proc, _} = Id, Handle) -> 78 | edump_idx:find_ids(edump_proc:related_ids(Id), Handle). 79 | 80 | related_ids(Id, Handle) -> 81 | [ id(S) || S <- related(Id, Handle) ]. 82 | 83 | type(#seg{} = Seg) -> 84 | type(id(Seg)); 85 | type({Type, _}) -> 86 | Type; 87 | type(Type) -> 88 | Type. 89 | 90 | ids_of_type(Type, Handle) -> 91 | [ id(S) 92 | || S <- edump_idx:segments_of_type(Type, Handle) ]. 93 | 94 | format(Segs, Handle) -> 95 | io:format("~s", [fmt_segs(Segs, Handle)]). 96 | 97 | fmt_segs(Segs, Handle) when is_list(Segs) -> 98 | [ begin 99 | Data = edump_idx:read_seg(Seg, Handle), 100 | fmt(Seg, Data) 101 | end 102 | || Seg <- Segs ]. 103 | 104 | fmt(#seg{id = {Type,Info}}, Data) -> 105 | [io_lib:format("=~p:~p~n", [Type, Info]), Data, "\n"]; 106 | fmt(#seg{id = Type}, Data) -> 107 | [io_lib:format("=~p~n", [Type]), Data, "\n"]. 108 | 109 | 110 | parse_id(Id, Handle) -> 111 | parse_data(type(Id), 112 | edump_idx:read_by_id(Id, Handle)). 113 | 114 | parse_seg(#seg{} = Seg, Handle) -> 115 | parse_data(type(Seg), 116 | edump_idx:read_seg(Seg, Handle)). 117 | 118 | print_data(Id, Handle) -> 119 | io:format("~p~n~s~n", [Id, data(Id, Handle)]). 120 | 121 | data(Id, Handle) -> 122 | edump_idx:read_by_id(Id, Handle). 123 | 124 | try_parse_all(Type, Handle) -> 125 | [ try parse_seg(S, Handle) of 126 | Parse -> 127 | {parse, {id(S), S, Parse}} 128 | catch 129 | Class:Ex -> 130 | {parse_fail, 131 | {{id(S), S, edump_idx:read_seg(S, Handle)}, 132 | {Class, Ex, erlang:get_stacktrace()}}} 133 | end 134 | || S <- edump_idx:segments_of_type(Type, Handle) ]. 135 | 136 | first_parse_failure(Type, Handle) -> 137 | case [ F || {parse_fail, F} <- try_parse_all(Type, Handle)] of 138 | [First | _] -> 139 | First; 140 | [] -> no_failures 141 | end. 142 | 143 | segment_types(Handle) -> 144 | lists:usort([type(Seg) || Seg <- edump_idx:segments(Handle)]). 145 | 146 | %%==================================================================== 147 | %% Internal functions 148 | %%==================================================================== 149 | 150 | parse_data(_, not_present) -> 151 | not_present; 152 | parse_data(erl_crash_dump, Data) -> 153 | edump_dump:parse(Data); 154 | parse_data(memory, Data) -> 155 | edump_parse:atom_int_block(Data); 156 | parse_data(hash_table, Data) -> 157 | edump_parse:atom_int_block(Data); 158 | parse_data(index_table, Data) -> 159 | edump_parse:atom_int_block(Data); 160 | parse_data(allocated_areas, Data) -> 161 | edump_allocator:parse_areas(Data); 162 | parse_data(allocator, Data) -> 163 | edump_allocator:parse(Data); 164 | parse_data(port, Data) -> 165 | edump_port:parse(Data); 166 | parse_data(ets, Data) -> 167 | edump_ets:parse(Data); 168 | parse_data(timer, Data) -> 169 | edump_timer:parse(Data); 170 | parse_data(node, <<>>) -> 171 | []; 172 | parse_data(no_distribution, <<>>) -> 173 | []; 174 | parse_data(visible_node, Data) -> 175 | edump_node:parse(Data); 176 | parse_data(hidden_node, Data) -> 177 | edump_node:parse(Data); 178 | parse_data(not_connected, Data) -> 179 | edump_node:parse(Data); 180 | parse_data(loaded_modules, Data) -> 181 | edump_modules:parse_loaded(Data); 182 | parse_data(mod, Data) -> 183 | edump_modules:parse_mod(Data); 184 | parse_data('fun', Data) -> 185 | edump_fun:parse(Data); 186 | parse_data(proc, Data) -> 187 | edump_proc:parse(Data); 188 | parse_data(proc_dictionary, Data) -> 189 | edump_heap:parse_dict(Data); 190 | parse_data(proc_messages, Data) -> 191 | edump_heap:parse_msgs(Data); 192 | parse_data(proc_heap, Data) -> 193 | edump_heap:parse(Data); 194 | parse_data(proc_stack, Data) -> 195 | edump_stack:parse(Data); 196 | parse_data(binary, Data) -> 197 | [edump_parse:len_hexbin(Data)]; 198 | parse_data(atoms, Data) -> 199 | [{atom, A} || A <- edump_parse:lines(Data)]; 200 | parse_data(scheduler, Data) -> 201 | edump_scheduler:parse(Data); 202 | parse_data('end', <<>>) -> 203 | []. 204 | -------------------------------------------------------------------------------- /src/edump_seg.hrl: -------------------------------------------------------------------------------- 1 | -type segment_id () :: {mod, atom()} | {atom(), any()} | atom(). 2 | 3 | -record(seg, {id :: segment_id(), 4 | seg_start = unknown :: pos_integer() | unknown, 5 | data_start = unknown :: pos_integer() | undefined | unknown, 6 | seg_end = unknown :: pos_integer() | unknown 7 | }). 8 | -------------------------------------------------------------------------------- /src/edump_stack.erl: -------------------------------------------------------------------------------- 1 | -module('edump_stack'). 2 | 3 | %% API exports 4 | -export([parse/1 5 | ,heap_ptrs/1 6 | ,reconstruct/2 7 | ]). 8 | -compile(export_all). 9 | 10 | %%==================================================================== 11 | %% API functions 12 | %%==================================================================== 13 | 14 | parse(Data) -> 15 | Lines = binary:split(Data, <<"\n">>, [global]), 16 | [parse_line(Line) || Line <- Lines, 17 | byte_size(Line) > 0]. 18 | 19 | heap_ptrs(Stack) -> 20 | heap_ptrs(Stack, []). 21 | 22 | heap_ptrs([], Acc) -> 23 | lists:usort(Acc); 24 | heap_ptrs([{{var, _}, {heap, _} = H} | Rest], Acc) -> 25 | heap_ptrs(Rest, [H | Acc]); 26 | heap_ptrs([{{var, _}, V} | Rest], Acc) -> 27 | heap_ptrs(Rest, process_mem:heap_ptrs(V, Acc)); 28 | heap_ptrs([_|Rest], Acc) -> 29 | heap_ptrs(Rest, Acc). 30 | 31 | reconstruct(Stack, Heap) -> 32 | [ r(Item, Heap) || Item <- Stack ]. 33 | 34 | r({{var, _}, {'catch',_,_}} = I, _Heap) -> 35 | I; 36 | r({{var, _} = V, Else}, Heap) -> 37 | {V, edump_heap:reconstruct(Else, Heap)}; 38 | r(Else, _Heap) -> 39 | Else. 40 | 41 | %%==================================================================== 42 | %% Internal functions 43 | %%==================================================================== 44 | 45 | parse_line(Line) -> 46 | case binary:split(Line, <<":">>) of 47 | [<<"y",Num/binary>>, Rest] -> 48 | parse_var(binary_to_integer(Num), Rest); 49 | [<<"0x", Addr:16/binary>>, Rest] -> 50 | parse_addr(binary_to_integer(Addr, 16), Rest) 51 | end. 52 | 53 | parse_var(VarNum, <<"SCatch 0x", Addr2:8/binary, " ", Info/binary>>) -> 54 | {{var, VarNum}, {'catch', Addr2, Info}}; 55 | parse_var(VarNum, Rest) -> 56 | {{var, VarNum}, edump_mem:parse(Rest)}. 57 | 58 | parse_addr(Addr, <<"SReturn addr 0x", Rest/binary>>) -> 59 | [Addr2, Info] = binary:split(Rest, <<" ">>), 60 | {{ptr, Addr}, {return_to, Addr2, Info}}. 61 | -------------------------------------------------------------------------------- /src/edump_timer.erl: -------------------------------------------------------------------------------- 1 | -module('edump_timer'). 2 | 3 | %% API exports 4 | -export([parse/1]). 5 | 6 | %%==================================================================== 7 | %% API functions 8 | %%==================================================================== 9 | 10 | parse(Data) -> 11 | edump_parse:kv_section(fun kv_line/1, Data). 12 | 13 | %%==================================================================== 14 | %% Internal functions 15 | %%==================================================================== 16 | 17 | kv_line({<<"Message">>, Message}) -> 18 | {message, binary_to_atom(Message, latin1)}; 19 | kv_line({<<"Time left">>, Rest}) -> 20 | case binary:split(Rest, <<" ">>) of 21 | [Time, <<"ms">>] -> 22 | {time_left, {binary_to_integer(Time), ms}}; 23 | [Time] -> 24 | {time_left, {binary_to_integer(Time), ms}} 25 | end. 26 | -------------------------------------------------------------------------------- /templates/proc_graph.rdtl: -------------------------------------------------------------------------------- 1 | digraph { 2 | {% for attr in graph_attributes %} {{ attr|safe }}; 3 | {% endfor %} 4 | 5 | {% for proc in procs %} "{{ proc.id|safe }}" [{{ proc.dot_attrs|join:","|safe }}]; 6 | {% endfor %} 7 | 8 | {% for rel in relations %} "{{ rel.from|safe }}" -> "{{ rel.to|safe }}" [{{ rel.dot_attrs|join:","|safe }}]; 9 | {% endfor %} 10 | } -------------------------------------------------------------------------------- /test/dumpfiles/erl_dist_forced_crash.dump: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5a52014b509aede3de7223540eefaf25d88c1eeb24169b8aeeaeeb8ce3a05acf 3 | size 452087 4 | -------------------------------------------------------------------------------- /test/dumpfiles/erl_dist_forced_crash.dump.md: -------------------------------------------------------------------------------- 1 | Example erl_crash.dump file 2 | 3 | Produced via `erl -sname foo -eval 'erlang:halt("forced dump").'` using R20. 4 | 5 | Contains some interesting remote pids that led to parsing improvements. -------------------------------------------------------------------------------- /test/dumpfiles/eshell_forced_crash.dump: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7906ac82c8519cea2e0cb6612f06d7dc18766a4a6192727bb59393add879223f 3 | size 441708 4 | -------------------------------------------------------------------------------- /test/dumpfiles/eshell_forced_crash.dump.md: -------------------------------------------------------------------------------- 1 | Example erl_crash.dump file. 2 | 3 | Produced for testing with R15 back in 2012: 4 | 5 | $ erl 6 | > erlang:halt("A test crash"). 7 | 8 | Was the initial dump file use for `edump` development. -------------------------------------------------------------------------------- /test/dumpfiles/eshell_forced_crash.dump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archaelus/edump/d031f00f4abbc161b81c862dfc865ad886fac5f8/test/dumpfiles/eshell_forced_crash.dump.png -------------------------------------------------------------------------------- /test/dumpfiles/gh_issue_3.dump: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:59cbb72496edb2da584289b9fb5bebde36596d452ccaf0f30fad0e2647e32152 3 | size 84396 4 | -------------------------------------------------------------------------------- /test/dumpfiles/gh_issue_3.dump.md: -------------------------------------------------------------------------------- 1 | Test case dump file for github issue archaelus/edump#3. --------------------------------------------------------------------------------