/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_FEATURE_BREAKPAD #include #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID #include #elif defined _WIN32 #if defined __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmicrosoft-enum-value" #endif #include #if defined __clang__ #pragma clang diagnostic pop #endif #include #include #endif osl::Mutex CrashReporter::maMutex; osl::Mutex CrashReporter::maActiveSfxObjectNameMutex; osl::Mutex CrashReporter::maUnoLogCmdMutex; std::unique_ptr 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) { OUString aPath(OUString::Concat(o3tl::toU(path)) + o3tl::toU(id) + ".dmp"); CrashReporter::addKeyValue("Active-SfxObject",CrashReporter::getActiveSfxObjectName(),CrashReporter::AddItem); CrashReporter::addKeyValue("Last-4-Uno-Commands",CrashReporter::getLoggedUnoCommands(),CrashReporter::AddItem); CrashReporter::addKeyValue("DumpFile", aPath, 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(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 constexpr OUString protocol = u"https"_ustr; static constexpr OUString url = u"crashreport.libreoffice.org"_ustr; const sal_Int32 port = 443; const OUString 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.isEmpty()) { addKeyValue("Proxy", proxy_server, 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 + 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(std::string{aOStringUrl}); mpExceptionHandler->set_minidump_descriptor(descriptor); #elif defined _WIN32 OUString aURL = getCrashDirectory(); mpExceptionHandler->set_dump_path(std::wstring(o3tl::toW(aURL))); #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(descriptor, nullptr, dumpCallback, nullptr, true, -1); #elif defined _WIN32 mpExceptionHandler = std::make_unique(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); 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: */