/* This file is part of Warzone 2100. Copyright (C) 2008 Giel van Schijndel Copyright (C) 2008-2009 Warzone Resurrection Project Warzone 2100 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Warzone 2100 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Warzone 2100; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include "dumpinfo.h" #include "lib/framework/stdio_ext.h" // FIXME: #include from src/ #include "src/version.h" #if defined(WZ_OS_UNIX) # include #endif #ifndef PACKAGE_DISTRIBUTOR # define PACKAGE_DISTRIBUTOR "UNKNOWN" #endif static const char endl[] = #if defined(WZ_OS_WIN) "\r\n"; #else "\n"; #endif static const std::size_t max_debug_messages = 20; static char* dbgHeader = NULL; static std::deque > dbgMessages; // used to add custom info to the crash log static std::ostringstream miscData; static void dumpstr(const DumpFileHandle file, const char * const str, std::size_t const size) { #if defined(WZ_OS_WIN) DWORD lNumberOfBytesWritten; WriteFile(file, str, size, &lNumberOfBytesWritten, NULL); #else std::size_t written = 0; while (written < size) { const ssize_t ret = write(file, str + written, size - written); if (ret == -1) { switch (errno) { case EAGAIN: // Sleep to prevent wasting of CPU in case of non-blocking I/O usleep(1); case EINTR: continue; default: // TODO find a decent way to deal with the fatal errors return; } } written += ret; } #endif } static void dumpstr(const DumpFileHandle file, const char * const str) { dumpstr(file, str, strlen(str)); } static void dumpEOL(const DumpFileHandle file) { dumpstr(file, endl); } static void debug_exceptionhandler_data(void **, const char * const str) { /* Can't use ASSERT here as that will cause us to be invoked again. * Possibly resulting in infinite recursion. */ assert(str != NULL && "Empty string sent to debug callback"); // For non-debug builds if (str == NULL) return; // Push this message on the message list const char * last = &str[strlen(str)]; // Strip finishing newlines while (last != str && *(last - 1) == '\n') { --last; } dbgMessages.push_back(std::vector(str, last)); // Ensure the message list's maximum size is maintained while (dbgMessages.size() > max_debug_messages) { dbgMessages.pop_front(); } } void dbgDumpHeader(DumpFileHandle file) { if (dbgHeader) { dumpstr(file, dbgHeader); // Now get any other data that we need to include in bug report dumpstr(file, "Misc Data:"); dumpEOL(file); dumpstr(file, miscData.str().c_str()); dumpEOL(file); } else { dumpstr(file, "No debug header available (yet)!"); dumpEOL(file); } } void dbgDumpLog(DumpFileHandle file) { // Write all messages to the given file for (std::deque >::const_iterator msg = dbgMessages.begin(); msg != dbgMessages.end(); ++msg) { dumpstr(file, "Log message: "); dumpstr(file, &(*msg)[0], msg->size()); dumpEOL(file); } // Terminate with a separating newline dumpEOL(file); } static std::string getProgramPath(const char* programCommand) { std::vector buf(PATH_MAX); #if defined(WZ_OS_WIN) while (GetModuleFileNameA(NULL, &buf[0], buf.size()) == buf.size()) { buf.resize(buf.size() * 2); } #elif defined(WZ_OS_UNIX) && !defined(WZ_OS_MAC) { FILE * whichProgramStream; char* whichProgramCommand; sasprintf(&whichProgramCommand, "which %s", programCommand); whichProgramStream = popen(whichProgramCommand, "r"); if (whichProgramStream == NULL) { debug(LOG_WARNING, "Failed to run \"%s\", will not create extended backtrace", whichProgramCommand); return std::string(); } size_t read = 0; while (!feof(whichProgramStream)) { if (read == buf.size()) buf.resize(buf.size() * 2); read += fread(&buf[read], 1, buf.size() - read, whichProgramStream); } pclose(whichProgramStream); } #endif std::string programPath(buf.begin(), buf.end()); if (!programPath.empty()) { // `which' adds a \n which confuses exec() std::string::size_type eol = programPath.find('\n'); if (eol != std::string::npos) programPath.erase(eol); // Strip any NUL chars std::string::size_type nul = programPath.find('\0'); if (nul != std::string::npos) programPath.erase(nul); debug(LOG_WZ, "Found us at %s", programPath.c_str()); } else { debug(LOG_WARNING, "Could not retrieve full path to %s, will not create extended backtrace", programCommand); } return programPath; } static std::string getSysinfo() { #if defined(WZ_OS_WIN) return std::string(); #elif defined(WZ_OS_UNIX) struct utsname sysInfo; std::ostringstream os; if (uname(&sysInfo) != 0) os << "System information may be invalid!" << endl << endl; os << "Operating system: " << sysInfo.sysname << endl << "Node name: " << sysInfo.nodename << endl << "Release: " << sysInfo.release << endl << "Version: " << sysInfo.version << endl << "Machine: " << sysInfo.machine << endl; return os.str(); #endif } static std::string getCurTime() { using std::string; // Get the current time const time_t currentTime = time(NULL); // Convert it to a string string time(ctime(¤tTime)); // Mark finishing newlines as NUL characters for (string::reverse_iterator newline = time.rbegin(); newline != time.rend() && *newline == '\n'; ++newline) { *newline = '\0'; } // Remove everything after, and including, the first NUL character string::size_type newline = time.find_first_of('\0'); if (newline != string::npos) time.erase(newline); return time; } template std::basic_ostream& operator<<(std::basic_ostream& os, PHYSFS_Version const& ver) { return os << static_cast(ver.major) << "." << static_cast(ver.minor) << "." << static_cast(ver.patch); } static void createHeader(int const argc, char* argv[]) { std::ostringstream os; os << "Program: " << getProgramPath(argv[0]) << "(" PACKAGE ")" << endl << "Command line: "; /* Dump all command line arguments surrounded by double quotes and * separated by spaces. */ for (int i = 0; i < argc; ++i) os << "\"" << argv[i] << "\" "; os << endl; os << "Version: " << version_getFormattedVersionString() << endl << "Distributor: " PACKAGE_DISTRIBUTOR << endl << "Compiled on: " __DATE__ " " __TIME__ << endl << "Compiled by: " #if defined(WZ_CC_GNU) && !defined(WZ_CC_INTEL) << "GCC " __VERSION__ << endl #elif defined(WZ_CC_INTEL) // Intel includes the compiler name within the version string << __VERSION__ << endl #else << "UNKNOWN" << endl #endif << "Executed on: " << getCurTime() << endl << getSysinfo() << endl << "Pointers: " << (sizeof(void*) * CHAR_BIT) << "bit" << endl << endl; PHYSFS_Version physfs_version; // Determine PhysicsFS compile time version PHYSFS_VERSION(&physfs_version) os << "Compiled against PhysicsFS version: " << physfs_version << endl; // Determine PhysicsFS runtime version PHYSFS_getLinkedVersion(&physfs_version); os << "Running with PhysicsFS version: " << physfs_version << endl << endl; dbgHeader = strdup(os.str().c_str()); if (dbgHeader == NULL) { debug(LOG_ERROR, "createHeader: Out of memory!"); abort(); return; } } void addDumpInfo( char *inbuffer) { time_t rawtime; struct tm * timeinfo; char ourtime[15]; //HH:MM:SS time ( &rawtime ); timeinfo = localtime ( &rawtime ); strftime (ourtime,15,"%I:%M:%S",timeinfo); // add timestamp to all strings miscData << "[" << ourtime << "]" << std::string(inbuffer) << endl; } void dbgDumpInit(int argc, char* argv[]) { debug_register_callback(&debug_exceptionhandler_data, NULL, NULL, NULL ); createHeader(argc, argv); }