1 /***************************************************************************
2 * Copyright (C) 2005 Max Howell <max.howell@methylblue.com> *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 ***************************************************************************/
12 #include "amarokconfig.h"
13 #include "crashhandler.h"
15 #include <kapplication.h> //invokeMailer()
16 #include <kdebug.h> //kBacktrace()
19 #include <k3tempfile.h>
20 #include <ktoolinvocation.h>
24 #include <q3textstream.h>
25 #include <qglobal.h> //qVersion()
29 #include <cstdio> //popen, fread
31 #include <sys/types.h> //pid_t
32 #include <sys/wait.h> //waitpid
34 #include <unistd.h> //write, getpid
42 #ifndef TAGLIB_PATCH_VERSION
43 // seems to be wheel's style
44 #define TAGLIB_PATCH_VERSION 0
52 class CrashHandlerWidget
: public KDialog
{
59 runCommand( const QByteArray
&command
)
61 static const uint SIZE
= 40960; //40 KiB
62 static char stdoutBuf
[ SIZE
] = {0};
64 // std::cout << "Running: " << command << std::endl;
66 FILE *process
= ::popen( command
, "r" );
69 stdoutBuf
[ std::fread( static_cast<void*>( stdoutBuf
), sizeof(char), SIZE
-1, process
) ] = '\0';
72 return QString::fromLocal8Bit( stdoutBuf
);
76 Crash::crashHandler( int /*signal*/ )
78 // we need to fork to be able to get a
79 // semi-decent bt - I dunno why
80 const pid_t pid
= ::fork();
84 std::cout
<< "forking crash reporter failed\n";
85 // continuing now can't do no good
90 // we are the child process (the result of the fork)
91 std::cout
<< "Amarok is crashing!\n";
93 QString subject
= APP_VERSION
" ";
95 "Amarok has crashed! We are terribly sorry about this :(\n\n"
96 "But, all is not lost! You could potentially help us fix the crash. "
97 "Information describing the crash is below, so just click send, "
98 "or if you have time, write a brief description of how the crash happened first.\n\n"
100 body
+= i18n( "\n\n\n\n\n\n"
101 "The information below is to help the developers identify the problem, "
102 "please do not modify it.\n\n\n\n" );
105 body
+= "======== DEBUG INFORMATION =======\n"
106 "Version: " APP_VERSION
"\n"
108 "Build date: " __DATE__
"\n"
109 "CC version: " __VERSION__
"\n" //assuming we're using GCC
110 "KDElibs: " KDE_VERSION_STRING
"\n"
115 QString cpucount
= "unknown";
119 QFile
cpuinfo( "/proc/cpuinfo" );
120 if ( cpuinfo
.open( QIODevice::ReadOnly
) ) {
122 while ( cpuinfo
.readLine( cline
, sizeof(cline
) ) != -1 ) {
124 if ( line
.startsWith( "processor" ) ) {
129 cpucount
= QString::number( cpuCount
);
132 KConfigGroup config
= KGlobal::config()->group( "Playback" );
133 QString soundSystem
= config
.readEntry( "Sound System", QString() );
134 body
= body
.arg( soundSystem
)
136 .arg( TAGLIB_MAJOR_VERSION
)
137 .arg( TAGLIB_MINOR_VERSION
)
138 .arg( TAGLIB_PATCH_VERSION
)
142 body
+= "NDEBUG: true";
146 /// obtain the backtrace with gdb
149 temp
.setAutoDelete( true );
151 const int handle
= temp
.handle();
153 // QCString gdb_command_string =
155 // "attach " + QCString().setNum( ::getppid() ) + "\n"
156 // "bt\n" "echo \\n\n"
157 // "thread apply all bt\n";
159 const QByteArray gdb_batch
=
164 "echo ==== (gdb) thread apply all bt ====\\n\n"
165 "thread apply all bt\n";
167 ::write( handle
, gdb_batch
, gdb_batch
.length() );
170 // so we can read stderr too
171 ::dup2( fileno( stdout
), fileno( stderr
) );
174 gdb
= "gdb --nw -n --batch -x ";
175 gdb
+= temp
.name().toLatin1();
177 gdb
+= QByteArray().setNum( ::getppid() );
179 QString bt
= runCommand( gdb
);
182 bt
.remove( "(no debugging symbols found)..." );
183 bt
.remove( "(no debugging symbols found)\n" );
184 bt
.replace( QRegExp("\n{2,}"), "\n" ); //clean up multiple \n characters
187 /// analyze usefulness
189 const QString fileCommandOutput
= runCommand( "file `which amarok`" );
191 if( fileCommandOutput
.indexOf( "not stripped", false ) == -1 )
192 subject
+= "[___stripped]"; //same length as below
194 subject
+= "[NOTstripped]";
196 if( !bt
.isEmpty() ) {
197 const int invalidFrames
= bt
.count( QRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in \\?\\?") );
198 const int validFrames
= bt
.count( QRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in [^?]") );
199 const int totalFrames
= invalidFrames
+ validFrames
;
201 if( totalFrames
> 0 ) {
202 const double validity
= double(validFrames
) / totalFrames
;
203 subject
+= QString("[validity: %1]").arg( validity
, 0, 'f', 2 );
204 if( validity
<= 0.5 ) useful
= false;
206 subject
+= QString("[frames: %1]").arg( totalFrames
, 3 /*padding*/ );
208 if( bt
.indexOf( QRegExp(" at \\w*\\.cpp:\\d+\n") ) >= 0 )
209 subject
+= "[line numbers]";
214 subject
+= QString("[%1]").arg( soundSystem
.remove( QRegExp("-?engine") ) );
216 std::cout
<< subject
.toLatin1().constData() << std::endl
;
219 //TODO -fomit-frame-pointer buggers up the backtrace, so detect it
220 //TODO -O optimization can rearrange execution and stuff so show a warning for the developer
221 //TODO pass the CXXFLAGS used with the email
224 body
+= "==== file `which amarok` =======\n";
225 body
+= fileCommandOutput
+ "\n\n";
226 body
+= "==== (gdb) bt =====================\n";
228 // body += "==== kBacktrace() ================\n";
229 // body += kBacktrace();
231 //TODO startup notification
232 // KToolInvocation::invokeMailer(
233 // /*to*/ "amarok-backtraces@lists.sf.net",
235 // /*bcc*/ QString(),
236 // /*subject*/ subject,
238 // /*messageFile*/ QString(),
239 // /*attachURLs*/ QStringList(),
240 // /*startup_id*/ "" );
241 std::cout
<< body
.toLatin1().data();
244 std::cout
<< i18n( "\nAmarok has crashed! We are terribly sorry about this :(\n\n"
245 "But, all is not lost! Perhaps an upgrade is already available "
246 "which fixes the problem. Please check your distribution's software repository.\n" ).toLocal8Bit().constData();
249 //_exit() exits immediately, otherwise this
250 //function is called repeatedly ad finitum
255 // we are the process that crashed
259 // wait for child to exit
260 ::waitpid( pid
, NULL
, 0 );
273 #include <kpushbutton.h>
274 #include <kstdguiitem.h>
275 #include <kstandarddirs.h>
277 Amarok::CrashHandlerWidget::CrashHandlerWidget()
279 QBoxLayout
*layout
= new QHBoxLayout( this, 18, 12 );
282 QVBoxLayout
*lay
= new QVBoxLayout( layout
);
283 QLabel
*label
= new QLabel( this );
284 label
->setPixmap( KStandardDirs::locate( "data", "drkonqi/pics/konqi.png" ) );
285 label
->setFrameStyle( QFrame::Plain
| QFrame::Box
);
287 lay
->addItem( new QSpacerItem( 3, 3, QSizePolicy::Minimum
, QSizePolicy::Expanding
) );
290 layout
= new QVBoxLayout( layout
, 6 );
292 layout
->add( new QLabel( /*i18n*/(
293 "<p>" "Amarok has crashed! We are terribly sorry about this :("
294 "<p>" "However you now have an opportunity to help us fix this crash so that it doesn't "
295 "happen again! Click <b>Send Email</b> and Amarok will prepare an email that you "
296 "can send to us that contains information about the crash, and we'll try to fix it "
297 "as soon as possible."
298 "<p>" "Thanks for choosing Amarok.<br>" ), this ) );
300 layout
= new QHBoxLayout( layout
, 6 );
302 layout
->addItem( new QSpacerItem( 6, 6, QSizePolicy::Expanding
) );
303 layout
->add( new KPushButton( KGuiItem( i18n("Send Email"), "mail_send" ), this, "email" ) );
304 layout
->add( new KPushButton( KStandardGuiItem::close(), this, "close" ) );
306 child
<QPushButton
*>("email")->setDefault( true );
308 connect( child( "email" ), SIGNAL(clicked()), SLOT(accept()) );
309 connect( child( "close" ), SIGNAL(clicked()), SLOT(reject()) );
311 setCaption( i18n("Crash Handler") );
312 setFixedSize( sizeHint() );