Files
wxWidgets/src/common/debugrpt.cpp
Vadim Zeitlin fdf20a26db Allow customizing wxDebugReportCompress output file.
It may be useful to change the directory where it is generated to allow the
users to find it more quickly. Also allow changing the crash report base name
for completeness.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@61663 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2009-08-14 00:06:03 +00:00

725 lines
20 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: src/common/debugrpt.cpp
// Purpose: wxDebugReport and related classes implementation
// Author: Vadim Zeitlin
// Modified by:
// Created: 2005-01-17
// RCS-ID: $Id$
// Copyright: (c) 2005 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
// License: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/log.h"
#include "wx/intl.h"
#include "wx/utils.h"
#endif // WX_PRECOMP
#if wxUSE_DEBUGREPORT && wxUSE_XML
#include "wx/debugrpt.h"
#include "wx/ffile.h"
#include "wx/filename.h"
#include "wx/dir.h"
#include "wx/dynlib.h"
#include "wx/xml/xml.h"
#if wxUSE_STACKWALKER
#include "wx/stackwalk.h"
#endif
#if wxUSE_CRASHREPORT
#include "wx/msw/crashrpt.h"
#endif
#if wxUSE_ZIPSTREAM
#include "wx/wfstream.h"
#include "wx/zipstrm.h"
#endif // wxUSE_ZIPSTREAM
WX_CHECK_BUILD_OPTIONS("wxQA")
// ----------------------------------------------------------------------------
// XmlStackWalker: stack walker specialization which dumps stack in XML
// ----------------------------------------------------------------------------
#if wxUSE_STACKWALKER
class XmlStackWalker : public wxStackWalker
{
public:
XmlStackWalker(wxXmlNode *nodeStack)
{
m_isOk = false;
m_nodeStack = nodeStack;
}
bool IsOk() const { return m_isOk; }
protected:
virtual void OnStackFrame(const wxStackFrame& frame);
wxXmlNode *m_nodeStack;
bool m_isOk;
};
// ----------------------------------------------------------------------------
// local functions
// ----------------------------------------------------------------------------
static inline void
HexProperty(wxXmlNode *node, const wxChar *name, unsigned long value)
{
node->AddAttribute(name, wxString::Format(wxT("%08lx"), value));
}
static inline void
NumProperty(wxXmlNode *node, const wxChar *name, unsigned long value)
{
node->AddAttribute(name, wxString::Format(wxT("%lu"), value));
}
static inline void
TextElement(wxXmlNode *node, const wxChar *name, const wxString& value)
{
wxXmlNode *nodeChild = new wxXmlNode(wxXML_ELEMENT_NODE, name);
node->AddChild(nodeChild);
nodeChild->AddChild(new wxXmlNode(wxXML_TEXT_NODE, wxEmptyString, value));
}
#if wxUSE_CRASHREPORT && defined(__INTEL__)
static inline void
HexElement(wxXmlNode *node, const wxChar *name, unsigned long value)
{
TextElement(node, name, wxString::Format(wxT("%08lx"), value));
}
#endif // wxUSE_CRASHREPORT
// ============================================================================
// XmlStackWalker implementation
// ============================================================================
void XmlStackWalker::OnStackFrame(const wxStackFrame& frame)
{
m_isOk = true;
wxXmlNode *nodeFrame = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("frame"));
m_nodeStack->AddChild(nodeFrame);
NumProperty(nodeFrame, wxT("level"), frame.GetLevel());
wxString func = frame.GetName();
if ( !func.empty() )
{
nodeFrame->AddAttribute(wxT("function"), func);
HexProperty(nodeFrame, wxT("offset"), frame.GetOffset());
}
if ( frame.HasSourceLocation() )
{
nodeFrame->AddAttribute(wxT("file"), frame.GetFileName());
NumProperty(nodeFrame, wxT("line"), frame.GetLine());
}
const size_t nParams = frame.GetParamCount();
if ( nParams )
{
wxXmlNode *nodeParams = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("parameters"));
nodeFrame->AddChild(nodeParams);
for ( size_t n = 0; n < nParams; n++ )
{
wxXmlNode *
nodeParam = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("parameter"));
nodeParams->AddChild(nodeParam);
NumProperty(nodeParam, wxT("number"), n);
wxString type, name, value;
if ( !frame.GetParam(n, &type, &name, &value) )
continue;
if ( !type.empty() )
TextElement(nodeParam, wxT("type"), type);
if ( !name.empty() )
TextElement(nodeParam, wxT("name"), name);
if ( !value.empty() )
TextElement(nodeParam, wxT("value"), value);
}
}
}
#endif // wxUSE_STACKWALKER
// ============================================================================
// wxDebugReport implementation
// ============================================================================
// ----------------------------------------------------------------------------
// initialization and cleanup
// ----------------------------------------------------------------------------
wxDebugReport::wxDebugReport()
{
// get a temporary directory name
wxString appname = GetReportName();
// we can't use CreateTempFileName() because it creates a file, not a
// directory, so do our best to create a unique name ourselves
//
// of course, this doesn't protect us against malicious users...
wxFileName fn;
fn.AssignTempFileName(appname);
#if wxUSE_DATETIME
m_dir.Printf(wxT("%s%c%s_dbgrpt-%lu-%s"),
fn.GetPath().c_str(), wxFILE_SEP_PATH, appname.c_str(),
wxGetProcessId(),
wxDateTime::Now().Format(wxT("%Y%m%dT%H%M%S")).c_str());
#else
m_dir.Printf(wxT("%s%c%s_dbgrpt-%lu"),
fn.GetPath().c_str(), wxFILE_SEP_PATH, appname.c_str(),
wxGetProcessId());
#endif
// as we are going to save the process state there use restrictive
// permissions
if ( !wxMkdir(m_dir, 0700) )
{
wxLogSysError(_("Failed to create directory \"%s\""), m_dir.c_str());
wxLogError(_("Debug report couldn't be created."));
Reset();
}
}
wxDebugReport::~wxDebugReport()
{
if ( !m_dir.empty() )
{
// remove all files in this directory
wxDir dir(m_dir);
wxString file;
for ( bool cont = dir.GetFirst(&file); cont; cont = dir.GetNext(&file) )
{
if ( wxRemove(wxFileName(m_dir, file).GetFullPath()) != 0 )
{
wxLogSysError(_("Failed to remove debug report file \"%s\""),
file.c_str());
m_dir.clear();
break;
}
}
}
if ( !m_dir.empty() )
{
// Temp fix: what should this be? eVC++ doesn't like wxRmDir
#ifdef __WXWINCE__
if ( wxRmdir(m_dir.fn_str()) != 0 )
#else
if ( wxRmDir(m_dir.fn_str()) != 0 )
#endif
{
wxLogSysError(_("Failed to clean up debug report directory \"%s\""),
m_dir.c_str());
}
}
}
// ----------------------------------------------------------------------------
// various helpers
// ----------------------------------------------------------------------------
wxString wxDebugReport::GetReportName() const
{
if ( wxTheApp )
return wxTheApp->GetAppName();
return wxT("wx");
}
void
wxDebugReport::AddFile(const wxString& filename, const wxString& description)
{
wxString name;
wxFileName fn(filename);
if ( fn.IsAbsolute() )
{
// we need to copy the file to the debug report directory: give it the
// same name there
name = fn.GetFullName();
wxCopyFile(fn.GetFullPath(),
wxFileName(GetDirectory(), name).GetFullPath());
}
else // file relative to the report directory
{
name = filename;
wxASSERT_MSG( wxFileName(GetDirectory(), name).FileExists(),
wxT("file should exist in debug report directory") );
}
m_files.Add(name);
m_descriptions.Add(description);
}
bool
wxDebugReport::AddText(const wxString& filename,
const wxString& text,
const wxString& description)
{
wxASSERT_MSG( !wxFileName(filename).IsAbsolute(),
wxT("filename should be relative to debug report directory") );
wxFileName fn(GetDirectory(), filename);
wxFFile file(fn.GetFullPath(), wxT("w"));
if ( !file.IsOpened() || !file.Write(text) )
return false;
AddFile(filename, description);
return true;
}
void wxDebugReport::RemoveFile(const wxString& name)
{
const int n = m_files.Index(name);
wxCHECK_RET( n != wxNOT_FOUND, wxT("No such file in wxDebugReport") );
m_files.RemoveAt(n);
m_descriptions.RemoveAt(n);
wxRemove(wxFileName(GetDirectory(), name).GetFullPath());
}
bool wxDebugReport::GetFile(size_t n, wxString *name, wxString *desc) const
{
if ( n >= m_files.GetCount() )
return false;
if ( name )
*name = m_files[n];
if ( desc )
*desc = m_descriptions[n];
return true;
}
void wxDebugReport::AddAll(Context context)
{
#if wxUSE_STACKWALKER
AddContext(context);
#endif // wxUSE_STACKWALKER
#if wxUSE_CRASHREPORT
AddDump(context);
#endif // wxUSE_CRASHREPORT
#if !wxUSE_STACKWALKER && !wxUSE_CRASHREPORT
wxUnusedVar(context);
#endif
}
// ----------------------------------------------------------------------------
// adding basic text information about current context
// ----------------------------------------------------------------------------
#if wxUSE_STACKWALKER
bool wxDebugReport::DoAddSystemInfo(wxXmlNode *nodeSystemInfo)
{
nodeSystemInfo->AddAttribute(wxT("description"), wxGetOsDescription());
return true;
}
bool wxDebugReport::DoAddLoadedModules(wxXmlNode *nodeModules)
{
wxDynamicLibraryDetailsArray modules(wxDynamicLibrary::ListLoaded());
const size_t count = modules.GetCount();
if ( !count )
return false;
for ( size_t n = 0; n < count; n++ )
{
const wxDynamicLibraryDetails& info = modules[n];
wxXmlNode *nodeModule = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("module"));
nodeModules->AddChild(nodeModule);
wxString path = info.GetPath();
if ( path.empty() )
path = info.GetName();
if ( !path.empty() )
nodeModule->AddAttribute(wxT("path"), path);
void *addr = NULL;
size_t len = 0;
if ( info.GetAddress(&addr, &len) )
{
HexProperty(nodeModule, wxT("address"), wxPtrToUInt(addr));
HexProperty(nodeModule, wxT("size"), len);
}
wxString ver = info.GetVersion();
if ( !ver.empty() )
{
nodeModule->AddAttribute(wxT("version"), ver);
}
}
return true;
}
bool wxDebugReport::DoAddExceptionInfo(wxXmlNode *nodeContext)
{
#if wxUSE_CRASHREPORT
wxCrashContext c;
if ( !c.code )
return false;
wxXmlNode *nodeExc = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("exception"));
nodeContext->AddChild(nodeExc);
HexProperty(nodeExc, wxT("code"), c.code);
nodeExc->AddAttribute(wxT("name"), c.GetExceptionString());
HexProperty(nodeExc, wxT("address"), wxPtrToUInt(c.addr));
#ifdef __INTEL__
wxXmlNode *nodeRegs = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("registers"));
nodeContext->AddChild(nodeRegs);
HexElement(nodeRegs, wxT("eax"), c.regs.eax);
HexElement(nodeRegs, wxT("ebx"), c.regs.ebx);
HexElement(nodeRegs, wxT("ecx"), c.regs.edx);
HexElement(nodeRegs, wxT("edx"), c.regs.edx);
HexElement(nodeRegs, wxT("esi"), c.regs.esi);
HexElement(nodeRegs, wxT("edi"), c.regs.edi);
HexElement(nodeRegs, wxT("ebp"), c.regs.ebp);
HexElement(nodeRegs, wxT("esp"), c.regs.esp);
HexElement(nodeRegs, wxT("eip"), c.regs.eip);
HexElement(nodeRegs, wxT("cs"), c.regs.cs);
HexElement(nodeRegs, wxT("ds"), c.regs.ds);
HexElement(nodeRegs, wxT("es"), c.regs.es);
HexElement(nodeRegs, wxT("fs"), c.regs.fs);
HexElement(nodeRegs, wxT("gs"), c.regs.gs);
HexElement(nodeRegs, wxT("ss"), c.regs.ss);
HexElement(nodeRegs, wxT("flags"), c.regs.flags);
#endif // __INTEL__
return true;
#else // !wxUSE_CRASHREPORT
wxUnusedVar(nodeContext);
return false;
#endif // wxUSE_CRASHREPORT/!wxUSE_CRASHREPORT
}
bool wxDebugReport::AddContext(wxDebugReport::Context ctx)
{
wxCHECK_MSG( IsOk(), false, wxT("use IsOk() first") );
// create XML dump of current context
wxXmlDocument xmldoc;
wxXmlNode *nodeRoot = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("report"));
xmldoc.SetRoot(nodeRoot);
nodeRoot->AddAttribute(wxT("version"), wxT("1.0"));
nodeRoot->AddAttribute(wxT("kind"), ctx == Context_Current ? wxT("user")
: wxT("exception"));
// add system information
wxXmlNode *nodeSystemInfo = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("system"));
if ( DoAddSystemInfo(nodeSystemInfo) )
nodeRoot->AddChild(nodeSystemInfo);
else
delete nodeSystemInfo;
// add information about the loaded modules
wxXmlNode *nodeModules = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("modules"));
if ( DoAddLoadedModules(nodeModules) )
nodeRoot->AddChild(nodeModules);
else
delete nodeModules;
// add CPU context information: this only makes sense for exceptions as our
// current context is not very interesting otherwise
if ( ctx == Context_Exception )
{
wxXmlNode *nodeContext = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("context"));
if ( DoAddExceptionInfo(nodeContext) )
nodeRoot->AddChild(nodeContext);
else
delete nodeContext;
}
// add stack traceback
#if wxUSE_STACKWALKER
wxXmlNode *nodeStack = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("stack"));
XmlStackWalker sw(nodeStack);
#if wxUSE_ON_FATAL_EXCEPTION
if ( ctx == Context_Exception )
{
sw.WalkFromException();
}
else // Context_Current
#endif // wxUSE_ON_FATAL_EXCEPTION
{
sw.Walk();
}
if ( sw.IsOk() )
nodeRoot->AddChild(nodeStack);
else
delete nodeStack;
#endif // wxUSE_STACKWALKER
// finally let the user add any extra information he needs
DoAddCustomContext(nodeRoot);
// save the entire context dump in a file
wxFileName fn(m_dir, GetReportName(), wxT("xml"));
if ( !xmldoc.Save(fn.GetFullPath()) )
return false;
AddFile(fn.GetFullName(), _("process context description"));
return true;
}
#endif // wxUSE_STACKWALKER
// ----------------------------------------------------------------------------
// adding core dump
// ----------------------------------------------------------------------------
#if wxUSE_CRASHREPORT
bool wxDebugReport::AddDump(Context ctx)
{
wxCHECK_MSG( IsOk(), false, wxT("use IsOk() first") );
wxFileName fn(m_dir, GetReportName(), wxT("dmp"));
wxCrashReport::SetFileName(fn.GetFullPath());
if ( !(ctx == Context_Exception ? wxCrashReport::Generate()
: wxCrashReport::GenerateNow()) )
return false;
AddFile(fn.GetFullName(), _("dump of the process state (binary)"));
return true;
}
#endif // wxUSE_CRASHREPORT
// ----------------------------------------------------------------------------
// report processing
// ----------------------------------------------------------------------------
bool wxDebugReport::Process()
{
if ( !GetFilesCount() )
{
wxLogError(_("Debug report generation has failed."));
return false;
}
if ( !DoProcess() )
{
wxLogError(_("Processing debug report has failed, leaving the files in \"%s\" directory."),
GetDirectory().c_str());
Reset();
return false;
}
return true;
}
bool wxDebugReport::DoProcess()
{
wxString msg(_("A debug report has been generated. It can be found in"));
msg << wxT("\n")
wxT("\t") << GetDirectory() << wxT("\n\n")
<< _("And includes the following files:\n");
wxString name, desc;
const size_t count = GetFilesCount();
for ( size_t n = 0; n < count; n++ )
{
GetFile(n, &name, &desc);
msg += wxString::Format("\t%s: %s\n", name, desc);
}
msg += _("\nPlease send this report to the program maintainer, thank you!\n");
wxLogMessage(wxT("%s"), msg.c_str());
// we have to do this or the report would be deleted, and we don't even
// have any way to ask the user if he wants to keep it from here
Reset();
return true;
}
// ============================================================================
// wxDebugReport-derived classes
// ============================================================================
#if wxUSE_ZIPSTREAM
// ----------------------------------------------------------------------------
// wxDebugReportCompress
// ----------------------------------------------------------------------------
void wxDebugReportCompress::SetCompressedFileDirectory(const wxString& dir)
{
wxASSERT_MSG( m_zipfile.empty(), "Too late: call this before Process()" );
m_zipDir = dir;
}
void wxDebugReportCompress::SetCompressedFileBaseName(const wxString& name)
{
wxASSERT_MSG( m_zipfile.empty(), "Too late: call this before Process()" );
m_zipName = name;
}
bool wxDebugReportCompress::DoProcess()
{
const size_t count = GetFilesCount();
if ( !count )
return false;
// create the compressed report file outside of the directory with the
// report files as it will be deleted by wxDebugReport dtor but we want to
// keep this one: for this we simply treat the directory name as the name
// of the file so that its last component becomes our base name
wxFileName fn(GetDirectory());
if ( !m_zipDir.empty() )
fn.SetPath(m_zipDir);
if ( !m_zipName.empty() )
fn.SetName(m_zipName);
fn.SetExt("zip");
// create the streams
wxFFileOutputStream os(fn.GetFullPath(), wxT("wb"));
wxZipOutputStream zos(os, 9);
// add all files to the ZIP one
wxString name, desc;
for ( size_t n = 0; n < count; n++ )
{
GetFile(n, &name, &desc);
wxZipEntry *ze = new wxZipEntry(name);
ze->SetComment(desc);
if ( !zos.PutNextEntry(ze) )
return false;
const wxFileName filename(GetDirectory(), name);
wxFFileInputStream is(filename.GetFullPath());
if ( !is.IsOk() || !zos.Write(is).IsOk() )
return false;
}
if ( !zos.Close() )
return false;
m_zipfile = fn.GetFullPath();
return true;
}
// ----------------------------------------------------------------------------
// wxDebugReportUpload
// ----------------------------------------------------------------------------
wxDebugReportUpload::wxDebugReportUpload(const wxString& url,
const wxString& input,
const wxString& action,
const wxString& curl)
: m_uploadURL(url),
m_inputField(input),
m_curlCmd(curl)
{
if ( m_uploadURL.Last() != wxT('/') )
m_uploadURL += wxT('/');
m_uploadURL += action;
}
bool wxDebugReportUpload::DoProcess()
{
if ( !wxDebugReportCompress::DoProcess() )
return false;
wxArrayString output, errors;
int rc = wxExecute(wxString::Format
(
wxT("%s -F %s=@\"%s\" %s"),
m_curlCmd.c_str(),
m_inputField.c_str(),
GetCompressedFileName().c_str(),
m_uploadURL.c_str()
),
output,
errors);
if ( rc == -1 )
{
wxLogError(_("Failed to execute curl, please install it in PATH."));
}
else if ( rc != 0 )
{
const size_t count = errors.GetCount();
if ( count )
{
for ( size_t n = 0; n < count; n++ )
{
wxLogWarning(wxT("%s"), errors[n].c_str());
}
}
wxLogError(_("Failed to upload the debug report (error code %d)."), rc);
}
else // rc == 0
{
if ( OnServerReply(output) )
return true;
}
return false;
}
#endif // wxUSE_ZIPSTREAM
#endif // wxUSE_DEBUGREPORT