bump product version to 7.2.5.1
[LibreOffice.git] / desktop / source / app / crashreport.cxx
blob6f3d72934d510bdeaf2c589a90e1a24e0e6879db
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <desktop/crashreport.hxx>
11 #include <rtl/bootstrap.hxx>
12 #include <osl/file.hxx>
13 #include <comphelper/processfactory.hxx>
14 #include <ucbhelper/proxydecider.hxx>
15 #include <unotools/bootstrap.hxx>
16 #include <o3tl/char16_t2wchar_t.hxx>
17 #include <desktop/minidump.hxx>
18 #include <rtl/ustrbuf.hxx>
20 #include <config_version.h>
21 #include <config_folders.h>
23 #include <string>
24 #include <regex>
27 #if HAVE_FEATURE_BREAKPAD
29 #include <fstream>
30 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
31 #include <client/linux/handler/exception_handler.h>
32 #elif defined _WIN32
33 #if defined __clang__
34 #pragma clang diagnostic push
35 #pragma clang diagnostic ignored "-Wmicrosoft-enum-value"
36 #endif
37 #include <client/windows/handler/exception_handler.h>
38 #if defined __clang__
39 #pragma clang diagnostic pop
40 #endif
41 #include <locale>
42 #include <codecvt>
43 #endif
45 osl::Mutex CrashReporter::maMutex;
46 std::unique_ptr<google_breakpad::ExceptionHandler> CrashReporter::mpExceptionHandler;
47 bool CrashReporter::mbInit = false;
48 CrashReporter::vmaKeyValues CrashReporter::maKeyValues;
51 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
52 static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* /*context*/, bool succeeded)
54 CrashReporter::addKeyValue("DumpFile", OStringToOUString(descriptor.path(), RTL_TEXTENCODING_UTF8), CrashReporter::Write);
55 SAL_WARN("desktop", "minidump generated: " << descriptor.path());
57 return succeeded;
59 #elif defined _WIN32
60 static bool dumpCallback(const wchar_t* path, const wchar_t* id,
61 void* /*context*/, EXCEPTION_POINTERS* /*exinfo*/,
62 MDRawAssertionInfo* /*assertion*/,
63 bool succeeded)
65 // TODO: moggi: can we avoid this conversion
66 #ifdef _MSC_VER
67 #pragma warning (disable: 4996)
68 #endif
69 std::wstring_convert<std::codecvt_utf8<wchar_t>> conv1;
70 std::string aPath = conv1.to_bytes(std::wstring(path)) + conv1.to_bytes(std::wstring(id)) + ".dmp";
71 CrashReporter::addKeyValue("DumpFile", OStringToOUString(aPath.c_str(), RTL_TEXTENCODING_UTF8), CrashReporter::AddItem);
72 CrashReporter::addKeyValue("GDIHandles", OUString::number(::GetGuiResources(::GetCurrentProcess(), GR_GDIOBJECTS)), CrashReporter::Write);
73 SAL_WARN("desktop", "minidump generated: " << aPath);
74 return succeeded;
76 #endif
79 void CrashReporter::writeToFile(std::ios_base::openmode Openmode)
81 #if defined _WIN32
82 const std::string iniPath = getIniFileName();
83 std::wstring iniPathW;
84 const int nChars = MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, nullptr, 0);
85 auto buf = std::make_unique<wchar_t[]>(nChars);
86 if (MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, buf.get(), nChars) != 0)
87 iniPathW = buf.get();
89 std::ofstream ini_file
90 = iniPathW.empty() ? std::ofstream(iniPath, Openmode) : std::ofstream(iniPathW, Openmode);
91 #else
92 std::ofstream ini_file(getIniFileName(), Openmode);
93 #endif
95 for (auto& keyValue : maKeyValues)
97 ini_file << OUStringToOString(keyValue.first, RTL_TEXTENCODING_UTF8) << "=";
98 ini_file << OUStringToOString(keyValue.second, RTL_TEXTENCODING_UTF8) << "\n";
101 maKeyValues.clear();
102 ini_file.close();
105 void CrashReporter::addKeyValue(const OUString& rKey, const OUString& rValue, tAddKeyHandling AddKeyHandling)
107 osl::MutexGuard aGuard(maMutex);
109 if (IsDumpEnable())
111 if (!rKey.isEmpty())
112 maKeyValues.push_back(mpair(rKey, rValue));
114 if (AddKeyHandling != AddItem)
116 if (mbInit)
117 writeToFile(std::ios_base::app);
118 else if (AddKeyHandling == Create)
119 writeCommonInfo();
124 void CrashReporter::writeCommonInfo()
126 writeSystemInfo();
128 ucbhelper::InternetProxyDecider proxy_decider(::comphelper::getProcessComponentContext());
130 static const OUStringLiteral protocol = u"https";
131 static const OUStringLiteral url = u"crashreport.libreoffice.org";
132 const sal_Int32 port = 443;
134 const ucbhelper::InternetProxyServer proxy_server = proxy_decider.getProxy(protocol, url, port);
136 // save the new Keys
137 vmaKeyValues atlast = maKeyValues;
138 // clear the keys, the following Keys should be at the begin
139 maKeyValues.clear();
141 // limit the amount of code that needs to be executed before the crash reporting
142 addKeyValue("ProductName", "LibreOffice", AddItem);
143 addKeyValue("Version", LIBO_VERSION_DOTTED, AddItem);
144 addKeyValue("BuildID", utl::Bootstrap::getBuildIdData(""), AddItem);
145 addKeyValue("URL", protocol + "://" + url + "/submit/", AddItem);
147 if (!proxy_server.aName.isEmpty())
149 addKeyValue("Proxy", proxy_server.aName + ":" + OUString::number(proxy_server.nPort), AddItem);
152 // write the new keys at the end
153 maKeyValues.insert(maKeyValues.end(), atlast.begin(), atlast.end());
155 mbInit = true;
157 writeToFile(std::ios_base::trunc);
159 updateMinidumpLocation();
163 namespace {
165 OUString getCrashDirectory()
167 OUString aCrashURL;
168 rtl::Bootstrap::get("CrashDirectory", aCrashURL);
169 // Need to convert to URL in case of user-defined path
170 osl::FileBase::getFileURLFromSystemPath(aCrashURL, aCrashURL);
172 if (aCrashURL.isEmpty()) { // Fall back to user profile
173 aCrashURL = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/crash/";
174 rtl::Bootstrap::expandMacros(aCrashURL);
177 if (!aCrashURL.endsWith("/"))
178 aCrashURL += "/";
180 osl::Directory::create(aCrashURL);
181 OUString aCrashPath;
182 osl::FileBase::getSystemPathFromFileURL(aCrashURL, aCrashPath);
183 return aCrashPath;
188 void CrashReporter::updateMinidumpLocation()
190 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
191 OUString aURL = getCrashDirectory();
192 OString aOStringUrl = OUStringToOString(aURL, RTL_TEXTENCODING_UTF8);
193 google_breakpad::MinidumpDescriptor descriptor(aOStringUrl.getStr());
194 mpExceptionHandler->set_minidump_descriptor(descriptor);
195 #elif defined _WIN32
196 OUString aURL = getCrashDirectory();
197 mpExceptionHandler->set_dump_path(o3tl::toW(aURL.getStr()));
198 #endif
201 bool CrashReporter::crashReportInfoExists()
203 static const bool InfoExist = crashreport::readConfig(CrashReporter::getIniFileName(), nullptr);
204 return InfoExist;
207 bool CrashReporter::readSendConfig(std::string& response)
209 return crashreport::readConfig(CrashReporter::getIniFileName(), &response);
212 void CrashReporter::installExceptionHandler()
214 if (!IsDumpEnable())
215 return;
216 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
217 google_breakpad::MinidumpDescriptor descriptor("/tmp");
218 mpExceptionHandler = std::make_unique<google_breakpad::ExceptionHandler>(descriptor, nullptr, dumpCallback, nullptr, true, -1);
219 #elif defined _WIN32
220 mpExceptionHandler = std::make_unique<google_breakpad::ExceptionHandler>(L".", nullptr, dumpCallback, nullptr, google_breakpad::ExceptionHandler::HANDLER_ALL);
221 #endif
224 void CrashReporter::removeExceptionHandler()
226 mpExceptionHandler.reset();
231 bool CrashReporter::IsDumpEnable()
233 auto const env = std::getenv("CRASH_DUMP_ENABLE");
234 if (env != nullptr && env[0] != '\0') {
235 return true;
237 // read configuration item 'CrashDumpEnable' -> bool on/off
238 OUString sToken;
239 if (rtl::Bootstrap::get("CrashDumpEnable", sToken))
241 return sToken.toBoolean();
243 return true; // default, always on
247 std::string CrashReporter::getIniFileName()
249 OUString url = getCrashDirectory() + "dump.ini";
250 OString aUrl = OUStringToOString(url, RTL_TEXTENCODING_UTF8);
251 std::string aRet(aUrl.getStr());
252 return aRet;
255 // Write system-specific information such as the CPU name and features.
256 // This may allow us to get some statistics for decisions (such as when
257 // deciding whether SSE2 can be made a hard-requirement for Windows).
258 // Breakpad provides this information poorly or not at all.
259 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
260 void CrashReporter::writeSystemInfo()
262 // Get 'model name' and 'flags' from /proc/cpuinfo.
263 if( std::ifstream cpuinfo( "/proc/cpuinfo" ); cpuinfo )
265 bool haveModel = false;
266 bool haveFlags = false;
267 std::regex modelRegex( "^model name[ \t]*:[ \t]*(.*)$" );
268 std::regex flagsRegex( "^flags[ \t]*:[ \t]*(.*)$" );
269 for( std::string line; std::getline( cpuinfo, line ); )
271 std::smatch match;
272 if( !haveModel && std::regex_match( line, match, modelRegex ) && match.size() == 2)
274 addKeyValue("CPUModelName", OUString::fromUtf8( match[ 1 ].str()), AddItem);
275 haveModel = true;
277 if( !haveFlags && std::regex_match( line, match, flagsRegex ) && match.size() == 2)
279 addKeyValue("CPUFlags", OUString::fromUtf8( match[ 1 ].str()), AddItem);
280 haveFlags = true;
282 if( haveModel && haveFlags )
283 break;
286 // Get 'MemTotal' from /proc/meminfo.
287 if( std::ifstream meminfo( "/proc/meminfo" ); meminfo )
289 std::regex memTotalRegex( "^MemTotal[ \t]*:[ \t]*(.*)$" );
290 for( std::string line; std::getline( meminfo, line ); )
292 std::smatch match;
293 if( std::regex_match( line, match, memTotalRegex ) && match.size() == 2)
295 addKeyValue("MemoryTotal", OUString::fromUtf8( match[ 1 ].str()), AddItem);
296 break;
301 #elif defined _WIN32
302 void CrashReporter::writeSystemInfo()
304 // Get CPU model name and flags.
305 // See https://docs.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex
306 // and https://en.wikipedia.org/wiki/CPUID .
307 int cpui[ 4 ];
308 __cpuid( cpui, 0x80000000 ); // Get the highest extended ID.
309 unsigned int exIds = cpui[ 0 ];
310 if( exIds >= 0x80000004 )
312 int brand[ 16 ];
313 __cpuidex( brand, 0x80000002, 0 );
314 __cpuidex( brand + 4, 0x80000003, 0 );
315 __cpuidex( brand + 8, 0x80000004, 0 );
316 brand[ 12 ] = 0;;
317 addKeyValue( "CPUModelName", OUString::fromUtf8( reinterpret_cast< const char* >( brand )),
318 AddItem );
320 __cpuid( cpui, 0 ); // Get the highest ID.
321 int ids = cpui[ 0 ];
322 unsigned int ecx1 = 0, edx1 = 0, ebx7 = 0, ecx7 = 0, ecx81 = 0, edx81 = 0;
323 if( ids >= 0x1 )
325 __cpuidex( cpui, 0x1, 0 );
326 ecx1 = cpui[ 2 ];
327 edx1 = cpui[ 3 ];
329 if( ids >= 0x7 )
331 __cpuidex( cpui, 0x7, 0 );
332 ebx7 = cpui[ 1 ];
333 ecx7 = cpui[ 2 ];
335 if( exIds >= 0x80000001 )
337 __cpuidex( cpui, 0x80000001, 0 );
338 ecx81 = cpui[ 2 ];
339 edx81 = cpui[ 3 ];
341 struct FlagItem
343 unsigned int* reg;
344 int bit;
345 const char* name;
347 const FlagItem flagItems[] =
349 { &ecx1, 0, "sse3" },
350 { &ecx1, 1, "pclmulqdq" },
351 { &ecx1, 3, "monitor" },
352 { &ecx1, 9, "ssse3" },
353 { &ecx1, 12, "fma" },
354 { &ecx1, 13, "cpmxch16b" },
355 { &ecx1, 19, "sse41" },
356 { &ecx1, 20, "sse42" },
357 { &ecx1, 22, "movbe" },
358 { &ecx1, 23, "popcnt" },
359 { &ecx1, 25, "aes" },
360 { &ecx1, 26, "xsave" },
361 { &ecx1, 27, "osxsave" },
362 { &ecx1, 28, "avx" },
363 { &ecx1, 29, "f16c" },
364 { &ecx1, 30, "rdrand" },
365 { &edx1, 5, "msr" },
366 { &edx1, 8, "cx8" },
367 { &edx1, 11, "sep" },
368 { &edx1, 15, "cmov" },
369 { &edx1, 19, "clfsh" },
370 { &edx1, 23, "mmx" },
371 { &edx1, 24, "fxsr" },
372 { &edx1, 25, "sse" },
373 { &edx1, 26, "sse2" },
374 { &edx1, 28, "ht" },
375 { &ebx7, 0, "fsgsbase" },
376 { &ebx7, 3, "bmi1" },
377 { &ebx7, 4, "hle" },
378 { &ebx7, 5, "avx2" },
379 { &ebx7, 8, "bmi2" },
380 { &ebx7, 9, "erms" },
381 { &ebx7, 10, "invpcid" },
382 { &ebx7, 11, "rtm" },
383 { &ebx7, 16, "avx512f" },
384 { &ebx7, 18, "rdseed" },
385 { &ebx7, 19, "adx" },
386 { &ebx7, 26, "avx512pf" },
387 { &ebx7, 27, "avx512er" },
388 { &ebx7, 28, "avx512cd" },
389 { &ebx7, 29, "sha" },
390 { &ecx7, 0, "prefetchwt1" },
391 { &ecx81, 0, "lahf" },
392 { &ecx81, 5, "abm" },
393 { &ecx81, 6, "sse4a" },
394 { &ecx81, 11, "xop" },
395 { &ecx81, 21, "tbm" },
396 { &edx81, 11, "syscall" },
397 { &edx81, 22, "mmxext" },
398 { &edx81, 27, "rdtscp" },
399 { &edx81, 30, "3dnowext" },
400 { &edx81, 31, "3dnow" }
402 OUStringBuffer flags;
403 for( const FlagItem& item : flagItems )
405 if( *item.reg & ( 1U << item.bit ))
407 if( !flags.isEmpty())
408 flags.append( " " );
409 flags.appendAscii( item.name );
412 if( !flags.isEmpty())
413 addKeyValue( "CPUFlags", flags.makeStringAndClear(), AddItem );
414 // Get total memory.
415 MEMORYSTATUSEX memoryStatus;
416 memoryStatus.dwLength = sizeof( memoryStatus );
417 if( GlobalMemoryStatusEx( &memoryStatus ))
419 addKeyValue( "MemoryTotal", OUString::number( int( memoryStatus.ullTotalPhys / 1024 ))
420 + " kB", AddItem );
423 #else
424 void CrashReporter::writeSystemInfo()
427 #endif
429 #endif //HAVE_FEATURE_BREAKPAD
431 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */