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