1/*
2 Copyright (C) 2009 George Kiagiadakis <gkiagia@users.sourceforge.net>
3
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.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16*/
17#include "drkonqibackends.h"
18
19#include <cstdlib>
20#include <cerrno>
21#include <sys/types.h>
22#include <signal.h>
23
24#include <QtCore/QTimer>
25#include <QtCore/QDir>
26
27#include <KCmdLineArgs>
28#include <KStandardDirs>
29#include <KDebug>
30#include <KConfig>
31#include <KConfigGroup>
32#include <KGlobal>
33#include <KStartupInfo>
34#include <KCrash>
35
36#include "crashedapplication.h"
37#include "debugger.h"
38#include "debuggermanager.h"
39#include "backtracegenerator.h"
40
41AbstractDrKonqiBackend::~AbstractDrKonqiBackend()
42{
43}
44
45bool AbstractDrKonqiBackend::init()
46{
47 m_crashedApplication = constructCrashedApplication();
48 m_debuggerManager = constructDebuggerManager();
49 return true;
50}
51
52
53KCrashBackend::KCrashBackend()
54 : QObject(), AbstractDrKonqiBackend(), m_state(ProcessRunning)
55{
56}
57
58KCrashBackend::~KCrashBackend()
59{
60 continueAttachedProcess();
61}
62
63bool KCrashBackend::init()
64{
65 AbstractDrKonqiBackend::init();
66
67 QString startupId(KCmdLineArgs::parsedArgs()->getOption("startupid"));
68 if (!startupId.isEmpty()) { // stop startup notification
69 KStartupInfoId id;
70 id.initId(startupId.toLocal8Bit());
71 KStartupInfo::sendFinish(id);
72 }
73
74 //check whether the attached process exists and whether we have permissions to inspect it
75 if (crashedApplication()->pid() <= 0) {
76 kError() << "Invalid pid specified";
77 return false;
78 }
79
80#if !defined(Q_OS_WIN32)
81 if (::kill(crashedApplication()->pid(), 0) < 0) {
82 switch (errno) {
83 case EPERM:
84 kError() << "DrKonqi doesn't have permissions to inspect the specified process";
85 break;
86 case ESRCH:
87 kError() << "The specified process does not exist.";
88 break;
89 default:
90 break;
91 }
92 return false;
93 }
94
95 //--keeprunning means: generate backtrace instantly and let the process continue execution
96 if(KCmdLineArgs::parsedArgs()->isSet("keeprunning")) {
97 stopAttachedProcess();
98 debuggerManager()->backtraceGenerator()->start();
99 connect(debuggerManager(), SIGNAL(debuggerFinished()), SLOT(continueAttachedProcess()));
100 } else {
101 connect(debuggerManager(), SIGNAL(debuggerStarting()), SLOT(onDebuggerStarting()));
102 connect(debuggerManager(), SIGNAL(debuggerFinished()), SLOT(onDebuggerFinished()));
103
104 //stop the process to avoid high cpu usage by other threads (bug 175362).
105 //if the process was started by kdeinit, we need to wait a bit for KCrash
106 //to reach the alarm(0); call in kdeui/util/kcrash.cpp line 406 or else
107 //if we stop it before this call, pending alarm signals will kill the
108 //process when we try to continue it.
109 QTimer::singleShot(2000, this, SLOT(stopAttachedProcess()));
110 }
111#endif
112
113 //Handle drkonqi crashes
114 s_pid = crashedApplication()->pid(); //copy pid for use by the crash handler, so that it is safer
115 KCrash::setEmergencySaveFunction(emergencySaveFunction);
116
117 return true;
118}
119
120CrashedApplication *KCrashBackend::constructCrashedApplication()
121{
122 CrashedApplication *a = new CrashedApplication(this);
123 a->m_datetime = QDateTime::currentDateTime();
124 KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
125 a->m_name = args->getOption("programname");
126 a->m_version = args->getOption("appversion").toUtf8();
127 a->m_reportAddress = BugReportAddress(args->getOption("bugaddress").toUtf8());
128 a->m_pid = args->getOption("pid").toInt();
129 a->m_signalNumber = args->getOption("signal").toInt();
130 a->m_restarted = args->isSet("restarted");
131 a->m_thread = args->getOption("thread").toInt();
132
133 //try to determine the executable that crashed
134 if ( QFileInfo(QString("/proc/%1/exe").arg(a->m_pid)).exists() ) {
135 //on linux, the fastest and most reliable way is to get the path from /proc
136 kDebug() << "Using /proc to determine executable path";
137 a->m_executable.setFile(QFile::symLinkTarget(QString("/proc/%1/exe").arg(a->m_pid)));
138
139 if (args->isSet("kdeinit") ||
140 a->m_executable.fileName().startsWith("python") ) {
141
142 a->m_fakeBaseName = args->getOption("appname");
143 }
144 } else {
145 if ( args->isSet("kdeinit") ) {
146 a->m_executable = QFileInfo(KStandardDirs::findExe("kdeinit4"));
147 a->m_fakeBaseName = args->getOption("appname");
148 } else {
149 QFileInfo execPath(args->getOption("appname"));
150 if ( execPath.isAbsolute() ) {
151 a->m_executable = execPath;
152 } else if ( !args->getOption("apppath").isEmpty() ) {
153 QDir execDir(args->getOption("apppath"));
154 a->m_executable = execDir.absoluteFilePath(execPath.fileName());
155 } else {
156 a->m_executable = QFileInfo(KStandardDirs::findExe(execPath.fileName()));
157 }
158 }
159 }
160
161 kDebug() << "Executable is:" << a->m_executable.absoluteFilePath();
162 kDebug() << "Executable exists:" << a->m_executable.exists();
163
164 return a;
165}
166
167DebuggerManager *KCrashBackend::constructDebuggerManager()
168{
169 QList<Debugger> internalDebuggers = Debugger::availableInternalDebuggers("KCrash");
170 KConfigGroup config(KGlobal::config(), "DrKonqi");
171#ifndef Q_OS_WIN
172 QString defaultDebuggerName = config.readEntry("Debugger", QString("gdb"));
173#else
174 QString defaultDebuggerName = config.readEntry("Debugger", QString("kdbgwin"));
175#endif
176
177 Debugger firstKnownGoodDebugger, preferredDebugger;
178 foreach (const Debugger & debugger, internalDebuggers) {
179 if (!firstKnownGoodDebugger.isValid() && debugger.isInstalled()) {
180 firstKnownGoodDebugger = debugger;
181 }
182 if (debugger.codeName() == defaultDebuggerName) {
183 preferredDebugger = debugger;
184 }
185 if (firstKnownGoodDebugger.isValid() && preferredDebugger.isValid()) {
186 break;
187 }
188 }
189
190 if (!preferredDebugger.isInstalled()) {
191 if (firstKnownGoodDebugger.isValid()) {
192 preferredDebugger = firstKnownGoodDebugger;
193 } else {
194 kError() << "Unable to find an internal debugger that can work with the KCrash backend";
195 }
196 }
197
198 return new DebuggerManager(preferredDebugger, Debugger::availableExternalDebuggers("KCrash"), this);
199}
200
201void KCrashBackend::stopAttachedProcess()
202{
203 if (m_state == ProcessRunning) {
204 kDebug() << "Sending SIGSTOP to process";
205 ::kill(crashedApplication()->pid(), SIGSTOP);
206 m_state = ProcessStopped;
207 }
208}
209
210void KCrashBackend::continueAttachedProcess()
211{
212 if (m_state == ProcessStopped) {
213 kDebug() << "Sending SIGCONT to process";
214 ::kill(crashedApplication()->pid(), SIGCONT);
215 m_state = ProcessRunning;
216 }
217}
218
219void KCrashBackend::onDebuggerStarting()
220{
221 continueAttachedProcess();
222 m_state = DebuggerRunning;
223}
224
225void KCrashBackend::onDebuggerFinished()
226{
227 m_state = ProcessRunning;
228 stopAttachedProcess();
229}
230
231//static
232qint64 KCrashBackend::s_pid = 0;
233
234//static
235void KCrashBackend::emergencySaveFunction(int signal)
236{
237 // In case drkonqi itself crashes, we need to get rid of the process being debugged,
238 // so we kill it, no matter what its state was.
239 Q_UNUSED(signal);
240 ::kill(s_pid, SIGKILL);
241}
242
243#include "drkonqibackends.moc"
244