DOS. On UNIX with USE_UNIX_SIGNALS defined, when any crash causing signal happens it will dump to a file the current crawl_state, anything caught by the items and monsters scans, and level building info if the crash happened during level generation.
Also, if crawl is linked against the GNU C library (and the exectuable is in ELF format) it will dump the stack trace. The code attempts to automatically detect the presence of glibc, but that might not work on all systems. This should work on OS X, since there's an OS X man page for the glibc functions that get the stack trace. Don't know if it would work with MinGW.
Actually getting function names for the stack trace requires the use of the "-rdynamic" linker option, which increases the size of the stripped executable by 27% (yikes!), but still prints the function names even when stripped.
All of the function names in the stack trace are mangled C++ ones, but that shouldn't be too much of a problem.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@8532 c06c8d41-db1a-0410-9941-cceddc491573
KOMZPTDEZP3P6EWBUECWDY7OWELOUTMAUKNEOJ7PWX5LJBTMRVQAC
3DQXSE4YGFBBDUWK4YEOFWW4UPWILWELFSLP37SL6BERGAZJC5YAC
HBXWZNXAJ7LUX7FYUIHQYBTRMWVJC6CAQQL3NNZHK5ETLIFEZJ7QC
WOHK7K7U64BMR6GDQ66K2IIMFYD5J3MJWOI4ZB7CJ3L7KGJU6GFAC
LCCFD6BLLDJO3PVPKKJ7YJ4PQUHLUKQBWRCYRH26UVMOTYZPJQRQC
IQGGFC563RBS7GDOACKCLXK752EE5RC3T6G5L6H446SXTMSA7T2AC
SVY2PTCLXR3KNPQAWXVXTTGCC5DR334HOAKHYO3VDDRWM2BWMALAC
JM7UAK777RAVDAVLQLEOBRTGNW2B47S5G55XITJXO243IUNZHVYQC
Y2VKZYSQXLYYQNB6OSQP44IYLT2M56SE2ZW2MHOAZUODKCVDHEAQC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
GCIZIUXO5TYROKDUYB3HAY7H7MRDTJNM7HR7DGSH7KXDIZC2LCDAC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
Q3B3UVMYEVC4YJUPYVSNTR4DJH4E6J4JJDHZNT5LNOCHCPPMEMXAC
25CH7HH4LKXFIZ75YNMXS3TSXO6O27DYSOPLOD45K4OCNFWLS4LQC
FWVE7KM7BGEZUFQVM7H7UFGM3QMMPT7QHLNXSP62HG3SMBIPZBSQC
L3DRKFURVDCV3EJKGG6GVVQX3D5MZPICTVOKNOD3LGM2PECBA7PQC
W7GNFYY2W7NCSNNNXIVZ5FN6F2MIZO2JUQXV2JBOLGUC2DXST7OQC
IIN7AVA6JYRBXH6ZYRR7BY7TV6PW7ANAQ2A3PD55FKBKKQFEEF2AC
UXBKTJSK6DEFSCQ4HB36YM6KVFGWYVCEORXIMOKQ3JZL4NNEOKGQC
YRY2TC3VHOYE47M23UJGUWDGF7H7WGU7WLWI4SUNM4EDNTGUPHGAC
QOYITYZZM3BXITPZGSQGAVUM4T5H6ZL4QYRG5H5W7PSELOF2PO2QC
TOKBONNNPTP2CIEHMMR4QAJZTXYETS55OGGDA6FY6NIMNDYMWJDAC
VPZUNOEZYK7HPIB7XIWRBE4UNDIIN3S6STSWNLELVIZPF2GAFEUQC
RBAGQ2PB7V5YAM5KSHSZR2E3MLKDSRVM5XYGI2TIXP5QMVBOQHDQC
5OZIFI7NLISEF4X2GXHWBCUA7VHPPLE45DFOXO33TACCV2SMROWQC
WLX2RQMMOMP2PYPAGJRM4VFD2WTLJTOAZZPPY3MV76FU2EGEJ54QC
UEI5JAVCMN7Y2SACTEZPZSNFJWOJTC55G24Q6LKQCT4XNDH5ZQIAC
}
void game_state::dump(FILE* file)
{
fprintf(file, "\r\nGame state:\r\n\r\n");
fprintf(file, "mouse_enabled: %d, waiting_for_command: %d "
"terminal_resized: %d\r\n",
mouse_enabled, waiting_for_command, terminal_resized);
fprintf(file, "io_inited: %d, need_save: %d, saving_game: %d, "
"updating_scores: %d\r\n:",
io_inited, need_save, saving_game, updating_scores);
fprintf(file, "seen_hups: %d, map_stat_gen: %d, arena: %d, "
"arena_suspended: %d, unicode_ok: %d\r\n",
seen_hups, map_stat_gen, arena, arena_suspended, unicode_ok);
fprintf(file, "\r\n");
if (!startup_errors.empty())
{
fprintf(file, "Startup errors:\r\n");
for (unsigned int i = 0; i < startup_errors.size(); i++)
fprintf(file, "%s\n", startup_errors[i].c_str());
fprintf(file, "\r\n");
}
fprintf(file, "prev_cmd = %s\r\n", command_to_name(prev_cmd).c_str());
if (doing_prev_cmd_again)
{
fprintf(file, "Doing prev_cmd again with keys: ");
for (unsigned int i = 0; i < prev_cmd_keys.size(); i++)
fprintf(file, "%d, ", prev_cmd_keys[i]);
fprintf(file, "\r\n");
fprintf(file, "As ASCII keys: ");
for (unsigned int i = 0; i < prev_cmd_keys.size(); i++)
fprintf(file, "%c", (char) prev_cmd_keys[i]);
fprintf(file, "\r\n\r\n");
}
fprintf(file, "repeat_cmd = %s\r\n", command_to_name(repeat_cmd).c_str());
if (cmd_repeat_count > 0 || cmd_repeat_goal > 0)
{
fprintf(file, "Doing command repitition: \r\n");
fprintf(file, "cmd_repeat_start:%d, cmd_repeat_count: %d, "
"cmd_repeat_goal:%d\r\nprev_cmd_repeat_goal: %d\r\n",
cmd_repeat_start, cmd_repeat_count, cmd_repeat_goal,
prev_cmd_repeat_goal);
fprintf(file, "Keys being repeated: ");
for (unsigned int i = 0; i < repeat_cmd_keys.size(); i++)
fprintf(file, "%d, ", repeat_cmd_keys[i]);
fprintf(file, "\r\n");
fprintf(file, "As ASCII keys: ");
for (unsigned int i = 0; i < repeat_cmd_keys.size(); i++)
fprintf(file, "%c", (char) repeat_cmd_keys[i]);
fprintf(file, "\r\n\r\n");
}
if (god_act.which_god != GOD_NO_GOD || god_act.depth != 0)
{
fprintf(file, "God %s currently acting with depth %d\r\n\r\n",
god_name(god_act.which_god).c_str(), god_act.depth);
}
if (god_act_stack.size() != 0)
{
fprintf(file, "Other gods acting: \r\n");
for (unsigned int i = 0; i < god_act_stack.size(); i++)
fprintf(file, "God %s with depth %d\r\n",
god_name(god_act_stack[i].which_god).c_str(),
god_act_stack[i].depth);
fprintf(file, "\r\n");
}
void set_msg_dump_file(FILE* file)
{
_msg_dump_file = file;
}
}
}
/////////////////////////////////////////////////////////////////////////////
// Code for printing out debugging info on a crash.
////////////////////////////////////////////////////////////////////////////
static int _crash_signal = 0;
static void _crash_signal_handler(int sig_num)
{
if (crawl_state.game_crashed)
{
fprintf(stderr, "Recursive crash.\n");
std::string dir = (!Options.morgue_dir.empty() ? Options.morgue_dir :
!SysEnv.crawl_dir.empty() ? SysEnv.crawl_dir
: "");
if (!dir.empty() && dir[dir.length() - 1] != FILE_SEPARATOR)
dir += FILE_SEPARATOR;
char name[180];
sprintf(name, "%scrash-recursive-%s-%d.txt", dir.c_str(),
you.your_name, (int) time(NULL));
FILE* file = fopen(name, "w");
if (file == NULL)
file = stderr;
write_stack_trace(file, 0);
if (file != stderr)
fclose(file);
return;
}
_crash_signal = sig_num;
crawl_state.game_crashed = true;
do_crash_dump();
// Now crash for real.
signal(sig_num, SIG_DFL);
raise(sig_num);
}
void init_crash_handler()
{
#if defined(USE_UNIX_SIGNALS)
for (int i = 1; i <= 64; i++)
{
#ifdef SIGHUP_SAVE
if (i == SIGHUP)
continue;
#endif
#ifdef SIGQUIT
if (i == SIGQUIT)
continue;
#endif
#ifdef SIGINT
if (i == SIGINT)
continue;
#endif
#ifdef SIGCHLD
if (i == SIGCHLD)
continue;
#endif
#ifdef SIGTSTP
if (i == SIGTSTP)
continue;
#endif
#ifdef SIGCONT
if (i == SIGCONT)
continue;
#endif
#ifdef SIGIO
if (i == SIGIO)
continue;
#endif
#ifdef SIGPROF
if (i == SIGPROF)
continue;
#endif
if (i == SIGWINCH)
continue;
signal(i, _crash_signal_handler);
#ifdef __GLIBC__
// NOTE: This should work on OS X, according to
// http://developer.apple.com/DOCUMENTATION/DARWIN/Reference/ManPages/man3/backtrace_symbols.3.html
void write_stack_trace(FILE* file, int ignore_count)
{
void* frames[50];
int num_frames = backtrace(frames, ARRAYSZ(frames));
char **symbols = backtrace_symbols(frames, num_frames);
if (symbols == NULL)
{
fprintf(stderr, "Out of memroy.\r\n");
fprintf(file, "Out of memory.\r\n");
// backtrace_symbols_fd() can print out the stack trace even if
// malloc() can't find any free memory.
backtrace_symbols_fd(frames, num_frames, fileno(file));
return;
}
for (int i = ignore_count; i < num_frames; i++)
{
fprintf(file, "%s\r\n", symbols[i]);
}
free(symbols);
}
#else // ifdef __GLIBC__
void write_stack_trace(FILE* file, int ignore_count)
{
const char* msg = "Unable to get stack trace on this platform.\n";
fprintf(stderr, msg);
fprintf(file, msg);
}
#endif
void do_crash_dump()
{
std::string dir = (!Options.morgue_dir.empty() ? Options.morgue_dir :
!SysEnv.crawl_dir.empty() ? SysEnv.crawl_dir
: "");
if (!dir.empty() && dir[dir.length() - 1] != FILE_SEPARATOR)
dir += FILE_SEPARATOR;
char name[180];
sprintf(name, "%scrash-%s-%d.txt", dir.c_str(),
you.your_name, (int) time(NULL));
FILE* file = fopen(name, "w");
if (file == NULL)
{
fprintf(stderr, "\r\nUnable to open file '%s' for writing: %s\r\n",
name, strerror(errno));
file = stderr;
}
else
fprintf(stderr, "\r\nWriting crash info to %s\r\n", name);
set_msg_dump_file(file);
// First get the immediate cause of the crash and the stack trace,
// since that's most important and later attempts to get more information
// might themselves cause crashes.
dump_crash_info(file);
// Ignore the top five stack frames, since the first three involve
// signal handling and the last two are do_crash_dump() and
// write_stack_trace().
write_stack_trace(file, 5);
if (Generating_Level)
{
fprintf(file, "\nCrashed while generating level.\r\n");
fprintf(file, "your_level = %d, level_type = %d, type_name = %s\r\n",
you.your_level, you.level_type, you.level_type_name.c_str());
extern std::string dgn_Build_Method;
extern bool river_level, lake_level, many_pools_level;
fprintf(file, "dgn_Build_Method = %s\r\n", dgn_Build_Method.c_str());
fprintf(file, "dgn_Layout_Type = %s\r\n", dgn_Layout_Type.c_str());
extern bool river_level, lake_level, many_pools_level;
if (river_level)
fprintf(file, "river level\r\n");
if (lake_level)
fprintf(file, "lake level\r\n");
if (many_pools_level)
fprintf(file, "many pools level\r\n");
fprintf(file, "\r\n");
}
// Dumping the crawl state is least likely to cause another crash,
// so do that next.
crawl_state.dump(file);
// Next item and monster scans. Any messages will be sent straight to
// the file because of set_msg_dump_file()
#if DEBUG_ITEM_SCAN
debug_item_scan();
#endif
#if DEBUG_MONS_SCAN
debug_mons_scan();
#endif
set_msg_dump_file(NULL);
if (file != stderr)
fclose(file);
}