diff options
23 files changed, 560 insertions, 205 deletions
diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml index 139057adb7..4eeea87bad 100644 --- a/erts/doc/src/notes.xml +++ b/erts/doc/src/notes.xml @@ -624,7 +624,7 @@ marker="erts:erl"><c>erl</c></seealso> command.</p> <p> See <url - href="http://pcre.org/original/changelog.txt"><c>http://pcre.org/original/changelog.txt</c></url> + href="http://pcre.org/original/changelog.txt">http://pcre.org/original/changelog.txt</url> for information about changes made to PCRE between the versions 8.33 and 8.40.</p> <p> diff --git a/lib/observer/priv/bin/cdv b/lib/observer/priv/bin/cdv index d14fd47e41..2a509c16af 100755 --- a/lib/observer/priv/bin/cdv +++ b/lib/observer/priv/bin/cdv @@ -1,4 +1,4 @@ #!/bin/sh -erl -noinput -s crashdump_viewer script_start $@ +erl -env ERL_CRASH_DUMP_SECONDS 0 -noinput -s crashdump_viewer script_start $@ diff --git a/lib/observer/priv/bin/cdv.bat b/lib/observer/priv/bin/cdv.bat index 18136a30d6..fa87c08adf 100644 --- a/lib/observer/priv/bin/cdv.bat +++ b/lib/observer/priv/bin/cdv.bat @@ -1,2 +1,2 @@ @ECHO OFF -CALL werl -s crashdump_viewer script_start %* +CALL werl -env ERL_CRASH_DUMP_SECONDS 0 -s crashdump_viewer script_start %* diff --git a/lib/observer/src/cdv_detail_wx.erl b/lib/observer/src/cdv_detail_wx.erl index 4c26e447a6..f6d282638a 100644 --- a/lib/observer/src/cdv_detail_wx.erl +++ b/lib/observer/src/cdv_detail_wx.erl @@ -20,7 +20,7 @@ -behaviour(wx_object). --export([start_link/4]). +-export([start_link/5]). -export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, handle_call/3, handle_info/2]). @@ -39,27 +39,42 @@ -define(ID_NOTEBOOK, 604). %% Detail view -start_link(Id, Data, ParentFrame, Callback) -> - wx_object:start_link(?MODULE, [Id, Data, ParentFrame, Callback, self()], []). +start_link(Id, Data, ParentFrame, Callback, App) -> + wx_object:start_link(?MODULE,[Id,Data,ParentFrame,Callback,App,self()],[]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init([Id, Data, ParentFrame, Callback, Parent]) -> +init([Id, Data, ParentFrame, Callback, App, Parent]) -> + display_progress(ParentFrame,App), case Callback:get_details(Id, Data) of {ok,Details} -> - init(Id,ParentFrame,Callback,Parent,Details); + init(Id,ParentFrame,Callback,App,Parent,Details); {yes_no, Info, Fun} -> + destroy_progress(App), case observer_lib:display_yes_no_dialog(Info) of ?wxID_YES -> Fun(); ?wxID_NO -> ok end, {stop,normal}; {info,Info} -> + destroy_progress(App), observer_lib:display_info_dialog(ParentFrame,Info), {stop,normal} end. -init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) -> +%% Display progress bar only if the calling app is crashdump_viewer +display_progress(ParentFrame,cdv) -> + observer_lib:display_progress_dialog(ParentFrame, + "Crashdump Viewer", + "Reading data"); +display_progress(_,_) -> + ok. +destroy_progress(cdv) -> + observer_lib:destroy_progress_dialog(); +destroy_progress(_) -> + ok. + +init(Id,ParentFrame,Callback,App,Parent,{Title,Info,TW}) -> Frame=wxFrame:new(ParentFrame, ?wxID_ANY, [Title], [{style, ?wxDEFAULT_FRAME_STYLE}, {size, {850,600}}]), MenuBar = wxMenuBar:new(), @@ -88,6 +103,7 @@ init(Id,ParentFrame,Callback,Parent,{Title,Info,TW}) -> wxFrame:connect(Frame, close_window), wxMenu:connect(Frame, command_menu_selected), wxFrame:show(Frame), + destroy_progress(App), {Frame, #state{parent=Parent, id=Id, frame=Frame, diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl index 5158e95a65..4b43b6a840 100644 --- a/lib/observer/src/cdv_html_wx.erl +++ b/lib/observer/src/cdv_html_wx.erl @@ -52,8 +52,12 @@ init([ParentWin, HtmlText]) -> init(ParentWin, HtmlText, undefined, cdv). init(ParentWin, HtmlText, Tab, App) -> + %% If progress dialog is shown, remove it now - and sett cursor busy instead + observer_lib:destroy_progress_dialog(), + wx_misc:beginBusyCursor(), HtmlWin = observer_lib:html_window(ParentWin), wxHtmlWindow:setPage(HtmlWin,HtmlText), + wx_misc:endBusyCursor(), {HtmlWin, #state{panel=HtmlWin,expand_table=Tab,app=App}}. %%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -123,11 +127,12 @@ handle_event(Event, State) -> %%%----------------------------------------------------------------- %%% Internal -expand(Id,Callback,#state{expand_wins=Opened0}=State) -> +expand(Id,Callback,#state{expand_wins=Opened0, app=App}=State) -> Opened = case lists:keyfind(Id,1,Opened0) of false -> - EW = cdv_detail_wx:start_link(Id,[],State#state.panel,Callback), + EW = cdv_detail_wx:start_link(Id,[],State#state.panel, + Callback,App), wx_object:get_pid(EW) ! active, [{Id,EW}|Opened0]; {_,EW} -> diff --git a/lib/observer/src/cdv_term_cb.erl b/lib/observer/src/cdv_term_cb.erl index f206d7e4c9..bdcb13f22d 100644 --- a/lib/observer/src/cdv_term_cb.erl +++ b/lib/observer/src/cdv_term_cb.erl @@ -30,23 +30,31 @@ detail_pages() -> [{"Term", fun init_term_page/2}]. init_term_page(ParentWin, {Type, [Term, Tab]}) -> + observer_lib:report_progress({ok,"Expanding term"}), + observer_lib:report_progress({ok,start_pulse}), Expanded = expand(Term, true), BinSaved = expand(Term, Tab), + observer_lib:report_progress({ok,stop_pulse}), cdv_multi_wx:start_link( ParentWin, [{"Format \~p",cdv_html_wx,{Type, format_term_fun("~p",BinSaved,Tab)}}, {"Format \~tp",cdv_html_wx,{Type,format_term_fun("~tp",BinSaved,Tab)}}, {"Format \~w",cdv_html_wx,{Type,format_term_fun("~w",BinSaved,Tab)}}, + {"Format \~tw",cdv_html_wx,{Type,format_term_fun("~tw",BinSaved,Tab)}}, {"Format \~s",cdv_html_wx,{Type,format_term_fun("~s",Expanded,Tab)}}, {"Format \~ts",cdv_html_wx,{Type,format_term_fun("~ts",Expanded,Tab)}}]). format_term_fun(Format,Term,Tab) -> fun() -> + observer_lib:report_progress({ok,"Formatting term"}), + observer_lib:report_progress({ok,start_pulse}), try io_lib:format(Format,[Term]) of Str -> {expand, plain_html(Str), Tab} catch error:badarg -> Warning = "This term can not be formatted with " ++ Format, observer_html_lib:warning(Warning) + after + observer_lib:report_progress({ok,stop_pulse}) end end. diff --git a/lib/observer/src/cdv_virtual_list_wx.erl b/lib/observer/src/cdv_virtual_list_wx.erl index f3daae8e4d..33c0c880b1 100644 --- a/lib/observer/src/cdv_virtual_list_wx.erl +++ b/lib/observer/src/cdv_virtual_list_wx.erl @@ -174,7 +174,7 @@ do_start_detail_win(Id, #state{panel=Panel,detail_wins=Opened, case lists:keyfind(Id, 1, Opened) of false -> Data = call(Holder, {get_data, self(), Id}), - case cdv_detail_wx:start_link(Id, Data, Panel, Callback) of + case cdv_detail_wx:start_link(Id, Data, Panel, Callback, cdv) of {error, _} -> Opened; IW -> [{Id, IW} | Opened] end; diff --git a/lib/observer/src/cdv_wx.erl b/lib/observer/src/cdv_wx.erl index 898f39ded2..c3f36cd689 100644 --- a/lib/observer/src/cdv_wx.erl +++ b/lib/observer/src/cdv_wx.erl @@ -412,7 +412,16 @@ load_dump(Frame,undefined) -> error end; load_dump(Frame,FileName) -> - ok = observer_lib:display_progress_dialog("Crashdump Viewer", + case maybe_warn_filename(FileName) of + continue -> + do_load_dump(Frame,FileName); + stop -> + error + end. + +do_load_dump(Frame,FileName) -> + ok = observer_lib:display_progress_dialog(wx:null(), + "Crashdump Viewer", "Loading crashdump"), crashdump_viewer:read_file(FileName), case observer_lib:wait_for_progress() of @@ -431,6 +440,33 @@ load_dump(Frame,FileName) -> error end. +maybe_warn_filename(FileName) -> + case os:getenv("ERL_CRASH_DUMP_SECONDS")=="0" orelse + os:getenv("ERL_CRASH_DUMP_BYTES")=="0" of + true -> + continue; + false -> + DumpName = case os:getenv("ERL_CRASH_DUMP") of + false -> filename:absname("erl_crash.dump"); + Name -> filename:absname(Name) + end, + case filename:absname(FileName) of + DumpName -> + Warning = + "WARNING: the current crashdump might be overwritten " + "if the crashdump_viewer node crashes.\n\n" + "Renaming the file before inspecting it will " + "remove the problem.\n\n" + "Do you want to continue?", + case observer_lib:display_yes_no_dialog(Warning) of + ?wxID_YES -> continue; + ?wxID_NO -> stop + end; + _ -> + continue + end + end. + %%%----------------------------------------------------------------- %%% Find help document (HTML files) get_help_doc(HelpId) -> diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl index fd8b3b9e67..95e12887cd 100644 --- a/lib/observer/src/crashdump_viewer.erl +++ b/lib/observer/src/crashdump_viewer.erl @@ -36,7 +36,6 @@ %% file: The name of the crashdump currently viewed. %% dump_vsn: The version number of the crashdump %% wordsize: 4 | 8, the number of bytes in a word. -%% binaries: a gb_tree containing binaries or links to binaries in the dump %% %% User API @@ -87,6 +86,7 @@ -define(max_line_size,100). % max number of bytes (i.e. characters) the % line_head/1 function can return -define(not_available,"N/A"). +-define(binary_size_progress_limit,10000). %% All possible tags - use macros in order to avoid misspelling in the code @@ -106,6 +106,8 @@ -define(internal_ets,internal_ets). -define(loaded_modules,loaded_modules). -define(memory,memory). +-define(memory_map,memory_map). +-define(memory_status,memory_status). -define(mod,mod). -define(no_distribution,no_distribution). -define(node,node). @@ -122,7 +124,7 @@ -define(visible_node,visible_node). --record(state,{file,dump_vsn,wordsize=4,num_atoms="unknown",binaries}). +-record(state,{file,dump_vsn,wordsize=4,num_atoms="unknown"}). %%%----------------------------------------------------------------- %%% Debugging @@ -203,7 +205,8 @@ do_script_start(StartFun) -> io:format("\ncdv crash: ~tp\n",[Reason]) end; _ -> - io:format("\ncdv crash: ~p\n",[unknown_reason]) + %io:format("\ncdv crash: ~p\n",[unknown_reason]) + ok end; Error -> io:format("\ncdv start failed: ~tp\n",[Error]) @@ -305,6 +308,8 @@ expand_binary(Pos) -> init([]) -> ets:new(cdv_dump_index_table,[ordered_set,named_table,public]), ets:new(cdv_reg_proc_table,[ordered_set,named_table,public]), + ets:new(cdv_binary_index_table,[ordered_set,named_table,public]), + ets:new(cdv_heap_file_chars,[ordered_set,named_table,public]), {ok, #state{}}. %%-------------------------------------------------------------------- @@ -348,9 +353,9 @@ handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) -> Procs = procs_summary(File,WS), {reply,{ok,Procs,TW},State}; handle_call({proc_details,Pid},_From, - State=#state{file=File,wordsize=WS,dump_vsn=DumpVsn,binaries=B})-> + State=#state{file=File,wordsize=WS,dump_vsn=DumpVsn})-> Reply = - case get_proc_details(File,Pid,WS,DumpVsn,B) of + case get_proc_details(File,Pid,WS,DumpVsn) of {ok,Proc,TW} -> {ok,Proc,TW}; Other -> @@ -462,9 +467,9 @@ handle_call(schedulers,_From,State=#state{file=File}) -> %%-------------------------------------------------------------------- handle_cast({read_file,File}, _State) -> case do_read_file(File) of - {ok,Binaries,DumpVsn} -> + {ok,DumpVsn} -> observer_lib:report_progress({ok,done}), - {noreply, #state{file=File,binaries=Binaries,dump_vsn=DumpVsn}}; + {noreply, #state{file=File,dump_vsn=DumpVsn}}; Error -> end_progress(Error), {noreply, #state{}} @@ -746,32 +751,6 @@ get_rest_of_line_1(Fd, <<>>, Acc) -> eof -> {eof,lists:reverse(Acc)} end. -get_lines_to_empty(Fd) -> - case get_chunk(Fd) of - {ok,Bin} -> - get_lines_to_empty(Fd,Bin,[],[]); - eof -> - [] - end. -get_lines_to_empty(Fd,<<$\n:8,Bin/binary>>,[],Lines) -> - put_chunk(Fd,Bin), - lists:reverse(Lines); -get_lines_to_empty(Fd,<<$\n:8,Bin/binary>>,Acc,Lines) -> - get_lines_to_empty(Fd,Bin,[],[byte_list_to_string(lists:reverse(Acc))|Lines]); -get_lines_to_empty(Fd,<<$\r:8,Bin/binary>>,Acc,Lines) -> - get_lines_to_empty(Fd,Bin,Acc,Lines); -get_lines_to_empty(Fd,<<$\s:8,Bin/binary>>,[],Lines) -> - get_lines_to_empty(Fd,Bin,[],Lines); -get_lines_to_empty(Fd,<<Char:8,Bin/binary>>,Acc,Lines) -> - get_lines_to_empty(Fd,Bin,[Char|Acc],Lines); -get_lines_to_empty(Fd,<<>>,Acc,Lines) -> - case get_chunk(Fd) of - {ok,Bin} -> - get_lines_to_empty(Fd,Bin,Acc,Lines); - eof -> - lists:reverse(Lines,[byte_list_to_string(lists:reverse(Acc))]) - end. - split(Str) -> split($ ,Str,[]). split(Char,Str) -> @@ -817,18 +796,17 @@ do_read_file(File) -> {Tag,Id,Rest,N1} = tag(Fd,TagAndRest,1), case Tag of ?erl_crash_dump -> - reset_index_table(), + reset_tables(), insert_index(Tag,Id,N1+1), put_last_tag(Tag,""), - indexify(Fd,Rest,N1), + DumpVsn = [list_to_integer(L) || + L<-string:tokens(Id,".")], + AddrAdj = get_bin_addr_adj(DumpVsn), + indexify(Fd,AddrAdj,Rest,N1), end_progress(), check_if_truncated(), - [{DumpVsn0,_}] = lookup_index(?erl_crash_dump), - DumpVsn = [list_to_integer(L) || - L<-string:tokens(DumpVsn0,".")], - Binaries = read_binaries(Fd,DumpVsn), close(Fd), - {ok,Binaries,DumpVsn}; + {ok,DumpVsn}; _Other -> R = io_lib:format( "~ts is not an Erlang crash dump~n", @@ -856,15 +834,31 @@ do_read_file(File) -> {error,R} end. -indexify(Fd,Bin,N) -> +indexify(Fd,AddrAdj,Bin,N) -> case binary:match(Bin,<<"\n=">>) of {Start,Len} -> Pos = Start+Len, <<_:Pos/binary,TagAndRest/binary>> = Bin, {Tag,Id,Rest,N1} = tag(Fd,TagAndRest,N+Pos), - insert_index(Tag,Id,N1+1), % +1 to get past newline - put_last_tag(Tag,Id), - indexify(Fd,Rest,N1); + NewPos = N1+1, % +1 to get past newline + case Tag of + ?binary -> + %% Binaries are stored in a separate table in + %% order to minimize lookup time. Key is the + %% translated address. + {HexAddr,_} = get_hex(Id), + Addr = HexAddr bor AddrAdj, + insert_binary_index(Addr,NewPos); + _ -> + insert_index(Tag,Id,NewPos) + end, + case put_last_tag(Tag,Id) of + {?proc_heap,LastId} -> + [{_,LastPos}] = lookup_index(?proc_heap,LastId), + ets:insert(cdv_heap_file_chars,{LastId,N+Start+1-LastPos}); + _ -> ok + end, + indexify(Fd,AddrAdj,Rest,N1); nomatch -> case progress_read(Fd) of {ok,Chunk0} when is_binary(Chunk0) -> @@ -875,7 +869,7 @@ indexify(Fd,Bin,N) -> _ -> {Chunk0,N+byte_size(Bin)} end, - indexify(Fd,Chunk,N1); + indexify(Fd,AddrAdj,Chunk,N1); eof -> eof end @@ -911,7 +905,11 @@ check_if_truncated() -> find_truncated_proc(TruncatedTag) end. -find_truncated_proc({?atoms,_Id}) -> +find_truncated_proc({Tag,_Id}) when Tag==?atoms; + Tag==?binary; + Tag==?instr_data; + Tag==?memory_status; + Tag==?memory_map -> put(truncated_proc,false); find_truncated_proc({Tag,Pid}) -> case is_proc_tag(Tag) of @@ -1060,14 +1058,14 @@ procs_summary(File,WS) -> %%----------------------------------------------------------------- %% Page with one process -get_proc_details(File,Pid,WS,DumpVsn,Binaries) -> +get_proc_details(File,Pid,WS,DumpVsn) -> case lookup_index(?proc,Pid) of [{_,Start}] -> Fd = open(File), {{Stack,MsgQ,Dict},TW} = case truncated_warning([{?proc,Pid}]) of [] -> - {expand_memory(Fd,Pid,DumpVsn,Binaries),[]}; + {expand_memory(Fd,Pid,DumpVsn),[]}; TW0 -> {{[],[],[]},TW0} end, @@ -1176,7 +1174,7 @@ all_procinfo(Fd,Fun,Proc,WS,LineHead) -> get_procinfo(Fd,Fun,Proc#proc{old_heap_end=bytes(Fd)},WS); %% - END - moved from get_procinfo - "Last calls" -> - get_procinfo(Fd,Fun,Proc#proc{last_calls=get_lines_to_empty(Fd)},WS); + get_procinfo(Fd,Fun,Proc#proc{last_calls=get_last_calls(Fd)},WS); "Link list" -> {Links,Monitors,MonitoredBy} = parse_link_list(bytes(Fd),[],[],[]), get_procinfo(Fd,Fun,Proc#proc{links=Links, @@ -1200,6 +1198,66 @@ all_procinfo(Fd,Fun,Proc,WS,LineHead) -> get_procinfo(Fd,Fun,Proc,WS) end. +%% The end of the 'Last calls' section is meant to be an empty line, +%% but in some cases this is not the case, so we also need to look for +%% the next heading which currently (OTP-20.1) can be "Link list: ", +%% "Dictionary: " or "Reductions: ". We do this by looking for ": " +%% and when found, pushing the heading back into the saved chunk. +%% +%% Note that the 'Last calls' section is only present if the +%% 'save_calls' process flag is set. +get_last_calls(Fd) -> + case get_chunk(Fd) of + {ok,Bin} -> + get_last_calls(Fd,Bin,[],[]); + eof -> + [] + end. +get_last_calls(Fd,<<$\n:8,Bin/binary>>,[],Lines) -> + %% Empty line - we're done + put_chunk(Fd,Bin), + lists:reverse(Lines); +get_last_calls(Fd,<<$::8>>,Acc,Lines) -> + case get_chunk(Fd) of + {ok,Bin} -> + %% Could be a colon followed by a space - see next function clause + get_last_calls(Fd,<<$::8,Bin/binary>>,Acc,Lines); + eof -> + %% Truncated here - either we've got the next heading, or + %% it was truncated in a last call function, in which case + %% we note that it was truncated + case byte_list_to_string(lists:reverse(Acc)) of + NextHeading when NextHeading=="Link list"; + NextHeading=="Dictionary"; + NextHeading=="Reductions" -> + put_chunk(Fd,list_to_binary(NextHeading++":")), + lists:reverse(Lines); + LastCallFunction-> + lists:reverse(Lines,[LastCallFunction++":...(truncated)"]) + end + end; +get_last_calls(Fd,<<$\::8,$\s:8,Bin/binary>>,Acc,Lines) -> + %% ": " - means we have the next heading in Acc - save it back + %% into the chunk and return the lines we've found + HeadingBin = list_to_binary(lists:reverse(Acc,[$:])), + put_chunk(Fd,<<HeadingBin/binary,Bin/binary>>), + lists:reverse(Lines); +get_last_calls(Fd,<<$\n:8,Bin/binary>>,Acc,Lines) -> + get_last_calls(Fd,Bin,[],[byte_list_to_string(lists:reverse(Acc))|Lines]); +get_last_calls(Fd,<<$\r:8,Bin/binary>>,Acc,Lines) -> + get_last_calls(Fd,Bin,Acc,Lines); +get_last_calls(Fd,<<$\s:8,Bin/binary>>,[],Lines) -> + get_last_calls(Fd,Bin,[],Lines); +get_last_calls(Fd,<<Char:8,Bin/binary>>,Acc,Lines) -> + get_last_calls(Fd,Bin,[Char|Acc],Lines); +get_last_calls(Fd,<<>>,Acc,Lines) -> + case get_chunk(Fd) of + {ok,Bin} -> + get_last_calls(Fd,Bin,Acc,Lines); + eof -> + lists:reverse(Lines,[byte_list_to_string(lists:reverse(Acc))]) + end. + parse_link_list([SB|Str],Links,Monitors,MonitoredBy) when SB==$[; SB==$] -> parse_link_list(Str,Links,Monitors,MonitoredBy); parse_link_list("#Port"++_=Str,Links,Monitors,MonitoredBy) -> @@ -1325,10 +1383,10 @@ maybe_other_node2(Channel) -> end. -expand_memory(Fd,Pid,DumpVsn,Binaries) -> +expand_memory(Fd,Pid,DumpVsn) -> BinAddrAdj = get_bin_addr_adj(DumpVsn), put(fd,Fd), - Dict = read_heap(Fd,Pid,BinAddrAdj,Binaries), + Dict = read_heap(Fd,Pid,BinAddrAdj,gb_trees:empty()), Expanded = {read_stack_dump(Fd,Pid,BinAddrAdj,Dict), read_messages(Fd,Pid,BinAddrAdj,Dict), read_dictionary(Fd,Pid,BinAddrAdj,Dict)}, @@ -1346,25 +1404,6 @@ get_bin_addr_adj(_) -> 0. %%% -%%% Read binaries. -%%% -read_binaries(Fd,DumpVsn) -> - AllBinaries = lookup_index(?binary), - AddrAdj = get_bin_addr_adj(DumpVsn), - Fun = fun({Addr0,Pos},Dict0) -> - pos_bof(Fd,Pos), - {HexAddr,_} = get_hex(Addr0), - Addr = HexAddr bor AddrAdj, - Bin = - case line_head(Fd) of - {eof,_} -> '#CDVTruncatedBinary'; - _Size -> {'#CDVBin',Pos} - end, - gb_trees:enter(Addr,Bin,Dict0) - end, - progress_foldl("Processing binaries",Fun,gb_trees:empty(),AllBinaries). - -%%% %%% Read top level section. %%% @@ -1454,6 +1493,8 @@ parse_dictionary(Line0, BinAddrAdj, D) -> read_heap(Fd,Pid,BinAddrAdj,Dict0) -> case lookup_index(?proc_heap,Pid) of [{_,Pos}] -> + [{_,Chars}] = ets:lookup(cdv_heap_file_chars,Pid), + init_progress("Reading process heap",Chars), pos_bof(Fd,Pos), read_heap(BinAddrAdj,Dict0); [] -> @@ -1464,13 +1505,16 @@ read_heap(BinAddrAdj,Dict0) -> %% This function is never called if the dump is truncated in {?proc_heap,Pid} case get(fd) of end_of_heap -> + end_progress(), Dict0; Fd -> case bytes(Fd) of "=" ++ _next_tag -> + end_progress(), put(fd, end_of_heap), Dict0; Line -> + update_progress(length(Line)+1), Dict = parse(Line,BinAddrAdj,Dict0), read_heap(BinAddrAdj,Dict) end @@ -2524,9 +2568,9 @@ parse_heap_term("Yc"++Line0, Addr, BinAddrAdj, D0) -> %Reference-counted binary. {Offset,":"++Line2} = get_hex(Line1), {Sz,Line} = get_hex(Line2), Binp = Binp0 bor BinAddrAdj, - Term = case gb_trees:lookup(Binp, D0) of - {value,Bin} -> cdvbin(Offset,Sz,Bin); - none -> '#CDVNonexistingBinary' + Term = case lookup_binary_index(Binp) of + [{_,Start}] -> cdvbin(Offset,Sz,{'#CDVBin',Start}); + [] -> '#CDVNonexistingBinary' end, D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; @@ -2535,15 +2579,14 @@ parse_heap_term("Ys"++Line0, Addr, BinAddrAdj, D0) -> %Sub binary. {Offset,":"++Line2} = get_hex(Line1), {Sz,Line} = get_hex(Line2), Binp = Binp0 bor BinAddrAdj, - Term = case gb_trees:lookup(Binp, D0) of - {value,Bin} -> cdvbin(Offset,Sz,Bin); - none when Binp0=/=Binp -> + Term = case lookup_binary_index(Binp) of + [{_,Start}] -> cdvbin(Offset,Sz,{'#CDVBin',Start}); + [] -> %% Might it be on the heap? - case gb_trees:lookup(Binp0, D0) of + case gb_trees:lookup(Binp, D0) of {value,Bin} -> cdvbin(Offset,Sz,Bin); none -> '#CDVNonexistingBinary' - end; - none -> '#CDVNonexistingBinary' + end end, D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}. @@ -2634,6 +2677,7 @@ deref_ptr(Ptr, Line, BinAddrAdj, D0) -> put(fd, end_of_heap), deref_ptr(Ptr, Line, BinAddrAdj, D0); L -> + update_progress(length(L)+1), D = parse(L, BinAddrAdj, D0), deref_ptr(Ptr, Line, BinAddrAdj, D) end @@ -2699,19 +2743,33 @@ get_label([H|T], Acc) -> get_label(T, [H|Acc]). get_binary(Line0) -> - {N,":"++Line} = get_hex(Line0), - do_get_binary(N, Line, []). + case get_hex(Line0) of + {N,":"++Line} -> + do_get_binary(N, Line, [], false); + _ -> + {'#CDVTruncatedBinary',[]} + end. get_binary(Offset,Size,Line0) -> - {_N,":"++Line} = get_hex(Line0), - do_get_binary(Size, lists:sublist(Line,(Offset*2)+1,Size*2), []). - -do_get_binary(0, Line, Acc) -> + case get_hex(Line0) of + {_N,":"++Line} -> + Progress = Size>?binary_size_progress_limit, + Progress andalso init_progress("Reading binary",Size), + do_get_binary(Size, lists:sublist(Line,(Offset*2)+1,Size*2), [], + Progress); + _ -> + {'#CDVTruncatedBinary',[]} + end. + +do_get_binary(0, Line, Acc, Progress) -> + Progress andalso end_progress(), {list_to_binary(lists:reverse(Acc)),Line}; -do_get_binary(N, [A,B|Line], Acc) -> +do_get_binary(N, [A,B|Line], Acc, Progress) -> Byte = (get_hex_digit(A) bsl 4) bor get_hex_digit(B), - do_get_binary(N-1, Line, [Byte|Acc]); -do_get_binary(_N, [], _Acc) -> + Progress andalso update_progress(), + do_get_binary(N-1, Line, [Byte|Acc], Progress); +do_get_binary(_N, [], _Acc, Progress) -> + Progress andalso end_progress(), {'#CDVTruncatedBinary',[]}. cdvbin(Offset,Size,{'#CDVBin',Pos}) -> @@ -2719,12 +2777,17 @@ cdvbin(Offset,Size,{'#CDVBin',Pos}) -> cdvbin(Offset,Size,['#CDVBin',_,_,Pos]) -> ['#CDVBin',Offset,Size,Pos]; cdvbin(_,_,'#CDVTruncatedBinary') -> - '#CDVTruncatedBinary'. + '#CDVTruncatedBinary'; +cdvbin(_,_,'#CDVNonexistingBinary') -> + '#CDVNonexistingBinary'. %%----------------------------------------------------------------- -%% Functions for accessing the cdv_dump_index_table -reset_index_table() -> - ets:delete_all_objects(cdv_dump_index_table). +%% Functions for accessing tables +reset_tables() -> + ets:delete_all_objects(cdv_dump_index_table), + ets:delete_all_objects(cdv_reg_proc_table), + ets:delete_all_objects(cdv_binary_index_table), + ets:delete_all_objects(cdv_heap_file_chars). insert_index(Tag,Id,Pos) -> ets:insert(cdv_dump_index_table,{{Tag,Pos},Id}). @@ -2739,6 +2802,11 @@ lookup_index(Tag,Id) -> count_index(Tag) -> ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},'_'},[],[true]}]). +insert_binary_index(Addr,Pos) -> + ets:insert(cdv_binary_index_table,{Addr,Pos}). + +lookup_binary_index(Addr) -> + ets:lookup(cdv_binary_index_table,Addr). %%----------------------------------------------------------------- %% Convert tags read from crashdump to atoms used as first part of key @@ -2809,23 +2877,6 @@ to_value_list(Record) -> Values. %%%----------------------------------------------------------------- -%%% Fold over List and report progress in percent. -%%% Report is the text to be presented in the progress dialog. -%%% Acc0 is the initial accumulator and will be passed to Fun as the -%%% second arguement, i.e. Fun = fun(Item,Acc) -> NewAcc end. -progress_foldl(Report,Fun,Acc0,List) -> - init_progress(Report, length(List)), - progress_foldl1(Fun,Acc0,List). - -progress_foldl1(Fun,Acc,[H|T]) -> - update_progress(), - progress_foldl1(Fun,Fun(H,Acc),T); -progress_foldl1(_Fun,Acc,[]) -> - end_progress(), - Acc. - - -%%%----------------------------------------------------------------- %%% Map over List and report progress in percent. %%% Report is the text to be presented in the progress dialog. %%% Distribute the load over a number of processes, and File is opened diff --git a/lib/observer/src/observer_html_lib.erl b/lib/observer/src/observer_html_lib.erl index 3dfcc42ada..a85808a472 100644 --- a/lib/observer/src/observer_html_lib.erl +++ b/lib/observer/src/observer_html_lib.erl @@ -337,17 +337,24 @@ href_proc_bin(From, T, Acc, LTB) -> Size = list_to_integer(SizeStr), PreviewSize = min(Size,10), Id = {list_to_integer(Offset),PreviewSize,list_to_integer(Pos)}, - {ok,PreviewBin} = crashdump_viewer:expand_binary(Id), - PreviewStr = preview_string(Size, PreviewBin), - if LTB -> - href("TARGET=\"expanded\"", - ["#Binary?offset="++Offset++ - "&size="++SizeStr++ - "&pos="++Pos], - PreviewStr); - true -> - PreviewStr - end; + case crashdump_viewer:expand_binary(Id) of + {ok, '#CDVTruncatedBinary'} -> + lists:flatten( + "<FONT COLOR=\"#FF0000\">" + "<<...(Truncated Binary)>>" + "</FONT>"); + {ok, PreviewBin} -> + PreviewStr = preview_string(Size, PreviewBin), + if LTB -> + href("TARGET=\"expanded\"", + ["#Binary?offset="++Offset++ + "&size="++SizeStr++ + "&pos="++Pos], + PreviewStr); + true -> + PreviewStr + end + end; [PreviewIntStr,SizeStr,Md5] when From =:= obs -> Size = list_to_integer(SizeStr), PreviewInt = list_to_integer(PreviewIntStr), diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 463fb5b8ef..29f4f9fabc 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -21,7 +21,7 @@ -export([get_wx_parent/1, display_info_dialog/2, display_yes_no_dialog/1, - display_progress_dialog/2, destroy_progress_dialog/0, + display_progress_dialog/3, destroy_progress_dialog/0, wait_for_progress/0, report_progress/1, user_term/3, user_term_multiline/3, interval_dialog/4, start_timer/1, start_timer/2, stop_timer/1, timer_config/1, @@ -40,6 +40,7 @@ -define(SINGLE_LINE_STYLE, ?wxBORDER_NONE bor ?wxTE_READONLY bor ?wxTE_RICH2). -define(MULTI_LINE_STYLE, ?SINGLE_LINE_STYLE bor ?wxTE_MULTILINE). +-define(pulse_timeout,50). get_wx_parent(Window) -> Parent = wxWindow:getParent(Window), @@ -688,11 +689,11 @@ create_status_bar(Panel) -> %%%----------------------------------------------------------------- %%% Progress dialog -define(progress_handler,cdv_progress_handler). -display_progress_dialog(Title,Str) -> +display_progress_dialog(Parent,Title,Str) -> Caller = self(), Env = wx:get_env(), spawn_link(fun() -> - progress_handler(Caller,Env,Title,Str) + progress_handler(Caller,Env,Parent,Title,Str) end), ok. @@ -716,31 +717,38 @@ report_progress(Progress) -> ok end. -progress_handler(Caller,Env,Title,Str) -> +progress_handler(Caller,Env,Parent,Title,Str) -> register(?progress_handler,self()), wx:set_env(Env), - PD = progress_dialog(Env,Title,Str), - try progress_loop(Title,PD,Caller) + PD = progress_dialog(Env,Parent,Title,Str), + try progress_loop(Title,PD,Caller,infinity) catch closed -> normal end. -progress_loop(Title,PD,Caller) -> +progress_loop(Title,PD,Caller,Pulse) -> receive {progress,{ok,done}} -> % to make wait_for_progress/0 return Caller ! continue, - progress_loop(Title,PD,Caller); + progress_loop(Title,PD,Caller,Pulse); + {progress,{ok,start_pulse}} -> + update_progress_pulse(PD), + progress_loop(Title,PD,Caller,?pulse_timeout); + {progress,{ok,stop_pulse}} -> + progress_loop(Title,PD,Caller,infinity); {progress,{ok,Percent}} when is_integer(Percent) -> update_progress(PD,Percent), - progress_loop(Title,PD,Caller); + progress_loop(Title,PD,Caller,Pulse); {progress,{ok,Msg}} -> update_progress_text(PD,Msg), - progress_loop(Title,PD,Caller); + progress_loop(Title,PD,Caller,Pulse); {progress,{error, Reason}} -> + {Dialog,_,_} = PD, + Parent = wxWindow:getParent(Dialog), finish_progress(PD), FailMsg = if is_list(Reason) -> Reason; true -> file:format_error(Reason) end, - display_info_dialog(PD,"Crashdump Viewer Error",FailMsg), + display_info_dialog(Parent,"Crashdump Viewer Error",FailMsg), Caller ! error, unregister(?progress_handler), unlink(Caller); @@ -748,28 +756,57 @@ progress_loop(Title,PD,Caller) -> finish_progress(PD), unregister(?progress_handler), unlink(Caller) + after Pulse -> + update_progress_pulse(PD), + progress_loop(Title,PD,Caller,?pulse_timeout) end. -progress_dialog(_Env,Title,Str) -> - PD = wxProgressDialog:new(Title,Str, - [{maximum,101}, - {style, - ?wxPD_APP_MODAL bor - ?wxPD_SMOOTH bor - ?wxPD_AUTO_HIDE}]), - wxProgressDialog:setMinSize(PD,{200,-1}), - PD. +progress_dialog(_Env,Parent,Title,Str) -> + progress_dialog_new(Parent,Title,Str). update_progress(PD,Value) -> - try wxProgressDialog:update(PD,Value) + try progress_dialog_update(PD,Value) catch _:_ -> throw(closed) %% Port or window have died end. update_progress_text(PD,Text) -> - try wxProgressDialog:update(PD,0,[{newmsg,Text}]) + try progress_dialog_update(PD,Text) + catch _:_ -> throw(closed) %% Port or window have died + end. +update_progress_pulse(PD) -> + try progress_dialog_pulse(PD) catch _:_ -> throw(closed) %% Port or window have died end. finish_progress(PD) -> - wxProgressDialog:destroy(PD). + try progress_dialog_update(PD,100) + catch _:_ -> ok + after progress_dialog_destroy(PD) + end. + +progress_dialog_new(Parent,Title,Str) -> + Dialog = wxDialog:new(Parent, ?wxID_ANY, Title, + [{style,?wxDEFAULT_DIALOG_STYLE}]), + Panel = wxPanel:new(Dialog), + Sizer = wxBoxSizer:new(?wxVERTICAL), + Message = wxStaticText:new(Panel, 1, Str), + Gauge = wxGauge:new(Panel, 2, 100, [{size, {170, -1}}, + {style, ?wxGA_HORIZONTAL}]), + SizerFlags = ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT bor ?wxTOP, + wxSizer:add(Sizer, Message, [{flag,SizerFlags},{border,15}]), + wxSizer:add(Sizer, Gauge, [{flag, SizerFlags bor ?wxBOTTOM},{border,15}]), + wxPanel:setSizer(Panel, Sizer), + wxSizer:setSizeHints(Sizer, Dialog), + wxDialog:show(Dialog), + {Dialog,Message,Gauge}. + +progress_dialog_update({_,_,Gauge},Value) when is_integer(Value) -> + wxGauge:setValue(Gauge,Value); +progress_dialog_update({_,Message,Gauge},Text) when is_list(Text) -> + wxGauge:setValue(Gauge,0), + wxStaticText:setLabel(Message,Text). +progress_dialog_pulse({_,_,Gauge}) -> + wxGauge:pulse(Gauge). +progress_dialog_destroy({Dialog,_,_}) -> + wxDialog:destroy(Dialog). make_obsbin(Bin,Tab) -> Size = byte_size(Bin), diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index 963def958b..fb02ae2728 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -151,7 +151,7 @@ handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Href}}}, Opened = case lists:keyfind(Id,1,Opened0) of false -> - Win = cdv_detail_wx:start_link(Id,[],Frame,Callback), + Win = cdv_detail_wx:start_link(Id,[],Frame,Callback,obs), [{Id,Win}|Opened0]; {_,Win} -> wxFrame:raise(Win), diff --git a/lib/observer/test/Makefile b/lib/observer/test/Makefile index fcb1b73911..a44e54fc52 100644 --- a/lib/observer/test/Makefile +++ b/lib/observer/test/Makefile @@ -47,7 +47,7 @@ RELSYSDIR = $(RELEASE_PATH)/observer_test # FLAGS # ---------------------------------------------------- ERL_MAKE_FLAGS += -ERL_COMPILE_FLAGS += +nowarn_export_all +ERL_COMPILE_FLAGS += +warnings_as_errors +nowarn_export_all EBIN = . diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl index 77cf086d4b..f9ac884743 100644 --- a/lib/observer/test/crashdump_viewer_SUITE.erl +++ b/lib/observer/test/crashdump_viewer_SUITE.erl @@ -76,7 +76,7 @@ end_per_testcase(Case, Config) -> end, ok. -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> []. all() -> [start_stop, @@ -364,6 +364,9 @@ special(File,Procs) -> crashdump_viewer:expand_binary({SOffset,SSize,SPos}), io:format(" expand binary ok",[]), + #proc{last_calls=LastCalls} = ProcDetails, + true = length(LastCalls) =< 4, + ['#CDVPid',X1,Y1,Z1] = proplists:get_value(ext_pid,Dict), ChannelStr1 = integer_to_list(X1), ExtPid = @@ -413,19 +416,90 @@ special(File,Procs) -> old_attrib=undefined, old_comp_info=undefined}=Mod2, ok; - %% ".strangemodname" -> - %% {ok,Mods,[]} = crashdump_viewer:loaded_modules(), - %% lookat_all_mods(Mods), - %% ok; - %% ".sort" -> - %% %% sort ports, atoms and modules ???? - %% ok; - %% ".trunc" -> - %% %% ???? - %% ok; - ".trunc.bytes" -> + ".trunc_bin1" -> + %% This is 'full_dist' truncated after the first + %% "=binary:" + %% i.e. no binary exist in the dump + [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs), + Pid = pid_to_list(Pid0), + {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid), + io:format(" process details ok",[]), + + #proc{dict=Dict} = ProcDetails, + + '#CDVNonexistingBinary' = proplists:get_value(bin,Dict), + '#CDVNonexistingBinary' = proplists:get_value(sub_bin,Dict), + + io:format(" nonexisting binaries ok",[]), + ok; + ".trunc_bin2" -> + %% This is 'full_dist' truncated after the first + %% "=binary:Addr\n + %% Size" + %% i.e. binaries are truncated + [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs), + Pid = pid_to_list(Pid0), + {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid), + io:format(" process details ok",[]), + + #proc{dict=Dict} = ProcDetails, + + ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict), + {ok,'#CDVTruncatedBinary'} = + crashdump_viewer:expand_binary({Offset,Size,Pos}), + ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict), + {ok,'#CDVTruncatedBinary'} = + crashdump_viewer:expand_binary({SOffset,SSize,SPos}), + + io:format(" expand truncated binary ok",[]), + ok; + ".trunc_bin3" -> + %% This is 'full_dist' truncated after the first + %% "=binary:Addr\n + %% Size:" + %% i.e. same as 'trunc_bin2', except the colon exists also + [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs), + Pid = pid_to_list(Pid0), + {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid), + io:format(" process details ok",[]), + + #proc{dict=Dict} = ProcDetails, + + ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict), + {ok,'#CDVTruncatedBinary'} = + crashdump_viewer:expand_binary({Offset,Size,Pos}), + ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict), + {ok,'#CDVTruncatedBinary'} = + crashdump_viewer:expand_binary({SOffset,SSize,SPos}), + + io:format(" expand truncated binary ok",[]), + ok; + ".trunc_bin4" -> + %% This is 'full_dist' truncated after the first + %% "=binary:Addr\n + %% Size:BinaryMissinOneByte" + %% i.e. the full binary is truncated, but the sub binary is complete + [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs), + Pid = pid_to_list(Pid0), + {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid), + io:format(" process details ok",[]), + + #proc{dict=Dict} = ProcDetails, + + ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict), + {ok,'#CDVTruncatedBinary'} = + crashdump_viewer:expand_binary({Offset,Size,Pos}), + io:format(" expand truncated binary ok",[]), + ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict), + {ok,<<_:SSize/binary>>} = + crashdump_viewer:expand_binary({SOffset,SSize,SPos}), + io:format(" expand complete sub binary ok",[]), + + ok; + ".trunc_bytes" -> {ok,_,[TW]} = crashdump_viewer:general_info(), {match,_} = re:run(TW,"CRASH DUMP SIZE LIMIT REACHED"), + io:format(" size limit information ok",[]), ok; ".unicode" -> #proc{pid=Pid0} = @@ -500,16 +574,45 @@ do_create_dumps(DataDir,Rel) -> current -> CD3 = dump_with_args(DataDir,Rel,"instr","+Mim true"), CD4 = dump_with_strange_module_name(DataDir,Rel,"strangemodname"), - Bytes = rand:uniform(300000) + 100, - CD5 = dump_with_args(DataDir,Rel,"trunc.bytes", + Tmp = dump_with_args(DataDir,Rel,"trunc_bytes",""), + {ok,#file_info{size=Max}} = file:read_file_info(Tmp), + ok = file:delete(Tmp), + Bytes = max(15,rand:uniform(Max)), + CD5 = dump_with_args(DataDir,Rel,"trunc_bytes", "-env ERL_CRASH_DUMP_BYTES " ++ integer_to_list(Bytes)), CD6 = dump_with_unicode_atoms(DataDir,Rel,"unicode"), - {[CD1,CD2,CD3,CD4,CD5,CD6], DosDump}; + TruncatedDumps = truncate_dump(CD1), + {[CD1,CD2,CD3,CD4,CD5,CD6|TruncatedDumps], DosDump}; _ -> {[CD1,CD2], DosDump} end. +truncate_dump(File) -> + {ok,Bin} = file:read_file(File), + BinTag = <<"\n=binary:">>, + Colon = <<":">>, + NewLine = case os:type() of + {win32,_} -> <<"\r\n">>; + _ -> <<"\n">> + end, + [StartBin,AfterTag] = binary:split(Bin,BinTag), + [AddrAndSize,BinaryAndRest] = binary:split(AfterTag,Colon), + [Binary,_Rest] = binary:split(BinaryAndRest,NewLine), + TruncSize = byte_size(Binary) - 2, + <<TruncBinary:TruncSize/binary,_/binary>> = Binary, + TruncName = filename:rootname(File) ++ ".trunc_bin", + write_trunc_files(TruncName,StartBin, + [BinTag,AddrAndSize,Colon,TruncBinary],1). + +write_trunc_files(TruncName0,Bin,[Part|Parts],N) -> + TruncName = TruncName0++integer_to_list(N), + Bin1 = <<Bin/binary,Part/binary>>, + ok = file:write_file(TruncName,Bin1), + [TruncName|write_trunc_files(TruncName0,Bin1,Parts,N+1)]; +write_trunc_files(_,_,[],_) -> + []. + %% Create a dump which has three visible nodes, one hidden and one %% not connected node, and with monitors and links between nodes. diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl index 41726b1521..0db2c1ea77 100644 --- a/lib/observer/test/observer_SUITE.erl +++ b/lib/observer/test/observer_SUITE.erl @@ -115,6 +115,7 @@ basic(doc) -> [""]; basic(Config) when is_list(Config) -> timer:send_after(100, "foobar"), %% Otherwise the timer server gets added to procs ProcsBefore = processes(), + ProcInfoBefore = [{P,process_info(P)} || P <- ProcsBefore], NumProcsBefore = length(ProcsBefore), ok = observer:start(), @@ -145,8 +146,10 @@ basic(Config) when is_list(Config) -> ProcsAfter = processes(), NumProcsAfter = length(ProcsAfter), if NumProcsAfter=/=NumProcsBefore -> + BeforeNotAfter = ProcsBefore -- ProcsAfter, ct:log("Before but not after:~n~p~n", - [[{P,process_info(P)} || P <- ProcsBefore -- ProcsAfter]]), + [[{P,I} || {P,I} <- ProcInfoBefore, + lists:member(P,BeforeNotAfter)]]), ct:log("After but not before:~n~p~n", [[{P,process_info(P)} || P <- ProcsAfter -- ProcsBefore]]), ct:fail("leaking processes"); @@ -304,10 +307,10 @@ table_win(Config) when is_list(Config) -> %% Test PR-1296/OTP-14151 %% Clicking a link to a port before the port tab has been activated the %% first time crashes observer. -port_win_when_tab_not_initiated(Config) -> +port_win_when_tab_not_initiated(_Config) -> {ok,Port} = gen_tcp:listen(0,[]), ok = observer:start(), - Notebook = setup_whitebox_testing(), + _Notebook = setup_whitebox_testing(), observer ! {open_link,erlang:port_to_list(Port)}, timer:sleep(1000), observer:stop(), diff --git a/lib/observer/test/ttb_SUITE.erl b/lib/observer/test/ttb_SUITE.erl index c06ec21f36..e8c2f9e37d 100644 --- a/lib/observer/test/ttb_SUITE.erl +++ b/lib/observer/test/ttb_SUITE.erl @@ -778,37 +778,37 @@ otp_4967_2(suite) -> otp_4967_2(doc) -> ["OTP-4967: Trace message sent to {Name, Node}"]; otp_4967_2(Config) when is_list(Config) -> - io:format("1: ~p",[now()]), + io:format("1: ~p",[erlang:timestamp()]), ?line Privdir = priv_dir(Config), - io:format("2: ~p",[now()]), + io:format("2: ~p",[erlang:timestamp()]), ?line File = filename:join(Privdir,"otp_4967"), - io:format("3: ~p",[now()]), + io:format("3: ~p",[erlang:timestamp()]), ?line S = self(), - io:format("4: ~p",[now()]), + io:format("4: ~p",[erlang:timestamp()]), ?line {ok,[Node]} = ttb:tracer(node(),[{file, File}, {handler,{fun myhandler/4, S}}]), - io:format("5: ~p",[now()]), + io:format("5: ~p",[erlang:timestamp()]), %% Test that delayed registration of a process works. receive after 200 -> ok end, ?line register(otp_4967,self()), - io:format("6: ~p",[now()]), + io:format("6: ~p",[erlang:timestamp()]), ?line {ok,[{S,[{matched,Node,1}]}]} = ttb:p(self(),s), - io:format("7: ~p",[now()]), + io:format("7: ~p",[erlang:timestamp()]), ?line {otp_4967,node()} ! heihopp, - io:format("8: ~p",[now()]), + io:format("8: ~p",[erlang:timestamp()]), ?line stopped = ttb:stop([format]), - io:format("9: ~p",[now()]), + io:format("9: ~p",[erlang:timestamp()]), ?line Msgs = flush(), - io:format("10: ~p",[now()]), + io:format("10: ~p",[erlang:timestamp()]), ?line io:format("Messages received: \n~p\n",[Msgs]), - io:format("11: ~p",[now()]), + io:format("11: ~p",[erlang:timestamp()]), ?line true = lists:member(heihopp,Msgs), % the heihopp message itself - io:format("13: ~p",[now()]), + io:format("13: ~p",[erlang:timestamp()]), ?line {value,{trace_ts,_,send,heihopp,{_,otp_4967,Node},{_,_,_}}} = lists:keysearch(heihopp,4,Msgs), % trace trace of the heihopp message - io:format("14: ~p",[now()]), + io:format("14: ~p",[erlang:timestamp()]), ?line end_of_trace = lists:last(Msgs), % end of the trace ok. diff --git a/lib/public_key/doc/src/public_key_records.xml b/lib/public_key/doc/src/public_key_records.xml index d34f3ed9a3..739310c88b 100644 --- a/lib/public_key/doc/src/public_key_records.xml +++ b/lib/public_key/doc/src/public_key_records.xml @@ -171,9 +171,9 @@ #'ECPrivateKey'{ version, % integer() privateKey, % binary() - parameters, % der_encoded() - {'EcpkParameters', #'ECParameters'{}} | - {'EcpkParameters', {namedCurve, oid()}} | - {'EcpkParameters', 'NULL'} % Inherited by CA + parameters, % {ecParameters, #'ECParameters'{}} | + % {namedCurve, Oid::tuple()} | + % {implicitlyCA, 'NULL'} publicKey % bitstring() }. diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index c2060c144c..9a61184f8a 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -71,7 +71,9 @@ -type rsa_private_key() :: #'RSAPrivateKey'{}. -type dsa_private_key() :: #'DSAPrivateKey'{}. -type dsa_public_key() :: {integer(), #'Dss-Parms'{}}. --type ec_public_key() :: {#'ECPoint'{},{namedCurve, Oid::tuple()} | #'ECParameters'{}}. +-type ecpk_parameters() :: {ecParameters, #'ECParameters'{}} | {namedCurve, Oid::tuple()}. +-type ecpk_parameters_api() :: ecpk_parameters() | #'ECParameters'{} | {namedCurve, Name::atom()}. +-type ec_public_key() :: {#'ECPoint'{}, ecpk_parameters_api()}. -type ec_private_key() :: #'ECPrivateKey'{}. -type der_encoded() :: binary(). -type pki_asn1_type() :: 'Certificate' | 'RSAPrivateKey' | 'RSAPublicKey' @@ -399,9 +401,7 @@ dh_gex_group(Min, N, Max, Groups) -> %%-------------------------------------------------------------------- -spec generate_key(#'DHParameter'{}) -> {Public::binary(), Private::binary()}; - ({namedCurve, Name ::oid()}) -> - #'ECPrivateKey'{}; - (#'ECParameters'{}) -> + (ecpk_parameters_api()) -> #'ECPrivateKey'{}; ({rsa, Size::pos_integer(), PubExp::pos_integer()}) -> #'RSAPrivateKey'{}. @@ -412,6 +412,8 @@ generate_key(#'DHParameter'{prime = P, base = G}) -> crypto:generate_key(dh, [P, G]); generate_key({namedCurve, _} = Params) -> ec_generate_key(Params); +generate_key({ecParameters, _} = Params) -> + ec_generate_key(Params); generate_key(#'ECParameters'{} = Params) -> ec_generate_key(Params); generate_key({rsa, ModulusSize, PublicExponent}) -> @@ -1286,22 +1288,34 @@ format_rsa_private_key(#'RSAPrivateKey'{modulus = N, publicExponent = E, is_integer(D) -> [E, N, D]. +-spec ec_generate_key(ecpk_parameters_api()) -> #'ECPrivateKey'{}. ec_generate_key(Params) -> Curve = ec_curve_spec(Params), Term = crypto:generate_key(ecdh, Curve), - ec_key(Term, Params). + NormParams = ec_normalize_params(Params), + ec_key(Term, NormParams). +-spec ec_normalize_params(ecpk_parameters_api()) -> ecpk_parameters(). +ec_normalize_params({namedCurve, Name}) when is_atom(Name) -> + {namedCurve, pubkey_cert_records:namedCurves(Name)}; +ec_normalize_params(#'ECParameters'{} = ECParams) -> + {ecParameters, ECParams}; +ec_normalize_params(Other) -> Other. + +-spec ec_curve_spec(ecpk_parameters_api()) -> term(). ec_curve_spec( #'ECParameters'{fieldID = FieldId, curve = PCurve, base = Base, order = Order, cofactor = CoFactor }) -> Field = {pubkey_cert_records:supportedCurvesTypes(FieldId#'FieldID'.fieldType), FieldId#'FieldID'.parameters}, Curve = {PCurve#'Curve'.a, PCurve#'Curve'.b, none}, {Field, Curve, Base, Order, CoFactor}; +ec_curve_spec({ecParameters, ECParams}) -> + ec_curve_spec(ECParams); ec_curve_spec({namedCurve, OID}) when is_tuple(OID), is_integer(element(1,OID)) -> ec_curve_spec({namedCurve, pubkey_cert_records:namedCurves(OID)}); ec_curve_spec({namedCurve, Name}) when is_atom(Name) -> crypto:ec_curve(Name). - +-spec ec_key({PubKey::term(), PrivateKey::term()}, Params::ecpk_parameters()) -> #'ECPrivateKey'{}. ec_key({PubKey, PrivateKey}, Params) -> #'ECPrivateKey'{version = 1, privateKey = PrivateKey, diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index 80895ce97c..4b1b771613 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -60,7 +60,8 @@ all() -> groups() -> [{pem_decode_encode, [], [dsa_pem, rsa_pem, ec_pem, encrypted_pem, - dh_pem, cert_pem, pkcs7_pem, pkcs10_pem]}, + dh_pem, cert_pem, pkcs7_pem, pkcs10_pem, ec_pem2, + ec_pem_encode_generated]}, {ssh_public_key_decode_encode, [], [ssh_rsa_public_key, ssh_dsa_public_key, ssh_ecdsa_public_key, ssh_rfc4716_rsa_comment, ssh_rfc4716_dsa_comment, @@ -101,6 +102,7 @@ init_per_testcase(TestCase, Config) -> ssh_hostkey_fingerprint_sha384 -> init_fingerprint_testcase([sha384], Config); ssh_hostkey_fingerprint_sha512 -> init_fingerprint_testcase([sha512], Config); ssh_hostkey_fingerprint_list -> init_fingerprint_testcase([sha,md5], Config); + ec_pem_encode_generated -> init_ec_pem_encode_generated(Config); _ -> init_common_per_testcase(Config) end. @@ -217,9 +219,47 @@ ec_pem(Config) when is_list(Config) -> true = check_entry_type(ECParams, 'EcpkParameters'), ECPrivKey = public_key:pem_entry_decode(Entry2), true = check_entry_type(ECPrivKey, 'ECPrivateKey'), + true = check_entry_type(ECPrivKey#'ECPrivateKey'.parameters, 'EcpkParameters'), ECPemNoEndNewLines = strip_superfluous_newlines(ECPrivPem), ECPemNoEndNewLines = strip_superfluous_newlines(public_key:pem_encode([Entry1, Entry2])). +ec_pem2() -> + [{doc, "EC key w/explicit params PEM-file decode/encode"}]. +ec_pem2(Config) when is_list(Config) -> + Datadir = proplists:get_value(data_dir, Config), + + %% Load key with explicit curve parameters. Generated with... + %% openssl ecparam -name secp521r1 -genkey -param_enc explicit -out ec_key2.pem + {ok, ECPrivPem} = file:read_file(filename:join(Datadir, "ec_key2.pem")), + [{'EcpkParameters', _, not_encrypted} = Entry1, + {'ECPrivateKey', _, not_encrypted} = Entry2] = public_key:pem_decode(ECPrivPem), + + ECParams = public_key:pem_entry_decode(Entry1), + true = check_entry_type(ECParams, 'EcpkParameters'), + ECPrivKey = public_key:pem_entry_decode(Entry2), + true = check_entry_type(ECPrivKey, 'ECPrivateKey'), + true = check_entry_type(ECPrivKey#'ECPrivateKey'.parameters, 'EcpkParameters'), + ECPemNoEndNewLines = strip_superfluous_newlines(ECPrivPem), + ECPemNoEndNewLines = strip_superfluous_newlines(public_key:pem_encode([Entry1, Entry2])). + + +init_ec_pem_encode_generated(Config) -> + case catch true = lists:member('secp384r1', crypto:ec_curves()) of + {'EXIT', _} -> {skip, {'secp384r1', not_supported}}; + _ -> init_common_per_testcase(Config) + end. + +ec_pem_encode_generated() -> + [{doc, "PEM-encode generated EC key"}]. +ec_pem_encode_generated(Config) -> + + Key1 = public_key:generate_key({namedCurve, 'secp384r1'}), + public_key:pem_entry_encode('ECPrivateKey', Key1), + + Key2 = public_key:generate_key({namedCurve, ?'secp384r1'}), + public_key:pem_entry_encode('ECPrivateKey', Key2). + + %%-------------------------------------------------------------------- encrypted_pem() -> @@ -1095,7 +1135,7 @@ check_entry_type(#'ECPrivateKey'{}, 'ECPrivateKey') -> true; check_entry_type({namedCurve, _}, 'EcpkParameters') -> true; -check_entry_type(#'ECParameters'{}, 'EcpkParameters') -> +check_entry_type({ecParameters, #'ECParameters'{}}, 'EcpkParameters') -> true; check_entry_type(_,_) -> false. diff --git a/lib/public_key/test/public_key_SUITE_data/ec_key2.pem b/lib/public_key/test/public_key_SUITE_data/ec_key2.pem new file mode 100644 index 0000000000..56b8169e86 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/ec_key2.pem @@ -0,0 +1,29 @@ +-----BEGIN EC PARAMETERS----- +MIIBwgIBATBNBgcqhkjOPQEBAkIB//////////////////////////////////// +//////////////////////////////////////////////////8wgZ4EQgH///// +//////////////////////////////////////////////////////////////// +/////////////////ARBUZU+uWGOHJofkpohoLaFQO6i2nJbmbMV87i0iZGO8Qnh +Vhk5Uex+k3sWUsC9O7G/BzVz34g9LDTx70Uf1GtQPwADFQDQnogAKRy4U5bMZxc5 +MoSqoNpkugSBhQQAxoWOBrcEBOnNnj7LZiOVtEKcZIE5BT+1Ifgor2BrTT26oUte +d+/nWSj+HcEnov+o3jNIs8GFakKb+X5+McLlvWYBGDkpaniaO8AEXIpftCx9G9mY +9URJV5tEaBevvRcnPmYsl+5ymV70JkDFULkBP60HYTU8cIaicsJAiL6Udp/RZlAC +QgH///////////////////////////////////////////pRhoeDvy+Wa3/MAUj3 +CaXQO7XJuImcR667b7cekThkCQIBAQ== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIICnQIBAQRCAVE6lUKRj5AE8Cw21A+iPWhXSg+XNuerrTyeFERY6AtOrRJ9mTQ3 +Av3xjiM3zhZy2KWnm62hvkvlGbZ7iDKcqg2GoIIBxjCCAcICAQEwTQYHKoZIzj0B +AQJCAf////////////////////////////////////////////////////////// +////////////////////////////MIGeBEIB//////////////////////////// +//////////////////////////////////////////////////////////wEQVGV +PrlhjhyaH5KaIaC2hUDuotpyW5mzFfO4tImRjvEJ4VYZOVHsfpN7FlLAvTuxvwc1 +c9+IPSw08e9FH9RrUD8AAxUA0J6IACkcuFOWzGcXOTKEqqDaZLoEgYUEAMaFjga3 +BATpzZ4+y2YjlbRCnGSBOQU/tSH4KK9ga009uqFLXnfv51ko/h3BJ6L/qN4zSLPB +hWpCm/l+fjHC5b1mARg5KWp4mjvABFyKX7QsfRvZmPVESVebRGgXr70XJz5mLJfu +cple9CZAxVC5AT+tB2E1PHCGonLCQIi+lHaf0WZQAkIB//////////////////// +///////////////////////6UYaHg78vlmt/zAFI9wml0Du1ybiJnEeuu2+3HpE4 +ZAkCAQGhgYkDgYYABAFLBJzBphlIJmSPuXzTDTnZpL7A0fnyqit9V3TBvaOcL6Iw +6m2TpXvNakxi8Flj0Ok4hdRt+YhawFs0bmzZCT8kfAFs7p55BPHk7FaMZaba77R8 +4V6MhUJSKLc0I/XQBtvoOgVlPJ0MPOndnIxPspCPll886yxG5kOMUAx3HjFg16RT +eA== +-----END EC PRIVATE KEY----- diff --git a/lib/sasl/src/sasl.appup.src b/lib/sasl/src/sasl.appup.src index 7f866507a0..dcb568c413 100644 --- a/lib/sasl/src/sasl.appup.src +++ b/lib/sasl/src/sasl.appup.src @@ -18,7 +18,11 @@ %% %CopyrightEnd% {"%VSN%", %% Up from - max one major revision back - [{<<"3\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-19.* + [{<<"3\\.0((\\.[0-3])(\\.[0-9]+)*)?">>,[restart_new_emulator]}, % OTP-19.* + {<<"3\\.0\\.[4-9](\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0* + {<<"3\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-20.1* %% Down to - max one major revision back - [{<<"3\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-19.* + [{<<"3\\.0((\\.[0-3])(\\.[0-9]+)*)?">>,[restart_new_emulator]}, % OTP-19.* + {<<"3\\.0\\.[4-9](\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0* + {<<"3\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-20.1* }. diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml index bdd5b39cd3..604d758db3 100644 --- a/lib/stdlib/doc/src/notes.xml +++ b/lib/stdlib/doc/src/notes.xml @@ -432,7 +432,7 @@ marker="erts:erl"><c>erl</c></seealso> command.</p> <p> See <url - href="http://pcre.org/original/changelog.txt"><c>http://pcre.org/original/changelog.txt</c></url> + href="http://pcre.org/original/changelog.txt">http://pcre.org/original/changelog.txt</url> for information about changes made to PCRE between the versions 8.33 and 8.40.</p> <p> diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src index 3100504a80..800c2c61f3 100644 --- a/lib/stdlib/src/stdlib.appup.src +++ b/lib/stdlib/src/stdlib.appup.src @@ -18,7 +18,9 @@ %% %CopyrightEnd% {"%VSN%", %% Up from - max one major revision back - [{<<"3\\.[0-3](\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-19.* + [{<<"3\\.[0-3](\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-19.* + {<<"3\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-20.* %% Down to - max one major revision back - [{<<"3\\.[0-3](\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-19.* + [{<<"3\\.[0-3](\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-19.* + {<<"3\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-20.* }. |