1 /*
2  * Copyright 2011-2013 Arx Libertatis Team (see the AUTHORS file)
3  *
4  * This file is part of Arx Libertatis.
5  *
6  * Arx Libertatis is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Arx Libertatis is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Arx Libertatis.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "crashreporter/ErrorReport.h"
21 
22 #ifdef ARX_HAVE_WINAPI
23 // Win32
24 #include <windows.h>
25 #include <psapi.h>
26 #else
27 #include <sys/wait.h>
28 #endif
29 
30 #ifdef ARX_HAVE_GETRUSAGE
31 #include <sys/resource.h>
32 #include <sys/time.h>
33 #endif
34 
35 #if defined(ARX_HAVE_PRCTL)
36 #include <sys/prctl.h>
37 #ifndef PR_SET_PTRACER
38 #define PR_SET_PTRACER 0x59616d61
39 #endif
40 #endif
41 
42 #ifdef ARX_HAVE_SYSCONF
43 #include <unistd.h>
44 #endif
45 
46 #ifdef ARX_HAVE_CRASHHANDLER_POSIX
47 #include <signal.h>
48 #endif
49 
50 #ifdef ARX_HAVE_SETENV
51 #include <stdlib.h>
52 #endif
53 
54 // Qt
55 #include <QApplication>
56 #include <QMessageBox>
57 #include <QDesktopWidget>
58 #include <QDir>
59 #include <QProcess>
60 #include <QTime>
61 #include <QFile>
62 #include <QFileInfo>
63 #include <QFileInfoList>
64 #include <QSslSocket>
65 #include <QThread>
66 #include <QXmlStreamWriter>
67 #include <QByteArray>
68 
69 // Boost
70 #include <boost/crc.hpp>
71 
72 #include "Configure.h"
73 
74 #include "core/Version.h"
75 
76 #include "io/fs/Filesystem.h"
77 #include "io/fs/FileStream.h"
78 
79 #include "crashreporter/Win32Utilities.h"
80 #include "crashreporter/tbg/TBG.h"
81 
82 #include "platform/Architecture.h"
83 #include "platform/OS.h"
84 
ErrorReport(const QString & sharedMemoryName)85 ErrorReport::ErrorReport(const QString& sharedMemoryName)
86 	: m_RunningTimeSec(0)
87 	, m_ProcessMemoryUsage(0)
88 	, m_SharedMemoryName(sharedMemoryName)
89 	, m_pCrashInfo()
90 	, m_Username("CrashBot")
91 	, m_Password("WbAtVjS9")
92 {
93 #if defined(ARX_HAVE_PRCTL)
94 	// Allow debuggers to be attached to this process, for development purpose...
95 	prctl(PR_SET_PTRACER, 1, 0, 0, 0);
96 #endif
97 }
98 
Initialize()99 bool ErrorReport::Initialize()
100 {
101 	// Create a shared memory object.
102 	m_SharedMemory = boost::interprocess::shared_memory_object(boost::interprocess::open_only, m_SharedMemoryName.toStdString().c_str(), boost::interprocess::read_write);
103 
104 	// Map the whole shared memory in this process
105 	m_MemoryMappedRegion = boost::interprocess::mapped_region(m_SharedMemory, boost::interprocess::read_write);
106 
107 	// Our SharedCrashInfo will be stored in this shared memory.
108 	m_pCrashInfo = (CrashInfo*)m_MemoryMappedRegion.get_address();
109 
110 	if(m_MemoryMappedRegion.get_size() != sizeof(CrashInfo))
111 	{
112 		m_DetailedError = "The size of the memory mapped region does not match the size of the CrashInfo structure.";
113 		return false;
114 	}
115 
116 	bool bMiscCrashInfo = GetMiscCrashInfo();
117 	if(!bMiscCrashInfo)
118 		return false;
119 
120 	// Add attached files from the report
121 	for(int i = 0; i < m_pCrashInfo->nbFilesAttached; i++)
122 		AddFile(m_pCrashInfo->attachedFiles[i]);
123 
124 	m_ReportFolder = fs::path(m_pCrashInfo->crashReportFolder) / fs::path(m_CrashDateTime.toString("yyyy.MM.dd hh.mm.ss").toUtf8());
125 
126 	if(!fs::create_directories(m_ReportFolder))
127 	{
128 		m_DetailedError = QString("Unable to create directory (%1) to store the crash report files.").arg(m_ReportFolder.string().c_str());
129 		return false;
130 	}
131 
132 	return true;
133 }
134 
GetCrashDump(const fs::path & fileName)135 bool ErrorReport::GetCrashDump(const fs::path & fileName) {
136 
137 #ifdef ARX_HAVE_WINAPI
138 	bool bHaveDump = false;
139 
140 	if(fs::exists(m_pCrashInfo->miniDumpTmpFile))
141 	{
142 		fs::path fullPath = m_ReportFolder / fileName;
143 		if(fs::rename(m_pCrashInfo->miniDumpTmpFile, fullPath))
144 		{
145 			AddFile(fullPath);
146 			bHaveDump = true;
147 		}
148 	}
149 
150 	return bHaveDump;
151 
152 #else // !ARX_HAVE_WINAPI
153 
154 	ARX_UNUSED(fileName);
155 
156 	// TODO: Write core dump to
157 	// fs::path fullPath = m_ReportFolder / fileName;
158 
159 	return getCrashDescription();
160 
161 #endif // !ARX_HAVE_WINAPI
162 }
163 
164 #ifndef ARX_HAVE_WINAPI
165 
getProcessSatus(QString filename,u64 & rss,u64 & startTicks)166 void getProcessSatus(QString filename, u64 & rss, u64 & startTicks) {
167 
168 	rss = startTicks = 0;
169 
170 	QFile file(filename);
171 
172 	if(!file.open(QIODevice::ReadOnly)) {
173 		return;
174 	}
175 	QByteArray stat = file.readAll();
176 	file.close();
177 
178 	QList<QByteArray> stat_fields = stat.split(' ');
179 
180 	const int rss_index = 23;
181 	if(rss_index < stat_fields.size()) {
182 		rss = stat_fields[rss_index].toULongLong();
183 	}
184 
185 	const int starttime_index = 21;
186 	if(starttime_index < stat_fields.size()) {
187 		startTicks = stat_fields[starttime_index].toULongLong();
188 	}
189 
190 }
191 
getResourceUsage(int pid,quint64 & memoryUsage,double & runningTimeSec)192 void getResourceUsage(int pid, quint64 & memoryUsage, double & runningTimeSec) {
193 
194 	memoryUsage = 0;
195 	runningTimeSec = 0.0;
196 
197 #if defined(ARX_HAVE_GETRUSAGE) && ARX_PLATFORM != ARX_PLATFORM_MACOSX
198 	{
199 		struct rusage usage;
200 		if(getrusage(pid, &usage) == 0) {
201 			memoryUsage = usage.ru_maxrss * 1024;
202 		}
203 	}
204 #endif
205 
206 #if defined(ARX_HAVE_SYSCONF) && (defined(_SC_PAGESIZE) || defined(_SC_CLK_TCK))
207 
208 	u64 rss, startTicks, endTicks, dummy;
209 
210 	getProcessSatus(QString("/proc/%1/stat").arg(pid), rss, startTicks);
211 
212 	// Get the rss memory usage from /proc/pid/stat
213 #ifdef _SC_PAGESIZE
214 	if(rss != 0) {
215 		long pagesize = sysconf(_SC_PAGESIZE);
216 		if(pagesize > 0) {
217 			memoryUsage = rss * pagesize;
218 		}
219 	}
220 #endif
221 
222 #ifdef _SC_CLK_TCK
223 
224 	if(startTicks == 0) {
225 		return;
226 	}
227 
228 	pid_t child = fork();
229 	if(!child) {
230 		while(1) {
231 			// wait
232 		}
233 	}
234 
235 	getProcessSatus(QString("/proc/%1/stat").arg(child), dummy, endTicks);
236 	kill(child, SIGTERM);
237 
238 	if(startTicks != 0 && endTicks != 0) {
239 		u64 ticksPerSecond = sysconf(_SC_CLK_TCK);
240 		if(ticksPerSecond > 0) {
241 			runningTimeSec = double(endTicks - startTicks) / double(ticksPerSecond);
242 		}
243 	}
244 
245 #endif
246 
247 #endif
248 
249 }
250 
251 #endif // !defined(ARX_HAVE_WINAPI)
252 
getCrashDescription()253 bool ErrorReport::getCrashDescription() {
254 
255 #ifdef ARX_HAVE_WINAPI
256 
257 	return true;
258 
259 #else // !defined(ARX_HAVE_WINAPI)
260 
261 	switch(m_pCrashInfo->signal) {
262 
263 		case SIGILL: {
264 			m_ReportDescription = "Illegal instruction";
265 			switch(m_pCrashInfo->code) {
266 				#ifdef ILL_ILLOPC
267 				case ILL_ILLOPC: m_ReportDescription += ": illegal opcode"; break;
268 				#endif
269 				#ifdef ILL_ILLOPN
270 				case ILL_ILLOPN: m_ReportDescription += ": illegal operand"; break;
271 				#endif
272 				#ifdef ILL_ADR
273 				case ILL_ADR: m_ReportDescription += ": illegal addressing mode"; break;
274 				#endif
275 				#ifdef ILL_ILLTRP
276 				case ILL_ILLTRP: m_ReportDescription += ": illegal trap"; break;
277 				#endif
278 				#ifdef ILL_PRVOPC
279 				case ILL_PRVOPC: m_ReportDescription += ": privileged opcode"; break;
280 				#endif
281 				#ifdef ILL_PRVREG
282 				case ILL_PRVREG: m_ReportDescription += ": privileged register"; break;
283 				#endif
284 				#ifdef ILL_COPROC
285 				case ILL_COPROC: m_ReportDescription += ": coprocessor error"; break;
286 				#endif
287 				#ifdef ILL_BADSTK
288 				case ILL_BADSTK: m_ReportDescription += ": internal stack error"; break;
289 				#endif
290 				default: break;
291 			}
292 			break;
293 		}
294 
295 		case SIGABRT: {
296 			m_ReportDescription = "Abnormal termination";
297 			break;
298 		}
299 
300 		case SIGBUS: {
301 			m_ReportDescription = "Bus error";
302 			switch(m_pCrashInfo->code) {
303 				#ifdef BUS_ADRALN
304 				case BUS_ADRALN: m_ReportDescription += ": invalid address alignment"; break;
305 				#endif
306 				#ifdef BUS_ADRERR
307 				case BUS_ADRERR: m_ReportDescription += ": non-existent physical address"; break;
308 				#endif
309 				#ifdef BUS_OBJERR
310 				case BUS_OBJERR: m_ReportDescription += ": object specific hardware error"; break;
311 				#endif
312 				default: break;
313 			}
314 			break;
315 		}
316 
317 		case SIGFPE: {
318 			m_ReportDescription = "Floating-point error";
319 			switch(m_pCrashInfo->code) {
320 				#ifdef FPE_INTDIV
321 				case FPE_INTDIV: m_ReportDescription += ": integer division by zero"; break;
322 				#endif
323 				#ifdef FPE_INTOVF
324 				case FPE_INTOVF: m_ReportDescription += ": integer overflow"; break;
325 				#endif
326 				#ifdef FPE_FLTDIV
327 				case FPE_FLTDIV: m_ReportDescription += ": floating point division by zero"; break;
328 				#endif
329 				#ifdef FPE_FLTOVF
330 				case FPE_FLTOVF: m_ReportDescription += ": floating point overflow"; break;
331 				#endif
332 				#ifdef FPE_FLTUND
333 				case FPE_FLTUND: m_ReportDescription += ": floating point underflow"; break;
334 				#endif
335 				#ifdef FPE_FLTRES
336 				case FPE_FLTRES: m_ReportDescription += ": floating point inexact result"; break;
337 				#endif
338 				#ifdef FPE_FLTINV
339 				case FPE_FLTINV: m_ReportDescription += ": invalid floating point operation"; break;
340 				#endif
341 				#ifdef FPE_FLTSUB
342 				case FPE_FLTSUB: m_ReportDescription += ": subscript out of range"; break;
343 				#endif
344 				default: break;
345 			}
346 			break;
347 		}
348 
349 		case SIGSEGV: {
350 			m_ReportDescription = "Illegal storage access";
351 			switch(m_pCrashInfo->code) {
352 				#ifdef SEGV_MAPERR
353 				case SEGV_MAPERR: {
354 					m_ReportDescription += ": address not mapped to object";
355 					break;
356 				}
357 				#endif
358 				#ifdef SEGV_ACCERR
359 				case SEGV_ACCERR: {
360 					m_ReportDescription += ": invalid permissions for mapped object";
361 					break;
362 				}
363 				#endif
364 				default: break;
365 			}
366 			break;
367 		}
368 
369 		default: {
370 			m_ReportDescription = QString("Received signal %1").arg(m_pCrashInfo->signal);
371 			break;
372 		}
373 	}
374 
375 	m_ReportDescription += "\n\n";
376 
377 	m_ReportDescriptionText = m_ReportDescription;
378 
379 #if defined(ARX_HAVE_FORK) && defined(ARX_HAVE_EXECLP) && defined(ARX_HAVE_DUP2)
380 
381 	fs::path tracePath = m_ReportFolder / "gdbtrace.txt";
382 
383 	// Fork so we retain control after launching GDB.
384 	int childPID = fork();
385 	if(childPID) {
386 		// Wait for GDB to exit.
387 		waitpid(childPID, NULL, 0);
388 	} else {
389 
390 		// Redirect output to a file
391 		int fd = open(tracePath.string().c_str(), O_WRONLY|O_CREAT, 0666);
392 		dup2(fd, 1);
393 		close(fd);
394 
395 		// Prepare pid argument for GDB.
396 		char pid_buf[30];
397 		memset(&pid_buf, 0, sizeof(pid_buf));
398 		sprintf(pid_buf, "%d", m_pCrashInfo->processId);
399 
400 		// Turn off localization for the backtrace output
401 		#ifdef ARX_HAVE_SETENV
402 		setenv("LANG", "C", 1);
403 		setenv("LC_ALL", "C", 1);
404 		#endif
405 
406 		// Try to execute gdb to get a very detailed stack trace.
407 		execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "set confirm off", "-ex", "set print frame-arguments all", "-ex", "set print static-members off", "-ex", "info threads", "-ex", "thread apply all bt full", m_pCrashInfo->execFullName, pid_buf, NULL);
408 
409 		// GDB failed to start.
410 		exit(1);
411 	}
412 #endif // defined(ARX_HAVE_EXECLP) && defined(ARX_HAVE_DUP2)
413 
414 	bool bWroteDump = fs::exists(tracePath) && fs::file_size(tracePath) > 0;
415 	if(!bWroteDump) {
416 		m_DetailedError = "GDB is probably not installed on your machine. Please install it in order to obtain valuable crash reports in the future.";
417 		return false;
418 	}
419 
420 #ifdef ARX_HAVE_BACKTRACE
421 
422 	boost::crc_32_type callstackCRC32;
423 
424 	for(size_t i = 0; i < ARRAY_SIZE(m_pCrashInfo->backtrace); i++) {
425 		if(m_pCrashInfo->backtrace[i] == 0) {
426 			break;
427 		}
428 		callstackCRC32.process_bytes(&m_pCrashInfo->backtrace[i], sizeof(m_pCrashInfo->backtrace[i]));
429 	}
430 
431 	u32 callstackCrc = callstackCRC32.checksum();
432 	m_ReportUniqueID = QString("[%1]").arg(QString::number(callstackCrc, 16).toUpper());
433 
434 #endif // ARX_HAVE_BACKTRACE
435 
436 	QFile traceFile(tracePath.string().c_str());
437 	traceFile.open(QIODevice::ReadOnly);
438 	if(!traceFile.isOpen()) {
439 		m_DetailedError = "Unable to read GDB output from the disk.";
440 		return false;
441 	}
442 
443 	QString traceStr = traceFile.readAll();
444 	traceFile.close();
445 	QString callstackTop = "Unknown";
446 
447 	// The useful stack frames are found below "<signal handler called>"
448 	int posStart = traceStr.indexOf("<signal handler called>");
449 	int posEnd = -1;
450 	if(posStart != -1) {
451 		posStart = traceStr.indexOf("#", posStart);
452 		if(posStart != -1)
453 			posEnd = traceStr.indexOf("\n", posStart);
454 	}
455 
456 	// Capture the entry where the crash occured and format it
457 	if(posStart != -1 && posEnd != -1)	{
458 		callstackTop = traceStr.mid(posStart, posEnd - posStart);
459 
460 		// Remove "#N 0x???????? in "
461 		posEnd = callstackTop.indexOf(" in ");
462 		if(posEnd != -1) {
463 			posEnd += 4;
464 			callstackTop.remove(0, posEnd);
465 		}
466 
467 		// Remove params
468 		posStart = callstackTop.indexOf("(");
469 		posEnd = callstackTop.indexOf(")", posStart);
470 		if(posStart != -1 && posEnd != -1) {
471 			posStart++;
472 			callstackTop.remove(posStart, posEnd - posStart);
473 		}
474 
475 		// Trim filenames
476 		posStart = callstackTop.lastIndexOf(") at");
477 		posEnd = callstackTop.lastIndexOf("/");
478 		if(posStart != -1 && posEnd != -1) {
479 			posStart += 2;
480 			posEnd++;
481 			callstackTop.remove(posStart, posEnd - posStart);
482 		}
483 	}
484 
485 	m_ReportDescription += "\nGDB stack trace:\n";
486 	m_ReportDescription += "<source lang=\"gdb\">\n";
487 	m_ReportDescription += traceStr;
488 	m_ReportDescription += "</source>\n";
489 
490 	m_ReportDescriptionText += "\nGDB stack trace:\n";
491 	m_ReportDescriptionText += "\n";
492 	m_ReportDescriptionText += traceStr;
493 
494 	m_ReportTitle = QString("%1 %2").arg(m_ReportUniqueID, callstackTop.trimmed());
495 
496 #endif // !defined(ARX_HAVE_WINAPI)
497 
498 	return true;
499 }
500 
GetMiscCrashInfo()501 bool ErrorReport::GetMiscCrashInfo() {
502 
503 	// Get crash time
504 	m_CrashDateTime = QDateTime::currentDateTime();
505 
506 	m_ProcessArchitecture = ARX_ARCH_NAME;
507 
508 	m_OSName = QString::fromUtf8(platform::getOSName().c_str());
509 	m_OSArchitecture = QString::fromUtf8(platform::getOSArchitecture().c_str());
510 	m_OSDistribution = QString::fromUtf8(platform::getOSDistribution().c_str());
511 
512 #ifdef ARX_HAVE_WINAPI
513 
514 	// Open parent process handle
515 	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_pCrashInfo->processId);
516 	if(hProcess != NULL)
517 	{
518 		// Get memory usage info
519 		PROCESS_MEMORY_COUNTERS meminfo;
520 		BOOL bGetMemInfo = GetProcessMemoryInfo(hProcess, &meminfo, sizeof(PROCESS_MEMORY_COUNTERS));
521 		if(bGetMemInfo)
522 			m_ProcessMemoryUsage = meminfo.WorkingSetSize;
523 
524 		// Determine the period of time the process is working.
525 		FILETIME CreationTime, ExitTime, KernelTime, UserTime;
526 		BOOL bGetTimes = GetProcessTimes(hProcess, &CreationTime, &ExitTime, &KernelTime, &UserTime);
527 		if(bGetTimes)
528 		{
529 			SYSTEMTIME AppStartTime;
530 			FileTimeToSystemTime(&CreationTime, &AppStartTime);
531 
532 			SYSTEMTIME CurTime;
533 			GetSystemTime(&CurTime);
534 			ULONG64 uCurTime = ConvertSystemTimeToULONG64(CurTime);
535 			ULONG64 uStartTime = ConvertSystemTimeToULONG64(AppStartTime);
536 
537 			// Check that the application works for at least one minute before crash.
538 			// This might help to avoid cyclic error report generation when the applciation
539 			// crashes on startup.
540 			m_RunningTimeSec = (double)(uCurTime-uStartTime)*10E-08;
541 		}
542 	}
543 	else
544 	{
545 		m_DetailedError = QString("Unable to obtain an handle to the crashed process (Error %1).").arg(QString::number(GetLastError()));
546 		return false;
547 	}
548 
549 	if(m_pCrashInfo->exceptionCode != 0)
550 	{
551 		QString exceptionStr = GetExceptionString(m_pCrashInfo->exceptionCode).c_str();
552 		if(!exceptionStr.isEmpty())
553 		{
554 			m_ReportDescription += "\nException code:\n  ";
555 			m_ReportDescription += exceptionStr;
556 			m_ReportDescription += "\n";
557 		}
558 	}
559 
560 	std::string callStack, callstackTop;
561 	u32 callstackCrc;
562 
563 	bool bCallstack = GetCallStackInfo(hProcess, m_pCrashInfo->threadHandle, &m_pCrashInfo->contextRecord, callStack, callstackTop, callstackCrc);
564 	if(!bCallstack)
565 	{
566 		m_DetailedError = "A failure occured when obtaining information regarding the callstack.";
567 		return false;
568 	}
569 
570 	m_ReportUniqueID = QString("[%1]").arg(QString::number(callstackCrc, 16).toUpper());
571 
572 	m_ReportDescription = m_pCrashInfo->detailedCrashInfo;
573 	m_ReportDescription += "\nCallstack:\n";
574 	m_ReportDescription += callStack.c_str();
575 	m_ReportTitle = QString("%1 %2").arg(m_ReportUniqueID, callstackTop.c_str());
576 
577 	QString registers(GetRegisters(&m_pCrashInfo->contextRecord).c_str());
578 	if(!registers.isEmpty())
579 	{
580 		m_ReportDescription += "\nRegisters:\n";
581 		m_ReportDescription += registers;
582 	}
583 
584 	CloseHandle(hProcess);
585 
586 	m_ReportDescriptionText = m_ReportDescription;
587 
588 #else // !ARX_HAVE_WINAPI
589 
590 	getResourceUsage(m_pCrashInfo->processId, m_ProcessMemoryUsage, m_RunningTimeSec);
591 
592 #endif // !ARX_HAVE_WINAPI
593 
594 	return true;
595 }
596 
WriteReport(const fs::path & fileName)597 bool ErrorReport::WriteReport(const fs::path & fileName) {
598 
599 	fs::path fullPath = m_ReportFolder / fileName;
600 
601 	QFile file(fullPath.string().c_str());
602 	if(!file.open(QIODevice::WriteOnly)) {
603 		m_DetailedError = "Unable to open report manifest for writing.";
604 		return false;
605 	}
606 
607 	QXmlStreamWriter xml;
608 	xml.setDevice(&file);
609 	xml.setAutoFormatting(true);
610 	xml.writeStartDocument();
611 	xml.writeStartElement("CrashReport");
612 
613 		xml.writeComment("Information related to the crashed process");
614 		xml.writeStartElement("Process");
615 			xml.writeTextElement("Path", m_pCrashInfo->executablePath);
616 			xml.writeTextElement("Version", m_pCrashInfo->executableVersion);
617 			if(m_ProcessMemoryUsage != 0) {
618 				xml.writeTextElement("MemoryUsage", QString::number(m_ProcessMemoryUsage));
619 			}
620 			xml.writeTextElement("Architecture", m_ProcessArchitecture);
621 			if(m_RunningTimeSec > 0) {
622 				xml.writeTextElement("RunningTime", QString::number(m_RunningTimeSec));
623 			}
624 			xml.writeTextElement("CrashDateTime", m_CrashDateTime.toString("dd.MM.yyyy hh:mm:ss"));
625 		xml.writeEndElement();
626 
627 		xml.writeComment("Information related to the OS");
628 		xml.writeStartElement("OS");
629 			if(!m_OSName.isEmpty()) {
630 				xml.writeTextElement("Name", m_OSName);
631 			}
632 			if(!m_OSArchitecture.isEmpty()) {
633 				xml.writeTextElement("Architecture", m_OSArchitecture);
634 			}
635 			if(!m_OSDistribution.isEmpty()) {
636 				xml.writeTextElement("Distribution", m_OSDistribution);
637 			}
638 		xml.writeEndElement();
639 
640 		xml.writeComment("List of files generated by the crash reporter");
641 		xml.writeComment("Note that some of these files could have been manually excluded from the report");
642 		xml.writeStartElement("Files");
643 		for(FileList::const_iterator it = m_AttachedFiles.begin();
644 		    it != m_AttachedFiles.end(); ++it) {
645 			xml.writeTextElement("File", it->path.string().c_str());
646 		}
647 		xml.writeEndElement();
648 
649 		xml.writeComment("Variables attached by the crashed process");
650 		xml.writeStartElement("Variables");
651 		for(int i = 0; i < m_pCrashInfo->nbVariables; ++i)
652 		{
653 			xml.writeStartElement("Variable");
654 			xml.writeAttribute("Name", m_pCrashInfo->variables[i].name);
655 			xml.writeAttribute("Value", m_pCrashInfo->variables[i].value);
656 			xml.writeEndElement();
657 		}
658 		xml.writeEndElement();
659 
660 	xml.writeEndElement();
661 	xml.writeEndDocument();
662 
663 	file.close();
664 
665 	AddFile(fullPath);
666 
667 	return true;
668 }
669 
GenerateReport(ErrorReport::IProgressNotifier * pProgressNotifier)670 bool ErrorReport::GenerateReport(ErrorReport::IProgressNotifier* pProgressNotifier)
671 {
672 	ErrorReport* report = this;
673 	BOOST_SCOPE_EXIT((report))
674 	{
675 		// Allow the crashed process to exit
676 		report->ReleaseApplicationLock();
677 	} BOOST_SCOPE_EXIT_END
678 
679 	pProgressNotifier->taskStarted("Generating crash report", 3);
680 
681 	// Initialize shared memory
682 	pProgressNotifier->taskStepStarted("Connecting to crashed application");
683 	bool bInit = Initialize();
684 	pProgressNotifier->taskStepEnded();
685 	if(!bInit)
686 	{
687 		pProgressNotifier->setError("Could not generate the crash dump.");
688 		pProgressNotifier->setDetailedError(m_DetailedError);
689 		return false;
690 	}
691 
692 	if(m_pCrashInfo->architecture != ARX_ARCH) {
693 		pProgressNotifier->setError("Architecture mismatch between the crashed process and the crash reporter.");
694 		pProgressNotifier->setDetailedError(m_DetailedError);
695 		return false;
696 	}
697 
698 	// Generate minidump
699 	pProgressNotifier->taskStepStarted("Generating crash dump");
700 	bool bCrashDump = GetCrashDump("crash.dmp");
701 	pProgressNotifier->taskStepEnded();
702 	if(!bCrashDump)
703 	{
704 		pProgressNotifier->setError("Could not generate the crash dump.");
705 		pProgressNotifier->setDetailedError(m_DetailedError);
706 		return false;
707 	}
708 
709 	// Generate manifest
710 	pProgressNotifier->taskStepStarted("Generating report manifest");
711 	bool bCrashXml = WriteReport("crash.xml");
712 	pProgressNotifier->taskStepEnded();
713 	if(!bCrashXml)
714 	{
715 		pProgressNotifier->setError("Could not generate the manifest.");
716 		pProgressNotifier->setDetailedError(m_DetailedError);
717 		return false;
718 	}
719 
720 	return true;
721 }
722 
SendReport(ErrorReport::IProgressNotifier * pProgressNotifier)723 bool ErrorReport::SendReport(ErrorReport::IProgressNotifier* pProgressNotifier)
724 {
725 	int nbFilesToSend = 0;
726 	for(FileList::const_iterator it = m_AttachedFiles.begin(); it != m_AttachedFiles.end(); ++it)
727 	{
728 		if(it->attachToReport)
729 			nbFilesToSend++;
730 	}
731 
732 	pProgressNotifier->taskStarted("Sending crash report", 3 + nbFilesToSend);
733 
734 	// This must be called before creating our QNetworkAccessManager
735 	AddSSLCertificate();
736 
737 	TBG::Server server("https://bugs.arx-libertatis.org");
738 
739 	// Login to TBG server
740 	pProgressNotifier->taskStepStarted("Connecting to the bug tracker");
741 	bool bLoggedIn = server.login(m_Username, m_Password);
742 	pProgressNotifier->taskStepEnded();
743 	if(!bLoggedIn)
744 	{
745 		pProgressNotifier->setError("Could not connect to the bug tracker");
746 		pProgressNotifier->setDetailedError(server.getErrorString());
747 		return false;
748 	}
749 
750 	// Look for existing issue
751 	int issue_id = -1;
752 	pProgressNotifier->taskStepStarted("Searching for existing issue");
753 	bool bSearchSuccessful = m_ReportUniqueID.isEmpty() || server.findIssue(m_ReportUniqueID, issue_id);
754 	pProgressNotifier->taskStepEnded();
755 	if(!bSearchSuccessful)
756 	{
757 		pProgressNotifier->setError("Failure occured when searching issue on the bug tracker");
758 		pProgressNotifier->setDetailedError(server.getErrorString());
759 		return false;
760 	}
761 
762 	// Create new issue if no match was found
763 	if(issue_id == -1)
764 	{
765 		pProgressNotifier->taskStepStarted("Creating new issue");
766 		bool bCreatedIssue = server.createCrashReport(m_ReportTitle, m_ReportDescription, m_ReproSteps, tbg_version_id, issue_id);
767 		if(!bCreatedIssue)
768 		{
769 			pProgressNotifier->taskStepEnded();
770 			pProgressNotifier->setError("Could not create a new issue on the bug tracker");
771 			pProgressNotifier->setDetailedError(server.getErrorString());
772 			return false;
773 		}
774 
775 		// Set OS
776 #if   ARX_PLATFORM == ARX_PLATFORM_WIN32
777 		int os_id = TBG::Server::OS_Windows;
778 #elif ARX_PLATFORM == ARX_PLATFORM_LINUX
779 		int os_id = TBG::Server::OS_Linux;
780 #elif ARX_PLATFORM == ARX_PLATFORM_MACOSX
781 		int os_id = TBG::Server::OS_MacOSX;
782 #elif ARX_PLATFORM == ARX_PLATFORM_BSD
783 		int os_id = TBG::Server::OS_FreeBSD;
784 #else
785 		int os_id = TBG::Server::OS_Other;
786 #endif
787 		server.setOperatingSystem(issue_id, os_id);
788 
789 		// Set Architecture
790 		int arch_id;
791 		if(m_ProcessArchitecture == ARX_ARCH_NAME_X86_64)
792 			arch_id = TBG::Server::Arch_Amd64;
793 		else if(m_ProcessArchitecture == ARX_ARCH_NAME_X86)
794 			arch_id = TBG::Server::Arch_x86;
795 		else
796 			arch_id = TBG::Server::Arch_Other;
797 		server.setArchitecture(issue_id, arch_id);
798 
799 		pProgressNotifier->taskStepEnded();
800 	}
801 	else
802 	{
803 		if(!m_ReproSteps.isEmpty())
804 		{
805 			pProgressNotifier->taskStepStarted("Duplicate issue found, adding information");
806 			bool bCommentAdded = server.addComment(issue_id, m_ReproSteps);
807 			pProgressNotifier->taskStepEnded();
808 			if(!bCommentAdded)
809 			{
810 				pProgressNotifier->setError("Failure occured when trying to add information to an existing issue");
811 				pProgressNotifier->setDetailedError(server.getErrorString());
812 				return false;
813 			}
814 		}
815 	}
816 
817 	// Send files
818 	for(FileList::const_iterator it = m_AttachedFiles.begin(); it != m_AttachedFiles.end(); ++it)
819 	{
820 		// Ignore files that were removed by the user.
821 		if(!it->attachToReport)
822 			continue;
823 
824 		// One more check to verify that the file still exists.
825 		if(!fs::exists(it->path))
826 			continue;
827 
828 		pProgressNotifier->taskStepStarted(QString("Sending file \"%1\"").arg(it->path.filename().c_str()));
829 		bool bAttached = server.attachFile(issue_id, it->path.string().c_str(), it->path.filename().c_str(), m_SharedMemoryName);
830 		pProgressNotifier->taskStepEnded();
831 		if(!bAttached)
832 		{
833 			pProgressNotifier->setError(QString("Could not send file \"%1\"").arg(it->path.filename().c_str()));
834 			pProgressNotifier->setDetailedError(server.getErrorString());
835 			return false;
836 		}
837 	}
838 
839 	m_IssueLink = server.getUrl().toString();
840 
841 	return true;
842 }
843 
ReleaseApplicationLock()844 void ErrorReport::ReleaseApplicationLock() {
845 #if ARX_PLATFORM == ARX_PLATFORM_WIN32
846 	m_pCrashInfo->exitLock.post();
847 #else
848 	// Kill the original, busy-waiting process.
849 	kill(m_pCrashInfo->processId, SIGKILL);
850 #endif
851 }
852 
AddSSLCertificate()853 void ErrorReport::AddSSLCertificate() {
854 
855 	QFile file(":/startcom.cer", 0);
856 	if(file.open(QIODevice::ReadOnly)) {
857 		QSslCertificate certificate(file.readAll(), QSsl::Der);
858 
859 		if(certificate.isNull()) {
860 			return;
861 		}
862 
863 		#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
864 		if(!certificate.isValid()) {
865 			return;
866 		}
867 		#else
868 		if(certificate.isBlacklisted()) {
869 			return;
870 		}
871 		#endif
872 
873 		QSslSocket::addDefaultCaCertificate(certificate);
874 	}
875 }
876 
AddFile(const fs::path & fileName)877 void ErrorReport::AddFile(const fs::path& fileName)
878 {
879 	// Do not include files that can't be found, and empty files...
880 	if(fs::exists(fileName) && fs::file_size(fileName) != 0)
881 		m_AttachedFiles.push_back(File(fileName, true));
882 }
883 
GetAttachedFiles()884 ErrorReport::FileList& ErrorReport::GetAttachedFiles()
885 {
886 	return m_AttachedFiles;
887 }
888 
GetErrorDescription() const889 const QString& ErrorReport::GetErrorDescription() const
890 {
891 	return m_ReportDescriptionText;
892 }
893 
GetIssueLink() const894 const QString& ErrorReport::GetIssueLink() const
895 {
896 	return m_IssueLink;
897 }
898 
SetLoginInfo(const QString & username,const QString & password)899 void ErrorReport::SetLoginInfo(const QString& username, const QString& password)
900 {
901 	m_Username = username;
902 	m_Password = password;
903 }
904 
SetReproSteps(const QString & reproSteps)905 void ErrorReport::SetReproSteps(const QString& reproSteps)
906 {
907 	m_ReproSteps = reproSteps;
908 }
909