/*************************************************************************** * Copyright (C) 2005 Max Howell * * * * This program 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. * * * ***************************************************************************/ #include "amarok.h" #include "amarokconfig.h" #include "crashhandler.h" #include //invokeMailer() #include //kdBacktrace() #include #include #include #include #include #include #include //qVersion() #include //popen, fread #include #include //pid_t #include //waitpid #include #include //write, getpid #ifndef TAGLIB_PATCH_VERSION // seems to be wheel's style #define TAGLIB_PATCH_VERSION 0 #endif namespace Amarok { #if 0 class CrashHandlerWidget : public KDialog { public: CrashHandlerWidget(); }; #endif static TQString runCommand( const TQCString &command ) { static const uint SIZE = 40960; //40 KiB static char stdoutBuf[ SIZE ] = {0}; std::cout << "Running: " << command.data() << std::endl; FILE *process = ::popen( command, "r" ); if ( process ) { stdoutBuf[ std::fread( static_cast( stdoutBuf ), sizeof(char), SIZE-1, process ) ] = '\0'; ::pclose( process ); } return TQString::fromLocal8Bit( stdoutBuf ); } void Crash::crashHandler( int /*signal*/ ) { // we need to fork to be able to get a // semi-decent bt - I dunno why const pid_t pid = ::fork(); if( pid < 0 ) { std::cout << "forking crash reporter failed\n"; // continuing now can't do no good _exit( 1 ); } else if ( pid == 0 ) { // we are the child process (the result of the fork) std::cout << "Amarok is crashing...\n"; TQString subject = APP_VERSION " "; TQString body = i18n( "Amarok has crashed! We are terribly sorry about this :(\n\n" "But, all is not lost! You could potentially help us fix the crash. " "Information describing the crash is below, so just click send, " "or if you have time, write a brief description of how the crash happened first.\n\n" "Many thanks.\n\n" ); body += i18n( "\n\n\n\n\n\n" "The information below is to help the developers identify the problem, " "please do not modify it.\n\n\n\n" ); body += "======== DEBUG INFORMATION =======\n" "Version: " APP_VERSION "\n" "Engine: %1\n" "Build date: " __DATE__ "\n" "CC version: " __VERSION__ "\n" //assuming we're using GCC "KDElibs: " KDE_VERSION_STRING "\n" "TQt: %2\n" "TagLib: %3.%4.%5\n" "CPU count: %6\n"; TQString cpucount = "unknown"; #ifdef __linux__ TQString line; uint cpuCount = 0; TQFile cpuinfo( "/proc/cpuinfo" ); if ( cpuinfo.open( IO_ReadOnly ) ) { while ( cpuinfo.readLine( line, 20000 ) != -1 ) { if ( line.startsWith( "processor" ) ) { ++cpuCount; } } } cpucount = TQString::number( cpuCount ); #endif body = body.arg( AmarokConfig::soundSystem() ) .arg( qVersion() ) .arg( TAGLIB_MAJOR_VERSION ) .arg( TAGLIB_MINOR_VERSION ) .arg( TAGLIB_PATCH_VERSION ) .arg( cpucount ); #ifdef NDEBUG body += "NDEBUG: true"; #endif body += '\n'; /// obtain the backtrace with gdb KTempFile temp; temp.setAutoDelete( true ); const int handle = temp.handle(); // TQCString gdb_command_string = // "file amarokapp\n" // "attach " + TQCString().setNum( ::getppid() ) + "\n" // "bt\n" "echo \\n\n" // "thread apply all bt\n"; const TQCString gdb_batch = "bt\n" "echo \\n\\n\n" "bt full\n" "echo \\n\\n\n" "echo ==== (gdb) thread apply all bt ====\\n\n" "thread apply all bt\n"; ::write( handle, gdb_batch, gdb_batch.length() ); ::fsync( handle ); // so we can read stderr too ::dup2( fileno( stdout ), fileno( stderr ) ); TQCString gdb; gdb = "gdb --nw -n --batch -x "; gdb += temp.name().latin1(); gdb += " amarokapp "; gdb += TQCString().setNum( ::getppid() ); TQString bt = runCommand( gdb ); /// clean up bt.remove( "(no debugging symbols found)..." ); bt.remove( "(no debugging symbols found)\n" ); bt.replace( TQRegExp("\n{2,}"), "\n" ); //clean up multiple \n characters bt.stripWhiteSpace(); /// analyze usefulness bool useful = true; const TQString fileCommandOutput = runCommand( "file `which amarokapp`" ); if( fileCommandOutput.find( "not stripped", false ) == -1 ) subject += "[___stripped]"; //same length as below else subject += "[NOTstripped]"; if( !bt.isEmpty() ) { const int invalidFrames = bt.contains( TQRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in \\?\\?") ); const int validFrames = bt.contains( TQRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in [^?]") ); const int totalFrames = invalidFrames + validFrames; if( totalFrames > 0 ) { const double validity = double(validFrames) / totalFrames; subject += TQString("[validity: %1]").arg( validity, 0, 'f', 2 ); if( validity <= 0.5 ) useful = false; } subject += TQString("[frames: %1]").arg( totalFrames, 3 /*padding*/ ); if( bt.find( TQRegExp(" at \\w*\\.cpp:\\d+\n") ) >= 0 ) subject += "[line numbers]"; } else useful = false; subject += TQString("[%1]").arg( AmarokConfig::soundSystem().remove( TQRegExp("-?engine") ) ); std::cout << subject.latin1() << std::endl; //TODO -fomit-frame-pointer buggers up the backtrace, so detect it //TODO -O optimization can rearrange execution and stuff so show a warning for the developer //TODO pass the CXXFLAGS used with the email if( useful ) { body += "==== file `which amarokapp` =======\n"; body += fileCommandOutput + "\n\n"; body += "==== (gdb) bt =====================\n"; body += bt + "\n\n"; body += "==== kdBacktrace() ================\n"; body += kdBacktrace(); //TODO startup notification kapp->invokeMailer( /*to*/ "amarok-backtraces@lists.sf.net", /*cc*/ TQString(), /*bcc*/ TQString(), /*subject*/ subject, /*body*/ body, /*messageFile*/ TQString(), /*attachURLs*/ TQStringList(), /*startup_id*/ "" ); } else { std::cout << i18n( "\nAmarok has crashed! We are terribly sorry about this :(\n\n" "But, all is not lost! Perhaps an upgrade is already available " "which fixes the problem. Please check your distribution's software repository.\n" ).local8Bit().data(); } //_exit() exits immediately, otherwise this //function is called repeatedly ad finitum ::_exit( 255 ); } else { // we are the process that crashed ::alarm( 0 ); // wait for child to exit ::waitpid( pid, NULL, 0 ); ::_exit( 253 ); } } } #if 0 #include #include #include #include #include #include #include Amarok::CrashHandlerWidget::CrashHandlerWidget() { TQBoxLayout *layout = new TQHBoxLayout( this, 18, 12 ); { TQBoxLayout *lay = new TQVBoxLayout( layout ); TQLabel *label = new TQLabel( this ); label->setPixmap( locate( "data", "drkonqi/pics/konqi.png" ) ); label->setFrameStyle( TQFrame::Plain | TQFrame::Box ); lay->add( label ); lay->addItem( new TQSpacerItem( 3, 3, TQSizePolicy::Minimum, TQSizePolicy::Expanding ) ); } layout = new TQVBoxLayout( layout, 6 ); layout->add( new TQLabel( /*i18n*/( "

" "Amarok has crashed! We are terribly sorry about this :(" "

" "However you now have an opportunity to help us fix this crash so that it doesn't " "happen again! Click Send Email and Amarok will prepare an email that you " "can send to us that contains information about the crash, and we'll try to fix it " "as soon as possible." "

" "Thanks for choosing Amarok.
" ), this ) ); layout = new TQHBoxLayout( layout, 6 ); layout->addItem( new TQSpacerItem( 6, 6, TQSizePolicy::Expanding ) ); layout->add( new KPushButton( KGuiItem( i18n("Send Email"), "mail_send" ), this, "email" ) ); layout->add( new KPushButton( KStdGuiItem::close(), this, "close" ) ); static_cast(child("email"))->setDefault( true ); connect( child( "email" ), TQT_SIGNAL(clicked()), TQT_SLOT(accept()) ); connect( child( "close" ), TQT_SIGNAL(clicked()), TQT_SLOT(reject()) ); setCaption( i18n("Crash Handler") ); setFixedSize( sizeHint() ); } #endif