1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2003-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(crashdump_viewer).
21
22%%
23%% This module is the main module in the crashdump viewer. It implements
24%% the server backend for the crashdump viewer tool.
25%%
26%% Tables
27%% ------
28%% cdv_dump_index_table: This table holds all tags read from the
29%% crashdump, except the 'binary' tag.  Each tag indicates where the
30%% information about a specific item starts.  The table entry for a
31%% tag includes the start position for this item-information. In a
32%% crash dump file, all tags start with a "=" at the beginning of a
33%% line.
34%%
35%% cdv_binary_index_table: This table holds all 'binary' tags. The hex
36%% address for each binary is converted to its integer value before
37%% storing Address -> Start Position in this table. The hex value of
38%% the address is never used for lookup.
39%%
40%% cdv_reg_proc_table: This table holds mappings between pid and
41%% registered name. This is used for timers and monitors.
42%%
43%% cdv_heap_file_chars: For each 'proc_heap' and 'literals' tag, this
44%% table contains the number of characters to read from the crash dump
45%% file. This is used for giving an indication in percent of the
46%% progress when parsing this data.
47%%
48%%
49%% Process state
50%% -------------
51%% file: The name of the crashdump currently viewed.
52%% dump_vsn: The version number of the crashdump
53%% wordsize: 4 | 8, the number of bytes in a word.
54%%
55
56%% User API
57-export([start/0,start/1,stop/0,script_start/0,script_start/1]).
58
59%% GUI API
60-export([start_link/0]).
61-export([read_file/1,
62	 general_info/0,
63	 processes/0,
64	 proc_details/1,
65	 port/1,
66	 ports/0,
67	 ets_tables/1,
68	 internal_ets_tables/0,
69	 timers/1,
70	 funs/0,
71	 atoms/0,
72	 dist_info/0,
73	 node_info/1,
74	 loaded_modules/0,
75	 loaded_mod_details/1,
76	 memory/0,
77         persistent_terms/0,
78	 allocated_areas/0,
79	 allocator_info/0,
80	 hash_tables/0,
81	 index_tables/0,
82	 schedulers/0,
83	 expand_binary/1]).
84
85%% Library function
86-export([to_proplist/2, to_value_list/1]).
87
88%% gen_server callbacks
89-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
90	 terminate/2, code_change/3]).
91
92%% Test support
93-export([get_dump_versions/0]).
94
95%% Debug support
96-export([debug/1,stop_debug/0]).
97
98-include("crashdump_viewer.hrl").
99-include_lib("kernel/include/file.hrl").
100-include_lib("stdlib/include/ms_transform.hrl").
101
102-define(SERVER, crashdump_viewer_server).
103-define(call_timeout,3600000).
104-define(chunk_size,1000). % number of bytes read from crashdump at a time
105-define(max_line_size,100). % max number of bytes (i.e. characters) the
106			    % line_head/1 function can return
107-define(not_available,"N/A").
108-define(binary_size_progress_limit,10000).
109-define(max_dump_version,[0,5]).
110
111%% The value of the next define must be divisible by 4.
112-define(base64_chunk_size, (4*256)).
113
114%% All possible tags - use macros in order to avoid misspelling in the code
115-define(abort,abort).
116-define(allocated_areas,allocated_areas).
117-define(allocator,allocator).
118-define(atoms,atoms).
119-define(binary,binary).
120-define(dirty_cpu_scheduler,dirty_cpu_scheduler).
121-define(dirty_cpu_run_queue,dirty_cpu_run_queue).
122-define(dirty_io_scheduler,dirty_io_scheduler).
123-define(dirty_io_run_queue,dirty_io_run_queue).
124-define(ende,ende).
125-define(erl_crash_dump,erl_crash_dump).
126-define(ets,ets).
127-define(fu,fu).
128-define(hash_table,hash_table).
129-define(hidden_node,hidden_node).
130-define(index_table,index_table).
131-define(instr_data,instr_data).
132-define(internal_ets,internal_ets).
133-define(literals,literals).
134-define(loaded_modules,loaded_modules).
135-define(memory,memory).
136-define(memory_map,memory_map).
137-define(memory_status,memory_status).
138-define(mod,mod).
139-define(no_distribution,no_distribution).
140-define(node,node).
141-define(not_connected,not_connected).
142-define(old_instr_data,old_instr_data).
143-define(persistent_terms,persistent_terms).
144-define(port,port).
145-define(proc,proc).
146-define(proc_dictionary,proc_dictionary).
147-define(proc_heap,proc_heap).
148-define(proc_messages,proc_messages).
149-define(proc_stack,proc_stack).
150-define(scheduler,scheduler).
151-define(timer,timer).
152-define(visible_node,visible_node).
153
154
155-record(state,{file,dump_vsn,wordsize=4,num_atoms="unknown"}).
156-record(dec_opts, {bin_addr_adj=0,base64=true}).
157
158%%%-----------------------------------------------------------------
159%%% Debugging
160%% Start tracing with
161%% debug(Functions).
162%% Functions = local | global | FunctionList
163%% FunctionList = [Function]
164%% Function = {FunctionName,Arity} | FunctionName
165debug(F) ->
166    ttb:tracer(all,[{file,"cdv"}]), % tracing all nodes
167    ttb:p(all,[call,timestamp]),
168    MS = [{'_',[],[{return_trace},{message,{caller}}]}],
169    tp(F,MS),
170    ttb:ctp(?MODULE,stop_debug), % don't want tracing of the stop_debug func
171    ok.
172tp([{M,F,A}|T],MS) -> % mod:func/arity
173    ttb:tpl(M,F,A,MS),
174    tp(T,MS);
175tp([{M,F}|T],MS) -> % mod:func
176    ttb:tpl(M,F,MS),
177    tp(T,MS);
178tp([M|T],MS) -> % mod
179    ttb:tp(M,MS), % only exported
180    tp(T,MS);
181tp([],_MS) ->
182    ok.
183stop_debug() ->
184    ttb:stop([format]).
185
186%%%-----------------------------------------------------------------
187%%% User API
188start() ->
189    start(undefined).
190start(File) ->
191    cdv_wx:start(File).
192
193stop() ->
194    case whereis(?SERVER) of
195	undefined ->
196	    ok;
197	Pid ->
198	    Ref = erlang:monitor(process,Pid),
199	    cast(stop),
200	    receive {'DOWN', Ref, process, Pid, _} -> ok end
201    end.
202
203%%%-----------------------------------------------------------------
204%%% Start crashdump_viewer via the cdv script located in
205%%% $OBSERVER_PRIV_DIR/bin
206script_start() ->
207    do_script_start(fun() -> start() end),
208    erlang:halt().
209script_start([FileAtom]) ->
210    File = atom_to_list(FileAtom),
211    case filelib:is_regular(File) of
212	true ->
213	    do_script_start(fun() -> start(File) end);
214	false ->
215	    io:format("cdv error: the given file does not exist\n"),
216	    usage()
217    end,
218    erlang:halt();
219script_start(_) ->
220    usage(),
221    erlang:halt().
222
223do_script_start(StartFun) ->
224    process_flag(trap_exit,true),
225    case StartFun() of
226	ok ->
227	    case whereis(cdv_wx) of
228		Pid when is_pid(Pid) ->
229		    link(Pid),
230		    receive
231			{'EXIT', Pid, normal} ->
232			    ok;
233			{'EXIT', Pid, Reason} ->
234			    io:format("\ncdv crash: ~tp\n",[Reason])
235		    end;
236		_ ->
237		    %io:format("\ncdv crash: ~p\n",[unknown_reason])
238                    ok
239	    end;
240	Error ->
241	    io:format("\ncdv start failed: ~tp\n",[Error])
242    end.
243
244usage() ->
245    io:format(
246      "usage: cdv [file]\n"
247      "\tThe \'file\' must be an existing erlang crash dump.\n"
248      "\tIf omitted a file dialog will be opened.\n",
249      []).
250
251%%====================================================================
252%% External functions
253%%====================================================================
254%%%--------------------------------------------------------------------
255%%% Start the server - called by cdv_wx
256start_link() ->
257    case whereis(?SERVER) of
258	undefined ->
259	    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []);
260	Pid ->
261	    {ok,Pid}
262    end.
263
264%%%-----------------------------------------------------------------
265%%% Called by cdv_wx
266read_file(File) ->
267    cast({read_file,File}).
268
269%%%-----------------------------------------------------------------
270%%% The following functions are called when the different tabs are
271%%% created
272general_info() ->
273    call(general_info).
274processes() ->
275    call(procs_summary).
276ports() ->
277    call(ports).
278ets_tables(Owner) ->
279    call({ets_tables,Owner}).
280internal_ets_tables() ->
281    call(internal_ets_tables).
282timers(Owner) ->
283    call({timers,Owner}).
284funs() ->
285    call(funs).
286atoms() ->
287    call(atoms).
288dist_info() ->
289    call(dist_info).
290node_info(Channel) ->
291    call({node_info,Channel}).
292loaded_modules() ->
293    call(loaded_mods).
294loaded_mod_details(Mod) ->
295    call({loaded_mod_details,Mod}).
296memory() ->
297    call(memory).
298persistent_terms() ->
299    call(persistent_terms).
300allocated_areas() ->
301    call(allocated_areas).
302allocator_info() ->
303    call(allocator_info).
304hash_tables() ->
305    call(hash_tables).
306index_tables() ->
307    call(index_tables).
308schedulers() ->
309    call(schedulers).
310
311%%%-----------------------------------------------------------------
312%%% Called when a link to a process (Pid) is clicked.
313proc_details(Pid) ->
314    call({proc_details,Pid}).
315
316%%%-----------------------------------------------------------------
317%%% Called when a link to a port is clicked.
318port(Id) ->
319    call({port,Id}).
320
321%%%-----------------------------------------------------------------
322%%% Called when "<< xxx bytes>>" link is clicket to open a new window
323%%% displaying the whole binary.
324expand_binary(Pos) ->
325    call({expand_binary,Pos}).
326
327%%%-----------------------------------------------------------------
328%%% For testing only - called from crashdump_viewer_SUITE
329get_dump_versions() ->
330    call(get_dump_versions).
331
332%%====================================================================
333%% Server functions
334%%====================================================================
335
336%%--------------------------------------------------------------------
337%% Function: init/1
338%% Description: Initiates the server
339%% Returns: {ok, State}          |
340%%          {ok, State, Timeout} |
341%%          ignore               |
342%%          {stop, Reason}
343%%--------------------------------------------------------------------
344init([]) ->
345    ets:new(cdv_dump_index_table,[ordered_set,named_table,public]),
346    ets:new(cdv_reg_proc_table,[ordered_set,named_table,public]),
347    ets:new(cdv_binary_index_table,[ordered_set,named_table,public]),
348    ets:new(cdv_heap_file_chars,[ordered_set,named_table,public]),
349    {ok, #state{}}.
350
351%%--------------------------------------------------------------------
352%% Function: handle_call/3
353%% Description: Handling call messages
354%% Returns: {reply, Reply, State}          |
355%%          {reply, Reply, State, Timeout} |
356%%          {noreply, State}               |
357%%          {noreply, State, Timeout}      |
358%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
359%%          {stop, Reason, State}            (terminate/2 is called)
360%%--------------------------------------------------------------------
361handle_call(general_info,_From,State=#state{file=File}) ->
362    GenInfo = general_info(File),
363    NumAtoms = GenInfo#general_info.num_atoms,
364    WS = parse_vsn_str(GenInfo#general_info.system_vsn,4),
365    TW = case get(truncated) of
366	     true ->
367                 case get(truncated_reason) of
368                     undefined ->
369                         ["WARNING: The crash dump is truncated. "
370                          "Some information might be missing."];
371                     Reason ->
372                         ["WARNING: The crash dump is truncated "
373                          "("++Reason++"). "
374                          "Some information might be missing."]
375                 end;
376	     false -> []
377	 end,
378    ets:insert(cdv_reg_proc_table,
379	       {cdv_dump_node_name,GenInfo#general_info.node_name}),
380    {reply,{ok,GenInfo,TW},State#state{wordsize=WS, num_atoms=NumAtoms}};
381handle_call({expand_binary,{Offset,Size,Pos}},_From,
382            #state{file=File,dump_vsn=DumpVsn}=State) ->
383    Fd = open(File),
384    pos_bof(Fd,Pos),
385    DecodeOpts = get_decode_opts(DumpVsn),
386    {Bin,_Line} = get_binary(Offset,Size,bytes(Fd),DecodeOpts),
387    close(Fd),
388    {reply,{ok,Bin},State};
389handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) ->
390    TW = truncated_warning([?proc]),
391    Procs = procs_summary(File,WS),
392    {reply,{ok,Procs,TW},State};
393handle_call({proc_details,Pid},_From,
394	    State=#state{file=File,wordsize=WS,dump_vsn=DumpVsn})->
395    Reply =
396	case get_proc_details(File,Pid,WS,DumpVsn) of
397	    {ok,Proc,TW} ->
398		{ok,Proc,TW};
399	    Other ->
400		{error,Other}
401	end,
402    {reply, Reply, State};
403handle_call({port,Id},_From,State=#state{file=File}) ->
404    Reply =
405	case get_port(File,Id) of
406	    {ok,PortInfo} ->
407		TW = truncated_warning([{?port,Id}]),
408		{ok,PortInfo,TW};
409	    Other ->
410		{error,Other}
411	end,
412    {reply,Reply,State};
413handle_call(ports,_From,State=#state{file=File}) ->
414    TW = truncated_warning([?port]),
415    Ports = get_ports(File),
416    {reply,{ok,Ports,TW},State};
417handle_call({ets_tables,Pid0},_From,State=#state{file=File,wordsize=WS}) ->
418    Pid =
419	case Pid0 of
420	    all -> '$2';
421	    _ -> Pid0
422	end,
423    TW = truncated_warning([?ets]),
424    Ets = get_ets_tables(File,Pid,WS),
425    {reply,{ok,Ets,TW},State};
426handle_call(internal_ets_tables,_From,State=#state{file=File,wordsize=WS}) ->
427    InternalEts = get_internal_ets_tables(File,WS),
428    TW = truncated_warning([?internal_ets]),
429    {reply,{ok,InternalEts,TW},State};
430handle_call({timers,Pid0},_From,State=#state{file=File}) ->
431    Pid =
432	case Pid0 of
433	    all -> '$2';
434	    _ -> Pid0
435	end,
436    TW = truncated_warning([?timer]),
437    Timers = get_timers(File,Pid),
438    {reply,{ok,Timers,TW},State};
439handle_call(dist_info,_From,State=#state{file=File}) ->
440    TW = truncated_warning([?visible_node,?hidden_node,?not_connected]),
441    Nods=nods(File),
442    {reply,{ok,Nods,TW},State};
443handle_call({node_info,Channel},_From,State=#state{file=File}) ->
444    Reply =
445	case get_node(File,Channel) of
446	    {ok,Nod} ->
447		TW = truncated_warning([?visible_node,
448					?hidden_node,
449					?not_connected]),
450		{ok,Nod,TW};
451	    {error,Other} ->
452		{error,Other}
453	end,
454    {reply,Reply,State};
455handle_call(loaded_mods,_From,State=#state{file=File}) ->
456    TW = truncated_warning([?mod]),
457    {_CC,_OC,Mods} = loaded_mods(File),
458    {reply,{ok,Mods,TW},State};
459handle_call({loaded_mod_details,Mod},_From,
460            #state{dump_vsn=DumpVsn,file=File}=State) ->
461    TW = truncated_warning([{?mod,Mod}]),
462    DecodeOpts = get_decode_opts(DumpVsn),
463    ModInfo = get_loaded_mod_details(File,Mod,DecodeOpts),
464    {reply,{ok,ModInfo,TW},State};
465handle_call(funs,_From,State=#state{file=File}) ->
466    TW = truncated_warning([?fu]),
467    Funs = funs(File),
468    {reply,{ok,Funs,TW},State};
469handle_call(atoms,_From,State=#state{file=File,num_atoms=NumAtoms0}) ->
470    TW = truncated_warning([?atoms]),
471    NumAtoms = try list_to_integer(NumAtoms0) catch error:badarg -> -1 end,
472    Atoms = atoms(File,NumAtoms),
473    {reply,{ok,Atoms,TW},State};
474handle_call(memory,_From,State=#state{file=File}) ->
475    Memory=memory(File),
476    TW = truncated_warning([?memory]),
477    {reply,{ok,Memory,TW},State};
478handle_call(persistent_terms,_From,State=#state{file=File,dump_vsn=DumpVsn}) ->
479    TW = truncated_warning([?persistent_terms,?literals]),
480    DecodeOpts = get_decode_opts(DumpVsn),
481    Terms = persistent_terms(File, DecodeOpts),
482    {reply,{ok,Terms,TW},State};
483handle_call(allocated_areas,_From,State=#state{file=File}) ->
484    AllocatedAreas=allocated_areas(File),
485    TW = truncated_warning([?allocated_areas]),
486    {reply,{ok,AllocatedAreas,TW},State};
487handle_call(allocator_info,_From,State=#state{file=File}) ->
488    SlAlloc=allocator_info(File),
489    TW = truncated_warning([?allocator]),
490    {reply,{ok,SlAlloc,TW},State};
491handle_call(hash_tables,_From,State=#state{file=File}) ->
492    HashTables=hash_tables(File),
493    TW = truncated_warning([?hash_table,?index_table]),
494    {reply,{ok,HashTables,TW},State};
495handle_call(index_tables,_From,State=#state{file=File}) ->
496    IndexTables=index_tables(File),
497    TW = truncated_warning([?hash_table,?index_table]),
498    {reply,{ok,IndexTables,TW},State};
499handle_call(schedulers,_From,State=#state{file=File}) ->
500    Schedulers=schedulers(File),
501    TW = truncated_warning([?scheduler]),
502    {reply,{ok,Schedulers,TW},State};
503handle_call(get_dump_versions,_From,State=#state{dump_vsn=DumpVsn}) ->
504    {reply,{ok,{?max_dump_version,DumpVsn}},State}.
505
506
507%%--------------------------------------------------------------------
508%% Function: handle_cast/2
509%% Description: Handling cast messages
510%% Returns: {noreply, State}          |
511%%          {noreply, State, Timeout} |
512%%          {stop, Reason, State}            (terminate/2 is called)
513%%--------------------------------------------------------------------
514handle_cast({read_file,File}, _State) ->
515    case do_read_file(File) of
516	{ok,DumpVsn} ->
517	    observer_lib:report_progress({ok,done}),
518	    {noreply, #state{file=File,dump_vsn=DumpVsn}};
519	Error ->
520	    end_progress(Error),
521	    {noreply, #state{}}
522    end;
523handle_cast(stop,State) ->
524    {stop,normal,State}.
525
526
527%%--------------------------------------------------------------------
528%% Function: handle_info/2
529%% Description: Handling all non call/cast messages
530%% Returns: {noreply, State}          |
531%%          {noreply, State, Timeout} |
532%%          {stop, Reason, State}            (terminate/2 is called)
533%%--------------------------------------------------------------------
534handle_info(_Info, State) ->
535    {noreply, State}.
536
537%%--------------------------------------------------------------------
538%% Function: terminate/2
539%% Description: Shutdown the server
540%% Returns: any (ignored by gen_server)
541%%--------------------------------------------------------------------
542terminate(_Reason, _State) ->
543    ok.
544
545%%--------------------------------------------------------------------
546%% Func: code_change/3
547%% Purpose: Convert process state when code is changed
548%% Returns: {ok, NewState}
549%%--------------------------------------------------------------------
550code_change(_OldVsn, State, _Extra) ->
551    {ok, State}.
552
553%%--------------------------------------------------------------------
554%%% Internal functions
555%%--------------------------------------------------------------------
556call(Request) ->
557     gen_server:call(?SERVER,Request,?call_timeout).
558
559cast(Msg) ->
560    gen_server:cast(?SERVER,Msg).
561
562unexpected(_Fd,{eof,_LastLine},_Where) ->
563    ok; % truncated file
564unexpected(Fd,{part,What},Where) ->
565    skip_rest_of_line(Fd),
566    io:format("WARNING: Found unexpected line in ~ts:~n~ts ...~n",[Where,What]);
567unexpected(_Fd,What,Where) ->
568    io:format("WARNING: Found unexpected line in ~ts:~n~ts~n",[Where,What]).
569
570truncated_warning([]) ->
571    [];
572truncated_warning([Tag|Tags]) ->
573    case truncated_here(Tag) of
574	true -> truncated_warning();
575	false -> truncated_warning(Tags)
576    end.
577truncated_warning() ->
578    case get(truncated_reason) of
579        undefined ->
580            ["WARNING: The crash dump is truncated here. "
581             "Some information might be missing."];
582        Reason ->
583            ["WARNING: The crash dump is truncated here "
584             "("++Reason++"). "
585             "Some information might be missing."]
586    end.
587
588truncated_here(Tag) ->
589    case get(truncated) of
590	true ->
591	    case get(last_tag) of
592		{Tag,_Pos} -> % Tag == {TagType,Id}
593		    true;
594		{{Tag,_Id},_Pos} ->
595		    true;
596		_LastTag ->
597		    truncated_earlier(Tag)
598	    end;
599	false ->
600	    false
601    end.
602
603
604%% Check if the dump was truncated with the same tag, but earlier id.
605%% Eg if this is {?proc,"<0.30.0>"}, we should warn if the dump was
606%% truncated in {?proc,"<0.29.0>"} or earlier
607truncated_earlier({?proc,Pid}) ->
608    compare_pid(Pid,get(truncated_proc));
609truncated_earlier(_Tag) ->
610    false.
611
612compare_pid("<"++Id,"<"++OtherId) ->
613    Id>=OtherId;
614compare_pid(_,_) ->
615    false.
616
617open(File) ->
618    {ok,Fd} = file:open(File,[read,read_ahead,raw,binary]),
619    Fd.
620close(Fd) ->
621    erase(chunk),
622    file:close(Fd).
623
624%% Set position relative to beginning of file
625%% If position is within the already read Chunk, then adjust 'chunk'
626%% and 'pos' in process dictionary. Else set position in file.
627pos_bof(Fd,Pos) ->
628    case get(pos) of
629	undefined ->
630	    hard_pos_bof(Fd,Pos);
631	OldPos when Pos>=OldPos ->
632	    case get(chunk) of
633		undefined ->
634		    hard_pos_bof(Fd,Pos);
635		Chunk ->
636		    ChunkSize = byte_size(Chunk),
637		    ChunkEnd = OldPos+ChunkSize,
638		    if Pos=<ChunkEnd ->
639			    Diff = Pos-OldPos,
640			    put(pos,Pos),
641			    put(chunk,binary:part(Chunk,Diff,ChunkEnd-Pos));
642		       true ->
643			    hard_pos_bof(Fd,Pos)
644		    end
645	    end;
646	_ ->
647	    hard_pos_bof(Fd,Pos)
648    end.
649
650hard_pos_bof(Fd,Pos) ->
651    reset_chunk(),
652    file:position(Fd,{bof,Pos}).
653
654
655get_chunk(Fd) ->
656    case erase(chunk) of
657	undefined ->
658	    case read(Fd) of
659		eof ->
660		    put_pos(Fd),
661		    eof;
662		Other ->
663		    Other
664	    end;
665	Bin ->
666	    {ok,Bin}
667    end.
668
669%% Read and report progress
670progress_read(Fd) ->
671    {R,Bytes} =
672	case read(Fd) of
673	    {ok,Bin} ->
674		{{ok,Bin},byte_size(Bin)};
675	    Other ->
676		{Other,0}
677	end,
678    update_progress(Bytes),
679    R.
680
681read(Fd) ->
682    file:read(Fd,?chunk_size).
683
684put_chunk(Fd,Bin) ->
685    {ok,Pos0} = file:position(Fd,cur),
686    Pos = Pos0 - byte_size(Bin),
687    put(chunk,Bin),
688    put(pos,Pos).
689
690put_pos(Fd) ->
691    {ok,Pos} = file:position(Fd,cur),
692    put(pos,Pos).
693
694reset_chunk() ->
695    erase(chunk),
696    erase(pos).
697
698line_head(Fd) ->
699    case get_chunk(Fd) of
700	{ok,Bin} -> line_head(Fd,Bin,[],0);
701	eof -> {eof,[]}
702    end.
703line_head(Fd,Bin,Acc,?max_line_size) ->
704    put_chunk(Fd,Bin),
705    {part,lists:reverse(Acc)};
706line_head(Fd,<<$\n:8,Bin/binary>>,Acc,_N) ->
707    put_chunk(Fd,Bin),
708    lists:reverse(Acc);
709line_head(Fd,<<$::8,$\r:8,$\n:8,Bin/binary>>,Acc,_N) ->
710    put_chunk(Fd,Bin),
711    lists:reverse(Acc);
712line_head(Fd,<<$::8,$\r:8>>,Acc,N) ->
713    case get_chunk(Fd) of
714	{ok,Bin} -> line_head(Fd,<<$:,Bin/binary>>,Acc,N);
715	eof -> {eof,lists:reverse(Acc)}
716    end;
717line_head(Fd,<<$::8>>,Acc,N) ->
718    case get_chunk(Fd) of
719	{ok,Bin} -> line_head(Fd,<<$:,Bin/binary>>,Acc,N);
720	eof -> {eof,lists:reverse(Acc)}
721    end;
722line_head(Fd,<<$::8,Space:8,Bin/binary>>,Acc,_N) when Space=:=$ ;Space=:=$\n ->
723    put_chunk(Fd,Bin),
724    lists:reverse(Acc);
725line_head(Fd,<<$::8,Bin/binary>>,Acc,_N) ->
726    put_chunk(Fd,Bin),
727    lists:reverse(Acc);
728line_head(Fd,<<$\r:8,Bin/binary>>,Acc,N) ->
729    line_head(Fd,Bin,Acc,N+1);
730line_head(Fd,<<Char:8,Bin/binary>>,Acc,N) ->
731    line_head(Fd,Bin,[Char|Acc],N+1);
732line_head(Fd,<<>>,Acc,N) ->
733    case get_chunk(Fd) of
734	{ok,Bin} -> line_head(Fd,Bin,Acc,N);
735	eof -> {eof,lists:reverse(Acc)}
736    end.
737
738skip_rest_of_line(Fd) ->
739    case get_chunk(Fd) of
740	{ok,Bin} -> skip(Fd,Bin);
741	eof -> ok
742    end.
743skip(Fd,<<$\n:8,Bin/binary>>) ->
744    put_chunk(Fd,Bin),
745    ok;
746skip(Fd,<<_Char:8,Bin/binary>>) ->
747    skip(Fd,Bin);
748skip(Fd,<<>>) ->
749    case get_chunk(Fd) of
750	{ok,Bin} -> skip(Fd,Bin);
751	eof -> ok
752    end.
753
754
755string(Fd) ->
756    string(Fd, "-1").
757string(Fd,NoExist) ->
758    case bytes(Fd,noexist) of
759        noexist -> NoExist;
760        Val -> byte_list_to_string(Val)
761    end.
762
763byte_list_to_string(ByteList) ->
764    Bin = list_to_binary(ByteList),
765    case unicode:characters_to_list(Bin) of
766        Str when is_list(Str) -> Str;
767        _ -> ByteList
768    end.
769
770bytes(Fd) ->
771    bytes(Fd, "-1").
772bytes(Fd, NoExist) ->
773    case get_rest_of_line(Fd) of
774	{eof,[]} -> NoExist;
775	[] -> NoExist;
776	{eof,Val} -> Val;
777        "=abort:"++_ -> NoExist;
778	Val -> Val
779    end.
780
781get_rest_of_line(Fd) ->
782    case get_chunk(Fd) of
783	{ok,Bin} -> get_rest_of_line_1(Fd, Bin, []);
784	eof -> {eof,[]}
785    end.
786
787get_rest_of_line_1(Fd, <<$\n:8,Bin/binary>>, Acc) ->
788    put_chunk(Fd, Bin),
789    lists:reverse(Acc);
790get_rest_of_line_1(Fd, <<$\r:8,Rest/binary>>, Acc) ->
791    get_rest_of_line_1(Fd, Rest, Acc);
792get_rest_of_line_1(Fd, <<Char:8,Rest/binary>>, Acc) ->
793    get_rest_of_line_1(Fd, Rest, [Char|Acc]);
794get_rest_of_line_1(Fd, <<>>, Acc) ->
795    case get_chunk(Fd) of
796	{ok,Bin} -> get_rest_of_line_1(Fd, Bin, Acc);
797	eof -> {eof,lists:reverse(Acc)}
798    end.
799
800split(Str) ->
801    split($ ,Str,[]).
802split(Char,Str) ->
803    split(Char,Str,[]).
804split(Char,[Char|Str],Acc) -> % match Char
805    {lists:reverse(Acc),Str};
806split(_Char,[$\r,$\n|Str],Acc) -> % new line
807    {lists:reverse(Acc),Str};
808split(_Char,[$\n|Str],Acc) -> % new line
809    {lists:reverse(Acc),Str};
810split(Char,[H|T],Acc) ->
811    split(Char,T,[H|Acc]);
812split(_Char,[],Acc) ->
813    {lists:reverse(Acc),[]}.
814
815%%%-----------------------------------------------------------------
816%%%
817parse_vsn_str([],WS) ->
818    WS;
819parse_vsn_str(Str,WS) ->
820    case Str of
821	"[64-bit]" ++ _Rest ->
822	    8;
823	[_Char|Rest] ->
824	    parse_vsn_str(Rest,WS)
825    end.
826
827
828%%%-----------------------------------------------------------------
829%%% Traverse crash dump and insert index in table for each heading.
830%%% Progress is reported during the time.
831do_read_file(File) ->
832    erase(?literals),                           %Clear literal cache.
833    put(truncated,false),                       %Not truncated (yet).
834    erase(truncated_reason),                    %Not truncated (yet).
835    case file:read_file_info(File) of
836	{ok,#file_info{type=regular,
837		       access=FileA,
838		       size=Size}} when FileA=:=read; FileA=:=read_write ->
839	    Fd = open(File),
840	    init_progress("Reading file",Size),
841	    case progress_read(Fd) of
842		{ok,<<$=:8,TagAndRest/binary>>} ->
843		    {Tag,Id,Rest,N1} = tag(Fd,TagAndRest,1),
844		    case Tag of
845			?erl_crash_dump ->
846                            case check_dump_version(Id) of
847                                {ok,DumpVsn} ->
848                                    reset_tables(),
849                                    insert_index(Tag,Id,Pos=N1+1),
850                                    put_last_tag(Tag,"",Pos),
851                                    DecodeOpts = get_decode_opts(DumpVsn),
852                                    indexify(Fd,DecodeOpts,Rest,N1),
853                                    end_progress(),
854                                    check_if_truncated(),
855                                    close(Fd),
856                                    {ok,DumpVsn};
857                                Error ->
858                                    close(Fd),
859                                    Error
860                            end;
861			_Other ->
862			    R = io_lib:format(
863				  "~ts is not an Erlang crash dump~n",
864				  [File]),
865			    close(Fd),
866			    {error,R}
867		    end;
868		{ok,<<"<Erlang crash dump>",_Rest/binary>>} ->
869		    %% old version - no longer supported
870		    R = io_lib:format(
871			  "The crashdump ~ts is in the pre-R10B format, "
872			  "which is no longer supported.~n",
873			     [File]),
874		    close(Fd),
875		    {error,R};
876		_Other ->
877		    R = io_lib:format(
878			  "~ts is not an Erlang crash dump~n",
879			  [File]),
880		    close(Fd),
881		    {error,R}
882	    end;
883	_other ->
884	    R = io_lib:format("~ts is not an Erlang crash dump~n",[File]),
885	    {error,R}
886    end.
887
888check_dump_version(Vsn) ->
889    DumpVsn = [list_to_integer(L) || L<-string:lexemes(Vsn,".")],
890    if DumpVsn > ?max_dump_version ->
891            Info =
892                "This Crashdump Viewer is too old for the given "
893                "Erlang crash dump. Please use a newer version of "
894                "Crashdump Viewer.",
895            {error,Info};
896       true ->
897            {ok,DumpVsn}
898    end.
899
900indexify(Fd,DecodeOpts,Bin,N) ->
901    case binary:match(Bin,<<"\n=">>) of
902	{Start,Len} ->
903	    Pos = Start+Len,
904	    <<_:Pos/binary,TagAndRest/binary>> = Bin,
905	    {Tag,Id,Rest,N1} = tag(Fd,TagAndRest,N+Pos),
906            NewPos = N1+1, % +1 to get past newline
907            case Tag of
908                ?binary ->
909                    %% Binaries are stored in a separate table in
910                    %% order to minimize lookup time. Key is the
911                    %% translated address.
912                    {HexAddr,_} = get_hex(Id),
913                    Addr = HexAddr bor DecodeOpts#dec_opts.bin_addr_adj,
914                    insert_binary_index(Addr,NewPos);
915                _ ->
916                    insert_index(Tag,Id,NewPos)
917            end,
918	    case put_last_tag(Tag,Id,NewPos) of
919                {{?proc_heap,LastId},LastPos} ->
920                    ets:insert(cdv_heap_file_chars,{LastId,N+Start+1-LastPos});
921                {{?literals,[]},LastPos} ->
922                    ets:insert(cdv_heap_file_chars,{literals,N+Start+1-LastPos});
923                _ -> ok
924            end,
925	    indexify(Fd,DecodeOpts,Rest,N1);
926	nomatch ->
927	    case progress_read(Fd) of
928		{ok,Chunk0} when is_binary(Chunk0) ->
929		    {Chunk,N1} =
930			case binary:last(Bin) of
931			    $\n ->
932				{<<$\n,Chunk0/binary>>,N+byte_size(Bin)-1};
933			    _ ->
934				{Chunk0,N+byte_size(Bin)}
935			end,
936		    indexify(Fd,DecodeOpts,Chunk,N1);
937		eof ->
938		    eof
939	    end
940    end.
941
942tag(Fd,Bin,N) ->
943    tag(Fd,Bin,N,[],[],tag).
944tag(_Fd,<<$\n:8,_/binary>>=Rest,N,Gat,Di,_Now) ->
945    {tag_to_atom(lists:reverse(Gat)),lists:reverse(Di),Rest,N};
946tag(Fd,<<$\r:8,Rest/binary>>,N,Gat,Di,Now) ->
947    tag(Fd,Rest,N+1,Gat,Di,Now);
948tag(Fd,<<$::8,IdAndRest/binary>>,N,Gat,Di,tag) ->
949    tag(Fd,IdAndRest,N+1,Gat,Di,id);
950tag(Fd,<<Char:8,Rest/binary>>,N,Gat,Di,tag) ->
951    tag(Fd,Rest,N+1,[Char|Gat],Di,tag);
952tag(Fd,<<Char:8,Rest/binary>>,N,Gat,Di,id) ->
953    tag(Fd,Rest,N+1,Gat,[Char|Di],id);
954tag(Fd,<<>>,N,Gat,Di,Now) ->
955    case progress_read(Fd) of
956	{ok,Chunk} when is_binary(Chunk) ->
957	    tag(Fd,Chunk,N,Gat,Di,Now);
958        eof ->
959	    {tag_to_atom(lists:reverse(Gat)),lists:reverse(Di),<<>>,N}
960    end.
961
962check_if_truncated() ->
963    case get(last_tag) of
964	{{?ende,_},_} ->
965	    put(truncated,false),
966	    put(truncated_proc,false);
967        {{?literals,[]},_} ->
968            put(truncated,true),
969            put(truncated_proc,false),
970            %% Literals are truncated. Make sure we never
971            %% attempt to read in the literals. (Heaps that
972            %% references literals will show markers for
973            %% incomplete heaps, but will otherwise work.)
974            delete_index(?literals, []);
975	{TruncatedTag,_} ->
976	    put(truncated,true),
977	    find_truncated_proc(TruncatedTag)
978    end.
979
980find_truncated_proc({Tag,_Id}) when Tag==?atoms;
981                                    Tag==?binary;
982                                    Tag==?instr_data;
983                                    Tag==?memory_status;
984                                    Tag==?memory_map ->
985    put(truncated_proc,false);
986find_truncated_proc({Tag,Pid}) ->
987    case is_proc_tag(Tag) of
988	true ->
989	    put(truncated_proc,Pid);
990	false ->
991	    %% This means that the dump is truncated between ?proc and
992	    %% ?proc_heap => memory info is missing for all procs.
993	    put(truncated_proc,"<0.0.0>")
994    end.
995
996is_proc_tag(Tag)  when Tag==?proc;
997		       Tag==?proc_dictionary;
998		       Tag==?proc_messages;
999		       Tag==?proc_stack;
1000		       Tag==?proc_heap ->
1001    true;
1002is_proc_tag(_) ->
1003    false.
1004
1005%%%-----------------------------------------------------------------
1006%%% Functions for reading information from the dump
1007general_info(File) ->
1008    [{_Id,Start}] = lookup_index(?erl_crash_dump),
1009    Fd = open(File),
1010    pos_bof(Fd,Start),
1011    Created = case get_rest_of_line(Fd) of
1012		  {eof,SomeOfLine} -> SomeOfLine;
1013		  WholeLine -> WholeLine
1014	      end,
1015
1016    {Slogan,SysVsn} = get_slogan_and_sysvsn(Fd,[]),
1017    GI = get_general_info(Fd,#general_info{created=Created,
1018                                           slogan=Slogan,
1019                                           system_vsn=SysVsn}),
1020
1021    {MemTot,MemMax} =
1022	case lookup_index(?memory) of
1023	    [{_,MemStart}] ->
1024		pos_bof(Fd,MemStart),
1025		Memory = get_meminfo(Fd,[]),
1026		Tot = case lists:keysearch(total,1,Memory) of
1027			  {value,{_,T}} -> T;
1028			  false -> ""
1029		      end,
1030		Max = case lists:keysearch(maximum,1,Memory) of
1031			  {value,{_,M}} -> M;
1032			  false -> ""
1033		      end,
1034		{Tot,Max};
1035	    _ ->
1036		{"",""}
1037	end,
1038
1039    close(Fd),
1040    {NumProcs,NumEts,NumFuns,NumTimers} = count(),
1041    NodeName =
1042	case lookup_index(?node) of
1043	    [{N,_Start}] ->
1044		N;
1045	    [] ->
1046		case lookup_index(?no_distribution) of
1047		    [_] -> "'nonode@nohost'";
1048		    [] -> "unknown"
1049		end
1050	end,
1051
1052    InstrInfo =
1053	case lookup_index(?old_instr_data) of
1054	    [] ->
1055		case lookup_index(?instr_data) of
1056		    [] ->
1057			false;
1058		    _ ->
1059			instr_data
1060		end;
1061	    _ ->
1062		old_instr_data
1063	end,
1064    GI#general_info{node_name=NodeName,
1065		    num_procs=integer_to_list(NumProcs),
1066		    num_ets=integer_to_list(NumEts),
1067		    num_timers=integer_to_list(NumTimers),
1068		    num_fun=integer_to_list(NumFuns),
1069		    mem_tot=MemTot,
1070		    mem_max=MemMax,
1071		    instr_info=InstrInfo}.
1072
1073get_slogan_and_sysvsn(Fd,Acc) ->
1074    case string(Fd,eof) of
1075        "Slogan: " ++ SloganPart when Acc==[] ->
1076            get_slogan_and_sysvsn(Fd,[SloganPart]);
1077        "System version: " ++ SystemVsn ->
1078            {lists:append(lists:reverse(Acc)),SystemVsn};
1079        eof ->
1080            {lists:append(lists:reverse(Acc)),"-1"};
1081        SloganPart ->
1082            get_slogan_and_sysvsn(Fd,[[$\n|SloganPart]|Acc])
1083    end.
1084
1085get_general_info(Fd,GenInfo) ->
1086    case line_head(Fd) of
1087	"Compiled" ->
1088	    get_general_info(Fd,GenInfo#general_info{compile_time=bytes(Fd)});
1089	"Taints" ->
1090	    Val = case string(Fd) of "-1" -> "(none)"; Line -> Line end,
1091	    get_general_info(Fd,GenInfo#general_info{taints=Val});
1092	"Atoms" ->
1093	    get_general_info(Fd,GenInfo#general_info{num_atoms=bytes(Fd)});
1094	"Calling Thread" ->
1095	    get_general_info(Fd,GenInfo#general_info{thread=bytes(Fd)});
1096	"=" ++ _next_tag ->
1097	    GenInfo;
1098	Other ->
1099	    unexpected(Fd,Other,"general information"),
1100	    GenInfo
1101    end.
1102
1103count() ->
1104    {count_index(?proc),count_index(?ets),count_index(?fu),count_index(?timer)}.
1105
1106
1107%%-----------------------------------------------------------------
1108%% Page with all processes
1109procs_summary(File,WS) ->
1110    ParseFun = fun(Fd,Pid0) ->
1111		       Pid = list_to_pid(Pid0),
1112		       Proc = get_procinfo(Fd,fun main_procinfo/5,
1113					   #proc{pid=Pid},WS),
1114		       case Proc#proc.name of
1115			   undefined ->
1116			       true;
1117			   Name ->
1118			       %% Registered process - store to allow
1119			       %% lookup for timers connected to
1120			       %% registered name instead of pid.
1121			       ets:insert(cdv_reg_proc_table,{Name,Pid}),
1122			       ets:insert(cdv_reg_proc_table,{Pid0,Name})
1123		       end,
1124		       case Proc#proc.memory of
1125			   undefined -> Proc#proc{memory=Proc#proc.stack_heap};
1126			   _ -> Proc
1127		       end
1128	       end,
1129    lookup_and_parse_index(File,?proc,ParseFun,"processes").
1130
1131%%-----------------------------------------------------------------
1132%% Page with one process
1133get_proc_details(File,Pid,WS,DumpVsn) ->
1134    case lookup_index(?proc,Pid) of
1135	[{_,Start}] ->
1136	    Fd = open(File),
1137	    {{Stack,MsgQ,Dict},TW} =
1138		case truncated_warning([{?proc,Pid}]) of
1139		    [] ->
1140                        expand_memory(Fd,Pid,DumpVsn);
1141		    TW0 ->
1142			{{[],[],[]},TW0}
1143		end,
1144	    pos_bof(Fd,Start),
1145	    Proc0 = #proc{pid=Pid,stack_dump=Stack,msg_q=MsgQ,dict=Dict},
1146	    Proc = get_procinfo(Fd,fun all_procinfo/5,Proc0,WS),
1147	    close(Fd),
1148	    {ok,Proc,TW};
1149	_ ->
1150	    maybe_other_node(Pid)
1151    end.
1152
1153get_procinfo(Fd,Fun,Proc,WS) ->
1154    case line_head(Fd) of
1155	"State" ->
1156	    State = case bytes(Fd) of
1157			"Garbing" -> "Garbing\n(limited info)";
1158			State0 -> State0
1159		    end,
1160	    get_procinfo(Fd,Fun,Proc#proc{state=State},WS);
1161	"Name" ->
1162	    get_procinfo(Fd,Fun,Proc#proc{name=string(Fd)},WS);
1163	"Spawned as" ->
1164	    IF = string(Fd),
1165	    case Proc#proc.name of
1166		undefined ->
1167		    get_procinfo(Fd,Fun,Proc#proc{name=IF,init_func=IF},WS);
1168		_ ->
1169		    get_procinfo(Fd,Fun,Proc#proc{init_func=IF},WS)
1170	    end;
1171	"Message queue length" ->
1172	    %% stored as integer so we can sort on it
1173	    get_procinfo(Fd,Fun,Proc#proc{msg_q_len=list_to_integer(bytes(Fd))},WS);
1174	"Reductions" ->
1175	    %% stored as integer so we can sort on it
1176	    get_procinfo(Fd,Fun,Proc#proc{reds=list_to_integer(bytes(Fd))},WS);
1177	"Stack+heap" ->
1178	    %% stored as integer so we can sort on it
1179	    get_procinfo(Fd,Fun,Proc#proc{stack_heap=
1180					  list_to_integer(bytes(Fd))*WS},WS);
1181	"Memory" ->
1182	    %% stored as integer so we can sort on it
1183	    get_procinfo(Fd,Fun,Proc#proc{memory=list_to_integer(bytes(Fd))},WS);
1184	{eof,_} ->
1185	    Proc; % truncated file
1186	Other ->
1187	    Fun(Fd,Fun,Proc,WS,Other)
1188    end.
1189
1190main_procinfo(Fd,Fun,Proc,WS,LineHead) ->
1191    case LineHead of
1192	"=" ++ _next_tag ->
1193	    Proc;
1194	"arity = " ++ _ ->
1195	    %%! Temporary workaround
1196	    get_procinfo(Fd,Fun,Proc,WS);
1197	_Other ->
1198	    skip_rest_of_line(Fd),
1199	    get_procinfo(Fd,Fun,Proc,WS)
1200    end.
1201all_procinfo(Fd,Fun,Proc,WS,LineHead) ->
1202    case LineHead of
1203	%% - START - moved from get_procinfo -
1204	"Spawned by" ->
1205	    case bytes(Fd) of
1206		"[]" ->
1207		    get_procinfo(Fd,Fun,Proc,WS);
1208		Parent ->
1209		    get_procinfo(Fd,Fun,Proc#proc{parent=Parent},WS)
1210	    end;
1211	"Started" ->
1212	    get_procinfo(Fd,Fun,Proc#proc{start_time=bytes(Fd)},WS);
1213	"Last scheduled in for" ->
1214	    get_procinfo(Fd,Fun,Proc#proc{current_func=
1215					  {"Last scheduled in for",
1216					   string(Fd)}},WS);
1217	"Current call" ->
1218	    get_procinfo(Fd,Fun,Proc#proc{current_func={"Current call",
1219							string(Fd)}},WS);
1220	"Number of heap fragments" ->
1221	    get_procinfo(Fd,Fun,Proc#proc{num_heap_frag=bytes(Fd)},WS);
1222	"Heap fragment data" ->
1223	    get_procinfo(Fd,Fun,Proc#proc{heap_frag_data=bytes(Fd)},WS);
1224	"OldHeap" ->
1225	    Bytes = list_to_integer(bytes(Fd))*WS,
1226	    get_procinfo(Fd,Fun,Proc#proc{old_heap=Bytes},WS);
1227	"Heap unused" ->
1228	    Bytes = list_to_integer(bytes(Fd))*WS,
1229	    get_procinfo(Fd,Fun,Proc#proc{heap_unused=Bytes},WS);
1230	"OldHeap unused" ->
1231	    Bytes = list_to_integer(bytes(Fd))*WS,
1232	    get_procinfo(Fd,Fun,Proc#proc{old_heap_unused=Bytes},WS);
1233	"BinVHeap" ->
1234	    Bytes = list_to_integer(bytes(Fd))*WS,
1235	    get_procinfo(Fd,Fun,Proc#proc{bin_vheap=Bytes},WS);
1236	"OldBinVHeap" ->
1237	    Bytes = list_to_integer(bytes(Fd))*WS,
1238	    get_procinfo(Fd,Fun,Proc#proc{old_bin_vheap=Bytes},WS);
1239	"BinVHeap unused" ->
1240	    Bytes = list_to_integer(bytes(Fd))*WS,
1241	    get_procinfo(Fd,Fun,Proc#proc{bin_vheap_unused=Bytes},WS);
1242	"OldBinVHeap unused" ->
1243            Bytes = case bytes(Fd) of
1244                        "overflow" -> -1;
1245                        Int -> list_to_integer(Int)*WS
1246                    end,
1247	    get_procinfo(Fd,Fun,Proc#proc{old_bin_vheap_unused=Bytes},WS);
1248	"New heap start" ->
1249	    get_procinfo(Fd,Fun,Proc#proc{new_heap_start=bytes(Fd)},WS);
1250	"New heap top" ->
1251	    get_procinfo(Fd,Fun,Proc#proc{new_heap_top=bytes(Fd)},WS);
1252	"Stack top" ->
1253	    get_procinfo(Fd,Fun,Proc#proc{stack_top=bytes(Fd)},WS);
1254	"Stack end" ->
1255	    get_procinfo(Fd,Fun,Proc#proc{stack_end=bytes(Fd)},WS);
1256	"Old heap start" ->
1257	    get_procinfo(Fd,Fun,Proc#proc{old_heap_start=bytes(Fd)},WS);
1258	"Old heap top" ->
1259	    get_procinfo(Fd,Fun,Proc#proc{old_heap_top=bytes(Fd)},WS);
1260	"Old heap end" ->
1261	    get_procinfo(Fd,Fun,Proc#proc{old_heap_end=bytes(Fd)},WS);
1262	%% - END - moved from get_procinfo -
1263	"Last calls" ->
1264	    get_procinfo(Fd,Fun,Proc#proc{last_calls=get_last_calls(Fd)},WS);
1265	"Link list" ->
1266	    {Links,Monitors,MonitoredBy} = get_link_list(Fd),
1267	    get_procinfo(Fd,Fun,Proc#proc{links=Links,
1268					  monitors=Monitors,
1269					  mon_by=MonitoredBy},WS);
1270	"Program counter" ->
1271	    get_procinfo(Fd,Fun,Proc#proc{prog_count=string(Fd)},WS);
1272	"CP" ->
1273	    get_procinfo(Fd,Fun,Proc#proc{cp=string(Fd)},WS);
1274	"arity = " ++ Arity ->
1275	    %%! Temporary workaround
1276	    get_procinfo(Fd,Fun,Proc#proc{arity=Arity--"\r\n"},WS);
1277	"Run queue" ->
1278	    get_procinfo(Fd,Fun,Proc#proc{run_queue=string(Fd)},WS);
1279	"Internal State" ->
1280	    get_procinfo(Fd,Fun,Proc#proc{int_state=string(Fd)},WS);
1281	"=" ++ _next_tag ->
1282	    Proc;
1283	Other ->
1284	    unexpected(Fd,Other,"process info"),
1285	    get_procinfo(Fd,Fun,Proc,WS)
1286    end.
1287
1288%% The end of the 'Last calls' section is meant to be an empty line,
1289%% but in some cases this is not the case, so we also need to look for
1290%% the next heading which currently (OTP-20.1) can be "Link list: ",
1291%% "Dictionary: " or "Reductions: ". We do this by looking for ": "
1292%% and when found, pushing the heading back into the saved chunk.
1293%%
1294%% Note that the 'Last calls' section is only present if the
1295%% 'save_calls' process flag is set.
1296get_last_calls(Fd) ->
1297    case get_chunk(Fd) of
1298	{ok,Bin} ->
1299	    get_last_calls(Fd,Bin,[],[]);
1300	eof ->
1301	    []
1302    end.
1303get_last_calls(Fd,<<$\n:8,Bin/binary>>,[],Lines) ->
1304    %% Empty line - we're done
1305    put_chunk(Fd,Bin),
1306    lists:reverse(Lines);
1307get_last_calls(Fd,<<$::8>>,Acc,Lines) ->
1308    case get_chunk(Fd) of
1309	{ok,Bin} ->
1310            %% Could be a colon followed by a space - see next function clause
1311	    get_last_calls(Fd,<<$::8,Bin/binary>>,Acc,Lines);
1312	eof ->
1313            %% Truncated here - either we've got the next heading, or
1314            %% it was truncated in a last call function, in which case
1315            %% we note that it was truncated
1316            case byte_list_to_string(lists:reverse(Acc)) of
1317                NextHeading when NextHeading=="Link list";
1318                                 NextHeading=="Dictionary";
1319                                 NextHeading=="Reductions" ->
1320                    put_chunk(Fd,list_to_binary(NextHeading++":")),
1321                    lists:reverse(Lines);
1322                LastCallFunction->
1323                    lists:reverse(Lines,[LastCallFunction++":...(truncated)"])
1324            end
1325    end;
1326get_last_calls(Fd,<<$\::8,$\s:8,Bin/binary>>,Acc,Lines) ->
1327    %% ": " - means we have the next heading in Acc - save it back
1328    %% into the chunk and return the lines we've found
1329    HeadingBin = list_to_binary(lists:reverse(Acc,[$:])),
1330    put_chunk(Fd,<<HeadingBin/binary,Bin/binary>>),
1331    lists:reverse(Lines);
1332get_last_calls(Fd,<<$\n:8,Bin/binary>>,Acc,Lines) ->
1333    get_last_calls(Fd,Bin,[],[byte_list_to_string(lists:reverse(Acc))|Lines]);
1334get_last_calls(Fd,<<$\r:8,Bin/binary>>,Acc,Lines) ->
1335    get_last_calls(Fd,Bin,Acc,Lines);
1336get_last_calls(Fd,<<$\s:8,Bin/binary>>,[],Lines) ->
1337    get_last_calls(Fd,Bin,[],Lines);
1338get_last_calls(Fd,<<Char:8,Bin/binary>>,Acc,Lines) ->
1339    get_last_calls(Fd,Bin,[Char|Acc],Lines);
1340get_last_calls(Fd,<<>>,Acc,Lines) ->
1341    case get_chunk(Fd) of
1342	{ok,Bin} ->
1343	    get_last_calls(Fd,Bin,Acc,Lines);
1344	eof ->
1345	    lists:reverse(Lines,[byte_list_to_string(lists:reverse(Acc))])
1346    end.
1347
1348get_link_list(Fd) ->
1349    case string(Fd) of
1350	"[" ++ Rest ->
1351            #{links:=Links,
1352              mons:=Monitors,
1353              mon_by:=MonitoredBy} =
1354                get_link_list(Rest,#{links=>[],mons=>[],mon_by=>[]}),
1355            {lists:reverse(Links),
1356             lists:reverse(Monitors),
1357             lists:reverse(MonitoredBy)};
1358        eof ->
1359            {[],[],[]}
1360    end.
1361
1362get_link_list(Bin,Acc) ->
1363    case string:split(Bin,", ") of
1364        [Link,Rest] ->
1365            get_link_list(Rest,get_link(Link,Acc));
1366        [Link] ->
1367            get_link(string:trim(Link,trailing,"]"),Acc)
1368    end.
1369
1370get_link("#Port"++_=PortStr,#{links:=Links}=Acc) ->
1371    Acc#{links=>[{PortStr,PortStr}|Links]};
1372get_link("<"++_=PidStr,#{links:=Links}=Acc) ->
1373    Acc#{links=>[{PidStr,PidStr}|Links]};
1374get_link("{to," ++ Rest,#{mons:=Monitors}=Acc) ->
1375    Acc#{mons=>[parse_monitor(Rest)|Monitors]};
1376get_link("{from," ++ Rest,#{mon_by:=MonitoredBy}=Acc) ->
1377    Acc#{mon_by=>[parse_monitor(Rest)|MonitoredBy]};
1378get_link(Unexpected,Acc) ->
1379    io:format("WARNING: found unexpected data in link list:~n~ts~n",[Unexpected]),
1380    Acc.
1381
1382parse_monitor(Monitor) ->
1383    case string:lexemes(Monitor,",{}") of
1384        [Node,"[]"] ->
1385            {Node,Node++" node monitor"};
1386        [Pid,Ref] ->
1387            {Pid,Pid++" ("++Ref++")"};
1388        [Name,Node,Ref] ->
1389            %% Named process
1390            PidStr = get_pid_from_name(Name,Node),
1391            {PidStr,"{"++Name++","++Node++"} ("++Ref++")"}
1392    end.
1393
1394get_pid_from_name(Name,Node) ->
1395    case ets:lookup(cdv_reg_proc_table,cdv_dump_node_name) of
1396	[{_,Node}] ->
1397	    case ets:lookup(cdv_reg_proc_table,Name) of
1398		[{_,Pid}] when is_pid(Pid) ->
1399		    pid_to_list(Pid);
1400		_ ->
1401		    "<unkonwn_pid>"
1402	    end;
1403	_ ->
1404	    "<unknown_pid_other_node>"
1405    end.
1406
1407maybe_other_node(Id) ->
1408    Channel =
1409	case split($.,Id) of
1410	    {"<" ++ N, _Rest} ->
1411		N;
1412	    {"#Port<" ++ N, _Rest} ->
1413		N;
1414	    {_, []} ->
1415		not_found
1416	end,
1417    maybe_other_node2(Channel).
1418
1419maybe_other_node2(not_found) -> not_found;
1420maybe_other_node2(Channel) ->
1421    Ms = ets:fun2ms(
1422	   fun({{Tag,Start},Ch}) when Tag=:=?visible_node, Ch=:=Channel ->
1423		   {"Visible Node",Start};
1424	      ({{Tag,Start},Ch}) when Tag=:=?hidden_node, Ch=:=Channel ->
1425		   {"Hidden Node",Start};
1426	      ({{Tag,Start},Ch}) when Tag=:=?not_connected, Ch=:=Channel ->
1427		   {"Not Connected Node",Start}
1428	   end),
1429
1430    case ets:select(cdv_dump_index_table,Ms) of
1431	[] ->
1432	    not_found;
1433	[_] ->
1434	    {other_node,Channel}
1435    end.
1436
1437
1438expand_memory(Fd,Pid,DumpVsn) ->
1439    DecodeOpts = get_decode_opts(DumpVsn),
1440    put(fd,Fd),
1441    Dict0 = get_literals(Fd,DecodeOpts),
1442    Dict = read_heap(Fd,Pid,DecodeOpts,Dict0),
1443    Expanded = {read_stack_dump(Fd,Pid,DecodeOpts,Dict),
1444		read_messages(Fd,Pid,DecodeOpts,Dict),
1445		read_dictionary(Fd,Pid,DecodeOpts,Dict)},
1446    erase(fd),
1447    IncompleteWarning =
1448        case erase(incomplete_heap) of
1449            undefined ->
1450                [];
1451            true ->
1452                ["WARNING: This process has an incomplete heap. "
1453                 "Some information might be missing."]
1454        end,
1455    {Expanded,IncompleteWarning}.
1456
1457get_literals(Fd,DecodeOpts) ->
1458    case get(?literals) of
1459        undefined ->
1460            OldFd = put(fd,Fd),
1461            Literals = read_literals(Fd,DecodeOpts),
1462            put(fd,OldFd),
1463            put(?literals,Literals),
1464            Literals;
1465        Literals ->
1466            Literals
1467    end.
1468
1469read_literals(Fd,DecodeOpts) ->
1470    case lookup_index(?literals,[]) of
1471	[{_,Start}] ->
1472            [{_,Chars}] = ets:lookup(cdv_heap_file_chars,literals),
1473            init_progress("Reading literals",Chars),
1474	    pos_bof(Fd,Start),
1475	    read_heap(DecodeOpts,gb_trees:empty());
1476        [] ->
1477            gb_trees:empty()
1478    end.
1479
1480get_decode_opts(DumpVsn) ->
1481    BinAddrAdj = if
1482                     DumpVsn < [0,3] ->
1483                         %% This is a workaround for a bug in dump
1484                         %% versions prior to 0.3: Addresses were
1485                         %% truncated to 32 bits. This could cause
1486                         %% binaries to get the same address as heap
1487                         %% terms in the dump. To work around it we
1488                         %% always store binaries on very high
1489                         %% addresses in the gb_tree.
1490                         16#f bsl 64;
1491                     true ->
1492                         0
1493                 end,
1494    Base64 = DumpVsn >= [0,5],
1495    #dec_opts{bin_addr_adj=BinAddrAdj,base64=Base64}.
1496
1497%%%
1498%%% Read top level section.
1499%%%
1500
1501read_stack_dump(Fd,Pid,DecodeOpts,Dict) ->
1502    case lookup_index(?proc_stack,Pid) of
1503	[{_,Start}] ->
1504	    pos_bof(Fd,Start),
1505	    read_stack_dump1(Fd,DecodeOpts,Dict,[]);
1506	[] ->
1507	    []
1508    end.
1509read_stack_dump1(Fd,DecodeOpts,Dict,Acc) ->
1510    %% This function is never called if the dump is truncated in {?proc_heap,Pid}
1511    case bytes(Fd) of
1512	"=" ++ _next_tag ->
1513	    lists:reverse(Acc);
1514	Line ->
1515	    Stack = parse_top(Line,DecodeOpts,Dict),
1516	    read_stack_dump1(Fd,DecodeOpts,Dict,[Stack|Acc])
1517    end.
1518
1519parse_top(Line0, DecodeOpts, D) ->
1520    {Label,Line1} = get_label(Line0),
1521    {Term,Line,D} = parse_term(Line1, DecodeOpts, D),
1522    [] = skip_blanks(Line),
1523    {Label,Term}.
1524
1525%%%
1526%%% Read message queue.
1527%%%
1528
1529read_messages(Fd,Pid,DecodeOpts,Dict) ->
1530    case lookup_index(?proc_messages,Pid) of
1531	[{_,Start}] ->
1532	    pos_bof(Fd,Start),
1533	    read_messages1(Fd,DecodeOpts,Dict,[]);
1534	[] ->
1535	    []
1536    end.
1537read_messages1(Fd,DecodeOpts,Dict,Acc) ->
1538    %% This function is never called if the dump is truncated in {?proc_heap,Pid}
1539    case bytes(Fd) of
1540	"=" ++ _next_tag ->
1541	    lists:reverse(Acc);
1542	Line ->
1543	    Msg = parse_message(Line,DecodeOpts,Dict),
1544	    read_messages1(Fd,DecodeOpts,Dict,[Msg|Acc])
1545    end.
1546
1547parse_message(Line0, DecodeOpts, D) ->
1548    {Msg,":"++Line1,_} = parse_term(Line0, DecodeOpts, D),
1549    {Token,Line,_} = parse_term(Line1, DecodeOpts, D),
1550    [] = skip_blanks(Line),
1551    {Msg,Token}.
1552
1553%%%
1554%%% Read process dictionary
1555%%%
1556
1557read_dictionary(Fd,Pid,DecodeOpts,Dict) ->
1558    case lookup_index(?proc_dictionary,Pid) of
1559	[{_,Start}] ->
1560	    pos_bof(Fd,Start),
1561	    read_dictionary1(Fd,DecodeOpts,Dict,[]);
1562	[] ->
1563	    []
1564    end.
1565read_dictionary1(Fd,DecodeOpts,Dict,Acc) ->
1566    %% This function is never called if the dump is truncated in {?proc_heap,Pid}
1567    case bytes(Fd) of
1568	"=" ++ _next_tag ->
1569	    lists:reverse(Acc);
1570	Line ->
1571	    Msg = parse_dictionary(Line,DecodeOpts,Dict),
1572	    read_dictionary1(Fd,DecodeOpts,Dict,[Msg|Acc])
1573    end.
1574
1575parse_dictionary(Line0, DecodeOpts, D) ->
1576    {Entry,Line,_} = parse_term(Line0, DecodeOpts, D),
1577    [] = skip_blanks(Line),
1578    Entry.
1579
1580%%%
1581%%% Read heap data.
1582%%%
1583
1584read_heap(Fd,Pid,DecodeOpts,Dict0) ->
1585    case lookup_index(?proc_heap,Pid) of
1586	[{_,Pos}] ->
1587            [{_,Chars}] = ets:lookup(cdv_heap_file_chars,Pid),
1588            init_progress("Reading process heap",Chars),
1589	    pos_bof(Fd,Pos),
1590	    read_heap(DecodeOpts,Dict0);
1591	[] ->
1592	    Dict0
1593    end.
1594
1595read_heap(DecodeOpts, Dict0) ->
1596    %% This function is never called if the dump is truncated in
1597    %% {?proc_heap,Pid}.
1598    %%
1599    %% It is not always possible to reconstruct the heap terms
1600    %% in a single pass, especially if maps are involved.
1601    %% See crashdump_helper:literal_map/0 for an example.
1602    %%
1603    %% Therefore, we need two passes. In the first pass
1604    %% we collect all lines without parsing them, and in the
1605    %% second pass we parse them.
1606    %%
1607    %% The first pass follows.
1608
1609    Lines0 = read_heap_lines(),
1610
1611    %% Save a map of all unprocessed lines so that deref_ptr() can
1612    %% access any line when there are references to terms not yet
1613    %% built.
1614
1615    LineMap = maps:from_list(Lines0),
1616    put(line_map, LineMap),
1617
1618    %% Refc binaries (tag "Yc") must be processed before any sub
1619    %% binaries (tag "Ys") referencing them, so we make sure to
1620    %% process all the refc binaries first.
1621    %%
1622    %% The other lines can be processed in any order, but processing
1623    %% them in the reverse order compared to how they are printed in
1624    %% the crash dump seems to minimize the number of references to
1625    %% terms that have not yet been built. That happens to be the
1626    %% order of the line list as returned by read_heap_lines/0.
1627
1628    RefcBins = [Refc || {_,<<"Yc",_/binary>>}=Refc <- Lines0],
1629    Lines = RefcBins ++ Lines0,
1630
1631    %% Second pass.
1632
1633    init_progress("Processing terms", map_size(LineMap)),
1634    Dict = parse_heap_terms(Lines, DecodeOpts, Dict0),
1635    erase(line_map),
1636    end_progress(),
1637    Dict.
1638
1639read_heap_lines() ->
1640    read_heap_lines_1(get(fd), []).
1641
1642read_heap_lines_1(Fd, Acc) ->
1643    case bytes(Fd) of
1644        "=" ++ _next_tag ->
1645            end_progress(),
1646            put(fd, end_of_heap),
1647            Acc;
1648        Line0 ->
1649            update_progress(length(Line0)+1),
1650            {Addr,":"++Line1} = get_hex(Line0),
1651
1652            %% Reduce the memory consumption by converting the
1653            %% line to a binary. Measurements show that it may also
1654            %% be benefical for performance, too, because it makes the
1655            %% garbage collections cheaper.
1656
1657            Line = list_to_binary(Line1),
1658            read_heap_lines_1(Fd, [{Addr,Line}|Acc])
1659    end.
1660
1661parse_heap_terms([{Addr,Line0}|T], DecodeOpts, Dict0) ->
1662    case gb_trees:is_defined(Addr, Dict0) of
1663        true ->
1664            %% Already parsed (by a recursive call from do_deref_ptr()
1665            %% to parse_line()). Nothing to do.
1666            parse_heap_terms(T, DecodeOpts, Dict0);
1667        false ->
1668            %% Parse this previously unparsed term.
1669            Dict = parse_line(Addr, Line0, DecodeOpts, Dict0),
1670            parse_heap_terms(T, DecodeOpts, Dict)
1671    end;
1672parse_heap_terms([], _DecodeOpts, Dict) ->
1673    Dict.
1674
1675parse_line(Addr, Line0, DecodeOpts, Dict0) ->
1676    update_progress(1),
1677    Line1 = binary_to_list(Line0),
1678    {_Term,Line,Dict} = parse_heap_term(Line1, Addr, DecodeOpts, Dict0),
1679    [] = skip_blanks(Line),                     %Assertion.
1680    Dict.
1681
1682%%-----------------------------------------------------------------
1683%% Page with one port
1684get_port(File,Port) ->
1685    case lookup_index(?port,Port) of
1686	[{_,Start}] ->
1687	    Fd = open(File),
1688	    pos_bof(Fd,Start),
1689	    R = get_portinfo(Fd,#port{id=Port}),
1690	    close(Fd),
1691	    {ok,R};
1692	[] ->
1693	    maybe_other_node(Port)
1694    end.
1695
1696%%-----------------------------------------------------------------
1697%% Page with all ports
1698get_ports(File) ->
1699    ParseFun = fun(Fd,Id) -> get_portinfo(Fd,#port{id=port_to_tuple(Id)}) end,
1700    lookup_and_parse_index(File,?port,ParseFun,"ports").
1701
1702%% Converting port string to tuple to secure correct sorting. This is
1703%% converted back in cdv_port_cb:format/1.
1704port_to_tuple("#Port<"++Port) ->
1705    [I1,I2] = string:lexemes(Port,".>"),
1706    {list_to_integer(I1),list_to_integer(I2)}.
1707
1708get_portinfo(Fd,Port) ->
1709    case line_head(Fd) of
1710        "State" ->
1711	    get_portinfo(Fd,Port#port{state=bytes(Fd)});
1712        "Task Flags" ->
1713	    get_portinfo(Fd,Port#port{task_flags=bytes(Fd)});
1714	"Slot" ->
1715	    %% stored as integer so we can sort on it
1716	    get_portinfo(Fd,Port#port{slot=list_to_integer(bytes(Fd))});
1717	"Connected" ->
1718	    %% stored as pid so we can sort on it
1719	    Connected0 = bytes(Fd),
1720	    Connected =
1721		try list_to_pid(Connected0)
1722		catch error:badarg -> Connected0
1723		end,
1724	    get_portinfo(Fd,Port#port{connected=Connected});
1725	"Links" ->
1726	    Pids = split_pid_list_no_space(bytes(Fd)),
1727	    Links = [{Pid,Pid} || Pid <- Pids],
1728	    get_portinfo(Fd,Port#port{links=Links});
1729	"Registered as" ->
1730	    get_portinfo(Fd,Port#port{name=string(Fd)});
1731	"Monitors" ->
1732	    Monitors0 = string:lexemes(bytes(Fd),"()"),
1733	    Monitors = [begin
1734			    [Pid,Ref] = string:lexemes(Mon,","),
1735			    {Pid,Pid++" ("++Ref++")"}
1736			end || Mon <- Monitors0],
1737	    get_portinfo(Fd,Port#port{monitors=Monitors});
1738        "Suspended" ->
1739	    Pids = split_pid_list_no_space(bytes(Fd)),
1740	    Suspended = [{Pid,Pid} || Pid <- Pids],
1741	    get_portinfo(Fd,Port#port{suspended=Suspended});
1742	"Port controls linked-in driver" ->
1743	    Str = lists:flatten(["Linked in driver: " | string(Fd)]),
1744	    get_portinfo(Fd,Port#port{controls=Str});
1745	"Port controls forker process" ->
1746	    Str = lists:flatten(["Forker process: " | string(Fd)]),
1747	    get_portinfo(Fd,Port#port{controls=Str});
1748	"Port controls external process" ->
1749	    Str = lists:flatten(["External proc: " | string(Fd)]),
1750	    get_portinfo(Fd,Port#port{controls=Str});
1751	"Port is a file" ->
1752	    Str = lists:flatten(["File: "| string(Fd)]),
1753	    get_portinfo(Fd,Port#port{controls=Str});
1754	"Port is UNIX fd not opened by emulator" ->
1755	    Str = lists:flatten(["UNIX fd not opened by emulator: "| string(Fd)]),
1756	    get_portinfo(Fd,Port#port{controls=Str});
1757        "Input" ->
1758	    get_portinfo(Fd,Port#port{input=list_to_integer(bytes(Fd))});
1759        "Output" ->
1760	    get_portinfo(Fd,Port#port{output=list_to_integer(bytes(Fd))});
1761        "Queue" ->
1762	    get_portinfo(Fd,Port#port{queue=list_to_integer(bytes(Fd))});
1763        "Port Data" ->
1764	    get_portinfo(Fd,Port#port{port_data=string(Fd)});
1765
1766	"=" ++ _next_tag ->
1767	    Port;
1768	Other ->
1769	    unexpected(Fd,Other,"port info"),
1770	    Port
1771    end.
1772
1773split_pid_list_no_space(String) ->
1774    split_pid_list_no_space(String,[],[]).
1775split_pid_list_no_space([$>|Rest],Acc,Pids) ->
1776    split_pid_list_no_space(Rest,[],[lists:reverse(Acc,[$>])|Pids]);
1777split_pid_list_no_space([H|T],Acc,Pids) ->
1778    split_pid_list_no_space(T,[H|Acc],Pids);
1779split_pid_list_no_space([],[],Pids) ->
1780    lists:reverse(Pids).
1781
1782%%-----------------------------------------------------------------
1783%% Page with external ets tables
1784get_ets_tables(File,Pid,WS) ->
1785    ParseFun = fun(Fd,Id) ->
1786		       ET = get_etsinfo(Fd,#ets_table{pid=list_to_pid(Id)},WS),
1787                       ET#ets_table{is_named=tab_is_named(ET)}
1788	       end,
1789    lookup_and_parse_index(File,{?ets,Pid},ParseFun,"ets").
1790
1791tab_is_named(#ets_table{id=Name,name=Name}) -> "yes";
1792tab_is_named(#ets_table{}) -> "no".
1793
1794get_etsinfo(Fd,EtsTable = #ets_table{details=Ds},WS) ->
1795    case line_head(Fd) of
1796	"Slot" ->
1797	    get_etsinfo(Fd,EtsTable#ets_table{slot=list_to_integer(bytes(Fd))},WS);
1798	"Table" ->
1799	    get_etsinfo(Fd,EtsTable#ets_table{id=string(Fd)},WS);
1800	"Name" ->
1801	    get_etsinfo(Fd,EtsTable#ets_table{name=string(Fd)},WS);
1802	"Ordered set (AVL tree), Elements" ->
1803	    skip_rest_of_line(Fd),
1804	    get_etsinfo(Fd,EtsTable#ets_table{data_type="tree"},WS);
1805	"Buckets" ->
1806	    %% A bug in erl_db_hash.c prints a space after the buckets
1807	    %% - need to strip the string to make list_to_integer/1 happy.
1808	    Buckets = list_to_integer(string:trim(bytes(Fd),both,"\s")),
1809	    get_etsinfo(Fd,EtsTable#ets_table{buckets=Buckets},WS);
1810	"Objects" ->
1811	    get_etsinfo(Fd,EtsTable#ets_table{size=list_to_integer(bytes(Fd))},WS);
1812	"Words" ->
1813	    Words = list_to_integer(bytes(Fd)),
1814	    Bytes =
1815		case Words of
1816		    -1 -> -1; % probably truncated
1817		    _ -> Words * WS
1818		end,
1819	    get_etsinfo(Fd,EtsTable#ets_table{memory={bytes,Bytes}},WS);
1820	"=" ++ _next_tag ->
1821	    EtsTable;
1822	"Chain Length Min" ->
1823	    Val = bytes(Fd),
1824	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_min=>Val}},WS);
1825	"Chain Length Avg" ->
1826	    Val = try list_to_float(string:trim(bytes(Fd),both,"\s"))
1827                  catch _:_ -> "-"
1828                  end,
1829	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_avg=>Val}},WS);
1830	"Chain Length Max" ->
1831	    Val = bytes(Fd),
1832	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_max=>Val}},WS);
1833	"Chain Length Std Dev" ->
1834	    Val = bytes(Fd),
1835	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_stddev=>Val}},WS);
1836	"Chain Length Expected Std Dev" ->
1837	    Val = bytes(Fd),
1838	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_exp_stddev=>Val}},WS);
1839	"Fixed" ->
1840	    Val = bytes(Fd),
1841	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{fixed=>Val}},WS);
1842	"Type" ->
1843	    Val = bytes(Fd),
1844	    get_etsinfo(Fd,EtsTable#ets_table{data_type=Val},WS);
1845	"Protection" ->
1846	    Val = bytes(Fd),
1847	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{protection=>Val}},WS);
1848	"Compressed" ->
1849	    Val = bytes(Fd),
1850	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{compressed=>Val}},WS);
1851	"Write Concurrency" ->
1852	    Val = bytes(Fd),
1853	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{write_c=>Val}},WS);
1854	"Read Concurrency" ->
1855	    Val = bytes(Fd),
1856	    get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{read_c=>Val}},WS);
1857	Other ->
1858	    unexpected(Fd,Other,"ETS info"),
1859	    EtsTable
1860    end.
1861
1862
1863%% Internal ets table page
1864get_internal_ets_tables(File,WS) ->
1865    InternalEts = lookup_index(?internal_ets),
1866    Fd = open(File),
1867    R = lists:map(
1868	  fun({Descr,Start}) ->
1869		  pos_bof(Fd,Start),
1870		  {Descr,get_etsinfo(Fd,#ets_table{},WS)}
1871	  end,
1872	  InternalEts),
1873    close(Fd),
1874    R.
1875
1876%%-----------------------------------------------------------------
1877%% Page with list of all timers
1878get_timers(File,Pid) ->
1879    ParseFun = fun(Fd,Id) -> get_timerinfo(Fd,Id) end,
1880    T1 = lookup_and_parse_index(File,{?timer,Pid},ParseFun,"timers"),
1881    T2 = case ets:lookup(cdv_reg_proc_table,Pid) of
1882	     [{_,Name}] ->
1883		 lookup_and_parse_index(File,{?timer,Name},ParseFun,"timers");
1884	     _ ->
1885		 []
1886	 end,
1887    T1 ++ T2.
1888
1889get_timerinfo(Fd,Id) ->
1890    case catch list_to_pid(Id) of
1891	Pid when is_pid(Pid) ->
1892	    get_timerinfo_1(Fd,#timer{pid=Pid});
1893	_ ->
1894	    case ets:lookup(cdv_reg_proc_table,Id) of
1895		[{_,Pid}] when is_pid(Pid) ->
1896		    get_timerinfo_1(Fd,#timer{pid=Pid,name=Id});
1897		[] ->
1898		    get_timerinfo_1(Fd,#timer{name=Id})
1899	    end
1900    end.
1901
1902get_timerinfo_1(Fd,Timer) ->
1903    case line_head(Fd) of
1904	"Message" ->
1905	    get_timerinfo_1(Fd,Timer#timer{msg=string(Fd)});
1906	"Time left" ->
1907	    TimeLeft = list_to_integer(bytes(Fd) -- " ms"),
1908	    get_timerinfo_1(Fd,Timer#timer{time=TimeLeft});
1909	"=" ++ _next_tag ->
1910	    Timer;
1911	Other ->
1912	    unexpected(Fd,Other,"timer info"),
1913	    Timer
1914    end.
1915
1916%%-----------------------------------------------------------------
1917%% Page with information about a node in the distribution
1918get_node(File,Channel) ->
1919    Ms = ets:fun2ms(
1920	   fun({{Tag,Start},Ch}) when Tag=:=?visible_node, Ch=:=Channel ->
1921		   {visible,Start};
1922	      ({{Tag,Start},Ch}) when Tag=:=?hidden_node, Ch=:=Channel ->
1923		   {hidden,Start};
1924	      ({{Tag,Start},Ch}) when Tag=:=?not_connected, Ch=:=Channel ->
1925		   {not_connected,Start}
1926	   end),
1927
1928    case ets:select(cdv_dump_index_table,Ms) of
1929	[] ->
1930	    {error,not_found};
1931	[{Type,Pos}] ->
1932	    Fd = open(File),
1933	    NodeInfo = get_nodeinfo(Fd,Channel,Type,Pos),
1934	    close(Fd),
1935	    {ok,NodeInfo}
1936    end.
1937
1938%%-----------------------------------------------------------------
1939%% Page with information about the erlang distribution
1940nods(File) ->
1941    case lookup_index(?no_distribution) of
1942	[] ->
1943	    V = lookup_index(?visible_node),
1944	    H = lookup_index(?hidden_node),
1945	    N = lookup_index(?not_connected),
1946	    Fd = open(File),
1947	    Visible = lists:map(
1948			fun({Channel,Start}) ->
1949				get_nodeinfo(Fd,Channel,visible,Start)
1950			end,
1951			V),
1952	    Hidden = lists:map(
1953		       fun({Channel,Start}) ->
1954			       get_nodeinfo(Fd,Channel,hidden,Start)
1955		       end,
1956		       H),
1957	    NotConnected = lists:map(
1958			     fun({Channel,Start}) ->
1959				     get_nodeinfo(Fd,Channel,not_connected,Start)
1960			     end,
1961			     N),
1962	    close(Fd),
1963	    Visible++Hidden++NotConnected;
1964	[_] ->
1965	    %% no_distribution
1966	    []
1967    end.
1968
1969get_nodeinfo(Fd,Channel,Type,Start) ->
1970    pos_bof(Fd,Start),
1971    get_nodeinfo(Fd,#nod{channel=list_to_integer(Channel),conn_type=Type}).
1972
1973get_nodeinfo(Fd,Nod) ->
1974    case line_head(Fd) of
1975	"Name" ->
1976	    get_nodeinfo(Fd,Nod#nod{name=bytes(Fd)});
1977	"Controller" ->
1978	    get_nodeinfo(Fd,Nod#nod{controller=bytes(Fd)});
1979	"Creation" ->
1980	    %% Throwing away elements like "(refc=1)", which might be
1981	    %% printed from a debug compiled emulator.
1982	    Creations = lists:flatmap(fun(C) -> try [list_to_integer(C)]
1983						catch error:badarg -> []
1984						end
1985				      end, string:lexemes(bytes(Fd)," ")),
1986	    get_nodeinfo(Fd,Nod#nod{creation={creations,Creations}});
1987	"Remote link" ->
1988	    Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>"
1989	    {Local,Remote} = split(Procs),
1990	    Str = Local++" <-> "++Remote,
1991	    NewRemLinks = [{Local,Str} | Nod#nod.remote_links],
1992	    get_nodeinfo(Fd,Nod#nod{remote_links=NewRemLinks});
1993	"Remote monitoring" ->
1994	    Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>"
1995	    {Local,Remote} = split(Procs),
1996	    Str = Local++" -> "++Remote,
1997	    NewRemMon = [{Local,Str} | Nod#nod.remote_mon],
1998	    get_nodeinfo(Fd,Nod#nod{remote_mon=NewRemMon});
1999	"Remotely monitored by" ->
2000	    Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>"
2001	    {Local,Remote} = split(Procs),
2002	    Str = Local++" <- "++Remote,
2003	    NewRemMonBy = [{Local,Str} | Nod#nod.remote_mon_by],
2004	    get_nodeinfo(Fd,Nod#nod{remote_mon_by=NewRemMonBy});
2005	"Error" ->
2006	    get_nodeinfo(Fd,Nod#nod{error="ERROR: "++string(Fd)});
2007	"=" ++ _next_tag ->
2008	    Nod;
2009	Other ->
2010	    unexpected(Fd,Other,"node info"),
2011	    Nod
2012    end.
2013
2014%%-----------------------------------------------------------------
2015%% Page with details about one loaded modules
2016get_loaded_mod_details(File,Mod,DecodeOpts) ->
2017    [{_,Start}] = lookup_index(?mod,Mod),
2018    Fd = open(File),
2019    pos_bof(Fd,Start),
2020    InitLM = #loaded_mod{mod=Mod,old_size="No old code exists"},
2021    Fun = fun(F, LM, LineHead) ->
2022                  all_modinfo(F, LM, LineHead, DecodeOpts)
2023          end,
2024    ModInfo = get_loaded_mod_info(Fd,InitLM,Fun),
2025    close(Fd),
2026    ModInfo.
2027
2028%%-----------------------------------------------------------------
2029%% Page with list of all loaded modules
2030loaded_mods(File) ->
2031    ParseFun =
2032	fun(Fd,Id) ->
2033		get_loaded_mod_info(Fd,
2034				    #loaded_mod{mod=get_atom(list_to_binary(Id))},
2035				    fun main_modinfo/3)
2036	end,
2037    {CC,OC} =
2038	case lookup_index(?loaded_modules) of
2039	    [{_,StartTotal}] ->
2040		Fd = open(File),
2041		pos_bof(Fd,StartTotal),
2042		R = get_loaded_mod_totals(Fd,{"unknown","unknown"}),
2043		close(Fd),
2044		R;
2045	    [] ->
2046		{"unknown","unknown"}
2047    end,
2048    {CC,OC,lookup_and_parse_index(File,?mod,ParseFun,"modules")}.
2049
2050get_loaded_mod_totals(Fd,{CC,OC}) ->
2051    case line_head(Fd) of
2052	"Current code" ->
2053	    get_loaded_mod_totals(Fd,{bytes(Fd),OC});
2054	"Old code" ->
2055	    get_loaded_mod_totals(Fd,{CC,bytes(Fd)});
2056	"=" ++ _next_tag ->
2057	    {CC,OC};
2058	Other ->
2059	    unexpected(Fd,Other,"loaded modules info"),
2060	    {CC,OC} % truncated file
2061    end.
2062
2063get_loaded_mod_info(Fd,LM,Fun) ->
2064    case line_head(Fd) of
2065	"Current size" ->
2066	    CS = list_to_integer(bytes(Fd)),
2067	    get_loaded_mod_info(Fd,LM#loaded_mod{current_size=CS},Fun);
2068	"Old size" ->
2069	    OS = list_to_integer(bytes(Fd)),
2070	    get_loaded_mod_info(Fd,LM#loaded_mod{old_size=OS},Fun);
2071	"=" ++ _next_tag ->
2072	    LM;
2073	{eof,_} ->
2074	    LM; % truncated file
2075	Other ->
2076	    LM1 = Fun(Fd,LM,Other),
2077	    get_loaded_mod_info(Fd,LM1,Fun)
2078    end.
2079
2080main_modinfo(_Fd,LM,_LineHead) ->
2081    LM.
2082all_modinfo(Fd,LM,LineHead,DecodeOpts) ->
2083    case LineHead of
2084	"Current attributes" ->
2085            Str = get_attribute(Fd, DecodeOpts),
2086	    LM#loaded_mod{current_attrib=Str};
2087	"Current compilation info" ->
2088            Str = get_attribute(Fd, DecodeOpts),
2089	    LM#loaded_mod{current_comp_info=Str};
2090	"Old attributes" ->
2091            Str = get_attribute(Fd, DecodeOpts),
2092	    LM#loaded_mod{old_attrib=Str};
2093	"Old compilation info" ->
2094            Str = get_attribute(Fd, DecodeOpts),
2095	    LM#loaded_mod{old_comp_info=Str};
2096	Other ->
2097	    unexpected(Fd,Other,"loaded modules info"),
2098	    LM
2099    end.
2100
2101get_attribute(Fd, DecodeOpts) ->
2102    Term = do_get_attribute(Fd, DecodeOpts),
2103    io_lib:format("~tp~n",[Term]).
2104
2105do_get_attribute(Fd, DecodeOpts) ->
2106    Bytes = bytes(Fd, ""),
2107    try get_binary(Bytes, DecodeOpts) of
2108        {Bin,_} ->
2109            try binary_to_term(Bin) of
2110                Term ->
2111                    Term
2112            catch
2113                _:_ ->
2114                    {"WARNING: The term is probably truncated!",
2115                     "I cannot do binary_to_term/1.",
2116                     Bin}
2117            end
2118    catch
2119        _:_ ->
2120            {"WARNING: The term is probably truncated!",
2121             "I cannot convert to binary.",
2122             Bytes}
2123    end.
2124
2125%%-----------------------------------------------------------------
2126%% Page with list of all funs
2127funs(File) ->
2128    ParseFun = fun(Fd,_Id) -> get_funinfo(Fd,#fu{}) end,
2129    lookup_and_parse_index(File,?fu,ParseFun,"funs").
2130
2131get_funinfo(Fd,Fu) ->
2132    case line_head(Fd) of
2133	"Module" ->
2134	    get_funinfo(Fd,Fu#fu{module=bytes(Fd)});
2135	"Uniq" ->
2136	    get_funinfo(Fd,Fu#fu{uniq=list_to_integer(bytes(Fd))});
2137	"Index" ->
2138	    get_funinfo(Fd,Fu#fu{index=list_to_integer(bytes(Fd))});
2139	"Address" ->
2140	    get_funinfo(Fd,Fu#fu{address=bytes(Fd)});
2141	"Native_address" ->
2142	    get_funinfo(Fd,Fu#fu{native_address=bytes(Fd)});
2143	"Refc" ->
2144	    get_funinfo(Fd,Fu#fu{refc=list_to_integer(bytes(Fd))});
2145	"=" ++ _next_tag ->
2146	    Fu;
2147	Other ->
2148	    unexpected(Fd,Other,"fun info"),
2149	    Fu
2150    end.
2151
2152%%-----------------------------------------------------------------
2153%% Page with list of all atoms
2154atoms(File,NumAtoms) ->
2155    case lookup_index(?atoms) of
2156	[{_Id,Start}] ->
2157	    Fd = open(File),
2158	    pos_bof(Fd,Start),
2159	    get_atoms(Fd,NumAtoms);
2160	_ ->
2161	    []
2162    end.
2163
2164get_atoms(Fd,NumAtoms) ->
2165    case get_chunk(Fd) of
2166	{ok,Bin} ->
2167	    init_progress("Processing atoms",NumAtoms),
2168	    get_atoms(Fd,Bin,NumAtoms,[]);
2169	eof ->
2170	    []
2171    end.
2172
2173
2174%% Atoms are written one per line in the crash dump, in creation order
2175%% from last to first.
2176get_atoms(Fd,Bin,NumAtoms,Atoms) ->
2177    Bins = binary:split(Bin,<<"\n">>,[global]),
2178    get_atoms1(Fd,Bins,NumAtoms,Atoms).
2179
2180get_atoms1(_Fd,[<<"=",_/binary>>|_],_N,Atoms) ->
2181    end_progress(),
2182    Atoms;
2183get_atoms1(Fd,[LastBin],N,Atoms) ->
2184    case get_chunk(Fd) of
2185	{ok,Bin0} ->
2186	    get_atoms(Fd,<<LastBin/binary,Bin0/binary>>,N,Atoms);
2187	eof ->
2188	    end_progress(),
2189	    [{N,get_atom(LastBin)}|Atoms]
2190    end;
2191get_atoms1(Fd,[Bin|Bins],N,Atoms) ->
2192    update_progress(),
2193    get_atoms1(Fd,Bins,N-1,[{N,get_atom(Bin)}|Atoms]).
2194
2195%% This ensures sorting according to first actual letter in the atom,
2196%% disregarding possible single quote. It is formatted back to correct
2197%% syntax in cdv_atom_cb:format/1
2198get_atom(<<"\'",Atom/binary>>) ->
2199    {Atom,q}; % quoted
2200get_atom(Atom) when is_binary(Atom) ->
2201    {Atom,nq}. % not quoted
2202
2203%%-----------------------------------------------------------------
2204%% Page with list of all persistent terms
2205persistent_terms(File, DecodeOpts) ->
2206    case lookup_index(?persistent_terms) of
2207	[{_Id,Start}] ->
2208	    Fd = open(File),
2209	    pos_bof(Fd,Start),
2210	    Terms = get_persistent_terms(Fd),
2211            Dict = get_literals(Fd,DecodeOpts),
2212            parse_persistent_terms(Terms,DecodeOpts,Dict);
2213	_ ->
2214	    []
2215    end.
2216
2217parse_persistent_terms([[Name0,Val0]|Terms],DecodeOpts,Dict) ->
2218    {Name,_,_} = parse_term(binary_to_list(Name0),DecodeOpts,Dict),
2219    {Val,_,_} = parse_term(binary_to_list(Val0),DecodeOpts,Dict),
2220    [{Name,Val}|parse_persistent_terms(Terms,DecodeOpts,Dict)];
2221parse_persistent_terms([],_,_) -> [].
2222
2223get_persistent_terms(Fd) ->
2224    case get_chunk(Fd) of
2225	{ok,Bin} ->
2226	    get_persistent_terms(Fd,Bin,[]);
2227	eof ->
2228	    []
2229    end.
2230
2231
2232%% Persistent_Terms are written one per line in the crash dump.
2233get_persistent_terms(Fd,Bin,PersistentTerms) ->
2234    Bins = binary:split(Bin,<<"\n">>,[global]),
2235    get_persistent_terms1(Fd,Bins,PersistentTerms).
2236
2237get_persistent_terms1(_Fd,[<<"=",_/binary>>|_],PersistentTerms) ->
2238    PersistentTerms;
2239get_persistent_terms1(Fd,[LastBin],PersistentTerms) ->
2240    case get_chunk(Fd) of
2241	{ok,Bin0} ->
2242	    get_persistent_terms(Fd,<<LastBin/binary,Bin0/binary>>,PersistentTerms);
2243	eof ->
2244	    [get_persistent_term(LastBin)|PersistentTerms]
2245    end;
2246get_persistent_terms1(Fd,[Bin|Bins],Persistent_Terms) ->
2247    get_persistent_terms1(Fd,Bins,[get_persistent_term(Bin)|Persistent_Terms]).
2248
2249get_persistent_term(Bin) ->
2250    binary:split(Bin,<<"|">>).
2251
2252
2253%%-----------------------------------------------------------------
2254%% Page with memory information
2255memory(File) ->
2256    case lookup_index(?memory) of
2257	[{_,Start}] ->
2258	    Fd = open(File),
2259	    pos_bof(Fd,Start),
2260	    R = get_meminfo(Fd,[]),
2261	    close(Fd),
2262	    R;
2263	_ ->
2264	    []
2265    end.
2266
2267get_meminfo(Fd,Acc) ->
2268    case line_head(Fd) of
2269	"=" ++ _next_tag ->
2270	    lists:reverse(Acc);
2271	{eof,_last_line} ->
2272	    lists:reverse(Acc);
2273	Key ->
2274	    get_meminfo(Fd,[{list_to_atom(Key),bytes(Fd)}|Acc])
2275    end.
2276
2277%%-----------------------------------------------------------------
2278%% Page with information about allocated areas
2279allocated_areas(File) ->
2280    case lookup_index(?allocated_areas) of
2281	[{_,Start}] ->
2282	    Fd = open(File),
2283	    pos_bof(Fd,Start),
2284	    R = get_allocareainfo(Fd,[]),
2285	    close(Fd),
2286	    R;
2287	_ ->
2288	    []
2289    end.
2290
2291get_allocareainfo(Fd,Acc) ->
2292    case line_head(Fd) of
2293	"=" ++ _next_tag ->
2294	    lists:reverse(Acc);
2295	{eof,_last_line} ->
2296	    lists:reverse(Acc);
2297	Key ->
2298	    Val = bytes(Fd),
2299	    AllocInfo =
2300		case split(Val) of
2301		    {Alloc,[]} ->
2302			{Key,Alloc,""};
2303		    {Alloc,Used} ->
2304			{Key,Alloc,Used}
2305		end,
2306	    get_allocareainfo(Fd,[AllocInfo|Acc])
2307    end.
2308
2309%%-----------------------------------------------------------------
2310%% Page with information about allocators
2311allocator_info(File) ->
2312    case lookup_index(?allocator) of
2313	[] ->
2314	    [];
2315	AllAllocators ->
2316	    Fd = open(File),
2317	    R = lists:map(fun({Heading,Start}) ->
2318				  {Heading,get_allocatorinfo(Fd,Start)}
2319			  end,
2320			  AllAllocators),
2321	    close(Fd),
2322	    [allocator_summary(R) | R]
2323    end.
2324
2325get_allocatorinfo(Fd,Start) ->
2326    pos_bof(Fd,Start),
2327    get_allocatorinfo1(Fd,[],0).
2328
2329get_allocatorinfo1(Fd,Acc,Max) ->
2330    case line_head(Fd) of
2331	"=" ++ _next_tag ->
2332	    pad_and_reverse(Acc,Max,[]);
2333	{eof,_last_line} ->
2334	    pad_and_reverse(Acc,Max,[]);
2335	Key ->
2336	    Values = get_all_vals(bytes(Fd),[]),
2337	    L = length(Values),
2338	    Max1 = if L > Max -> L; true -> Max end,
2339	    get_allocatorinfo1(Fd,[{Key,Values}|Acc],Max1)
2340    end.
2341
2342get_all_vals([$ |Rest],Acc) ->
2343    [lists:reverse(Acc)|get_all_vals(Rest,[])];
2344get_all_vals([],Acc) ->
2345    [lists:reverse(Acc)];
2346get_all_vals([Char|Rest],Acc) ->
2347    get_all_vals(Rest,[Char|Acc]).
2348
2349%% Make sure all V have the same length by padding with "".
2350pad_and_reverse([{K,V}|T],Len,Rev) ->
2351    VLen = length(V),
2352    V1 = if VLen == Len -> V;
2353	    true -> V ++ lists:duplicate(Len-VLen,"")
2354	 end,
2355    pad_and_reverse(T,Len,[{K,V1}|Rev]);
2356pad_and_reverse([],_,Rev) ->
2357    Rev.
2358
2359%% Calculate allocator summary:
2360%%
2361%% System totals:
2362%%   blocks size   = sum of mbcs, mbcs_pool and sbcs blocks size over
2363%%                   all allocator instances of all types
2364%%   carriers size = sum of mbcs, mbcs_pool and sbcs carriers size over
2365%%                   all allocator instances of all types
2366%%
2367%% I any allocator except sbmbc_alloc has "option e: false" then don't
2368%% present system totals.
2369%%
2370%% For each allocator type:
2371%%   blocks size        = sum of sbmbcs, mbcs, mbcs_pool and sbcs blocks
2372%%                        size over all allocator instances of this type
2373%%   carriers size      = sum of sbmbcs, mbcs, mbcs_pool and sbcs carriers
2374%%                        size over all allocator instances of this type
2375%%   mseg carriers size = sum of mbcs and sbcs mseg carriers size over all
2376%%                        allocator instances of this type
2377%%
2378
2379-define(sbmbcs_blocks_size,"sbmbcs blocks size").
2380-define(mbcs_blocks_size,"mbcs blocks size").
2381-define(sbcs_blocks_size,"sbcs blocks size").
2382-define(sbmbcs_carriers_size,"sbmbcs carriers size").
2383-define(mbcs_carriers_size,"mbcs carriers size").
2384-define(sbcs_carriers_size,"sbcs carriers size").
2385-define(mbcs_mseg_carriers_size,"mbcs mseg carriers size").
2386-define(sbcs_mseg_carriers_size,"sbcs mseg carriers size").
2387-define(segments_size,"segments_size").
2388-define(mbcs_pool_blocks_size,"mbcs_pool blocks size").
2389-define(mbcs_pool_carriers_size,"mbcs_pool carriers size").
2390
2391-define(type_blocks_size,[?sbmbcs_blocks_size,
2392			  ?mbcs_blocks_size,
2393			  ?mbcs_pool_blocks_size,
2394			  ?sbcs_blocks_size]).
2395-define(type_carriers_size,[?sbmbcs_carriers_size,
2396			    ?mbcs_carriers_size,
2397			    ?mbcs_pool_carriers_size,
2398			    ?sbcs_carriers_size]).
2399-define(type_mseg_carriers_size,[?mbcs_mseg_carriers_size,
2400				 ?sbcs_mseg_carriers_size]).
2401-define(total_blocks_size,[?mbcs_blocks_size,
2402			   ?mbcs_pool_blocks_size,
2403			   ?sbcs_blocks_size]).
2404-define(total_carriers_size,[?mbcs_carriers_size,
2405			     ?mbcs_pool_carriers_size,
2406			     ?sbcs_carriers_size]).
2407-define(total_mseg_carriers_size,[?mbcs_mseg_carriers_size,
2408				  ?sbcs_mseg_carriers_size]).
2409-define(interesting_allocator_info, [?sbmbcs_blocks_size,
2410				     ?mbcs_blocks_size,
2411				     ?mbcs_pool_blocks_size,
2412				     ?sbcs_blocks_size,
2413				     ?sbmbcs_carriers_size,
2414				     ?mbcs_carriers_size,
2415				     ?sbcs_carriers_size,
2416				     ?mbcs_mseg_carriers_size,
2417				     ?mbcs_pool_carriers_size,
2418				     ?sbcs_mseg_carriers_size,
2419				     ?segments_size]).
2420-define(mseg_alloc,"mseg_alloc").
2421-define(seg_size,"segments_size").
2422-define(sbmbc_alloc,"sbmbc_alloc").
2423-define(opt_e_false,{"option e","false"}).
2424
2425allocator_summary(Allocators) ->
2426    {Sorted,DoTotal} = sort_allocator_types(Allocators,[],true),
2427    {TypeTotals0,Totals} = sum_allocator_data(Sorted,DoTotal),
2428    {TotalMCS,TypeTotals} =
2429	case lists:keytake(?mseg_alloc,1,TypeTotals0) of
2430	    {value,{_,[{?seg_size,SegSize}]},Rest} ->
2431		{integer_to_list(SegSize),Rest};
2432	    false ->
2433		{?not_available,TypeTotals0}
2434	end,
2435    {TotalBS,TotalCS} =
2436	case Totals of
2437	    false ->
2438		{?not_available,?not_available};
2439	    {TBS,TCS} ->
2440		{integer_to_list(TBS),integer_to_list(TCS)}
2441	end,
2442    {"Allocator Summary",
2443     ["blocks size","carriers size","mseg carriers size"],
2444     [{"total",[TotalBS,TotalCS,TotalMCS]} |
2445      format_allocator_summary(lists:reverse(TypeTotals))]}.
2446
2447format_allocator_summary([{Type,Data}|Rest]) ->
2448    [format_allocator_summary(Type,Data) | format_allocator_summary(Rest)];
2449format_allocator_summary([]) ->
2450    [].
2451
2452format_allocator_summary(Type,Data) ->
2453    BS = get_size_value(blocks_size,Data),
2454    CS = get_size_value(carriers_size,Data),
2455    MCS = get_size_value(mseg_carriers_size,Data),
2456    {Type,[BS,CS,MCS]}.
2457
2458get_size_value(Key,Data) ->
2459    case proplists:get_value(Key,Data) of
2460	undefined ->
2461	    ?not_available;
2462	Int ->
2463	    integer_to_list(Int)
2464    end.
2465
2466%% Sort allocator data per type
2467%%  Input  = [{Instance,[{Key,Data}]}]
2468%%  Output = [{Type,[{Key,Value}]}]
2469%% where Key in Output is one of ?interesting_allocator_info
2470%% and Value is the sum over all allocator instances of each type.
2471sort_allocator_types([{Name,Data}|Allocators],Acc,DoTotal) ->
2472    Type =
2473	case string:lexemes(Name,"[]") of
2474	    [T,_Id] -> T;
2475	    [Name] -> Name;
2476            Other -> Other
2477	end,
2478    TypeData = proplists:get_value(Type,Acc,[]),
2479    {NewTypeData,NewDoTotal} = sort_type_data(Type,Data,TypeData,DoTotal),
2480    NewAcc = lists:keystore(Type,1,Acc,{Type,NewTypeData}),
2481    sort_allocator_types(Allocators,NewAcc,NewDoTotal);
2482sort_allocator_types([],Acc,DoTotal) ->
2483    {Acc,DoTotal}.
2484
2485sort_type_data(Type,[?opt_e_false|Data],Acc,_) when Type=/=?sbmbc_alloc->
2486    sort_type_data(Type,Data,Acc,false);
2487sort_type_data(Type,[{Key,Val0}|Data],Acc,DoTotal) ->
2488    case lists:member(Key,?interesting_allocator_info) of
2489	true ->
2490	    Val = list_to_integer(hd(Val0)),
2491	    sort_type_data(Type,Data,update_value(Key,Val,Acc),DoTotal);
2492	false ->
2493	    sort_type_data(Type,Data,Acc,DoTotal)
2494    end;
2495sort_type_data(_Type,[],Acc,DoTotal) ->
2496    {Acc,DoTotal}.
2497
2498%% Sum up allocator data in total blocks- and carriers size for all
2499%% allocators and per type of allocator.
2500%% Input  = Output from sort_allocator_types/3
2501%% Output = {[{"mseg_alloc",[{"segments_size",Value}]},
2502%%            {Type,[{blocks_size,Value},
2503%%                   {carriers_size,Value},
2504%%                   {mseg_carriers_size,Value}]},
2505%%            ...],
2506%%           {TotalBlocksSize,TotalCarriersSize}}
2507sum_allocator_data(AllocData,false) ->
2508    sum_allocator_data(AllocData,[],false);
2509sum_allocator_data(AllocData,true) ->
2510    sum_allocator_data(AllocData,[],{0,0}).
2511
2512sum_allocator_data([{_Type,[]}|AllocData],TypeAcc,Total) ->
2513    sum_allocator_data(AllocData,TypeAcc,Total);
2514sum_allocator_data([{Type,Data}|AllocData],TypeAcc,Total) ->
2515    {TypeSum,NewTotal} = sum_type_data(Data,[],Total),
2516    sum_allocator_data(AllocData,[{Type,TypeSum}|TypeAcc],NewTotal);
2517sum_allocator_data([],TypeAcc,Total) ->
2518    {TypeAcc,Total}.
2519
2520sum_type_data([{Key,Value}|Data],TypeAcc,Total) ->
2521    NewTotal =
2522	case Total of
2523	    false ->
2524		false;
2525	    {TotalBS,TotalCS} ->
2526		case lists:member(Key,?total_blocks_size) of
2527		    true ->
2528			{TotalBS+Value,TotalCS};
2529		    false ->
2530			case lists:member(Key,?total_carriers_size) of
2531			    true ->
2532				{TotalBS,TotalCS+Value};
2533			    false ->
2534				{TotalBS,TotalCS}
2535			end
2536		end
2537	end,
2538    NewTypeAcc =
2539	case lists:member(Key,?type_blocks_size) of
2540	    true ->
2541		update_value(blocks_size,Value,TypeAcc);
2542	    false ->
2543		case lists:member(Key,?type_carriers_size) of
2544		    true ->
2545			update_value(carriers_size,Value,TypeAcc);
2546		    false ->
2547			case lists:member(Key,?type_mseg_carriers_size) of
2548			    true ->
2549				update_value(mseg_carriers_size,Value,TypeAcc);
2550			    false ->
2551				%% "segments_size" for "mseg_alloc"
2552				update_value(Key,Value,TypeAcc)
2553			end
2554		end
2555	end,
2556    sum_type_data(Data,NewTypeAcc,NewTotal);
2557sum_type_data([],TypeAcc,Total) ->
2558    {TypeAcc,Total}.
2559
2560update_value(Key,Value,Acc) ->
2561    case lists:keytake(Key,1,Acc) of
2562	false ->
2563	    [{Key,Value}|Acc];
2564	{value,{Key,Old},Acc1} ->
2565	    [{Key,Old+Value}|Acc1]
2566    end.
2567
2568%%-----------------------------------------------------------------
2569%% Page with hash table information
2570hash_tables(File) ->
2571    case lookup_index(?hash_table) of
2572	[] ->
2573	    [];
2574	AllHashTables ->
2575	    Fd = open(File),
2576	    R = lists:map(fun({Name,Start}) ->
2577				  get_hashtableinfo(Fd,Name,Start)
2578			  end,
2579			  AllHashTables),
2580	    close(Fd),
2581	    R
2582    end.
2583
2584get_hashtableinfo(Fd,Name,Start) ->
2585    pos_bof(Fd,Start),
2586    get_hashtableinfo1(Fd,#hash_table{name=Name}).
2587
2588get_hashtableinfo1(Fd,HashTable) ->
2589    case line_head(Fd) of
2590	"size" ->
2591	    get_hashtableinfo1(Fd,HashTable#hash_table{size=bytes(Fd)});
2592	"used" ->
2593	    get_hashtableinfo1(Fd,HashTable#hash_table{used=bytes(Fd)});
2594	"objs" ->
2595	    get_hashtableinfo1(Fd,HashTable#hash_table{objs=bytes(Fd)});
2596	"depth" ->
2597	    get_hashtableinfo1(Fd,HashTable#hash_table{depth=bytes(Fd)});
2598    	"=" ++ _next_tag ->
2599	    HashTable;
2600	Other ->
2601	    unexpected(Fd,Other,"hash table information"),
2602	    HashTable
2603    end.
2604
2605%%-----------------------------------------------------------------
2606%% Page with index table information
2607index_tables(File) ->
2608    case lookup_index(?index_table) of
2609	[] ->
2610	    [];
2611	AllIndexTables ->
2612	    Fd = open(File),
2613	    R = lists:map(fun({Name,Start}) ->
2614				  get_indextableinfo(Fd,Name,Start)
2615			  end,
2616			  AllIndexTables),
2617	    close(Fd),
2618	    R
2619    end.
2620
2621get_indextableinfo(Fd,Name,Start) ->
2622    pos_bof(Fd,Start),
2623    get_indextableinfo1(Fd,#index_table{name=Name}).
2624
2625get_indextableinfo1(Fd,IndexTable) ->
2626    case line_head(Fd) of
2627	"size" ->
2628	    get_indextableinfo1(Fd,IndexTable#index_table{size=bytes(Fd)});
2629	"used" ->
2630	    get_indextableinfo1(Fd,IndexTable#index_table{used=bytes(Fd)});
2631	"limit" ->
2632	    get_indextableinfo1(Fd,IndexTable#index_table{limit=bytes(Fd)});
2633	"rate" ->
2634	    get_indextableinfo1(Fd,IndexTable#index_table{rate=bytes(Fd)});
2635	"entries" ->
2636	    get_indextableinfo1(Fd,IndexTable#index_table{entries=bytes(Fd)});
2637    	"=" ++ _next_tag ->
2638	    IndexTable;
2639	Other ->
2640	    unexpected(Fd,Other,"index table information"),
2641	    IndexTable
2642    end.
2643
2644
2645%%-----------------------------------------------------------------
2646%% Page with scheduler table information
2647schedulers(File) ->
2648    Fd = open(File),
2649
2650    Schds0 = case lookup_index(?scheduler) of
2651                 [] ->
2652                     [];
2653                 Normals ->
2654                     [{Normals, #sched{type=normal}}]
2655             end,
2656    Schds1 = case lookup_index(?dirty_cpu_scheduler) of
2657                 [] ->
2658                     Schds0;
2659                 DirtyCpus ->
2660                     [{DirtyCpus, get_dirty_runqueue(Fd, ?dirty_cpu_run_queue)}
2661                      | Schds0]
2662             end,
2663    Schds2 = case lookup_index(?dirty_io_scheduler) of
2664                 [] ->
2665                     Schds1;
2666                 DirtyIos ->
2667                     [{DirtyIos, get_dirty_runqueue(Fd, ?dirty_io_run_queue)}
2668                      | Schds1]
2669             end,
2670
2671    R = schedulers1(Fd, Schds2, []),
2672    close(Fd),
2673    R.
2674
2675schedulers1(_Fd, [], Acc) ->
2676    Acc;
2677schedulers1(Fd, [{Scheds,Sched0} | Tail], Acc0) ->
2678    Acc1 = lists:foldl(fun({Name,Start}, AccIn) ->
2679                               [get_schedulerinfo(Fd,Name,Start,Sched0) | AccIn]
2680                       end,
2681                       Acc0,
2682                       Scheds),
2683    schedulers1(Fd, Tail, Acc1).
2684
2685get_schedulerinfo(Fd,Name,Start,Sched0) ->
2686    pos_bof(Fd,Start),
2687    get_schedulerinfo1(Fd,Sched0#sched{name=list_to_integer(Name)}).
2688
2689sched_type(?dirty_cpu_run_queue) -> dirty_cpu;
2690sched_type(?dirty_io_run_queue) ->  dirty_io.
2691
2692get_schedulerinfo1(Fd, Sched) ->
2693    case get_schedulerinfo2(Fd, Sched) of
2694        {more, Sched2} ->
2695            get_schedulerinfo1(Fd, Sched2);
2696        {done, Sched2} ->
2697            Sched2
2698    end.
2699
2700get_schedulerinfo2(Fd, Sched=#sched{details=Ds}) ->
2701    case line_head(Fd) of
2702	"Current Process" ->
2703	    {more, Sched#sched{process=bytes(Fd, "None")}};
2704	"Current Port" ->
2705	    {more, Sched#sched{port=bytes(Fd, "None")}};
2706
2707	"Scheduler Sleep Info Flags" ->
2708	    {more, Sched#sched{details=Ds#{sleep_info=>bytes(Fd, "None")}}};
2709	"Scheduler Sleep Info Aux Work" ->
2710	    {more, Sched#sched{details=Ds#{sleep_aux=>bytes(Fd, "None")}}};
2711
2712	"Current Process State" ->
2713	    {more, Sched#sched{details=Ds#{currp_state=>bytes(Fd)}}};
2714	"Current Process Internal State" ->
2715	    {more, Sched#sched{details=Ds#{currp_int_state=>bytes(Fd)}}};
2716	"Current Process Program counter" ->
2717	    {more, Sched#sched{details=Ds#{currp_prg_cnt=>string(Fd)}}};
2718	"Current Process CP" ->
2719	    {more, Sched#sched{details=Ds#{currp_cp=>string(Fd)}}};
2720	"Current Process Limited Stack Trace" ->
2721	    %% If there shall be last in scheduler information block
2722	    {done, Sched#sched{details=get_limited_stack(Fd, 0, Ds)}};
2723
2724	"=" ++ _next_tag ->
2725            {done, Sched};
2726
2727	Other ->
2728            case Sched#sched.type of
2729                normal ->
2730                    get_runqueue_info2(Fd, Other, Sched);
2731                _ ->
2732                    unexpected(Fd,Other,"dirty scheduler information"),
2733                    {done, Sched}
2734            end
2735    end.
2736
2737get_dirty_runqueue(Fd, Tag) ->
2738    case lookup_index(Tag) of
2739        [{_, Start}] ->
2740            pos_bof(Fd,Start),
2741            get_runqueue_info1(Fd,#sched{type=sched_type(Tag)});
2742        [] ->
2743            #sched{}
2744    end.
2745
2746get_runqueue_info1(Fd, Sched) ->
2747    case get_runqueue_info2(Fd, line_head(Fd), Sched) of
2748        {more, Sched2} ->
2749            get_runqueue_info1(Fd, Sched2);
2750        {done, Sched2} ->
2751            Sched2
2752    end.
2753
2754get_runqueue_info2(Fd, LineHead, Sched=#sched{details=Ds}) ->
2755    case LineHead of
2756	"Run Queue Max Length" ->
2757	    RQMax = list_to_integer(bytes(Fd)),
2758	    RQ = RQMax + Sched#sched.run_q,
2759	    {more, Sched#sched{run_q=RQ, details=Ds#{runq_max=>RQMax}}};
2760	"Run Queue High Length" ->
2761	    RQHigh = list_to_integer(bytes(Fd)),
2762	    RQ = RQHigh + Sched#sched.run_q,
2763	    {more, Sched#sched{run_q=RQ, details=Ds#{runq_high=>RQHigh}}};
2764	"Run Queue Normal Length" ->
2765	    RQNorm = list_to_integer(bytes(Fd)),
2766	    RQ = RQNorm + Sched#sched.run_q,
2767	    {more, Sched#sched{run_q=RQ, details=Ds#{runq_norm=>RQNorm}}};
2768	"Run Queue Low Length" ->
2769	    RQLow = list_to_integer(bytes(Fd)),
2770	    RQ = RQLow + Sched#sched.run_q,
2771	    {more, Sched#sched{run_q=RQ, details=Ds#{runq_low=>RQLow}}};
2772	"Run Queue Port Length" ->
2773	    RQ = list_to_integer(bytes(Fd)),
2774	    {more, Sched#sched{port_q=RQ}};
2775
2776	"Run Queue Flags" ->
2777	    {more, Sched#sched{details=Ds#{runq_flags=>bytes(Fd, "None")}}};
2778
2779	"=" ++ _next_tag ->
2780            {done, Sched};
2781	Other ->
2782	    unexpected(Fd,Other,"scheduler information"),
2783	    {done, Sched}
2784    end.
2785
2786get_limited_stack(Fd, N, Ds) ->
2787    case string(Fd) of
2788	Addr = "0x" ++ _ ->
2789	    get_limited_stack(Fd, N+1, Ds#{{currp_stack, N} => Addr});
2790	"=" ++ _next_tag ->
2791	    Ds;
2792	Line ->
2793	    get_limited_stack(Fd, N+1, Ds#{{currp_stack, N} => Line})
2794    end.
2795
2796%%%-----------------------------------------------------------------
2797%%% Parse memory in crashdump version 0.1 and newer
2798%%%
2799parse_heap_term([$l|Line0], Addr, DecodeOpts, D0) ->	%Cons cell.
2800    {H,"|"++Line1,D1} = parse_term(Line0, DecodeOpts, D0),
2801    {T,Line,D2} = parse_term(Line1, DecodeOpts, D1),
2802    Term = [H|T],
2803    D = gb_trees:insert(Addr, Term, D2),
2804    {Term,Line,D};
2805parse_heap_term([$t|Line0], Addr, DecodeOpts, D) ->	%Tuple
2806    {N,":"++Line} = get_hex(Line0),
2807    parse_tuple(N, Line, Addr, DecodeOpts, D, []);
2808parse_heap_term([$F|Line0], Addr, _DecodeOpts, D0) ->	%Float
2809    {N,":"++Line1} = get_hex(Line0),
2810    {Chars,Line} = get_chars(N, Line1),
2811    Term = list_to_float(Chars),
2812    D = gb_trees:insert(Addr, Term, D0),
2813    {Term,Line,D};
2814parse_heap_term("B16#"++Line0, Addr, _DecodeOpts, D0) -> %Positive big number.
2815    {Term,Line} = get_hex(Line0),
2816    D = gb_trees:insert(Addr, Term, D0),
2817    {Term,Line,D};
2818parse_heap_term("B-16#"++Line0, Addr, _DecodeOpts, D0) -> %Negative big number
2819    {Term0,Line} = get_hex(Line0),
2820    Term = -Term0,
2821    D = gb_trees:insert(Addr, Term, D0),
2822    {Term,Line,D};
2823parse_heap_term("B"++Line0, Addr, _DecodeOpts, D0) ->	%Decimal big num
2824    case string:to_integer(Line0) of
2825	{Int,Line} when is_integer(Int) ->
2826	    D = gb_trees:insert(Addr, Int, D0),
2827	    {Int,Line,D}
2828    end;
2829parse_heap_term([$P|Line0], Addr, _DecodeOpts, D0) ->	% External Pid.
2830    {Pid0,Line} = get_id(Line0),
2831    Pid = ['#CDVPid'|Pid0],
2832    D = gb_trees:insert(Addr, Pid, D0),
2833    {Pid,Line,D};
2834parse_heap_term([$p|Line0], Addr, _DecodeOpts, D0) ->   % External Port.
2835    {Port0,Line} = get_id(Line0),
2836    Port = ['#CDVPort'|Port0],
2837    D = gb_trees:insert(Addr, Port, D0),
2838    {Port,Line,D};
2839parse_heap_term("E"++Line0, Addr, DecodeOpts, D0) ->	%Term encoded in external format.
2840    {Bin,Line} = get_binary(Line0, DecodeOpts),
2841    Term = binary_to_term(Bin),
2842    D = gb_trees:insert(Addr, Term, D0),
2843    {Term,Line,D};
2844parse_heap_term("Yh"++Line0, Addr, DecodeOpts, D0) ->	%Heap binary.
2845    {Term,Line} = get_binary(Line0, DecodeOpts),
2846    D = gb_trees:insert(Addr, Term, D0),
2847    {Term,Line,D};
2848parse_heap_term("Yc"++Line0, Addr, DecodeOpts, D0) ->	%Reference-counted binary.
2849    {Binp0,":"++Line1} = get_hex(Line0),
2850    {Offset,":"++Line2} = get_hex(Line1),
2851    {Sz,Line} = get_hex(Line2),
2852    Binp = Binp0 bor DecodeOpts#dec_opts.bin_addr_adj,
2853    case lookup_binary_index(Binp) of
2854        [{_,Start}] ->
2855            SymbolicBin = {'#CDVBin',Start},
2856            Term = cdvbin(Offset, Sz, SymbolicBin),
2857            D1 = gb_trees:insert(Addr, Term, D0),
2858            D = gb_trees:enter(Binp, SymbolicBin, D1),
2859            {Term,Line,D};
2860        [] ->
2861            Term = '#CDVNonexistingBinary',
2862            D1 = gb_trees:insert(Addr, Term, D0),
2863            D = gb_trees:enter(Binp, Term, D1),
2864            {Term,Line,D}
2865    end;
2866parse_heap_term("Ys"++Line0, Addr, DecodeOpts, D0) ->	%Sub binary.
2867    {Binp0,":"++Line1} = get_hex(Line0),
2868    {Offset,":"++Line2} = get_hex(Line1),
2869    {Sz,Line3} = get_hex(Line2),
2870    {Term,Line,D1} = deref_bin(Binp0, Offset, Sz, Line3, DecodeOpts, D0),
2871    D = gb_trees:insert(Addr, Term, D1),
2872    {Term,Line,D};
2873parse_heap_term("Mf"++Line0, Addr, DecodeOpts, D0) -> %Flatmap.
2874    {Size,":"++Line1} = get_hex(Line0),
2875    case parse_term(Line1, DecodeOpts, D0) of
2876        {Keys,":"++Line2,D1} when is_tuple(Keys) ->
2877            {Values,Line,D2} = parse_tuple(Size, Line2, Addr,DecodeOpts, D1, []),
2878            Pairs = zip_tuples(tuple_size(Keys), Keys, Values, []),
2879            Map = maps:from_list(Pairs),
2880            D = gb_trees:update(Addr, Map, D2),
2881            {Map,Line,D};
2882        {Incomplete,_Line,D1} ->
2883            D = gb_trees:insert(Addr, Incomplete, D1),
2884            {Incomplete,"",D}
2885    end;
2886parse_heap_term("Mh"++Line0, Addr, DecodeOpts, D0) -> %Head node in a hashmap.
2887    {MapSize,":"++Line1} = get_hex(Line0),
2888    {N,":"++Line2} = get_hex(Line1),
2889    {Nodes,Line,D1} = parse_tuple(N, Line2, Addr, DecodeOpts, D0, []),
2890    Map = maps:from_list(flatten_hashmap_nodes(Nodes)),
2891    MapSize = maps:size(Map),                   %Assertion.
2892    D = gb_trees:update(Addr, Map, D1),
2893    {Map,Line,D};
2894parse_heap_term("Mn"++Line0, Addr, DecodeOpts, D) -> %Interior node in a hashmap.
2895    {N,":"++Line} = get_hex(Line0),
2896    parse_tuple(N, Line, Addr, DecodeOpts, D, []).
2897
2898parse_tuple(0, Line, Addr, _, D0, Acc) ->
2899    Tuple = list_to_tuple(lists:reverse(Acc)),
2900    D = gb_trees:insert(Addr, Tuple, D0),
2901    {Tuple,Line,D};
2902parse_tuple(N, Line0, Addr, DecodeOpts, D0, Acc) ->
2903    case parse_term(Line0, DecodeOpts, D0) of
2904	{Term,[$,|Line],D} when N > 1 ->
2905	    parse_tuple(N-1, Line, Addr, DecodeOpts, D, [Term|Acc]);
2906	{Term,Line,D}->
2907	    parse_tuple(N-1, Line, Addr, DecodeOpts, D, [Term|Acc])
2908    end.
2909
2910zip_tuples(0, _T1, _T2, Acc) ->
2911    Acc;
2912zip_tuples(N, T1, T2, Acc) when N =< tuple_size(T1) ->
2913    zip_tuples(N-1, T1, T2, [{element(N, T1),element(N, T2)}|Acc]).
2914
2915flatten_hashmap_nodes(Tuple) ->
2916    flatten_hashmap_nodes_1(tuple_size(Tuple), Tuple, []).
2917
2918flatten_hashmap_nodes_1(0, _Tuple, Acc) ->
2919    Acc;
2920flatten_hashmap_nodes_1(N, Tuple0, Acc0) ->
2921    case element(N, Tuple0) of
2922        [K|V] ->
2923            flatten_hashmap_nodes_1(N-1, Tuple0, [{K,V}|Acc0]);
2924        Tuple when is_tuple(Tuple) ->
2925            Acc = flatten_hashmap_nodes_1(N-1, Tuple0, Acc0),
2926            flatten_hashmap_nodes_1(tuple_size(Tuple), Tuple, Acc)
2927    end.
2928
2929parse_term([$H|Line0], DecodeOpts, D) ->        %Pointer to heap term.
2930    {Ptr,Line} = get_hex(Line0),
2931    deref_ptr(Ptr, Line, DecodeOpts, D);
2932parse_term([$N|Line], _, D) ->			%[] (nil).
2933    {[],Line,D};
2934parse_term([$I|Line0], _, D) ->			%Small.
2935    {Int,Line} = string:to_integer(Line0),
2936    {Int,Line,D};
2937parse_term([$A|_]=Line, _, D) ->		%Atom.
2938    parse_atom(Line, D);
2939parse_term([$P|Line0], _, D) ->			%Pid.
2940    {Pid,Line} = get_id(Line0),
2941    {['#CDVPid'|Pid],Line,D};
2942parse_term([$p|Line0], _, D) ->			%Port.
2943    {Port,Line} = get_id(Line0),
2944    {['#CDVPort'|Port],Line,D};
2945parse_term([$S|Str0], _, D) ->			%Information string.
2946    Str1 = byte_list_to_string(Str0),
2947    Str = lists:reverse(skip_blanks(lists:reverse(Str1))),
2948    {Str,[],D};
2949parse_term([$D|Line0], DecodeOpts, D) ->                 %DistExternal
2950    try
2951	{AttabSize,":"++Line1} = get_hex(Line0),
2952	{Attab, "E"++Line2} = parse_atom_translation_table(AttabSize, Line1, []),
2953	{Bin,Line3} = get_binary(Line2, DecodeOpts),
2954	{try
2955	     erts_debug:dist_ext_to_term(Attab, Bin)
2956	 catch
2957	     error:_ -> '<invalid-distribution-message>'
2958	 end,
2959	 Line3,
2960	 D}
2961    catch
2962	error:_ ->
2963	    {'#CDVBadDistExt', skip_dist_ext(Line0), D}
2964    end.
2965
2966skip_dist_ext(Line) ->
2967    skip_dist_ext(lists:reverse(Line), []).
2968
2969skip_dist_ext([], SeqTraceToken) ->
2970    SeqTraceToken;
2971skip_dist_ext([$:| _], SeqTraceToken) ->
2972    [$:|SeqTraceToken];
2973skip_dist_ext([C|Cs], KeptCs) ->
2974    skip_dist_ext(Cs, [C|KeptCs]).
2975
2976parse_atom([$A|Line0], D) ->
2977    {N,":"++Line1} = get_hex(Line0),
2978    {Chars, Line} = get_chars(N, Line1),
2979    {binary_to_atom(list_to_binary(Chars),utf8), Line, D}.
2980
2981parse_atom_translation_table(0, Line0, As) ->
2982    {list_to_tuple(lists:reverse(As)), Line0};
2983parse_atom_translation_table(N, Line0, As) ->
2984    {A, Line1, _} = parse_atom(Line0, []),
2985    parse_atom_translation_table(N-1, Line1, [A|As]).
2986
2987
2988deref_ptr(Ptr, Line, DecodeOpts, D) ->
2989    Lookup0 = fun(D0) ->
2990                      gb_trees:lookup(Ptr, D0)
2991              end,
2992    Lookup = wrap_line_map(Ptr, Lookup0),
2993    do_deref_ptr(Lookup, Line, DecodeOpts, D).
2994
2995deref_bin(Binp0, Offset, Sz, Line, DecodeOpts, D) ->
2996    Binp = Binp0 bor DecodeOpts#dec_opts.bin_addr_adj,
2997    Lookup0 = fun(D0) ->
2998                      lookup_binary(Binp, Offset, Sz, D0)
2999              end,
3000    Lookup = wrap_line_map(Binp, Lookup0),
3001    do_deref_ptr(Lookup, Line, DecodeOpts, D).
3002
3003lookup_binary(Binp, Offset, Sz, D) ->
3004    case lookup_binary_index(Binp) of
3005        [{_,Start}] ->
3006            Term = cdvbin(Offset, Sz, {'#CDVBin',Start}),
3007            {value,Term};
3008        [] ->
3009            case gb_trees:lookup(Binp, D) of
3010                {value,<<_:Offset/bytes,Sub:Sz/bytes,_/bytes>>} ->
3011                    {value,Sub};
3012                {value,SymbolicBin} ->
3013                    {value,cdvbin(Offset, Sz, SymbolicBin)};
3014                none ->
3015                    none
3016            end
3017    end.
3018
3019wrap_line_map(Ptr, Lookup) ->
3020    wrap_line_map_1(get(line_map), Ptr, Lookup).
3021
3022wrap_line_map_1(#{}=LineMap, Ptr, Lookup) ->
3023    fun(D) ->
3024            case Lookup(D) of
3025                {value,_}=Res ->
3026                    Res;
3027                none ->
3028                    case LineMap of
3029                        #{Ptr:=Line} ->
3030                            {line,Ptr,Line};
3031                        #{} ->
3032                            none
3033                    end
3034            end
3035    end;
3036wrap_line_map_1(undefined, _Ptr, Lookup) ->
3037    Lookup.
3038
3039do_deref_ptr(Lookup, Line, DecodeOpts, D0) ->
3040    case Lookup(D0) of
3041	{value,Term} ->
3042	    {Term,Line,D0};
3043	none ->
3044            put(incomplete_heap, true),
3045            {'#CDVIncompleteHeap',Line,D0};
3046        {line,Addr,NewLine} ->
3047            D = parse_line(Addr, NewLine, DecodeOpts, D0),
3048            do_deref_ptr(Lookup, Line, DecodeOpts, D)
3049    end.
3050
3051get_hex(L) ->
3052    get_hex_1(L, 0).
3053
3054get_hex_1([H|T]=L, Acc) ->
3055    case get_hex_digit(H) of
3056	none -> {Acc,L};
3057	Digit -> get_hex_1(T, (Acc bsl 4) bor Digit)
3058    end;
3059get_hex_1([], Acc) -> {Acc,[]}.
3060
3061get_hex_digit(C) when $0 =< C, C =< $9 -> C-$0;
3062get_hex_digit(C) when $a =< C, C =< $f -> C-$a+10;
3063get_hex_digit(C) when $A =< C, C =< $F -> C-$A+10;
3064get_hex_digit(_) -> none.
3065
3066skip_blanks([$\s|T]) ->
3067    skip_blanks(T);
3068skip_blanks([$\r|T]) ->
3069    skip_blanks(T);
3070skip_blanks([$\n|T]) ->
3071    skip_blanks(T);
3072skip_blanks([$\t|T]) ->
3073    skip_blanks(T);
3074skip_blanks(T) -> T.
3075
3076get_chars(N, Line) ->
3077    get_chars(N, Line, []).
3078
3079get_chars(0, Line, Acc) ->
3080    {lists:reverse(Acc),Line};
3081get_chars(N, [H|T], Acc) ->
3082    get_chars(N-1, T, [H|Acc]).
3083
3084get_id(Line0) ->
3085    [$<|Line] = lists:dropwhile(fun($<) -> false; (_) -> true end,Line0),
3086    get_id(Line, [], []).
3087
3088get_id([$>|Line], Acc, Id) ->
3089    {lists:reverse(Id,[list_to_integer(lists:reverse(Acc))]),Line};
3090get_id([$.|Line], Acc, Id) ->
3091    get_id(Line,[],[list_to_integer(lists:reverse(Acc))|Id]);
3092get_id([H|T], Acc, Id) ->
3093    get_id(T, [H|Acc], Id).
3094
3095get_label(L) ->
3096    get_label(L, []).
3097
3098get_label([$:|Line], Acc) ->
3099    Label = lists:reverse(Acc),
3100    case get_hex(Label) of
3101	{Int,[]} ->
3102	    {Int,Line};
3103	_ ->
3104	    {list_to_atom(Label),Line}
3105    end;
3106get_label([H|T], Acc) ->
3107    get_label(T, [H|Acc]).
3108
3109get_binary(Line0,DecodeOpts) ->
3110    case get_hex(Line0) of
3111        {N,":"++Line} ->
3112            get_binary_1(N, Line, DecodeOpts);
3113        _  ->
3114           {'#CDVTruncatedBinary',[]}
3115    end.
3116
3117get_binary_1(N,Line,#dec_opts{base64=false}) ->
3118    get_binary_hex(N, Line, [], false);
3119get_binary_1(N,Line0,#dec_opts{base64=true}) ->
3120    NumBytes = ((N+2) div 3) * 4,
3121    {Base64,Line} = lists:split(NumBytes, Line0),
3122    Bin = get_binary_base64(list_to_binary(Base64), <<>>, false),
3123    {Bin,Line}.
3124
3125get_binary(Offset,Size,Line0,DecodeOpts) ->
3126    case get_hex(Line0) of
3127        {_N,":"++Line} ->
3128            get_binary_1(Offset,Size,Line,DecodeOpts);
3129        _ ->
3130            {'#CDVTruncatedBinary',[]}
3131    end.
3132
3133get_binary_1(Offset,Size,Line,#dec_opts{base64=false}) ->
3134    Progress = Size > ?binary_size_progress_limit,
3135    Progress andalso init_progress("Reading binary",Size),
3136    get_binary_hex(Size, lists:sublist(Line,(Offset*2)+1,Size*2), [],
3137                  Progress);
3138get_binary_1(StartOffset,Size,Line,#dec_opts{base64=true}) ->
3139    Progress = Size > ?binary_size_progress_limit,
3140    Progress andalso init_progress("Reading binary",Size),
3141    EndOffset = StartOffset + Size,
3142    StartByte = (StartOffset div 3) * 4,
3143    EndByte = ((EndOffset + 2) div 3) * 4,
3144    NumBytes = EndByte - StartByte,
3145    case list_to_binary(Line) of
3146        <<_:StartByte/bytes,Base64:NumBytes/bytes,_/bytes>> ->
3147            Bin0 = get_binary_base64(Base64, <<>>, Progress),
3148            Skip = StartOffset - (StartOffset div 3) * 3,
3149            <<_:Skip/bytes,Bin:Size/bytes,_/bytes>> = Bin0,
3150            {Bin,[]};
3151        _ ->
3152            {'#CDVTruncatedBinary',[]}
3153    end.
3154
3155get_binary_hex(0, Line, Acc, Progress) ->
3156    Progress andalso end_progress(),
3157    {list_to_binary(lists:reverse(Acc)),Line};
3158get_binary_hex(N, [A,B|Line], Acc, Progress) ->
3159    Byte = (get_hex_digit(A) bsl 4) bor get_hex_digit(B),
3160    Progress andalso update_progress(),
3161    get_binary_hex(N-1, Line, [Byte|Acc], Progress);
3162get_binary_hex(_N, [], _Acc, Progress) ->
3163    Progress andalso end_progress(),
3164    {'#CDVTruncatedBinary',[]}.
3165
3166get_binary_base64(<<Chunk0:?base64_chunk_size/bytes,T/bytes>>,
3167                  Acc0, Progress) ->
3168    Chunk = base64:decode(Chunk0),
3169    Acc = <<Acc0/binary,Chunk/binary>>,
3170    Progress andalso update_progress(?base64_chunk_size * 3 div 4),
3171    get_binary_base64(T, Acc, Progress);
3172get_binary_base64(Chunk0, Acc, Progress) ->
3173    case Progress of
3174        true ->
3175            update_progress(?base64_chunk_size * 3 div 4),
3176            end_progress();
3177        false ->
3178            ok
3179    end,
3180    Chunk = base64:decode(Chunk0),
3181    <<Acc/binary,Chunk/binary>>.
3182
3183cdvbin(Offset,Size,{'#CDVBin',Pos}) ->
3184    ['#CDVBin',Offset,Size,Pos];
3185cdvbin(Offset,Size,['#CDVBin',_,_,Pos]) ->
3186    ['#CDVBin',Offset,Size,Pos];
3187cdvbin(_,_,'#CDVTruncatedBinary') ->
3188    '#CDVTruncatedBinary';
3189cdvbin(_,_,'#CDVNonexistingBinary') ->
3190    '#CDVNonexistingBinary'.
3191
3192%%-----------------------------------------------------------------
3193%% Functions for accessing tables
3194reset_tables() ->
3195    ets:delete_all_objects(cdv_dump_index_table),
3196    ets:delete_all_objects(cdv_reg_proc_table),
3197    ets:delete_all_objects(cdv_binary_index_table),
3198    ets:delete_all_objects(cdv_heap_file_chars).
3199
3200insert_index(Tag,Id,Pos) ->
3201    ets:insert(cdv_dump_index_table,{{Tag,Pos},Id}).
3202
3203delete_index(Tag,Id) ->
3204    Ms = [{{{Tag,'$1'},Id},[],[true]}],
3205    ets:select_delete(cdv_dump_index_table, Ms).
3206
3207lookup_index({Tag,Id}) ->
3208    lookup_index(Tag,Id);
3209lookup_index(Tag) ->
3210    lookup_index(Tag,'$2').
3211lookup_index(Tag,Id) ->
3212    ets:select(cdv_dump_index_table,[{{{Tag,'$1'},Id},[],[{{Id,'$1'}}]}]).
3213
3214count_index(Tag) ->
3215    ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},'_'},[],[true]}]).
3216
3217insert_binary_index(Addr,Pos) ->
3218    ets:insert(cdv_binary_index_table,{Addr,Pos}).
3219
3220lookup_binary_index(Addr) ->
3221    ets:lookup(cdv_binary_index_table,Addr).
3222
3223
3224%%-----------------------------------------------------------------
3225%% Convert tags read from crashdump to atoms used as first part of key
3226%% in cdv_dump_index_table
3227tag_to_atom("abort") -> ?abort;
3228tag_to_atom("allocated_areas") -> ?allocated_areas;
3229tag_to_atom("allocator") -> ?allocator;
3230tag_to_atom("atoms") -> ?atoms;
3231tag_to_atom("binary") -> ?binary;
3232tag_to_atom("dirty_cpu_scheduler") -> ?dirty_cpu_scheduler;
3233tag_to_atom("dirty_cpu_run_queue") -> ?dirty_cpu_run_queue;
3234tag_to_atom("dirty_io_scheduler") -> ?dirty_io_scheduler;
3235tag_to_atom("dirty_io_run_queue") -> ?dirty_io_run_queue;
3236tag_to_atom("end") -> ?ende;
3237tag_to_atom("erl_crash_dump") -> ?erl_crash_dump;
3238tag_to_atom("ets") -> ?ets;
3239tag_to_atom("fun") -> ?fu;
3240tag_to_atom("hash_table") -> ?hash_table;
3241tag_to_atom("hidden_node") -> ?hidden_node;
3242tag_to_atom("index_table") -> ?index_table;
3243tag_to_atom("instr_data") -> ?instr_data;
3244tag_to_atom("internal_ets") -> ?internal_ets;
3245tag_to_atom("literals") -> ?literals;
3246tag_to_atom("loaded_modules") -> ?loaded_modules;
3247tag_to_atom("memory") -> ?memory;
3248tag_to_atom("mod") -> ?mod;
3249tag_to_atom("persistent_terms") -> ?persistent_terms;
3250tag_to_atom("no_distribution") -> ?no_distribution;
3251tag_to_atom("node") -> ?node;
3252tag_to_atom("not_connected") -> ?not_connected;
3253tag_to_atom("old_instr_data") -> ?old_instr_data;
3254tag_to_atom("port") -> ?port;
3255tag_to_atom("proc") -> ?proc;
3256tag_to_atom("proc_dictionary") -> ?proc_dictionary;
3257tag_to_atom("proc_heap") -> ?proc_heap;
3258tag_to_atom("proc_messages") -> ?proc_messages;
3259tag_to_atom("proc_stack") -> ?proc_stack;
3260tag_to_atom("scheduler") -> ?scheduler;
3261tag_to_atom("timer") -> ?timer;
3262tag_to_atom("visible_node") -> ?visible_node;
3263tag_to_atom(UnknownTag) ->
3264    io:format("WARNING: Found unexpected tag:~ts~n",[UnknownTag]),
3265    list_to_atom(UnknownTag).
3266
3267%%%-----------------------------------------------------------------
3268%%% Store last tag for use when truncated, and reason if aborted
3269put_last_tag(?abort,Reason,_Pos) ->
3270    %% Don't overwrite the real last tag, and don't return it either,
3271    %% since that would make the caller of this function believe that
3272    %% the tag was complete.
3273    put(truncated_reason,Reason);
3274put_last_tag(Tag,Id,Pos) ->
3275    put(last_tag,{{Tag,Id},Pos}).
3276
3277%%%-----------------------------------------------------------------
3278%%% Fetch next chunk from crashdump file
3279lookup_and_parse_index(File,What,ParseFun,Str) when is_list(File) ->
3280    Indices = lookup_index(What),
3281    Fun  = fun(Fd,{Id,Start}) ->
3282		   pos_bof(Fd,Start),
3283		   ParseFun(Fd,Id)
3284	   end,
3285    Report = "Processing " ++ Str,
3286    progress_pmap(Report,File,Fun,Indices).
3287
3288%%%-----------------------------------------------------------------
3289%%% Convert a record to a proplist
3290to_proplist(Fields,Record) ->
3291    Values = to_value_list(Record),
3292    lists:zip(Fields,Values).
3293
3294%%%-----------------------------------------------------------------
3295%%% Convert a record to a simple list of field values
3296to_value_list(Record) ->
3297    [_RecordName|Values] = tuple_to_list(Record),
3298    Values.
3299
3300%%%-----------------------------------------------------------------
3301%%% Map over List and report progress in percent.
3302%%% Report is the text to be presented in the progress dialog.
3303%%% Distribute the load over a number of processes, and File is opened
3304%%% on each process and passed to the Fun as first argument.
3305%%% I.e. Fun = fun(Fd,Item) -> ItemResult end.
3306progress_pmap(Report,File,Fun,List) ->
3307    NTot = length(List),
3308    NProcs = erlang:system_info(schedulers) * 2,
3309    NPerProc = (NTot div NProcs) + 1,
3310
3311    %% Worker processes send message to collector for each ReportInterval.
3312    ReportInterval = (NTot div 100) + 1,
3313
3314    %% Progress reporter on collector process reports 1 percent for
3315    %% each message from worker process.
3316    init_progress(Report,99),
3317
3318    Collector = self(),
3319    {[],Pids} =
3320	lists:foldl(
3321	  fun(_,{L,Ps}) ->
3322		  {L1,L2} = if length(L)>=NPerProc -> lists:split(NPerProc,L);
3323			       true -> {L,[]} % last chunk
3324			    end,
3325		  {P,_Ref} =
3326		      spawn_monitor(
3327			fun() ->
3328				progress_map(Collector,ReportInterval,File,Fun,L1)
3329			end),
3330		  {L2,[P|Ps]}
3331	  end,
3332	  {List,[]},
3333	  lists:seq(1,NProcs)),
3334    collect(Pids,[]).
3335
3336progress_map(Collector,ReportInterval,File,Fun,List) ->
3337    Fd = open(File),
3338    init_progress(ReportInterval, fun(_) -> Collector ! progress end, ok),
3339    progress_map(Fd,Fun,List,[]).
3340progress_map(Fd,Fun,[H|T],Acc) ->
3341    update_progress(),
3342    progress_map(Fd,Fun,T,[Fun(Fd,H)|Acc]);
3343progress_map(Fd,_Fun,[],Acc) ->
3344    close(Fd),
3345    exit({pmap_done,Acc}).
3346
3347collect([],Acc) ->
3348    end_progress(),
3349    lists:append(Acc);
3350collect(Pids,Acc) ->
3351    receive
3352	progress ->
3353	    update_progress(),
3354	    collect(Pids,Acc);
3355	{'DOWN', _Ref, process, Pid, {pmap_done,Result}} ->
3356	    collect(lists:delete(Pid,Pids),[Result|Acc]);
3357        {'DOWN', _Ref, process, Pid, _Error} ->
3358            Warning =
3359                "WARNING: an error occured while parsing data.\n" ++
3360                case get(truncated) of
3361                    true -> "This might be because the dump is truncated.\n";
3362                    false -> ""
3363                end,
3364            io:format(Warning),
3365            collect(lists:delete(Pid,Pids),Acc)
3366    end.
3367
3368%%%-----------------------------------------------------------------
3369%%% Help functions for progress reporting
3370
3371%% Set text in progress dialog and initialize the progress counter
3372init_progress(Report,N) ->
3373    observer_lib:report_progress({ok,Report}),
3374    Interval = (N div 100) + 1,
3375    Fun = fun(P0) -> P=P0+1,observer_lib:report_progress({ok,P}),P end,
3376    init_progress(Interval,Fun,0).
3377init_progress(Interval,Fun,Acc) ->
3378    put(progress,{Interval,Interval,Fun,Acc}),
3379    ok.
3380
3381%% Count progress and report on given interval
3382update_progress() ->
3383    update_progress(1).
3384update_progress(Processed) ->
3385    do_update_progress(get(progress),Processed).
3386
3387do_update_progress({Count,Interval,Fun,Acc},Processed) when Processed>Count ->
3388    do_update_progress({Interval,Interval,Fun,Fun(Acc)},Processed-Count);
3389do_update_progress({Count,Interval,Fun,Acc},Processed) ->
3390    put(progress,{Count-Processed,Interval,Fun,Acc}),
3391    ok.
3392
3393%% End progress reporting for this item
3394end_progress() ->
3395    end_progress({ok,100}).
3396end_progress(Report) ->
3397    observer_lib:report_progress(Report),
3398    erase(progress),
3399    ok.
3400