]> git.saurik.com Git - wxWidgets.git/blob - src/common/debugrpt.cpp
Compilo.
[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 = GetReportName();
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.fn_str()) != 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 if(wxTheApp)
241 return wxTheApp->GetAppName();
242
243 return _T("wx");
244 }
245
246 void wxDebugReport::AddFile(const wxString& name, const wxString& description)
247 {
248 m_files.Add(name);
249 m_descriptions.Add(description);
250 }
251
252 void wxDebugReport::RemoveFile(const wxString& name)
253 {
254 const int n = m_files.Index(name);
255 wxCHECK_RET( n != wxNOT_FOUND, _T("No such file in wxDebugReport") );
256
257 m_files.RemoveAt(n);
258 m_descriptions.RemoveAt(n);
259
260 wxRemove(wxFileName(GetDirectory(), name).GetFullPath());
261 }
262
263 bool wxDebugReport::GetFile(size_t n, wxString *name, wxString *desc) const
264 {
265 if ( n >= m_files.GetCount() )
266 return false;
267
268 if ( name )
269 *name = m_files[n];
270 if ( desc )
271 *desc = m_descriptions[n];
272
273 return true;
274 }
275
276 void wxDebugReport::AddAll(Context context)
277 {
278 #if wxUSE_STACKWALKER
279 AddContext(context);
280 #endif // wxUSE_STACKWALKER
281
282 #if wxUSE_CRASHREPORT
283 AddDump(context);
284 #endif // wxUSE_CRASHREPORT
285
286 #if !wxUSE_STACKWALKER && !wxUSE_CRASHREPORT
287 wxUnusedVar(context);
288 #endif
289 }
290
291 // ----------------------------------------------------------------------------
292 // adding basic text information about current context
293 // ----------------------------------------------------------------------------
294
295 #if wxUSE_STACKWALKER
296
297 bool wxDebugReport::DoAddSystemInfo(wxXmlNode *nodeSystemInfo)
298 {
299 nodeSystemInfo->AddProperty(_T("description"), wxGetOsDescription());
300
301 return true;
302 }
303
304 bool wxDebugReport::DoAddLoadedModules(wxXmlNode *nodeModules)
305 {
306 wxDynamicLibraryDetailsArray modules(wxDynamicLibrary::ListLoaded());
307 const size_t count = modules.GetCount();
308 if ( !count )
309 return false;
310
311 for ( size_t n = 0; n < count; n++ )
312 {
313 const wxDynamicLibraryDetails& info = modules[n];
314
315 wxXmlNode *nodeModule = new wxXmlNode(wxXML_ELEMENT_NODE, _T("module"));
316 nodeModules->AddChild(nodeModule);
317
318 wxString path = info.GetPath();
319 if ( path.empty() )
320 path = info.GetName();
321 if ( !path.empty() )
322 nodeModule->AddProperty(_T("path"), path);
323
324 void *addr;
325 size_t len;
326 if ( info.GetAddress(&addr, &len) )
327 {
328 HexProperty(nodeModule, _T("address"), (unsigned long)addr);
329 HexProperty(nodeModule, _T("size"), len);
330 }
331
332 wxString ver = info.GetVersion();
333 if ( !ver.empty() )
334 {
335 nodeModule->AddProperty(_T("version"), ver);
336 }
337 }
338
339 return true;
340 }
341
342 bool wxDebugReport::DoAddExceptionInfo(wxXmlNode *nodeContext)
343 {
344 #if wxUSE_CRASHREPORT
345 wxCrashContext c;
346 if ( !c.code )
347 return false;
348
349 wxXmlNode *nodeExc = new wxXmlNode(wxXML_ELEMENT_NODE, _T("exception"));
350 nodeContext->AddChild(nodeExc);
351
352 HexProperty(nodeExc, _T("code"), c.code);
353 nodeExc->AddProperty(_T("name"), c.GetExceptionString());
354 HexProperty(nodeExc, _T("address"), (unsigned long)c.addr);
355
356 #ifdef __INTEL__
357 wxXmlNode *nodeRegs = new wxXmlNode(wxXML_ELEMENT_NODE, _T("registers"));
358 nodeContext->AddChild(nodeRegs);
359 HexElement(nodeRegs, _T("eax"), c.regs.eax);
360 HexElement(nodeRegs, _T("ebx"), c.regs.ebx);
361 HexElement(nodeRegs, _T("ecx"), c.regs.edx);
362 HexElement(nodeRegs, _T("edx"), c.regs.edx);
363 HexElement(nodeRegs, _T("esi"), c.regs.esi);
364 HexElement(nodeRegs, _T("edi"), c.regs.edi);
365
366 HexElement(nodeRegs, _T("ebp"), c.regs.ebp);
367 HexElement(nodeRegs, _T("esp"), c.regs.esp);
368 HexElement(nodeRegs, _T("eip"), c.regs.eip);
369
370 HexElement(nodeRegs, _T("cs"), c.regs.cs);
371 HexElement(nodeRegs, _T("ds"), c.regs.ds);
372 HexElement(nodeRegs, _T("es"), c.regs.es);
373 HexElement(nodeRegs, _T("fs"), c.regs.fs);
374 HexElement(nodeRegs, _T("gs"), c.regs.gs);
375 HexElement(nodeRegs, _T("ss"), c.regs.ss);
376
377 HexElement(nodeRegs, _T("flags"), c.regs.flags);
378 #endif // __INTEL__
379
380 return true;
381 #else // !wxUSE_CRASHREPORT
382 wxUnusedVar(nodeContext);
383
384 return false;
385 #endif // wxUSE_CRASHREPORT/!wxUSE_CRASHREPORT
386 }
387
388 bool wxDebugReport::AddContext(wxDebugReport::Context ctx)
389 {
390 wxCHECK_MSG( IsOk(), false, _T("use IsOk() first") );
391
392 // create XML dump of current context
393 wxXmlDocument xmldoc;
394 wxXmlNode *nodeRoot = new wxXmlNode(wxXML_ELEMENT_NODE, _T("report"));
395 xmldoc.SetRoot(nodeRoot);
396 nodeRoot->AddProperty(_T("version"), _T("1.0"));
397 nodeRoot->AddProperty(_T("kind"), ctx == Context_Curent ? _T("user")
398 : _T("exception"));
399
400 // add system information
401 wxXmlNode *nodeSystemInfo = new wxXmlNode(wxXML_ELEMENT_NODE, _T("system"));
402 if ( DoAddSystemInfo(nodeSystemInfo) )
403 nodeRoot->AddChild(nodeSystemInfo);
404 else
405 delete nodeSystemInfo;
406
407 // add information about the loaded modules
408 wxXmlNode *nodeModules = new wxXmlNode(wxXML_ELEMENT_NODE, _T("modules"));
409 if ( DoAddLoadedModules(nodeModules) )
410 nodeRoot->AddChild(nodeModules);
411 else
412 delete nodeModules;
413
414 // add CPU context information: this only makes sense for exceptions as our
415 // current context is not very interesting otherwise
416 if ( ctx == Context_Exception )
417 {
418 wxXmlNode *nodeContext = new wxXmlNode(wxXML_ELEMENT_NODE, _T("context"));
419 if ( DoAddExceptionInfo(nodeContext) )
420 nodeRoot->AddChild(nodeContext);
421 else
422 delete nodeContext;
423 }
424
425 // add stack traceback
426 #if wxUSE_STACKWALKER
427 wxXmlNode *nodeStack = new wxXmlNode(wxXML_ELEMENT_NODE, _T("stack"));
428 XmlStackWalker sw(nodeStack);
429 if ( ctx == Context_Exception )
430 {
431 sw.WalkFromException();
432 }
433 else // Context_Curent
434 {
435 sw.Walk();
436 }
437
438 if ( sw.IsOk() )
439 nodeRoot->AddChild(nodeStack);
440 else
441 delete nodeStack;
442 #endif // wxUSE_STACKWALKER
443
444 // finally let the user add any extra information he needs
445 DoAddCustomContext(nodeRoot);
446
447
448 // save the entire context dump in a file
449 wxFileName fn(m_dir, GetReportName(), _T("xml"));
450
451 if ( !xmldoc.Save(fn.GetFullPath()) )
452 return false;
453
454 AddFile(fn.GetFullName(), _("process context description"));
455
456 return true;
457 }
458
459 #endif // wxUSE_STACKWALKER
460
461 // ----------------------------------------------------------------------------
462 // adding core dump
463 // ----------------------------------------------------------------------------
464
465 #if wxUSE_CRASHREPORT
466
467 bool wxDebugReport::AddDump(Context ctx)
468 {
469 wxCHECK_MSG( IsOk(), false, _T("use IsOk() first") );
470
471 wxFileName fn(m_dir, GetReportName(), _T("dmp"));
472 wxCrashReport::SetFileName(fn.GetFullPath());
473
474 if ( !(ctx == Context_Exception ? wxCrashReport::Generate()
475 : wxCrashReport::GenerateNow()) )
476 return false;
477
478 AddFile(fn.GetFullName(), _("dump of the process state (binary)"));
479
480 return true;
481 }
482
483 #endif // wxUSE_CRASHREPORT
484
485 // ----------------------------------------------------------------------------
486 // report processing
487 // ----------------------------------------------------------------------------
488
489 bool wxDebugReport::Process()
490 {
491 if ( !GetFilesCount() )
492 {
493 wxLogError(_("Debug report generation has failed."));
494
495 return false;
496 }
497
498 if ( !DoProcess() )
499 {
500 wxLogError(_("Processing debug report has failed, leaving the files in \"%s\" directory."),
501 GetDirectory().c_str());
502
503 Reset();
504
505 return false;
506 }
507
508 return true;
509 }
510
511 bool wxDebugReport::DoProcess()
512 {
513 wxString msg = _("*** A debug report has been generated\n");
514 msg += wxString::Format(_("*** It can be found in \"%s\"\n"),
515 GetDirectory().c_str());
516 msg += _("*** And includes the following files:\n");
517
518 wxString name, desc;
519 const size_t count = GetFilesCount();
520 for ( size_t n = 0; n < count; n++ )
521 {
522 GetFile(n, &name, &desc);
523 msg += wxString::Format(_("\t%s: %s\n"), name.c_str(), desc.c_str());
524 }
525
526 msg += _("\nPlease send this report to the program maintainer, thank you!\n");
527
528 wxLogMessage(_T("%s"), msg.c_str());
529
530 // we have to do this or the report would be deleted, and we don't even
531 // have any way to ask the user if he wants to keep it from here
532 Reset();
533
534 return true;
535 }
536
537 // ============================================================================
538 // wxDebugReport-derived classes
539 // ============================================================================
540
541 #if wxUSE_ZIPSTREAM
542
543 // ----------------------------------------------------------------------------
544 // wxDebugReportCompress
545 // ----------------------------------------------------------------------------
546
547 bool wxDebugReportCompress::DoProcess()
548 {
549 const size_t count = GetFilesCount();
550 if ( !count )
551 return false;
552
553 // create the streams
554 wxFileName fn(GetDirectory(), GetReportName(), _T("zip"));
555 wxFFileOutputStream os(fn.GetFullPath(), _T("wb"));
556 wxZipOutputStream zos(os, 9);
557
558 // add all files to the ZIP one
559 wxString name, desc;
560 for ( size_t n = 0; n < count; n++ )
561 {
562 GetFile(n, &name, &desc);
563
564 wxZipEntry *ze = new wxZipEntry(name);
565 ze->SetComment(desc);
566
567 if ( !zos.PutNextEntry(ze) )
568 return false;
569
570 wxFileName filename(fn.GetPath(), name);
571 wxFFileInputStream is(filename.GetFullPath());
572 if ( !is.IsOk() || !zos.Write(is).IsOk() )
573 return false;
574 }
575
576 if ( !zos.Close() )
577 return false;
578
579 m_zipfile = fn.GetFullPath();
580
581 return true;
582 }
583
584 // ----------------------------------------------------------------------------
585 // wxDebugReportUpload
586 // ----------------------------------------------------------------------------
587
588 wxDebugReportUpload::wxDebugReportUpload(const wxString& url,
589 const wxString& input,
590 const wxString& action,
591 const wxString& curl)
592 : m_uploadURL(url),
593 m_inputField(input),
594 m_curlCmd(curl)
595 {
596 if ( m_uploadURL.Last() != _T('/') )
597 m_uploadURL += _T('/');
598 m_uploadURL += action;
599 }
600
601 bool wxDebugReportUpload::DoProcess()
602 {
603 if ( !wxDebugReportCompress::DoProcess() )
604 return false;
605
606
607 wxArrayString output, errors;
608 int rc = wxExecute(wxString::Format
609 (
610 _T("%s -F %s=@%s %s"),
611 m_curlCmd.c_str(),
612 m_inputField.c_str(),
613 GetCompressedFileName().c_str(),
614 m_uploadURL.c_str()
615 ),
616 output,
617 errors);
618 if ( rc == -1 )
619 {
620 wxLogError(_("Failed to execute curl, please install it in PATH."));
621 }
622 else if ( rc != 0 )
623 {
624 const size_t count = errors.GetCount();
625 if ( count )
626 {
627 for ( size_t n = 0; n < count; n++ )
628 {
629 wxLogWarning(_T("%s"), errors[n].c_str());
630 }
631 }
632
633 wxLogError(_("Failed to upload the debug report (error code %d)."), rc);
634 }
635 else // rc == 0
636 {
637 if ( OnServerReply(output) )
638 return true;
639 }
640
641 return false;
642 }
643
644 #endif // wxUSE_ZIPSTREAM
645
646 #endif // wxUSE_DEBUGREPORT
647