summaryrefslogtreecommitdiffstats
path: root/desktop/source/app/crashreport.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/source/app/crashreport.cxx')
-rw-r--r--desktop/source/app/crashreport.cxx478
1 files changed, 478 insertions, 0 deletions
diff --git a/desktop/source/app/crashreport.cxx b/desktop/source/app/crashreport.cxx
new file mode 100644
index 000000000..0458edf97
--- /dev/null
+++ b/desktop/source/app/crashreport.cxx
@@ -0,0 +1,478 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <desktop/crashreport.hxx>
+#include <rtl/bootstrap.hxx>
+#include <osl/file.hxx>
+#include <comphelper/processfactory.hxx>
+#include <ucbhelper/proxydecider.hxx>
+#include <unotools/bootstrap.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <desktop/minidump.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <config_version.h>
+#include <config_folders.h>
+
+#include <string>
+#include <regex>
+
+
+#if HAVE_FEATURE_BREAKPAD
+
+#include <fstream>
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+#include <client/linux/handler/exception_handler.h>
+#elif defined _WIN32
+#if defined __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmicrosoft-enum-value"
+#endif
+#include <client/windows/handler/exception_handler.h>
+#if defined __clang__
+#pragma clang diagnostic pop
+#endif
+#include <locale>
+#include <codecvt>
+#endif
+
+osl::Mutex CrashReporter::maMutex;
+osl::Mutex CrashReporter::maActiveSfxObjectNameMutex;
+osl::Mutex CrashReporter::maUnoLogCmdMutex;
+std::unique_ptr<google_breakpad::ExceptionHandler> CrashReporter::mpExceptionHandler;
+bool CrashReporter::mbInit = false;
+CrashReporter::vmaKeyValues CrashReporter::maKeyValues;
+CrashReporter::vmaloggedUnoCommands CrashReporter::maloggedUnoCommands;
+OUString CrashReporter::msActiveSfxObjectName;
+
+
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* /*context*/, bool succeeded)
+{
+ CrashReporter::addKeyValue("Active-SfxObject",CrashReporter::getActiveSfxObjectName(),CrashReporter::AddItem);
+ CrashReporter::addKeyValue("Last-4-Uno-Commands",CrashReporter::getLoggedUnoCommands(),CrashReporter::AddItem);
+ CrashReporter::addKeyValue("DumpFile", OStringToOUString(descriptor.path(), RTL_TEXTENCODING_UTF8), CrashReporter::Write);
+ SAL_WARN("desktop", "minidump generated: " << descriptor.path());
+
+ return succeeded;
+}
+#elif defined _WIN32
+static bool dumpCallback(const wchar_t* path, const wchar_t* id,
+ void* /*context*/, EXCEPTION_POINTERS* /*exinfo*/,
+ MDRawAssertionInfo* /*assertion*/,
+ bool succeeded)
+{
+ // TODO: moggi: can we avoid this conversion
+#ifdef _MSC_VER
+#pragma warning (disable: 4996)
+#endif
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> conv1;
+ std::string aPath = conv1.to_bytes(std::wstring(path)) + conv1.to_bytes(std::wstring(id)) + ".dmp";
+ CrashReporter::addKeyValue("Active-SfxObject",CrashReporter::getActiveSfxObjectName(),CrashReporter::AddItem);
+ CrashReporter::addKeyValue("Last-4-Uno-Commands",CrashReporter::getLoggedUnoCommands(),CrashReporter::AddItem);
+ CrashReporter::addKeyValue("DumpFile", OStringToOUString(aPath.c_str(), RTL_TEXTENCODING_UTF8), CrashReporter::AddItem);
+ CrashReporter::addKeyValue("GDIHandles", OUString::number(::GetGuiResources(::GetCurrentProcess(), GR_GDIOBJECTS)), CrashReporter::Write);
+ SAL_WARN("desktop", "minidump generated: " << aPath);
+ return succeeded;
+}
+#endif
+
+
+void CrashReporter::writeToFile(std::ios_base::openmode Openmode)
+{
+#if defined _WIN32
+ const std::string iniPath = getIniFileName();
+ std::wstring iniPathW;
+ const int nChars = MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, nullptr, 0);
+ auto buf = std::make_unique<wchar_t[]>(nChars);
+ if (MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, buf.get(), nChars) != 0)
+ iniPathW = buf.get();
+
+ std::ofstream ini_file
+ = iniPathW.empty() ? std::ofstream(iniPath, Openmode) : std::ofstream(iniPathW, Openmode);
+#else
+ std::ofstream ini_file(getIniFileName(), Openmode);
+#endif
+
+ for (auto& keyValue : maKeyValues)
+ {
+ ini_file << OUStringToOString(keyValue.first, RTL_TEXTENCODING_UTF8) << "=";
+ ini_file << OUStringToOString(keyValue.second, RTL_TEXTENCODING_UTF8) << "\n";
+ }
+
+ maKeyValues.clear();
+ ini_file.close();
+}
+
+void CrashReporter::addKeyValue(const OUString& rKey, const OUString& rValue, tAddKeyHandling AddKeyHandling)
+{
+ osl::MutexGuard aGuard(maMutex);
+
+ if (IsDumpEnable())
+ {
+ if (!rKey.isEmpty())
+ maKeyValues.push_back(mpair(rKey, rValue));
+
+ if (AddKeyHandling != AddItem)
+ {
+ if (mbInit)
+ writeToFile(std::ios_base::app);
+ else if (AddKeyHandling == Create)
+ writeCommonInfo();
+ }
+ }
+}
+
+void CrashReporter::writeCommonInfo()
+{
+ writeSystemInfo();
+
+ ucbhelper::InternetProxyDecider proxy_decider(::comphelper::getProcessComponentContext());
+
+ static const OUStringLiteral protocol = u"https";
+ static const OUStringLiteral url = u"crashreport.libreoffice.org";
+ const sal_Int32 port = 443;
+
+ const ucbhelper::InternetProxyServer proxy_server = proxy_decider.getProxy(protocol, url, port);
+
+ // save the new Keys
+ vmaKeyValues atlast = maKeyValues;
+ // clear the keys, the following Keys should be at the begin
+ maKeyValues.clear();
+
+ // limit the amount of code that needs to be executed before the crash reporting
+ addKeyValue("ProductName", "LibreOffice", AddItem);
+ addKeyValue("Version", LIBO_VERSION_DOTTED, AddItem);
+ addKeyValue("BuildID", utl::Bootstrap::getBuildIdData(""), AddItem);
+ addKeyValue("URL", protocol + "://" + url + "/submit/", AddItem);
+
+ if (!proxy_server.aName.isEmpty())
+ {
+ addKeyValue("Proxy", proxy_server.aName + ":" + OUString::number(proxy_server.nPort), AddItem);
+ }
+
+ // write the new keys at the end
+ maKeyValues.insert(maKeyValues.end(), atlast.begin(), atlast.end());
+
+ mbInit = true;
+
+ writeToFile(std::ios_base::trunc);
+
+ updateMinidumpLocation();
+}
+
+void CrashReporter::setActiveSfxObjectName(const OUString& rActiveSfxObjectName)
+{
+ osl::MutexGuard aGuard(maActiveSfxObjectNameMutex);
+ msActiveSfxObjectName = rActiveSfxObjectName;
+}
+
+OUString CrashReporter::getActiveSfxObjectName()
+{
+ osl::MutexGuard aGuard(maActiveSfxObjectNameMutex);
+ return msActiveSfxObjectName;
+}
+
+void CrashReporter::logUnoCommand(const OUString& rUnoCommand)
+{
+ osl::MutexGuard aGuard(maUnoLogCmdMutex);
+
+ if( maloggedUnoCommands.size() == 4 )
+ maloggedUnoCommands.pop_front();
+
+ maloggedUnoCommands.push_back(rUnoCommand);
+}
+
+OUString CrashReporter::getLoggedUnoCommands()
+{
+ osl::MutexGuard aGuard(maUnoLogCmdMutex);
+
+ OUString aCommandSeperator="";
+ OUStringBuffer aUnoCommandBuffer;
+
+ for( auto& unocommand: maloggedUnoCommands)
+ {
+ aUnoCommandBuffer.append(aCommandSeperator);
+ aUnoCommandBuffer.append(unocommand);
+ aCommandSeperator=",";
+ }
+ return aUnoCommandBuffer.makeStringAndClear();
+}
+
+namespace {
+
+OUString getCrashDirectory()
+{
+ OUString aCrashURL;
+ rtl::Bootstrap::get("CrashDirectory", aCrashURL);
+ // Need to convert to URL in case of user-defined path
+ osl::FileBase::getFileURLFromSystemPath(aCrashURL, aCrashURL);
+
+ if (aCrashURL.isEmpty()) { // Fall back to user profile
+ aCrashURL = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/crash/";
+ rtl::Bootstrap::expandMacros(aCrashURL);
+ }
+
+ if (!aCrashURL.endsWith("/"))
+ aCrashURL += "/";
+
+ osl::Directory::create(aCrashURL);
+ OUString aCrashPath;
+ osl::FileBase::getSystemPathFromFileURL(aCrashURL, aCrashPath);
+ return aCrashPath;
+}
+
+}
+
+void CrashReporter::updateMinidumpLocation()
+{
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+ OUString aURL = getCrashDirectory();
+ OString aOStringUrl = OUStringToOString(aURL, RTL_TEXTENCODING_UTF8);
+ google_breakpad::MinidumpDescriptor descriptor(aOStringUrl.getStr());
+ mpExceptionHandler->set_minidump_descriptor(descriptor);
+#elif defined _WIN32
+ OUString aURL = getCrashDirectory();
+ mpExceptionHandler->set_dump_path(o3tl::toW(aURL.getStr()));
+#endif
+}
+
+bool CrashReporter::crashReportInfoExists()
+{
+ static const bool InfoExist = crashreport::readConfig(CrashReporter::getIniFileName(), nullptr);
+ return InfoExist;
+}
+
+bool CrashReporter::readSendConfig(std::string& response)
+{
+ return crashreport::readConfig(CrashReporter::getIniFileName(), &response);
+}
+
+void CrashReporter::installExceptionHandler()
+{
+ if (!IsDumpEnable())
+ return;
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+ google_breakpad::MinidumpDescriptor descriptor("/tmp");
+ mpExceptionHandler = std::make_unique<google_breakpad::ExceptionHandler>(descriptor, nullptr, dumpCallback, nullptr, true, -1);
+#elif defined _WIN32
+ mpExceptionHandler = std::make_unique<google_breakpad::ExceptionHandler>(L".", nullptr, dumpCallback, nullptr, google_breakpad::ExceptionHandler::HANDLER_ALL);
+#endif
+}
+
+void CrashReporter::removeExceptionHandler()
+{
+ mpExceptionHandler.reset();
+}
+
+
+
+bool CrashReporter::IsDumpEnable()
+{
+ auto const env = std::getenv("CRASH_DUMP_ENABLE");
+ if (env != nullptr && env[0] != '\0') {
+ return true;
+ }
+ // read configuration item 'CrashDumpEnable' -> bool on/off
+ OUString sToken;
+ if (rtl::Bootstrap::get("CrashDumpEnable", sToken))
+ {
+ return sToken.toBoolean();
+ }
+ return true; // default, always on
+}
+
+
+std::string CrashReporter::getIniFileName()
+{
+ OUString url = getCrashDirectory() + "dump.ini";
+ OString aUrl = OUStringToOString(url, RTL_TEXTENCODING_UTF8);
+ std::string aRet(aUrl.getStr());
+ return aRet;
+}
+
+// Write system-specific information such as the CPU name and features.
+// This may allow us to get some statistics for decisions (such as when
+// deciding whether SSE2 can be made a hard-requirement for Windows).
+// Breakpad provides this information poorly or not at all.
+#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
+void CrashReporter::writeSystemInfo()
+{
+ // Get 'model name' and 'flags' from /proc/cpuinfo.
+ if( std::ifstream cpuinfo( "/proc/cpuinfo" ); cpuinfo )
+ {
+ bool haveModel = false;
+ bool haveFlags = false;
+ std::regex modelRegex( "^model name[ \t]*:[ \t]*(.*)$" );
+ std::regex flagsRegex( "^flags[ \t]*:[ \t]*(.*)$" );
+ for( std::string line; std::getline( cpuinfo, line ); )
+ {
+ std::smatch match;
+ if( !haveModel && std::regex_match( line, match, modelRegex ) && match.size() == 2)
+ {
+ addKeyValue("CPUModelName", OUString::fromUtf8( match[ 1 ].str()), AddItem);
+ haveModel = true;
+ }
+ if( !haveFlags && std::regex_match( line, match, flagsRegex ) && match.size() == 2)
+ {
+ addKeyValue("CPUFlags", OUString::fromUtf8( match[ 1 ].str()), AddItem);
+ haveFlags = true;
+ }
+ if( haveModel && haveFlags )
+ break;
+ }
+ }
+ // Get 'MemTotal' from /proc/meminfo.
+ if( std::ifstream meminfo( "/proc/meminfo" ); meminfo )
+ {
+ std::regex memTotalRegex( "^MemTotal[ \t]*:[ \t]*(.*)$" );
+ for( std::string line; std::getline( meminfo, line ); )
+ {
+ std::smatch match;
+ if( std::regex_match( line, match, memTotalRegex ) && match.size() == 2)
+ {
+ addKeyValue("MemoryTotal", OUString::fromUtf8( match[ 1 ].str()), AddItem);
+ break;
+ }
+ }
+ }
+}
+#elif defined _WIN32
+void CrashReporter::writeSystemInfo()
+{
+#if !defined(_ARM64_)
+ // Get CPU model name and flags.
+ // See https://docs.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex
+ // and https://en.wikipedia.org/wiki/CPUID .
+ int cpui[ 4 ];
+ __cpuid( cpui, 0x80000000 ); // Get the highest extended ID.
+ unsigned int exIds = cpui[ 0 ];
+ if( exIds >= 0x80000004 )
+ {
+ int brand[ 16 ];
+ __cpuidex( brand, 0x80000002, 0 );
+ __cpuidex( brand + 4, 0x80000003, 0 );
+ __cpuidex( brand + 8, 0x80000004, 0 );
+ brand[ 12 ] = 0;;
+ addKeyValue( "CPUModelName", OUString::fromUtf8( reinterpret_cast< const char* >( brand )),
+ AddItem );
+ }
+ __cpuid( cpui, 0 ); // Get the highest ID.
+ int ids = cpui[ 0 ];
+ unsigned int ecx1 = 0, edx1 = 0, ebx7 = 0, ecx7 = 0, ecx81 = 0, edx81 = 0;
+ if( ids >= 0x1 )
+ {
+ __cpuidex( cpui, 0x1, 0 );
+ ecx1 = cpui[ 2 ];
+ edx1 = cpui[ 3 ];
+ }
+ if( ids >= 0x7 )
+ {
+ __cpuidex( cpui, 0x7, 0 );
+ ebx7 = cpui[ 1 ];
+ ecx7 = cpui[ 2 ];
+ }
+ if( exIds >= 0x80000001 )
+ {
+ __cpuidex( cpui, 0x80000001, 0 );
+ ecx81 = cpui[ 2 ];
+ edx81 = cpui[ 3 ];
+ }
+ struct FlagItem
+ {
+ unsigned int* reg;
+ int bit;
+ const char* name;
+ };
+ const FlagItem flagItems[] =
+ {
+ { &ecx1, 0, "sse3" },
+ { &ecx1, 1, "pclmulqdq" },
+ { &ecx1, 3, "monitor" },
+ { &ecx1, 9, "ssse3" },
+ { &ecx1, 12, "fma" },
+ { &ecx1, 13, "cpmxch16b" },
+ { &ecx1, 19, "sse41" },
+ { &ecx1, 20, "sse42" },
+ { &ecx1, 22, "movbe" },
+ { &ecx1, 23, "popcnt" },
+ { &ecx1, 25, "aes" },
+ { &ecx1, 26, "xsave" },
+ { &ecx1, 27, "osxsave" },
+ { &ecx1, 28, "avx" },
+ { &ecx1, 29, "f16c" },
+ { &ecx1, 30, "rdrand" },
+ { &edx1, 5, "msr" },
+ { &edx1, 8, "cx8" },
+ { &edx1, 11, "sep" },
+ { &edx1, 15, "cmov" },
+ { &edx1, 19, "clfsh" },
+ { &edx1, 23, "mmx" },
+ { &edx1, 24, "fxsr" },
+ { &edx1, 25, "sse" },
+ { &edx1, 26, "sse2" },
+ { &edx1, 28, "ht" },
+ { &ebx7, 0, "fsgsbase" },
+ { &ebx7, 3, "bmi1" },
+ { &ebx7, 4, "hle" },
+ { &ebx7, 5, "avx2" },
+ { &ebx7, 8, "bmi2" },
+ { &ebx7, 9, "erms" },
+ { &ebx7, 10, "invpcid" },
+ { &ebx7, 11, "rtm" },
+ { &ebx7, 16, "avx512f" },
+ { &ebx7, 18, "rdseed" },
+ { &ebx7, 19, "adx" },
+ { &ebx7, 26, "avx512pf" },
+ { &ebx7, 27, "avx512er" },
+ { &ebx7, 28, "avx512cd" },
+ { &ebx7, 29, "sha" },
+ { &ecx7, 0, "prefetchwt1" },
+ { &ecx81, 0, "lahf" },
+ { &ecx81, 5, "abm" },
+ { &ecx81, 6, "sse4a" },
+ { &ecx81, 11, "xop" },
+ { &ecx81, 21, "tbm" },
+ { &edx81, 11, "syscall" },
+ { &edx81, 22, "mmxext" },
+ { &edx81, 27, "rdtscp" },
+ { &edx81, 30, "3dnowext" },
+ { &edx81, 31, "3dnow" }
+ };
+ OUStringBuffer flags;
+ for( const FlagItem& item : flagItems )
+ {
+ if( *item.reg & ( 1U << item.bit ))
+ {
+ if( !flags.isEmpty())
+ flags.append( " " );
+ flags.appendAscii( item.name );
+ }
+ }
+ if( !flags.isEmpty())
+ addKeyValue( "CPUFlags", flags.makeStringAndClear(), AddItem );
+#endif
+ // Get total memory.
+ MEMORYSTATUSEX memoryStatus;
+ memoryStatus.dwLength = sizeof( memoryStatus );
+ if( GlobalMemoryStatusEx( &memoryStatus ))
+ {
+ addKeyValue( "MemoryTotal", OUString::number( int( memoryStatus.ullTotalPhys / 1024 ))
+ + " kB", AddItem );
+ }
+}
+#else
+void CrashReporter::writeSystemInfo()
+{
+}
+#endif
+
+#endif //HAVE_FEATURE_BREAKPAD
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */