LibreOffice Module desktop (master) 1
crashreport.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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 */
9
11#include <rtl/bootstrap.hxx>
12#include <osl/file.hxx>
17#include <desktop/minidump.hxx>
18#include <rtl/ustrbuf.hxx>
19
20#include <config_version.h>
21#include <config_folders.h>
22
23#include <string>
24#include <regex>
25
26
27#if HAVE_FEATURE_BREAKPAD
28
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
44
45osl::Mutex CrashReporter::maMutex;
46osl::Mutex CrashReporter::maActiveSfxObjectNameMutex;
47osl::Mutex CrashReporter::maUnoLogCmdMutex;
48std::unique_ptr<google_breakpad::ExceptionHandler> CrashReporter::mpExceptionHandler;
49bool CrashReporter::mbInit = false;
50CrashReporter::vmaKeyValues CrashReporter::maKeyValues;
51CrashReporter::vmaloggedUnoCommands CrashReporter::maloggedUnoCommands;
52OUString CrashReporter::msActiveSfxObjectName;
53
54
55#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
56static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* /*context*/, bool succeeded)
57{
60 CrashReporter::addKeyValue("DumpFile", OStringToOUString(descriptor.path(), RTL_TEXTENCODING_UTF8), CrashReporter::Write);
61 SAL_WARN("desktop", "minidump generated: " << descriptor.path());
62
63 return succeeded;
64}
65#elif defined _WIN32
66static bool dumpCallback(const wchar_t* path, const wchar_t* id,
67 void* /*context*/, EXCEPTION_POINTERS* /*exinfo*/,
68 MDRawAssertionInfo* /*assertion*/,
69 bool succeeded)
70{
71 // TODO: moggi: can we avoid this conversion
72#ifdef _MSC_VER
73#pragma warning (disable: 4996)
74#endif
75 std::wstring_convert<std::codecvt_utf8<wchar_t>> conv1;
76 std::string aPath = conv1.to_bytes(std::wstring(path)) + conv1.to_bytes(std::wstring(id)) + ".dmp";
79 CrashReporter::addKeyValue("DumpFile", OStringToOUString(aPath.c_str(), RTL_TEXTENCODING_UTF8), CrashReporter::AddItem);
80 CrashReporter::addKeyValue("GDIHandles", OUString::number(::GetGuiResources(::GetCurrentProcess(), GR_GDIOBJECTS)), CrashReporter::Write);
81 SAL_WARN("desktop", "minidump generated: " << aPath);
82 return succeeded;
83}
84#endif
85
86
87void CrashReporter::writeToFile(std::ios_base::openmode Openmode)
88{
89#if defined _WIN32
90 const std::string iniPath = getIniFileName();
91 std::wstring iniPathW;
92 const int nChars = MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, nullptr, 0);
93 auto buf = std::make_unique<wchar_t[]>(nChars);
94 if (MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, buf.get(), nChars) != 0)
95 iniPathW = buf.get();
96
97 std::ofstream ini_file
98 = iniPathW.empty() ? std::ofstream(iniPath, Openmode) : std::ofstream(iniPathW, Openmode);
99#else
100 std::ofstream ini_file(getIniFileName(), Openmode);
101#endif
102
103 for (auto& keyValue : maKeyValues)
104 {
105 ini_file << OUStringToOString(keyValue.first, RTL_TEXTENCODING_UTF8) << "=";
106 ini_file << OUStringToOString(keyValue.second, RTL_TEXTENCODING_UTF8) << "\n";
107 }
108
109 maKeyValues.clear();
110 ini_file.close();
111}
112
113void CrashReporter::addKeyValue(const OUString& rKey, const OUString& rValue, tAddKeyHandling AddKeyHandling)
114{
115 osl::MutexGuard aGuard(maMutex);
116
117 if (IsDumpEnable())
118 {
119 if (!rKey.isEmpty())
120 maKeyValues.push_back(mpair(rKey, rValue));
121
122 if (AddKeyHandling != AddItem)
123 {
124 if (mbInit)
125 writeToFile(std::ios_base::app);
126 else if (AddKeyHandling == Create)
127 writeCommonInfo();
128 }
129 }
130}
131
132void CrashReporter::writeCommonInfo()
133{
134 writeSystemInfo();
135
136 ucbhelper::InternetProxyDecider proxy_decider(::comphelper::getProcessComponentContext());
137
138 static const OUStringLiteral protocol = u"https";
139 static const OUStringLiteral url = u"crashreport.libreoffice.org";
140 const sal_Int32 port = 443;
141
142 const ucbhelper::InternetProxyServer proxy_server = proxy_decider.getProxy(protocol, url, port);
143
144 // save the new Keys
145 vmaKeyValues atlast = maKeyValues;
146 // clear the keys, the following Keys should be at the begin
147 maKeyValues.clear();
148
149 // limit the amount of code that needs to be executed before the crash reporting
150 addKeyValue("ProductName", "LibreOffice", AddItem);
151 addKeyValue("Version", LIBO_VERSION_DOTTED, AddItem);
153 addKeyValue("URL", protocol + "://" + url + "/submit/", AddItem);
154
155 if (!proxy_server.aName.isEmpty())
156 {
157 addKeyValue("Proxy", proxy_server.aName + ":" + OUString::number(proxy_server.nPort), AddItem);
158 }
159
160 // write the new keys at the end
161 maKeyValues.insert(maKeyValues.end(), atlast.begin(), atlast.end());
162
163 mbInit = true;
164
165 writeToFile(std::ios_base::trunc);
166
167 updateMinidumpLocation();
168}
169
170void CrashReporter::setActiveSfxObjectName(const OUString& rActiveSfxObjectName)
171{
172 osl::MutexGuard aGuard(maActiveSfxObjectNameMutex);
173 msActiveSfxObjectName = rActiveSfxObjectName;
174}
175
177{
178 osl::MutexGuard aGuard(maActiveSfxObjectNameMutex);
179 return msActiveSfxObjectName;
180}
181
182void CrashReporter::logUnoCommand(const OUString& rUnoCommand)
183{
184 osl::MutexGuard aGuard(maUnoLogCmdMutex);
185
186 if( maloggedUnoCommands.size() == 4 )
187 maloggedUnoCommands.pop_front();
188
189 maloggedUnoCommands.push_back(rUnoCommand);
190}
191
193{
194 osl::MutexGuard aGuard(maUnoLogCmdMutex);
195
196 OUString aCommandSeperator="";
197 OUStringBuffer aUnoCommandBuffer;
198
199 for( auto& unocommand: maloggedUnoCommands)
200 {
201 aUnoCommandBuffer.append(aCommandSeperator);
202 aUnoCommandBuffer.append(unocommand);
203 aCommandSeperator=",";
204 }
205 return aUnoCommandBuffer.makeStringAndClear();
206}
207
208namespace {
209
210OUString getCrashDirectory()
211{
212 OUString aCrashURL;
213 rtl::Bootstrap::get("CrashDirectory", aCrashURL);
214 // Need to convert to URL in case of user-defined path
215 osl::FileBase::getFileURLFromSystemPath(aCrashURL, aCrashURL);
216
217 if (aCrashURL.isEmpty()) { // Fall back to user profile
218 aCrashURL = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/crash/";
219 rtl::Bootstrap::expandMacros(aCrashURL);
220 }
221
222 if (!aCrashURL.endsWith("/"))
223 aCrashURL += "/";
224
225 osl::Directory::create(aCrashURL);
226 OUString aCrashPath;
227 osl::FileBase::getSystemPathFromFileURL(aCrashURL, aCrashPath);
228 return aCrashPath;
229}
230
231}
232
233void CrashReporter::updateMinidumpLocation()
234{
235#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
236 OUString aURL = getCrashDirectory();
237 OString aOStringUrl = OUStringToOString(aURL, RTL_TEXTENCODING_UTF8);
238 google_breakpad::MinidumpDescriptor descriptor(aOStringUrl.getStr());
239 mpExceptionHandler->set_minidump_descriptor(descriptor);
240#elif defined _WIN32
241 OUString aURL = getCrashDirectory();
242 mpExceptionHandler->set_dump_path(o3tl::toW(aURL.getStr()));
243#endif
244}
245
246bool CrashReporter::crashReportInfoExists()
247{
248 static const bool InfoExist = crashreport::readConfig(CrashReporter::getIniFileName(), nullptr);
249 return InfoExist;
250}
251
252bool CrashReporter::readSendConfig(std::string& response)
253{
254 return crashreport::readConfig(CrashReporter::getIniFileName(), &response);
255}
256
257void CrashReporter::installExceptionHandler()
258{
259 if (!IsDumpEnable())
260 return;
261#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
262 google_breakpad::MinidumpDescriptor descriptor("/tmp");
263 mpExceptionHandler = std::make_unique<google_breakpad::ExceptionHandler>(descriptor, nullptr, dumpCallback, nullptr, true, -1);
264#elif defined _WIN32
265 mpExceptionHandler = std::make_unique<google_breakpad::ExceptionHandler>(L".", nullptr, dumpCallback, nullptr, google_breakpad::ExceptionHandler::HANDLER_ALL);
266#endif
267}
268
269void CrashReporter::removeExceptionHandler()
270{
271 mpExceptionHandler.reset();
272}
273
274
275
276bool CrashReporter::IsDumpEnable()
277{
278 auto const env = std::getenv("CRASH_DUMP_ENABLE");
279 if (env != nullptr && env[0] != '\0') {
280 return true;
281 }
282 // read configuration item 'CrashDumpEnable' -> bool on/off
283 OUString sToken;
284 if (rtl::Bootstrap::get("CrashDumpEnable", sToken))
285 {
286 return sToken.toBoolean();
287 }
288 return true; // default, always on
289}
290
291
292std::string CrashReporter::getIniFileName()
293{
294 OUString url = getCrashDirectory() + "dump.ini";
295 OString aUrl = OUStringToOString(url, RTL_TEXTENCODING_UTF8);
296 std::string aRet(aUrl.getStr());
297 return aRet;
298}
299
300// Write system-specific information such as the CPU name and features.
301// This may allow us to get some statistics for decisions (such as when
302// deciding whether SSE2 can be made a hard-requirement for Windows).
303// Breakpad provides this information poorly or not at all.
304#if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
305void CrashReporter::writeSystemInfo()
306{
307 // Get 'model name' and 'flags' from /proc/cpuinfo.
308 if( std::ifstream cpuinfo( "/proc/cpuinfo" ); cpuinfo )
309 {
310 bool haveModel = false;
311 bool haveFlags = false;
312 std::regex modelRegex( "^model name[ \t]*:[ \t]*(.*)$" );
313 std::regex flagsRegex( "^flags[ \t]*:[ \t]*(.*)$" );
314 for( std::string line; std::getline( cpuinfo, line ); )
315 {
316 std::smatch match;
317 if( !haveModel && std::regex_match( line, match, modelRegex ) && match.size() == 2)
318 {
319 addKeyValue("CPUModelName", OUString::fromUtf8( match[ 1 ].str()), AddItem);
320 haveModel = true;
321 }
322 if( !haveFlags && std::regex_match( line, match, flagsRegex ) && match.size() == 2)
323 {
324 addKeyValue("CPUFlags", OUString::fromUtf8( match[ 1 ].str()), AddItem);
325 haveFlags = true;
326 }
327 if( haveModel && haveFlags )
328 break;
329 }
330 }
331 // Get 'MemTotal' from /proc/meminfo.
332 if( std::ifstream meminfo( "/proc/meminfo" ); meminfo )
333 {
334 std::regex memTotalRegex( "^MemTotal[ \t]*:[ \t]*(.*)$" );
335 for( std::string line; std::getline( meminfo, line ); )
336 {
337 std::smatch match;
338 if( std::regex_match( line, match, memTotalRegex ) && match.size() == 2)
339 {
340 addKeyValue("MemoryTotal", OUString::fromUtf8( match[ 1 ].str()), AddItem);
341 break;
342 }
343 }
344 }
345}
346#elif defined _WIN32
347void CrashReporter::writeSystemInfo()
348{
349#if !defined(_ARM64_)
350 // Get CPU model name and flags.
351 // See https://docs.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex
352 // and https://en.wikipedia.org/wiki/CPUID .
353 int cpui[ 4 ];
354 __cpuid( cpui, 0x80000000 ); // Get the highest extended ID.
355 unsigned int exIds = cpui[ 0 ];
356 if( exIds >= 0x80000004 )
357 {
358 int brand[ 16 ];
359 __cpuidex( brand, 0x80000002, 0 );
360 __cpuidex( brand + 4, 0x80000003, 0 );
361 __cpuidex( brand + 8, 0x80000004, 0 );
362 brand[ 12 ] = 0;;
363 addKeyValue( "CPUModelName", OUString::fromUtf8( reinterpret_cast< const char* >( brand )),
364 AddItem );
365 }
366 __cpuid( cpui, 0 ); // Get the highest ID.
367 int ids = cpui[ 0 ];
368 unsigned int ecx1 = 0, edx1 = 0, ebx7 = 0, ecx7 = 0, ecx81 = 0, edx81 = 0;
369 if( ids >= 0x1 )
370 {
371 __cpuidex( cpui, 0x1, 0 );
372 ecx1 = cpui[ 2 ];
373 edx1 = cpui[ 3 ];
374 }
375 if( ids >= 0x7 )
376 {
377 __cpuidex( cpui, 0x7, 0 );
378 ebx7 = cpui[ 1 ];
379 ecx7 = cpui[ 2 ];
380 }
381 if( exIds >= 0x80000001 )
382 {
383 __cpuidex( cpui, 0x80000001, 0 );
384 ecx81 = cpui[ 2 ];
385 edx81 = cpui[ 3 ];
386 }
387 struct FlagItem
388 {
389 unsigned int* reg;
390 int bit;
391 const char* name;
392 };
393 const FlagItem flagItems[] =
394 {
395 { &ecx1, 0, "sse3" },
396 { &ecx1, 1, "pclmulqdq" },
397 { &ecx1, 3, "monitor" },
398 { &ecx1, 9, "ssse3" },
399 { &ecx1, 12, "fma" },
400 { &ecx1, 13, "cpmxch16b" },
401 { &ecx1, 19, "sse41" },
402 { &ecx1, 20, "sse42" },
403 { &ecx1, 22, "movbe" },
404 { &ecx1, 23, "popcnt" },
405 { &ecx1, 25, "aes" },
406 { &ecx1, 26, "xsave" },
407 { &ecx1, 27, "osxsave" },
408 { &ecx1, 28, "avx" },
409 { &ecx1, 29, "f16c" },
410 { &ecx1, 30, "rdrand" },
411 { &edx1, 5, "msr" },
412 { &edx1, 8, "cx8" },
413 { &edx1, 11, "sep" },
414 { &edx1, 15, "cmov" },
415 { &edx1, 19, "clfsh" },
416 { &edx1, 23, "mmx" },
417 { &edx1, 24, "fxsr" },
418 { &edx1, 25, "sse" },
419 { &edx1, 26, "sse2" },
420 { &edx1, 28, "ht" },
421 { &ebx7, 0, "fsgsbase" },
422 { &ebx7, 3, "bmi1" },
423 { &ebx7, 4, "hle" },
424 { &ebx7, 5, "avx2" },
425 { &ebx7, 8, "bmi2" },
426 { &ebx7, 9, "erms" },
427 { &ebx7, 10, "invpcid" },
428 { &ebx7, 11, "rtm" },
429 { &ebx7, 16, "avx512f" },
430 { &ebx7, 18, "rdseed" },
431 { &ebx7, 19, "adx" },
432 { &ebx7, 26, "avx512pf" },
433 { &ebx7, 27, "avx512er" },
434 { &ebx7, 28, "avx512cd" },
435 { &ebx7, 29, "sha" },
436 { &ecx7, 0, "prefetchwt1" },
437 { &ecx81, 0, "lahf" },
438 { &ecx81, 5, "abm" },
439 { &ecx81, 6, "sse4a" },
440 { &ecx81, 11, "xop" },
441 { &ecx81, 21, "tbm" },
442 { &edx81, 11, "syscall" },
443 { &edx81, 22, "mmxext" },
444 { &edx81, 27, "rdtscp" },
445 { &edx81, 30, "3dnowext" },
446 { &edx81, 31, "3dnow" }
447 };
448 OUStringBuffer flags;
449 for( const FlagItem& item : flagItems )
450 {
451 if( *item.reg & ( 1U << item.bit ))
452 {
453 if( !flags.isEmpty())
454 flags.append( " " );
455 flags.appendAscii( item.name );
456 }
457 }
458 if( !flags.isEmpty())
459 addKeyValue( "CPUFlags", flags.makeStringAndClear(), AddItem );
460#endif
461 // Get total memory.
462 MEMORYSTATUSEX memoryStatus;
463 memoryStatus.dwLength = sizeof( memoryStatus );
464 if( GlobalMemoryStatusEx( &memoryStatus ))
465 {
466 addKeyValue( "MemoryTotal", OUString::number( int( memoryStatus.ullTotalPhys / 1024 ))
467 + " kB", AddItem );
468 }
469}
470#else
471void CrashReporter::writeSystemInfo()
472{
473}
474#endif
475
476#endif //HAVE_FEATURE_BREAKPAD
477
478/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static OUString getLoggedUnoCommands()
static void logUnoCommand(SAL_UNUSED_PARAMETER const OUString &)
static void setActiveSfxObjectName(SAL_UNUSED_PARAMETER const OUString &)
static OUString getActiveSfxObjectName()
static void addKeyValue(SAL_UNUSED_PARAMETER const OUString &, SAL_UNUSED_PARAMETER const OUString &, SAL_UNUSED_PARAMETER tAddKeyHandling)
static OUString getBuildIdData(OUString const &_sDefault)
#define SAL_CONFIGFILE(name)
URL aURL
float u
const char * name
#define SAL_WARN(area, stream)
const css::uno::Reference< css::xml::crypto::XSecurityEnvironment > & env
bool match(const sal_Unicode *pWild, const sal_Unicode *pStr, const sal_Unicode cEscape)
bool readConfig(const std::string &iniPath, std::string *response)
Read+Send, Test and send info from the Dump.ini .
Definition: minidump.cxx:190
css::uno::Reference< css::deployment::XPackageRegistry > create(css::uno::Reference< css::deployment::XPackageRegistry > const &xRootRegistry, OUString const &context, OUString const &cachePath, css::uno::Reference< css::uno::XComponentContext > const &xComponentContext)
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
#define bit(name)