/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1996-2018. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* %CopyrightEnd%
*/
/* This File contains functions which are called if a user hits ^C */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sys.h"
#include "erl_vm.h"
#include "global.h"
#include "erl_process.h"
#include "version.h"
#include "error.h"
#include "version.h"
#include "erl_db.h"
#include "bif.h"
#include "erl_version.h"
#include "hash.h"
#include "atom.h"
#include "beam_load.h"
#include "erl_hl_timer.h"
#include "erl_thr_progress.h"
#include "erl_proc_sig_queue.h"
#include "dist.h"
/* Forward declarations -- should really appear somewhere else */
static void process_killer(void);
void do_break(void);
void erl_crash_dump_v(char *file, int line, char* fmt, va_list args);
#ifdef DEBUG
static void bin_check(void);
#endif
static void print_garb_info(fmtfn_t to, void *to_arg, Process* p);
#ifdef OPPROF
static void dump_frequencies(void);
#endif
static void dump_attributes(fmtfn_t to, void *to_arg, byte* ptr, int size);
extern char* erts_system_version[];
#define WRITE_BUFFER_SIZE (64*1024)
static void
port_info(fmtfn_t to, void *to_arg)
{
int i, max = erts_ptab_max(&erts_port);
for (i = 0; i < max; i++) {
Port *p = erts_pix2port(i);
if (p)
print_port_info(p, to, to_arg);
}
}
void
process_info(fmtfn_t to, void *to_arg)
{
ErtsSchedulerData *esdp = erts_get_scheduler_data();
int i, max = erts_ptab_max(&erts_proc);
for (i = 0; i < max; i++) {
Process *p = erts_pix2proc(i);
if (p && p->i != ENULL) {
/* Do not include processes with no heap,
* they are most likely just created and has invalid data
*/
if (p->heap != NULL) {
ErtsProcLocks locks = ((esdp && (p == esdp->current_process ||
p == esdp->free_process))
? ERTS_PROC_LOCK_MAIN : 0);
print_process_info(to, to_arg, p, locks);
}
}
}
/* Look for FREE processes in the run-queues and dist entries.
These have been removed from the ptab but we still want them
in the crash dump for debugging. */
/* First loop through all run-queues */
for (i = 0; i < erts_no_schedulers + ERTS_NUM_DIRTY_RUNQS; i++) {
ErtsRunQueue *rq = ERTS_RUNQ_IX(i);
int j;
for (j = 0; j < ERTS_NO_PROC_PRIO_QUEUES; j++) {
Process *p = rq->procs.prio[j].first;
while (p) {
if (ERTS_PSFLG_FREE & erts_atomic32_read_acqb(&p->state))
print_process_info(to, to_arg, p, 0);
p = p->next;
}
}
}
/* Then check all dist entries */
erts_dist_print_procs_suspended_on_de(to, to_arg);
port_info(to, to_arg);
}
static void
process_killer(void)
{
int i, j, max = erts_ptab_max(&erts_proc);
Process* rp;
erts_printf("\n\nProcess Information\n\n");
erts_printf("--------------------------------------------------\n");
for (i = max-1; i >= 0; i--) {
rp = erts_pix2proc(i);
if (rp && rp->i != ENULL) {
int br;
print_process_info(ERTS_PRINT_STDOUT, NULL, rp, 0);
erts_printf("(k)ill (n)ext (r)eturn:\n");
while(1) {
if ((j = sys_get_key(0)) <= 0)
erts_exit(0, "");
switch(j) {
case 'k':
ASSERT(erts_init_process_id != ERTS_INVALID_PID);
/* Send a 'kill' exit signal from init process */
erts_proc_sig_send_exit(NULL, erts_init_process_id,
rp->common.id, am_kill, NIL,
0);
case 'n': br = 1; break;
case 'r': return;
default: return;
}
if (br == 1) break;
}
}
}
}
typedef struct {
int is_first;
fmtfn_t to;
void *to_arg;
} PrintMonitorContext;
static int doit_print_link(ErtsLink *lnk, void *vpcontext, Sint reds)
{
PrintMonitorContext *pcontext = vpcontext;
fmtfn_t to = pcontext->to;
void *to_arg = pcontext->to_arg;
if (pcontext->is_first) {
pcontext->is_first = 0;
erts_print(to, to_arg, "%T", lnk->other.item);
} else {
erts_print(to, to_arg, ", %T", lnk->other.item);
}
return 1;
}
static int doit_print_monitor(ErtsMonitor *mon, void *vpcontext, Sint reds)
{
ErtsMonitorData *mdp;
PrintMonitorContext *pcontext = vpcontext;
fmtfn_t to = pcontext->to;
void *to_arg = pcontext->to_arg;
char *prefix = ", ";
mdp = erts_monitor_to_data(mon);
switch (mon->type) {
case ERTS_MON_TYPE_PROC:
case ERTS_MON_TYPE_PORT:
case ERTS_MON_TYPE_TIME_OFFSET:
case ERTS_MON_TYPE_DIST_PROC:
case ERTS_MON_TYPE_RESOURCE:
case ERTS_MON_TYPE_NODE:
if (pcontext->is_first) {
pcontext->is_first = 0;
prefix = "";
}
if (erts_monitor_is_target(mon)) {
if (mon->type != ERTS_MON_TYPE_RESOURCE)
erts_print(to, to_arg, "%s{from,%T,%T}", prefix, mon->other.item, mdp->ref);
else {
ErtsResource* rsrc = mon->other.ptr;
erts_print(to, to_arg, "%s{from,{%T,%T},%T}", prefix, rsrc->type->module,
rsrc->type->name, mdp->ref);
}
}
else {
if (!(mon->flags & ERTS_ML_FLG_NAME))
erts_print(to, to_arg, "%s{to,%T,%T}", prefix, mon->other.item, mdp->ref);
else {
ErtsMonitorDataExtended *mdep = (ErtsMonitorDataExtended *) mdp;
Eterm node;
if (mdep->dist)
node = mdep->dist->nodename;
else
node = erts_this_dist_entry->sysname;
erts_print(to, to_arg, "%s{to,{%T,%T},%T}", prefix, mdep->u.name,
node, mdp->ref);
}
}
break;
default:
/* ignore other monitors... */
break;
}
return 1;
}
/* Display info about an individual Erlang process */
void
print_process_info(fmtfn_t to, void *to_arg, Process *p, ErtsProcLocks orig_locks)
{
int garbing = 0;
int running = 0;
int exiting = 0;
Sint len;
struct saved_calls *scb;
erts_aint32_t state;
ErtsProcLocks locks = orig_locks;
/* display the PID */
erts_print(to, to_arg, "=proc:%T\n", p->common.id);
/* Display the state */
erts_print(to, to_arg, "State: ");
state = erts_atomic32_read_acqb(&p->state);
erts_dump_process_state(to, to_arg, state);
if (state & ERTS_PSFLG_GC) {
garbing = 1;
running = 1;
} else if (state & (ERTS_PSFLG_RUNNING
| ERTS_PSFLG_DIRTY_RUNNING))
running = 1;
if (state & ERTS_PSFLG_EXITING)
exiting = 1;
if (!(locks & ERTS_PROC_LOCK_MAIN)) {
locks |= ERTS_PROC_LOCK_MAIN;
if (ERTS_IS_CRASH_DUMPING) {
if (erts_proc_trylock(p, locks)) {
/* crash dumping and main lock taken, this probably means that
the process is doing a GC on a dirty-scheduler... so we cannot
do erts_proc_sig_fetch as that would potentially cause a segfault */
locks = 0;
}
} else {
erts_proc_lock(p, locks);
}
} else {
ERTS_ASSERT(locks == ERTS_PROC_LOCK_MAIN && "Only main lock should be held");
}
/*
* If the process is registered as a global process, display the
* registered name
*/
if (!ERTS_PROC_IS_EXITING(p) && p->common.u.alive.reg)
erts_print(to, to_arg, "Name: %T\n", p->common.u.alive.reg->name);
/*
* Display the initial function name
*/
erts_print(to, to_arg, "Spawned as: %T:%T/%bpu\n",
p->u.initial.module,
p->u.initial.function,
p->u.initial.arity);
if (p->current != NULL) {
if (running) {
erts_print(to, to_arg, "Last scheduled in for: ");
} else {
erts_print(to, to_arg, "Current call: ");
}
erts_print(to, to_arg, "%T:%T/%bpu\n",
p->current->module,
p->current->function,
p->current->arity);
}
erts_print(to, to_arg, "Spawned by: %T\n", p->parent);
if (locks & ERTS_PROC_LOCK_MAIN) {
erts_proc_lock(p, ERTS_PROC_LOCK_MSGQ);
len = erts_proc_sig_fetch(p);
erts_proc_unlock(p, ERTS_PROC_LOCK_MSGQ);
} else {
len = p->sig_qs.len;
}
erts_print(to, to_arg, "Message queue length: %d\n", len);
/* display the message queue only if there is anything in it
and we can do it safely */
if (!ERTS_IS_CRASH_DUMPING && p->sig_qs.first != NULL && !garbing
&& (locks & ERTS_PROC_LOCK_MAIN)) {
erts_print(to, to_arg, "Message queue: [");
ERTS_FOREACH_SIG_PRIVQS(
p, mp,
{
if (ERTS_SIG_IS_NON_MSG((ErtsSignal *) mp))
erts_print(to, to_arg, mp->next ? "%T," : "%T",
ERL_MESSAGE_TERM(mp));
});
erts_print(to, to_arg, "]\n");
}
{
int frags = 0;
ErlHeapFragment *m = p->mbuf;
while (m != NULL) {
frags++;
m = m->next;
}
erts_print(to, to_arg, "Number of heap fragments: %d\n", frags);
}
erts_print(to, to_arg, "Heap fragment data: %beu\n", MBUF_SIZE(p));
scb = ERTS_PROC_GET_SAVED_CALLS_BUF(p);
if (scb) {
int i, j;
erts_print(to, to_arg, "Last calls:");
for (i = 0; i < scb->n; i++) {
erts_print(to, to_arg, " ");
j = scb->cur - i - 1;
if (j < 0)
j += scb->len;
if (scb->ct[j] == &exp_send)
erts_print(to, to_arg, "send");
else if (scb->ct[j] == &exp_receive)
erts_print(to, to_arg, "'receive'");
else if (scb->ct[j] == &exp_timeout)
erts_print(to, to_arg, "timeout");
else
erts_print(to, to_arg, "%T:%T/%bpu\n",
scb->ct[j]->info.mfa.module,
scb->ct[j]->info.mfa.function,
scb->ct[j]->info.mfa.arity);
}
erts_print(to, to_arg, "\n");
}
/* display the links only if there are any*/
if (!exiting && (ERTS_P_LINKS(p) || ERTS_P_MONITORS(p) || ERTS_P_LT_MONITORS(p))) {
PrintMonitorContext context = {1, to, to_arg};
erts_print(to, to_arg,"Link list: [");
erts_link_tree_foreach(ERTS_P_LINKS(p), doit_print_link, &context);
erts_monitor_tree_foreach(ERTS_P_MONITORS(p), doit_print_monitor, &context);
erts_monitor_list_foreach(ERTS_P_LT_MONITORS(p), doit_print_monitor, &context);
erts_print(to, to_arg,"]\n");
}
if (!ERTS_IS_CRASH_DUMPING) {
/* and the dictionary */
if (p->dictionary != NULL && !garbing) {
erts_print(to, to_arg, "Dictionary: ");
erts_dictionary_dump(to, to_arg, p->dictionary);
erts_print(to, to_arg, "\n");
}
}
/* print the number of reductions etc */
erts_print(to, to_arg, "Reductions: %beu\n", p->reds);
erts_print(to, to_arg, "Stack+heap: %beu\n", p->heap_sz);
erts_print(to, to_arg, "OldHeap: %bpu\n",
(OLD_HEAP(p) == NULL) ? 0 : (OLD_HEND(p) - OLD_HEAP(p)) );
erts_print(to, to_arg, "Heap unused: %bpu\n", (p->hend - p->htop));
erts_print(to, to_arg, "OldHeap unused: %bpu\n",
(OLD_HEAP(p) == NULL) ? 0 : (OLD_HEND(p) - OLD_HTOP(p)) );
erts_print(to, to_arg, "BinVHeap: %b64u\n", p->off_heap.overhead);
erts_print(to, to_arg, "OldBinVHeap: %b64u\n", BIN_OLD_VHEAP(p));
erts_print(to, to_arg, "BinVHeap unused: %b64u\n",
BIN_VHEAP_SZ(p) - p->off_heap.overhead);
if (BIN_OLD_VHEAP_SZ(p) >= BIN_OLD_VHEAP(p)) {
erts_print(to, to_arg, "OldBinVHeap unused: %b64u\n",
BIN_OLD_VHEAP_SZ(p) - BIN_OLD_VHEAP(p));
} else {
erts_print(to, to_arg, "OldBinVHeap unused: overflow\n");
}
erts_print(to, to_arg, "Memory: %beu\n", erts_process_memory(p, !0));
if (garbing) {
print_garb_info(to, to_arg, p);
}
if (ERTS_IS_CRASH_DUMPING) {
erts_program_counter_info(to, to_arg, p);
} else {
erts_print(to, to_arg, "Stack dump:\n");
if (!garbing)
erts_stack_dump(to, to_arg, p);
}
/* Display all states */
erts_print(to, to_arg, "Internal State: ");
erts_dump_extended_process_state(to, to_arg, state);
erts_proc_unlock(p, locks & ~orig_locks);
}
static void
print_garb_info(fmtfn_t to, void *to_arg, Process* p)
{
/* A scheduler is probably concurrently doing gc... */
if (!ERTS_IS_CRASH_DUMPING)
return;
erts_print(to, to_arg, "New heap start: %bpX\n", p->heap);
erts_print(to, to_arg, "New heap top: %bpX\n", p->htop);
erts_print(to, to_arg, "Stack top: %bpX\n", p->stop);
erts_print(to, to_arg, "Stack end: %bpX\n", p->hend);
erts_print(to, to_arg, "Old heap start: %bpX\n", OLD_HEAP(p));
erts_print(to, to_arg, "Old heap top: %bpX\n", OLD_HTOP(p));
erts_print(to, to_arg, "Old heap end: %bpX\n", OLD_HEND(p));
}
void
info(fmtfn_t to, void *to_arg)
{
erts_memory(&to, to_arg, NULL, THE_NON_VALUE);
atom_info(to, to_arg);
module_info(to, to_arg);
export_info(to, to_arg);
register_info(to, to_arg);
erts_fun_info(to, to_arg);
erts_node_table_info(to, to_arg);
erts_dist_table_info(to, to_arg);
erts_allocated_areas(&to, to_arg, NULL);
erts_allocator_info(to, to_arg);
}
static int code_size(struct erl_module_instance* modi)
{
int size = modi->code_length;
if (modi->code_hdr) {
ErtsLiteralArea* lit = modi->code_hdr->literal_area;
if (lit)
size += (lit->end - lit->start) * sizeof(Eterm);
}
return size;
}
void
loaded(fmtfn_t to, void *to_arg)
{
int i;
int old = 0;
int cur = 0;
BeamCodeHeader* code;
Module* modp;
ErtsCodeIndex code_ix;
code_ix = erts_active_code_ix();
erts_rlock_old_code(code_ix);
/*
* Calculate and print totals.
*/
for (i = 0; i < module_code_size(code_ix); i++) {
if ((modp = module_code(i, code_ix)) != NULL) {
cur += code_size(&modp->curr);
old += code_size(&modp->old);
}
}
erts_print(to, to_arg, "Current code: %d\n", cur);
erts_print(to, to_arg, "Old code: %d\n", old);
/*
* Print one line per module.
*/
for (i = 0; i < module_code_size(code_ix); i++) {
modp = module_code(i, code_ix);
if (!ERTS_IS_CRASH_DUMPING) {
/*
* Interactive dump; keep it brief.
*/
if (modp != NULL && ((modp->curr.code_length != 0) ||
(modp->old.code_length != 0))) {
erts_print(to, to_arg, "%T %d", make_atom(modp->module),
code_size(&modp->curr));
if (modp->old.code_length != 0)
erts_print(to, to_arg, " (%d old)", code_size(&modp->old));
erts_print(to, to_arg, "\n");
}
} else {
/*
* To crash dump; make it parseable.
*/
if (modp != NULL && ((modp->curr.code_length != 0) ||
(modp->old.code_length != 0))) {
erts_print(to, to_arg, "=mod:");
erts_print(to, to_arg, "%T", make_atom(modp->module));
erts_print(to, to_arg, "\n");
erts_print(to, to_arg, "Current size: %d\n",
code_size(&modp->curr));
code = modp->curr.code_hdr;
if (code != NULL && code->attr_ptr) {
erts_print(to, to_arg, "Current attributes: ");
dump_attributes(to, to_arg, code->attr_ptr,
code->attr_size);
}
if (code != NULL && code->compile_ptr) {
erts_print(to, to_arg, "Current compilation info: ");
dump_attributes(to, to_arg, code->compile_ptr,
code->compile_size);
}
if (modp->old.code_length != 0) {
erts_print(to, to_arg, "Old size: %d\n", code_size(&modp->old));
code = modp->old.code_hdr;
if (code->attr_ptr) {
erts_print(to, to_arg, "Old attributes: ");
dump_attributes(to, to_arg, code->attr_ptr,
code->attr_size);
}
if (code->compile_ptr) {
erts_print(to, to_arg, "Old compilation info: ");
dump_attributes(to, to_arg, code->compile_ptr,
code->compile_size);
}
}
}
}
}
erts_runlock_old_code(code_ix);
}
static void
dump_attributes(fmtfn_t to, void *to_arg, byte* ptr, int size)
{
erts_print_base64(to, to_arg, ptr, size);
erts_print(to, to_arg, "\n");
}
void
do_break(void)
{
int i;
#ifdef __WIN32__
char *mode; /* enough for storing "window" */
/* check if we're in console mode and, if so,
halt immediately if break is called */
mode = erts_read_env("ERL_CONSOLE_MODE");
if (mode && sys_strcmp(mode, "window") != 0)
erts_exit(0, "");
erts_free_read_env(mode);
#endif /* __WIN32__ */
ASSERT(erts_thr_progress_is_blocking());
erts_printf("\n"
"BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded\n"
" (v)ersion (k)ill (D)b-tables (d)istribution\n");
while (1) {
if ((i = sys_get_key(0)) <= 0)
erts_exit(0, "");
switch (i) {
case 'q':
case 'a':
case '*': /*
* The asterisk is an read error on windows,
* where sys_get_key isn't that great in console mode.
* The usual reason for a read error is Ctrl-C. Treat this as
* 'a' to avoid infinite loop.
*/
erts_exit(0, "");
case 'A': /* Halt generating crash dump */
erts_exit(ERTS_ERROR_EXIT, "Crash dump requested by user");
case 'c':
return;
case 'p':
process_info(ERTS_PRINT_STDOUT, NULL);
return;
case 'm':
return;
case 'o':
port_info(ERTS_PRINT_STDOUT, NULL);
return;
case 'i':
info(ERTS_PRINT_STDOUT, NULL);
return;
case 'l':
loaded(ERTS_PRINT_STDOUT, NULL);
return;
case 'v':
erts_printf("Erlang (%s) emulator version "
ERLANG_VERSION "\n",
EMULATOR);
#if ERTS_SAVED_COMPILE_TIME
erts_printf("Compiled on " ERLANG_COMPILE_DATE "\n");
#endif
return;
case 'd':
distribution_info(ERTS_PRINT_STDOUT, NULL);
return;
case 'D':
db_info(ERTS_PRINT_STDOUT, NULL, 1);
return;
case 'k':
process_killer();
return;
#ifdef OPPROF
case 'X':
dump_frequencies();
return;
case 'x':
{
int i;
for (i = 0; i <= HIGHEST_OP; i++) {
if (opc[i].name != NULL) {
erts_printf("%-16s %8d\n", opc[i].name, opc[i].count);
}
}
}
return;
case 'z':
{
int i;
for (i = 0; i <= HIGHEST_OP; i++)
opc[i].count = 0;
}
return;
#endif
#ifdef DEBUG
case 't':
/* erts_p_slpq(); */
return;
case 'b':
bin_check();
return;
case 'C':
abort();
#endif
case '\n':
continue;
default:
erts_printf("Eh?\n\n");
}
}
}
#ifdef OPPROF
static void
dump_frequencies(void)
{
int i;
FILE* fp;
time_t now;
static char name[] = "op_freq.dump";
fp = fopen(name, "w");
if (fp == NULL) {
fprintf(stderr, "Failed to open %s for writing\n", name);
return;
}
time(&now);
fprintf(fp, "# Generated %s\n", ctime(&now));
for (i = 0; i <= HIGHEST_OP; i++) {
if (opc[i].name != NULL) {
fprintf(fp, "%s %d\n", opc[i].name, opc[i].count);
}
}
fclose(fp);
erts_printf("Frequencies dumped to %s\n", name);
}
#endif
#ifdef DEBUG
static void
bin_check(void)
{
Process *rp;
struct erl_off_heap_header* hdr;
int i, printed = 0, max = erts_ptab_max(&erts_proc);
for (i=0; i < max; i++) {
rp = erts_pix2proc(i);
if (!rp)
continue;
for (hdr = rp->off_heap.first; hdr; hdr = hdr->next) {
if (hdr->thing_word == HEADER_PROC_BIN) {
ProcBin *bp = (ProcBin*) hdr;
if (!printed) {
erts_printf("Process %T holding binary data \n", rp->common.id);
printed = 1;
}
erts_printf("%p orig_size: %bpd, norefs = %bpd\n",
bp->val,
bp->val->orig_size,
erts_refc_read(&bp->val->intern.refc, 1));
}
}
if (printed) {
erts_printf("--------------------------------------\n");
printed = 0;
}
}
/* db_bin_check() has to be rewritten for the AVL trees... */
/*db_bin_check();*/
}
#endif
static Sint64 crash_dump_limit = ERTS_SINT64_MAX;
static Sint64 crash_dump_written = 0;
typedef struct LimitedWriterInfo_ {
fmtfn_t to;
void* to_arg;
} LimitedWriterInfo;
static int
crash_dump_limited_writer(void* vfdp, char* buf, size_t len)
{
const char stop_msg[] = "\n=abort:CRASH DUMP SIZE LIMIT REACHED\n";
LimitedWriterInfo* lwi = (LimitedWriterInfo *) vfdp;
crash_dump_written += len;
if (crash_dump_written <= crash_dump_limit) {
return lwi->to(lwi->to_arg, buf, len);
}
len -= (crash_dump_written - crash_dump_limit);
lwi->to(lwi->to_arg, buf, len);
lwi->to(lwi->to_arg, (char*)stop_msg, sizeof(stop_msg)-1);
if (lwi->to == &erts_write_fp) {
fclose((FILE *) lwi->to_arg);
}
/* We assume that crash dump was called from erts_exit_vv() */
erts_exit_epilogue();
}
/* XXX THIS SHOULD BE IN SYSTEM !!!! */
void
erl_crash_dump_v(char *file, int line, char* fmt, va_list args)
{
ErtsThrPrgrData tpd_buf; /* in case we aren't a managed thread... */
int fd;
size_t envsz;
time_t now;
char env[21]; /* enough to hold any 64-bit integer */
size_t dumpnamebufsize = MAXPATHLEN;
char dumpnamebuf[MAXPATHLEN];
char* dumpname;
int secs;
int env_erl_crash_dump_seconds_set = 1;
int i;
fmtfn_t to = &erts_write_fd;
void* to_arg;
FILE* fp = 0;
LimitedWriterInfo lwi;
static char* write_buffer; /* 'static' to avoid a leak warning in valgrind */
if (ERTS_SOMEONE_IS_CRASH_DUMPING)
return;
/* Order all managed threads to block, this has to be done
first to guarantee that this is the only thread to generate
crash dump. */
erts_thr_progress_fatal_error_block(&tpd_buf);
#ifdef ERTS_SYS_SUSPEND_SIGNAL
/*
* We suspend all scheduler threads so that we can dump some
* data about the currently running processes and scheduler data.
* We have to be very very careful when doing this as the schedulers
* could be anywhere.
*/
sys_init_suspend_handler();
for (i = 0; i < erts_no_schedulers; i++) {
erts_tid_t tid = ERTS_SCHEDULER_IX(i)->tid;
if (!erts_equal_tids(tid,erts_thr_self()))
sys_thr_suspend(tid);
}
#endif
/* Allow us to pass certain places without locking... */
erts_atomic32_set_mb(&erts_writing_erl_crash_dump, 1);
erts_tsd_set(erts_is_crash_dumping_key, (void *) 1);
envsz = sizeof(env);
/* ERL_CRASH_DUMP_SECONDS not set
* if we have a heart port, break immediately
* otherwise dump crash indefinitely (until crash is complete)
* same as ERL_CRASH_DUMP_SECONDS = 0
* - do not write dump
* - do not set an alarm
* - break immediately
*
* ERL_CRASH_DUMP_SECONDS = 0
* - do not write dump
* - do not set an alarm
* - break immediately
*
* ERL_CRASH_DUMP_SECONDS < 0
* - do not set alarm
* - write dump until done
*
* ERL_CRASH_DUMP_SECONDS = S (and S positive)
* - Don't dump file forever
* - set alarm (set in sys)
* - write dump until alarm or file is written completely
*/
if (erts_sys_explicit_8bit_getenv("ERL_CRASH_DUMP_SECONDS", env, &envsz) == 1) {
env_erl_crash_dump_seconds_set = 1;
secs = atoi(env);
} else {
env_erl_crash_dump_seconds_set = 0;
secs = -1;
}
if (secs == 0) {
return;
}
/* erts_sys_prepare_crash_dump returns 1 if heart port is found, otherwise 0
* If we don't find heart (0) and we don't have ERL_CRASH_DUMP_SECONDS set
* we should continue writing a dump
*
* beware: secs -1 means no alarm
*/
if (erts_sys_prepare_crash_dump(secs) && !env_erl_crash_dump_seconds_set ) {
return;
}
crash_dump_limit = ERTS_SINT64_MAX;
envsz = sizeof(env);
if (erts_sys_explicit_8bit_getenv("ERL_CRASH_DUMP_BYTES", env, &envsz) == 1) {
Sint64 limit;
char* endptr;
errno = 0;
limit = ErtsStrToSint64(env, &endptr, 10);
if (errno == 0 && limit >= 0 && endptr != env && *endptr == 0) {
if (limit == 0)
return;
crash_dump_limit = limit;
to = &crash_dump_limited_writer;
}
}
if (erts_sys_explicit_8bit_getenv("ERL_CRASH_DUMP",&dumpnamebuf[0],&dumpnamebufsize) != 1)
dumpname = "erl_crash.dump";
else
dumpname = &dumpnamebuf[0];
erts_fprintf(stderr,"\nCrash dump is being written to: %s...", dumpname);
fd = open(dumpname,O_WRONLY | O_CREAT | O_TRUNC,0640);
if (fd < 0)
return; /* Can't create the crash dump, skip it */
/*
* Wrap into a FILE* so that we can use buffered output. Set an
* explicit buffer to make sure the first write does not fail because
* of a failure to allocate a buffer.
*/
write_buffer = (char *) erts_alloc_fnf(ERTS_ALC_T_TMP, WRITE_BUFFER_SIZE);
if (write_buffer && (fp = fdopen(fd, "w")) != NULL) {
setvbuf(fp, write_buffer, _IOFBF, WRITE_BUFFER_SIZE);
lwi.to = &erts_write_fp;
lwi.to_arg = (void*)fp;
} else {
lwi.to = &erts_write_fd;
lwi.to_arg = (void*)&fd;
}
if (to == &crash_dump_limited_writer) {
to_arg = (void *) &lwi;
} else {
to = lwi.to;
to_arg = lwi.to_arg;
}
time(&now);
erts_cbprintf(to, to_arg, "=erl_crash_dump:0.5\n%s", ctime(&now));
if (file != NULL)
erts_cbprintf(to, to_arg, "The error occurred in file %s, line %d\n", file, line);
if (fmt != NULL && *fmt != '\0') {
erts_cbprintf(to, to_arg, "Slogan: ");
erts_vcbprintf(to, to_arg, fmt, args);
}
erts_cbprintf(to, to_arg, "System version: ");
erts_print_system_version(to, to_arg, NULL);
#if ERTS_SAVED_COMPILE_TIME
erts_cbprintf(to, to_arg, "%s\n", "Compiled: " ERLANG_COMPILE_DATE);
#endif
erts_cbprintf(to, to_arg, "Taints: ");
erts_print_nif_taints(to, to_arg);
erts_cbprintf(to, to_arg, "Atoms: %d\n", atom_table_size());
/* We want to note which thread it was that called erts_exit */
if (erts_get_scheduler_data()) {
erts_cbprintf(to, to_arg, "Calling Thread: scheduler:%d\n",
erts_get_scheduler_data()->no);
} else {
if (!erts_thr_getname(erts_thr_self(), dumpnamebuf, MAXPATHLEN))
erts_cbprintf(to, to_arg, "Calling Thread: %s\n", dumpnamebuf);
else
erts_cbprintf(to, to_arg, "Calling Thread: %p\n", erts_thr_self());
}
#if defined(ERTS_HAVE_TRY_CATCH)
/*
* erts_print_scheduler_info is not guaranteed to be safe to call
* here for all schedulers as we may have suspended a scheduler
* in the middle of updating the STACK_TOP and STACK_START
* variables and thus when scanning the stack we could get
* segmentation faults. We protect against this very unlikely
* scenario by using the ERTS_SYS_TRY_CATCH.
*/
for (i = 0; i < erts_no_schedulers; i++) {
ERTS_SYS_TRY_CATCH(
erts_print_scheduler_info(to, to_arg, ERTS_SCHEDULER_IX(i)),
erts_cbprintf(to, to_arg, "** crashed **\n"));
}
for (i = 0; i < erts_no_dirty_cpu_schedulers; i++) {
ERTS_SYS_TRY_CATCH(
erts_print_scheduler_info(to, to_arg, ERTS_DIRTY_CPU_SCHEDULER_IX(i)),
erts_cbprintf(to, to_arg, "** crashed **\n"));
}
erts_cbprintf(to, to_arg, "=dirty_cpu_run_queue\n");
erts_print_run_queue_info(to, to_arg, ERTS_DIRTY_CPU_RUNQ);
for (i = 0; i < erts_no_dirty_io_schedulers; i++) {
ERTS_SYS_TRY_CATCH(
erts_print_scheduler_info(to, to_arg, ERTS_DIRTY_IO_SCHEDULER_IX(i)),
erts_cbprintf(to, to_arg, "** crashed **\n"));
}
erts_cbprintf(to, to_arg, "=dirty_io_run_queue\n");
erts_print_run_queue_info(to, to_arg, ERTS_DIRTY_IO_RUNQ);
#endif
#ifdef ERTS_SYS_SUSPEND_SIGNAL
/* We resume all schedulers so that we are in a known safe state
when we write the rest of the crash dump */
for (i = 0; i < erts_no_schedulers; i++) {
erts_tid_t tid = ERTS_SCHEDULER_IX(i)->tid;
if (!erts_equal_tids(tid,erts_thr_self()))
sys_thr_resume(tid);
}
#endif
/*
* Wait for all managed threads to block. If all threads haven't blocked
* after a minute, we go anyway and hope for the best...
*
* We do not release system again. We expect an exit() or abort() after
* dump has been written.
*/
erts_thr_progress_fatal_error_wait(60000);
/* Either worked or not... */
#ifndef ERTS_HAVE_TRY_CATCH
/* This is safe to call here, as all schedulers are blocked */
for (i = 0; i < erts_no_schedulers; i++) {
erts_print_scheduler_info(to, to_arg, ERTS_SCHEDULER_IX(i));
}
#endif
info(to, to_arg); /* General system info */
if (erts_ptab_initialized(&erts_proc))
process_info(to, to_arg); /* Info about each process and port */
db_info(to, to_arg, 0);
erts_print_bif_timer_info(to, to_arg);
distribution_info(to, to_arg);
erts_cbprintf(to, to_arg, "=loaded_modules\n");
loaded(to, to_arg);
erts_dump_fun_entries(to, to_arg);
erts_deep_process_dump(to, to_arg);
erts_cbprintf(to, to_arg, "=atoms\n");
dump_atoms(to, to_arg);
erts_cbprintf(to, to_arg, "=end\n");
if (fp) {
fclose(fp);
}
close(fd);
erts_fprintf(stderr,"done\n");
}
void
erts_print_base64(fmtfn_t to, void *to_arg, byte* src, Uint size)
{
static const byte base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
while (size >= 3) {
erts_putc(to, to_arg, base64_chars[src[0] >> 2]);
erts_putc(to, to_arg, base64_chars[((src[0] & 0x03) << 4) | (src[1] >> 4)]);
erts_putc(to, to_arg, base64_chars[((src[1] & 0x0f) << 2) | (src[2] >> 6)]);
erts_putc(to, to_arg, base64_chars[src[2] & 0x3f]);
size -= 3;
src += 3;
}
if (size == 1) {
erts_putc(to, to_arg, base64_chars[src[0] >> 2]);
erts_putc(to, to_arg, base64_chars[(src[0] & 0x03) << 4]);
erts_print(to, to_arg, "==");
} else if (size == 2) {
erts_putc(to, to_arg, base64_chars[src[0] >> 2]);
erts_putc(to, to_arg, base64_chars[((src[0] & 0x03) << 4) | (src[1] >> 4)]);
erts_putc(to, to_arg, base64_chars[(src[1] & 0x0f) << 2]);
erts_putc(to, to_arg, '=');
}
}