1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
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>
27 #if HAVE_FEATURE_BREAKPAD
30 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
31 #include <client/linux/handler/exception_handler.h>
34 #pragma clang diagnostic push
35 #pragma clang diagnostic ignored "-Wmicrosoft-enum-value"
37 #include <client/windows/handler/exception_handler.h>
39 #pragma clang diagnostic pop
45 osl::Mutex
CrashReporter::maMutex
;
46 osl::Mutex
CrashReporter::maActiveSfxObjectNameMutex
;
47 osl::Mutex
CrashReporter::maUnoLogCmdMutex
;
48 std::unique_ptr
<google_breakpad::ExceptionHandler
> CrashReporter::mpExceptionHandler
;
49 bool CrashReporter::mbInit
= false;
50 CrashReporter::vmaKeyValues
CrashReporter::maKeyValues
;
51 CrashReporter::vmaloggedUnoCommands
CrashReporter::maloggedUnoCommands
;
52 OUString
CrashReporter::msActiveSfxObjectName
;
55 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
56 static bool dumpCallback(const google_breakpad::MinidumpDescriptor
& descriptor
, void* /*context*/, bool succeeded
)
58 CrashReporter::addKeyValue("Active-SfxObject",CrashReporter::getActiveSfxObjectName(),CrashReporter::AddItem
);
59 CrashReporter::addKeyValue("Last-4-Uno-Commands",CrashReporter::getLoggedUnoCommands(),CrashReporter::AddItem
);
60 CrashReporter::addKeyValue("DumpFile", OStringToOUString(descriptor
.path(), RTL_TEXTENCODING_UTF8
), CrashReporter::Write
);
61 SAL_WARN("desktop", "minidump generated: " << descriptor
.path());
66 static bool dumpCallback(const wchar_t* path
, const wchar_t* id
,
67 void* /*context*/, EXCEPTION_POINTERS
* /*exinfo*/,
68 MDRawAssertionInfo
* /*assertion*/,
71 // TODO: moggi: can we avoid this conversion
73 #pragma warning (disable: 4996)
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";
77 CrashReporter::addKeyValue("Active-SfxObject",CrashReporter::getActiveSfxObjectName(),CrashReporter::AddItem
);
78 CrashReporter::addKeyValue("Last-4-Uno-Commands",CrashReporter::getLoggedUnoCommands(),CrashReporter::AddItem
);
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
);
87 void CrashReporter::writeToFile(std::ios_base::openmode Openmode
)
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)
97 std::ofstream ini_file
98 = iniPathW
.empty() ? std::ofstream(iniPath
, Openmode
) : std::ofstream(iniPathW
, Openmode
);
100 std::ofstream
ini_file(getIniFileName(), Openmode
);
103 for (auto& keyValue
: maKeyValues
)
105 ini_file
<< OUStringToOString(keyValue
.first
, RTL_TEXTENCODING_UTF8
) << "=";
106 ini_file
<< OUStringToOString(keyValue
.second
, RTL_TEXTENCODING_UTF8
) << "\n";
113 void CrashReporter::addKeyValue(const OUString
& rKey
, const OUString
& rValue
, tAddKeyHandling AddKeyHandling
)
115 osl::MutexGuard
aGuard(maMutex
);
120 maKeyValues
.push_back(mpair(rKey
, rValue
));
122 if (AddKeyHandling
!= AddItem
)
125 writeToFile(std::ios_base::app
);
126 else if (AddKeyHandling
== Create
)
132 void CrashReporter::writeCommonInfo()
136 ucbhelper::InternetProxyDecider
proxy_decider(::comphelper::getProcessComponentContext());
138 static const OUStringLiteral protocol
= u
"https";
139 static const OUStringLiteral url
= u
"crashreport.libreoffice.org";
140 const sal_Int32 port
= 443;
142 const ucbhelper::InternetProxyServer proxy_server
= proxy_decider
.getProxy(protocol
, url
, port
);
145 vmaKeyValues atlast
= maKeyValues
;
146 // clear the keys, the following Keys should be at the begin
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
);
152 addKeyValue("BuildID", utl::Bootstrap::getBuildIdData(""), AddItem
);
153 addKeyValue("URL", protocol
+ "://" + url
+ "/submit/", AddItem
);
155 if (!proxy_server
.aName
.isEmpty())
157 addKeyValue("Proxy", proxy_server
.aName
+ ":" + OUString::number(proxy_server
.nPort
), AddItem
);
160 // write the new keys at the end
161 maKeyValues
.insert(maKeyValues
.end(), atlast
.begin(), atlast
.end());
165 writeToFile(std::ios_base::trunc
);
167 updateMinidumpLocation();
170 void CrashReporter::setActiveSfxObjectName(const OUString
& rActiveSfxObjectName
)
172 osl::MutexGuard
aGuard(maActiveSfxObjectNameMutex
);
173 msActiveSfxObjectName
= rActiveSfxObjectName
;
176 OUString
CrashReporter::getActiveSfxObjectName()
178 osl::MutexGuard
aGuard(maActiveSfxObjectNameMutex
);
179 return msActiveSfxObjectName
;
182 void CrashReporter::logUnoCommand(const OUString
& rUnoCommand
)
184 osl::MutexGuard
aGuard(maUnoLogCmdMutex
);
186 if( maloggedUnoCommands
.size() == 4 )
187 maloggedUnoCommands
.pop_front();
189 maloggedUnoCommands
.push_back(rUnoCommand
);
192 OUString
CrashReporter::getLoggedUnoCommands()
194 osl::MutexGuard
aGuard(maUnoLogCmdMutex
);
196 OUString aCommandSeperator
="";
197 OUStringBuffer aUnoCommandBuffer
;
199 for( auto& unocommand
: maloggedUnoCommands
)
201 aUnoCommandBuffer
.append(aCommandSeperator
+ unocommand
);
202 aCommandSeperator
=",";
204 return aUnoCommandBuffer
.makeStringAndClear();
209 OUString
getCrashDirectory()
212 rtl::Bootstrap::get("CrashDirectory", aCrashURL
);
213 // Need to convert to URL in case of user-defined path
214 osl::FileBase::getFileURLFromSystemPath(aCrashURL
, aCrashURL
);
216 if (aCrashURL
.isEmpty()) { // Fall back to user profile
217 aCrashURL
= "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
"/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/crash/";
218 rtl::Bootstrap::expandMacros(aCrashURL
);
221 if (!aCrashURL
.endsWith("/"))
224 osl::Directory::create(aCrashURL
);
226 osl::FileBase::getSystemPathFromFileURL(aCrashURL
, aCrashPath
);
232 void CrashReporter::updateMinidumpLocation()
234 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
235 OUString aURL
= getCrashDirectory();
236 OString aOStringUrl
= OUStringToOString(aURL
, RTL_TEXTENCODING_UTF8
);
237 google_breakpad::MinidumpDescriptor
descriptor(std::string
{aOStringUrl
});
238 mpExceptionHandler
->set_minidump_descriptor(descriptor
);
240 OUString aURL
= getCrashDirectory();
241 mpExceptionHandler
->set_dump_path(o3tl::toW(aURL
.getStr()));
245 bool CrashReporter::crashReportInfoExists()
247 static const bool InfoExist
= crashreport::readConfig(CrashReporter::getIniFileName(), nullptr);
251 bool CrashReporter::readSendConfig(std::string
& response
)
253 return crashreport::readConfig(CrashReporter::getIniFileName(), &response
);
256 void CrashReporter::installExceptionHandler()
260 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
261 google_breakpad::MinidumpDescriptor
descriptor("/tmp");
262 mpExceptionHandler
= std::make_unique
<google_breakpad::ExceptionHandler
>(descriptor
, nullptr, dumpCallback
, nullptr, true, -1);
264 mpExceptionHandler
= std::make_unique
<google_breakpad::ExceptionHandler
>(L
".", nullptr, dumpCallback
, nullptr, google_breakpad::ExceptionHandler::HANDLER_ALL
);
268 void CrashReporter::removeExceptionHandler()
270 mpExceptionHandler
.reset();
275 bool CrashReporter::IsDumpEnable()
277 auto const env
= std::getenv("CRASH_DUMP_ENABLE");
278 if (env
!= nullptr && env
[0] != '\0') {
281 // read configuration item 'CrashDumpEnable' -> bool on/off
283 if (rtl::Bootstrap::get("CrashDumpEnable", sToken
))
285 return sToken
.toBoolean();
287 return true; // default, always on
291 std::string
CrashReporter::getIniFileName()
293 OUString url
= getCrashDirectory() + "dump.ini";
294 OString aUrl
= OUStringToOString(url
, RTL_TEXTENCODING_UTF8
);
295 std::string
aRet(aUrl
);
299 // Write system-specific information such as the CPU name and features.
300 // This may allow us to get some statistics for decisions (such as when
301 // deciding whether SSE2 can be made a hard-requirement for Windows).
302 // Breakpad provides this information poorly or not at all.
303 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
304 void CrashReporter::writeSystemInfo()
306 // Get 'model name' and 'flags' from /proc/cpuinfo.
307 if( std::ifstream
cpuinfo( "/proc/cpuinfo" ); cpuinfo
)
309 bool haveModel
= false;
310 bool haveFlags
= false;
311 std::regex
modelRegex( "^model name[ \t]*:[ \t]*(.*)$" );
312 std::regex
flagsRegex( "^flags[ \t]*:[ \t]*(.*)$" );
313 for( std::string line
; std::getline( cpuinfo
, line
); )
316 if( !haveModel
&& std::regex_match( line
, match
, modelRegex
) && match
.size() == 2)
318 addKeyValue("CPUModelName", OUString::fromUtf8( match
[ 1 ].str()), AddItem
);
321 if( !haveFlags
&& std::regex_match( line
, match
, flagsRegex
) && match
.size() == 2)
323 addKeyValue("CPUFlags", OUString::fromUtf8( match
[ 1 ].str()), AddItem
);
326 if( haveModel
&& haveFlags
)
330 // Get 'MemTotal' from /proc/meminfo.
331 if( std::ifstream
meminfo( "/proc/meminfo" ); meminfo
)
333 std::regex
memTotalRegex( "^MemTotal[ \t]*:[ \t]*(.*)$" );
334 for( std::string line
; std::getline( meminfo
, line
); )
337 if( std::regex_match( line
, match
, memTotalRegex
) && match
.size() == 2)
339 addKeyValue("MemoryTotal", OUString::fromUtf8( match
[ 1 ].str()), AddItem
);
346 void CrashReporter::writeSystemInfo()
348 #if !defined(_ARM64_)
349 // Get CPU model name and flags.
350 // See https://docs.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex
351 // and https://en.wikipedia.org/wiki/CPUID .
353 __cpuid( cpui
, 0x80000000 ); // Get the highest extended ID.
354 unsigned int exIds
= cpui
[ 0 ];
355 if( exIds
>= 0x80000004 )
358 __cpuidex( brand
, 0x80000002, 0 );
359 __cpuidex( brand
+ 4, 0x80000003, 0 );
360 __cpuidex( brand
+ 8, 0x80000004, 0 );
362 addKeyValue( "CPUModelName", OUString::fromUtf8( reinterpret_cast< const char* >( brand
)),
365 __cpuid( cpui
, 0 ); // Get the highest ID.
367 unsigned int ecx1
= 0, edx1
= 0, ebx7
= 0, ecx7
= 0, ecx81
= 0, edx81
= 0;
370 __cpuidex( cpui
, 0x1, 0 );
376 __cpuidex( cpui
, 0x7, 0 );
380 if( exIds
>= 0x80000001 )
382 __cpuidex( cpui
, 0x80000001, 0 );
392 const FlagItem flagItems
[] =
394 { &ecx1
, 0, "sse3" },
395 { &ecx1
, 1, "pclmulqdq" },
396 { &ecx1
, 3, "monitor" },
397 { &ecx1
, 9, "ssse3" },
398 { &ecx1
, 12, "fma" },
399 { &ecx1
, 13, "cpmxch16b" },
400 { &ecx1
, 19, "sse41" },
401 { &ecx1
, 20, "sse42" },
402 { &ecx1
, 22, "movbe" },
403 { &ecx1
, 23, "popcnt" },
404 { &ecx1
, 25, "aes" },
405 { &ecx1
, 26, "xsave" },
406 { &ecx1
, 27, "osxsave" },
407 { &ecx1
, 28, "avx" },
408 { &ecx1
, 29, "f16c" },
409 { &ecx1
, 30, "rdrand" },
412 { &edx1
, 11, "sep" },
413 { &edx1
, 15, "cmov" },
414 { &edx1
, 19, "clfsh" },
415 { &edx1
, 23, "mmx" },
416 { &edx1
, 24, "fxsr" },
417 { &edx1
, 25, "sse" },
418 { &edx1
, 26, "sse2" },
420 { &ebx7
, 0, "fsgsbase" },
421 { &ebx7
, 3, "bmi1" },
423 { &ebx7
, 5, "avx2" },
424 { &ebx7
, 8, "bmi2" },
425 { &ebx7
, 9, "erms" },
426 { &ebx7
, 10, "invpcid" },
427 { &ebx7
, 11, "rtm" },
428 { &ebx7
, 16, "avx512f" },
429 { &ebx7
, 18, "rdseed" },
430 { &ebx7
, 19, "adx" },
431 { &ebx7
, 26, "avx512pf" },
432 { &ebx7
, 27, "avx512er" },
433 { &ebx7
, 28, "avx512cd" },
434 { &ebx7
, 29, "sha" },
435 { &ecx7
, 0, "prefetchwt1" },
436 { &ecx81
, 0, "lahf" },
437 { &ecx81
, 5, "abm" },
438 { &ecx81
, 6, "sse4a" },
439 { &ecx81
, 11, "xop" },
440 { &ecx81
, 21, "tbm" },
441 { &edx81
, 11, "syscall" },
442 { &edx81
, 22, "mmxext" },
443 { &edx81
, 27, "rdtscp" },
444 { &edx81
, 30, "3dnowext" },
445 { &edx81
, 31, "3dnow" }
447 OUStringBuffer flags
;
448 for( const FlagItem
& item
: flagItems
)
450 if( *item
.reg
& ( 1U << item
.bit
))
452 if( !flags
.isEmpty())
454 flags
.appendAscii( item
.name
);
457 if( !flags
.isEmpty())
458 addKeyValue( "CPUFlags", flags
.makeStringAndClear(), AddItem
);
461 MEMORYSTATUSEX memoryStatus
;
462 memoryStatus
.dwLength
= sizeof( memoryStatus
);
463 if( GlobalMemoryStatusEx( &memoryStatus
))
465 addKeyValue( "MemoryTotal", OUString::number( int( memoryStatus
.ullTotalPhys
/ 1024 ))
470 void CrashReporter::writeSystemInfo()
475 #endif //HAVE_FEATURE_BREAKPAD
477 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */