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