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 | |
41 | AbstractDrKonqiBackend::~AbstractDrKonqiBackend() |
42 | { |
43 | } |
44 | |
45 | bool AbstractDrKonqiBackend::init() |
46 | { |
47 | m_crashedApplication = constructCrashedApplication(); |
48 | m_debuggerManager = constructDebuggerManager(); |
49 | return true; |
50 | } |
51 | |
52 | |
53 | KCrashBackend::KCrashBackend() |
54 | : QObject(), AbstractDrKonqiBackend(), m_state(ProcessRunning) |
55 | { |
56 | } |
57 | |
58 | KCrashBackend::~KCrashBackend() |
59 | { |
60 | continueAttachedProcess(); |
61 | } |
62 | |
63 | bool 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 | |
120 | CrashedApplication *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 | |
167 | DebuggerManager *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 | |
201 | void 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 | |
210 | void 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 | |
219 | void KCrashBackend::onDebuggerStarting() |
220 | { |
221 | continueAttachedProcess(); |
222 | m_state = DebuggerRunning; |
223 | } |
224 | |
225 | void KCrashBackend::onDebuggerFinished() |
226 | { |
227 | m_state = ProcessRunning; |
228 | stopAttachedProcess(); |
229 | } |
230 | |
231 | //static |
232 | qint64 KCrashBackend::s_pid = 0; |
233 | |
234 | //static |
235 | void 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 | |