You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
amarok/amarok/src/amarokcore/crashhandler.cpp

304 lines
11 KiB

/***************************************************************************
* Copyright (C) 2005 Max Howell <max.howell@methylblue.com> *
* *
* 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 <kapplication.h> //invokeMailer()
#include <kdebug.h> //kdBacktrace()
#include <kdeversion.h>
#include <klocale.h>
#include <ktempfile.h>
#include <tqfile.h>
#include <tqregexp.h>
#include <tqtextstream.h>
#include <tqglobal.h> //qVersion()
#include <cstdio> //popen, fread
#include <iostream>
#include <sys/types.h> //pid_t
#include <sys/wait.h> //waitpid
#include <taglib/taglib.h>
#include <unistd.h> //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<void*>( 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.tqarg( AmarokConfig::soundSystem() )
.tqarg( qVersion() )
.tqarg( TAGLIB_MAJOR_VERSION )
.tqarg( TAGLIB_MINOR_VERSION )
.tqarg( TAGLIB_PATCH_VERSION )
.tqarg( 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]").tqarg( validity, 0, 'f', 2 );
if( validity <= 0.5 ) useful = false;
}
subject += TQString("[frames: %1]").tqarg( totalFrames, 3 /*padding*/ );
if( bt.find( TQRegExp(" at \\w*\\.cpp:\\d+\n") ) >= 0 )
subject += "[line numbers]";
}
else
useful = false;
subject += TQString("[%1]").tqarg( 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 <tqlabel.h>
#include <tqlayout.h>
#include <tqvbox.h>
#include <kdialog.h>
#include <kpushbutton.h>
#include <kstdguiitem.h>
#include <kstandarddirs.h>
Amarok::CrashHandlerWidget::CrashHandlerWidget()
{
TQBoxLayout *tqlayout = new TQHBoxLayout( this, 18, 12 );
{
TQBoxLayout *lay = new TQVBoxLayout( tqlayout );
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 ) );
}
tqlayout = new TQVBoxLayout( tqlayout, 6 );
tqlayout->add( new TQLabel( /*i18n*/(
"<p>" "Amarok has crashed! We are terribly sorry about this :("
"<p>" "However you now have an opportunity to help us fix this crash so that it doesn't "
"happen again! Click <b>Send Email</b> 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."
"<p>" "Thanks for choosing Amarok.<br>" ), this ) );
tqlayout = new TQHBoxLayout( tqlayout, 6 );
tqlayout->addItem( new TQSpacerItem( 6, 6, TQSizePolicy::Expanding ) );
tqlayout->add( new KPushButton( KGuiItem( i18n("Send Email"), "mail_send" ), this, "email" ) );
tqlayout->add( new KPushButton( KStdGuiItem::close(), this, "close" ) );
static_cast<TQPushButton*>(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( tqsizeHint() );
}
#endif