aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/beam/break.c
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/beam/break.c')
-rw-r--r--erts/emulator/beam/break.c747
1 files changed, 747 insertions, 0 deletions
diff --git a/erts/emulator/beam/break.c b/erts/emulator/beam/break.c
new file mode 100644
index 0000000000..5ea47e16f5
--- /dev/null
+++ b/erts/emulator/beam/break.c
@@ -0,0 +1,747 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 1996-2009. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Erlang Public License,
+ * Version 1.1, (the "License"); you may not use this file except in
+ * compliance with the License. You should have received a copy of the
+ * Erlang Public License along with this software. If not, it can be
+ * retrieved online at http://www.erlang.org/.
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights 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_instrument.h"
+#include "erl_bif_timer.h"
+
+#ifdef _OSE_
+#include "time.h"
+#endif
+
+/* 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);
+void erl_crash_dump(char* file, int line, char* fmt, ...);
+
+#ifdef DEBUG
+static void bin_check(void);
+#endif
+
+static void print_garb_info(int to, void *to_arg, Process* p);
+#ifdef OPPROF
+static void dump_frequencies(void);
+#endif
+
+static void dump_attributes(int to, void *to_arg, byte* ptr, int size);
+
+extern char* erts_system_version[];
+
+static void
+port_info(int to, void *to_arg)
+{
+ int i;
+ for (i = 0; i < erts_max_ports; i++)
+ print_port_info(to, to_arg, i);
+}
+
+void
+process_info(int to, void *to_arg)
+{
+ int i;
+ for (i = 0; i < erts_max_processes; i++) {
+ if ((process_tab[i] != NULL) && (process_tab[i]->i != ENULL)) {
+ if (process_tab[i]->status != P_EXITING)
+ print_process_info(to, to_arg, process_tab[i]);
+ }
+ }
+
+ port_info(to, to_arg);
+}
+
+static void
+process_killer(void)
+{
+ int i, j;
+ Process* rp;
+
+ erts_printf("\n\nProcess Information\n\n");
+ erts_printf("--------------------------------------------------\n");
+ for (i = erts_max_processes-1; i >= 0; i--) {
+ if (((rp = process_tab[i]) != NULL) && rp->i != ENULL) {
+ int br;
+ print_process_info(ERTS_PRINT_STDOUT, NULL, rp);
+ erts_printf("(k)ill (n)ext (r)eturn:\n");
+ while(1) {
+ if ((j = sys_get_key(0)) <= 0)
+ halt_0(0);
+ switch(j) {
+ case 'k':
+ if (rp->status == P_WAITING) {
+ Uint32 rp_locks = ERTS_PROC_LOCKS_XSIG_SEND;
+ erts_smp_proc_inc_refc(rp);
+ erts_smp_proc_lock(rp, rp_locks);
+ (void) erts_send_exit_signal(NULL,
+ NIL,
+ rp,
+ &rp_locks,
+ am_kill,
+ NIL,
+ NULL,
+ 0);
+ erts_smp_proc_unlock(rp, rp_locks);
+ erts_smp_proc_dec_refc(rp);
+ }
+ else
+ erts_printf("Can only kill WAITING processes this way\n");
+
+ case 'n': br = 1; break;
+ case 'r': return;
+ default: return;
+ }
+ if (br == 1) break;
+ }
+ }
+ }
+}
+
+typedef struct {
+ int is_first;
+ int to;
+ void *to_arg;
+} PrintMonitorContext;
+
+static void doit_print_link(ErtsLink *lnk, void *vpcontext)
+{
+ PrintMonitorContext *pcontext = vpcontext;
+ int to = pcontext->to;
+ void *to_arg = pcontext->to_arg;
+
+ if (pcontext->is_first) {
+ pcontext->is_first = 0;
+ erts_print(to, to_arg, "%T", lnk->pid);
+ } else {
+ erts_print(to, to_arg, ", %T", lnk->pid);
+ }
+}
+
+
+static void doit_print_monitor(ErtsMonitor *mon, void *vpcontext)
+{
+ PrintMonitorContext *pcontext = vpcontext;
+ int to = pcontext->to;
+ void *to_arg = pcontext->to_arg;
+ char *prefix = ", ";
+
+ if (pcontext->is_first) {
+ pcontext->is_first = 0;
+ prefix = "";
+ }
+
+ if (mon->type == MON_ORIGIN) {
+ if (is_atom(mon->pid)) { /* dist by name */
+ ASSERT(is_node_name_atom(mon->pid));
+ erts_print(to, to_arg, "%s{to,{%T,%T},%T}", prefix, mon->name,
+ mon->pid, mon->ref);
+ erts_print(to, to_arg,"}");
+ } else if (is_atom(mon->name)){ /* local by name */
+ erts_print(to, to_arg, "%s{to,{%T,%T},%T}", prefix, mon->name,
+ erts_this_dist_entry->sysname, mon->ref);
+ } else { /* local and distributed by pid */
+ erts_print(to, to_arg, "%s{to,%T,%T}", prefix, mon->pid, mon->ref);
+ }
+ } else { /* MON_TARGET */
+ erts_print(to, to_arg, "%s{from,%T,%T}", prefix, mon->pid, mon->ref);
+ }
+}
+
+/* Display info about an individual Erlang process */
+void
+print_process_info(int to, void *to_arg, Process *p)
+{
+ int garbing = 0;
+ int running = 0;
+ struct saved_calls *scb;
+
+ /* display the PID */
+ erts_print(to, to_arg, "=proc:%T\n", p->id);
+
+ /* Display the state */
+ erts_print(to, to_arg, "State: ");
+ switch (p->status) {
+ case P_FREE:
+ erts_print(to, to_arg, "Non Existing\n"); /* Should never happen */
+ break;
+ case P_RUNABLE:
+ erts_print(to, to_arg, "Scheduled\n");
+ break;
+ case P_WAITING:
+ erts_print(to, to_arg, "Waiting\n");
+ break;
+ case P_SUSPENDED:
+ erts_print(to, to_arg, "Suspended\n");
+ break;
+ case P_RUNNING:
+ erts_print(to, to_arg, "Running\n");
+ running = 1;
+ break;
+ case P_EXITING:
+ erts_print(to, to_arg, "Exiting\n");
+ break;
+ case P_GARBING:
+ erts_print(to, to_arg, "Garbing\n");
+ garbing = 1;
+ running = 1;
+ break;
+ }
+
+ /*
+ * If the process is registered as a global process, display the
+ * registered name
+ */
+ if (p->reg != NULL)
+ erts_print(to, to_arg, "Name: %T\n", p->reg->name);
+
+ /*
+ * Display the initial function name
+ */
+ erts_print(to, to_arg, "Spawned as: %T:%T/%bpu\n",
+ p->initial[INITIAL_MOD],
+ p->initial[INITIAL_FUN],
+ p->initial[INITIAL_ARI]);
+
+ 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[0],
+ p->current[1],
+ p->current[2]);
+ }
+
+ erts_print(to, to_arg, "Spawned by: %T\n", p->parent);
+
+ erts_print(to, to_arg, "Started: %s", ctime((time_t*)&p->started.tv_sec));
+ ERTS_SMP_MSGQ_MV_INQ2PRIVQ(p);
+ erts_print(to, to_arg, "Message queue length: %d\n", p->msg.len);
+
+ /* display the message queue only if there is anything in it */
+ if (!ERTS_IS_CRASH_DUMPING && p->msg.first != NULL && !garbing) {
+ ErlMessage* mp;
+ erts_print(to, to_arg, "Message queue: [");
+ for (mp = p->msg.first; mp; mp = mp->next)
+ erts_print(to, to_arg, mp->next ? "%T," : "%T", ERL_MESSAGE_TERM(mp));
+ erts_print(to, to_arg, "]\n");
+ }
+
+ {
+ long s = 0;
+ int frags = 0;
+ ErlHeapFragment *m = p->mbuf;
+ while (m != NULL) {
+ frags++;
+ s += m->size;
+ m = m->next;
+ }
+ erts_print(to, to_arg, "Number of heap fragments: %d\n", frags);
+ }
+ erts_print(to, to_arg, "Heap fragment data: %bpu\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]->code[0],
+ scb->ct[j]->code[1],
+ scb->ct[j]->code[2]);
+ }
+ erts_print(to, to_arg, "\n");
+ }
+
+ /* display the links only if there are any*/
+ if (p->nlinks != NULL || p->monitors != NULL) {
+ PrintMonitorContext context = {1,to};
+ erts_print(to, to_arg,"Link list: [");
+ erts_doforall_links(p->nlinks, &doit_print_link, &context);
+ erts_doforall_monitors(p->monitors, &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: %bpu\n", p->reds);
+
+ erts_print(to, to_arg, "Stack+heap: %bpu\n", p->heap_sz);
+ erts_print(to, to_arg, "OldHeap: %bpu\n",
+ (OLD_HEAP(p) == NULL) ? 0 :
+ (unsigned)(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_HEAP(p)) );
+
+ 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");
+#ifdef ERTS_SMP
+ if (!garbing)
+#endif
+ erts_stack_dump(to, to_arg, p);
+ }
+}
+
+static void
+print_garb_info(int to, void *to_arg, Process* p)
+{
+ /* ERTS_SMP: A scheduler is probably concurrently doing gc... */
+#ifndef ERTS_SMP
+ 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));
+#endif
+}
+
+void
+info(int 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);
+
+}
+
+void
+loaded(int to, void *to_arg)
+{
+ int i;
+ int old = 0;
+ int cur = 0;
+ Eterm* code;
+
+ /*
+ * Calculate and print totals.
+ */
+ for (i = 0; i < module_code_size(); i++) {
+ if (module_code(i) != NULL &&
+ ((module_code(i)->code_length != 0) ||
+ (module_code(i)->old_code_length != 0))) {
+ cur += module_code(i)->code_length;
+ if (module_code(i)->old_code_length != 0) {
+ old += module_code(i)->old_code_length;
+ }
+ }
+ }
+ 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(); i++) {
+ if (!ERTS_IS_CRASH_DUMPING) {
+ /*
+ * Interactive dump; keep it brief.
+ */
+ if (module_code(i) != NULL &&
+ ((module_code(i)->code_length != 0) ||
+ (module_code(i)->old_code_length != 0))) {
+ erts_print(to, to_arg, "%T", make_atom(module_code(i)->module));
+ cur += module_code(i)->code_length;
+ erts_print(to, to_arg, " %d", module_code(i)->code_length );
+ if (module_code(i)->old_code_length != 0) {
+ erts_print(to, to_arg, " (%d old)",
+ module_code(i)->old_code_length );
+ old += module_code(i)->old_code_length;
+ }
+ erts_print(to, to_arg, "\n");
+ }
+ } else {
+ /*
+ * To crash dump; make it parseable.
+ */
+ if (module_code(i) != NULL &&
+ ((module_code(i)->code_length != 0) ||
+ (module_code(i)->old_code_length != 0))) {
+ erts_print(to, to_arg, "=mod:");
+ erts_print(to, to_arg, "%T", make_atom(module_code(i)->module));
+ erts_print(to, to_arg, "\n");
+ erts_print(to, to_arg, "Current size: %d\n",
+ module_code(i)->code_length);
+ code = module_code(i)->code;
+ if (code != NULL && code[MI_ATTR_PTR]) {
+ erts_print(to, to_arg, "Current attributes: ");
+ dump_attributes(to, to_arg, (byte *) code[MI_ATTR_PTR],
+ code[MI_ATTR_SIZE]);
+ }
+ if (code != NULL && code[MI_COMPILE_PTR]) {
+ erts_print(to, to_arg, "Current compilation info: ");
+ dump_attributes(to, to_arg, (byte *) code[MI_COMPILE_PTR],
+ code[MI_COMPILE_SIZE]);
+ }
+
+ if (module_code(i)->old_code_length != 0) {
+ erts_print(to, to_arg, "Old size: %d\n", module_code(i)->old_code_length);
+ code = module_code(i)->old_code;
+ if (code[MI_ATTR_PTR]) {
+ erts_print(to, to_arg, "Old attributes: ");
+ dump_attributes(to, to_arg, (byte *) code[MI_ATTR_PTR],
+ code[MI_ATTR_SIZE]);
+ }
+ if (code[MI_COMPILE_PTR]) {
+ erts_print(to, to_arg, "Old compilation info: ");
+ dump_attributes(to, to_arg, (byte *) code[MI_COMPILE_PTR],
+ code[MI_COMPILE_SIZE]);
+ }
+ }
+ }
+ }
+ }
+}
+
+
+static void
+dump_attributes(int to, void *to_arg, byte* ptr, int size)
+{
+ while (size-- > 0) {
+ erts_print(to, to_arg, "%02X", *ptr++);
+ }
+ 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 && strcmp(mode, "window") != 0)
+ erl_exit(0, "");
+ erts_free_read_env(mode);
+#endif /* __WIN32__ */
+
+ 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)
+ erl_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.
+ */
+ erl_exit(0, "");
+ case 'A': /* Halt generating crash dump */
+ erl_exit(1, "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);
+ erts_printf("Compiled on " ERLANG_COMPILE_DATE "\n");
+ 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':
+ 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;
+ ProcBin *bp;
+ int i, printed;
+
+ for (i=0; i < erts_max_processes; i++) {
+ if ((rp = process_tab[i]) == NULL)
+ continue;
+ if (!(bp = rp->off_heap.mso))
+ continue;
+ printed = 0;
+ while (bp) {
+ if (printed == 0) {
+ erts_printf("Process %T holding binary data \n", rp->id);
+ printed = 1;
+ }
+ erts_printf("0x%08lx orig_size: %ld, norefs = %ld\n",
+ (unsigned long)bp->val,
+ (long)bp->val->orig_size,
+ erts_smp_atomic_read(&bp->val->refc));
+
+ bp = bp->next;
+ }
+ if (printed == 1)
+ erts_printf("--------------------------------------\n");
+ }
+ /* db_bin_check() has to be rewritten for the AVL trees... */
+ /*db_bin_check();*/
+}
+
+#endif
+
+/* XXX THIS SHOULD BE IN SYSTEM !!!! */
+void
+erl_crash_dump_v(char *file, int line, char* fmt, va_list args)
+{
+ int fd;
+ time_t now;
+ size_t dumpnamebufsize = MAXPATHLEN;
+ char dumpnamebuf[MAXPATHLEN];
+ char* dumpname;
+
+ if (ERTS_IS_CRASH_DUMPING)
+ return;
+
+ /* Wait for all 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.
+ *
+ * NOTE: We allow gc therefore it is important not to lock *any*
+ * process locks.
+ */
+ erts_smp_emergency_block_system(60000, ERTS_BS_FLG_ALLOW_GC);
+ /* Either worked or not... */
+
+ /* Allow us to pass certain places without locking... */
+#ifdef ERTS_SMP
+ erts_smp_atomic_inc(&erts_writing_erl_crash_dump);
+#else
+ erts_writing_erl_crash_dump = 1;
+#endif
+
+ erts_sys_prepare_crash_dump();
+
+ if (erts_sys_getenv("ERL_CRASH_DUMP",&dumpnamebuf[0],&dumpnamebufsize) != 0)
+ dumpname = "erl_crash.dump";
+ else
+ dumpname = &dumpnamebuf[0];
+
+ fd = open(dumpname,O_WRONLY | O_CREAT | O_TRUNC,0640);
+ if (fd < 0)
+ return; /* Can't create the crash dump, skip it */
+
+ time(&now);
+ erts_fdprintf(fd, "=erl_crash_dump:0.1\n%s", ctime(&now));
+
+ if (file != NULL)
+ erts_fdprintf(fd, "The error occurred in file %s, line %d\n", file, line);
+
+ if (fmt != NULL && *fmt != '\0') {
+ erts_fdprintf(fd, "Slogan: ");
+ erts_vfdprintf(fd, fmt, args);
+ }
+ erts_fdprintf(fd, "System version: ");
+ erts_print_system_version(fd, NULL, NULL);
+ erts_fdprintf(fd, "%s\n", "Compiled: " ERLANG_COMPILE_DATE);
+ erts_fdprintf(fd, "Atoms: %d\n", atom_table_size());
+ info(fd, NULL); /* General system info */
+ if (process_tab != NULL) /* XXX true at init */
+ process_info(fd, NULL); /* Info about each process and port */
+ db_info(fd, NULL, 0);
+ erts_print_bif_timer_info(fd, NULL);
+ distribution_info(fd, NULL);
+ erts_fdprintf(fd, "=loaded_modules\n");
+ loaded(fd, NULL);
+ erts_dump_fun_entries(fd, NULL);
+ erts_deep_process_dump(fd, NULL);
+ erts_fdprintf(fd, "=atoms\n");
+ dump_atoms(fd, NULL);
+
+ /* Keep the instrumentation data at the end of the dump */
+ if (erts_instr_memory_map || erts_instr_stat) {
+ erts_fdprintf(fd, "=instr_data\n");
+
+ if (erts_instr_stat) {
+ erts_fdprintf(fd, "=memory_status\n");
+ erts_instr_dump_stat_to_fd(fd, 0);
+ }
+ if (erts_instr_memory_map) {
+ erts_fdprintf(fd, "=memory_map\n");
+ erts_instr_dump_memory_map_to_fd(fd);
+ }
+ }
+
+ erts_fdprintf(fd, "=end\n");
+ close(fd);
+ erts_fprintf(stderr,"\nCrash dump was written to: %s\n", dumpname);
+}
+
+void
+erl_crash_dump(char* file, int line, char* fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ erl_crash_dump_v(file, line, fmt, args);
+ va_end(args);
+}