1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-10-20 09:31:15 +02:00

Crashreporting

This commit is contained in:
Vitaly Sverchinsky
2021-05-04 21:43:19 +03:00
parent 5c05f6b421
commit e8b186a9b4
24 changed files with 1363 additions and 19 deletions

View File

@@ -0,0 +1,41 @@
#Adds a Crash Reporting dialog which may be invoked by a crashing program
set(TARGET crashreporter)
set(TARGET_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
message( STATUS "========== Configuring ${TARGET} ==========" )
set(SOURCES
PRIVATE
warning.xpm
CrashReportApp.h
CrashReportApp.cpp
)
add_executable(${TARGET})
target_sources(${TARGET} ${SOURCES})
target_link_libraries(${TARGET} breakpad::processor breakpad::sender wxwidgets::wxwidgets)
if(WIN32)
set_target_properties(${TARGET} PROPERTIES WIN32_EXECUTABLE ON)
endif()
if( CMAKE_SYSTEM_NAME MATCHES "Darwin" )
add_custom_command(
TARGET
${TARGET}
COMMAND
${CMAKE_COMMAND} -D SRC="${_EXEDIR}/crashreporter"
-D DST="${_PKGLIB}"
-D WXWIN="${_SHARED_PROXY_BASE_PATH}/$<CONFIG>"
-P ${AUDACITY_MODULE_PATH}/CopyLibs.cmake
POST_BUILD
)
elseif(UNIX)
target_compile_definitions(${TARGET} PRIVATE -DINSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${TARGET} RUNTIME)
endif()
set_target_property_all( ${TARGET} RUNTIME_OUTPUT_DIRECTORY "${_EXEDIR}" )
organize_source( "${TARGET_ROOT}" "" "${SOURCES}" )

View File

@@ -0,0 +1,464 @@
#include "CrashReportApp.h"
#include <sstream>
#include <memory>
#include <wx/cmdline.h>
#include <wx/chartype.h>
#include <wx/artprov.h>
#include <wx/filename.h>
#include <wx/stdpaths.h>
#include "google_breakpad/processor/basic_source_line_resolver.h"
#include "google_breakpad/processor/minidump_processor.h"
#include "google_breakpad/processor/process_state.h"
#include "google_breakpad/processor/minidump.h"
#include "processor/stackwalk_common.h"
#include "warning.xpm"
//Temporary solution until lib-strings is added
#define XC(msg, ctx) (wxGetTranslation(msg, wxEmptyString, ctx))
#if defined(_WIN32)
#include <locale>
#include <codecvt>
#include "client/windows/sender/crash_report_sender.h"
namespace
{
std::wstring ToPlatformString(const std::string& utf8)
{
return std::wstring_convert<std::codecvt_utf8<std::wstring::traits_type::char_type>, std::wstring::traits_type::char_type>().from_bytes(utf8);
}
bool SendMinidump(const std::string& url, const wxString& minidumpPath, const std::map<std::string, std::string>& arguments, const wxString& commentsFilePath)
{
std::map<std::wstring, std::wstring> files;
files[L"upload_file_minidump"] = minidumpPath.wc_str();
if (!commentsFilePath.empty())
{
files[wxFileName(commentsFilePath).GetFullName().wc_str()] = commentsFilePath.wc_str();
}
std::map<std::wstring, std::wstring> parameters;
for (auto& p : arguments)
{
parameters[ToPlatformString(p.first)] = ToPlatformString(p.second);
}
google_breakpad::CrashReportSender sender(L"");
auto result = sender.SendCrashReport(
ToPlatformString(url),
parameters,
files,
nullptr
);
return result == google_breakpad::RESULT_SUCCEEDED;
}
}
#else
#include "common/linux/http_upload.h"
namespace
{
bool SendMinidump(const std::string& url, const wxString& minidumpPath, const std::map<std::string, std::string>& arguments, const wxString& commentsFilePath)
{
std::map<std::string, std::string> files;
files["upload_file_minidump"] = minidumpPath.ToStdString();
if (!commentsFilePath.empty())
{
files["comments.txt"] = commentsFilePath.ToStdString();
}
std::string response, error;
bool success = google_breakpad::HTTPUpload::SendRequest(
url,
arguments,
files,
std::string(),
std::string(),
std::string(),
&response,
NULL,
&error);
return success;
}
}
#endif
IMPLEMENT_APP(CrashReportApp);
namespace
{
std::map<std::string, std::string> parseArguments(const std::string& str)
{
int TOKEN_IDENTIFIER{ 0 };
constexpr int TOKEN_EQ{ 1 };
constexpr int TOKEN_COMMA{ 2 };
constexpr int TOKEN_VALUE{ 3 };
int i = 0;
std::string key;
int state = TOKEN_COMMA;
std::map<std::string, std::string> result;
while (true)
{
if (str[i] == 0)
break;
else if (isspace(str[i]))
++i;
else if (isalpha(str[i]))
{
if (state != TOKEN_COMMA)
throw std::logic_error("malformed parameters string: unexpected identifier");
int begin = i;
while (isalnum(str[i]))
++i;
key = str.substr(begin, i - begin);
state = TOKEN_IDENTIFIER;
}
else if (str[i] == '=')
{
if (state != TOKEN_IDENTIFIER)
throw std::logic_error("malformed parameters string: unexpected '=' symbol");
++i;
state = TOKEN_EQ;
}
else if (str[i] == '\"')
{
if (state != TOKEN_EQ)
throw std::logic_error("malformed parameters string: unexpected '\"' symbol");
int begin = ++i;
while (true)
{
if (str[i] == 0)
throw std::logic_error("unterminated string literal");
else if (str[i] == '\"')
{
if (i > begin)
result[key] = str.substr(begin, i - begin);
else
result[key] = std::string();
++i;
state = TOKEN_VALUE;
break;
}
++i;
}
}
else if (str[i] == ',')
{
if (state != TOKEN_VALUE)
throw std::logic_error("malformed parameters string: unexpected ',' symbol");
state = TOKEN_COMMA;
++i;
}
else
throw std::logic_error("malformed parameters string");
}
if (state != TOKEN_VALUE)
throw std::logic_error("malformed parameters string");
return result;
}
void PrintMinidump(google_breakpad::Minidump& minidump)
{
google_breakpad::BasicSourceLineResolver resolver;
google_breakpad::MinidumpProcessor minidumpProcessor(nullptr, &resolver);
google_breakpad::MinidumpThreadList::set_max_threads(std::numeric_limits<uint32_t>::max());
google_breakpad::MinidumpMemoryList::set_max_regions(std::numeric_limits<uint32_t>::max());
google_breakpad::ProcessState processState;
if (minidumpProcessor.Process(&minidump, &processState) != google_breakpad::PROCESS_OK)
{
printf("Failed to process minidump");
}
else
{
google_breakpad::PrintProcessState(processState, true, &resolver);
}
}
wxString MakeDumpString(google_breakpad::Minidump& minidump, const wxString& temp)
{
#if _WIN32
auto stream = _wfreopen(temp.wc_str(), L"w+", stdout);
#else
auto stream = freopen(temp.utf8_str().data(), "w+", stdout);
#endif
if (stream == NULL)
throw std::runtime_error("Failed to print minidump: cannot open temp file");
PrintMinidump(minidump);
fflush(stdout);
auto length = ftell(stream);
std::vector<char> bytes(length);
fseek(stream, 0, SEEK_SET);
fread(&bytes[0], 1, length, stream);
fclose(stream);
#if _WIN32
_wremove(temp.wc_str());
#else
remove(temp.utf8_str().data());
#endif
return wxString::From8BitData(&bytes[0], bytes.size());
}
wxString MakeHeaderString(google_breakpad::Minidump& minidump)
{
if (auto exception = minidump.GetException())
{
if (auto rawException = exception->exception())
{
// i18n-hint C++ programming assertion
return wxString::Format(_("Exception code 0x%x"), rawException->exception_record.exception_code);
}
else
{
// i18n-hint C++ programming assertion
return _("Unknown exception");
}
}
else if (auto assertion = minidump.GetAssertion())
{
auto expression = assertion->expression();
if (!expression.empty())
{
return expression;
}
}
return _("Unknown error");
}
void DoShowCrashReportFrame(const wxString& header, const wxString& dump, const std::function<bool(const wxString& comment)>& onSend)
{
static constexpr int MaxUserCommentLength = 2000;
auto frame = new wxFrame(
nullptr,
wxID_ANY,
_("Problem Report for Audacity"),
wxDefaultPosition,
wxDefaultSize,
wxDEFAULT_FRAME_STYLE & ~(wxRESIZE_BORDER | wxMAXIMIZE_BOX)//disable frame resize
);
frame->SetOwnBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK));
auto mainLayout = new wxBoxSizer(wxVERTICAL);
auto headerText = new wxStaticText(frame, wxID_ANY, header);
headerText->SetFont(wxFont(wxFontInfo().Bold()));
auto headerLayout = new wxBoxSizer(wxHORIZONTAL);
headerLayout->Add(new wxStaticBitmap(frame, wxID_ANY, wxIcon(warning)));
headerLayout->AddSpacer(5);
headerLayout->Add(headerText, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
auto buttonsLayout = new wxBoxSizer(wxHORIZONTAL);
wxTextCtrl* commentCtrl = nullptr;
if (onSend != nullptr)
{
commentCtrl = new wxTextCtrl(frame, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(500, 100), wxTE_MULTILINE);
commentCtrl->SetMaxLength(MaxUserCommentLength);
}
if (onSend != nullptr)
{
auto okButton = new wxButton(frame, wxID_ANY, XC("&Don't send", "crash reporter button"));
auto sendButton = new wxButton(frame, wxID_ANY, XC("&Send", "crash reporter button"));
okButton->Bind(wxEVT_BUTTON, [frame](wxCommandEvent&)
{
frame->Close(true);
});
sendButton->Bind(wxEVT_BUTTON, [frame, commentCtrl, onSend](wxCommandEvent&)
{
if (onSend(commentCtrl->GetValue()))
{
frame->Close(true);
}
});
buttonsLayout->Add(okButton);
buttonsLayout->AddSpacer(5);
buttonsLayout->Add(sendButton);
}
else
{
auto okButton = new wxButton(frame, wxID_OK, wxT("OK"));
okButton->Bind(wxEVT_BUTTON, [frame](wxCommandEvent&)
{
frame->Close(true);
});
buttonsLayout->Add(okButton);
}
mainLayout->Add(headerLayout, wxSizerFlags().Border(wxALL));
if (onSend != nullptr)
{
mainLayout->AddSpacer(5);
mainLayout->Add(new wxStaticText(frame, wxID_ANY, _("Click \"Send\" to submit the report to Audacity. This information is collected anonymously.")), wxSizerFlags().Border(wxALL));
}
mainLayout->AddSpacer(10);
mainLayout->Add(new wxStaticText(frame, wxID_ANY, _("Problem details")), wxSizerFlags().Border(wxALL));
auto dumpTextCtrl = new wxTextCtrl(frame, wxID_ANY, dump, wxDefaultPosition, wxSize(500, 300), wxTE_RICH | wxTE_READONLY | wxTE_MULTILINE | wxTE_DONTWRAP);
dumpTextCtrl->SetFont(wxFont(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)));
dumpTextCtrl->ShowPosition(0);//scroll to top
mainLayout->Add(dumpTextCtrl, wxSizerFlags().Border(wxALL).Expand());
if (onSend != nullptr)
{
mainLayout->AddSpacer(10);
mainLayout->Add(new wxStaticText(frame, wxID_ANY, _("Comments")), wxSizerFlags().Border(wxALL));
mainLayout->Add(commentCtrl, wxSizerFlags().Border(wxALL).Expand());
}
mainLayout->Add(buttonsLayout, wxSizerFlags().Border(wxALL).Align(wxALIGN_RIGHT));
frame->SetSizerAndFit(mainLayout);
frame->Show(true);
}
}
bool CrashReportApp::OnInit()
{
if (!wxApp::OnInit())
return false;
if (mSilent)
{
if (!mURL.empty())
SendMinidump(mURL, mMinidumpPath, mArguments, wxEmptyString);
}
else
{
static std::unique_ptr<wxLocale> sLocale(new wxLocale(wxLANGUAGE_DEFAULT));
#if defined(__WXOSX__)
sLocale->AddCatalogLookupPathPrefix(wxT("../Resources"));
#elif defined(__WXMSW__)
sLocale->AddCatalogLookupPathPrefix(wxT("Languages"));
#elif defined(__WXGTK__)
sLocale->AddCatalogLookupPathPrefix(wxT("./locale"));
sLocale->AddCatalogLookupPathPrefix(wxString::Format(wxT("%s/share/locale"), wxT(INSTALL_PREFIX)));
#endif
sLocale->AddCatalog("audacity");
sLocale->AddCatalog("wxstd");
google_breakpad::Minidump minidump(mMinidumpPath.ToStdString(), false);
if (minidump.Read())
{
SetExitOnFrameDelete(true);
wxFileName temp(mMinidumpPath);
temp.SetExt("tmp");
try
{
ShowCrashReport(MakeHeaderString(minidump), MakeDumpString(minidump, temp.GetFullPath()));
}
catch (std::exception& e)
{
wxMessageBox(e.what());
return false;
}
return true;
}
}
return false;
}
void CrashReportApp::OnInitCmdLine(wxCmdLineParser& parser)
{
static const wxCmdLineEntryDesc cmdLineEntryDesc[] =
{
{ wxCMD_LINE_SWITCH, "h", "help", "Display help on the command line parameters", wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
{ wxCMD_LINE_SWITCH, "s", "silent", "Send without displaying the confirmation dialog" },
{ wxCMD_LINE_OPTION, "u", "url", "Crash report server URL", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_OPTION, "a", "args", "A set of arguments to send", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_PARAM, NULL, NULL, "path to minidump file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY },
{ wxCMD_LINE_NONE }
};
parser.SetDesc(cmdLineEntryDesc);
wxApp::OnInitCmdLine(parser);
}
bool CrashReportApp::OnCmdLineParsed(wxCmdLineParser& parser)
{
wxString url;
wxString arguments;
if (parser.Found("u", &url))
{
mURL = url.ToStdString();
}
if (parser.Found("a", &arguments))
{
try
{
mArguments = parseArguments(arguments.ToStdString());
}
catch (std::exception& e)
{
wxMessageBox(e.what());
return false;
}
}
mMinidumpPath = parser.GetParam(0);
mSilent = parser.Found("s");
return wxApp::OnCmdLineParsed(parser);
}
void CrashReportApp::ShowCrashReport(const wxString& header, const wxString& text)
{
if (mURL.empty())
{
DoShowCrashReportFrame(header, text, nullptr);
}
else
{
DoShowCrashReportFrame(header, text, [this](const wxString& comments)
{
wxString commentsFilePath;
if (!comments.empty())
{
wxFileName temp(mMinidumpPath);
temp.SetName(temp.GetName() + "-comments");
temp.SetExt("txt");
commentsFilePath = temp.GetFullPath();
wxFile file;
if (file.Open(commentsFilePath, wxFile::write))
{
file.Write(comments);
file.Close();
}
}
auto result = SendMinidump(mURL, mMinidumpPath, mArguments, commentsFilePath);
if (!commentsFilePath.empty())
wxRemoveFile(commentsFilePath);
if (!result)
{
wxMessageBox(_("Failed to send crash report"));
}
return result;
});
}
}

View File

@@ -0,0 +1,36 @@
/*!********************************************************************
*
Audacity: A Digital Audio Editor
CrashReportApp.h
Vitaly Sverchinsky
**********************************************************************/
#include <wx/wx.h>
#include <map>
#include <string>
//! Crash reporter GUI application
/*! Used to send crash reports to a remote server, or view them.
* Shows brief report content, and allows user to send report to developers.
* Reporting URL and other parameters are specified as a command line arguments.
*/
class CrashReportApp final : public wxApp
{
std::string mURL;
wxString mMinidumpPath;
std::map<std::string, std::string> mArguments;
bool mSilent{ false };
public:
bool OnInit() override;
void OnInitCmdLine(wxCmdLineParser& parser) override;
bool OnCmdLineParsed(wxCmdLineParser& parser) override;
private:
void ShowCrashReport(const wxString& header, const wxString& text);
};
DECLARE_APP(CrashReportApp);

View File

@@ -0,0 +1,138 @@
/* XPM */
static const char *warning[] = {
/* columns rows colors chars-per-pixel */
"24 24 108 2 ",
" c None",
". c black",
"X c #010100",
"o c #020200",
"O c #020201",
"+ c #070601",
"@ c #070701",
"# c #0E0C02",
"$ c #151204",
"% c #1C1804",
"& c #1F1A05",
"* c #261F00",
"= c #262000",
"- c #262005",
"; c #292305",
": c #2D2705",
"> c #312A06",
", c #403705",
"< c #413806",
"1 c #8F7300",
"2 c #957700",
"3 c #BA9500",
"4 c #B99600",
"5 c #E3B800",
"6 c #E5BA00",
"7 c #FEBB0B",
"8 c #FFBC08",
"9 c #FFBA0C",
"0 c #FFBF10",
"q c #FFBF11",
"w c #FFC000",
"e c #FFC100",
"r c #FFC202",
"t c #FFC400",
"y c #FFC500",
"u c #FFC700",
"i c #FFC307",
"p c #FFC800",
"a c #FFC900",
"s c #FECA00",
"d c #FFCB00",
"f c #FFCC00",
"g c #FFCD00",
"h c #FFCA04",
"j c #FFCF04",
"k c #FFC20B",
"l c #FFC00D",
"z c #FFC20D",
"x c #FFC00E",
"c c #FFCF0D",
"v c #FED004",
"b c #FFD104",
"n c #FFD00D",
"m c #FFD10D",
"M c #FFD20D",
"N c #FEC116",
"B c #FFCB14",
"V c #FFC61C",
"C c #FFCA1F",
"Z c #FFD215",
"A c #FFD11C",
"S c #FFD11D",
"D c #FFD31D",
"F c #FFD41D",
"G c #E6C327",
"H c #E7C527",
"J c #FFC621",
"K c #FFC525",
"L c #FFC624",
"P c #FFCC21",
"I c #FFCA25",
"U c #FFC927",
"Y c #FFCB26",
"T c #FFCC27",
"R c #FFCE27",
"E c #FFC22B",
"W c #FFC52A",
"Q c #FFC62C",
"! c #F2CE29",
"~ c #F3CF29",
"^ c #FFC828",
"/ c #FFCE28",
"( c #FFCC2A",
") c #FFCC2C",
"_ c #FFD423",
"` c #FFD623",
"' c #FFD226",
"] c #FFD527",
"[ c #F6D129",
"{ c #FFD129",
"} c #FFD229",
"| c #FFD22A",
" . c #FFD32A",
".. c #FDD42A",
"X. c #FCD52B",
"o. c #FFD42A",
"O. c #FFD52A",
"+. c #FFD52B",
"@. c #FFD62A",
"#. c #FFD62B",
"$. c #FFD72B",
"%. c #FFD828",
"&. c #FFD92B",
"*. c #FFDA2B",
"=. c #FFDA2C",
"-. c #FFC633",
";. c #FFC830",
":. c #FFCA31",
/* pixels */
" ",
" -.E ",
" / R ",
" U +.+.) ",
" ;.| +.+.| W ",
" U +.[ [ +.R ",
" ;.+.H X X G | J ",
" R +.! X X ! +.R ",
" L +.+.+.@ @ +.+.+.^ ",
" | +.+.=.# # *.+.+.| ",
" / +.+.+.*.$ $ *.+.+.+.U ",
" L ' ] ] ] *.% % *.] ] ] ' Q ",
" C ` ` ` ` ` - ; ` ` ` ` ` P ",
" J D D D D D D : > F D D D D D V ",
" B Z Z Z Z Z Z < , Z Z Z Z Z Z B ",
" k c n n n n n n n n n n n n n n c x ",
" q h j j j j j j j b b j j j j j j j h x ",
" i f f f f f f f 6 = * 5 f f f f f f f w ",
" 8 p p p p p p p p 3 X X 3 p p p f f p p p q ",
" w p p p p p p p p p 2 1 p p p p p p p p p w ",
"9 u u u u u u u u u u u u u u u u u u u u u u 7 ",
"N u u u u u u u u u u u u u u u u u u u u u u x ",
" ",
" "
};