1/////////////////////////////////////////////////////////////////////////////// 2// Name: src/common/debugrpt.cpp 3// Purpose: wxDebugReport and related classes implementation 4// Author: Vadim Zeitlin 5// Modified by: 6// Created: 2005-01-17 7// RCS-ID: $Id: debugrpt.cpp 42650 2006-10-29 19:53:53Z VZ $ 8// Copyright: (c) 2005 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr> 9// License: wxWindows licence 10/////////////////////////////////////////////////////////////////////////////// 11 12// ============================================================================ 13// declarations 14// ============================================================================ 15 16// ---------------------------------------------------------------------------- 17// headers 18// ---------------------------------------------------------------------------- 19 20#include "wx/wxprec.h" 21 22#ifdef __BORLANDC__ 23 #pragma hdrstop 24#endif 25 26#ifndef WX_PRECOMP 27 #include "wx/app.h" 28 #include "wx/log.h" 29 #include "wx/intl.h" 30 #include "wx/utils.h" 31#endif // WX_PRECOMP 32 33#if wxUSE_DEBUGREPORT && wxUSE_XML 34 35#include "wx/debugrpt.h" 36 37#include "wx/ffile.h" 38#include "wx/filename.h" 39#include "wx/dir.h" 40#include "wx/dynlib.h" 41 42#include "wx/xml/xml.h" 43 44#if wxUSE_STACKWALKER 45 #include "wx/stackwalk.h" 46#endif 47 48#if wxUSE_CRASHREPORT 49 #include "wx/msw/crashrpt.h" 50#endif 51 52#if wxUSE_ZIPSTREAM 53 #include "wx/wfstream.h" 54 #include "wx/zipstrm.h" 55#endif // wxUSE_ZIPSTREAM 56 57WX_CHECK_BUILD_OPTIONS("wxQA") 58 59// ---------------------------------------------------------------------------- 60// XmlStackWalker: stack walker specialization which dumps stack in XML 61// ---------------------------------------------------------------------------- 62 63#if wxUSE_STACKWALKER 64 65class XmlStackWalker : public wxStackWalker 66{ 67public: 68 XmlStackWalker(wxXmlNode *nodeStack) 69 { 70 m_isOk = false; 71 m_nodeStack = nodeStack; 72 } 73 74 bool IsOk() const { return m_isOk; } 75 76protected: 77 virtual void OnStackFrame(const wxStackFrame& frame); 78 79 wxXmlNode *m_nodeStack; 80 bool m_isOk; 81}; 82 83// ---------------------------------------------------------------------------- 84// local functions 85// ---------------------------------------------------------------------------- 86 87static inline void 88HexProperty(wxXmlNode *node, const wxChar *name, unsigned long value) 89{ 90 node->AddProperty(name, wxString::Format(_T("%08lx"), value)); 91} 92 93static inline void 94NumProperty(wxXmlNode *node, const wxChar *name, unsigned long value) 95{ 96 node->AddProperty(name, wxString::Format(_T("%lu"), value)); 97} 98 99static inline void 100TextElement(wxXmlNode *node, const wxChar *name, const wxString& value) 101{ 102 wxXmlNode *nodeChild = new wxXmlNode(wxXML_ELEMENT_NODE, name); 103 node->AddChild(nodeChild); 104 nodeChild->AddChild(new wxXmlNode(wxXML_TEXT_NODE, wxEmptyString, value)); 105} 106 107#if wxUSE_CRASHREPORT && defined(__INTEL__) 108 109static inline void 110HexElement(wxXmlNode *node, const wxChar *name, unsigned long value) 111{ 112 TextElement(node, name, wxString::Format(_T("%08lx"), value)); 113} 114 115#endif // wxUSE_CRASHREPORT 116 117// ============================================================================ 118// XmlStackWalker implementation 119// ============================================================================ 120 121void XmlStackWalker::OnStackFrame(const wxStackFrame& frame) 122{ 123 m_isOk = true; 124 125 wxXmlNode *nodeFrame = new wxXmlNode(wxXML_ELEMENT_NODE, _T("frame")); 126 m_nodeStack->AddChild(nodeFrame); 127 128 NumProperty(nodeFrame, _T("level"), frame.GetLevel()); 129 wxString func = frame.GetName(); 130 if ( !func.empty() ) 131 { 132 nodeFrame->AddProperty(_T("function"), func); 133 HexProperty(nodeFrame, _T("offset"), frame.GetOffset()); 134 } 135 136 if ( frame.HasSourceLocation() ) 137 { 138 nodeFrame->AddProperty(_T("file"), frame.GetFileName()); 139 NumProperty(nodeFrame, _T("line"), frame.GetLine()); 140 } 141 142 const size_t nParams = frame.GetParamCount(); 143 if ( nParams ) 144 { 145 wxXmlNode *nodeParams = new wxXmlNode(wxXML_ELEMENT_NODE, _T("parameters")); 146 nodeFrame->AddChild(nodeParams); 147 148 for ( size_t n = 0; n < nParams; n++ ) 149 { 150 wxXmlNode * 151 nodeParam = new wxXmlNode(wxXML_ELEMENT_NODE, _T("parameter")); 152 nodeParams->AddChild(nodeParam); 153 154 NumProperty(nodeParam, _T("number"), n); 155 156 wxString type, name, value; 157 if ( !frame.GetParam(n, &type, &name, &value) ) 158 continue; 159 160 if ( !type.empty() ) 161 TextElement(nodeParam, _T("type"), type); 162 163 if ( !name.empty() ) 164 TextElement(nodeParam, _T("name"), name); 165 166 if ( !value.empty() ) 167 TextElement(nodeParam, _T("value"), value); 168 } 169 } 170} 171 172#endif // wxUSE_STACKWALKER 173 174// ============================================================================ 175// wxDebugReport implementation 176// ============================================================================ 177 178// ---------------------------------------------------------------------------- 179// initialization and cleanup 180// ---------------------------------------------------------------------------- 181 182wxDebugReport::wxDebugReport() 183{ 184 // get a temporary directory name 185 wxString appname = GetReportName(); 186 187 // we can't use CreateTempFileName() because it creates a file, not a 188 // directory, so do our best to create a unique name ourselves 189 // 190 // of course, this doesn't protect us against malicious users... 191 wxFileName fn; 192 fn.AssignTempFileName(appname); 193#if wxUSE_DATETIME 194 m_dir.Printf(_T("%s%c%s_dbgrpt-%lu-%s"), 195 fn.GetPath().c_str(), wxFILE_SEP_PATH, appname.c_str(), 196 wxGetProcessId(), 197 wxDateTime::Now().Format(_T("%Y%m%dT%H%M%S")).c_str()); 198#else 199 m_dir.Printf(_T("%s%c%s_dbgrpt-%lu"), 200 fn.GetPath().c_str(), wxFILE_SEP_PATH, appname.c_str(), 201 wxGetProcessId()); 202#endif 203 204 // as we are going to save the process state there use restrictive 205 // permissions 206 if ( !wxMkdir(m_dir, 0700) ) 207 { 208 wxLogSysError(_("Failed to create directory \"%s\""), m_dir.c_str()); 209 wxLogError(_("Debug report couldn't be created.")); 210 211 Reset(); 212 } 213} 214 215wxDebugReport::~wxDebugReport() 216{ 217 if ( !m_dir.empty() ) 218 { 219 // remove all files in this directory 220 wxDir dir(m_dir); 221 wxString file; 222 for ( bool cont = dir.GetFirst(&file); cont; cont = dir.GetNext(&file) ) 223 { 224 if ( wxRemove(wxFileName(m_dir, file).GetFullPath()) != 0 ) 225 { 226 wxLogSysError(_("Failed to remove debug report file \"%s\""), 227 file.c_str()); 228 m_dir.clear(); 229 break; 230 } 231 } 232 } 233 234 if ( !m_dir.empty() ) 235 { 236 // Temp fix: what should this be? eVC++ doesn't like wxRmDir 237#ifdef __WXWINCE__ 238 if ( wxRmdir(m_dir.fn_str()) != 0 ) 239#else 240 if ( wxRmDir(m_dir.fn_str()) != 0 ) 241#endif 242 { 243 wxLogSysError(_("Failed to clean up debug report directory \"%s\""), 244 m_dir.c_str()); 245 } 246 } 247} 248 249// ---------------------------------------------------------------------------- 250// various helpers 251// ---------------------------------------------------------------------------- 252 253wxString wxDebugReport::GetReportName() const 254{ 255 if(wxTheApp) 256 return wxTheApp->GetAppName(); 257 258 return _T("wx"); 259} 260 261void 262wxDebugReport::AddFile(const wxString& filename, const wxString& description) 263{ 264 wxString name; 265 wxFileName fn(filename); 266 if ( fn.IsAbsolute() ) 267 { 268 // we need to copy the file to the debug report directory: give it the 269 // same name there 270 name = fn.GetFullName(); 271 wxCopyFile(fn.GetFullPath(), 272 wxFileName(GetDirectory(), name).GetFullPath()); 273 } 274 else // file relative to the report directory 275 { 276 name = filename; 277 278 wxASSERT_MSG( wxFileName(GetDirectory(), name).FileExists(), 279 _T("file should exist in debug report directory") ); 280 } 281 282 m_files.Add(name); 283 m_descriptions.Add(description); 284} 285 286bool 287wxDebugReport::AddText(const wxString& filename, 288 const wxString& text, 289 const wxString& description) 290{ 291 wxASSERT_MSG( !wxFileName(filename).IsAbsolute(), 292 _T("filename should be relative to debug report directory") ); 293 294 wxFileName fn(GetDirectory(), filename); 295 wxFFile file(fn.GetFullPath(), _T("w")); 296 if ( !file.IsOpened() || !file.Write(text) ) 297 return false; 298 299 AddFile(filename, description); 300 301 return true; 302} 303 304void wxDebugReport::RemoveFile(const wxString& name) 305{ 306 const int n = m_files.Index(name); 307 wxCHECK_RET( n != wxNOT_FOUND, _T("No such file in wxDebugReport") ); 308 309 m_files.RemoveAt(n); 310 m_descriptions.RemoveAt(n); 311 312 wxRemove(wxFileName(GetDirectory(), name).GetFullPath()); 313} 314 315bool wxDebugReport::GetFile(size_t n, wxString *name, wxString *desc) const 316{ 317 if ( n >= m_files.GetCount() ) 318 return false; 319 320 if ( name ) 321 *name = m_files[n]; 322 if ( desc ) 323 *desc = m_descriptions[n]; 324 325 return true; 326} 327 328void wxDebugReport::AddAll(Context context) 329{ 330#if wxUSE_STACKWALKER 331 AddContext(context); 332#endif // wxUSE_STACKWALKER 333 334#if wxUSE_CRASHREPORT 335 AddDump(context); 336#endif // wxUSE_CRASHREPORT 337 338#if !wxUSE_STACKWALKER && !wxUSE_CRASHREPORT 339 wxUnusedVar(context); 340#endif 341} 342 343// ---------------------------------------------------------------------------- 344// adding basic text information about current context 345// ---------------------------------------------------------------------------- 346 347#if wxUSE_STACKWALKER 348 349bool wxDebugReport::DoAddSystemInfo(wxXmlNode *nodeSystemInfo) 350{ 351 nodeSystemInfo->AddProperty(_T("description"), wxGetOsDescription()); 352 353 return true; 354} 355 356bool wxDebugReport::DoAddLoadedModules(wxXmlNode *nodeModules) 357{ 358 wxDynamicLibraryDetailsArray modules(wxDynamicLibrary::ListLoaded()); 359 const size_t count = modules.GetCount(); 360 if ( !count ) 361 return false; 362 363 for ( size_t n = 0; n < count; n++ ) 364 { 365 const wxDynamicLibraryDetails& info = modules[n]; 366 367 wxXmlNode *nodeModule = new wxXmlNode(wxXML_ELEMENT_NODE, _T("module")); 368 nodeModules->AddChild(nodeModule); 369 370 wxString path = info.GetPath(); 371 if ( path.empty() ) 372 path = info.GetName(); 373 if ( !path.empty() ) 374 nodeModule->AddProperty(_T("path"), path); 375 376 void *addr = NULL; 377 size_t len = 0; 378 if ( info.GetAddress(&addr, &len) ) 379 { 380 HexProperty(nodeModule, _T("address"), wxPtrToUInt(addr)); 381 HexProperty(nodeModule, _T("size"), len); 382 } 383 384 wxString ver = info.GetVersion(); 385 if ( !ver.empty() ) 386 { 387 nodeModule->AddProperty(_T("version"), ver); 388 } 389 } 390 391 return true; 392} 393 394bool wxDebugReport::DoAddExceptionInfo(wxXmlNode *nodeContext) 395{ 396#if wxUSE_CRASHREPORT 397 wxCrashContext c; 398 if ( !c.code ) 399 return false; 400 401 wxXmlNode *nodeExc = new wxXmlNode(wxXML_ELEMENT_NODE, _T("exception")); 402 nodeContext->AddChild(nodeExc); 403 404 HexProperty(nodeExc, _T("code"), c.code); 405 nodeExc->AddProperty(_T("name"), c.GetExceptionString()); 406 HexProperty(nodeExc, _T("address"), wxPtrToUInt(c.addr)); 407 408#ifdef __INTEL__ 409 wxXmlNode *nodeRegs = new wxXmlNode(wxXML_ELEMENT_NODE, _T("registers")); 410 nodeContext->AddChild(nodeRegs); 411 HexElement(nodeRegs, _T("eax"), c.regs.eax); 412 HexElement(nodeRegs, _T("ebx"), c.regs.ebx); 413 HexElement(nodeRegs, _T("ecx"), c.regs.edx); 414 HexElement(nodeRegs, _T("edx"), c.regs.edx); 415 HexElement(nodeRegs, _T("esi"), c.regs.esi); 416 HexElement(nodeRegs, _T("edi"), c.regs.edi); 417 418 HexElement(nodeRegs, _T("ebp"), c.regs.ebp); 419 HexElement(nodeRegs, _T("esp"), c.regs.esp); 420 HexElement(nodeRegs, _T("eip"), c.regs.eip); 421 422 HexElement(nodeRegs, _T("cs"), c.regs.cs); 423 HexElement(nodeRegs, _T("ds"), c.regs.ds); 424 HexElement(nodeRegs, _T("es"), c.regs.es); 425 HexElement(nodeRegs, _T("fs"), c.regs.fs); 426 HexElement(nodeRegs, _T("gs"), c.regs.gs); 427 HexElement(nodeRegs, _T("ss"), c.regs.ss); 428 429 HexElement(nodeRegs, _T("flags"), c.regs.flags); 430#endif // __INTEL__ 431 432 return true; 433#else // !wxUSE_CRASHREPORT 434 wxUnusedVar(nodeContext); 435 436 return false; 437#endif // wxUSE_CRASHREPORT/!wxUSE_CRASHREPORT 438} 439 440bool wxDebugReport::AddContext(wxDebugReport::Context ctx) 441{ 442 wxCHECK_MSG( IsOk(), false, _T("use IsOk() first") ); 443 444 // create XML dump of current context 445 wxXmlDocument xmldoc; 446 wxXmlNode *nodeRoot = new wxXmlNode(wxXML_ELEMENT_NODE, _T("report")); 447 xmldoc.SetRoot(nodeRoot); 448 nodeRoot->AddProperty(_T("version"), _T("1.0")); 449 nodeRoot->AddProperty(_T("kind"), ctx == Context_Current ? _T("user") 450 : _T("exception")); 451 452 // add system information 453 wxXmlNode *nodeSystemInfo = new wxXmlNode(wxXML_ELEMENT_NODE, _T("system")); 454 if ( DoAddSystemInfo(nodeSystemInfo) ) 455 nodeRoot->AddChild(nodeSystemInfo); 456 else 457 delete nodeSystemInfo; 458 459 // add information about the loaded modules 460 wxXmlNode *nodeModules = new wxXmlNode(wxXML_ELEMENT_NODE, _T("modules")); 461 if ( DoAddLoadedModules(nodeModules) ) 462 nodeRoot->AddChild(nodeModules); 463 else 464 delete nodeModules; 465 466 // add CPU context information: this only makes sense for exceptions as our 467 // current context is not very interesting otherwise 468 if ( ctx == Context_Exception ) 469 { 470 wxXmlNode *nodeContext = new wxXmlNode(wxXML_ELEMENT_NODE, _T("context")); 471 if ( DoAddExceptionInfo(nodeContext) ) 472 nodeRoot->AddChild(nodeContext); 473 else 474 delete nodeContext; 475 } 476 477 // add stack traceback 478#if wxUSE_STACKWALKER 479 wxXmlNode *nodeStack = new wxXmlNode(wxXML_ELEMENT_NODE, _T("stack")); 480 XmlStackWalker sw(nodeStack); 481 if ( ctx == Context_Exception ) 482 { 483 sw.WalkFromException(); 484 } 485 else // Context_Current 486 { 487 sw.Walk(); 488 } 489 490 if ( sw.IsOk() ) 491 nodeRoot->AddChild(nodeStack); 492 else 493 delete nodeStack; 494#endif // wxUSE_STACKWALKER 495 496 // finally let the user add any extra information he needs 497 DoAddCustomContext(nodeRoot); 498 499 500 // save the entire context dump in a file 501 wxFileName fn(m_dir, GetReportName(), _T("xml")); 502 503 if ( !xmldoc.Save(fn.GetFullPath()) ) 504 return false; 505 506 AddFile(fn.GetFullName(), _("process context description")); 507 508 return true; 509} 510 511#endif // wxUSE_STACKWALKER 512 513// ---------------------------------------------------------------------------- 514// adding core dump 515// ---------------------------------------------------------------------------- 516 517#if wxUSE_CRASHREPORT 518 519bool wxDebugReport::AddDump(Context ctx) 520{ 521 wxCHECK_MSG( IsOk(), false, _T("use IsOk() first") ); 522 523 wxFileName fn(m_dir, GetReportName(), _T("dmp")); 524 wxCrashReport::SetFileName(fn.GetFullPath()); 525 526 if ( !(ctx == Context_Exception ? wxCrashReport::Generate() 527 : wxCrashReport::GenerateNow()) ) 528 return false; 529 530 AddFile(fn.GetFullName(), _("dump of the process state (binary)")); 531 532 return true; 533} 534 535#endif // wxUSE_CRASHREPORT 536 537// ---------------------------------------------------------------------------- 538// report processing 539// ---------------------------------------------------------------------------- 540 541bool wxDebugReport::Process() 542{ 543 if ( !GetFilesCount() ) 544 { 545 wxLogError(_("Debug report generation has failed.")); 546 547 return false; 548 } 549 550 if ( !DoProcess() ) 551 { 552 wxLogError(_("Processing debug report has failed, leaving the files in \"%s\" directory."), 553 GetDirectory().c_str()); 554 555 Reset(); 556 557 return false; 558 } 559 560 return true; 561} 562 563bool wxDebugReport::DoProcess() 564{ 565 wxString msg(_("A debug report has been generated. It can be found in")); 566 msg << _T("\n") 567 _T("\t") << GetDirectory() << _T("\n\n") 568 << _("And includes the following files:\n"); 569 570 wxString name, desc; 571 const size_t count = GetFilesCount(); 572 for ( size_t n = 0; n < count; n++ ) 573 { 574 GetFile(n, &name, &desc); 575 msg += wxString::Format(_("\t%s: %s\n"), name.c_str(), desc.c_str()); 576 } 577 578 msg += _("\nPlease send this report to the program maintainer, thank you!\n"); 579 580 wxLogMessage(_T("%s"), msg.c_str()); 581 582 // we have to do this or the report would be deleted, and we don't even 583 // have any way to ask the user if he wants to keep it from here 584 Reset(); 585 586 return true; 587} 588 589// ============================================================================ 590// wxDebugReport-derived classes 591// ============================================================================ 592 593#if wxUSE_ZIPSTREAM 594 595// ---------------------------------------------------------------------------- 596// wxDebugReportCompress 597// ---------------------------------------------------------------------------- 598 599bool wxDebugReportCompress::DoProcess() 600{ 601 const size_t count = GetFilesCount(); 602 if ( !count ) 603 return false; 604 605 // create the streams 606 wxFileName fn(GetDirectory(), GetReportName(), _T("zip")); 607 wxFFileOutputStream os(fn.GetFullPath(), _T("wb")); 608 wxZipOutputStream zos(os, 9); 609 610 // add all files to the ZIP one 611 wxString name, desc; 612 for ( size_t n = 0; n < count; n++ ) 613 { 614 GetFile(n, &name, &desc); 615 616 wxZipEntry *ze = new wxZipEntry(name); 617 ze->SetComment(desc); 618 619 if ( !zos.PutNextEntry(ze) ) 620 return false; 621 622 wxFileName filename(fn.GetPath(), name); 623 wxFFileInputStream is(filename.GetFullPath()); 624 if ( !is.IsOk() || !zos.Write(is).IsOk() ) 625 return false; 626 } 627 628 if ( !zos.Close() ) 629 return false; 630 631 m_zipfile = fn.GetFullPath(); 632 633 return true; 634} 635 636// ---------------------------------------------------------------------------- 637// wxDebugReportUpload 638// ---------------------------------------------------------------------------- 639 640wxDebugReportUpload::wxDebugReportUpload(const wxString& url, 641 const wxString& input, 642 const wxString& action, 643 const wxString& curl) 644 : m_uploadURL(url), 645 m_inputField(input), 646 m_curlCmd(curl) 647{ 648 if ( m_uploadURL.Last() != _T('/') ) 649 m_uploadURL += _T('/'); 650 m_uploadURL += action; 651} 652 653bool wxDebugReportUpload::DoProcess() 654{ 655 if ( !wxDebugReportCompress::DoProcess() ) 656 return false; 657 658 659 wxArrayString output, errors; 660 int rc = wxExecute(wxString::Format 661 ( 662 _T("%s -F %s=@\"%s\" %s"), 663 m_curlCmd.c_str(), 664 m_inputField.c_str(), 665 GetCompressedFileName().c_str(), 666 m_uploadURL.c_str() 667 ), 668 output, 669 errors); 670 if ( rc == -1 ) 671 { 672 wxLogError(_("Failed to execute curl, please install it in PATH.")); 673 } 674 else if ( rc != 0 ) 675 { 676 const size_t count = errors.GetCount(); 677 if ( count ) 678 { 679 for ( size_t n = 0; n < count; n++ ) 680 { 681 wxLogWarning(_T("%s"), errors[n].c_str()); 682 } 683 } 684 685 wxLogError(_("Failed to upload the debug report (error code %d)."), rc); 686 } 687 else // rc == 0 688 { 689 if ( OnServerReply(output) ) 690 return true; 691 } 692 693 return false; 694} 695 696#endif // wxUSE_ZIPSTREAM 697 698#endif // wxUSE_DEBUGREPORT 699