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