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 21-module(crashdump_viewer_SUITE). 22 23-include_lib("observer/src/crashdump_viewer.hrl"). 24 25%% Test functions 26-export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2, 27 start_stop/1,load_file/1,not_found_items/1, 28 non_existing/1,not_a_crashdump/1,old_crashdump/1,new_crashdump/1]). 29-export([init_per_suite/1, end_per_suite/1]). 30-export([init_per_testcase/2, end_per_testcase/2]). 31 32-include_lib("common_test/include/ct.hrl"). 33-include_lib("kernel/include/file.hrl"). 34 35-define(failed_file,"failed-cases.txt"). 36-define(helper_mod,crashdump_helper). 37 38%% -define(P(F), print(F)). 39-define(P(F, A), print(F, A)). 40 41 42init_per_testcase(start_stop, Config) -> 43 catch crashdump_viewer:stop(), 44 try 45 case os:type() of 46 {unix,darwin} -> 47 exit("Can not test on MacOSX"); 48 {unix, _} -> 49 io:format("DISPLAY ~s~n", [os:getenv("DISPLAY")]), 50 case ct:get_config(xserver, none) of 51 none -> ignore; 52 Server -> os:putenv("DISPLAY", Server) 53 end; 54 _ -> ignore 55 end, 56 wx:new(), 57 wx:destroy(), 58 Config 59 catch 60 _:undef -> 61 {skipped, "No wx compiled for this platform"}; 62 _:Reason -> 63 SkipReason = io_lib:format("Start wx failed: ~p", [Reason]), 64 {skipped, lists:flatten(SkipReason)} 65 end; 66init_per_testcase(_Case, Config) -> 67 catch crashdump_viewer:stop(), 68 Config. 69end_per_testcase(Case, Config) -> 70 case ?config(tc_status,Config) of 71 ok -> 72 ok; 73 _Fail -> 74 File = filename:join(?config(data_dir,Config),?failed_file), 75 {ok,Fd}=file:open(File,[append]), 76 file:write(Fd,io_lib:format("~w.~n",[Case])), 77 file:close(Fd) 78 end, 79 ok. 80 81suite() -> []. 82 83all() -> 84 [start_stop, 85 non_existing, 86 not_a_crashdump, 87 old_crashdump, 88 new_crashdump, 89 load_file, 90 not_found_items 91 ]. 92 93groups() -> 94 []. 95 96init_per_group(_GroupName, Config) -> 97 Config. 98 99end_per_group(_GroupName, Config) -> 100 Config. 101 102 103%% Create a lot of crashdumps which can be used in the testcases below 104init_per_suite(Config) when is_list(Config) -> 105 delete_saved(Config), 106 DataDir = ?config(data_dir,Config), 107 CurrVsn = list_to_integer(erlang:system_info(otp_release)), 108 OldRels = [R || R <- [CurrVsn-2,CurrVsn-1], 109 test_server:is_release_available(list_to_atom(integer_to_list(R)))], 110 Rels = OldRels ++ [current], 111 io:format("Creating crash dumps for the following releases: ~p", [Rels]), 112 AllDumps = create_dumps(DataDir,Rels), 113 [{dumps,AllDumps}|Config]. 114 115delete_saved(Config) -> 116 DataDir = ?config(data_dir,Config), 117 file:delete(filename:join(DataDir,?failed_file)), 118 SaveDir = filename:join(DataDir,"save"), 119 Dumps = filelib:wildcard(filename:join(SaveDir,"*")), 120 lists:foreach(fun(F) -> file:delete(F) end, Dumps), 121 file:del_dir(SaveDir), 122 ok. 123 124 125start_stop(Config) when is_list(Config) -> 126 Dump = hd(?config(dumps,Config)), 127 timer:sleep(1000), 128 129 ProcsBefore = processes(), 130 NumProcsBefore = length(ProcsBefore), 131 ok = crashdump_viewer:start(Dump), 132 ExpectedRegistered = [crashdump_viewer_server, 133 cdv_wx, 134 cdv_proc_cb, 135 cdv_proc_cb__holder, 136 cdv_port_cb, 137 cdv_port_cb__holder, 138 cdv_ets_cb, 139 cdv_ets_cb__holder, 140 cdv_timer_cb, 141 cdv_timer_cb__holder, 142 cdv_fun_cb, 143 cdv_fun_cb__holder, 144 cdv_atom_cb, 145 cdv_atom_cb__holder, 146 cdv_dist_cb, 147 cdv_dist_cb__holder, 148 cdv_mod_cb, 149 cdv_mod_cb__holder], 150 Regs=[begin 151 P=whereis(N), 152 {P,N,erlang:monitor(process,P)} 153 end || N <- ExpectedRegistered], 154 ct:log("CDV procs: ~n~p~n",[Regs]), 155 [true=is_pid(P) || {P,_,_} <- Regs], 156 timer:sleep(5000), % give some time to live 157 ct:log("try stop crashdump viewer (async)~n"), 158 ok = crashdump_viewer:stop(), 159 ct:log("await crashdump viewer processes termination~n"), 160 recv_downs(Regs), 161 ct:log("sleep some~n"), 162 timer:sleep(2000), 163 ct:log("try get all processes~n"), 164 ProcsAfter = processes(), 165 NumProcsAfter = length(ProcsAfter), 166 ct:log("try verify crashdump viewer stopped~n"), 167 if (NumProcsAfter =/= NumProcsBefore) -> 168 ct:log("Leaking processes: " 169 "~n Before but not after:" 170 "~n ~p" 171 "~n After but not before:" 172 "~n ~p", 173 [ 174 [{P,process_info(P)} || P <- ProcsBefore -- ProcsAfter], 175 [{P,process_info(P)} || P <- ProcsAfter -- ProcsBefore] 176 ]); 177 true -> 178 ok 179 end, 180 ok. 181 182recv_downs([]) -> 183 ct:log("'DOWN' received from all registered proceses~n", []), 184 ok; 185recv_downs(Regs) -> 186 receive 187 {'DOWN', Ref, process, _Pid, _} -> 188 Regs2 = lists:keydelete(Ref, 3, Regs), 189 ct:log("Got 'DOWN' for process ~p (~w procs remaining)~n", 190 [_Pid, length(Regs2)]), 191 recv_downs(Regs2) 192 after 30000 -> 193 ct:log("Timeout waiting for down:~n~p~n", 194 [[{Reg,process_info(P)} || {P,_,_}=Reg <- Regs]]), 195 ct:log("Message queue:~n~p~n",[process_info(self(),messages)]) 196 end. 197 198%% Try to load nonexisting file 199non_existing(Config) when is_list(Config) -> 200 ExpectedReason = "non-existing-file is not an Erlang crash dump\n", 201 {error, ExpectedReason} = start_backend("non-existing-file"), 202 ok = crashdump_viewer:stop(). 203 204%% Try to load a crashdump of old (earlier than OTP R10B) format 205old_crashdump(Config) when is_list(Config) -> 206 DataDir = ?config(data_dir,Config), 207 OldFile = filename:join(DataDir,"old_format.dump"), 208 ExpectedReason = "The crashdump " ++ OldFile ++ 209 " is in the pre-R10B format, which is no longer supported.\n", 210 {error, ExpectedReason} = start_backend(OldFile), 211 ok = crashdump_viewer:stop(). 212 213%% Try to load a file which is not an erlang crashdump 214not_a_crashdump(Config) when is_list(Config) -> 215 PrivDir = ?config(priv_dir,Config), 216 F1 = filename:join(PrivDir,"f1"), 217 F2 = filename:join(PrivDir,"f2"), 218 219 file:write_file(F1,"=unexpected_tag:xyz"), 220 file:write_file(F2,""), 221 222 ExpReason1 = F1 ++ " is not an Erlang crash dump\n", 223 ExpReason2 = F2 ++ " is not an Erlang crash dump\n", 224 225 {error,ExpReason1} = start_backend(F1), 226 {error,ExpReason2} = start_backend(F2), 227 228 ok = crashdump_viewer:stop(). 229 230%% Try to load a file with newer version than this crashdump viewer can handle 231new_crashdump(Config) -> 232 Dump = hd(?config(dumps,Config)), 233 ok = start_backend(Dump), 234 {ok,{MaxVsn,CurrentVsn}} = crashdump_viewer:get_dump_versions(), 235 if MaxVsn =/= CurrentVsn -> 236 ct:fail("Current dump version is not equal to cdv's max version"); 237 true -> 238 ok 239 end, 240 ok = crashdump_viewer:stop(), 241 NewerVsn = lists:join($.,[integer_to_list(X+1) || X <- MaxVsn]), 242 PrivDir = ?config(priv_dir,Config), 243 NewDump = filename:join(PrivDir,"new_erl_crash.dump"), 244 ok = file:write_file(NewDump,"=erl_crash_dump:"++NewerVsn++"\n"), 245 {error, Reason} = start_backend(NewDump), 246 "This Crashdump Viewer is too old" ++_ = Reason, 247 ok = crashdump_viewer:stop(). 248 249%% Load files into the tool and view all pages 250load_file(Config) when is_list(Config) -> 251 case test_server:is_debug() of 252 true -> 253 {skip,"Debug-compiled emulator -- far too slow"}; 254 false -> 255 load_file_1(Config) 256 end. 257 258 259load_file_1(Config) -> 260 DataDir = ?config(data_dir,Config), 261 crashdump_viewer:start_link(), 262 263 %% Read both created and predefined dumps 264 AllFiles = filelib:wildcard(filename:join(DataDir,"r*_dump.*")), 265 lists:foreach( 266 fun(File) -> 267 Content = browse_file(File), 268 special(File,Content) 269 end, 270 AllFiles), 271 ok = crashdump_viewer:stop(). 272 273%% Try to lookup nonexisting process, port and node 274not_found_items(Config) -> 275 Dump = hd(?config(dumps,Config)), 276 277 ok = start_backend(Dump), 278 279 {ok,#general_info{},_} = crashdump_viewer:general_info(), 280 281 {error,not_found} = crashdump_viewer:proc_details("<1111.1111.1111>"), 282 {error,not_found} = crashdump_viewer:port("#Port<1111.1111>"), 283 {error,not_found} = crashdump_viewer:node_info("1111"), 284 285 ok = crashdump_viewer:stop(). 286 287%% Remove generated crashdumps 288end_per_suite(Config) when is_list(Config) -> 289 Dumps = ?config(dumps,Config), 290 DataDir = ?config(data_dir,Config), 291 FailedFile = filename:join(DataDir,?failed_file), 292 case filelib:is_file(FailedFile) of 293 true -> 294 SaveDir = filename:join(DataDir,"save"), 295 file:make_dir(SaveDir), 296 file:copy(FailedFile,filename:join(SaveDir,?failed_file)), 297 lists:foreach( 298 fun(CD) -> 299 File = filename:basename(CD), 300 New = filename:join(SaveDir,File), 301 file:copy(CD,New) 302 end, Dumps); 303 false -> 304 ok 305 end, 306 file:delete(FailedFile), 307 lists:foreach(fun(CD) -> ok = file:delete(CD) end,Dumps), 308 lists:keydelete(dumps,1,Config). 309 310 311%%%----------------------------------------------------------------- 312%%% Internal 313%%%----------------------------------------------------------------- 314%%% Start the crashdump_viewer backend and load a dump 315start_backend(File) -> 316 crashdump_viewer:start_link(), 317 register_progress_handler(), 318 ok = crashdump_viewer:read_file(File), 319 wait_for_progress_done(). 320 321%%%----------------------------------------------------------------- 322%%% Simulate the progress handler in observer_lib 323register_progress_handler() -> 324 register(cdv_progress_handler,self()). 325 326wait_for_progress_done() -> 327 receive 328 {progress,{error,Reason}} -> 329 unregister(cdv_progress_handler), 330 {error,lists:flatten(Reason)}; 331 {progress,{ok,done}} -> 332 unregister(cdv_progress_handler), 333 ok; 334 {progress,_} -> 335 wait_for_progress_done() 336 end. 337 338%%%----------------------------------------------------------------- 339%%% General check of what is displayed for a dump 340browse_file(File) -> 341 io:format("~n[~s] Browsing file: ~s", [formated_timestamp(), File]), 342 %% io:format("~nBrowsing file: ~s",[File]), 343 344 ok = start_backend(File), 345 346 io:format(" backend started",[]), 347 348 {ok,_GI=#general_info{},_GenTW} = crashdump_viewer:general_info(), 349 {ok,Procs,_ProcsTW} = crashdump_viewer:processes(), 350 {ok,Ports,_PortsTW} = crashdump_viewer:ports(), 351 {ok,_Ets,_EtsTW} = crashdump_viewer:ets_tables(all), 352 {ok,_IntEts,_IntEtsTW} = crashdump_viewer:internal_ets_tables(), 353 {ok,_Timers,_TimersTW} = crashdump_viewer:timers(all), 354 {ok,_Funs,_FunsTW} = crashdump_viewer:funs(), 355 {ok,_Atoms,_AtomsTW} = crashdump_viewer:atoms(), 356 {ok,Nodes,_NodesTW} = crashdump_viewer:dist_info(), 357 {ok,Mods,_ModsTW} = crashdump_viewer:loaded_modules(), 358 {ok,_Mem,_MemTW} = crashdump_viewer:memory(), 359 {ok,_AllocAreas,_AreaTW} = crashdump_viewer:allocated_areas(), 360 {ok,_AllocINfo,_AllocInfoTW} = crashdump_viewer:allocator_info(), 361 {ok,_HashTabs,_HashTabsTW} = crashdump_viewer:hash_tables(), 362 {ok,_IndexTabs,_IndexTabsTW} = crashdump_viewer:index_tables(), 363 {ok,_PTs,_PTsTW} = crashdump_viewer:persistent_terms(), 364 365 io:format(" info read",[]), 366 367 lookat_all_pids(Procs,is_truncated(File),incomplete_allowed(File)), 368 io:format(" pids ok",[]), 369 lookat_all_ports(Ports), 370 io:format(" ports ok",[]), 371 lookat_all_mods(Mods), 372 io:format(" mods ok",[]), 373 lookat_all_nodes(Nodes), 374 io:format(" nodes ok",[]), 375 376 Procs. % used as second arg to special/2 377 378is_truncated(File) -> 379 case filename:extension(File) of 380 ".trunc"++_ -> 381 true; 382 _ -> 383 false 384 end. 385 386incomplete_allowed(File) -> 387 %% Incomplete heap is allowed for pre OTP-20 (really pre 20.2) 388 %% releases, since literals were not dumped at all then. 389 Rel = get_rel_from_dump_name(File), 390 Rel < 20. 391 392special(File,Procs) -> 393 case filename:extension(File) of 394 ".full_dist" -> 395 %% I registered a process as aaaaaaaa in the full_dist dumps 396 %% to make sure it will be the first in the list when sorted 397 %% on names. There are some special data here, so I'll thoroughly 398 %% read the process details for this process. Other processes 399 %% are just briefly traversed. 400 [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs), 401 Pid = pid_to_list(Pid0), 402 {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid), 403 io:format(" process details ok",[]), 404 405 #proc{dict=Dict} = ProcDetails, 406 407 ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict), 408 {ok,<<_:Size/binary>>} = 409 crashdump_viewer:expand_binary({Offset,Size,Pos}), 410 {ok,'#CDVTruncatedBinary'} = 411 crashdump_viewer:expand_binary({Offset,Size+1,Pos}), 412 ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict), 413 {ok,<<_:SSize/binary>>} = 414 crashdump_viewer:expand_binary({SOffset,SSize,SPos}), 415 io:format(" expand binary ok",[]), 416 417 ProcBins = proplists:get_value(proc_bins,Dict), 418 {['#CDVBin',0,65,ProcBin], 419 ['#CDVBin',65,65,ProcBin], 420 ['#CDVBin',130,125,ProcBin]} = ProcBins, 421 io:format(" ProcBins ok",[]), 422 423 424 Binaries = crashdump_helper:create_binaries(), 425 verify_binaries(Binaries, proplists:get_value(bins,Dict)), 426 io:format(" binaries ok",[]), 427 428 SubBinaries = crashdump_helper:create_sub_binaries(Binaries), 429 verify_binaries(SubBinaries, proplists:get_value(sub_bins,Dict)), 430 io:format(" sub binaries ok",[]), 431 432 #proc{last_calls=LastCalls} = ProcDetails, 433 true = length(LastCalls) =< 4, 434 435 ['#CDVPid',X1,Y1,Z1] = proplists:get_value(ext_pid,Dict), 436 ChannelStr1 = integer_to_list(X1), 437 ExtPid = 438 "<" ++ ChannelStr1 ++ "." ++ 439 integer_to_list(Y1) ++ "." ++ 440 integer_to_list(Z1) ++ ">", 441 {error,{other_node,ChannelStr1}} = 442 crashdump_viewer:proc_details(ExtPid), 443 io:format(" process details external ok",[]), 444 445 ['#CDVPort',X2,Y2] = proplists:get_value(port,Dict), 446 ChannelStr2 = integer_to_list(X2), 447 Port = "#Port<"++ChannelStr2++"."++integer_to_list(Y2)++">", 448 {ok,_PortDetails=#port{},[]} = crashdump_viewer:port(Port), 449 io:format(" port details ok",[]), 450 451 ['#CDVPort',X3,Y3] = proplists:get_value(ext_port,Dict), 452 ChannelStr3 = integer_to_list(X3), 453 ExtPort = "#Port<"++ChannelStr3++"."++integer_to_list(Y3)++">", 454 {error,{other_node,ChannelStr3}} = crashdump_viewer:port(ExtPort), 455 io:format(" port details external ok",[]), 456 457 {ok,[_Ets=#ets_table{}],[]} = crashdump_viewer:ets_tables(Pid), 458 io:format(" ets tables ok",[]), 459 460 {ok,[#timer{pid=Pid0,name=undefined}, 461 #timer{pid=Pid0,name="aaaaaaaa"}],[]} = 462 crashdump_viewer:timers(Pid), 463 {ok,AllTimers,_TimersTW} = crashdump_viewer:timers(all), 464 #timer{name="noexistproc"} = 465 lists:keyfind(undefined,#timer.pid,AllTimers), 466 io:format(" timers ok:",[]), 467 468 {ok,Mod1=#loaded_mod{},[]} = 469 crashdump_viewer:loaded_mod_details(atom_to_list(?helper_mod)), 470 io:format(" modules ok",[]), 471 #loaded_mod{current_size=CS, old_size=OS, 472 old_attrib=A,old_comp_info=C}=Mod1, 473 true = is_integer(CS), 474 true = (CS==OS), 475 true = (A=/=undefined), 476 true = (C=/=undefined), 477 {ok,Mod2=#loaded_mod{},[]} = 478 crashdump_viewer:loaded_mod_details("application"), 479 io:format(" module details ok",[]), 480 #loaded_mod{old_size="No old code exists", 481 old_attrib=undefined, 482 old_comp_info=undefined}=Mod2, 483 ok; 484 ".trunc_mod" -> 485 ModName = atom_to_list(?helper_mod), 486 {ok,Mod=#loaded_mod{},[TW]} = 487 crashdump_viewer:loaded_mod_details(ModName), 488 "WARNING: The crash dump is truncated here."++_ = TW, 489 #loaded_mod{current_attrib=CA,current_comp_info=CCI, 490 old_attrib=OA,old_comp_info=OCI} = Mod, 491 case lists:all(fun(undefined) -> 492 true; 493 (S) when is_list(S) -> 494 io_lib:printable_unicode_list(lists:flatten(S)); 495 (_) -> false 496 end, 497 [CA,CCI,OA,OCI]) of 498 true -> 499 ok; 500 false -> 501 ct:fail({should_be_printable_strings_or_undefined, 502 {CA,CCI,OA,OCI}}) 503 end, 504 ok; 505 ".trunc_bin1" -> 506 %% This is 'full_dist' truncated after the first 507 %% "=binary:" 508 %% i.e. no binary exist in the dump 509 [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs), 510 Pid = pid_to_list(Pid0), 511 %%WarnIncompleteHeap = ["WARNING: This process has an incomplete heap. Some information might be missing."], 512 {ok,ProcDetails=#proc{},[]} = 513 crashdump_viewer:proc_details(Pid), 514 io:format(" process details ok",[]), 515 516 #proc{dict=Dict} = ProcDetails, 517 518 '#CDVNonexistingBinary' = proplists:get_value(bin,Dict), 519 '#CDVNonexistingBinary' = proplists:get_value(sub_bin,Dict), 520 521 io:format(" nonexisting binaries ok",[]), 522 ok; 523 ".trunc_bin2" -> 524 %% This is 'full_dist' truncated after the first 525 %% "=binary:Addr\n 526 %% Size" 527 %% i.e. binaries are truncated 528 [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs), 529 Pid = pid_to_list(Pid0), 530 {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid), 531 io:format(" process details ok",[]), 532 533 #proc{dict=Dict} = ProcDetails, 534 535 ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict), 536 {ok,'#CDVTruncatedBinary'} = 537 crashdump_viewer:expand_binary({Offset,Size,Pos}), 538 ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict), 539 {ok,'#CDVTruncatedBinary'} = 540 crashdump_viewer:expand_binary({SOffset,SSize,SPos}), 541 542 io:format(" expand truncated binary ok",[]), 543 ok; 544 ".trunc_bin3" -> 545 %% This is 'full_dist' truncated after the first 546 %% "=binary:Addr\n 547 %% Size:" 548 %% i.e. same as 'trunc_bin2', except the colon exists also 549 [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs), 550 Pid = pid_to_list(Pid0), 551 {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid), 552 io:format(" process details ok",[]), 553 554 #proc{dict=Dict} = ProcDetails, 555 556 ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict), 557 {ok,'#CDVTruncatedBinary'} = 558 crashdump_viewer:expand_binary({Offset,Size,Pos}), 559 ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict), 560 {ok,'#CDVTruncatedBinary'} = 561 crashdump_viewer:expand_binary({SOffset,SSize,SPos}), 562 563 io:format(" expand truncated binary ok",[]), 564 ok; 565 ".trunc_bin4" -> 566 %% This is 'full_dist' truncated after the first 567 %% "=binary:Addr\n 568 %% Size:BinaryMissinOneByte" 569 %% i.e. the full binary is truncated, but the sub binary is complete 570 [#proc{pid=Pid0}|_Rest] = lists:keysort(#proc.name,Procs), 571 Pid = pid_to_list(Pid0), 572 {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid), 573 io:format(" process details ok",[]), 574 575 #proc{dict=Dict} = ProcDetails, 576 577 ['#CDVBin',Offset,Size,Pos] = proplists:get_value(bin,Dict), 578 {ok,'#CDVTruncatedBinary'} = 579 crashdump_viewer:expand_binary({Offset,Size,Pos}), 580 io:format(" expand truncated binary ok",[]), 581 ['#CDVBin',SOffset,SSize,SPos] = proplists:get_value(sub_bin,Dict), 582 {ok,<<_:SSize/binary>>} = 583 crashdump_viewer:expand_binary({SOffset,SSize,SPos}), 584 io:format(" expand complete sub binary ok",[]), 585 586 ok; 587 ".trunc_bytes" -> 588 {ok,_,[TW]} = crashdump_viewer:general_info(), 589 {match,_} = re:run(TW,"CRASH DUMP SIZE LIMIT REACHED"), 590 io:format(" size limit information ok",[]), 591 ok; 592 ".unicode" -> 593 #proc{pid=Pid0} = 594 lists:keyfind("'unicode_reg_name_αβ'",#proc.name,Procs), 595 Pid = pid_to_list(Pid0), 596 {ok,Proc,[]} = crashdump_viewer:proc_details(Pid), 597 #proc{last_calls=LastCalls,stack_dump=Stk} = Proc, 598 io:format(" unicode registered name ok",[]), 599 600 ["crashdump_helper_unicode:'спутник'/0", 601 "ets:new/2"|_] = lists:reverse(LastCalls), 602 io:format(" last calls ok",[]), 603 604 verify_unicode_stack(Stk), 605 io:format(" unicode stack values ok",[]), 606 607 {ok,[#ets_table{id="'tab_αβ'",name="'tab_αβ'"}],[]} = 608 crashdump_viewer:ets_tables(Pid), 609 io:format(" unicode table name ok",[]), 610 611 ok; 612 ".maps" -> 613 %% I registered a process as aaaaaaaa_maps in the map dump 614 %% to make sure it will be the first in the list when sorted 615 %% on names. 616 [#proc{pid=Pid0,name=Name}|_Rest] = lists:keysort(#proc.name,Procs), 617 "aaaaaaaa_maps" = Name, 618 Pid = pid_to_list(Pid0), 619 {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid), 620 io:format(" process details ok",[]), 621 622 #proc{dict=Dict} = ProcDetails, 623 %% io:format("~p\n", [Dict]), 624 Maps = crashdump_helper:create_maps(), 625 Maps = proplists:get_value(maps,Dict), 626 io:format(" maps ok",[]), 627 ok; 628 ".persistent_terms" -> 629 %% I registered a process as aaaaaaaa_persistent_term in 630 %% the dump to make sure it will be the first in the list 631 %% when sorted on names. 632 [#proc{pid=Pid0,name=Name}|_Rest] = lists:keysort(#proc.name,Procs), 633 "aaaaaaaa_persistent_terms" = Name, 634 Pid = pid_to_list(Pid0), 635 {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid), 636 io:format(" process details ok",[]), 637 638 #proc{dict=Dict} = ProcDetails, 639 %% io:format("~p\n", [Dict]), 640 Pts = crashdump_helper:create_persistent_terms(), 641 Pts = proplists:get_value(pts,Dict), 642 io:format(" persistent terms ok",[]), 643 ok; 644 ".global_literals" -> 645 %% I registered a process as aaaaaaaa_global_literals in 646 %% the dump to make sure it will be the first in the list 647 %% when sorted on names. 648 [#proc{pid=Pid0,name=Name}|_Rest] = lists:keysort(#proc.name,Procs), 649 "aaaaaaaa_global_literals" = Name, 650 Pid = pid_to_list(Pid0), 651 {ok,ProcDetails=#proc{},[]} = crashdump_viewer:proc_details(Pid), 652 io:format(" process details ok",[]), 653 654 #proc{dict=Dict} = ProcDetails, 655 Globals = proplists:get_value(global_literals,Dict), 656 Globals = {os:type(),os:version()}, 657 io:format(" global_literals ok",[]), 658 ok; 659 _ -> 660 ok 661 end, 662 ok. 663 664verify_unicode_stack([{_,{state,Str,Atom,Bin,LongBin}}|_]) -> 665 'unicode_atom_αβ' = Atom, 666 "unicode_string_αβ" = Str, 667 <<"bin αβ"/utf8>> = Bin, 668 <<"long bin αβ - a utf8 binary which can be expanded αβ"/utf8>> = LongBin, 669 ok; 670verify_unicode_stack([_|T]) -> 671 verify_unicode_stack(T). 672 673verify_binaries([H|T1], [H|T2]) -> 674 %% Heap binary. 675 verify_binaries(T1, T2); 676verify_binaries([Bin|T1], [['#CDVBin',Offset,Size,Pos]|T2]) -> 677 %% Refc binary. 678 {ok,<<Bin:Size/binary>>} = crashdump_viewer:expand_binary({Offset,Size,Pos}), 679 verify_binaries(T1, T2); 680verify_binaries([], []) -> 681 ok. 682 683lookat_all_pids([],_,_) -> 684 ok; 685lookat_all_pids([#proc{pid=Pid0}|Procs],TruncAllowed,IncompAllowed) -> 686 Pid = pid_to_list(Pid0), 687 {ok,_ProcDetails=#proc{},ProcTW} = crashdump_viewer:proc_details(Pid), 688 {ok,_Ets,EtsTW} = crashdump_viewer:ets_tables(Pid), 689 {ok,_Timers,TimersTW} = crashdump_viewer:timers(Pid), 690 case {ProcTW,EtsTW,TimersTW} of 691 {[],[],[]} -> 692 ok; 693 {["WARNING: This process has an incomplete heap."++_],[],[]} 694 when IncompAllowed -> 695 ok; % native libs, literals might not be included in dump 696 _ when TruncAllowed -> 697 ok; % truncated dump 698 TWs -> 699 ?P("lookat_all_pids -> unexpected warning" 700 "~n Pid: ~s" 701 "~n IncompAllowed: ~p" 702 "~n TruncAllowed: ~p" 703 "~n ~p", [Pid, IncompAllowed, TruncAllowed, TWs]), 704 ct:fail({unexpected_warning, Pid, TWs, IncompAllowed, TruncAllowed}) 705 end, 706 lookat_all_pids(Procs,TruncAllowed,IncompAllowed). 707 708lookat_all_ports([]) -> 709 ok; 710lookat_all_ports([#port{id=Port0}|Procs]) -> 711 Port = cdv_port_cb:format(Port0), 712 {ok,_PortDetails=#port{},_PortTW} = crashdump_viewer:port(Port), 713 lookat_all_ports(Procs). 714 715lookat_all_mods([]) -> 716 ok; 717lookat_all_mods([#loaded_mod{mod=ModId}|Mods]) -> 718 ModName = cdv_mod_cb:format(ModId), 719 {ok,_Mod=#loaded_mod{},_ModTW} = crashdump_viewer:loaded_mod_details(ModName), 720 lookat_all_mods(Mods). 721 722lookat_all_nodes([]) -> 723 ok; 724lookat_all_nodes([#nod{channel=Channel0}|Nodes]) -> 725 Channel = integer_to_list(Channel0), 726 {ok,_Node=#nod{},_NodeTW} = crashdump_viewer:node_info(Channel), 727 lookat_all_nodes(Nodes). 728 729%%%----------------------------------------------------------------- 730%%% 731create_dumps(DataDir,Rels) -> 732 create_dumps(DataDir,Rels,[]). 733create_dumps(DataDir,[Rel|Rels],Acc) -> 734 Fun = fun() -> do_create_dumps(DataDir,Rel) end, 735 Pa = filename:dirname(code:which(?MODULE)), 736 {Dumps,DosDump} = 737 test_server:run_on_shielded_node(Fun, compat_rel(Rel) ++ "-pa \"" ++ Pa ++ "\""), 738 create_dumps(DataDir,Rels,Dumps ++ Acc ++ DosDump); 739create_dumps(_DataDir,[],Acc) -> 740 Acc. 741 742do_create_dumps(DataDir,Rel) -> 743 CD1 = full_dist_dump(DataDir,Rel), 744 CD2 = dump_with_args(DataDir,Rel,"port_is_unix_fd","-oldshell"), 745 DosDump = 746 case os:type() of 747 {unix,sunos} -> dos_dump(DataDir,Rel,CD1); 748 _ -> [] 749 end, 750 case Rel of 751 current -> 752 CD3 = dump_with_args(DataDir,Rel,"instr","+Muatags true"), 753 CD4 = dump_with_strange_module_name(DataDir,Rel,"strangemodname"), 754 CD5 = dump_with_size_limit_reached(DataDir,Rel,"trunc_bytes"), 755 CD6 = dump_with_unicode_atoms(DataDir,Rel,"unicode"), 756 CD7 = dump_with_maps(DataDir,Rel,"maps"), 757 CD8 = dump_with_persistent_terms(DataDir,Rel,"persistent_terms"), 758 CD9 = dump_with_global_literals(DataDir,Rel,"global_literals"), 759 TruncDumpMod = truncate_dump_mod(CD1), 760 TruncatedDumpsBinary = truncate_dump_binary(CD1), 761 {[CD1,CD2,CD3,CD4,CD5,CD6,CD7,CD8,CD9, 762 TruncDumpMod|TruncatedDumpsBinary], 763 DosDump}; 764 _ -> 765 {[CD1,CD2], DosDump} 766 end. 767 768truncate_dump_mod(File) -> 769 {ok,Bin} = file:read_file(File), 770 ModNameBin = atom_to_binary(?helper_mod,latin1), 771 NewLine = case os:type() of 772 {win32,_} -> <<"\r\n">>; 773 _ -> <<"\n">> 774 end, 775 RE = <<NewLine/binary,"=mod:",ModNameBin/binary, 776 NewLine/binary,"Current size: [0-9]*", 777 NewLine/binary,"Current attributes: ...">>, 778 {match,[{Pos,Len}]} = re:run(Bin,RE), 779 Size = Pos + Len, 780 <<Truncated:Size/binary,_/binary>> = Bin, 781 DumpName = filename:rootname(File) ++ ".trunc_mod", 782 file:write_file(DumpName,Truncated), 783 DumpName. 784 785truncate_dump_binary(File) -> 786 {ok,Bin} = file:read_file(File), 787 BinTag = <<"\n=binary:">>, 788 Colon = <<":">>, 789 NewLine = case os:type() of 790 {win32,_} -> <<"\r\n">>; 791 _ -> <<"\n">> 792 end, 793 %% Split after "our binary" created by crashdump_helper 794 %% (it may not be the first binary). 795 RE = <<"\n=binary:(?=[0-9A-Z]+",NewLine/binary,"FF:AQID)">>, 796 [StartBin,AfterTag] = re:split(Bin,RE,[{parts,2}]), 797 [AddrAndSize,BinaryAndRest] = binary:split(AfterTag,Colon), 798 [Binary,_Rest] = binary:split(BinaryAndRest,NewLine), 799 TruncSize = byte_size(Binary) - 2, 800 <<TruncBinary:TruncSize/binary,_/binary>> = Binary, 801 TruncName = filename:rootname(File) ++ ".trunc_bin", 802 write_trunc_files(TruncName,StartBin, 803 [BinTag,AddrAndSize,Colon,TruncBinary],1). 804 805write_trunc_files(TruncName0,Bin,[Part|Parts],N) -> 806 TruncName = TruncName0++integer_to_list(N), 807 Bin1 = <<Bin/binary,Part/binary>>, 808 ok = file:write_file(TruncName,Bin1), 809 [TruncName|write_trunc_files(TruncName0,Bin1,Parts,N+1)]; 810write_trunc_files(_,_,[],_) -> 811 []. 812 813 814%% Create a dump which has three visible nodes, one hidden and one 815%% not connected node, and with monitors and links between nodes. 816%% One of the visible nodes is stopped and started again in order to 817%% get multiple creations. 818full_dist_dump(DataDir,Rel) -> 819 Opt = rel_opt(Rel), 820 Pz = "-pz \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"", 821 PzOpt = [{args,Pz}], 822 {ok,N1} = test_server:start_node(n1,peer,Opt ++ PzOpt), 823 {ok,N2} = test_server:start_node(n2,peer,Opt ++ PzOpt), 824 {ok,N3} = test_server:start_node(n3,peer,Opt ++ PzOpt), 825 {ok,N4} = test_server:start_node(n4,peer,Opt ++ [{args,"-hidden " ++ Pz}]), 826 Creator = self(), 827 828 P1 = rpc:call(N1,?helper_mod,n1_proc,[N2,Creator]), 829 P2 = rpc:call(N2,?helper_mod,remote_proc,[P1,Creator]), 830 P3 = rpc:call(N3,?helper_mod,remote_proc,[P1,Creator]), 831 P4 = rpc:call(N4,?helper_mod,remote_proc,[P1,Creator]), 832 833 get_response(P2), 834 get_response(P3), 835 get_response(P4), 836 get_response(P1), 837 838 %% start, stop and start a node in order to get multiple 'creations' 839 {ok,N5} = test_server:start_node(n5,peer,Opt ++ PzOpt), 840 P51 = rpc:call(N5,?helper_mod,remote_proc,[P1,Creator]), 841 get_response(P51), 842 test_server:stop_node(N5), 843 {ok,N5} = test_server:start_node(n5,peer,Opt ++ PzOpt), 844 P52 = rpc:call(N5,?helper_mod,remote_proc,[P1,Creator]), 845 get_response(P52), 846 847 {aaaaaaaa,N1} ! {hello,from,other,node}, % distribution message 848 849 test_server:stop_node(N3), 850 DumpName = "full_dist", 851 CD = dump(N1,DataDir,Rel,DumpName), 852 853 test_server:stop_node(N2), 854 test_server:stop_node(N4), 855 test_server:stop_node(N5), 856 CD. 857 858get_response(P) -> 859 receive {P,done} -> ok 860 after 3000 -> ct:fail({get_response_timeout,P,node(P)}) 861 end. 862 863 864dump_with_args(DataDir,Rel,DumpName,Args) -> 865 RelOpt = rel_opt(Rel), 866 Opt = RelOpt ++ [{args,Args}], 867 {ok,N1} = test_server:start_node(n1,peer,Opt), 868 CD = dump(N1,DataDir,Rel,DumpName), 869 test_server:stop_node(n1), 870 CD. 871 872%% This dump is added to test OTP-10090 - regarding URL encoding of 873%% module names in the module detail link. 874dump_with_strange_module_name(DataDir,Rel,DumpName) -> 875 Opt = rel_opt(Rel), 876 {ok,N1} = test_server:start_node(n1,peer,Opt), 877 878 Mod = '<mod ule#with?strange%name>', 879 File = atom_to_list(Mod) ++ ".erl", 880 Forms = [{attribute,1,file,{File,1}}, 881 {attribute,1,module,Mod}, 882 {eof,4}], 883 {ok,Mod,Bin} = rpc:call(N1,compile,forms,[Forms,[binary]]), 884 {module,Mod} = rpc:call(N1,code,load_binary,[Mod,File,Bin]), 885 CD = dump(N1,DataDir,Rel,DumpName), 886 test_server:stop_node(n1), 887 CD. 888 889dump_with_size_limit_reached(DataDir,Rel,DumpName) -> 890 Tmp = dump_with_args(DataDir,Rel,DumpName,""), 891 {ok,#file_info{size=Max}} = file:read_file_info(Tmp), 892 ok = file:delete(Tmp), 893 dump_with_size_limit_reached(DataDir,Rel,DumpName,Max). 894 895dump_with_size_limit_reached(DataDir,Rel,DumpName,Max) -> 896 Bytes = max(15,rand:uniform(Max)), 897 CD = dump_with_args(DataDir,Rel,DumpName, 898 "-env ERL_CRASH_DUMP_BYTES " ++ 899 integer_to_list(Bytes)), 900 {ok,#file_info{size=Size}} = file:read_file_info(CD), 901 if Size =< Bytes -> 902 %% This means that the dump was actually smaller than the 903 %% randomly selected truncation size, so we'll just do it 904 %% again with a smaller number 905 ok = file:delete(CD), 906 dump_with_size_limit_reached(DataDir,Rel,DumpName,Size-3); 907 true -> 908 CD 909 end. 910 911dump_with_unicode_atoms(DataDir,Rel,DumpName) -> 912 Opt = rel_opt(Rel), 913 Pz = "-pz \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"", 914 PzOpt = [{args,Pz}], 915 {ok,N1} = test_server:start_node(n1,peer,Opt ++ PzOpt), 916 {ok,_Pid} = rpc:call(N1,crashdump_helper_unicode,start,[]), 917 CD = dump(N1,DataDir,Rel,DumpName), 918 test_server:stop_node(n1), 919 CD. 920 921dump_with_maps(DataDir,Rel,DumpName) -> 922 Opt = rel_opt(Rel), 923 Pz = "-pz \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"", 924 PzOpt = [{args,Pz}], 925 {ok,N1} = test_server:start_node(n1,peer,Opt ++ PzOpt), 926 {ok,_Pid} = rpc:call(N1,crashdump_helper,dump_maps,[]), 927 CD = dump(N1,DataDir,Rel,DumpName), 928 test_server:stop_node(n1), 929 CD. 930 931dump_with_persistent_terms(DataDir,Rel,DumpName) -> 932 Opt = rel_opt(Rel), 933 Pz = "-pz \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"", 934 PzOpt = [{args,Pz}], 935 {ok,N1} = test_server:start_node(n1,peer,Opt ++ PzOpt), 936 {ok,_Pid} = rpc:call(N1,crashdump_helper,dump_persistent_terms,[]), 937 CD = dump(N1,DataDir,Rel,DumpName), 938 test_server:stop_node(n1), 939 CD. 940 941dump_with_global_literals(DataDir,Rel,DumpName) -> 942 Opt = rel_opt(Rel), 943 Pz = "-pz \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"", 944 PzOpt = [{args,Pz}], 945 {ok,N1} = test_server:start_node(n1,peer,Opt ++ PzOpt), 946 {ok,_Pid} = rpc:call(N1,crashdump_helper,dump_global_literals,[]), 947 CD = dump(N1,DataDir,Rel,DumpName), 948 test_server:stop_node(n1), 949 CD. 950 951dump(Node,DataDir,Rel,DumpName) -> 952 Crashdump = filename:join(DataDir, dump_prefix(Rel)++DumpName), 953 rpc:call(Node,os,putenv,["ERL_CRASH_DUMP",Crashdump]), 954 rpc:call(Node,erlang,halt,[DumpName]), 955 ok = check_complete(Crashdump), 956 Crashdump. 957 958check_complete(File) -> 959 check_complete1(File,10). 960 961check_complete1(_File,0) -> 962 {error,enoent}; 963check_complete1(File,N) -> 964 case file:read_file_info(File) of 965 {error,enoent} -> 966 timer:sleep(500), 967 check_complete1(File,N-1); 968 {ok,#file_info{size=Size}} -> 969 check_complete2(File,Size) 970 end. 971 972check_complete2(File,Size) -> 973 timer:sleep(500), 974 case file:read_file_info(File) of 975 {ok,#file_info{size=Size}} -> 976 ok; 977 {ok,#file_info{size=OtherSize}} -> 978 check_complete2(File,OtherSize) 979 end. 980 981dos_dump(DataDir,Rel,Dump) -> 982 DosDumpName = filename:join(DataDir,dump_prefix(Rel)++"dos"), 983 Cmd = "unix2dos " ++ Dump ++ " > " ++ DosDumpName, 984 Port = open_port({spawn,Cmd},[exit_status]), 985 receive 986 {Port,{exit_status,0}} -> 987 [DosDumpName]; 988 {Port,{exit_status,_Error}} -> 989 test_server:comment("Couldn't run \'unix2dos\'"), 990 [] 991 end. 992 993rel_opt(current) -> 994 []; 995rel_opt(Rel) -> 996 [{erl,[{release,lists:concat([Rel,"_latest"])}]}]. 997 998dump_prefix(current) -> 999 dump_prefix(erlang:system_info(otp_release)); 1000dump_prefix(Rel) -> 1001 lists:concat(["r",Rel,"_dump."]). 1002 1003get_rel_from_dump_name(File) -> 1004 Name = filename:basename(File), 1005 ["r"++Rel|_] = string:split(Name,"_"), 1006 list_to_integer(Rel). 1007 1008compat_rel(current) -> 1009 ""; 1010compat_rel(Rel) -> 1011 lists:concat(["+R",Rel," "]). 1012 1013 1014%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1015 1016%% f(F, A) -> 1017%% lists:flatten(io_lib:format(F, A)). 1018 1019formated_timestamp() -> 1020 format_timestamp(os:timestamp()). 1021 1022format_timestamp({_N1, _N2, N3} = TS) -> 1023 {_Date, Time} = calendar:now_to_local_time(TS), 1024 {Hour, Min, Sec} = Time, 1025 FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.3.0w", 1026 [Hour, Min, Sec, N3 div 1000]), 1027 lists:flatten(FormatTS). 1028 1029%% print(F) -> 1030%% print(F, []). 1031 1032print(F, A) -> 1033 io:format("~s ~p " ++ F ++ "~n", [formated_timestamp(), self() | A]). 1034 1035