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 
10 #include <desktop/crashreport.hxx>
11 #include <rtl/bootstrap.hxx>
12 #include <osl/file.hxx>
15 #include <unotools/bootstrap.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 
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;
53 
54 
55 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
56 static 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
66 static 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 
87 void 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 
113 void 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 
132 void CrashReporter::writeCommonInfo()
133 {
134  writeSystemInfo();
135 
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 
170 void 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 
182 void 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 
208 namespace {
209 
210 OUString 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 
233 void 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 
246 bool CrashReporter::crashReportInfoExists()
247 {
248  static const bool InfoExist = crashreport::readConfig(CrashReporter::getIniFileName(), nullptr);
249  return InfoExist;
250 }
251 
252 bool CrashReporter::readSendConfig(std::string& response)
253 {
254  return crashreport::readConfig(CrashReporter::getIniFileName(), &response);
255 }
256 
257 void 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 
269 void CrashReporter::removeExceptionHandler()
270 {
271  mpExceptionHandler.reset();
272 }
273 
274 
275 
276 bool 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 
292 std::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
305 void 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
347 void CrashReporter::writeSystemInfo()
348 {
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 .
352  int cpui[ 4 ];
353  __cpuid( cpui, 0x80000000 ); // Get the highest extended ID.
354  unsigned int exIds = cpui[ 0 ];
355  if( exIds >= 0x80000004 )
356  {
357  int brand[ 16 ];
358  __cpuidex( brand, 0x80000002, 0 );
359  __cpuidex( brand + 4, 0x80000003, 0 );
360  __cpuidex( brand + 8, 0x80000004, 0 );
361  brand[ 12 ] = 0;;
362  addKeyValue( "CPUModelName", OUString::fromUtf8( reinterpret_cast< const char* >( brand )),
363  AddItem );
364  }
365  __cpuid( cpui, 0 ); // Get the highest ID.
366  int ids = cpui[ 0 ];
367  unsigned int ecx1 = 0, edx1 = 0, ebx7 = 0, ecx7 = 0, ecx81 = 0, edx81 = 0;
368  if( ids >= 0x1 )
369  {
370  __cpuidex( cpui, 0x1, 0 );
371  ecx1 = cpui[ 2 ];
372  edx1 = cpui[ 3 ];
373  }
374  if( ids >= 0x7 )
375  {
376  __cpuidex( cpui, 0x7, 0 );
377  ebx7 = cpui[ 1 ];
378  ecx7 = cpui[ 2 ];
379  }
380  if( exIds >= 0x80000001 )
381  {
382  __cpuidex( cpui, 0x80000001, 0 );
383  ecx81 = cpui[ 2 ];
384  edx81 = cpui[ 3 ];
385  }
386  struct FlagItem
387  {
388  unsigned int* reg;
389  int bit;
390  const char* name;
391  };
392  const FlagItem flagItems[] =
393  {
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" },
410  { &edx1, 5, "msr" },
411  { &edx1, 8, "cx8" },
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" },
419  { &edx1, 28, "ht" },
420  { &ebx7, 0, "fsgsbase" },
421  { &ebx7, 3, "bmi1" },
422  { &ebx7, 4, "hle" },
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" }
446  };
447  OUStringBuffer flags;
448  for( const FlagItem& item : flagItems )
449  {
450  if( *item.reg & ( 1U << item.bit ))
451  {
452  if( !flags.isEmpty())
453  flags.append( " " );
454  flags.appendAscii( item.name );
455  }
456  }
457  if( !flags.isEmpty())
458  addKeyValue( "CPUFlags", flags.makeStringAndClear(), AddItem );
459  // Get total memory.
460  MEMORYSTATUSEX memoryStatus;
461  memoryStatus.dwLength = sizeof( memoryStatus );
462  if( GlobalMemoryStatusEx( &memoryStatus ))
463  {
464  addKeyValue( "MemoryTotal", OUString::number( int( memoryStatus.ullTotalPhys / 1024 ))
465  + " kB", AddItem );
466  }
467 }
468 #else
469 void CrashReporter::writeSystemInfo()
470 {
471 }
472 #endif
473 
474 #endif //HAVE_FEATURE_BREAKPAD
475 
476 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
URL aURL
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)
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:198
#define bit(name)
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
#define SAL_CONFIGFILE(name)
float u
static OUString getActiveSfxObjectName()
Reference< XComponentContext > getProcessComponentContext()
const char * name
static OUString getLoggedUnoCommands()
#define SAL_WARN(area, stream)
static void logUnoCommand(SAL_UNUSED_PARAMETER const OUString &)
static void setActiveSfxObjectName(SAL_UNUSED_PARAMETER const OUString &)
static OUString getBuildIdData(OUString const &_sDefault)
static void addKeyValue(SAL_UNUSED_PARAMETER const OUString &, SAL_UNUSED_PARAMETER const OUString &, SAL_UNUSED_PARAMETER tAddKeyHandling)